Item 258: JAXB types of a WSDL's schemas are linked to it; soap-endpoints lists typesPackages and their users as clients
This commit is contained in:
@@ -81,7 +81,9 @@ class KorinGapsCIT {
|
||||
write(clientRoot, "src/main/resources/wsdl/korin/WS_Leitwaehrung.wsdl", """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
|
||||
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="urn:x">
|
||||
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="urn:x"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
<wsdl:types><xsd:schema targetNamespace="urn:com-softwareag-entirex-rpc:WSK386"/></wsdl:types>
|
||||
<wsdl:portType name="WSK386Port"><wsdl:operation name="getData"/></wsdl:portType>
|
||||
<wsdl:binding name="B" type="tns:WSK386Port"><soap:binding style="document"/></wsdl:binding>
|
||||
<wsdl:service name="WS_Leitwaehrung">
|
||||
@@ -111,6 +113,18 @@ class KorinGapsCIT {
|
||||
}
|
||||
}
|
||||
""");
|
||||
// item 258: a class that only uses the request type JAXB generates from the WSDL's schema
|
||||
write(clientRoot, "src/main/java/at/k3/base/ws/wsk386/WSK386Request.java", """
|
||||
package at.k3.base.ws.wsk386;
|
||||
|
||||
import rpc.entirex.softwareag.com.wsk386.GetData;
|
||||
|
||||
public class WSK386Request {
|
||||
public GetData build() {
|
||||
return new GetData();
|
||||
}
|
||||
}
|
||||
""");
|
||||
write(clientRoot, "src/main/java/at/k3/ws/Stete.java", """
|
||||
package at.k3.ws;
|
||||
import jakarta.jws.WebMethod;
|
||||
@@ -221,7 +235,10 @@ class KorinGapsCIT {
|
||||
void wsimportBindingLinksTheGeneratedClientToItsWsdl() {
|
||||
given().when().get(api(CLIENT, "/soap-endpoints?kind=wsdl&service=WS_Leitwaehrung")).then().statusCode(200)
|
||||
.body("[0].clientPackage", equalTo("at.k3.entirex.ws.wsk386"))
|
||||
.body("[0].clients", contains("at.k3.base.ws.wsk386.WSK386GetDataService"))
|
||||
// item 258: the user of the schema's JAXB types is a client too
|
||||
.body("[0].clients", containsInAnyOrder("at.k3.base.ws.wsk386.WSK386GetDataService",
|
||||
"at.k3.base.ws.wsk386.WSK386Request"))
|
||||
.body("[0].typesPackages", equalTo("rpc.entirex.softwareag.com.wsk386"))
|
||||
.body("[0].counterpartProject", equalTo(SERVICE));
|
||||
given().when().get(api(CLIENT, "/modules?moduleKind=WSIMPORT_BINDING")).then().statusCode(200)
|
||||
.body("name", contains("src/main/resources/wsdl/korin/WS_Leitwaehrung-binding.xml"));
|
||||
|
||||
@@ -4906,6 +4906,32 @@ public final class CypherQueries {
|
||||
MERGE (ph)-[r:COUNTERPART_OF]->(w)
|
||||
SET r.via = 'wsimport'
|
||||
""";
|
||||
/**
|
||||
* Item 258: the request/response classes JAXB generates from a WSDL's schemas
|
||||
* ({@code rpc.entirex.softwareag.com.wsk369.GetData}) are, like the item-239 client, not in the sources —
|
||||
* placeholders. Each is linked {@code COUNTERPART_OF {via: jaxb}} to the WSDL service whose
|
||||
* {@code typesPackages} names its package, when exactly one service does. Top-level classes of the
|
||||
* package only (the package is the name up to the last dot). The placeholders are read once and the
|
||||
* packages tested with {@code IN}, which is hashed.
|
||||
*/
|
||||
public static final String LINK_WSDL_TYPES = """
|
||||
MATCH (w:MODULE {project: $project})
|
||||
WHERE w.moduleKind = 'WSDL_SERVICE' AND w.typesPackages IS NOT NULL
|
||||
UNWIND split(w.typesPackages, ',') AS pkg
|
||||
WITH pkg, collect(w) AS ws
|
||||
WHERE size(ws) = 1
|
||||
WITH collect({pkg: pkg, w: ws[0]}) AS owned
|
||||
WITH owned, [x IN owned | x.pkg] AS pkgs
|
||||
WHERE size(owned) > 0
|
||||
MATCH (ph:AstNode {project: $project, sourceFile: "", type: 'MODULE'})
|
||||
WHERE ph.name CONTAINS '.'
|
||||
WITH owned, pkgs, ph, substring(ph.name, 0, size(ph.name) - size(last(split(ph.name, '.'))) - 1) AS pkg
|
||||
WHERE pkg IN pkgs
|
||||
WITH ph, head([x IN owned WHERE x.pkg = pkg | x.w]) AS w
|
||||
MERGE (ph)-[r:COUNTERPART_OF]->(w)
|
||||
SET r.via = 'jaxb'
|
||||
""";
|
||||
|
||||
/**
|
||||
* Item 225: a WSDL a client project keeps for a service (K3's {@code wsdl/korinX/WS_X.wsdl}) is linked
|
||||
* to the counterpart project's Axis2 {@code SOAP_SERVICE} of the same name — unique match only. With
|
||||
@@ -5218,14 +5244,16 @@ public final class CypherQueries {
|
||||
// clientPackage) — computed for the page only.
|
||||
CALL {
|
||||
WITH node
|
||||
OPTIONAL MATCH (node)<-[:COUNTERPART_OF {via: 'wsimport'}]-(:AstNode)<-[:MENTIONS|CALLS|REFERENCES|INJECTS]-(src:AstNode)
|
||||
// item 258: users of the schema types (via jaxb) are clients as well
|
||||
OPTIONAL MATCH (node)<-[cp:COUNTERPART_OF]-(:AstNode)<-[:MENTIONS|CALLS|REFERENCES|INJECTS]-(src:AstNode)
|
||||
WHERE cp.via IN ['wsimport', 'jaxb']
|
||||
OPTIONAL MATCH (cm:MODULE {project: $project})-[:CONTAINS*0..1]->(src)
|
||||
WHERE cm.sourceFile <> ''
|
||||
RETURN [x IN collect(DISTINCT cm.name) WHERE x IS NOT NULL] AS clients
|
||||
}
|
||||
RETURN kind, service, operation, implementation, sourceFile, line, location, port,
|
||||
c.project AS counterpartProject, c.name AS counterpartService,
|
||||
node.clientPackage AS clientPackage, clients
|
||||
node.clientPackage AS clientPackage, clients, node.typesPackages AS typesPackages
|
||||
""";
|
||||
public static final String SOAP_ENDPOINTS_COUNT = SOAP_ENDPOINTS_CORE + """
|
||||
RETURN count(*) AS total
|
||||
|
||||
@@ -345,7 +345,8 @@ public class GraphRepository {
|
||||
new EnrichmentStep("link-counterparts-field", CypherQueries.LINK_COUNTERPARTS_FIELD),
|
||||
new EnrichmentStep("link-counterparts-natjav", CypherQueries.LINK_COUNTERPARTS_NATJAV),
|
||||
new EnrichmentStep("link-counterparts-soap", CypherQueries.LINK_COUNTERPARTS_SOAP),
|
||||
new EnrichmentStep("link-wsimport-clients", CypherQueries.LINK_WSIMPORT_CLIENTS));
|
||||
new EnrichmentStep("link-wsimport-clients", CypherQueries.LINK_WSIMPORT_CLIENTS),
|
||||
new EnrichmentStep("link-wsdl-types", CypherQueries.LINK_WSDL_TYPES));
|
||||
|
||||
/**
|
||||
* Strips a single leading Natural sigil ({@code #}, {@code &}, {@code +}) so name search is sigil-insensitive.
|
||||
@@ -3105,7 +3106,8 @@ public class GraphRepository {
|
||||
record.get("sourceFile").asString(""), intOrNull(record.get("line")),
|
||||
nullableString(record, "location"), nullableString(record, "counterpartProject"),
|
||||
nullableString(record, "counterpartService"), nullableString(record, "port"),
|
||||
nullableString(record, "clientPackage"), record.get("clients").asList(Value::asString)))
|
||||
nullableString(record, "clientPackage"), record.get("clients").asList(Value::asString),
|
||||
nullableString(record, "typesPackages")))
|
||||
.flatMap(rows -> withTotal(rows, limit, offset, () -> count(CypherQueries.SOAP_ENDPOINTS_COUNT, params)));
|
||||
}
|
||||
|
||||
|
||||
@@ -23,9 +23,12 @@ import java.util.List;
|
||||
* @param clientPackage item 239: for a WSDL row, the Java package its wsimport client is generated into
|
||||
* ({@code at.k3.entirex.ws.wsk386}), from the project's wsimport binding file
|
||||
* @param clients item 239: the modules importing or calling a class of that package
|
||||
* @param typesPackages item 258: for a WSDL row, the comma-separated Java packages JAXB generates the
|
||||
* request/response classes of its schemas into ({@code rpc.entirex.softwareag.com.wsk369})
|
||||
*/
|
||||
public record SoapEndpoint(String kind, String service, @Nullable String operation, @Nullable String implementation,
|
||||
String sourceFile, @Nullable Integer line, @Nullable String location,
|
||||
@Nullable String counterpartProject, @Nullable String counterpartService,
|
||||
@Nullable String port, @Nullable String clientPackage, List<String> clients) {
|
||||
@Nullable String port, @Nullable String clientPackage, List<String> clients,
|
||||
@Nullable String typesPackages) {
|
||||
}
|
||||
|
||||
@@ -198,6 +198,7 @@ public final class SoapDescriptorParser {
|
||||
Map<String, Map<String, Integer>> portTypeOperations = new LinkedHashMap<>();
|
||||
Map<String, String> bindingPortType = new LinkedHashMap<>();
|
||||
List<Service> services = new ArrayList<>();
|
||||
java.util.Set<String> typesPackages = new java.util.LinkedHashSet<>();
|
||||
read(content, new DefaultHandler() {
|
||||
private @Nullable Locator locator;
|
||||
private @Nullable String portType;
|
||||
@@ -250,6 +251,14 @@ public final class SoapDescriptorParser {
|
||||
service.location = a.get("location");
|
||||
}
|
||||
}
|
||||
// Item 258: the JAXB classes of an embedded schema land in the package its namespace
|
||||
// maps to (urn:com-softwareag-entirex-rpc:WSK369 -> rpc.entirex.softwareag.com.wsk369)
|
||||
case "schema" -> {
|
||||
String pkg = a.containsKey("targetNamespace") ? jaxbPackage(a.get("targetNamespace")) : null;
|
||||
if (pkg != null) {
|
||||
typesPackages.add(pkg);
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
@@ -286,6 +295,9 @@ public final class SoapDescriptorParser {
|
||||
if (s.port != null) {
|
||||
props.put("port", s.port);
|
||||
}
|
||||
if (!typesPackages.isEmpty()) {
|
||||
props.put("typesPackages", String.join(",", typesPackages));
|
||||
}
|
||||
AstNode module = new AstNode(UUID.randomUUID(), NodeType.MODULE, sourceFile + "#" + s.name, sourceFile,
|
||||
ConfigFileParser.LANGUAGE, s.line, lineCount, null, null, props);
|
||||
nodes.add(module);
|
||||
@@ -310,6 +322,47 @@ public final class SoapDescriptorParser {
|
||||
*/
|
||||
private static final Pattern ACTION_PORT = Pattern.compile("/([A-Za-z0-9_]+)Port/");
|
||||
|
||||
/**
|
||||
* Item 258: the Java package JAXB derives from a namespace URI (JAXB 2.x, appendix D.5.1): scheme
|
||||
* dropped, split at {@code /} and {@code :}, a URN's first part with dashes read as dots, the host
|
||||
* part reversed without a leading {@code www}, everything lower-cased, characters no identifier may
|
||||
* hold replaced by {@code _}. {@code http://namespace.softwareag.com/entirex/xml/mapping} →
|
||||
* {@code com.softwareag.namespace.entirex.xml.mapping}. A binding customisation that names another
|
||||
* package is not read. {@code null} for an empty namespace.
|
||||
*/
|
||||
static @Nullable String jaxbPackage(String namespace) {
|
||||
String ns = namespace.trim();
|
||||
boolean urn = ns.regionMatches(true, 0, "urn:", 0, 4);
|
||||
ns = ns.replaceFirst("^[A-Za-z][A-Za-z0-9+.-]*:(//)?", "");
|
||||
ns = ns.replaceFirst("\\.(xsd|wsdl|xml|html?)$", "");
|
||||
List<String> tokens = new ArrayList<>();
|
||||
for (String token : ns.split("[/:]")) {
|
||||
if (!token.isEmpty()) {
|
||||
tokens.add(token);
|
||||
}
|
||||
}
|
||||
if (tokens.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String host = urn ? tokens.getFirst().replace('-', '.') : tokens.getFirst();
|
||||
List<String> hostParts = new ArrayList<>(java.util.Arrays.asList(host.split("\\.")));
|
||||
if (!hostParts.isEmpty() && hostParts.getFirst().equalsIgnoreCase("www")) {
|
||||
hostParts.removeFirst();
|
||||
}
|
||||
java.util.Collections.reverse(hostParts);
|
||||
List<String> parts = new ArrayList<>(hostParts);
|
||||
parts.addAll(tokens.subList(1, tokens.size()));
|
||||
List<String> segments = new ArrayList<>();
|
||||
for (String part : parts) {
|
||||
String segment = part.toLowerCase(java.util.Locale.ROOT).replaceAll("[^a-z0-9_]", "_");
|
||||
if (segment.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
segments.add(Character.isDigit(segment.charAt(0)) ? "_" + segment : segment);
|
||||
}
|
||||
return segments.isEmpty() ? null : String.join(".", segments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Item 238: {@code WSK369Port} → {@code WSK369}; a portType without the suffix is the port itself.
|
||||
*/
|
||||
|
||||
@@ -152,4 +152,40 @@ class ConfigAndSoapParserTest {
|
||||
assertEquals(1, result.nodes().stream().filter(n -> n.type() == NodeType.FUNCTION).count());
|
||||
assertEquals("getData", node(result, NodeType.FUNCTION, "getData").name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Item 258: the JAXB package of a namespace — checked against a name the corpus confirms: the korin-ws
|
||||
* skeleton {@code com.softwareag.namespace.entirex.xml.mapping.WS_KontoSkeleton} is generated for the
|
||||
* namespace {@code http://namespace.softwareag.com/entirex/xml/mapping}.
|
||||
*/
|
||||
@Test
|
||||
void jaxbDerivesThePackageFromTheNamespace() {
|
||||
assertEquals("com.softwareag.namespace.entirex.xml.mapping",
|
||||
SoapDescriptorParser.jaxbPackage("http://namespace.softwareag.com/entirex/xml/mapping"));
|
||||
assertEquals("rpc.entirex.softwareag.com.wsk369",
|
||||
SoapDescriptorParser.jaxbPackage("urn:com-softwareag-entirex-rpc:WSK369"));
|
||||
assertEquals("com.example.types.v1",
|
||||
SoapDescriptorParser.jaxbPackage("https://www.example.com/types/v1.xsd"));
|
||||
assertNull(SoapDescriptorParser.jaxbPackage(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aWsdlNamesThePackagesOfItsSchemaTypes() {
|
||||
ParseResult result = SoapDescriptorParser.parse("src/main/resources/wsdl/korinKonto/WS_Konto.wsdl", """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions name="WS_Konto" targetNamespace="http://namespace.softwareag.com/entirex/xml/mapping"
|
||||
xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:tns="http://namespace.softwareag.com/entirex/xml/mapping">
|
||||
<types>
|
||||
<xsd:schema targetNamespace="urn:com-softwareag-entirex-rpc:WSK369"/>
|
||||
</types>
|
||||
<portType name="WSK369Port"><operation name="getData"/></portType>
|
||||
<binding name="WSK369Binding" type="tns:WSK369Port"/>
|
||||
<service name="WS_Konto"><port name="WSK369Port" binding="tns:WSK369Binding"/></service>
|
||||
</definitions>
|
||||
""");
|
||||
AstNode service = node(result, NodeType.MODULE, "src/main/resources/wsdl/korinKonto/WS_Konto.wsdl#WS_Konto");
|
||||
assertEquals("rpc.entirex.softwareag.com.wsk369", prop(service, "typesPackages"));
|
||||
assertEquals("WSK369", prop(service, "port"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user