org.eclipse.skalli.services.extension.rest.ResourceRepresentation.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.skalli.services.extension.rest.ResourceRepresentation.java

Source

/*******************************************************************************
 * 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);
        }
    }
}