| /******************************************************************************* |
| * Copyright (c) 2010-2014 SAP AG and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * SAP AG - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.skalli.services.extension.rest; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.Reader; |
| import java.io.Writer; |
| import java.text.MessageFormat; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| |
| import org.apache.commons.lang.StringUtils; |
| import org.eclipse.skalli.services.Services; |
| import org.eclipse.skalli.services.rest.RequestContext; |
| import org.eclipse.skalli.services.rest.RestReader; |
| import org.eclipse.skalli.services.rest.RestService; |
| import org.eclipse.skalli.services.rest.RestWriter; |
| import org.restlet.data.MediaType; |
| import org.restlet.representation.Representation; |
| import org.restlet.representation.WriterRepresentation; |
| |
| import com.thoughtworks.xstream.XStream; |
| import com.thoughtworks.xstream.io.xml.CompactWriter; |
| import com.thoughtworks.xstream.mapper.MapperWrapper; |
| |
| /** |
| * Base class for REST resources based on XStream as converter technology between beans |
| * and their XML representation. |
| * |
| * @param <T> the type of the bean with which this REST representation is associated. |
| */ |
| public class ResourceRepresentation<T> extends WriterRepresentation { |
| |
| private static final String RELATIVE_LINKS = "relative"; //$NON-NLS-1$ |
| private static final String LINKS_QUERY_ATTRIBUTE = "links"; //$NON-NLS-1$ |
| private static final String MEMBERS_QUERY_ATTRIBUTE = "members"; //$NON-NLS-1$ |
| private static final String ALL_MEMBERS = "all"; //$NON-NLS-1$ |
| |
| private T object; |
| private RequestContext context; |
| private RestConverter<T> converter; |
| |
| private XStream xstream; |
| private Set<Class<?>> annotatedClasses = new HashSet<Class<?>>(); |
| private Set<RestConverter<?>> converters = new HashSet<RestConverter<?>>(); |
| private Map<String, Class<?>> aliases = new HashMap<String, Class<?>>(); |
| private ClassLoader classLoader; |
| private boolean compact; |
| |
| /** |
| * Creates an uninitialized representation for converting an XML representation |
| * into an instance of the bean. |
| */ |
| public ResourceRepresentation() { |
| super(MediaType.APPLICATION_XML); |
| } |
| |
| /** |
| * Creates a representation initialized with the given bean instance for |
| * converting it to its XML representation. |
| * |
| * @param object a bean instance. |
| */ |
| @Deprecated |
| public ResourceRepresentation(T object) { |
| this(); |
| this.object = object; |
| if (object != null) { |
| addAnnotatedClass(object.getClass()); |
| } |
| } |
| |
| /** |
| * Creates a representation initialized with the given bean instance for |
| * converting it to its XML representation, and adds additional converters |
| * for non-standard property types. |
| * |
| * @param object a bean instance. |
| * @param converters additional converters that may be necessary for |
| * conversion of certain properties or inner beans of the bean. |
| */ |
| @Deprecated |
| public ResourceRepresentation(T object, RestConverter<?>... converters) { |
| this(object); |
| if (converters != null) { |
| for (RestConverter<?> converter: converters) { |
| addConverter(converter); |
| } |
| } |
| } |
| |
| /** |
| * Creates a resource representation for unmarshalling an input stream to |
| * an object using the given REST converter. The REST converter must implement |
| * {@link RestConverter#unmarshal(RestReader)}. |
| * |
| * @param context |
| * @param converter |
| */ |
| public ResourceRepresentation(RequestContext context, RestConverter<T> converter) { |
| super(context.getMediaType()); |
| this.context = context; |
| this.converter = converter; |
| } |
| |
| /** |
| * Creates a resource representation for marshalling a given object to |
| * an output stream using the given REST converter. The REST converter must implement |
| * {@link RestConverter#marshal(Object, RestWriter)}. |
| * |
| * @param context the request context. |
| * @param object the object to convert. |
| * @param converter the converter to use. |
| */ |
| public ResourceRepresentation(RequestContext context, T object, RestConverter<T> converter) { |
| this(context, converter); |
| this.object = object; |
| } |
| |
| @Override |
| public void write(Writer writer) throws IOException { |
| if (object != null) { |
| if (context == null) { |
| writer.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"); //$NON-NLS-1$ |
| XStream xstream = getXStream(); |
| if (compact) { |
| xstream.marshal(object, new CompactWriter(writer)); |
| } else { |
| xstream.toXML(object, writer); |
| } |
| } else if (converter.getConversionClass().isAssignableFrom(object.getClass())) { |
| RestService restService = Services.getRequiredService(RestService.class); |
| MediaType mediaType = context.getMediaType(); |
| if (!restService.isSupported(context)) { |
| throw new IOException(MessageFormat.format("Unsupported media type ''{0}''", mediaType)); |
| } |
| RestWriter restWriter = restService.getRestWriter(writer, context); |
| String hrefQueryAttr = context.getQueryAttribute(LINKS_QUERY_ATTRIBUTE); |
| if (RELATIVE_LINKS.equalsIgnoreCase(hrefQueryAttr)) { |
| restWriter.set(RestWriter.RELATIVE_LINKS); |
| } |
| String membersQueryAttr = context.getQueryAttribute(MEMBERS_QUERY_ATTRIBUTE); |
| if (ALL_MEMBERS.equalsIgnoreCase(membersQueryAttr)) { |
| restWriter.set(RestWriter.ALL_MEMBERS); |
| } |
| try { |
| converter.marshal(object, restWriter); |
| } catch (RuntimeException e) { |
| // don't trust the integrity of plugins! |
| throw new IOException(MessageFormat.format("Failed to write response for {0}", |
| context.getPath()), e); |
| } finally { |
| restWriter.flush(); |
| } |
| } else { |
| throw new IOException("Failed to create resource representation"); |
| } |
| } |
| } |
| |
| public T read(Reader reader) throws IOException, RestException { |
| RestService restService = Services.getRequiredService(RestService.class); |
| if (!restService.isSupported(context)) { |
| throw new IOException(MessageFormat.format("Unsupported media type ''{0}''", context.getMediaType())); |
| } |
| RestReader restReader = restService.getRestReader(reader, context); |
| try { |
| return converter.unmarshal(restReader); |
| } catch (RuntimeException e) { |
| // don't trust the integrity of plugins! |
| throw new RestException(MessageFormat.format("Failed to read request {0} {1}", |
| context.getAction(), context.getPath()), e); |
| } |
| } |
| |
| /** |
| * Reads the XML representation of a bean from a given representation (e.g. a string |
| * or a socket) and initializes a bean instance from it. |
| * |
| * @param representation the representation from which to read the XML representation of the bean. |
| * @param c the class of the bean to instantiate. |
| * |
| * @return an instance of the requested bean class initialized from it XML representation. |
| * |
| * @throws IOException if reading the bean caused an i/o error. |
| * @throws ClassCastExeption if the XML representation of the bean cannot be casted to the |
| * requested class. |
| */ |
| @Deprecated |
| public T read(Representation representation, Class<T> c) throws IOException { |
| return read(representation.getStream(), c); |
| } |
| |
| /** |
| * Reads the XML representation of a bean from a given input stream and initializes |
| * a bean instance from it. |
| * |
| * @param in the stream to read. |
| * @param c the class of the bean to instantiate. |
| * |
| * @return an instance of the requested bean class initialized from it XML representation. |
| * |
| * @throws IOException if reading the bean caused an i/o error. |
| * @throws ClassCastExeption if the XML representation of the bean cannot be casted to the |
| * requested class. |
| */ |
| @Deprecated |
| public T read(InputStream in, Class<T> c) throws IOException { |
| return c.cast(getXStream().fromXML(in)); |
| } |
| |
| /** |
| * Adds an additional class with XStream annotations that represents an inner structure |
| * of the class to convert, e.g. the entries of a collection-like property. If the class |
| * to convert and the classes representing its inner structure live in different bundles, |
| * calling this method is mandatory. |
| * <p> |
| * The class to convert is automatically added. |
| * |
| * @param annotatedClass the class to add to the conversion. |
| */ |
| @Deprecated |
| public void addAnnotatedClass(Class<?> annotatedClass) { |
| if (annotatedClass != null) { |
| annotatedClasses.add(annotatedClass); |
| } |
| } |
| |
| /** |
| * Adds an alias name for a given type. |
| * |
| * @param name the alias. |
| * @param type the class for which to use the alias. |
| */ |
| @Deprecated |
| public void addAlias(String name, Class<?> type) { |
| if (StringUtils.isNotBlank(name) && type != null) { |
| aliases.put(name, type); |
| } |
| } |
| |
| /** |
| * Adds an additional converter for the conversion of certain properties |
| * or inner classes, which are not supported by XStream out-of-the-box. |
| * |
| * @param converter the REST converter to add. |
| */ |
| @Deprecated |
| public void addConverter(RestConverter<?> converter) { |
| if (converter != null) { |
| converters.add(converter); |
| } |
| } |
| |
| /** |
| * Sets an alternative classloader for the conversion. |
| * @param classLoader the classloader to use, or <code>null</code> |
| * if the default classloader should be used. |
| */ |
| @Deprecated |
| public void setClassLoader(ClassLoader classLoader) { |
| this.classLoader = classLoader; |
| } |
| |
| /** |
| * Sets an alternative {@link XStream} instance. By default, a suitable |
| * <code>XStream</code> instance is constructed automatically. |
| * |
| * @param xstream a <code>XStream</code> instance, or <code>null</code> |
| * if the default <code>XStream</code> instance should be used. |
| */ |
| @Deprecated |
| public void setXStream(XStream xstream) { |
| this.xstream = xstream; |
| } |
| |
| /** |
| * Switches compact output on or off (default is off). Switching on |
| * compact output will remove line breaks and indentations from the output |
| * performing slightly faster and reducing the amount of data written |
| * to the underlying socket. |
| * |
| * @param compact if <code>true</code> the output is compacted. |
| */ |
| @Deprecated |
| public void setCompact(boolean compact) { |
| this.compact = compact; |
| } |
| |
| /** |
| * Creates an <code>XStream</code> instance for rendering/parsing the XML |
| * representation of the bean. |
| * |
| * @return an <code>XStream</code> instance pre-initialized with the |
| * specified {@link #setClassLoader(ClassLoader) class loader}, |
| * {@link #setConverters(RestConverter...) converters} and |
| * {@link #setAnnotatedClasses(Class...) (inner) bean classes}. A special |
| * wrapper is used to skip unknown tags in the XML representation. |
| * {@link XStream#NO_REFERENCES} is set so that references betweem tags |
| * are always resolved. |
| */ |
| @Deprecated |
| protected XStream getXStream() { |
| if (xstream != null) { |
| return xstream; |
| } |
| XStream result = new XStream() { |
| @Override |
| protected MapperWrapper wrapMapper(MapperWrapper next) { |
| return new MapperWrapperIgnoreUnknownElements(next); |
| } |
| }; |
| for (RestConverter<?> converter : converters) { |
| result.registerConverter(converter); |
| result.alias(converter.getAlias(), converter.getConversionClass()); |
| } |
| for (Class<?> annotatedClass : annotatedClasses) { |
| result.processAnnotations(annotatedClass); |
| } |
| for (Entry<String, Class<?>> alias: aliases.entrySet()) { |
| result.alias(alias.getKey(), alias.getValue()); |
| } |
| if (classLoader != null) { |
| result.setClassLoader(classLoader); |
| } |
| result.setMode(XStream.NO_REFERENCES); |
| return result; |
| } |
| |
| @Deprecated |
| private static class MapperWrapperIgnoreUnknownElements extends MapperWrapper { |
| public MapperWrapperIgnoreUnknownElements(MapperWrapper next) { |
| super(next); |
| } |
| |
| @SuppressWarnings("rawtypes") |
| @Override |
| public boolean shouldSerializeMember(Class definedIn, String fieldName) { |
| if (definedIn == Object.class) { |
| return false; |
| } |
| return super.shouldSerializeMember(definedIn, fieldName); |
| } |
| } |
| } |