/* (c) 2017 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.rest.converters;
import java.io.IOException;
import java.util.Collection;
import org.geoserver.config.util.SecureXStream;
import org.geoserver.config.util.XStreamPersister;
import org.geoserver.ows.util.OwsUtils;
import org.geoserver.rest.wrapper.RestListWrapper;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.converters.collections.CollectionConverter;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;
/**
* Converter to handle the serialization of lists of catalog resources, which need some special handling
*/
public abstract class XStreamCatalogListConverter
extends XStreamMessageConverter<RestListWrapper<?>> {
public XStreamCatalogListConverter(MediaType... supportedMediaTypes) {
super(supportedMediaTypes);
}
@Override
protected boolean supports(Class<?> clazz) {
return RestListWrapper.class.isAssignableFrom(clazz); // can write RestListWrapper
}
//
// reading
//
@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return false;
}
@Override
public RestListWrapper<?> readInternal(Class<? extends RestListWrapper<?>> clazz,
HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
throw new HttpMessageNotReadableException(
getClass().getName() + " does not support deserialization of catalog lists");
}
//
// writing
//
@Override
public void writeInternal(RestListWrapper<?> wrapper, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
XStream xstream = this.createXStreamInstance();
Class<?> targetClass = wrapper.getObjectClass();
Collection<?> data = wrapper.getCollection();
this.aliasCollection(data, xstream, targetClass);
this.configureXStream(xstream, targetClass, wrapper);
xstream.toXML(data, outputMessage.getBody());
}
private void configureXStream(XStream xstream, Class<?> clazz, RestListWrapper<?> wrapper) {
XStreamPersister xp = xpf.createXMLPersister();
wrapper.configurePersister(xp, this);
final String name = getItemName(xp, clazz);
xstream.alias(name, clazz);
xstream.registerConverter(new CollectionConverter(xstream.getMapper()) {
@SuppressWarnings("rawtypes")
@Override
public boolean canConvert(Class type) {
return Collection.class.isAssignableFrom(type);
}
@Override
protected void writeItem(Object item, MarshallingContext context,
HierarchicalStreamWriter writer) {
writer.startNode(name);
context.convertAnother(item);
writer.endNode();
}
});
xstream.registerConverter(new Converter() {
@SuppressWarnings("rawtypes")
public boolean canConvert(Class type) {
return clazz.isAssignableFrom(type);
}
public void marshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
String ref;
if (OwsUtils.getter(clazz, "name", String.class) != null) {
ref = (String) OwsUtils.get(source, "name");
} else if (OwsUtils.getter(clazz, "id", String.class) != null) {
ref = (String) OwsUtils.get(source, "id");
} else if (OwsUtils.getter(clazz, "id", Long.class) != null) {
// For some reason Importer objects have Long ids so this catches that case
ref = OwsUtils.get(source, "id").toString();
}
else {
throw new RuntimeException(
"Could not determine identifier for: " + clazz.getName());
}
writer.startNode("name");
writer.setValue(ref);
writer.endNode();
encodeLink(encode(ref), writer);
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
return null;
}
});
}
/**
* Template method to alias the type of the collection.
* <p>
* The default works with list, subclasses may override for instance to work with a Set.
* </p>
*/
protected void aliasCollection(Object data, XStream xstream, Class<?> clazz) {
XStreamPersister xp = xpf.createXMLPersister();
final String alias = getItemName(xp, clazz);
xstream.alias(alias + "s", Collection.class, data.getClass());
}
protected String getItemName(XStreamPersister xp, Class<?> clazz) {
return xp.getClassAliasingMapper().serializedClass(clazz);
}
/**
* XML handling for catalog lists
*/
public static class XMLXStreamListConverter extends XStreamCatalogListConverter {
public XMLXStreamListConverter() {
super(MediaType.APPLICATION_XML, MediaType.TEXT_XML);
}
@Override
protected XStream createXStreamInstance() {
return new SecureXStream();
}
@Override
public void encodeLink(String link, HierarchicalStreamWriter writer) {
encodeAlternateAtomLink(link, writer);
}
@Override
public void encodeCollectionLink(String link, HierarchicalStreamWriter writer) {
encodeAlternateAtomLink(link, writer);
}
@Override
public String getMediaType() {
return MediaType.APPLICATION_ATOM_XML_VALUE;
}
@Override
public String getExtension() {
return "xml";
}
}
public static class JSONXStreamListConverter extends XStreamCatalogListConverter {
public JSONXStreamListConverter() {
super(MediaType.APPLICATION_JSON,XStreamJSONMessageConverter.TEXT_JSON);
}
@Override
public void encodeLink(String link, HierarchicalStreamWriter writer) {
writer.startNode("href");
writer.setValue(href(link));
writer.endNode();
}
@Override
public void encodeCollectionLink(String link, HierarchicalStreamWriter writer) {
writer.setValue(href(link));
}
@Override
protected XStream createXStreamInstance() {
return new XStream(new JettisonMappedXmlDriver());
}
@Override
public String getExtension() {
return "json";
}
@Override
public String getMediaType() {
return MediaType.APPLICATION_JSON_VALUE;
}
}
}