Skip to content

Commit 39fd7d7

Browse files
committed
feat(flow-translator): support translating integration XMLs with absent element ids
1 parent 947a20a commit 39fd7d7

File tree

8 files changed

+238
-16
lines changed

8 files changed

+238
-16
lines changed

flow-translator/flow-translator-lib/pom.xml

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

77
<groupId>org.codice.keip</groupId>
88
<artifactId>flow-translator-lib</artifactId>
9-
<version>0.4.1</version>
9+
<version>0.5.0</version>
1010

1111
<packaging>jar</packaging>
1212

@@ -189,7 +189,7 @@
189189
<limit>
190190
<counter>BRANCH</counter>
191191
<value>COVEREDRATIO</value>
192-
<minimum>0.96</minimum>
192+
<minimum>0.95</minimum>
193193
</limit>
194194
<limit>
195195
<counter>COMPLEXITY</counter>

flow-translator/flow-translator-lib/src/main/java/org/codice/keip/flow/xml/spring/DefaultXmlElementTransformer.java

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ public EipNode apply(XmlElement element, ComponentRegistry registry) {
2121

2222
EipId eipId = new EipId(element.prefix(), element.localName());
2323
Map<String, Object> filteredAttrs = new LinkedHashMap<>(element.attributes());
24-
filteredAttrs.remove(ID);
24+
String id = removeOrGenerateId(filteredAttrs);
2525

2626
List<EipChild> children = element.children().stream().map(this::convertChild).toList();
2727

2828
return new EipNode(
29-
element.attributes().get(ID).toString(),
29+
id,
3030
eipId,
3131
null,
3232
null,
@@ -42,18 +42,20 @@ private EipChild convertChild(XmlElement element) {
4242
}
4343

4444
private void validateElement(XmlElement element, ComponentRegistry registry) {
45-
if (!element.attributes().containsKey(ID)) {
46-
throw new IllegalArgumentException(
47-
String.format(
48-
"%s:%s element does not have an 'id' attribute",
49-
element.prefix(), element.localName()));
50-
}
51-
5245
EipId id = new EipId(element.prefix(), element.localName());
5346
if (!registry.isRegistered(id)) {
5447
throw new IllegalArgumentException(
5548
String.format(
5649
"%s:%s is not a supported EIP Component", element.prefix(), element.localName()));
5750
}
5851
}
52+
53+
private String removeOrGenerateId(Map<String, Object> attributes) {
54+
Object id = attributes.remove(ID);
55+
if (id == null) {
56+
return NodeIdGenerator.randomId();
57+
}
58+
59+
return id.toString();
60+
}
5961
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Original code from JNanoID, licensed under the MIT License (see below)
3+
* Copyright (c) 2017 The JNanoID Authors
4+
* Copyright (c) 2017 Aventrix LLC
5+
* Copyright (c) 2017 Andrey Sitnik
6+
*
7+
* ----------------------------------------------------------------------
8+
* The MIT License (MIT)
9+
*
10+
* Permission is hereby granted, free of charge, to any person obtaining a copy
11+
* of this software and associated documentation files (the "Software"), to deal
12+
* in the Software without restriction, including without limitation the rights
13+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14+
* copies of the Software, and to permit persons to whom the Software is
15+
* furnished to do so, subject to the following conditions:
16+
*
17+
* The above copyright notice and this permission notice shall be included in
18+
* all copies or substantial portions of the Software.
19+
*
20+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25+
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
26+
* IN THE SOFTWARE.
27+
* ----------------------------------------------------------------------
28+
*/
29+
package org.codice.keip.flow.xml.spring;
30+
31+
import java.util.Random;
32+
33+
/**
34+
* A class for generating unique String IDs.
35+
*
36+
* <p>Implementation based on JNanoID (<a href="https://github.com/aventrix/jnanoid">...</a>)
37+
*/
38+
public class NodeIdGenerator {
39+
40+
private NodeIdGenerator() {}
41+
42+
public static final Random DEFAULT_NUMBER_GENERATOR = new Random();
43+
44+
public static final char[] DEFAULT_ALPHABET =
45+
"_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
46+
47+
public static final int DEFAULT_SIZE = 10;
48+
49+
/**
50+
* Static factory to retrieve a url-friendly, pseudo randomly generated, NanoId String.
51+
*
52+
* <p>The generated NanoId String will have 10 symbols.
53+
*
54+
* <p>The NanoId String is generated using {@link Random}, so ids are NOT cryptographically
55+
* secure.
56+
*
57+
* @return A randomly generated NanoId String.
58+
*/
59+
public static String randomId() {
60+
return randomId(DEFAULT_NUMBER_GENERATOR, DEFAULT_ALPHABET, DEFAULT_SIZE);
61+
}
62+
63+
/**
64+
* Static factory to retrieve a NanoId String.
65+
*
66+
* <p>The string is generated using the given random number generator.
67+
*
68+
* @param random The random number generator.
69+
* @param alphabet The symbols used in the NanoId String.
70+
* @param size The number of symbols in the NanoId String.
71+
* @return A randomly generated NanoId String.
72+
*/
73+
public static String randomId(final Random random, final char[] alphabet, final int size) {
74+
if (random == null) {
75+
throw new IllegalArgumentException("random cannot be null.");
76+
}
77+
78+
if (alphabet == null) {
79+
throw new IllegalArgumentException("alphabet cannot be null.");
80+
}
81+
82+
if (alphabet.length == 0 || alphabet.length >= 256) {
83+
throw new IllegalArgumentException("alphabet must contain between 1 and 255 symbols.");
84+
}
85+
86+
if (size <= 0) {
87+
throw new IllegalArgumentException("size must be greater than zero.");
88+
}
89+
90+
final int mask = (2 << (int) Math.floor(Math.log(alphabet.length - 1) / Math.log(2))) - 1;
91+
final int step = (int) Math.ceil(1.6 * mask * size / alphabet.length);
92+
93+
final StringBuilder idBuilder = new StringBuilder();
94+
95+
while (true) {
96+
97+
final byte[] bytes = new byte[step];
98+
random.nextBytes(bytes);
99+
100+
for (int i = 0; i < step; i++) {
101+
102+
final int alphabetIndex = bytes[i] & mask;
103+
104+
if (alphabetIndex < alphabet.length) {
105+
idBuilder.append(alphabet[alphabetIndex]);
106+
if (idBuilder.length() == size) {
107+
return idBuilder.toString();
108+
}
109+
}
110+
}
111+
}
112+
}
113+
}

flow-translator/flow-translator-lib/src/test/groovy/org/codice/keip/flow/xml/spring/DefaultXmlElementTransformerTest.groovy

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,18 +95,27 @@ class DefaultXmlElementTransformerTest extends Specification {
9595
}
9696
}
9797

98-
def "transform xml element with no 'id' attribute -> exception thrown"() {
98+
def "transform xml element with no 'id' attribute -> random id is generated"() {
9999
given:
100100
def element = new XmlElement(
101101
new QName("http://example.com", "adapter", "test"),
102102
["key1": "val1"],
103103
[])
104104

105105
when:
106-
transformer.apply(element, registry)
106+
def node = transformer.apply(element, registry)
107107

108108
then:
109-
thrown(IllegalArgumentException)
109+
with(node) {
110+
id().size() == 10
111+
eipId() == new EipId("test", "adapter")
112+
role() == Role.ENDPOINT
113+
connectionType() == ConnectionType.SOURCE
114+
attributes() == ["key1": "val1"]
115+
children().isEmpty()
116+
label() == null
117+
description() == null
118+
}
110119
}
111120

112121
def "transform unregistered eip node -> exception thrown"() {

flow-translator/flow-translator-lib/src/test/groovy/org/codice/keip/flow/xml/spring/IntegrationGraphXmlParserTest.groovy

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,43 @@ class IntegrationGraphXmlParserTest extends Specification {
258258
result.customEntities() == expectedEntities
259259
}
260260

261+
def "xml to graph with missing element ids -> random ids generated"() {
262+
given:
263+
InputStream xml = readTestXml("multi-node-missing-ids.xml")
264+
265+
when:
266+
def result = xmlParser.fromXml(xml)
267+
268+
then:
269+
def graph = result.graph()
270+
def nodes = graph.traverse().toList()
271+
nodes.size() == 3
272+
273+
def inAdapter = nodes[0]
274+
with(inAdapter) {
275+
id().size() == 10
276+
eipId() == new EipId("integration", "inbound-channel-adapter")
277+
attributes() == ["expression": "'TestMessage'"]
278+
children().size() == 1
279+
}
280+
281+
def transformer = nodes[1]
282+
with(transformer) {
283+
id().size() == 10
284+
eipId() == new EipId("integration", "transformer")
285+
attributes() == ["expression": "payload + ' processed'"]
286+
children().isEmpty()
287+
}
288+
289+
def outAdapter = nodes[2]
290+
with(outAdapter) {
291+
id().size() == 10
292+
eipId() == new EipId("jms", "outbound-channel-adapter")
293+
attributes() == ["destination-name": "test-target"]
294+
children().isEmpty()
295+
}
296+
}
297+
261298
def "xsd validation with invalid xml -> failure"(String xmlFilePath) {
262299
given:
263300
InputStream xml = readTestXml(xmlFilePath)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.codice.keip.flow.xml.spring
2+
3+
import spock.lang.Specification
4+
5+
import static org.codice.keip.flow.xml.spring.NodeIdGenerator.DEFAULT_ALPHABET
6+
import static org.codice.keip.flow.xml.spring.NodeIdGenerator.DEFAULT_NUMBER_GENERATOR
7+
import static org.codice.keip.flow.xml.spring.NodeIdGenerator.DEFAULT_SIZE
8+
9+
class NodeIdGeneratorTest extends Specification {
10+
11+
def "test generating ids"() {
12+
when:
13+
def id = NodeIdGenerator.randomId()
14+
15+
then:
16+
id.length() == 10
17+
}
18+
19+
def "test bad inputs"(Random generator, char[] alphabet, int size) {
20+
when:
21+
NodeIdGenerator.randomId(generator, alphabet, size)
22+
23+
then:
24+
thrown(IllegalArgumentException)
25+
26+
where:
27+
generator | alphabet | size
28+
null | DEFAULT_ALPHABET | DEFAULT_SIZE
29+
DEFAULT_NUMBER_GENERATOR | null | DEFAULT_SIZE
30+
DEFAULT_NUMBER_GENERATOR | new char[300] | DEFAULT_SIZE
31+
DEFAULT_NUMBER_GENERATOR | DEFAULT_ALPHABET | -1
32+
}
33+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<beans xmlns="http://www.springframework.org/schema/beans"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xmlns:integration="http://www.springframework.org/schema/integration"
5+
xmlns:jms="http://www.springframework.org/schema/integration/jms"
6+
xsi:schemaLocation="http://www.springframework.org/schema/beans
7+
https://www.springframework.org/schema/beans/spring-beans.xsd
8+
http://www.springframework.org/schema/integration
9+
https://www.springframework.org/schema/integration/spring-integration.xsd
10+
http://www.springframework.org/schema/integration/jms
11+
https://www.springframework.org/schema/integration/jms/spring-integration-jms.xsd
12+
">
13+
14+
<integration:inbound-channel-adapter channel="messageSource"
15+
expression="'TestMessage'">
16+
<integration:poller fixed-rate="5000"/>
17+
</integration:inbound-channel-adapter>
18+
19+
<integration:channel id="messageSource"/>
20+
21+
<integration:transformer input-channel="messageSource"
22+
output-channel="transformerOut"
23+
expression="payload + ' processed'"/>
24+
25+
<integration:channel id="transformerOut"/>
26+
27+
<jms:outbound-channel-adapter destination-name="test-target" channel="transformerOut"/>
28+
</beans>

flow-translator/flow-translator-webapp/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
<groupId>org.codice.keip</groupId>
1313
<artifactId>flow-translator-webapp</artifactId>
14-
<version>0.4.2</version>
14+
<version>0.5.0</version>
1515

1616
<packaging>jar</packaging>
1717

@@ -43,7 +43,7 @@
4343
<dependency>
4444
<groupId>org.codice.keip</groupId>
4545
<artifactId>flow-translator-lib</artifactId>
46-
<version>0.4.1</version>
46+
<version>0.5.0</version>
4747
</dependency>
4848
<dependency>
4949
<groupId>org.codice.keip.xsd</groupId>

0 commit comments

Comments
 (0)