Skip to content

Commit ec6ec16

Browse files
author
Rob Tjalma
authored
Merge pull request #52 from com-pas/fix-namespace-scl
Make sure the SCL Namespace is the default namespace
2 parents 5b281b4 + 69e16b0 commit ec6ec16

File tree

11 files changed

+358
-16
lines changed

11 files changed

+358
-16
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// SPDX-FileCopyrightText: 2021 Alliander N.V.
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
package org.lfenergy.compas.scl.data.rest;
5+
6+
import org.lfenergy.compas.core.commons.ElementConverter;
7+
import org.lfenergy.compas.scl.data.util.SclDataModelMarshaller;
8+
import org.lfenergy.compas.scl.data.util.SclElementProcessor;
9+
10+
import javax.enterprise.inject.Produces;
11+
12+
/**
13+
* Create Beans from other dependencies that are used in the application.
14+
*/
15+
public class CompasSclDataServiceConfiguration {
16+
@Produces
17+
public ElementConverter createElementConverter() {
18+
return new ElementConverter();
19+
}
20+
21+
@Produces
22+
public SclElementProcessor creatSclElementProcessor() {
23+
return new SclElementProcessor();
24+
}
25+
26+
@Produces
27+
public SclDataModelMarshaller createSclDataModelMarshaller() {
28+
return new SclDataModelMarshaller();
29+
}
30+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// SPDX-FileCopyrightText: 2021 Alliander N.V.
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
package org.lfenergy.compas.scl.data.rest;
5+
6+
import org.junit.jupiter.api.Test;
7+
8+
import static org.junit.jupiter.api.Assertions.assertNotNull;
9+
10+
class CompasSclDataServiceConfigurationTest {
11+
private CompasSclDataServiceConfiguration configuration = new CompasSclDataServiceConfiguration();
12+
13+
@Test
14+
void createElementConverter_WhenCalled_ThenObjectReturned() {
15+
assertNotNull(configuration.createElementConverter());
16+
}
17+
18+
@Test
19+
void creatSclElementProcessor_WhenCalled_ThenObjectReturned() {
20+
assertNotNull(configuration.creatSclElementProcessor());
21+
}
22+
23+
@Test
24+
void createSclDataModelMarshaller_WhenCalled_ThenObjectReturned() {
25+
assertNotNull(configuration.createSclDataModelMarshaller());
26+
}
27+
}

repository-basex/src/main/java/org/lfenergy/compas/scl/data/repository/CompasSclDataBaseXRepository.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@
3030
import static org.lfenergy.compas.scl.data.exception.CompasSclDataServiceErrorCode.BASEX_COMMAND_ERROR_CODE;
3131
import static org.lfenergy.compas.scl.data.exception.CompasSclDataServiceErrorCode.BASEX_QUERY_ERROR_CODE;
3232

33+
/**
34+
* This implementation of the repository will store the SCL XML Files in BaseX, this is a XML Database.
35+
* For more information see https://basex.org/.
36+
* <p>
37+
* For every type of SCL a separate database is created in which the SCL XML Files are stored.
38+
* every entries is stored under &lt;ID&gt;/&lt;Major version&gt;/&lt;Minor version&gt;/&lt;Patch version&gt;/scl.xml.
39+
* This combination is always unique and easy to use.
40+
*/
3341
@ApplicationScoped
3442
public class CompasSclDataBaseXRepository implements CompasSclDataRepository {
3543
private static final Logger LOGGER = LoggerFactory.getLogger(CompasSclDataBaseXRepository.class);
@@ -58,11 +66,12 @@ public class CompasSclDataBaseXRepository implements CompasSclDataRepository {
5866
private final ElementConverter elementConverter;
5967

6068
@Inject
61-
public CompasSclDataBaseXRepository(BaseXClientFactory baseXClientFactory) {
69+
public CompasSclDataBaseXRepository(BaseXClientFactory baseXClientFactory,
70+
ElementConverter elementConverter,
71+
SclDataModelMarshaller sclDataMarshaller) {
6272
this.baseXClientFactory = baseXClientFactory;
63-
64-
this.sclDataMarshaller = new SclDataModelMarshaller();
65-
this.elementConverter = new ElementConverter();
73+
this.sclDataMarshaller = sclDataMarshaller;
74+
this.elementConverter = elementConverter;
6675

6776
// At startup create all needed databases.
6877
Arrays.stream(SclType.values()).forEach(type ->

repository-basex/src/test/java/org/lfenergy/compas/scl/data/repository/CompasSclDataBaseXRepositoryTest.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.lfenergy.compas.scl.data.model.ChangeSetType;
1515
import org.lfenergy.compas.scl.data.model.SclType;
1616
import org.lfenergy.compas.scl.data.model.Version;
17+
import org.lfenergy.compas.scl.data.util.SclDataModelMarshaller;
1718
import org.lfenergy.compas.scl.data.util.SclElementProcessor;
1819
import org.mockito.junit.jupiter.MockitoExtension;
1920
import org.w3c.dom.Element;
@@ -23,7 +24,8 @@
2324
import static org.junit.jupiter.api.Assertions.*;
2425
import static org.lfenergy.compas.scl.data.Constants.*;
2526
import static org.lfenergy.compas.scl.data.basex.BaseXServerUtil.createClientFactory;
26-
import static org.lfenergy.compas.scl.data.exception.CompasSclDataServiceErrorCode.*;
27+
import static org.lfenergy.compas.scl.data.exception.CompasSclDataServiceErrorCode.BASEX_QUERY_ERROR_CODE;
28+
import static org.lfenergy.compas.scl.data.exception.CompasSclDataServiceErrorCode.HEADER_NOT_FOUND_ERROR_CODE;
2729

2830
@ExtendWith({MockitoExtension.class, BaseXServerJUnitExtension.class})
2931
class CompasSclDataBaseXRepositoryTest {
@@ -35,6 +37,7 @@ class CompasSclDataBaseXRepositoryTest {
3537

3638
private final ElementConverter converter = new ElementConverter();
3739
private final SclElementProcessor processor = new SclElementProcessor();
40+
private final SclDataModelMarshaller marshaller = new SclDataModelMarshaller();
3841

3942
@BeforeAll
4043
static void beforeAll() {
@@ -44,7 +47,7 @@ static void beforeAll() {
4447
@BeforeEach
4548
void beforeEach() throws Exception {
4649
factory.createClient().executeXQuery("db:create('" + TYPE + "')");
47-
repository = new CompasSclDataBaseXRepository(factory);
50+
repository = new CompasSclDataBaseXRepository(factory, converter, marshaller);
4851
}
4952

5053
@Test

repository/src/main/java/org/lfenergy/compas/scl/data/repository/CompasSclDataRepository.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,75 @@
1111
import java.util.List;
1212
import java.util.UUID;
1313

14+
/**
15+
* Repository class that will be used to handle SCL File for a specific type of storage.
16+
* The repository class needs to be able to create and delete entries and find/list entries.
17+
*/
1418
public interface CompasSclDataRepository {
19+
/**
20+
* List the latest version of all SCL Entries for a type of SCL.
21+
*
22+
* @param type The type of SCL to search for.
23+
* @return The list of entries found for the passed type.
24+
*/
1525
List<Item> list(SclType type);
1626

27+
/**
28+
* List all versions for a specific SCL Entry for a type of SCL.
29+
*
30+
* @param type The type of SCL to search for the specific SCL.
31+
* @param id The ID of the SCL to search for.
32+
* @return The list of versions found for that specific sCl Entry.
33+
*/
1734
List<Item> listVersionsByUUID(SclType type, UUID id);
1835

36+
/**
37+
* Return the latest version of a specific SCL Entry.
38+
*
39+
* @param type The type of SCL to search for the specific SCL.
40+
* @param id The ID of the SCL to search for.
41+
* @return The SCL XML File Content that is search for.
42+
*/
1943
Element findByUUID(SclType type, UUID id);
2044

45+
/**
46+
* Return the specific version of a specific SCL Entry.
47+
*
48+
* @param type The type of SCL to search for the specific SCL.
49+
* @param id The ID of the SCL to search for.
50+
* @param version The version of the ScL to search for.
51+
* @return The SCL XML File Content that is search for.
52+
*/
2153
Element findByUUID(SclType type, UUID id, Version version);
2254

55+
/**
56+
* Create a new entry for the passed UUID with the version number passed.
57+
* <p>
58+
* For a complete new entry the service layer will create a new UUID and set the version to 1.0.0.
59+
* When a entry is updated the service layer will increase the version and always create a new entry
60+
* in the repository.
61+
*
62+
* @param type The type of SCL to store it in.
63+
* @param id The ID of the new entry to be created.
64+
* @param scl The SCL XML File content to store.
65+
* @param version The version of the new entry to be created.
66+
*/
2367
void create(SclType type, UUID id, Element scl, Version version);
2468

69+
/**
70+
* Delete all versions for a specific SCL File using it's ID.
71+
*
72+
* @param type The type of SCL where to find the SCL File
73+
* @param id The ID of the SCL File to delete.
74+
*/
2575
void delete(SclType type, UUID id);
2676

77+
/**
78+
* Delete passed versions for a specific SCL File using it's ID.
79+
*
80+
* @param type The type of SCL where to find the SCL File
81+
* @param id The ID of the SCL File to delete.
82+
* @param version The version of that SCL File to delete.
83+
*/
2784
void delete(SclType type, UUID id, Version version);
2885
}

repository/src/main/java/org/lfenergy/compas/scl/data/util/SclElementProcessor.java

Lines changed: 101 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,103 @@
66
import org.lfenergy.compas.scl.data.exception.CompasSclDataServiceException;
77
import org.w3c.dom.Element;
88
import org.w3c.dom.Node;
9+
import org.w3c.dom.NodeList;
910

10-
import java.util.ArrayList;
11-
import java.util.List;
12-
import java.util.Optional;
11+
import java.util.*;
1312

1413
import static org.lfenergy.compas.scl.data.Constants.*;
1514
import static org.lfenergy.compas.scl.data.exception.CompasSclDataServiceErrorCode.HEADER_NOT_FOUND_ERROR_CODE;
15+
import static org.w3c.dom.Node.ELEMENT_NODE;
1616

17+
/**
18+
* Support class to work with the SCL XML in a generic way as W3C Element/Node class.
19+
* This way multiple versions of the SCL XSD can easily be supported.
20+
*/
1721
public class SclElementProcessor {
22+
/**
23+
* Make sure that the SCL XSD Namespace will be the default namespace used on the passed
24+
* Node and all it's child nodes. Attributes aren't fixed, because these use the "null" namespace
25+
* as they are marked as "unqualified" in the XSD (attributeFormDefault="unqualified").
26+
*
27+
* @param root The root node from which to start, will mostly be the SCL XML Element.
28+
*/
29+
public void fixDefaultPrefix(Node root) {
30+
var oldNamespacePrefixes = new HashSet<String>();
31+
// Use a stack to walk through all child nodes.
32+
var nodes = new Stack<Node>();
33+
// Start with the root node.
34+
nodes.push(root);
35+
36+
while (!nodes.isEmpty()) {
37+
var node = nodes.pop();
38+
if (SCL_NS_URI.equals(node.getNamespaceURI())
39+
&& node.getPrefix() != null && !node.getPrefix().isBlank()) {
40+
// The namespace is the SCL XSD Namespace, but with a prefix, so we will fix that.
41+
oldNamespacePrefixes.add(node.getPrefix());
42+
node.setPrefix("");
43+
}
44+
45+
// Push all the Child Nodes (Type Element) on the Stack and continue with these.
46+
NodeList childNodes = node.getChildNodes();
47+
if (childNodes != null) {
48+
for (int i = 0, count = childNodes.getLength(); i < count; ++i) {
49+
var childNode = childNodes.item(i);
50+
if (childNode.getNodeType() == ELEMENT_NODE) {
51+
nodes.push(childNode);
52+
}
53+
}
54+
}
55+
}
56+
57+
oldNamespacePrefixes.forEach(
58+
prefix -> root.getAttributes().removeNamedItem("xmlns:" + prefix)
59+
);
60+
}
61+
62+
/**
63+
* Search for the SCL Header in the SCL Root Element and return that.
64+
*
65+
* @param scl The SCL Root Element
66+
* @return The Header Element if found or empty() if not.
67+
*/
1868
public Optional<Element> getSclHeader(Element scl) {
1969
return getChildNodesByName(scl, SCL_HEADER_ELEMENT_NAME).stream()
2070
.findFirst();
2171
}
2272

73+
/**
74+
* Add the SCL Header ot the SCL Root Element, because that element is important for the SCL Data Service
75+
* we want to make sure it's there.
76+
*
77+
* @param scl The SCL Root Element
78+
* @return The new created Header Element.
79+
*/
2380
public Element addSclHeader(Element scl) {
2481
var header = scl.getOwnerDocument().createElementNS(SCL_NS_URI, SCL_HEADER_ELEMENT_NAME);
2582
header.setPrefix(SCL_NS_PREFIX);
2683
scl.insertBefore(header, scl.getFirstChild());
2784
return header;
2885
}
2986

87+
/**
88+
* Search for the private element with type "#Constants.COMPAS_SCL_EXTENSION_TYPE" on the SCL Root Element.
89+
*
90+
* @param scl The SCL Root Element
91+
* @return The Private Element with the correct type if found or empty() if not.
92+
*/
3093
public Optional<Element> getCompasPrivate(Element scl) {
3194
return getChildNodesByName(scl, SCL_PRIVATE_ELEMENT_NAME).stream()
3295
.filter(element -> element.hasAttribute(SCL_PRIVATE_TYPE_ATTR))
3396
.filter(element -> element.getAttribute(SCL_PRIVATE_TYPE_ATTR).equals(COMPAS_SCL_EXTENSION_TYPE))
3497
.findFirst();
3598
}
3699

100+
/**
101+
* Create a Private Element with type "#COMPAS_SCL_EXTENSION_TYPE" on the SCL Root Element.
102+
*
103+
* @param scl The SCL Root Element
104+
* @return The new created Private Element with the correct type.
105+
*/
37106
public Element addCompasPrivate(Element scl) {
38107
scl.setAttribute("xmlns:" + COMPAS_EXTENSION_NS_PREFIX, COMPAS_EXTENSION_NS_URI);
39108

@@ -47,7 +116,14 @@ public Element addCompasPrivate(Element scl) {
47116
return tPrivate;
48117
}
49118

50-
119+
/**
120+
* Add a new element with namespace "#COMPAS_EXTENSION_NS_URI" to the Private element of CoMPAS.
121+
*
122+
* @param compasPrivate The Private Element on which to add the new Element.
123+
* @param localName The name of the Element to create.
124+
* @param value The value set on the new Element.
125+
* @return The new created Element.
126+
*/
51127
public Element addCompasElement(Element compasPrivate, String localName, String value) {
52128
Element element = compasPrivate.getOwnerDocument().createElementNS(COMPAS_EXTENSION_NS_URI, localName);
53129
element.setPrefix(COMPAS_EXTENSION_NS_PREFIX);
@@ -56,17 +132,38 @@ public Element addCompasElement(Element compasPrivate, String localName, String
56132
return element;
57133
}
58134

135+
/**
136+
* Returns the value of a specific attribute from the passed Element.
137+
*
138+
* @param element The Element to search for the attribute.
139+
* @param attributeName The name of the Attribute to search for.
140+
* @return The value if found or empty() if not.
141+
*/
59142
public Optional<String> getAttributeValue(Element element, String attributeName) {
60143
var value = element.getAttribute(attributeName);
61144
return (value != null && !value.isBlank()) ? Optional.of(value) : Optional.empty();
62145
}
63146

147+
/**
148+
* Search for a Child Node on the passed Element.
149+
*
150+
* @param root The element on which to search for a child Node.
151+
* @param localName The name of the Child Node.
152+
* @return The Child Node if found or empty() if not.
153+
*/
64154
public Optional<Element> getChildNodeByName(Element root, String localName) {
65155
return getChildNodesByName(root, localName)
66156
.stream()
67157
.findFirst();
68158
}
69159

160+
/**
161+
* Search for all Child Node on the passed Element.
162+
*
163+
* @param root The element on which to search for all child Node.
164+
* @param localName The name of the Child Node.
165+
* @return The list of Child Nodes found.
166+
*/
70167
public List<Element> getChildNodesByName(Element root, String localName) {
71168
var foundElements = new ArrayList<Element>();
72169
var childNodes = root.getChildNodes();

0 commit comments

Comments
 (0)