All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.vfny.geoserver.servlets.AbstractService Maven / Gradle / Ivy

There is a newer version: 1.7.0
Show newest version
/* 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.vfny.geoserver.servlets;

import org.geoserver.ows.ServiceStrategy;
import org.geoserver.ows.util.XmlCharsetDetector;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.context.WebApplicationContext;
import org.vfny.geoserver.ExceptionHandler;
import org.vfny.geoserver.Request;
import org.vfny.geoserver.Response;
import org.vfny.geoserver.ServiceException;
import org.vfny.geoserver.global.Data;
import org.vfny.geoserver.global.GeoServer;
import org.vfny.geoserver.global.Service;
import org.vfny.geoserver.util.PartialBufferedOutputStream2;
import org.vfny.geoserver.util.requests.readers.KvpRequestReader;
import org.vfny.geoserver.util.requests.readers.XmlRequestReader;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Reader;
import java.net.SocketException;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


/**
 * Represents a service that all others extend from.  Subclasses should provide
 * response and exception handlers as appropriate.
 *
 * 

* It is really important to adhere to the following workflow: * *

    *
  1. * get a Request reader *
  2. *
  3. * ask the Request Reader for the Request object *
  4. *
  5. * Provide the resulting Request with the ServletRequest that generated it *
  6. *
  7. * get the appropiate ResponseHandler *
  8. *
  9. * ask it to execute the Request *
  10. *
  11. * set the response content type *
  12. *
  13. * write to the http response's output stream *
  14. *
  15. * pending - call Response cleanup *
  16. *
*

* *

* If anything goes wrong a ServiceException can be thrown and will be written * to the output stream instead. *

* *

* This is because we have to be sure that no exception have been produced * before setting the response's content type, so we can set the exception * specific content type; and that Response.getContentType is called AFTER * Response.execute, since the MIME type can depend on any request parameter * or another kind of desission making during the execute process. (i.e. * FORMAT in WMS GetMap) *

* *

* TODO: We need to call Response.abort() if anything goes wrong to allow the * Response a chance to cleanup after itself. *

* * @author Gabriel Rold?n * @author Chris Holmes * @author Jody Garnett, Refractions Research * @version $Id: AbstractService.java 8443 2008-02-25 13:18:59Z groldan $ */ public abstract class AbstractService extends HttpServlet implements ApplicationContextAware { /** Class logger */ protected static Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.vfny.geoserver.servlets"); /** * Servivce group (maps to 'SERVICE' parameter in OGC service urls) */ String service; /** * Request type (maps to 'REQUEST' parameter in OGC service urls) */ String request; /** * Application context used to look up "Services" */ WebApplicationContext context; /** * Reference to the global geoserver instnace. */ GeoServer geoServer; /** * Reference to the catalog. */ Data catalog; /** * Id of the service strategy to use. */ String serviceStrategy; /** * buffer size to use when PARTIAL-BUFFER is being used */ int partialBufferSize; /** * Cached service strategy object */ // ServiceStrategy strategy; /** * Reference to the service */ Service serviceRef; private String kvpString; // /** DOCUMENT ME! */ // protected HttpServletRequest curRequest; /** * Constructor for abstract service. * * @param service The service group the service falls into (WFS,WMS,...) * @param request The service being requested (GetCapabilities, GetMap, ...) * @param serviceRef The global service this "servlet" falls into */ public AbstractService(String service, String request, Service serviceRef) { this.service = service; this.request = request; this.serviceRef = serviceRef; } /** * @return Returns the "service group" that this service falls into. */ public String getService() { return service; } /** * @return Returns the "request" this service maps to. */ public String getRequest() { return request; } /** * Sets a refeference to the global service instance. */ public void setServiceRef(Service serviceRef) { this.serviceRef = serviceRef; } /** * @return The reference to the global service instance. */ public Service getServiceRef() { return serviceRef; } /** * Sets the application context. *

* Used to process the {@link Service} extension point. *

*/ public void setApplicationContext(ApplicationContext context) throws BeansException { this.context = (WebApplicationContext) context; } /** * @return The application context. */ public WebApplicationContext getApplicationContext() { return context; } /** * Sets the reference to the global geoserver instance. */ public void setGeoServer(GeoServer geoServer) { this.geoServer = geoServer; } /** * @return the reference to the global geoserver instance. */ public GeoServer getGeoServer() { return geoServer; } /** * @return The reference to the global catalog instance. */ public Data getCatalog() { return catalog; } /** * Sets the reference to the global catalog instance. * */ public void setCatalog(Data catalog) { this.catalog = catalog; } /** * @return The id used to identify the service strategy to be used. * @see ServiceStrategy#getId() */ public String getServiceStrategy() { return serviceStrategy; } /** * Sets the id used to identify the service strategy to be used. */ public void setServiceStrategy(String serviceStrategy) { this.serviceStrategy = serviceStrategy; } /** * Determines if the service is enabled. *

* Subclass should override this method if the service can be turned on/off. * This implementation returns true *

*/ protected boolean isServiceEnabled(HttpServletRequest req) { return true; } /** * Override and use spring set servlet context. */ public ServletContext getServletContext() { //override and use spring return ((WebApplicationContext) context).getServletContext(); } /** * DOCUMENT ME! * * @param request DOCUMENT ME! * @param response DOCUMENT ME! * * @throws ServletException DOCUMENT ME! * @throws IOException DOCUMENT ME! */ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // implements the main request/response logic // this.curRequest = request; Request serviceRequest = null; if (!isServiceEnabled(request)) { sendDisabledServiceError(response); return; } try { Map requestParams = new HashMap(); String qString = ((this.kvpString != null) ? this.kvpString : request.getQueryString()); LOGGER.fine("reading request: " + qString); if (this.kvpString != null) { requestParams = KvpRequestReader.parseKvpSet(qString); } else { String paramName; String paramValue; for (Enumeration pnames = request.getParameterNames(); pnames.hasMoreElements();) { paramName = (String) pnames.nextElement(); paramValue = request.getParameter(paramName); requestParams.put(paramName.toUpperCase(), paramValue); } } KvpRequestReader requestReader = getKvpReader(requestParams); serviceRequest = requestReader.getRequest(request); LOGGER.finer("serviceRequest provided with HttpServletRequest: " + request); //serviceRequest.setHttpServletRequest(request); } catch (ServiceException se) { sendError(request, response, se); return; } catch (Throwable e) { sendError(request, response, e); return; } finally { this.kvpString = null; } doService(request, response, serviceRequest); } /** * Sends the standard disabled service error message (a 503 error followed by an english description). * @param response * @throws IOException */ protected void sendDisabledServiceError(HttpServletResponse response) throws IOException { response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, getService() + " service is not enabled. " + "You can enable it in the web admin tool."); } /** * Performs the post method. Simply passes itself on to the three argument * doPost method, with null for the reader, because the * request.getReader() will not have been used if this servlet is called * directly. * * @param request DOCUMENT ME! * @param response DOCUMENT ME! * * @throws ServletException DOCUMENT ME! * @throws IOException DOCUMENT ME! */ public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response, null); } /** * Performs the post method. Gets the appropriate xml reader and * determines the request from that, and then passes the request on to * doService. * * @param request The request made. * @param response The response to be returned. * @param requestXml A reader of the xml to be read. This is only used by * the dispatcher, everyone else should just pass in null. This is * needed because afaik HttpServletRequest.getReader() can not be * used twice. So in a dispatched case we write it to a temp file, * which we can then read in twice. * * @throws ServletException DOCUMENT ME! * @throws IOException DOCUMENT ME! */ public void doPost(HttpServletRequest request, HttpServletResponse response, Reader requestXml) throws ServletException, IOException { // this.curRequest = request; Request serviceRequest = null; //TODO: This isn't a proper ogc service response. if (!isServiceEnabled(request)) { sendDisabledServiceError(response); return; } // implements the main request/response logic try { XmlRequestReader requestReader = getXmlRequestReader(); //JD: GEOS-323, adding support for character encoding detection // Reader xml = (requestXml != null) ? requestXml : request.getReader(); Reader xml; if (null != requestXml) { xml = requestXml; } else { /* * `getCharsetAwareReader` returns a reader which not support * mark/reset. So it is a good idea to wrap it into BufferedReader. * In this case the below debug output will work. */ xml = new BufferedReader(XmlCharsetDetector.getCharsetAwareReader( request.getInputStream())); } //JD: GEOS-323 //DJB: add support for POST loggin if (LOGGER.isLoggable(Level.FINE)) { if (xml.markSupported()) { // a little protection for large POSTs (ie. updates) // for FINE, I assume people just want to see the "normal" ones - not the big ones // for FINER, I assume they would want to see a bit more // for FINEST, I assume they would want to see even more int maxChars = 16000; if (LOGGER.isLoggable(Level.FINER)) { maxChars = 64000; } if (LOGGER.isLoggable(Level.FINEST)) { maxChars = 640000; // Bill gates says 640k is good enough for anyone } xml.mark(maxChars + 1); // +1 so if you read the whole thing you can still reset() char[] buffer = new char[maxChars]; int actualRead = xml.read(buffer); xml.reset(); LOGGER.fine("------------XML POST START-----------\n" + new String(buffer, 0, actualRead) + "\n------------XML POST END-----------"); if (actualRead == maxChars) { LOGGER.fine("------------XML POST REPORT WAS TRUNCATED AT " + maxChars + " CHARACTERS. RUN WITH HIGHER LOGGING LEVEL TO SEE MORE"); } } else { LOGGER.fine( "ATTEMPTED TO LOG POST XML, BUT WAS PREVENTED BECAUSE markSupported() IS FALSE"); } } serviceRequest = requestReader.read(xml, request); serviceRequest.setHttpServletRequest(request); } catch (ServiceException se) { sendError(request, response, se); return; } catch (Throwable e) { sendError(request, response, e); return; } doService(request, response, serviceRequest); } /** * Peforms service according to ServiceStrategy. * *

* This method has very strict requirements, please see the class * description for the specifics. *

* *

* It has a lot of try/catch blocks, but they are fairly necessary to * handle things correctly and to avoid as many ugly servlet responses, so * that everything is wrapped correctly. *

* * @param request The httpServlet of the request. * @param response The response to be returned. * @param serviceRequest The OGC request to service. * * @throws ServletException if the strategy can't be instantiated */ protected void doService(HttpServletRequest request, HttpServletResponse response, Request serviceRequest) throws ServletException { LOGGER.info("handling request: " + serviceRequest); if (!isServiceEnabled(request)) { try { sendDisabledServiceError(response); } catch (IOException e) { LOGGER.log(Level.WARNING, "Error writing service unavailable response", e); } return; } ServiceStrategy strategy = null; Response serviceResponse = null; try { strategy = createServiceStrategy(); LOGGER.fine("strategy is: " + strategy.getId()); serviceResponse = getResponseHandler(); } catch (Throwable t) { sendError(request, response, t); return; } Map services = context.getBeansOfType(Service.class); Service s = null; for (Iterator itr = services.entrySet().iterator(); itr.hasNext();) { Map.Entry entry = (Map.Entry) itr.next(); String id = (String) entry.getKey(); Service service = (Service) entry.getValue(); if (id.toLowerCase().startsWith(serviceRequest.getService().toLowerCase().trim())) { s = service; break; } } if (s == null) { String msg = "No service found matching: " + serviceRequest.getService(); sendError(request, response, new ServiceException(msg)); return; } try { // execute request LOGGER.finer("executing request"); serviceResponse.execute(serviceRequest); LOGGER.finer("execution succeed"); } catch (ServiceException serviceException) { LOGGER.warning("service exception while executing request: " + serviceRequest + "\ncause: " + serviceException.getMessage()); serviceResponse.abort(s); sendError(request, response, serviceException); return; } catch (Throwable t) { //we can safelly send errors here, since we have not touched response yet serviceResponse.abort(s); sendError(request, response, t); return; } OutputStream strategyOuput = null; //obtain the strategy output stream try { LOGGER.finest("getting strategy output"); strategyOuput = strategy.getDestination(response); LOGGER.finer("strategy output is: " + strategyOuput.getClass().getName()); String mimeType = serviceResponse.getContentType(s.getGeoServer()); LOGGER.fine("mime type is: " + mimeType); response.setContentType(mimeType); String encoding = serviceResponse.getContentEncoding(); if (encoding != null) { LOGGER.fine("content encoding is: " + encoding); response.setHeader("Content-Encoding", encoding); } String disposition = serviceResponse.getContentDisposition(); if (disposition != null) { LOGGER.fine("content encoding is: " + encoding); response.setHeader("Content-Disposition", disposition); } } catch (SocketException socketException) { LOGGER.fine("it seems that the user has closed the request stream: " + socketException.getMessage()); // It seems the user has closed the request stream // Apparently this is a "cancel" and will quietly go away // // I will still give strategy and serviceResponse // a chance to clean up // serviceResponse.abort(s); strategy.abort(); return; } catch (IOException ex) { serviceResponse.abort(s); strategy.abort(); sendError(request, response, ex); return; } try { // gather response serviceResponse.writeTo(strategyOuput); strategyOuput.flush(); strategy.flush(response); } catch (java.net.SocketException sockEx) { // user cancel LOGGER.info("Stream abruptly closed by client, response aborted"); serviceResponse.abort(s); strategy.abort(); return; } catch (IOException ioException) { // strategyOutput error response.setHeader("Content-Disposition", ""); // reset it so we get a proper XML error returned LOGGER.info("Stream abruptly closed by client, response aborted"); LOGGER.log(Level.FINE, "Error writing out " + ioException.getMessage(), ioException); serviceResponse.abort(s); strategy.abort(); return; } catch (ServiceException writeToFailure) { // writeTo Failure response.setHeader("Content-Disposition", ""); // reset it so we get a proper XML error returned serviceResponse.abort(s); strategy.abort(); sendError(request, response, writeToFailure); return; } catch (Throwable help) { // This is an unexpected error(!) response.setHeader("Content-Disposition", ""); // reset it so we get a proper XML error returned help.printStackTrace(); serviceResponse.abort(s); strategy.abort(); sendError(request, response, help); return; } // Finish Response // I have moved closing the output stream out here, it was being // done by a few of the ServiceStrategy // // By this time serviceResponse has finished successfully // and strategy is also finished // try { response.getOutputStream().flush(); response.getOutputStream().close(); } catch (SocketException sockEx) { // user cancel LOGGER.warning("Could not send completed response to user:" + sockEx); return; } catch (IOException ioException) { // This is bad, the user did not get the completed response LOGGER.warning("Could not send completed response to user:" + ioException); return; } LOGGER.info("Service handled"); } /** * Gets the response class that should handle the request of this service. * All subclasses must implement. *

* This method is not abstract to support subclasses that use the * request-response mechanism. *

* * @return The response that the request read by this servlet should be * passed to. */ protected Response getResponseHandler() { return null; } /** * This method was added in order to adapt the old style servlet services * to the new ows dispatching interface, without having to modify the * services themselves. * * @return A call to {@link #getResponseHandler()}. */ public final Response getResponse() { return getResponseHandler(); } /** * Gets a reader that will figure out the correct Key Vaule Pairs for this * service. *

* Subclasses should override to supply a specific kvp reader. Default * implementation returns null *

* @param params A map of the kvp pairs. * * @return An initialized KVP reader to decode the request. */ protected KvpRequestReader getKvpReader(Map params) { return null; } /** * Gets a reader that will handle a posted xml request for this servlet. *

* Subclasses should override to supply a specific xml reader. Default * implementation returns null *

* @return An XmlRequestReader appropriate to this service. */ protected XmlRequestReader getXmlRequestReader() { return null; } /** * Gets the exception handler for this service. * * @return The correct ExceptionHandler */ protected abstract ExceptionHandler getExceptionHandler(); /** * Gets the strategy for outputting the response. This method gets the * strategy from the serviceStrategy param in the web.xml file. This is * sort of odd behavior, as all other such parameters are set in the * services and catalog xml files, and this param may move there. But as * it is much more of a programmer configuration than a user * configuration there is no rush to move it. * *

* Subclasses may choose to override this method in order to get a strategy * more suited to their response. Currently only Transaction will do * this, since the commit is only called after writeTo, and it often * messes up, so we want to be able to see the error message (SPEED writes * the output directly, so errors in writeTo do not show up.) *

* *

* Most subclasses should not override, this method will most always return * the SPEED strategy, since it is the fastest response and should work * fine if everything is well tested. FILE and BUFFER should be used when * there are errors in writeTo methods of child classes, set by the * programmer in the web.xml file. *

* * @return The service strategy found in the web.xml serviceStrategy * parameter. The code that finds this is in the init method * * @throws ServiceException If the service strategy set in #init() is not * valid. * * @see #init() for the code that sets the serviceStrategy. */ protected ServiceStrategy createServiceStrategy() throws ServiceException { // If verbose exceptions is on then lets make sure they actually get the // exception by using the file strategy. ServiceStrategy theStrategy = null; if (geoServer.isVerboseExceptions()) { theStrategy = (ServiceStrategy) context.getBean("fileServiceStrategy"); } else { if (serviceStrategy == null) { // none set, look up in web applicatino context serviceStrategy = getServletContext().getInitParameter("serviceStrategy"); } // do a lookup if (serviceStrategy != null) { Map strategies = context.getBeansOfType(ServiceStrategy.class); for (Iterator itr = strategies.values().iterator(); itr.hasNext();) { ServiceStrategy bean = (ServiceStrategy) itr.next(); if (bean.getId().equals(serviceStrategy)) { theStrategy = bean; break; } } } } if (theStrategy == null) { // default to buffer theStrategy = (ServiceStrategy) context.getBean("bufferServiceStrategy"); } // clone the strategy since at the moment the strategies are marked as singletons // in the web.xml file. try { theStrategy = (ServiceStrategy) theStrategy.clone(); } catch (CloneNotSupportedException e) { LOGGER.log(Level.SEVERE, "Programming error found, service strategies should be cloneable, " + e, e); throw new RuntimeException("Found a strategy that does not support cloning...", e); } // TODO: this hack should be removed once modules have their own config if (theStrategy instanceof PartialBufferStrategy2) { if (partialBufferSize == 0) { String size = getServletContext().getInitParameter("PARTIAL_BUFFER_STRATEGY_SIZE"); if (size != null) { try { partialBufferSize = Integer.valueOf(size).intValue(); if (partialBufferSize <= 0) { LOGGER.warning("Invalid partial buffer size, defaulting to " + PartialBufferedOutputStream2.DEFAULT_BUFFER_SIZE + " (was " + partialBufferSize + ")"); partialBufferSize = 0; } } catch (NumberFormatException nfe) { LOGGER.warning("Invalid partial buffer size, defaulting to " + PartialBufferedOutputStream2.DEFAULT_BUFFER_SIZE + " (was " + partialBufferSize + ")"); partialBufferSize = 0; } } } ((PartialBufferStrategy2) theStrategy).setBufferSize(partialBufferSize); } return theStrategy; } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ protected String getMimeType() { ServletContext servContext = getServletContext(); try { return ((GeoServer) servContext.getAttribute("GeoServer")).getMimeType(); } catch (NullPointerException e) { return "text/xml; charset=" + Charset.forName("UTF-8").name(); } } /** * DOCUMENT ME! * * @param response DOCUMENT ME! * @param content DOCUMENT ME! */ protected void send(HttpServletResponse response, CharSequence content) { send(response, content, getMimeType()); } /** * DOCUMENT ME! * * @param response DOCUMENT ME! * @param content DOCUMENT ME! * @param mimeType DOCUMENT ME! */ protected void send(HttpServletResponse response, CharSequence content, String mimeType) { try { response.setContentType(mimeType); response.getWriter().write(content.toString()); } catch (IOException ex) { //stream closed by client, do nothing LOGGER.info("Stream abruptly closed by client, response aborted"); LOGGER.fine(ex.getMessage()); } catch (IllegalStateException ex) { //stream closed by client, do nothing LOGGER.info("Stream abruptly closed by client, response aborted"); LOGGER.fine(ex.getMessage()); } } /** * Send error produced during getService opperation. * *

* Some errors know how to write themselves out WfsTransactionException for * instance. It looks like this might be is handled by * getExceptionHandler().newServiceException( t, pre, null ). I still * would not mind seeing a check for ServiceConfig Exception here. *

* *

* This code says that it deals with UNCAUGHT EXCEPTIONS, so I think it * would be wise to explicitly catch ServiceExceptions. *

* * @param response DOCUMENT ME! * @param t DOCUMENT ME! */ protected void sendError(HttpServletRequest request, HttpServletResponse response, Throwable t) { if (t instanceof ServiceException) { sendError(request, response, (ServiceException) t); return; } LOGGER.info("Had an undefined error: " + t.getMessage()); //TODO: put the stack trace in the logger. //t.printStackTrace(); //String pre = "UNCAUGHT EXCEPTION"; ExceptionHandler exHandler = getExceptionHandler(); ServiceException se = exHandler.newServiceException(t); sendError(request, response, se); //GeoServer geoServer = (GeoServer) this.getServletConfig() // .getServletContext().getAttribute(GeoServer.WEB_CONTAINER_KEY); //send(response, se.getXmlResponse(geoServer.isVerboseExceptions())); } /** * Send a serviceException produced during getService opperation. * * @param response DOCUMENT ME! * @param se DOCUMENT ME! */ protected void sendError(HttpServletRequest request, HttpServletResponse response, ServiceException se) { // first log the exception LOGGER.log(Level.SEVERE, "Service exception occurred", se); String mimeType = se.getMimeType(geoServer); send(response, se.getXmlResponse(geoServer.isVerboseExceptions(), request, geoServer), mimeType); } /** * DOCUMENT ME! * * @param response DOCUMENT ME! * @param result DOCUMENT ME! */ protected void send(HttpServletRequest httpRequest, HttpServletResponse response, Response result) { OutputStream responseOut = null; try { responseOut = response.getOutputStream(); } catch (IOException ex) { //stream closed, do nothing. LOGGER.info("apparently client has closed stream: " + ex.getMessage()); } OutputStream out = new BufferedOutputStream(responseOut); ServletContext servContext = getServletContext(); response.setContentType(result.getContentType( (GeoServer) servContext.getAttribute("GeoServer"))); try { result.writeTo(out); out.flush(); responseOut.flush(); } catch (IOException ioe) { //user just closed the socket stream, do nothing LOGGER.fine("connection closed by user: " + ioe.getMessage()); } catch (ServiceException ex) { sendError(httpRequest, response, ex); } } /** * Checks if the client requests supports gzipped responses by quering it's * 'accept-encoding' header. * * @param request the request to query the HTTP header from * * @return true if 'gzip' if one of the supported content encodings of * request, false otherwise. */ protected boolean requestSupportsGzip(HttpServletRequest request) { boolean supportsGzip = false; String header = request.getHeader("accept-encoding"); if ((header != null) && (header.indexOf("gzip") > -1)) { supportsGzip = true; } if (LOGGER.isLoggable(Level.CONFIG)) { LOGGER.config("user-agent=" + request.getHeader("user-agent")); LOGGER.config("accept=" + request.getHeader("accept")); LOGGER.config("accept-encoding=" + request.getHeader("accept-encoding")); } return supportsGzip; } public String getKvpString() { return kvpString; } public void setKvpString(String kvpString) { this.kvpString = kvpString; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy