org.conqat.engine.service.shared.XmlSerializationUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of teamscale-commons Show documentation
Show all versions of teamscale-commons Show documentation
Provides common DTOs for Teamscale
/*-------------------------------------------------------------------------+
| |
| Copyright 2005-2011 the ConQAT Project |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
+-------------------------------------------------------------------------*/
package org.conqat.engine.service.shared;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.XStreamException;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.naming.NameCoder;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
/**
* Utility code for serializing to XML.
*/
public class XmlSerializationUtils {
/** We allow xstream to deserialize all classes from these packages. */
private static final String[] WHITELISTED_PACKAGES = new String[] { "com.teamscale.**", "org.conqat.**" };
/** We also allow to deserialize these specific classes. */
private static final String[] WHITELISTED_CLASSES = new String[] { "java.util.Collections$ReverseComparator" };
/**
* Patterns used for removing character escapes that are not allowed in XML 1.0.
* See here for the
* origin of the regex.
*/
private static final Pattern INVALID_CHARACTER_ESCAPE_PATTERN = Pattern
.compile("(0?[0-8bcef]|1[0-9]|d[89abcd]..|fff[ef]);", Pattern.CASE_INSENSITIVE);
/**
* Serializes the given object to XML.
*
* @param cdataAttributes
* all attributes with given name are converted to CDATA instead of
* "normal" strings.
*/
public static String serializeToXML(Object object, String... cdataAttributes) {
CDataAwareXStream xstream = new CDataAwareXStream();
return xstream.toXML(object, cdataAttributes);
}
/**
* Deserializes the given object from XML.
*
* @throws IOException
* if deserialization returns a different class or the underlying
* XStream library throws an {@link XStreamException} .
*/
public static T deserializeFromXML(String xml, Class expectedClass) throws IOException {
return deserializeFromXMLWithAliases(xml, expectedClass, Collections.emptyMap());
}
/**
* Identical to {@link #deserializeFromXML(String, Class)} with the additional
* option of adding class aliases.
*
* @param elementAliases
* Map of fully classified class name to actual class
*
* @throws IOException
* if deserialization returns a different class or the underlying
* XStream library throws an {@link XStreamException} .
*/
public static T deserializeFromXMLWithAliases(String xml, Class expectedClass,
Map> elementAliases) throws IOException {
XStream xstream = new CDataAwareXStream();
xstream.processAnnotations(expectedClass);
xstream.autodetectAnnotations(true);
for (Map.Entry> aliasEntry : elementAliases.entrySet()) {
xstream.alias(aliasEntry.getKey(), aliasEntry.getValue());
}
// remove any entities that are not allowed in XML 1.0 and replace with
// "?". In most cases where we use this code, this will not cause any
// problems.
xml = INVALID_CHARACTER_ESCAPE_PATTERN.matcher(xml).replaceAll("?");
try {
Object result = xstream.fromXML(xml);
if (result == null) {
// Leave as is
} else if (result instanceof List && expectedClass.isArray()) {
// Got a List but expected an array. Convert on the fly.
List> list = (List>) result;
result = list.toArray((Object[]) Array.newInstance(expectedClass.getComponentType(), list.size()));
} else if (result instanceof Object[] && List.class.isAssignableFrom(expectedClass)) {
// Got an array but expected a List. Convert on the fly.
Object[] array = (Object[]) result;
result = Arrays.asList(array);
}
return expectedClass.cast(result);
} catch (ClassCastException e) {
throw new IOException("Invalid class returned during deserialization. Expected " + expectedClass, e);
} catch (XStreamException e) {
throw new IOException(e);
}
}
/** An {@link XStream} that also support CDATA encoding. */
private static class CDataAwareXStream extends XStream {
/** Attributes that are to be encoded as CDATA. */
private final Set cdataAttributes;
/** Constructor. */
public CDataAwareXStream() {
this(new HashSet<>());
}
/**
* Constructor. This separate constructor is a trick to pass the same set to the
* object created for the super constructor and also hold a reference to it in
* an attribute. If we would construct the set at the declaration point of the
* attribute, this could not be passed to super, as super is called before
* attributes are initialized.
*/
private CDataAwareXStream(Set cdataAttributes) {
super(new CDataAwareDomDriver(cdataAttributes));
this.cdataAttributes = cdataAttributes;
setMode(XStream.NO_REFERENCES);
autodetectAnnotations(true);
XStream.setupDefaultSecurity(this);
allowTypesByWildcard(WHITELISTED_PACKAGES);
allowTypes(WHITELISTED_CLASSES);
}
/**
* Converts the given object to XML.
*
* @param cdataAttributes
* all attributes with given name are converted to CDATA instead of
* "normal" strings.
*/
public String toXML(Object object, String[] cdataAttributes) {
try {
this.cdataAttributes.addAll(Arrays.asList(cdataAttributes));
return toXML(object);
} finally {
this.cdataAttributes.clear();
}
}
}
/** A XStream driver that support CDATA output. */
private static class CDataAwareDomDriver extends DomDriver {
/** Attributes that are to be encoded as CDATA. */
private final Set cdataAttributes;
/** Constructor. */
public CDataAwareDomDriver(Set cdataAttributes) {
this.cdataAttributes = cdataAttributes;
}
/** {@inheritDoc} */
@Override
public HierarchicalStreamWriter createWriter(Writer out) {
return new CDataAwarePrettyPrintWriter(out, getNameCoder(), cdataAttributes);
}
}
/**
* A pretty printer that outputs attributes that contains "JSON" as CDATA.
*/
private static class CDataAwarePrettyPrintWriter extends PrettyPrintWriter {
/** Flag for storing CData mode. */
private boolean nextIsCData = false;
/** Attributes that are to be encoded as CDATA. */
private final Set cdataAttributes;
/** Constructor. */
public CDataAwarePrettyPrintWriter(Writer out, NameCoder nameCoder, Set cdataAttributes) {
super(out, nameCoder);
this.cdataAttributes = cdataAttributes;
}
/** {@inheritDoc} */
@Override
public void startNode(String name, @SuppressWarnings("rawtypes") Class clazz) {
super.startNode(name, clazz);
nextIsCData = this.cdataAttributes.contains(name);
}
/** {@inheritDoc} */
@Override
protected void writeText(QuickWriter writer, String text) {
if (nextIsCData) {
writer.write("");
} else {
super.writeText(writer, text);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy