Skip to content

Commit f09c6a5

Browse files
authored
Add an element "PipelinePart" as possible root-element (#438)
1 parent f0e8b43 commit f09c6a5

File tree

14 files changed

+442
-42
lines changed

14 files changed

+442
-42
lines changed

frank-doc-doclet/src/main/java/org/frankframework/frankdoc/Constants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public class Constants {
2020
static final String MODULE_ELEMENT_NAME = "Module";
2121
static final String MODULE_ELEMENT_DESCRIPTION = "Wrapper element to help split up large configuration files into smaller valid XML files. It may be used as root tag when an XML file contains multiple adapters and/or jobs. The Module element itself does not influence the behavior of Frank configurations.";
2222
static final String MODULE_ELEMENT_FRANK_DOC_GROUP = "Other";
23+
2324
public static final String FRANK_DOC_GROUP_VALUES_PACKAGE = "org.frankframework.doc.";
2425

2526
public static final String CONFIG_WARNING_ELEMENT_NAME = "ConfigWarning";

frank-doc-doclet/src/main/java/org/frankframework/frankdoc/FrankDocJsonFactory.java

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,46 @@
1616

1717
package org.frankframework.frankdoc;
1818

19-
import lombok.NonNull;
19+
import java.util.Comparator;
20+
import java.util.List;
21+
import java.util.Map;
22+
import java.util.Optional;
23+
import java.util.stream.Collectors;
24+
25+
import javax.json.Json;
26+
import javax.json.JsonArray;
27+
import javax.json.JsonArrayBuilder;
28+
import javax.json.JsonBuilderFactory;
29+
import javax.json.JsonException;
30+
import javax.json.JsonObject;
31+
import javax.json.JsonObjectBuilder;
32+
2033
import org.apache.commons.lang3.StringUtils;
2134
import org.apache.logging.log4j.Logger;
22-
import org.frankframework.frankdoc.model.*;
35+
import org.frankframework.frankdoc.model.AttributeEnum;
36+
import org.frankframework.frankdoc.model.AttributeType;
37+
import org.frankframework.frankdoc.model.ConfigChild;
38+
import org.frankframework.frankdoc.model.CredentialProvider;
39+
import org.frankframework.frankdoc.model.DeprecationInfo;
40+
import org.frankframework.frankdoc.model.ElementChild;
41+
import org.frankframework.frankdoc.model.ElementType;
42+
import org.frankframework.frankdoc.model.EnumValue;
43+
import org.frankframework.frankdoc.model.Forward;
44+
import org.frankframework.frankdoc.model.FrankAttribute;
45+
import org.frankframework.frankdoc.model.FrankDocModel;
46+
import org.frankframework.frankdoc.model.FrankElement;
47+
import org.frankframework.frankdoc.model.MandatoryStatus;
48+
import org.frankframework.frankdoc.model.Note;
49+
import org.frankframework.frankdoc.model.ObjectConfigChild;
50+
import org.frankframework.frankdoc.model.ParsedJavaDocTag;
51+
import org.frankframework.frankdoc.model.ServletAuthenticator;
52+
import org.frankframework.frankdoc.model.ServletAuthenticatorMethod;
2353
import org.frankframework.frankdoc.properties.Group;
2454
import org.frankframework.frankdoc.properties.Property;
2555
import org.frankframework.frankdoc.util.LogUtil;
56+
import org.frankframework.frankdoc.wrapper.AdditionalRootElement;
2657

27-
import javax.json.*;
28-
import java.util.ArrayList;
29-
import java.util.List;
30-
import java.util.Map;
31-
import java.util.Optional;
32-
import java.util.stream.Collectors;
58+
import lombok.NonNull;
3359

3460
public class FrankDocJsonFactory {
3561
private static final Logger log = LogUtil.getLogger(FrankDocJsonFactory.class);
@@ -42,11 +68,25 @@ public class FrankDocJsonFactory {
4268
List<FrankElement> elementsOutsideChildren;
4369
private final String frankFrameworkVersion;
4470

71+
private final List<AdditionalRootElement> additionalRootElements;
72+
4573
public FrankDocJsonFactory(FrankDocModel model, String frankFrameworkVersion) {
4674
this.model = model;
47-
elementsOutsideChildren = new ArrayList<>(model.getElementsOutsideConfigChildren());
75+
elementsOutsideChildren = model.getElementsOutsideConfigChildren();
4876
bf = Json.createBuilderFactory(null);
4977
this.frankFrameworkVersion = frankFrameworkVersion;
78+
this.additionalRootElements = findAdditionalRootElements();
79+
}
80+
81+
private List<AdditionalRootElement> findAdditionalRootElements() {
82+
return model.getAllTypes()
83+
.values()
84+
.stream()
85+
.map(ElementType::getSimpleName)
86+
.filter(AdditionalRootElement.VALUE_BY_TYPE::containsKey)
87+
.map(AdditionalRootElement.VALUE_BY_TYPE::get)
88+
.sorted(Comparator.comparing(AdditionalRootElement::getElementName))
89+
.toList();
5090
}
5191

5292
public JsonObject getJson() {
@@ -87,9 +127,16 @@ private JsonObject getTypes() {
87127
.forEach(type -> addType(type.getValue(), result));
88128
elementsOutsideChildren.forEach(element -> addNonChildType(element, result));
89129
getTypeReferencedEntityRoot(result);
130+
additionalRootElements.forEach(element -> addAdditionalRootElementType(result, element));
90131
return result.build();
91132
}
92133

134+
private void addAdditionalRootElementType(JsonObjectBuilder result, AdditionalRootElement additionalRootElement) {
135+
JsonArrayBuilder members = bf.createArrayBuilder();
136+
members.add(additionalRootElement.getElementName());
137+
result.add(additionalRootElement.getElementName(), members);
138+
}
139+
93140
private void getTypeReferencedEntityRoot(JsonObjectBuilder result) {
94141
JsonArrayBuilder members = bf.createArrayBuilder();
95142
members.add(Constants.MODULE_ELEMENT_NAME);
@@ -114,6 +161,8 @@ private JsonObject getElements() throws JsonException {
114161
Map<String, List<FrankElement>> elementsByName = getAllElements();
115162
builder.add(Constants.MODULE_ELEMENT_NAME, getRootElementObject());
116163

164+
additionalRootElements.forEach(element -> builder.add(element.getElementName(), getObjectForElement(element)));
165+
117166
for (List<FrankElement> elementsPerClass : elementsByName.values()) {
118167
for (FrankElement element : elementsPerClass) {
119168
builder.add(element.getFullName(), getElement(element));
@@ -123,6 +172,13 @@ private JsonObject getElements() throws JsonException {
123172
return builder.build();
124173
}
125174

175+
private JsonObject getObjectForElement(AdditionalRootElement element) {
176+
JsonObjectBuilder result = bf.createObjectBuilder();
177+
result.add("name", element.getElementName());
178+
addDescription(result, element.getDocString());
179+
return result.build();
180+
}
181+
126182
private Map<String, List<FrankElement>> getAllElements() {
127183
return model
128184
.getAllElements()
@@ -376,6 +432,8 @@ private JsonObject getElementNames() {
376432
final JsonObjectBuilder result = bf.createObjectBuilder();
377433
result.add(Constants.MODULE_ELEMENT_NAME, getElementName(Constants.MODULE_ELEMENT_NAME, Constants.MODULE_ELEMENT_NAME, Map.of("FrankDocGroup", List.of(Constants.MODULE_ELEMENT_FRANK_DOC_GROUP))));
378434

435+
additionalRootElements.forEach(element -> result.add(element.getElementName(), getElementName(element.getElementName(), element.getElementName(), Map.of("FrankDocGroup", List.of(Constants.MODULE_ELEMENT_FRANK_DOC_GROUP)))));
436+
379437
Map<String, List<FrankElement>> elements = getAllElements();
380438
for (List<FrankElement> elementsPerClass : elements.values()) {
381439
for (FrankElement frankElement : elementsPerClass) {

frank-doc-doclet/src/main/java/org/frankframework/frankdoc/FrankDocXsdFactory.java

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
import org.frankframework.frankdoc.model.TextConfigChild;
6767
import org.frankframework.frankdoc.util.LogUtil;
6868
import org.frankframework.frankdoc.util.XmlBuilder;
69+
import org.frankframework.frankdoc.wrapper.AdditionalRootElement;
6970

7071
/**
7172
* This class writes the XML schema document (XSD) that checks the validity of a
@@ -167,6 +168,11 @@ public class FrankDocXsdFactory implements AttributeReuseManagerCallback {
167168
private final Set<String> definedAttributeEnumInstances = new HashSet<>();
168169
private final AttributeTypeStrategy attributeTypeStrategy;
169170
private final String frankFrameworkVersion;
171+
/**
172+
* Map {@link AdditionalRootElement} enum values for Frank!Framework Java types that have been encountered to the XSD types
173+
* that they map to.
174+
*/
175+
private final Map<AdditionalRootElement, String> additionalRootElements = new EnumMap<>(AdditionalRootElement.class);
170176

171177
public FrankDocXsdFactory(FrankDocModel model, AttributeTypeStrategy attributeTypeStrategy, String frankFrameworkVersion, String startClassName, XsdVersion version) {
172178
this.model = model;
@@ -230,6 +236,7 @@ private void defineElements(FrankElement startElement) {
230236
default:
231237
throw new IllegalArgumentException("Cannot happen - all case labels should be in switch");
232238
}
239+
additionalRootElements.forEach(this::createAdditionalRootElement);
233240
}
234241

235242
/** Defines XML element {@code <Configuration>} */
@@ -261,6 +268,26 @@ private void addReferencedEntityRoot(FrankElement startElement) {
261268
attributeReuseManager.addAttribute(anyOther, complexType);
262269
}
263270

271+
/**
272+
* Defines XML element {@code <PipelinePart/>} or other additional root elements
273+
*/
274+
private void createAdditionalRootElement(AdditionalRootElement additionalRootElement, String referencedElementGroupName) {
275+
String elementName = additionalRootElement.getElementName();
276+
log.trace("Creating element [{}]", elementName);
277+
XmlBuilder startElementBuilder = createElementWithName(elementName);
278+
xsdElements.add(startElementBuilder);
279+
addDocumentation(startElementBuilder, additionalRootElement.getDocString());
280+
XmlBuilder complexType = addComplexType(startElementBuilder);
281+
XmlBuilder sequence = addSequence(complexType);
282+
FrankDocXsdFactoryXmlUtils.addGroupRef(sequence, referencedElementGroupName, "0", "unbounded");
283+
284+
log.trace("Adding attribute active explicitly to [{}] and also any attribute in another namespace", elementName);
285+
XmlBuilder attributeActive = AttributeTypeStrategy.createAttributeActive();
286+
attributeReuseManager.addAttribute(attributeActive, complexType);
287+
XmlBuilder anyOther = createAnyOtherNamespaceAttribute();
288+
attributeReuseManager.addAttribute(anyOther, complexType);
289+
}
290+
264291
private String getConfigChildGroupOf(FrankElement frankElement) {
265292
// TODO: Add cumulative group if the start element (typically <Configuration>) has
266293
// ancestors with config children. Or even take a declared/cumulative group of an ancestor
@@ -521,7 +548,7 @@ private void addConfigChildren(ElementBuildingStrategy elementBuildingStrategy,
521548
private void addConfigChildrenNoPluralConfigChildSets(ElementBuildingStrategy elementBuildingStrategy, FrankElement frankElement) {
522549
Consumer<GroupCreator.Callback<ConfigChild>> cumulativeGroupTrigger =
523550
ca -> frankElement.walkCumulativeConfigChildren(ca, version.getChildSelector(), version.getChildRejector());
524-
new GroupCreator<ConfigChild>(frankElement, version.getHasRelevantChildrenPredicate(ConfigChild.class), cumulativeGroupTrigger, new GroupCreator.Callback<ConfigChild>() {
551+
new GroupCreator<>(frankElement, version.getHasRelevantChildrenPredicate(ConfigChild.class), cumulativeGroupTrigger, new GroupCreator.Callback<>() {
525552
private XmlBuilder cumulativeBuilder;
526553
private String cumulativeGroupName;
527554

@@ -618,12 +645,7 @@ private void addObjectConfigChild(XmlBuilder context, ObjectConfigChild child) {
618645

619646
private boolean isNoElementTypeNeeded(ElementRole role) {
620647
ElementType elementType = role.getElementType();
621-
if(elementType.isFromJavaInterface()) {
622-
return false;
623-
}
624-
else {
625-
return true;
626-
}
648+
return !elementType.isFromJavaInterface();
627649
}
628650

629651
private void addConfigChildSingleReferredElement(XmlBuilder context, ObjectConfigChild child) {
@@ -633,6 +655,7 @@ private void addConfigChildSingleReferredElement(XmlBuilder context, ObjectConfi
633655
log.trace("Omitting config child [{}] because of name conflict", child::toString);
634656
return;
635657
}
658+
636659
String referredXsdElementName = elementInType.getXsdElementName(role);
637660
log.trace("Config child appears as element reference to FrankElement [{}], XSD element [{}]", elementInType::getFullName, () -> referredXsdElementName);
638661
addElement(context, referredXsdElementName, getTypeName(referredXsdElementName), getMinOccurs(child), getMaxOccurs(child));
@@ -707,6 +730,15 @@ private void requestElementGroup(List<ElementRole> roles) {
707730
XmlBuilder choice = addChoice(group);
708731
addElementGroupGenericOption(choice, roles);
709732
addElementGroupOptions(choice, roles);
733+
String elementTypeSimpleName = key.iterator().next().getElementTypeSimpleName();
734+
735+
// The "Include" element should be manually added to the XSD as part of the "xs:choice" for this element-group.
736+
// We cannot have it added in a simpler way from the Frank!Framework code, as it will then not be valid as
737+
// part of the choice of elements in this element-group, but as a separate group of elements.
738+
if (model.shouldGetIncludeElement(elementTypeSimpleName)) {
739+
addElement(choice, "Include", "IncludeType", "0", "unbounded");
740+
additionalRootElements.put(model.getAdditionalRootElement(elementTypeSimpleName), groupName);
741+
}
710742
} else {
711743
log.trace("Element group already exists");
712744
}
@@ -734,7 +766,8 @@ private void addElementGroupOptions(XmlBuilder context, List<ElementRole> roles)
734766
}
735767

736768
private void defineElementGroupBaseUnchecked(ElementRole role) {
737-
XmlBuilder group = createGroup(role.createXsdElementName(ELEMENT_GROUP_BASE));
769+
String groupName = role.createXsdElementName(ELEMENT_GROUP_BASE);
770+
XmlBuilder group = createGroup(groupName);
738771
xsdComplexItems.add(group);
739772
XmlBuilder choice = addChoice(group);
740773
List<FrankElement> frankElementOptions = role.getMembers().stream()

frank-doc-doclet/src/main/java/org/frankframework/frankdoc/model/FrankDocModel.java

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,34 +16,52 @@
1616

1717
package org.frankframework.frankdoc.model;
1818

19-
import lombok.Getter;
19+
import static org.frankframework.frankdoc.model.ElementChild.ALL;
20+
21+
import java.io.IOException;
22+
import java.io.Reader;
23+
import java.net.URL;
24+
import java.util.ArrayList;
25+
import java.util.Arrays;
26+
import java.util.Collections;
27+
import java.util.Comparator;
28+
import java.util.HashMap;
29+
import java.util.HashSet;
30+
import java.util.LinkedHashMap;
31+
import java.util.List;
32+
import java.util.Map;
33+
import java.util.Map.Entry;
34+
import java.util.Set;
35+
import java.util.stream.Collectors;
36+
2037
import org.apache.commons.lang3.StringUtils;
2138
import org.apache.commons.text.WordUtils;
2239
import org.apache.logging.log4j.Logger;
2340
import org.frankframework.frankdoc.Constants;
2441
import org.frankframework.frankdoc.Utils;
25-
import org.frankframework.frankdoc.feature.*;
42+
import org.frankframework.frankdoc.feature.Default;
2643
import org.frankframework.frankdoc.feature.Deprecated;
44+
import org.frankframework.frankdoc.feature.Description;
45+
import org.frankframework.frankdoc.feature.ExcludeFromTypeFeature;
46+
import org.frankframework.frankdoc.feature.Mandatory;
47+
import org.frankframework.frankdoc.feature.Notes;
2748
import org.frankframework.frankdoc.feature.Optional;
49+
import org.frankframework.frankdoc.feature.Protected;
50+
import org.frankframework.frankdoc.feature.Reference;
51+
import org.frankframework.frankdoc.feature.Reintroduce;
2852
import org.frankframework.frankdoc.model.AncestorMethodBrowser.References;
2953
import org.frankframework.frankdoc.properties.Group;
3054
import org.frankframework.frankdoc.properties.PropertyParser;
3155
import org.frankframework.frankdoc.util.LogUtil;
56+
import org.frankframework.frankdoc.wrapper.AdditionalRootElement;
3257
import org.frankframework.frankdoc.wrapper.FrankClass;
3358
import org.frankframework.frankdoc.wrapper.FrankClassRepository;
3459
import org.frankframework.frankdoc.wrapper.FrankDocException;
3560
import org.frankframework.frankdoc.wrapper.FrankMethod;
3661
import org.xml.sax.InputSource;
3762
import org.xml.sax.SAXException;
3863

39-
import java.io.IOException;
40-
import java.io.Reader;
41-
import java.net.URL;
42-
import java.util.*;
43-
import java.util.Map.Entry;
44-
import java.util.stream.Collectors;
45-
46-
import static org.frankframework.frankdoc.model.ElementChild.ALL;
64+
import lombok.Getter;
4765

4866
public class FrankDocModel {
4967
private static final Logger log = LogUtil.getLogger(FrankDocModel.class);
@@ -188,6 +206,14 @@ public boolean hasType(String typeName) {
188206
return allTypes.containsKey(typeName);
189207
}
190208

209+
public boolean shouldGetIncludeElement(String classSimpleName) {
210+
return classRepository.isIncludeTypePresent() && AdditionalRootElement.VALUE_BY_TYPE.containsKey(classSimpleName);
211+
}
212+
213+
public AdditionalRootElement getAdditionalRootElement(String classSimpleName) {
214+
return AdditionalRootElement.VALUE_BY_TYPE.get(classSimpleName);
215+
}
216+
191217
void buildDescendants() throws Exception {
192218
log.trace("Enter");
193219
boolean addedDescendants;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
Copyright 2025 WeAreFrank!
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package org.frankframework.frankdoc.wrapper;
17+
18+
import java.util.Arrays;
19+
import java.util.Map;
20+
import java.util.stream.Collectors;
21+
22+
import lombok.Getter;
23+
24+
/**
25+
* This enum class allows for an extensible way to add multiple additional root elements in the XML that can be included in
26+
* a configuration.
27+
*/
28+
public enum AdditionalRootElement {
29+
PIPELINE_PART("PipelinePart", "IPipe", "Wrapper element to help create reusable parts of a pipeline");
30+
31+
/**
32+
* Lookup-map to find the Enum value by the (simple) classname of a type being processed.
33+
*/
34+
public static final Map<String, AdditionalRootElement> VALUE_BY_TYPE = Arrays.stream(values())
35+
.collect(Collectors.toMap(AdditionalRootElement::getTypeName, r -> r));
36+
37+
/**
38+
* Name of the element that should be added as root-level element in the XSD or JSON.
39+
*/
40+
private final @Getter String elementName;
41+
/**
42+
* Name of the type that, if present, should trigger the generation of the corresponding root element.
43+
* The XSD typename is dynamically generated for the type and not part of the enum definition.
44+
*/
45+
private final @Getter String typeName;
46+
/**
47+
* Doc string that should be added to the XSD or JSON for this top-level element.
48+
*/
49+
private final @Getter String docString;
50+
51+
AdditionalRootElement(String elementName, String typeName, String docString) {
52+
this.elementName = elementName;
53+
this.typeName = typeName;
54+
this.docString = docString;
55+
}
56+
}

0 commit comments

Comments
 (0)