/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, availible at the root * application directory. */ package org.geoserver.rest; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import org.geoserver.ows.util.ResponseUtils; import org.geoserver.rest.format.DataFormat; import org.geoserver.rest.format.ReflectiveHTMLFormat; import org.geoserver.rest.format.ReflectiveJSONFormat; import org.geoserver.rest.format.ReflectiveXMLFormat; import org.restlet.Context; import org.restlet.data.Request; import org.restlet.data.Response; import org.restlet.data.Status; import com.thoughtworks.xstream.XStream; /** * Base class for resources which work reflectively from underlying target objects. * <h2>HTTP Methods</h2> * <p> * By default this class allows access via GET, POST, PUT and denies access via DELETE. * Subclasses should override this behavior via the methods * {@link #allowGet()}, {@link #allowPost()}, {@link #allowPut()}, and {@link #allowDelete()} * as needed. * </p> * <h2>Object Serialization and Deserialization</h2> * <p> * A reflective resource corresponds to another class of object, referred here as the * "target" object. During GET requests instances of the target object can be serialized as * HTML, XML, or JSON. During POST and PUT requests instances of the "target" object can be * de-serialized as XML or JSON. HTML serialization is achieved via Freemarker, XML and JSON * (de)serialization is achieved via XStream. Serialization and de-serialization is performed * by the {@link DataFormat} class. The supported formats listed above can be customized as * need be by overriding/extending the {@link #createSupportedFormats()} method. * </p> * * @author David Winslow, OpenGeo * @author Justin Deoliveira, OpenGeo */ public abstract class ReflectiveResource extends AbstractResource { /** * logger */ static Logger LOG = org.geotools.util.logging.Logging.getLogger("org.geoserver.rest"); /** * Creates a new reflective resource. */ public ReflectiveResource( Context context, Request request, Response response ) { super(context, request, response); } /** * Creates a new reflective resource relying on {@link #init(Context, Request, Response)} to * initialize the resource. * */ public ReflectiveResource() { } /** * Handles a GET request by serializing the target object. * <p> * This method operates by: * <ol> * <li>Determining the serialization format from {@link #getFormatGet()} * <li>Getting the target object from {@link #handleObjectGet()} * <li>Serializing to output * <li> * </ol> * </p> * * @see #handleObjectGet() */ @Override public final void handleGet() { DataFormat format = getFormatGet(); try { getResponse().setEntity(format.toRepresentation(handleObjectGet())); } catch (Exception e) { handleException(e); } } /** * Gets the target object to be serialized in a GET request. */ protected abstract Object handleObjectGet() throws Exception; /** * Handles a POST request by de-serializing the content of the request into an instance * of the target object. * <p> * This method operates by: * <ol> * <li>Determining the serialization format from {@link #getFormatPostOrPut()} * <li>De-Serializing to an instanceof the target object. * <li>Passing the target object to {@link #handleObjectPost(Object)} * </ol> * </p> * * @see #handleObjectPost(Object) */ @Override public final void handlePost() { DataFormat format = getFormatPostOrPut(); Object object = format.toObject( getRequest().getEntity() ); String location = null; try { location = handleObjectPost(object); } catch (Exception e) { handleException(e); } if ( location != null ) { // set the Location header by appending the location to the current resource String uri = getRequest().getResourceRef().getIdentifier(); int question = uri.indexOf( '?' ); if ( question != -1 ) { uri = uri.substring(0,question); } uri = ResponseUtils.appendPath(uri, location); getResponse().redirectSeeOther(uri); // set response 201 getResponse().setStatus( Status.SUCCESS_CREATED ); } else { } } /** * Handles a de-serialized instance of the target object in a POST request. * <p> * Subclasses should override this method as well as {@link #allowPost()} to handle the * POST method. * </p> * <p> * This method returns the location that the object can be retrieved from after the * POST. This value is used to set the 'Location' header of the http response by * appending this value to the uri used to make the request. For example consider the * following: * <pre> * protected String handleObjectPost(Object object) { * Foo foo = (Foo) object; * doSomethingWithFoo(foo); * * return "foo"; * } * </pre> * Along with the POST request to "http://localhost:8080/geoserver/rest/foos". The resulting value * of the 'Location' header would be "http://localhost:8080/geoserver/rest/foos/foo". * </p> * * @param object Instance of the target object. * * @return The location of the resulting resource. */ protected String handleObjectPost(Object object) throws Exception { return null; } /** * Handles a PUT request by de-serializing the content of the request into an instance * of the target object. * <p> * This method operates by: * <ol> * <li>Determining the serialization format from {@link #getFormatPostOrPut()} * <li>De-serializing to an instance of the target object. * <li>Passing the target object to {@link #handleObjectPut(Object)} * </ol> * </p> * * @see #handleObjectPost(Object) */ @Override public final void handlePut() { DataFormat format = getFormatPostOrPut(); Object object = format.toObject( getRequest().getEntity() ); try { handleObjectPut(object); } catch (Exception e) { handleException(e); } } /** * Handles a de-serialized instance of the target object in a PUT request. * <p> * Subclasses should override this method as well as {@link #allowDelete()} to handle the * PUT method. * </p> * @param object Instance of the target object. */ protected void handleObjectPut(Object object) throws Exception { } /** * Handles a DELETE request. * <p> * This method simply delegates to {@link #handleObjectDelete()}. Note that the DELETE method is * not allowed by default, so if subclasses which to handle the DELETE method they must override * {@link #allowDelete()}. * </p> * * @see #handleObjectDelete() */ @Override public final void handleDelete() { try { handleObjectDelete(); } catch (Exception e) { handleException(e); } } /** * Handles a delete on the target object. * <p> * Subclasses should override this method as well as {@link #allowDelete()} to handle the * DELETE method. * </p> * */ protected void handleObjectDelete() throws Exception { } /** * Creates the list of formats used to serialize and de-serialize instances of the target object. * <p> * Subclasses may override or extend this method to customize the supported formats. By default * this method supports html, xml, and json. * </p> * * @see #createHTMLFormat() * @see #createXMLFormat() * @see #createJSONFormat() */ protected List<DataFormat> createSupportedFormats(Request request,Response response) { List<DataFormat> formats = new ArrayList<DataFormat>(); formats.add(createHTMLFormat(request,response)); formats.add(createXMLFormat(request,response) ); formats.add(createJSONFormat(request,response) ); return formats; } /** * Creates the data format for serializing objects as HTML. * <p> * This implementation returns a new instance of {@link ReflectiveHTMLFormat}. Subclasses * may override as need be. * </p> */ protected DataFormat createHTMLFormat(Request request,Response response) { return new ReflectiveHTMLFormat(request,response,this); } /** * Creates the data format for serializing and de-serializing objects as XML. * <p> * This implementation returns a new instance of {@link ReflectiveXMLFormat}. Subclasses * may override as need be. * </p> */ protected ReflectiveXMLFormat createXMLFormat(Request request,Response response) { ReflectiveXMLFormat format = new ReflectiveXMLFormat(); configureXStream( format.getXStream() ); return format; } /** * Creates the data format for serializing and de-serializing objects as JSON. * <p> * This implementation returns a new instance of {@link ReflectiveJSONLFormat}. Subclasses * may override as need be. * </p> */ protected ReflectiveJSONFormat createJSONFormat(Request request,Response response) { ReflectiveJSONFormat format = new ReflectiveJSONFormat(); configureXStream( format.getXStream() ); return format; } /** * Method for subclasses to customize of modify the xstream instance being * used to persist and depersist XML and JSON. */ protected void configureXStream( XStream xstream ) { } protected String encode(String component) { try { return URLEncoder.encode(component, "UTF-8"); } catch (UnsupportedEncodingException e) { LOG.warning("Unable to URL-encode component: " + component); return component; } } /** * Helper method which checks if a restlet exception was thrown, if so it throws it, else * wraps it in a restlet exception. */ void handleException( Exception e ) throws RestletException { if ( e instanceof RestletException ) { throw (RestletException) e ; } throw new RestletException( "", Status.SERVER_ERROR_INTERNAL, e ); } }