osscodeiq 0.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- osscodeiq/__init__.py +0 -0
- osscodeiq/analyzer.py +467 -0
- osscodeiq/cache/__init__.py +0 -0
- osscodeiq/cache/hasher.py +23 -0
- osscodeiq/cache/store.py +300 -0
- osscodeiq/classifiers/__init__.py +0 -0
- osscodeiq/classifiers/layer_classifier.py +69 -0
- osscodeiq/cli.py +721 -0
- osscodeiq/config.py +113 -0
- osscodeiq/detectors/__init__.py +0 -0
- osscodeiq/detectors/auth/__init__.py +0 -0
- osscodeiq/detectors/auth/certificate_auth.py +139 -0
- osscodeiq/detectors/auth/ldap_auth.py +89 -0
- osscodeiq/detectors/auth/session_header_auth.py +120 -0
- osscodeiq/detectors/base.py +41 -0
- osscodeiq/detectors/config/__init__.py +0 -0
- osscodeiq/detectors/config/batch_structure.py +128 -0
- osscodeiq/detectors/config/cloudformation.py +183 -0
- osscodeiq/detectors/config/docker_compose.py +179 -0
- osscodeiq/detectors/config/github_actions.py +150 -0
- osscodeiq/detectors/config/gitlab_ci.py +216 -0
- osscodeiq/detectors/config/helm_chart.py +187 -0
- osscodeiq/detectors/config/ini_structure.py +101 -0
- osscodeiq/detectors/config/json_structure.py +72 -0
- osscodeiq/detectors/config/kubernetes.py +305 -0
- osscodeiq/detectors/config/kubernetes_rbac.py +212 -0
- osscodeiq/detectors/config/openapi.py +194 -0
- osscodeiq/detectors/config/package_json.py +99 -0
- osscodeiq/detectors/config/properties_detector.py +108 -0
- osscodeiq/detectors/config/pyproject_toml.py +169 -0
- osscodeiq/detectors/config/sql_structure.py +155 -0
- osscodeiq/detectors/config/toml_structure.py +93 -0
- osscodeiq/detectors/config/tsconfig_json.py +105 -0
- osscodeiq/detectors/config/yaml_structure.py +82 -0
- osscodeiq/detectors/cpp/__init__.py +0 -0
- osscodeiq/detectors/cpp/cpp_structures.py +192 -0
- osscodeiq/detectors/csharp/__init__.py +0 -0
- osscodeiq/detectors/csharp/csharp_efcore.py +184 -0
- osscodeiq/detectors/csharp/csharp_minimal_apis.py +156 -0
- osscodeiq/detectors/csharp/csharp_structures.py +317 -0
- osscodeiq/detectors/docs/__init__.py +0 -0
- osscodeiq/detectors/docs/markdown_structure.py +117 -0
- osscodeiq/detectors/frontend/__init__.py +0 -0
- osscodeiq/detectors/frontend/angular_components.py +177 -0
- osscodeiq/detectors/frontend/frontend_routes.py +259 -0
- osscodeiq/detectors/frontend/react_components.py +148 -0
- osscodeiq/detectors/frontend/svelte_components.py +84 -0
- osscodeiq/detectors/frontend/vue_components.py +150 -0
- osscodeiq/detectors/generic/__init__.py +1 -0
- osscodeiq/detectors/generic/imports_detector.py +413 -0
- osscodeiq/detectors/go/__init__.py +0 -0
- osscodeiq/detectors/go/go_orm.py +202 -0
- osscodeiq/detectors/go/go_structures.py +162 -0
- osscodeiq/detectors/go/go_web.py +157 -0
- osscodeiq/detectors/iac/__init__.py +0 -0
- osscodeiq/detectors/iac/bicep.py +135 -0
- osscodeiq/detectors/iac/dockerfile.py +182 -0
- osscodeiq/detectors/iac/terraform.py +188 -0
- osscodeiq/detectors/java/__init__.py +0 -0
- osscodeiq/detectors/java/azure_functions.py +424 -0
- osscodeiq/detectors/java/azure_messaging.py +350 -0
- osscodeiq/detectors/java/class_hierarchy.py +349 -0
- osscodeiq/detectors/java/config_def.py +82 -0
- osscodeiq/detectors/java/cosmos_db.py +105 -0
- osscodeiq/detectors/java/graphql_resolver.py +188 -0
- osscodeiq/detectors/java/grpc_service.py +142 -0
- osscodeiq/detectors/java/ibm_mq.py +178 -0
- osscodeiq/detectors/java/jaxrs.py +160 -0
- osscodeiq/detectors/java/jdbc.py +196 -0
- osscodeiq/detectors/java/jms.py +116 -0
- osscodeiq/detectors/java/jpa_entity.py +143 -0
- osscodeiq/detectors/java/kafka.py +113 -0
- osscodeiq/detectors/java/kafka_protocol.py +70 -0
- osscodeiq/detectors/java/micronaut.py +248 -0
- osscodeiq/detectors/java/module_deps.py +191 -0
- osscodeiq/detectors/java/public_api.py +206 -0
- osscodeiq/detectors/java/quarkus.py +176 -0
- osscodeiq/detectors/java/rabbitmq.py +150 -0
- osscodeiq/detectors/java/raw_sql.py +136 -0
- osscodeiq/detectors/java/repository.py +131 -0
- osscodeiq/detectors/java/rmi.py +129 -0
- osscodeiq/detectors/java/spring_events.py +117 -0
- osscodeiq/detectors/java/spring_rest.py +168 -0
- osscodeiq/detectors/java/spring_security.py +212 -0
- osscodeiq/detectors/java/tibco_ems.py +193 -0
- osscodeiq/detectors/java/websocket.py +188 -0
- osscodeiq/detectors/kotlin/__init__.py +0 -0
- osscodeiq/detectors/kotlin/kotlin_structures.py +124 -0
- osscodeiq/detectors/kotlin/ktor_routes.py +163 -0
- osscodeiq/detectors/proto/__init__.py +0 -0
- osscodeiq/detectors/proto/proto_structure.py +153 -0
- osscodeiq/detectors/python/__init__.py +0 -0
- osscodeiq/detectors/python/celery_tasks.py +88 -0
- osscodeiq/detectors/python/django_auth.py +132 -0
- osscodeiq/detectors/python/django_models.py +157 -0
- osscodeiq/detectors/python/django_views.py +74 -0
- osscodeiq/detectors/python/fastapi_auth.py +143 -0
- osscodeiq/detectors/python/fastapi_routes.py +68 -0
- osscodeiq/detectors/python/flask_routes.py +67 -0
- osscodeiq/detectors/python/kafka_python.py +175 -0
- osscodeiq/detectors/python/pydantic_models.py +115 -0
- osscodeiq/detectors/python/python_structures.py +234 -0
- osscodeiq/detectors/python/sqlalchemy_models.py +82 -0
- osscodeiq/detectors/registry.py +100 -0
- osscodeiq/detectors/rust/__init__.py +0 -0
- osscodeiq/detectors/rust/actix_web.py +234 -0
- osscodeiq/detectors/rust/rust_structures.py +174 -0
- osscodeiq/detectors/scala/__init__.py +0 -0
- osscodeiq/detectors/scala/scala_structures.py +128 -0
- osscodeiq/detectors/shell/__init__.py +0 -0
- osscodeiq/detectors/shell/bash_detector.py +127 -0
- osscodeiq/detectors/shell/powershell_detector.py +118 -0
- osscodeiq/detectors/typescript/__init__.py +0 -0
- osscodeiq/detectors/typescript/express_routes.py +55 -0
- osscodeiq/detectors/typescript/fastify_routes.py +156 -0
- osscodeiq/detectors/typescript/graphql_resolvers.py +100 -0
- osscodeiq/detectors/typescript/kafka_js.py +164 -0
- osscodeiq/detectors/typescript/mongoose_orm.py +151 -0
- osscodeiq/detectors/typescript/nestjs_controllers.py +99 -0
- osscodeiq/detectors/typescript/nestjs_guards.py +138 -0
- osscodeiq/detectors/typescript/passport_jwt.py +133 -0
- osscodeiq/detectors/typescript/prisma_orm.py +96 -0
- osscodeiq/detectors/typescript/remix_routes.py +160 -0
- osscodeiq/detectors/typescript/sequelize_orm.py +136 -0
- osscodeiq/detectors/typescript/typeorm_entities.py +86 -0
- osscodeiq/detectors/typescript/typescript_structures.py +185 -0
- osscodeiq/detectors/utils.py +49 -0
- osscodeiq/discovery/__init__.py +11 -0
- osscodeiq/discovery/change_detector.py +97 -0
- osscodeiq/discovery/file_discovery.py +342 -0
- osscodeiq/flow/__init__.py +0 -0
- osscodeiq/flow/engine.py +78 -0
- osscodeiq/flow/models.py +72 -0
- osscodeiq/flow/renderer.py +127 -0
- osscodeiq/flow/templates/interactive.html +252 -0
- osscodeiq/flow/vendor/cytoscape-dagre.min.js +8 -0
- osscodeiq/flow/vendor/cytoscape.min.js +32 -0
- osscodeiq/flow/vendor/dagre.min.js +3809 -0
- osscodeiq/flow/views.py +357 -0
- osscodeiq/graph/__init__.py +0 -0
- osscodeiq/graph/backend.py +52 -0
- osscodeiq/graph/backends/__init__.py +23 -0
- osscodeiq/graph/backends/kuzu.py +576 -0
- osscodeiq/graph/backends/networkx.py +135 -0
- osscodeiq/graph/backends/sqlite_backend.py +406 -0
- osscodeiq/graph/builder.py +297 -0
- osscodeiq/graph/query.py +228 -0
- osscodeiq/graph/store.py +183 -0
- osscodeiq/graph/views.py +231 -0
- osscodeiq/models/__init__.py +17 -0
- osscodeiq/models/graph.py +116 -0
- osscodeiq/output/__init__.py +0 -0
- osscodeiq/output/dot.py +171 -0
- osscodeiq/output/mermaid.py +160 -0
- osscodeiq/output/safety.py +58 -0
- osscodeiq/output/serializers.py +42 -0
- osscodeiq/parsing/__init__.py +5 -0
- osscodeiq/parsing/languages/__init__.py +0 -0
- osscodeiq/parsing/languages/base.py +23 -0
- osscodeiq/parsing/languages/java.py +68 -0
- osscodeiq/parsing/languages/python.py +57 -0
- osscodeiq/parsing/languages/typescript.py +95 -0
- osscodeiq/parsing/parser_manager.py +125 -0
- osscodeiq/parsing/structured/__init__.py +0 -0
- osscodeiq/parsing/structured/gradle_parser.py +78 -0
- osscodeiq/parsing/structured/json_parser.py +24 -0
- osscodeiq/parsing/structured/properties_parser.py +56 -0
- osscodeiq/parsing/structured/sql_parser.py +54 -0
- osscodeiq/parsing/structured/xml_parser.py +148 -0
- osscodeiq/parsing/structured/yaml_parser.py +38 -0
- osscodeiq/server/__init__.py +7 -0
- osscodeiq/server/app.py +53 -0
- osscodeiq/server/mcp_server.py +174 -0
- osscodeiq/server/middleware.py +16 -0
- osscodeiq/server/routes.py +184 -0
- osscodeiq/server/service.py +445 -0
- osscodeiq/server/templates/welcome.html +56 -0
- osscodeiq-0.0.0.dist-info/METADATA +30 -0
- osscodeiq-0.0.0.dist-info/RECORD +183 -0
- osscodeiq-0.0.0.dist-info/WHEEL +5 -0
- osscodeiq-0.0.0.dist-info/entry_points.txt +2 -0
- osscodeiq-0.0.0.dist-info/licenses/LICENSE +21 -0
- osscodeiq-0.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""ConfigDef detector for Java source files using Kafka's ConfigDef.define() pattern."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from osscodeiq.detectors.base import DetectorContext, DetectorResult
|
|
8
|
+
from osscodeiq.detectors.utils import decode_text
|
|
9
|
+
from osscodeiq.models.graph import (
|
|
10
|
+
EdgeKind,
|
|
11
|
+
GraphEdge,
|
|
12
|
+
GraphNode,
|
|
13
|
+
NodeKind,
|
|
14
|
+
SourceLocation,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
_CLASS_RE = re.compile(r"(?:public\s+)?class\s+(\w+)")
|
|
18
|
+
_DEFINE_RE = re.compile(r'\.define\s*\(\s*"([^"]+)"')
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ConfigDefDetector:
|
|
22
|
+
"""Detects Kafka ConfigDef.define() configuration definitions."""
|
|
23
|
+
|
|
24
|
+
name: str = "config_def"
|
|
25
|
+
supported_languages: tuple[str, ...] = ("java",)
|
|
26
|
+
|
|
27
|
+
def detect(self, ctx: DetectorContext) -> DetectorResult:
|
|
28
|
+
result = DetectorResult()
|
|
29
|
+
text = decode_text(ctx)
|
|
30
|
+
|
|
31
|
+
if "ConfigDef" not in text:
|
|
32
|
+
return result
|
|
33
|
+
|
|
34
|
+
lines = text.split("\n")
|
|
35
|
+
|
|
36
|
+
# Find class name
|
|
37
|
+
class_name: str | None = None
|
|
38
|
+
for line in lines:
|
|
39
|
+
cm = _CLASS_RE.search(line)
|
|
40
|
+
if cm:
|
|
41
|
+
class_name = cm.group(1)
|
|
42
|
+
break
|
|
43
|
+
|
|
44
|
+
if not class_name:
|
|
45
|
+
return result
|
|
46
|
+
|
|
47
|
+
class_node_id = f"{ctx.file_path}:{class_name}"
|
|
48
|
+
seen_keys: set[str] = set()
|
|
49
|
+
|
|
50
|
+
# Find all .define("config.key") calls
|
|
51
|
+
for i, line in enumerate(lines):
|
|
52
|
+
m = _DEFINE_RE.search(line)
|
|
53
|
+
if not m:
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
config_key = m.group(1)
|
|
57
|
+
if config_key in seen_keys:
|
|
58
|
+
continue
|
|
59
|
+
seen_keys.add(config_key)
|
|
60
|
+
|
|
61
|
+
node_id = f"config:{config_key}"
|
|
62
|
+
|
|
63
|
+
result.nodes.append(GraphNode(
|
|
64
|
+
id=node_id,
|
|
65
|
+
kind=NodeKind.CONFIG_DEFINITION,
|
|
66
|
+
label=config_key,
|
|
67
|
+
location=SourceLocation(
|
|
68
|
+
file_path=ctx.file_path,
|
|
69
|
+
line_start=i + 1,
|
|
70
|
+
),
|
|
71
|
+
properties={"config_key": config_key},
|
|
72
|
+
))
|
|
73
|
+
|
|
74
|
+
result.edges.append(GraphEdge(
|
|
75
|
+
source=class_node_id,
|
|
76
|
+
target=node_id,
|
|
77
|
+
kind=EdgeKind.READS_CONFIG,
|
|
78
|
+
label=f"{class_name} reads config {config_key}",
|
|
79
|
+
properties={"config_key": config_key},
|
|
80
|
+
))
|
|
81
|
+
|
|
82
|
+
return result
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Azure Cosmos DB client usage detector for Java and TypeScript/JavaScript."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from osscodeiq.detectors.base import DetectorContext, DetectorResult
|
|
9
|
+
from osscodeiq.detectors.utils import decode_text
|
|
10
|
+
from osscodeiq.models.graph import (
|
|
11
|
+
EdgeKind,
|
|
12
|
+
GraphEdge,
|
|
13
|
+
GraphNode,
|
|
14
|
+
NodeKind,
|
|
15
|
+
SourceLocation,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
_CLASS_RE = re.compile(r"(?:public\s+)?class\s+(\w+)")
|
|
19
|
+
_DATABASE_RE = re.compile(r'\.(?:getDatabase|database)\s*\(\s*"([^"]+)"')
|
|
20
|
+
_CONTAINER_RE = re.compile(r'\.(?:getContainer|container)\s*\(\s*"([^"]+)"')
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CosmosDbDetector:
|
|
24
|
+
"""Detects Azure Cosmos DB client usage patterns."""
|
|
25
|
+
|
|
26
|
+
name: str = "cosmos_db"
|
|
27
|
+
supported_languages: tuple[str, ...] = ("java", "typescript", "javascript")
|
|
28
|
+
|
|
29
|
+
def detect(self, ctx: DetectorContext) -> DetectorResult:
|
|
30
|
+
result = DetectorResult()
|
|
31
|
+
text = decode_text(ctx)
|
|
32
|
+
|
|
33
|
+
# Fast bail
|
|
34
|
+
if (
|
|
35
|
+
"CosmosClient" not in text
|
|
36
|
+
and "CosmosDatabase" not in text
|
|
37
|
+
and "CosmosContainer" not in text
|
|
38
|
+
and "@azure/cosmos" not in text
|
|
39
|
+
):
|
|
40
|
+
return result
|
|
41
|
+
|
|
42
|
+
lines = text.split("\n")
|
|
43
|
+
|
|
44
|
+
# Find class name (for Java) or use file path as source
|
|
45
|
+
class_name: str | None = None
|
|
46
|
+
for line in lines:
|
|
47
|
+
cm = _CLASS_RE.search(line)
|
|
48
|
+
if cm:
|
|
49
|
+
class_name = cm.group(1)
|
|
50
|
+
break
|
|
51
|
+
|
|
52
|
+
source_node_id = f"{ctx.file_path}:{class_name}" if class_name else ctx.file_path
|
|
53
|
+
seen_databases: set[str] = set()
|
|
54
|
+
seen_containers: set[str] = set()
|
|
55
|
+
|
|
56
|
+
for i, line in enumerate(lines):
|
|
57
|
+
# Detect database references
|
|
58
|
+
for db_match in _DATABASE_RE.finditer(line):
|
|
59
|
+
db_name = db_match.group(1)
|
|
60
|
+
if db_name not in seen_databases:
|
|
61
|
+
seen_databases.add(db_name)
|
|
62
|
+
db_node_id = f"azure:cosmos:db:{db_name}"
|
|
63
|
+
result.nodes.append(GraphNode(
|
|
64
|
+
id=db_node_id,
|
|
65
|
+
kind=NodeKind.AZURE_RESOURCE,
|
|
66
|
+
label=f"cosmosdb:{db_name}",
|
|
67
|
+
module=ctx.module_name,
|
|
68
|
+
location=SourceLocation(file_path=ctx.file_path, line_start=i + 1),
|
|
69
|
+
properties={
|
|
70
|
+
"cosmos_type": "database",
|
|
71
|
+
"resource_name": db_name,
|
|
72
|
+
},
|
|
73
|
+
))
|
|
74
|
+
result.edges.append(GraphEdge(
|
|
75
|
+
source=source_node_id,
|
|
76
|
+
target=db_node_id,
|
|
77
|
+
kind=EdgeKind.CONNECTS_TO,
|
|
78
|
+
label=f"{class_name or ctx.file_path} connects to cosmosdb:{db_name}",
|
|
79
|
+
))
|
|
80
|
+
|
|
81
|
+
# Detect container references
|
|
82
|
+
for cont_match in _CONTAINER_RE.finditer(line):
|
|
83
|
+
container_name = cont_match.group(1)
|
|
84
|
+
if container_name not in seen_containers:
|
|
85
|
+
seen_containers.add(container_name)
|
|
86
|
+
container_node_id = f"azure:cosmos:container:{container_name}"
|
|
87
|
+
result.nodes.append(GraphNode(
|
|
88
|
+
id=container_node_id,
|
|
89
|
+
kind=NodeKind.AZURE_RESOURCE,
|
|
90
|
+
label=f"cosmosdb-container:{container_name}",
|
|
91
|
+
module=ctx.module_name,
|
|
92
|
+
location=SourceLocation(file_path=ctx.file_path, line_start=i + 1),
|
|
93
|
+
properties={
|
|
94
|
+
"cosmos_type": "container",
|
|
95
|
+
"resource_name": container_name,
|
|
96
|
+
},
|
|
97
|
+
))
|
|
98
|
+
result.edges.append(GraphEdge(
|
|
99
|
+
source=source_node_id,
|
|
100
|
+
target=container_node_id,
|
|
101
|
+
kind=EdgeKind.CONNECTS_TO,
|
|
102
|
+
label=f"{class_name or ctx.file_path} connects to container:{container_name}",
|
|
103
|
+
))
|
|
104
|
+
|
|
105
|
+
return result
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""GraphQL resolver detector for Java source files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from osscodeiq.detectors.base import DetectorContext, DetectorResult
|
|
8
|
+
from osscodeiq.detectors.utils import decode_text
|
|
9
|
+
from osscodeiq.models.graph import (
|
|
10
|
+
EdgeKind,
|
|
11
|
+
GraphEdge,
|
|
12
|
+
GraphNode,
|
|
13
|
+
NodeKind,
|
|
14
|
+
SourceLocation,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
_CLASS_RE = re.compile(r"(?:public\s+)?class\s+(\w+)")
|
|
18
|
+
|
|
19
|
+
# Spring GraphQL annotations
|
|
20
|
+
_QUERY_MAPPING_RE = re.compile(r"@QueryMapping(?:\s*\(\s*(?:name\s*=\s*)?\"([^\"]+)\"\s*\))?")
|
|
21
|
+
_MUTATION_MAPPING_RE = re.compile(r"@MutationMapping(?:\s*\(\s*(?:name\s*=\s*)?\"([^\"]+)\"\s*\))?")
|
|
22
|
+
_SUBSCRIPTION_MAPPING_RE = re.compile(r"@SubscriptionMapping(?:\s*\(\s*(?:name\s*=\s*)?\"([^\"]+)\"\s*\))?")
|
|
23
|
+
_SCHEMA_MAPPING_RE = re.compile(r'@SchemaMapping\s*\(\s*(?:typeName\s*=\s*"([^"]+)")?')
|
|
24
|
+
_BATCH_MAPPING_RE = re.compile(r"@BatchMapping(?:\s*\(\s*(?:field\s*=\s*)?\"([^\"]+)\"\s*\))?")
|
|
25
|
+
|
|
26
|
+
# DGS framework annotations
|
|
27
|
+
_DGS_QUERY_RE = re.compile(r"@DgsQuery(?:\s*\(\s*field\s*=\s*\"([^\"]+)\"\s*\))?")
|
|
28
|
+
_DGS_MUTATION_RE = re.compile(r"@DgsMutation(?:\s*\(\s*field\s*=\s*\"([^\"]+)\"\s*\))?")
|
|
29
|
+
_DGS_SUBSCRIPTION_RE = re.compile(r"@DgsSubscription(?:\s*\(\s*field\s*=\s*\"([^\"]+)\"\s*\))?")
|
|
30
|
+
_DGS_DATA_RE = re.compile(r"@DgsData\s*\(\s*parentType\s*=\s*\"([^\"]+)\"(?:\s*,\s*field\s*=\s*\"([^\"]+)\")?")
|
|
31
|
+
|
|
32
|
+
_METHOD_RE = re.compile(r"(?:public|protected|private)?\s*(?:[\w<>\[\],?\s]+)\s+(\w+)\s*\(")
|
|
33
|
+
|
|
34
|
+
# Controller annotation for GraphQL controllers
|
|
35
|
+
_CONTROLLER_RE = re.compile(r"@(?:Controller|DgsComponent)")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class GraphqlResolverDetector:
|
|
39
|
+
"""Detects GraphQL resolvers from Spring GraphQL and DGS framework annotations."""
|
|
40
|
+
|
|
41
|
+
name: str = "graphql_resolver"
|
|
42
|
+
supported_languages: tuple[str, ...] = ("java",)
|
|
43
|
+
|
|
44
|
+
def detect(self, ctx: DetectorContext) -> DetectorResult:
|
|
45
|
+
result = DetectorResult()
|
|
46
|
+
text = decode_text(ctx)
|
|
47
|
+
lines = text.split("\n")
|
|
48
|
+
|
|
49
|
+
# Quick check for relevant annotations
|
|
50
|
+
if not any(kw in text for kw in (
|
|
51
|
+
"@QueryMapping", "@MutationMapping", "@SubscriptionMapping",
|
|
52
|
+
"@SchemaMapping", "@BatchMapping",
|
|
53
|
+
"@DgsQuery", "@DgsMutation", "@DgsSubscription", "@DgsData",
|
|
54
|
+
)):
|
|
55
|
+
return result
|
|
56
|
+
|
|
57
|
+
# Find class name
|
|
58
|
+
class_name: str | None = None
|
|
59
|
+
for line in lines:
|
|
60
|
+
cm = _CLASS_RE.search(line)
|
|
61
|
+
if cm:
|
|
62
|
+
class_name = cm.group(1)
|
|
63
|
+
break
|
|
64
|
+
|
|
65
|
+
if not class_name:
|
|
66
|
+
return result
|
|
67
|
+
|
|
68
|
+
class_node_id = f"{ctx.file_path}:{class_name}"
|
|
69
|
+
|
|
70
|
+
# All annotation patterns to scan
|
|
71
|
+
patterns = [
|
|
72
|
+
(_QUERY_MAPPING_RE, "Query"),
|
|
73
|
+
(_MUTATION_MAPPING_RE, "Mutation"),
|
|
74
|
+
(_SUBSCRIPTION_MAPPING_RE, "Subscription"),
|
|
75
|
+
(_DGS_QUERY_RE, "Query"),
|
|
76
|
+
(_DGS_MUTATION_RE, "Mutation"),
|
|
77
|
+
(_DGS_SUBSCRIPTION_RE, "Subscription"),
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
for i, line in enumerate(lines):
|
|
81
|
+
for pattern, gql_type in patterns:
|
|
82
|
+
m = pattern.search(line)
|
|
83
|
+
if not m:
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
field_name = m.group(1) if m.lastindex and m.group(1) else None
|
|
87
|
+
# Resolve field name from method if not in annotation
|
|
88
|
+
if not field_name:
|
|
89
|
+
for k in range(i + 1, min(i + 4, len(lines))):
|
|
90
|
+
mm = _METHOD_RE.search(lines[k])
|
|
91
|
+
if mm:
|
|
92
|
+
field_name = mm.group(1)
|
|
93
|
+
break
|
|
94
|
+
|
|
95
|
+
if not field_name:
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
resolver_id = f"{ctx.file_path}:{class_name}:{gql_type}:{field_name}"
|
|
99
|
+
result.nodes.append(GraphNode(
|
|
100
|
+
id=resolver_id,
|
|
101
|
+
kind=NodeKind.ENDPOINT,
|
|
102
|
+
label=f"GraphQL {gql_type}.{field_name}",
|
|
103
|
+
fqn=f"{class_name}.{field_name}",
|
|
104
|
+
module=ctx.module_name,
|
|
105
|
+
location=SourceLocation(file_path=ctx.file_path, line_start=i + 1),
|
|
106
|
+
properties={
|
|
107
|
+
"graphql_type": gql_type,
|
|
108
|
+
"field": field_name,
|
|
109
|
+
"protocol": "graphql",
|
|
110
|
+
},
|
|
111
|
+
))
|
|
112
|
+
|
|
113
|
+
result.edges.append(GraphEdge(
|
|
114
|
+
source=class_node_id,
|
|
115
|
+
target=resolver_id,
|
|
116
|
+
kind=EdgeKind.EXPOSES,
|
|
117
|
+
label=f"{class_name} exposes {gql_type}.{field_name}",
|
|
118
|
+
))
|
|
119
|
+
|
|
120
|
+
# Handle @SchemaMapping
|
|
121
|
+
sm = _SCHEMA_MAPPING_RE.search(line)
|
|
122
|
+
if sm:
|
|
123
|
+
type_name = sm.group(1) or "Unknown"
|
|
124
|
+
method_name = None
|
|
125
|
+
for k in range(i + 1, min(i + 4, len(lines))):
|
|
126
|
+
mm = _METHOD_RE.search(lines[k])
|
|
127
|
+
if mm:
|
|
128
|
+
method_name = mm.group(1)
|
|
129
|
+
break
|
|
130
|
+
|
|
131
|
+
if method_name:
|
|
132
|
+
resolver_id = f"{ctx.file_path}:{class_name}:SchemaMapping:{type_name}.{method_name}"
|
|
133
|
+
result.nodes.append(GraphNode(
|
|
134
|
+
id=resolver_id,
|
|
135
|
+
kind=NodeKind.ENDPOINT,
|
|
136
|
+
label=f"GraphQL {type_name}.{method_name}",
|
|
137
|
+
fqn=f"{class_name}.{method_name}",
|
|
138
|
+
module=ctx.module_name,
|
|
139
|
+
location=SourceLocation(file_path=ctx.file_path, line_start=i + 1),
|
|
140
|
+
properties={
|
|
141
|
+
"graphql_type": type_name,
|
|
142
|
+
"field": method_name,
|
|
143
|
+
"protocol": "graphql",
|
|
144
|
+
},
|
|
145
|
+
))
|
|
146
|
+
result.edges.append(GraphEdge(
|
|
147
|
+
source=class_node_id,
|
|
148
|
+
target=resolver_id,
|
|
149
|
+
kind=EdgeKind.EXPOSES,
|
|
150
|
+
label=f"{class_name} exposes {type_name}.{method_name}",
|
|
151
|
+
))
|
|
152
|
+
|
|
153
|
+
# Handle @DgsData
|
|
154
|
+
dm = _DGS_DATA_RE.search(line)
|
|
155
|
+
if dm:
|
|
156
|
+
parent_type = dm.group(1)
|
|
157
|
+
field_name = dm.group(2) if dm.lastindex and dm.lastindex >= 2 and dm.group(2) else None
|
|
158
|
+
if not field_name:
|
|
159
|
+
for k in range(i + 1, min(i + 4, len(lines))):
|
|
160
|
+
mm = _METHOD_RE.search(lines[k])
|
|
161
|
+
if mm:
|
|
162
|
+
field_name = mm.group(1)
|
|
163
|
+
break
|
|
164
|
+
|
|
165
|
+
if field_name:
|
|
166
|
+
resolver_id = f"{ctx.file_path}:{class_name}:DgsData:{parent_type}.{field_name}"
|
|
167
|
+
result.nodes.append(GraphNode(
|
|
168
|
+
id=resolver_id,
|
|
169
|
+
kind=NodeKind.ENDPOINT,
|
|
170
|
+
label=f"GraphQL {parent_type}.{field_name}",
|
|
171
|
+
fqn=f"{class_name}.{field_name}",
|
|
172
|
+
module=ctx.module_name,
|
|
173
|
+
location=SourceLocation(file_path=ctx.file_path, line_start=i + 1),
|
|
174
|
+
properties={
|
|
175
|
+
"graphql_type": parent_type,
|
|
176
|
+
"field": field_name,
|
|
177
|
+
"protocol": "graphql",
|
|
178
|
+
"framework": "dgs",
|
|
179
|
+
},
|
|
180
|
+
))
|
|
181
|
+
result.edges.append(GraphEdge(
|
|
182
|
+
source=class_node_id,
|
|
183
|
+
target=resolver_id,
|
|
184
|
+
kind=EdgeKind.EXPOSES,
|
|
185
|
+
label=f"{class_name} exposes {parent_type}.{field_name}",
|
|
186
|
+
))
|
|
187
|
+
|
|
188
|
+
return result
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""gRPC service detector for Java source files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from osscodeiq.detectors.base import DetectorContext, DetectorResult
|
|
8
|
+
from osscodeiq.detectors.utils import decode_text
|
|
9
|
+
from osscodeiq.models.graph import (
|
|
10
|
+
EdgeKind,
|
|
11
|
+
GraphEdge,
|
|
12
|
+
GraphNode,
|
|
13
|
+
NodeKind,
|
|
14
|
+
SourceLocation,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
_CLASS_RE = re.compile(r"(?:public\s+)?class\s+(\w+)")
|
|
18
|
+
|
|
19
|
+
# gRPC service implementation pattern: extends XxxGrpc.XxxImplBase
|
|
20
|
+
_GRPC_IMPL_RE = re.compile(
|
|
21
|
+
r"class\s+(\w+)\s+extends\s+(\w+)Grpc\.(\w+)ImplBase"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# @GrpcService annotation (grpc-spring-boot-starter)
|
|
25
|
+
_GRPC_SERVICE_ANNO_RE = re.compile(r"@GrpcService")
|
|
26
|
+
|
|
27
|
+
# Override methods in gRPC service
|
|
28
|
+
_OVERRIDE_METHOD_RE = re.compile(
|
|
29
|
+
r"@Override\s+\n?\s*public\s+void\s+(\w+)\s*\("
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# gRPC channel/stub usage for client detection
|
|
33
|
+
_GRPC_STUB_RE = re.compile(
|
|
34
|
+
r"(\w+)Grpc\.new(?:Blocking|Future|)Stub\s*\("
|
|
35
|
+
)
|
|
36
|
+
_GRPC_CHANNEL_RE = re.compile(
|
|
37
|
+
r'ManagedChannelBuilder\s*\.forAddress\s*\(\s*"([^"]+)"\s*,\s*(\d+)'
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Method override pattern (simpler)
|
|
41
|
+
_METHOD_RE = re.compile(
|
|
42
|
+
r"(?:public)\s+(?:void|[\w<>\[\]]+)\s+(\w+)\s*\(\s*(\w+)"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class GrpcServiceDetector:
|
|
47
|
+
"""Detects gRPC service implementations and client stubs."""
|
|
48
|
+
|
|
49
|
+
name: str = "grpc_service"
|
|
50
|
+
supported_languages: tuple[str, ...] = ("java",)
|
|
51
|
+
|
|
52
|
+
def detect(self, ctx: DetectorContext) -> DetectorResult:
|
|
53
|
+
result = DetectorResult()
|
|
54
|
+
text = decode_text(ctx)
|
|
55
|
+
lines = text.split("\n")
|
|
56
|
+
|
|
57
|
+
has_grpc_impl = "ImplBase" in text or "@GrpcService" in text
|
|
58
|
+
has_grpc_stub = "Grpc.new" in text
|
|
59
|
+
|
|
60
|
+
if not has_grpc_impl and not has_grpc_stub:
|
|
61
|
+
return result
|
|
62
|
+
|
|
63
|
+
# Find class name
|
|
64
|
+
class_name: str | None = None
|
|
65
|
+
class_line: int = 0
|
|
66
|
+
for i, line in enumerate(lines):
|
|
67
|
+
cm = _CLASS_RE.search(line)
|
|
68
|
+
if cm:
|
|
69
|
+
class_name = cm.group(1)
|
|
70
|
+
class_line = i + 1
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
if not class_name:
|
|
74
|
+
return result
|
|
75
|
+
|
|
76
|
+
class_node_id = f"{ctx.file_path}:{class_name}"
|
|
77
|
+
|
|
78
|
+
# Detect gRPC service implementation
|
|
79
|
+
impl_match = _GRPC_IMPL_RE.search(text)
|
|
80
|
+
if impl_match:
|
|
81
|
+
service_proto = impl_match.group(2) # The proto service name
|
|
82
|
+
service_id = f"grpc:service:{service_proto}"
|
|
83
|
+
|
|
84
|
+
result.nodes.append(GraphNode(
|
|
85
|
+
id=service_id,
|
|
86
|
+
kind=NodeKind.ENDPOINT,
|
|
87
|
+
label=f"gRPC {service_proto}",
|
|
88
|
+
fqn=f"{class_name} ({service_proto})",
|
|
89
|
+
module=ctx.module_name,
|
|
90
|
+
location=SourceLocation(file_path=ctx.file_path, line_start=class_line),
|
|
91
|
+
annotations=["@GrpcService"] if "@GrpcService" in text else [],
|
|
92
|
+
properties={
|
|
93
|
+
"protocol": "grpc",
|
|
94
|
+
"service": service_proto,
|
|
95
|
+
"implementation": class_name,
|
|
96
|
+
},
|
|
97
|
+
))
|
|
98
|
+
|
|
99
|
+
result.edges.append(GraphEdge(
|
|
100
|
+
source=class_node_id,
|
|
101
|
+
target=service_id,
|
|
102
|
+
kind=EdgeKind.EXPOSES,
|
|
103
|
+
label=f"{class_name} implements gRPC {service_proto}",
|
|
104
|
+
))
|
|
105
|
+
|
|
106
|
+
# Find RPC methods (overridden methods)
|
|
107
|
+
for i, line in enumerate(lines):
|
|
108
|
+
if "@Override" in line:
|
|
109
|
+
for k in range(i + 1, min(i + 3, len(lines))):
|
|
110
|
+
mm = _METHOD_RE.search(lines[k])
|
|
111
|
+
if mm:
|
|
112
|
+
method_name = mm.group(1)
|
|
113
|
+
rpc_id = f"grpc:rpc:{service_proto}/{method_name}"
|
|
114
|
+
result.nodes.append(GraphNode(
|
|
115
|
+
id=rpc_id,
|
|
116
|
+
kind=NodeKind.ENDPOINT,
|
|
117
|
+
label=f"gRPC {service_proto}/{method_name}",
|
|
118
|
+
fqn=f"{class_name}.{method_name}",
|
|
119
|
+
module=ctx.module_name,
|
|
120
|
+
location=SourceLocation(file_path=ctx.file_path, line_start=k + 1),
|
|
121
|
+
properties={
|
|
122
|
+
"protocol": "grpc",
|
|
123
|
+
"service": service_proto,
|
|
124
|
+
"method": method_name,
|
|
125
|
+
},
|
|
126
|
+
))
|
|
127
|
+
break
|
|
128
|
+
|
|
129
|
+
# Detect gRPC client stubs
|
|
130
|
+
for m in _GRPC_STUB_RE.finditer(text):
|
|
131
|
+
target_service = m.group(1)
|
|
132
|
+
line_num = text[:m.start()].count("\n") + 1
|
|
133
|
+
|
|
134
|
+
result.edges.append(GraphEdge(
|
|
135
|
+
source=class_node_id,
|
|
136
|
+
target=f"grpc:service:{target_service}",
|
|
137
|
+
kind=EdgeKind.CALLS,
|
|
138
|
+
label=f"{class_name} calls gRPC {target_service}",
|
|
139
|
+
properties={"protocol": "grpc", "target_service": target_service},
|
|
140
|
+
))
|
|
141
|
+
|
|
142
|
+
return result
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""IBM MQ detector for Java source files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from osscodeiq.detectors.base import DetectorContext, DetectorResult
|
|
8
|
+
from osscodeiq.detectors.utils import decode_text
|
|
9
|
+
from osscodeiq.models.graph import (
|
|
10
|
+
EdgeKind,
|
|
11
|
+
GraphEdge,
|
|
12
|
+
GraphNode,
|
|
13
|
+
NodeKind,
|
|
14
|
+
SourceLocation,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
_CLASS_RE = re.compile(r"(?:public\s+)?class\s+(\w+)")
|
|
18
|
+
|
|
19
|
+
# MQQueueManager instantiation with name
|
|
20
|
+
_QM_NEW_RE = re.compile(r'new\s+MQQueueManager\s*\(\s*"([^"]+)"')
|
|
21
|
+
|
|
22
|
+
# accessQueue("QUEUE_NAME", ...)
|
|
23
|
+
_ACCESS_QUEUE_RE = re.compile(r'accessQueue\s*\(\s*"([^"]+)"')
|
|
24
|
+
|
|
25
|
+
# MQQueue / MQTopic field or local variable declarations
|
|
26
|
+
_MQ_QUEUE_DECL_RE = re.compile(r'\bMQQueue\b')
|
|
27
|
+
_MQ_TOPIC_DECL_RE = re.compile(r'\bMQTopic\b')
|
|
28
|
+
|
|
29
|
+
# JMS-style IBM MQ connection factory
|
|
30
|
+
_MQ_JMS_FACTORY_RE = re.compile(r'\bMQConnectionFactory\b|\bMQQueueConnectionFactory\b|\bMQTopicConnectionFactory\b')
|
|
31
|
+
|
|
32
|
+
# createQueue / createTopic with IBM MQ JMS
|
|
33
|
+
_JMS_CREATE_QUEUE_RE = re.compile(r'createQueue\s*\(\s*"([^"]+)"')
|
|
34
|
+
_JMS_CREATE_TOPIC_RE = re.compile(r'createTopic\s*\(\s*"([^"]+)"')
|
|
35
|
+
|
|
36
|
+
# put / get calls indicate send / receive
|
|
37
|
+
_MQ_PUT_RE = re.compile(r'\bput\s*\(')
|
|
38
|
+
_MQ_GET_RE = re.compile(r'\bget\s*\(')
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class IbmMqDetector:
|
|
42
|
+
"""Detects IBM MQ queue manager, queue, and topic usage in Java source files."""
|
|
43
|
+
|
|
44
|
+
name: str = "ibm_mq"
|
|
45
|
+
supported_languages: tuple[str, ...] = ("java",)
|
|
46
|
+
|
|
47
|
+
def detect(self, ctx: DetectorContext) -> DetectorResult:
|
|
48
|
+
result = DetectorResult()
|
|
49
|
+
text = decode_text(ctx)
|
|
50
|
+
lines = text.split("\n")
|
|
51
|
+
|
|
52
|
+
if "MQQueueManager" not in text and "JmsConnectionFactory" not in text and "com.ibm.mq" not in text and "MQQueue" not in text:
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
# Find class name
|
|
56
|
+
class_name: str | None = None
|
|
57
|
+
for line in lines:
|
|
58
|
+
cm = _CLASS_RE.search(line)
|
|
59
|
+
if cm:
|
|
60
|
+
class_name = cm.group(1)
|
|
61
|
+
break
|
|
62
|
+
|
|
63
|
+
if not class_name:
|
|
64
|
+
return result
|
|
65
|
+
|
|
66
|
+
class_node_id = f"{ctx.file_path}:{class_name}"
|
|
67
|
+
seen_qms: set[str] = set()
|
|
68
|
+
seen_queues: set[str] = set()
|
|
69
|
+
seen_topics: set[str] = set()
|
|
70
|
+
|
|
71
|
+
def _ensure_qm_node(qm_name: str) -> str:
|
|
72
|
+
qm_id = f"ibmmq:qm:{qm_name}"
|
|
73
|
+
if qm_name not in seen_qms:
|
|
74
|
+
seen_qms.add(qm_name)
|
|
75
|
+
result.nodes.append(GraphNode(
|
|
76
|
+
id=qm_id,
|
|
77
|
+
kind=NodeKind.MESSAGE_QUEUE,
|
|
78
|
+
label=f"ibmmq:qm:{qm_name}",
|
|
79
|
+
properties={"broker": "ibm_mq", "queue_manager": qm_name},
|
|
80
|
+
))
|
|
81
|
+
return qm_id
|
|
82
|
+
|
|
83
|
+
def _ensure_queue_node(queue_name: str) -> str:
|
|
84
|
+
queue_id = f"ibmmq:queue:{queue_name}"
|
|
85
|
+
if queue_name not in seen_queues:
|
|
86
|
+
seen_queues.add(queue_name)
|
|
87
|
+
result.nodes.append(GraphNode(
|
|
88
|
+
id=queue_id,
|
|
89
|
+
kind=NodeKind.QUEUE,
|
|
90
|
+
label=f"ibmmq:queue:{queue_name}",
|
|
91
|
+
properties={"broker": "ibm_mq", "queue": queue_name},
|
|
92
|
+
))
|
|
93
|
+
return queue_id
|
|
94
|
+
|
|
95
|
+
def _ensure_topic_node(topic_name: str) -> str:
|
|
96
|
+
topic_id = f"ibmmq:topic:{topic_name}"
|
|
97
|
+
if topic_name not in seen_topics:
|
|
98
|
+
seen_topics.add(topic_name)
|
|
99
|
+
result.nodes.append(GraphNode(
|
|
100
|
+
id=topic_id,
|
|
101
|
+
kind=NodeKind.TOPIC,
|
|
102
|
+
label=f"ibmmq:topic:{topic_name}",
|
|
103
|
+
properties={"broker": "ibm_mq", "topic": topic_name},
|
|
104
|
+
))
|
|
105
|
+
return topic_id
|
|
106
|
+
|
|
107
|
+
# Track whether we see put/get patterns for edge direction
|
|
108
|
+
has_put = bool(_MQ_PUT_RE.search(text))
|
|
109
|
+
has_get = bool(_MQ_GET_RE.search(text))
|
|
110
|
+
|
|
111
|
+
# Detect MQQueueManager instantiation
|
|
112
|
+
for i, line in enumerate(lines):
|
|
113
|
+
m = _QM_NEW_RE.search(line)
|
|
114
|
+
if m:
|
|
115
|
+
qm_name = m.group(1)
|
|
116
|
+
qm_id = _ensure_qm_node(qm_name)
|
|
117
|
+
result.edges.append(GraphEdge(
|
|
118
|
+
source=class_node_id,
|
|
119
|
+
target=qm_id,
|
|
120
|
+
kind=EdgeKind.CONNECTS_TO,
|
|
121
|
+
label=f"{class_name} connects to queue manager {qm_name}",
|
|
122
|
+
properties={"queue_manager": qm_name},
|
|
123
|
+
))
|
|
124
|
+
|
|
125
|
+
# Detect accessQueue calls
|
|
126
|
+
for i, line in enumerate(lines):
|
|
127
|
+
m = _ACCESS_QUEUE_RE.search(line)
|
|
128
|
+
if m:
|
|
129
|
+
queue_name = m.group(1)
|
|
130
|
+
queue_id = _ensure_queue_node(queue_name)
|
|
131
|
+
if has_put:
|
|
132
|
+
result.edges.append(GraphEdge(
|
|
133
|
+
source=class_node_id,
|
|
134
|
+
target=queue_id,
|
|
135
|
+
kind=EdgeKind.SENDS_TO,
|
|
136
|
+
label=f"{class_name} sends to {queue_name}",
|
|
137
|
+
properties={"queue": queue_name},
|
|
138
|
+
))
|
|
139
|
+
if has_get:
|
|
140
|
+
result.edges.append(GraphEdge(
|
|
141
|
+
source=class_node_id,
|
|
142
|
+
target=queue_id,
|
|
143
|
+
kind=EdgeKind.RECEIVES_FROM,
|
|
144
|
+
label=f"{class_name} receives from {queue_name}",
|
|
145
|
+
properties={"queue": queue_name},
|
|
146
|
+
))
|
|
147
|
+
if not has_put and not has_get:
|
|
148
|
+
result.edges.append(GraphEdge(
|
|
149
|
+
source=class_node_id,
|
|
150
|
+
target=queue_id,
|
|
151
|
+
kind=EdgeKind.CONNECTS_TO,
|
|
152
|
+
label=f"{class_name} accesses {queue_name}",
|
|
153
|
+
properties={"queue": queue_name},
|
|
154
|
+
))
|
|
155
|
+
|
|
156
|
+
# Detect JMS-style createQueue / createTopic
|
|
157
|
+
for i, line in enumerate(lines):
|
|
158
|
+
m = _JMS_CREATE_QUEUE_RE.search(line)
|
|
159
|
+
if m:
|
|
160
|
+
queue_name = m.group(1)
|
|
161
|
+
_ensure_queue_node(queue_name)
|
|
162
|
+
|
|
163
|
+
m = _JMS_CREATE_TOPIC_RE.search(line)
|
|
164
|
+
if m:
|
|
165
|
+
topic_name = m.group(1)
|
|
166
|
+
_ensure_topic_node(topic_name)
|
|
167
|
+
|
|
168
|
+
# If we found MQTopic declarations but no explicit topic names, create a
|
|
169
|
+
# generic node to show MQ topic usage
|
|
170
|
+
if _MQ_TOPIC_DECL_RE.search(text) and not seen_topics:
|
|
171
|
+
result.nodes.append(GraphNode(
|
|
172
|
+
id=f"ibmmq:topic:__unknown__",
|
|
173
|
+
kind=NodeKind.TOPIC,
|
|
174
|
+
label="ibmmq:topic:unknown",
|
|
175
|
+
properties={"broker": "ibm_mq"},
|
|
176
|
+
))
|
|
177
|
+
|
|
178
|
+
return result
|