Skip to content

Commit

Permalink
Merge pull request #30 from prowide/feature/24-escape-handler
Browse files Browse the repository at this point in the history
MxWriteConfiguration and EscapeHandler API to tweak the serialization
  • Loading branch information
zubri authored Jun 4, 2021
2 parents a9bd8d3 + 5f62b9f commit e5c4559
Show file tree
Hide file tree
Showing 15 changed files with 497 additions and 102 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

RELEASE 9.1.7 - June 2021
* (GH-26) Fixed AppHdr JSON conversion with explicit new namespace field as discriminator
* (GH-24) Added a new MxWriteConfiguration and EscapeHandler API to tweak the serialization into XML

RELEASE 9.1.6 - April 2021
* (GH-17|JIRA-506) Enhanced the XML format in the serializing, spaces and line breaks
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ task writePom {
}

dependencies {
// included build (keep in sync with the latest Prowide Core version)
// included build (keep in sync with the latest Prowide Core version, this goes to Maven transitive dependency)
api 'com.prowidesoftware:pw-swift-core:SRU2020-9.1.4'
implementation 'org.apache.commons:commons-lang3:3.8.1'
implementation 'com.google.code.gson:gson:2.8.2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
Expand All @@ -66,14 +67,17 @@
* @since 7.6
*/
public abstract class AbstractMX extends AbstractMessage implements IDocument, JsonSerializable {
public static final String DOCUMENT_LOCALNAME = "Document";
private static final transient Logger log = Logger.getLogger(AbstractMX.class.getName());

public static final String DOCUMENT_LOCALNAME = "Document";

/**
* Default root element when an MX is serialized as XML including both AppHdr and Document
*
* @since 8.0.2
*/
public static String DEFAULT_ROOT_ELEMENT = "RequestPayload";

/**
* Header portion of the message payload, common to all specific MX subclasses.
* This information is required before opening the actual message to process the content properly.
Expand All @@ -93,8 +97,20 @@ protected AbstractMX(final AppHdr appHdr) {
this.appHdr = appHdr;
}

/**
* @deprecated use {@link #message(String, AbstractMX, Class[], String, boolean, EscapeHandler)} instead
*/
@Deprecated
@ProwideDeprecated(phase2 = TargetYear.SRU2022)
protected static String message(final String namespace, final AbstractMX obj, @SuppressWarnings("rawtypes") final Class[] classes, final String prefix, boolean includeXMLDeclaration) {
return MxWriteImpl.write(namespace, obj, classes, prefix, includeXMLDeclaration);
return message(namespace, obj, classes, prefix, includeXMLDeclaration, null);
}

/**
* @since 9.1.7
*/
protected static String message(final String namespace, final AbstractMX obj, @SuppressWarnings("rawtypes") final Class[] classes, final String prefix, boolean includeXMLDeclaration, EscapeHandler escapeHandler) {
return MxWriteImpl.write(namespace, obj, classes, prefix, includeXMLDeclaration, escapeHandler);
}

@SuppressWarnings({"rawtypes", "unchecked"})
Expand Down Expand Up @@ -193,7 +209,7 @@ protected static <T> T fromJson(String json, Class<T> classOfT) {
final Gson gson = new GsonBuilder()
.registerTypeAdapter(AbstractMX.class, new AbstractMXAdapter())
.registerTypeAdapter(XMLGregorianCalendar.class, new XMLGregorianCalendarAdapter())
.registerTypeAdapter(AppHdr.class, new AppHdrAdapter())
.registerTypeAdapter(AppHdr.class, new AppHdrAdapter())
.create();
return gson.fromJson(json, classOfT);
}
Expand All @@ -209,7 +225,7 @@ public static AbstractMX fromJson(String json) {
final Gson gson = new GsonBuilder()
.registerTypeAdapter(AbstractMX.class, new AbstractMXAdapter())
.registerTypeAdapter(XMLGregorianCalendar.class, new XMLGregorianCalendarAdapter())
.registerTypeAdapter(AppHdr.class, new AppHdrAdapter())
.registerTypeAdapter(AppHdr.class, new AppHdrAdapter())
.create();
return gson.fromJson(json, AbstractMX.class);
}
Expand Down Expand Up @@ -263,116 +279,152 @@ public static AbstractMX fromJson(String json) {

/**
* Get this message as an XML string.
*
* <p>If the header is present, then 'AppHdr' and 'Document' elements will be wrapped under a
* {@link #DEFAULT_ROOT_ELEMENT}
* <br>Both header and documents are generated with the corresponding namespaces and by default the prefix 'h' is
* used for the header and the prefix 'Doc' for the document.
* {@link #DEFAULT_ROOT_ELEMENT}. Both header and document are generated with the corresponding namespaces and by
* default the prefix 'h' is used for the header and the prefix 'Doc' for the document.
* <br>For more serialization options see {@link #message(MxWriteConfiguration)}
* <br>To serialize only the header or the document (without header) see {@link #header()} and {@link #document()}
*
* @see #message(String, boolean)
* @return the XML content or null if errors occur during serialization
* @since 7.7
*/
@Override
public String message() {
return message(null, true);
return message((MxWriteConfiguration) null);
}

/**
* @deprecated use {@link #message(MxWriteConfiguration)} instead
*/
@Deprecated
@ProwideDeprecated(phase2 = TargetYear.SRU2022)
public String message(final String rootElement, boolean includeXMLDeclaration) {
MxWriteConfiguration conf = new MxWriteConfiguration();
conf.rootElement = rootElement;
conf.includeXMLDeclaration = includeXMLDeclaration;
return message(conf);
}

/**
* @deprecated use {@link #message(MxWriteConfiguration)} instead
*/
@Deprecated
@ProwideDeprecated(phase2 = TargetYear.SRU2022)
public String message(final String rootElement) {
MxWriteConfiguration conf = new MxWriteConfiguration();
conf.rootElement = rootElement;
return message(conf);
}

/**
* Get this message as an XML string.
*
* <p>If the business header is set, the created XML will include both the 'AppHdr' and the 'Document' elements,
* under a the indicated or default root element.
* <br>If the header is not present, the created XMl will only include the 'Document'.
* <br>Both 'AppHdr' and 'Document' are generated with namespace declaration and default prefixes 'h' and 'Doc'
* respectively.
* under a the indicated or default root element. If the header is not present, the created XMl will only include
* the 'Document'. Both 'AppHdr' and 'Document' are generated with namespace declaration and if optional prefixes
* if present in the configuration.
*
* <p>IMPORTANT: The name of the envelope element that binds a Header to the message to which it applies is
* implementation/network specific. The header root element ‘AppHdr’ and the ISO 20022 MessageDefinition
* root element ‘Document’ must always be sibling elements in any XML document, with the AppHdr element preceding
* the Document element.
*
* @param rootElement optional specification of the root element if not provided {@link #DEFAULT_ROOT_ELEMENT} is used
* @param includeXMLDeclaration true to include the XML declaration
* @return header serialized into XML string or null if the header is not set or errors occur during serialization
* @return created XML
* @since 7.8
* @param conf specific options for the serialization or null to use the default parameters
* @return the XML content or null if errors occur during serialization
*/
public String message(final String rootElement, boolean includeXMLDeclaration) {
String root = rootElement != null ? rootElement : DEFAULT_ROOT_ELEMENT;
public String message(MxWriteConfiguration conf) {
MxWriteConfiguration usableConf = conf != null? conf : new MxWriteConfiguration();
String root = usableConf.rootElement;
StringBuilder xml = new StringBuilder();
if (includeXMLDeclaration) {
if (usableConf.includeXMLDeclaration) {
xml.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
}
final String header = header("h", false);
final String header = header(usableConf.headerPrefix, false, usableConf.escapeHandler);
if (header != null) {
xml.append("<" + root + ">\n");
xml.append(header + "\n");
xml.append("<").append(root).append(">\n");
xml.append(header).append("\n");
}
xml.append(document("Doc", false) + "\n");
xml.append(document(usableConf.documentPrefix, false, usableConf.escapeHandler)).append("\n");
if (header != null) {
xml.append("</" + root + ">");
xml.append("</").append(root).append(">");
}
return xml.toString();
}

/**
* Same as {@link #message(String, boolean)} with includeXMLDeclaration set to true
* Get this message AppHdr as an XML string.
*
* <p>The XML will not include the XML declaration, will bind the namespace to all elements without prefix and will
* use the default escape handler. For more serialization options use {@link #header(String, boolean, EscapeHandler)}
*
* @return the serialized header or null if header is not set or errors occur during serialization
* @since 7.8
*/
public String message(final String rootElement) {
return message(rootElement, true);
public String header() {
return header(null, false, null);
}

/**
* Get this message AppHdr as an XML string.
* <p>The XML will not include the XML declaration, and will include de namespace as default (without prefix).
*
* @return the serialized header or null if header is not set or errors occur during serialization
* @see #header(String, boolean)
* @since 7.8
* @deprecated use {@link #header(String, boolean, EscapeHandler)} instead
*/
public String header() {
return header(null, false);
@Deprecated
@ProwideDeprecated(phase2 = TargetYear.SRU2022)
public String header(final String prefix, boolean includeXMLDeclaration) {
return header(prefix, includeXMLDeclaration, null);
}

/**
* Get this message AppHdr as an XML string.
*
* @param prefix optional prefix for namespace (empty by default)
* @param includeXMLDeclaration true to include the XML declaration
* @param escapeHandler a specific escape handler for the header elements content
* @return header serialized into XML string or null if the header is not set or errors occur during serialization
* @since 7.8
* @since 9.1.7
*/
public String header(final String prefix, boolean includeXMLDeclaration) {
public String header(final String prefix, boolean includeXMLDeclaration, EscapeHandler escapeHandler) {
if (this.appHdr != null) {
return this.appHdr.xml(prefix, includeXMLDeclaration);
return this.appHdr.xml(prefix, includeXMLDeclaration, escapeHandler);
} else {
return null;
}
}

/**
* Get this message Document as an XML string.
* <p>The XML will include the XML declaration, and will use "Doc" as prefix for the elements.
*
* <p>The XML will not include the XML declaration, will bind the namespace to all elements using "Doc" as default
* prefix and will use the default escape handler. For more serialization options use
* {@link #document(String, boolean, EscapeHandler)}
*
* @return document serialized into XML string or null if errors occur during serialization
* @see #document(String, boolean)
* @since 7.8
*/
public String document() {
return document("Doc", true);
}

/**
* @deprecated use {@link #document(String, boolean, EscapeHandler)} instead
*/
@Deprecated
@ProwideDeprecated(phase2 = TargetYear.SRU2022)
public String document(final String prefix, boolean includeXMLDeclaration) {
return document(prefix, includeXMLDeclaration, null);
}

/**
* Get this message Document as an XML string.
*
* @param prefix optional prefix for namespace (empty by default)
* @param includeXMLDeclaration true to include the XML declaration
* @param escapeHandler a specific escape handler for the document elements content
* @return document serialized into XML string or null if errors occur during serialization
* @since 7.8
* @since 9.1.7
*/
public String document(final String prefix, boolean includeXMLDeclaration) {
return message(getNamespace(), this, getClasses(), prefix, includeXMLDeclaration);
public String document(final String prefix, boolean includeXMLDeclaration, EscapeHandler escapeHandler) {
return message(getNamespace(), this, getClasses(), prefix, includeXMLDeclaration, escapeHandler);
}

/**
Expand All @@ -392,7 +444,7 @@ public Source xmlSource() {
}

/**
* Writes the message document content into a file in XML format (headers not included).
* Writes the message content into a file in XML format.
*
* @param file a not null file to write, if it does not exists, it will be created
* @since 7.7
Expand All @@ -417,7 +469,7 @@ public void write(final File file) throws IOException {
*/
public void write(final OutputStream stream) throws IOException {
Validate.notNull(stream, "the stream to write cannot be null");
stream.write(message().getBytes("UTF-8"));
stream.write(message().getBytes(StandardCharsets.UTF_8));
}

/**
Expand Down Expand Up @@ -521,7 +573,7 @@ public String toJson() {
final Gson gson = new GsonBuilder()
.registerTypeAdapter(AbstractMX.class, new AbstractMXAdapter())
.registerTypeAdapter(XMLGregorianCalendar.class, new XMLGregorianCalendarAdapter())
.registerTypeAdapter(AppHdr.class, new AppHdrAdapter())
.registerTypeAdapter(AppHdr.class, new AppHdrAdapter())
.setPrettyPrinting()
.create();
// we use AbstractMX and not this.getClass() in order to force usage of the adapter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public interface AppHdr {
* Get this header as an XML string.
* <p>The implementation uses {@link #xml(String, boolean)} with no prefix and no XML declaration.
*
* @return header serialized into XML string or null if neither header version is present
* @return header serialized into XML string or null in case of unexpected error
*/
default String xml() {
return xml(null, false);
Expand All @@ -125,16 +125,31 @@ default String xml() {
*
* @param prefix optional prefix for namespace (empty by default)
* @param includeXMLDeclaration true to include the XML declaration (false by default)
* @return header serialized into XML string or null in case of unexpected error
*/
String xml(final String prefix, boolean includeXMLDeclaration);

/**
* Get this header as an XML string.
*
* @param prefix optional prefix for namespace (empty by default)
* @param includeXMLDeclaration true to include the XML declaration (false by default)
* @param escapeHandler a specific escape handler for the header elements content
* @return header serialized into XML string or null in case of unexpected error
* @since 9.1.7
*/
default String xml(final String prefix, boolean includeXMLDeclaration, EscapeHandler escapeHandler) {
return xml(prefix, includeXMLDeclaration);
}

/**
* Gets the header as an Element object.
*/
Element element();

/**
* Gets the specific namespace of the header
*
* @return default implementation returns null
* @since 9.1.7
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,14 +185,20 @@ public void setCreationDate(boolean overwrite) {
}

@Override
public String xml(String prefix, boolean includeXMLDeclaration) {
public String xml(final String prefix, boolean includeXMLDeclaration) {
return xml(prefix, includeXMLDeclaration, null);
}

@Override
public String xml(String prefix, boolean includeXMLDeclaration, EscapeHandler escapeHandler) {
try {
JAXBContext context = JAXBContext.newInstance(BusinessApplicationHeaderV01Impl.class);
final Marshaller marshaller = context.createMarshaller();

final StringWriter sw = new StringWriter();
JAXBElement<BusinessApplicationHeaderV01Impl> element = new JAXBElement(new QName(NAMESPACE, AppHdr.HEADER_LOCALNAME), BusinessApplicationHeaderV01Impl.class, null, this);
marshaller.marshal(element, new XmlEventWriter(sw, prefix, includeXMLDeclaration, AppHdr.HEADER_LOCALNAME));
XmlEventWriter eventWriter = new XmlEventWriter(sw, prefix, includeXMLDeclaration, AppHdr.HEADER_LOCALNAME, escapeHandler);
marshaller.marshal(element, eventWriter);
return sw.getBuffer().toString();

} catch (JAXBException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,13 +186,19 @@ public void setCreationDate(boolean overwrite) {

@Override
public String xml(String prefix, boolean includeXMLDeclaration) {
return xml(prefix, includeXMLDeclaration, null);
}

@Override
public String xml(String prefix, boolean includeXMLDeclaration, EscapeHandler escapeHandler) {
try {
JAXBContext context = JAXBContext.newInstance(BusinessApplicationHeaderV02Impl.class);
final Marshaller marshaller = context.createMarshaller();

final StringWriter sw = new StringWriter();
JAXBElement<BusinessApplicationHeaderV02Impl> element = new JAXBElement(new QName(NAMESPACE, AppHdr.HEADER_LOCALNAME), BusinessApplicationHeaderV02Impl.class, null, this);
marshaller.marshal(element, new XmlEventWriter(sw, prefix, includeXMLDeclaration, AppHdr.HEADER_LOCALNAME));
XmlEventWriter eventWriter = new XmlEventWriter(sw, prefix, includeXMLDeclaration, AppHdr.HEADER_LOCALNAME, escapeHandler);
marshaller.marshal(element, eventWriter);
return sw.getBuffer().toString();

} catch (JAXBException e) {
Expand Down
Loading

0 comments on commit e5c4559

Please sign in to comment.