From 317d9cfd61f3154c1a0450453ac29e02b6cd71ee Mon Sep 17 00:00:00 2001 From: Thomas Bayer Date: Mon, 9 Dec 2024 16:15:25 +0100 Subject: [PATCH] proxies.xml: Allow XML Content in Mixed Configuration Elements (#1388) * Cleanup * Cleanup2 --- annot/pom.xml | 10 ++ .../predic8/membrane/annot/AnnotUtils.java | 98 +++++++++++++++---- .../membrane/annot/generator/Parsers.java | 35 +++---- .../membrane/annot/generator/Schemas.java | 54 +++++----- .../membrane/annot/AnnotUtilsTest.java | 94 ++++++++++++++++++ .../interceptor/flow/ResponseInterceptor.java | 13 +-- pom.xml | 2 +- 7 files changed, 225 insertions(+), 81 deletions(-) create mode 100644 annot/src/test/java/com/predic8/membrane/annot/AnnotUtilsTest.java diff --git a/annot/pom.xml b/annot/pom.xml index 536b9de53d..e18eeac674 100644 --- a/annot/pom.xml +++ b/annot/pom.xml @@ -35,6 +35,16 @@ org.springframework spring-context + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + diff --git a/annot/src/main/java/com/predic8/membrane/annot/AnnotUtils.java b/annot/src/main/java/com/predic8/membrane/annot/AnnotUtils.java index 2fc6ee5122..ee2c2d2292 100644 --- a/annot/src/main/java/com/predic8/membrane/annot/AnnotUtils.java +++ b/annot/src/main/java/com/predic8/membrane/annot/AnnotUtils.java @@ -13,27 +13,87 @@ limitations under the License. */ package com.predic8.membrane.annot; -import javax.lang.model.element.TypeElement; +import org.w3c.dom.*; + +import javax.lang.model.element.*; + +import static org.w3c.dom.Node.*; public class AnnotUtils { - public static String javaify(String s) { - StringBuilder sb = new StringBuilder(s); - sb.replace(0, 1, "" + Character.toUpperCase(s.charAt(0))); - return sb.toString(); - } - - public static String dejavaify(String s) { - StringBuilder sb = new StringBuilder(s); - sb.replace(0, 1, "" + Character.toLowerCase(s.charAt(0))); - return sb.toString(); - } - - public static String getRuntimeClassName(TypeElement element) { - if (element.getEnclosingElement() instanceof TypeElement) { - return getRuntimeClassName((TypeElement) element.getEnclosingElement()) + "$" + element.getSimpleName(); - } - return element.getQualifiedName().toString(); - } + public static final String QUOTE = "\""; + + public static String javaify(String s) { + StringBuilder sb = new StringBuilder(s); + sb.replace(0, 1, "" + Character.toUpperCase(s.charAt(0))); + return sb.toString(); + } + + public static String dejavaify(String s) { + StringBuilder sb = new StringBuilder(s); + sb.replace(0, 1, "" + Character.toLowerCase(s.charAt(0))); + return sb.toString(); + } + + public static String getRuntimeClassName(TypeElement element) { + if (element.getEnclosingElement() instanceof TypeElement) { + return getRuntimeClassName((TypeElement) element.getEnclosingElement()) + "$" + element.getSimpleName(); + } + return element.getQualifiedName().toString(); + } + + /** + * Takes a XML element an converts its child content into a string. In contrast to + * getTextcontent it renders all the elements and their attributes. It is used to get + * rid of CDATA sections in proxies.xml files. + * @param element DOM node + * @return XML String + */ + public static String getContentAsString(Node element) { + StringBuilder sb = new StringBuilder(); + NodeList nl = element.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) { + Node n = nl.item(i); + switch (n.getNodeType()) { + case ELEMENT_NODE: { + sb.append(elementToString(n)); + break; + } + case TEXT_NODE: + sb.append(n.getNodeValue()); + break; + case CDATA_SECTION_NODE: + sb.append(n.getTextContent()); + break; + } + } + return sb.toString(); + } + + private static String elementToString(Node n) { + return "<" + n.getNodeName() + + attrToString(n) + + ">" + + getContentAsString(n) + + ""; + } + + private static String attrToString(Node n) { + if (!n.hasAttributes()) + return ""; + + StringBuilder sb = new StringBuilder(); + + NamedNodeMap nm = n.getAttributes(); + for (int j = 0; j < nm.getLength(); j++) { + Node a = nm.item(j); + sb.append(" ").append(a.getNodeName()).append("="); + sb.append(QUOTE); + sb.append(a.getNodeValue()); + sb.append(QUOTE); + } + + return sb.toString(); + } } diff --git a/annot/src/main/java/com/predic8/membrane/annot/generator/Parsers.java b/annot/src/main/java/com/predic8/membrane/annot/generator/Parsers.java index b0be4a4745..f037957166 100644 --- a/annot/src/main/java/com/predic8/membrane/annot/generator/Parsers.java +++ b/annot/src/main/java/com/predic8/membrane/annot/generator/Parsers.java @@ -13,24 +13,14 @@ limitations under the License. */ package com.predic8.membrane.annot.generator; -import java.io.BufferedWriter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import javax.annotation.processing.FilerException; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; -import javax.tools.FileObject; - -import com.predic8.membrane.annot.AnnotUtils; -import com.predic8.membrane.annot.model.AttributeInfo; -import com.predic8.membrane.annot.model.ChildElementDeclarationInfo; -import com.predic8.membrane.annot.model.ChildElementInfo; -import com.predic8.membrane.annot.model.ElementInfo; -import com.predic8.membrane.annot.model.MainInfo; -import com.predic8.membrane.annot.model.Model; +import com.predic8.membrane.annot.*; +import com.predic8.membrane.annot.model.*; + +import javax.annotation.processing.*; +import javax.lang.model.element.*; +import javax.tools.*; +import java.io.*; +import java.util.*; public class Parsers { @@ -43,8 +33,7 @@ public Parsers(ProcessingEnvironment processingEnv) { public void writeParserDefinitior(Model m) throws IOException { for (MainInfo main : m.getMains()) { - List sources = new ArrayList<>(); - sources.addAll(main.getInterceptorElements()); + List sources = new ArrayList<>(main.getInterceptorElements()); sources.add(main.getElement()); try { @@ -189,8 +178,7 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit } bw.write( - " }\r\n" + - ""); + " }\r\n"); bw.write( """ @@ -213,8 +201,7 @@ protected void handleChildObject(Element ele, ParserContext parserContext, BeanD "}\r\n"); bw.write( - "}\r\n" + - ""); + "}\r\n"); } } catch (FilerException e) { if (e.getMessage().contains("Source file already created")) diff --git a/annot/src/main/java/com/predic8/membrane/annot/generator/Schemas.java b/annot/src/main/java/com/predic8/membrane/annot/generator/Schemas.java index 0156a2d1d0..96b10e961a 100644 --- a/annot/src/main/java/com/predic8/membrane/annot/generator/Schemas.java +++ b/annot/src/main/java/com/predic8/membrane/annot/generator/Schemas.java @@ -13,31 +13,20 @@ limitations under the License. */ package com.predic8.membrane.annot.generator; -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.Writer; -import java.util.ArrayList; -import java.util.List; - -import javax.annotation.processing.FilerException; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Element; -import javax.tools.FileObject; -import javax.tools.StandardLocation; - -import com.predic8.membrane.annot.ProcessingException; -import com.predic8.membrane.annot.model.AbstractJavadocedInfo; -import com.predic8.membrane.annot.model.AttributeInfo; -import com.predic8.membrane.annot.model.ChildElementInfo; -import com.predic8.membrane.annot.model.ElementInfo; -import com.predic8.membrane.annot.model.MainInfo; -import com.predic8.membrane.annot.model.Model; -import com.predic8.membrane.annot.model.doc.Doc; -import com.predic8.membrane.annot.model.doc.Doc.Entry; +import com.predic8.membrane.annot.*; +import com.predic8.membrane.annot.model.*; +import com.predic8.membrane.annot.model.doc.*; +import com.predic8.membrane.annot.model.doc.Doc.*; + +import javax.annotation.processing.*; +import javax.lang.model.element.*; +import javax.tools.*; +import java.io.*; +import java.util.*; public class Schemas { - private ProcessingEnvironment processingEnv; + private final ProcessingEnvironment processingEnv; public Schemas(ProcessingEnvironment processingEnv) { this.processingEnv = processingEnv; @@ -88,7 +77,9 @@ private void assembleXSD(Writer w, Model m, MainInfo main) throws IOException, P """).formatted(namespace, namespace, Schemas.class.getName()).replace("\n", "\r\n"); - w.append("\n" + xsdHeaders); + w.append(""" + + """).append(xsdHeaders); assembleDeclarations(w, m, main); w.append(""); } @@ -101,7 +92,7 @@ private void assembleDeclarations(Writer w, Model m, MainInfo main) throws Proce private void assembleElementDeclaration(Writer w, Model m, MainInfo main, ElementInfo i) throws ProcessingException, IOException { String footer; if (i.getAnnotation().topLevel()) { - w.append("\r\n"); + w.append("\r\n"); assembleDocumentation(w, i); w.append("\r\n"); footer = """ @@ -109,14 +100,13 @@ private void assembleElementDeclaration(Writer w, Model m, MainInfo main, Elemen \r """; } else { - w.append("\r\n"); + w.append("\r\n"); footer = "\r\n"; } - w.append("\r\n" + - "\r\n"); + w.append("\r\n").append("\r\n"); - if (i.getAnnotation().mixed() && i.getCeis().size() > 0) { + if (i.getAnnotation().mixed() && !i.getCeis().isEmpty()) { throw new ProcessingException( "@MCElement(..., mixed=true) and @MCTextContent is not compatible with @MCChildElement.", i.getElement()); @@ -147,6 +137,12 @@ private void assembleElementInfo(Writer w, Model m, MainInfo main, ElementInfo i w.append("\r\n"); w.append("\r\n"); } + + // For mixed content like XML in template interceptor: e.g. + if (i.getAnnotation().mixed()) { + w.append(""); + } + w.append("\r\n"); for (AttributeInfo ai : i.getAis()) if (!ai.getXMLName().equals("id")) @@ -188,7 +184,7 @@ private CharSequence xmlEscape(String string) { } private String capitalize(String key) { - if (key.length() == 0) + if (key.isEmpty()) return key; return Character.toUpperCase(key.charAt(0)) + key.substring(1); } diff --git a/annot/src/test/java/com/predic8/membrane/annot/AnnotUtilsTest.java b/annot/src/test/java/com/predic8/membrane/annot/AnnotUtilsTest.java new file mode 100644 index 0000000000..beded221b3 --- /dev/null +++ b/annot/src/test/java/com/predic8/membrane/annot/AnnotUtilsTest.java @@ -0,0 +1,94 @@ +package com.predic8.membrane.annot; + +import org.junit.jupiter.api.*; +import org.w3c.dom.*; + +import javax.xml.parsers.*; +import java.io.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class AnnotUtilsTest { + + @Test + void simple() throws Exception { + assertEquals("123", str(""" + 123 + """)); + } + + @Test + void empty() throws Exception { + assertEquals("", str(""" + + """)); + } + + @Test + void str() throws Exception { + assertEquals("123", str(""" + 123 + """)); + } + + @Test + void nothing() throws Exception { + assertEquals("", str(""" + + """)); + } + + @Test + void attr() throws Exception { + assertEquals("", str(""" + + """)); + } + + @Test + void nested() throws Exception { + assertEquals("", str(""" + + """)); + } + + @Test + void mixed() throws Exception { + assertEquals("1234567", str(""" + 1234567 + """)); + } + + @Test + void ns() throws Exception { + assertEquals("", str(""" + + """)); + } + + @Test + void cdata() throws Exception { + assertEquals("12345", str(""" + 1245 + """)); + } + + @Test + void entities() throws Exception { + assertEquals("1> < &2", str(""" + 1> < &2 + """)); + } + + + private String str(String s) throws Exception { + return AnnotUtils.getContentAsString(parse(s)); + } + + private Element parse(String xml) throws Exception { + ByteArrayInputStream bais = new ByteArrayInputStream(xml.getBytes()); + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document doc = builder.parse(bais); + return doc.getDocumentElement(); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/ResponseInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/ResponseInterceptor.java index e1baeb6609..07e8656b3d 100644 --- a/core/src/main/java/com/predic8/membrane/core/interceptor/flow/ResponseInterceptor.java +++ b/core/src/main/java/com/predic8/membrane/core/interceptor/flow/ResponseInterceptor.java @@ -14,15 +14,12 @@ package com.predic8.membrane.core.interceptor.flow; -import java.util.EnumSet; +import com.predic8.membrane.annot.*; +import com.predic8.membrane.core.exchange.*; +import com.predic8.membrane.core.interceptor.*; -import com.predic8.membrane.annot.MCElement; -import com.predic8.membrane.core.exchange.Exchange; -import com.predic8.membrane.core.interceptor.Interceptor; -import com.predic8.membrane.core.interceptor.Outcome; - -import static com.predic8.membrane.core.interceptor.Interceptor.Flow.RESPONSE; -import static com.predic8.membrane.core.interceptor.Outcome.CONTINUE; +import static com.predic8.membrane.core.interceptor.Interceptor.Flow.*; +import static com.predic8.membrane.core.interceptor.Outcome.*; /** * @description Interceptors are usually applied to requests and responses. By nesting interceptors into a diff --git a/pom.xml b/pom.xml index 38743fbf84..a1e79621b5 100644 --- a/pom.xml +++ b/pom.xml @@ -679,4 +679,4 @@ - + \ No newline at end of file