Skip to content

Commit 428cb64

Browse files
aksOpsclaude
andcommitted
Fix Spring bean wiring: make Neo4j-dependent beans conditional for indexing profile
The indexing profile disables Neo4j (codeiq.neo4j.enabled=false) but Spring still tried to wire beans that depend on GraphStore/GraphRepository, causing startup failures for index, enrich, stats, and other non-serve commands. Changes: - Add @ConditionalOnBean(GraphStore.class) to QueryService, GraphHealthIndicator - Add @Profile("serving") to GraphController, FlowController - Make GraphStore/FlowEngine/QueryService dependencies Optional in CLI commands (QueryCommand, GraphCommand, FindCommand, FlowCommand, BundleCommand) so they gracefully degrade when Neo4j is unavailable - Keep backward-compatible package-private constructors for test compatibility All 1,227 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3900087 commit 428cb64

9 files changed

Lines changed: 87 additions & 13 deletions

File tree

src/main/java/io/github/randomcodespace/iq/api/FlowController.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.github.randomcodespace.iq.flow.FlowEngine;
44
import io.github.randomcodespace.iq.flow.FlowModels.FlowDiagram;
5+
import org.springframework.context.annotation.Profile;
56
import org.springframework.http.HttpStatus;
67
import org.springframework.http.MediaType;
78
import org.springframework.http.ResponseEntity;
@@ -21,6 +22,7 @@
2122
*/
2223
@RestController
2324
@RequestMapping("/api/flow")
25+
@Profile("serving")
2426
public class FlowController {
2527

2628
private final FlowEngine flowEngine;

src/main/java/io/github/randomcodespace/iq/api/GraphController.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.springframework.http.HttpStatus;
1313
import org.springframework.http.MediaType;
1414
import org.springframework.http.ResponseEntity;
15+
import org.springframework.context.annotation.Profile;
1516
import org.springframework.web.bind.annotation.GetMapping;
1617
import org.springframework.web.bind.annotation.PathVariable;
1718
import org.springframework.web.bind.annotation.PostMapping;
@@ -33,6 +34,7 @@
3334
*/
3435
@RestController
3536
@RequestMapping("/api")
37+
@Profile("serving")
3638
public class GraphController {
3739

3840
private final QueryService queryService;

src/main/java/io/github/randomcodespace/iq/cli/BundleCommand.java

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import io.github.randomcodespace.iq.config.CodeIqConfig;
88
import io.github.randomcodespace.iq.flow.FlowEngine;
99
import io.github.randomcodespace.iq.graph.GraphStore;
10+
import org.springframework.beans.factory.annotation.Autowired;
1011
import org.springframework.stereotype.Component;
1112
import picocli.CommandLine.Command;
1213
import picocli.CommandLine.Option;
@@ -19,6 +20,7 @@
1920
import java.time.Instant;
2021
import java.util.LinkedHashMap;
2122
import java.util.Map;
23+
import java.util.Optional;
2224
import java.util.concurrent.Callable;
2325
import java.util.stream.Stream;
2426
import java.util.zip.ZipEntry;
@@ -46,8 +48,18 @@ public class BundleCommand implements Callable<Integer> {
4648
private final GraphStore graphStore;
4749
private final FlowEngine flowEngine;
4850

51+
@Autowired
4952
public BundleCommand(CodeIqConfig config, Analyzer analyzer,
50-
GraphStore graphStore, FlowEngine flowEngine) {
53+
Optional<GraphStore> graphStore, Optional<FlowEngine> flowEngine) {
54+
this.config = config;
55+
this.analyzer = analyzer;
56+
this.graphStore = graphStore.orElse(null);
57+
this.flowEngine = flowEngine.orElse(null);
58+
}
59+
60+
/** Convenience constructor for tests. */
61+
BundleCommand(CodeIqConfig config, Analyzer analyzer,
62+
GraphStore graphStore, FlowEngine flowEngine) {
5163
this.config = config;
5264
this.analyzer = analyzer;
5365
this.graphStore = graphStore;
@@ -88,12 +100,16 @@ public Integer call() {
88100
nodeCount = analysisResult.nodeCount();
89101
edgeCount = analysisResult.edgeCount();
90102
filesAnalyzed = analysisResult.totalFiles();
91-
} else {
103+
} else if (graphStore != null) {
92104
nodeCount = graphStore.count();
93105
edgeCount = graphStore.findAll().stream()
94106
.mapToLong(n -> n.getEdges().size())
95107
.sum();
96108
filesAnalyzed = 0;
109+
} else {
110+
nodeCount = 0;
111+
edgeCount = 0;
112+
filesAnalyzed = 0;
97113
}
98114

99115
// 1. Write manifest.json (matching Python format)
@@ -119,13 +135,15 @@ public Integer call() {
119135
}
120136

121137
// 3. Generate interactive flow HTML
122-
try {
123-
String flowHtml = flowEngine.renderInteractive(projectName);
124-
zos.putNextEntry(new ZipEntry("flow.html"));
125-
zos.write(flowHtml.getBytes(StandardCharsets.UTF_8));
126-
zos.closeEntry();
127-
} catch (Exception e) {
128-
CliOutput.warn("Could not generate flow.html: " + e.getMessage());
138+
if (flowEngine != null) {
139+
try {
140+
String flowHtml = flowEngine.renderInteractive(projectName);
141+
zos.putNextEntry(new ZipEntry("flow.html"));
142+
zos.write(flowHtml.getBytes(StandardCharsets.UTF_8));
143+
zos.closeEntry();
144+
} catch (Exception e) {
145+
CliOutput.warn("Could not generate flow.html: " + e.getMessage());
146+
}
129147
}
130148

131149
// 4. Bundle source files

src/main/java/io/github/randomcodespace/iq/cli/FindCommand.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
import io.github.randomcodespace.iq.graph.GraphStore;
44
import io.github.randomcodespace.iq.model.CodeNode;
55
import io.github.randomcodespace.iq.model.NodeKind;
6+
import org.springframework.beans.factory.annotation.Autowired;
67
import org.springframework.stereotype.Component;
78
import picocli.CommandLine.Command;
89
import picocli.CommandLine.Option;
910
import picocli.CommandLine.Parameters;
1011

1112
import java.nio.file.Path;
1213
import java.util.List;
14+
import java.util.Optional;
1315
import java.util.concurrent.Callable;
1416

1517
/**
@@ -35,12 +37,22 @@ public class FindCommand implements Callable<Integer> {
3537

3638
private final GraphStore graphStore;
3739

38-
public FindCommand(GraphStore graphStore) {
40+
@Autowired
41+
public FindCommand(Optional<GraphStore> graphStore) {
42+
this.graphStore = graphStore.orElse(null);
43+
}
44+
45+
/** Convenience constructor for tests. */
46+
FindCommand(GraphStore graphStore) {
3947
this.graphStore = graphStore;
4048
}
4149

4250
@Override
4351
public Integer call() {
52+
if (graphStore == null) {
53+
CliOutput.error("Find queries require Neo4j. Run 'code-iq enrich' first, then 'code-iq serve'.");
54+
return 1;
55+
}
4456
NodeKind kind = resolveKind(what);
4557
if (kind == null) {
4658
CliOutput.error("Unknown find target: " + what);

src/main/java/io/github/randomcodespace/iq/cli/FlowCommand.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.github.randomcodespace.iq.flow.FlowEngine;
44
import io.github.randomcodespace.iq.flow.FlowModels.FlowDiagram;
5+
import org.springframework.beans.factory.annotation.Autowired;
56
import org.springframework.stereotype.Component;
67
import picocli.CommandLine.Command;
78
import picocli.CommandLine.Option;
@@ -11,6 +12,7 @@
1112
import java.nio.charset.StandardCharsets;
1213
import java.nio.file.Files;
1314
import java.nio.file.Path;
15+
import java.util.Optional;
1416
import java.util.concurrent.Callable;
1517

1618
/**
@@ -38,12 +40,22 @@ public class FlowCommand implements Callable<Integer> {
3840

3941
private final FlowEngine flowEngine;
4042

41-
public FlowCommand(FlowEngine flowEngine) {
43+
@Autowired
44+
public FlowCommand(Optional<FlowEngine> flowEngine) {
45+
this.flowEngine = flowEngine.orElse(null);
46+
}
47+
48+
/** Convenience constructor for tests. */
49+
FlowCommand(FlowEngine flowEngine) {
4250
this.flowEngine = flowEngine;
4351
}
4452

4553
@Override
4654
public Integer call() {
55+
if (flowEngine == null) {
56+
CliOutput.error("Flow diagrams require Neo4j. Run 'code-iq enrich' first, then 'code-iq serve'.");
57+
return 1;
58+
}
4759
try {
4860
String content;
4961

src/main/java/io/github/randomcodespace/iq/cli/GraphCommand.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.github.randomcodespace.iq.graph.GraphStore;
44
import io.github.randomcodespace.iq.model.CodeNode;
5+
import org.springframework.beans.factory.annotation.Autowired;
56
import org.springframework.stereotype.Component;
67
import picocli.CommandLine.Command;
78
import picocli.CommandLine.Option;
@@ -16,6 +17,7 @@
1617
import java.util.LinkedHashMap;
1718
import java.util.List;
1819
import java.util.Map;
20+
import java.util.Optional;
1921
import java.util.concurrent.Callable;
2022
import java.util.stream.Collectors;
2123

@@ -50,12 +52,22 @@ public class GraphCommand implements Callable<Integer> {
5052

5153
private final GraphStore graphStore;
5254

53-
public GraphCommand(GraphStore graphStore) {
55+
@Autowired
56+
public GraphCommand(Optional<GraphStore> graphStore) {
57+
this.graphStore = graphStore.orElse(null);
58+
}
59+
60+
/** Convenience constructor for tests. */
61+
GraphCommand(GraphStore graphStore) {
5462
this.graphStore = graphStore;
5563
}
5664

5765
@Override
5866
public Integer call() {
67+
if (graphStore == null) {
68+
CliOutput.error("Graph export requires Neo4j. Run 'code-iq enrich' first, then 'code-iq serve'.");
69+
return 1;
70+
}
5971
List<CodeNode> nodes;
6072
if (focus != null && !focus.isBlank()) {
6173
nodes = graphStore.findEgoGraph(focus, hops);

src/main/java/io/github/randomcodespace/iq/cli/QueryCommand.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.randomcodespace.iq.cli;
22

33
import io.github.randomcodespace.iq.query.QueryService;
4+
import org.springframework.beans.factory.annotation.Autowired;
45
import org.springframework.stereotype.Component;
56
import picocli.CommandLine.Command;
67
import picocli.CommandLine.Option;
@@ -9,6 +10,7 @@
910
import java.nio.file.Path;
1011
import java.util.List;
1112
import java.util.Map;
13+
import java.util.Optional;
1214
import java.util.concurrent.Callable;
1315

1416
/**
@@ -48,12 +50,22 @@ public class QueryCommand implements Callable<Integer> {
4850

4951
private final QueryService queryService;
5052

51-
public QueryCommand(QueryService queryService) {
53+
@Autowired
54+
public QueryCommand(Optional<QueryService> queryService) {
55+
this.queryService = queryService.orElse(null);
56+
}
57+
58+
/** Convenience constructor for tests. */
59+
QueryCommand(QueryService queryService) {
5260
this.queryService = queryService;
5361
}
5462

5563
@Override
5664
public Integer call() {
65+
if (queryService == null) {
66+
CliOutput.error("Graph queries require Neo4j. Run 'code-iq enrich' first, then 'code-iq serve'.");
67+
return 1;
68+
}
5769
if (consumersOf != null) {
5870
return printResult("Consumers of " + consumersOf, queryService.consumersOf(consumersOf));
5971
}

src/main/java/io/github/randomcodespace/iq/health/GraphHealthIndicator.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.randomcodespace.iq.health;
22

33
import io.github.randomcodespace.iq.graph.GraphStore;
4+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
45
import org.springframework.boot.health.contributor.Health;
56
import org.springframework.boot.health.contributor.HealthIndicator;
67
import org.springframework.stereotype.Component;
@@ -10,6 +11,7 @@
1011
* has been populated with nodes.
1112
*/
1213
@Component
14+
@ConditionalOnBean(GraphStore.class)
1315
public class GraphHealthIndicator implements HealthIndicator {
1416

1517
private final GraphStore graphStore;

src/main/java/io/github/randomcodespace/iq/query/QueryService.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import io.github.randomcodespace.iq.graph.GraphStore;
55
import io.github.randomcodespace.iq.model.CodeEdge;
66
import io.github.randomcodespace.iq.model.CodeNode;
7+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
78
import org.springframework.cache.annotation.Cacheable;
89
import org.springframework.stereotype.Service;
910

@@ -18,6 +19,7 @@
1819
* All methods return simple Map/List structures for JSON serialization.
1920
*/
2021
@Service
22+
@ConditionalOnBean(GraphStore.class)
2123
public class QueryService {
2224

2325
private final GraphStore graphStore;

0 commit comments

Comments
 (0)