Skip to content

Commit

Permalink
proxies.xml: Allow XML Content in Mixed Configuration Elements (#1388)
Browse files Browse the repository at this point in the history
* Cleanup

* Cleanup2
  • Loading branch information
predic8 authored Dec 9, 2024
1 parent f5cb56f commit 317d9cf
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 81 deletions.
10 changes: 10 additions & 0 deletions annot/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
98 changes: 79 additions & 19 deletions annot/src/main/java/com/predic8/membrane/annot/AnnotUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) +
"</" + n.getNodeName() + ">";
}

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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -43,8 +33,7 @@ public Parsers(ProcessingEnvironment processingEnv) {
public void writeParserDefinitior(Model m) throws IOException {

for (MainInfo main : m.getMains()) {
List<Element> sources = new ArrayList<>();
sources.addAll(main.getInterceptorElements());
List<Element> sources = new ArrayList<>(main.getInterceptorElements());
sources.add(main.getElement());

try {
Expand Down Expand Up @@ -189,8 +178,7 @@ protected void doParse(Element element, ParserContext parserContext, BeanDefinit
}

bw.write(
" }\r\n" +
"");
" }\r\n");

bw.write(
"""
Expand All @@ -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"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -88,7 +77,9 @@ private void assembleXSD(Writer w, Model m, MainInfo main) throws IOException, P
</xsd:simpleType>
""").formatted(namespace, namespace, Schemas.class.getName()).replace("\n", "\r\n");
w.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + xsdHeaders);
w.append("""
<?xml version="1.0" encoding="UTF-8"?>
""").append(xsdHeaders);
assembleDeclarations(w, m, main);
w.append("</xsd:schema>");
}
Expand All @@ -101,22 +92,21 @@ 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("<xsd:element name=\""+ i.getAnnotation().name() +"\">\r\n");
w.append("<xsd:element name=\"").append(i.getAnnotation().name()).append("\">\r\n");
assembleDocumentation(w, i);
w.append("<xsd:complexType>\r\n");
footer = """
</xsd:complexType>\r
</xsd:element>\r
""";
} else {
w.append("<xsd:complexType name=\""+ i.getXSDTypeName(m) +"\">\r\n");
w.append("<xsd:complexType name=\"").append(i.getXSDTypeName(m)).append("\">\r\n");
footer = "</xsd:complexType>\r\n";
}

w.append("<xsd:complexContent " + (i.getAnnotation().mixed() ? "mixed=\"true\"" : "") + ">\r\n" +
"<xsd:extension base=\"beans:identifiedType\">\r\n");
w.append("<xsd:complexContent ").append(i.getAnnotation().mixed() ? "mixed=\"true\"" : "").append(">\r\n").append("<xsd:extension base=\"beans:identifiedType\">\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());
Expand Down Expand Up @@ -147,6 +137,12 @@ private void assembleElementInfo(Writer w, Model m, MainInfo main, ElementInfo i
w.append("<xsd:any namespace=\"##other\" processContents=\"strict\" />\r\n");
w.append("</xsd:choice>\r\n");
}

// For mixed content like XML in template interceptor: e.g. <template> <foo>123</foo></template>
if (i.getAnnotation().mixed()) {
w.append("<xsd:any minOccurs=\"0\" maxOccurs=\"unbounded\" processContents=\"lax\"/>");
}

w.append("</xsd:sequence>\r\n");
for (AttributeInfo ai : i.getAis())
if (!ai.getXMLName().equals("id"))
Expand Down Expand Up @@ -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);
}
Expand Down
94 changes: 94 additions & 0 deletions annot/src/test/java/com/predic8/membrane/annot/AnnotUtilsTest.java
Original file line number Diff line number Diff line change
@@ -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("<foo>123</foo>", str("""
<e><foo>123</foo></e>
"""));
}

@Test
void empty() throws Exception {
assertEquals("<foo></foo>", str("""
<e><foo/></e>
"""));
}

@Test
void str() throws Exception {
assertEquals("123", str("""
<e>123</e>
"""));
}

@Test
void nothing() throws Exception {
assertEquals("", str("""
<e></e>
"""));
}

@Test
void attr() throws Exception {
assertEquals("<foo bar=\"3\"></foo>", str("""
<e><foo bar="3"/></e>
"""));
}

@Test
void nested() throws Exception {
assertEquals("<a><b><c></c></b></a>", str("""
<e><a><b><c></c></b></a></e>
"""));
}

@Test
void mixed() throws Exception {
assertEquals("1<a>2<b>3<c>4</c>5</b>6</a>7", str("""
<e>1<a>2<b>3<c>4</c>5</b>6</a>7</e>
"""));
}

@Test
void ns() throws Exception {
assertEquals("<foo ns=\"bar\"></foo>", str("""
<e><foo ns="bar"></foo></e>
"""));
}

@Test
void cdata() throws Exception {
assertEquals("1<foo ns=\"bar\">234</foo>5", str("""
<e>1<foo ns="bar">2<![CDATA[3]]>4</foo>5</e>
"""));
}

@Test
void entities() throws Exception {
assertEquals("1<foo>> < &</foo>2", str("""
<e>1<foo>&gt; &lt; &amp;</foo>2</e>
"""));
}


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();
}
}
Loading

0 comments on commit 317d9cf

Please sign in to comment.