/* 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.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.geoserver.ows.util.ResponseUtils; import org.geoserver.rest.format.DataFormat; import org.geoserver.rest.format.MediaTypes; import org.geoserver.rest.util.RESTUtils; import org.geotools.util.Converters; import org.restlet.Context; import org.restlet.data.MediaType; import org.restlet.data.Preference; import org.restlet.data.Request; import org.restlet.data.Response; import org.restlet.data.Status; import org.restlet.resource.Resource; /** * Abstract base class for resources. * * @author Justin Deoliveira, OpenGEO * */ public abstract class AbstractResource extends Resource { /** * media type setup */ static { MediaTypes.registerExtension( "html", MediaType.TEXT_HTML ); MediaTypes.registerExtension( "xml", MediaType.APPLICATION_XML ); MediaTypes.registerExtension( "json", MediaType.APPLICATION_JSON ); MediaTypes.registerSynonym( MediaType.APPLICATION_XML, MediaType.TEXT_XML ); MediaTypes.registerSynonym( MediaType.APPLICATION_JSON, new MediaType( "text/json") ); } /** * list of formats used to read and write representations of the resource. */ protected Map<MediaType,DataFormat> formats; /** * Constructs a new resource from context, request, and response. */ protected AbstractResource( Context context, Request request, Response response ) { super( context, request, response ); } /** * Constructs a new resource. * <p> * When using this constructor, {@link #init(Context, Request, Response)} needs to be * called before any other operations. * </p> */ protected AbstractResource() { } /** * Creates the list of formats used to create and read representations of the resource. * <p> * The first entry in the list is considered to be the default format. This format will * be used when the client does not explicitly specify another format. * </p> */ protected abstract List<DataFormat> createSupportedFormats(Request request,Response response); /** * Returns the format to use to serialize during a GET request. * <p> * To determine the format first the "Accepts" header is checked. If not set then the * "extension" of the resource being requested is used, which originates from the route * template under the {format} or {type} variable. IF neither the header or extension is * set the default format is used, ie the first format returned from * {@link #createSupportedFormats(Request, Response)}. * </p> */ protected DataFormat getFormatGet() { DataFormat df = null; //check if the client specified an extension String ext = (String) getRequest().getAttributes().get( "format" ); if ( ext == null ) { ext = (String) getRequest().getAttributes().get( "type" ); } if ( ext == null ) { //try from the resource uri String uri = getRequest().getResourceRef() != null ? getRequest().getResourceRef().getLastSegment() : null; if ( uri != null ) { ext = ResponseUtils.getExtension(uri); } } if ( ext != null ) { //lookup the media type matching the extension MediaType mt = MediaTypes.getMediaTypeForExtension( ext ); if ( mt != null ) { df = lookupFormat(mt); } } List<Preference<MediaType>> accepts = null; if ( df == null ) { //next check the Accepts header accepts = new ArrayList( getRequest().getClientInfo().getAcceptedMediaTypes() ); for ( Iterator<Preference<MediaType>> i = accepts.iterator(); i.hasNext(); ) { Preference<MediaType> pref = i.next(); if ( pref.getMetadata().equals( MediaType.ALL ) ) { i.remove(); continue; } df = lookupFormat( pref.getMetadata() ); if ( df != null ) { break; } } } if ( df == null && accepts.isEmpty() ) { //could not find suitable format, if client specifically did not specify // any accepted formats, just return the first df = getFormats().values().iterator().next(); } return df; } /** * returns the format to use to de-serialize during a POST or PUT request. * <p> * The format is located by looking up <pre>getRequest().getEntity().getMediaType()</pre> from * the list created by {@link #createSupportedFormats()}. * </p> */ protected DataFormat getFormatPostOrPut() { MediaType type = getRequest().getEntity().getMediaType(); if ( type != null ) { //DataFormat format = getFormats().get( type.toString() ); DataFormat format = lookupFormat( type ); /* if ( format == null ) { //check the sub type String sub = type.getSubType(); if ( sub != null ) { format = getFormats().get( sub ); if ( format == null ) { //check for sub type specified with '+' int plus = sub.indexOf( '+' ); if ( plus != -1 ) { sub = sub.substring(0,plus); format = getFormats().get( sub ); } } } } */ if ( format != null ) { return format; } } throw new RestletException( "Could not determine format. Try setting the Content-type header.", Status.CLIENT_ERROR_BAD_REQUEST ); } /** * Accessor for formats which lazily creates the format map. */ protected Map<MediaType,DataFormat> getFormats() { if ( formats == null ) { synchronized (this) { if ( formats == null ) { formats = new LinkedHashMap(); for ( DataFormat format : createSupportedFormats(getRequest(), getResponse()) ) { formats.put( format.getMediaType(), format ); } if ( formats.isEmpty() ) { throw new RuntimeException( "Empty format list" ); } } } } return formats; } /* * Helper method for looking up a format based on a media type. */ DataFormat lookupFormat( MediaType mt ) { //exact match DataFormat fmt = getFormats().get( mt ); if ( fmt == null ) { //check for the case of a media type being "contained" for ( MediaType mediaType : getFormats().keySet() ) { if ( mediaType.includes( mt ) || mt.includes( mediaType ) ) { fmt = getFormats().get( mediaType ); break; } } } if ( fmt == null ) { //do a check for synonyms for( MediaType syn : MediaTypes.getSynonyms( mt ) ) { fmt = getFormats().get( syn ); if ( fmt != null ) { break; } } } if ( fmt == null ) { //do a reverse check check for synonyms for ( MediaType mediaType : getFormats().keySet() ) { for( MediaType syn : MediaTypes.getSynonyms( mediaType ) ) { if ( mt.equals( syn ) ) { fmt = getFormats().get( mediaType ); } } } } if ( fmt == null ) { //do a "contains" check on synonyms for( MediaType syn : MediaTypes.getSynonyms( mt ) ) { for ( MediaType mediaType : getFormats().keySet() ) { if ( mediaType.includes( syn ) || syn.includes( mediaType )) { fmt = getFormats().get( mediaType ); break; } } } } //TODO: reverse contains check on synonyms return fmt; } /** * Returns the object which contains information about the page / resource bring requested. * <p> * This object is created by the rest dispatcher when a request comes in. * </p> */ protected PageInfo getPageInfo() { return (PageInfo) getRequest().getAttributes().get( PageInfo.KEY ); } /** * Convenience method for subclasses to look up the (URL-decoded)value of * an attribute from the request, ie {@link Request#getAttributes()}. * * @param attribute THe name of the attribute to lookup. * * @return The value as a string, or null if the attribute does not exist * or cannot be url-decoded. */ protected String getAttribute(String attribute) { return RESTUtils.getAttribute(getRequest(), attribute); } /** * Convenience method for subclasses to look up the (URL-decoded) value of a value specified * in the request query string. * * @param key The name of the value to lookup. * * @return The converted value, or <code>null</code> if the value was not specified. */ protected String getQueryStringValue(String key) { return RESTUtils.getQueryStringValue(getRequest(), key); } /** * Convenience method for subclasses to look up the (URL-decoded) value of a value specified * in the request query string, converting to the specified type. * * @param key The name of the value to lookup. * @param clazz The class to convert to. * @param defalt The default value to return if the attribute does not exist or no * conversion was possible. May be <code>null</code> * * @return The converted value, or <code>null</code> if either (a) the value was not * specified or (b) the value could not be converted to the specified type. */ protected <T> T getQueryStringValue(String key, Class<T> clazz, T defalt) { String value = getQueryStringValue(key); if (value != null) { T converted = Converters.convert(value, clazz); if (converted != null) { return converted; } } return defalt; } }