Package org.vfny.geoserver.servlets

Source Code of org.vfny.geoserver.servlets.PartialBufferStrategy

/* Copyright (c) 2001, 2003 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 java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
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.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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.GeoServer;
import org.vfny.geoserver.global.Service;
import org.vfny.geoserver.util.PartialBufferedOutputStream;
import org.vfny.geoserver.util.requests.XmlCharsetDetector;
import org.vfny.geoserver.util.requests.readers.KvpRequestReader;
import org.vfny.geoserver.util.requests.readers.XmlRequestReader;


/**
* Represents a service that all others extend from.  Subclasses should provide
* response and exception handlers as appropriate.
*
* <p>
* It is <b>really</b> important to adhere to the following workflow:
*
* <ol>
* <li>
* get a Request reader
* </li>
* <li>
* ask the Request Reader for the Request object
* </li>
* <li>
* Provide the resulting Request with the ServletRequest that generated it
* </li>
* <li>
* get the appropiate ResponseHandler
* </li>
* <li>
* ask it to execute the Request
* </li>
* <li>
* set the response content type
* </li>
* <li>
* write to the http response's output stream
* </li>
* <li>
* pending - call Response cleanup
* </li>
* </ol>
* </p>
*
* <p>
* If anything goes wrong a ServiceException can be thrown and will be written
* to the output stream instead.
* </p>
*
* <p>
* 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)
* </p>
*
* <p>
* TODO: We need to call Response.abort() if anything goes wrong to allow the
* Response a chance to cleanup after itself.
* </p>
*
* @author Gabriel Rold?n
* @author Chris Holmes
* @author Jody Garnett, Refractions Research
* @version $Id: AbstractService.java,v 1.23 2004/09/08 17:34:38 cholmesny Exp $
*/
public abstract class AbstractService extends HttpServlet {
    /** Class logger */
    protected static Logger LOGGER = Logger.getLogger(
            "org.vfny.geoserver.servlets");

    /** DOCUMENT ME! */

    //protected static final GeoServer config = GeoServer.getInstance();

    /** Specifies mime type */

    //protected static final String MIME_TYPE = config.getMimeType();
    private static Map context;

    /**
     * GR: if SPEED, FILE and BUFFER are static instances, so their methods
     * should be synchronized, ending in a not multiuser server, so I made
     * safetyMode dynamically instantiated in init() and the strategy choosed
     * at server config level in web.xml. If I'm wrong, just tell me. If this
     * is correct, may be it will be better to allow for user customized
     * ServiceStrategy implementations to be parametrized by a servlet context
     * param JG: You are exactly right! My-Bad, I was just trying to
     * understand what chris was talking about.
     */
    public static final Map serviceStrategys = new HashMap();

    static {
      serviceStrategys.put("PARTIAL-BUFFER", PartialBufferStrategy.class);
        serviceStrategys.put("SPEED", SpeedStrategy.class);
        serviceStrategys.put("FILE", FileStrategy.class);
        serviceStrategys.put("BUFFER", BufferStrategy.class);
    }

    public static int BUFFER_SIZE;
   
    /** Controls the Safty Mode used when using execute/writeTo. */
    private static Class safetyMode;

    /** DOCUMENT ME!  */
    protected HttpServletRequest curRequest;

    /**
     * loads the "serviceStrategy" servlet context parameter and checks it if
     * reffers to a valid ServiceStrategy (by now, one of SPEED, FILE or
     * BUFFER); if no, just sets the strategy to BUFFER as default
     *
     * @param config the servlet environment
     *
     * @throws ServletException if the configured strategy class is not a
     *         derivate of ServiceStrategy or it is thrown by the parent class
     */
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        LOGGER.info("Looking for configured service responses' strategy");

        ServletContext servContext = config.getServletContext();
        String stgyKey = servContext.getInitParameter("serviceStratagy");
        Class stgyClass = BufferStrategy.class;

        if (stgyKey == null) {
            LOGGER.info("No service strategy configured, defaulting to BUFFER");
        } else {
            LOGGER.info("Looking for configured service strategy " + stgyKey);

            Class configurefStgyClass = (Class) serviceStrategys.get(stgyKey);

            if (configurefStgyClass == null) {
                LOGGER.info("No service strategy named " + stgyKey
                    + "found, defaulting to BUFFER. Please check your config");
            } else {
                stgyClass = configurefStgyClass;
            }
        }

        LOGGER.fine("verifying configured strategy");

        if (!(ServiceStrategy.class.isAssignableFrom(stgyClass))) {
            throw new ServletException("the configured service strategy "
                + stgyClass + " is not a ServiceStrategy derivate");
        }

        LOGGER.info("Using service strategy " + stgyClass);
        AbstractService.safetyMode = stgyClass;
       
        if (stgyClass == PartialBufferStrategy.class)
        {
          // this is a little hacky cause we are still dealing with a class and not an object
         
          // get the default value of the buffer size
          int buffSize = PartialBufferStrategy.DEFAULT_BUFFER_SIZE();
          String size = servContext.getInitParameter("PARTIAL_BUFFER_STRATEGY_SIZE");
          if (size != null)
          {
        try {
          //... convert string to # ...
          Integer i = new Integer(size);
          buffSize = i.intValue();
          LOGGER.info("Set buffer size to " + buffSize);
        }
        catch (Exception e) {
          LOGGER.warning("Invalid default buffer size for PARTIAL-BUFFER: " + size);
        }
          }
         
          BUFFER_SIZE = buffSize;
        }
    }

    /**
     * DOCUMENT ME!
     *
     * @param req DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    protected abstract boolean isServiceEnabled(HttpServletRequest req);

    /**
     * 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)) {
            response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);

            return;
        }

        try {
            String qString = request.getQueryString();
            LOGGER.fine("reading request: " + qString);

            //Map requestParams = KvpRequestReader.parseKvpSet(qString);
            Map requestParams = new HashMap();
            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(response, se);

            return;
        } catch (Throwable e) {
            sendError(response, e);

            return;
        }

        doService(request, response, serviceRequest);
    }

    /**
     * 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)) {
            response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);

            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(response, se);

            return;
        } catch (Throwable e) {
            sendError(response, e);

            return;
        }

        doService(request, response, serviceRequest);
    }

    /**
     * Peforms service according to ServiceStrategy.
     *
     * <p>
     * This method has very strict requirements, please see the class
     * description for the specifics.
     * </p>
     *
     * <p>
     * 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.
     * </p>
     *
     * @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 {
                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
            } catch (IOException e) {
                // do nothing
            }

            return;
        }

        ServiceStrategy strategy = null;
        Response serviceResponse = null;

        try {
            strategy = getServiceStrategy();
            LOGGER.fine("strategy is: " + strategy);
            serviceResponse = getResponseHandler();
        } catch (Throwable t) {
            sendError(response, t);

            return;
        }

        Service s = null;

        if ("WFS".equals(serviceRequest.getService())) {
            s = serviceRequest.getWFS();
        } else {
            s = serviceRequest.getWMS();
        }

        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(response, serviceException);

            return;
        } catch (Throwable t) {
            //we can safelly send errors here, since we have not touched response yet
            serviceResponse.abort(s);
            sendError(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);
            }
        } 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(response, ex);

            return;
        }

        try {
            // gather response
            serviceResponse.writeTo(strategyOuput);
            strategyOuput.flush();
            strategy.flush();
        } catch (java.net.SocketException sockEx) { // user cancel
            serviceResponse.abort(s);
            strategy.abort();

            return;
        } catch (IOException ioException) { // strategyOutput error
            serviceResponse.abort(s);
            strategy.abort();
            sendError(response, ioException);

            return;
        } catch (ServiceException writeToFailure) { // writeTo Failure
            serviceResponse.abort(s);
            strategy.abort();
            sendError(response, writeToFailure);

            return;
        } catch (Throwable help) { // This is an unexpected error(!)
          help.printStackTrace();
            serviceResponse.abort(s);
            strategy.abort();
            sendError(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.
     *
     * @return The response that the request read by this servlet should be
     *         passed to.
     */
    protected abstract Response getResponseHandler();

    /**
     * Gets a reader that will figure out the correct Key Vaule Pairs for this
     * service.
     *
     * @param params A map of the kvp pairs.
     *
     * @return An initialized KVP reader to decode the request.
     */
    protected abstract KvpRequestReader getKvpReader(Map params);

    /**
     * Gets a reader that will handle a posted xml request for this servlet.
     *
     * @return An XmlRequestReader appropriate to this service.
     */
    protected abstract XmlRequestReader getXmlRequestReader();

    /**
     * Gets the exception handler for this service.
     *
     * @return The correct ExceptionHandler
     */
    protected abstract ExceptionHandler getExceptionHandler();

    /**
     * Instantiates a given strategy class and throws the proper exceptions.
     * Used as a helper class to create service strategies, since the
     * hierarchy for inner classes and whatnot is a bit funky.
     *
     * @param strategyClass the strategy to instantiate.
     *
     * @return The Service Strategy insantiated.
     *
     * @throws ServiceException for any instantiation problems.
     *
     * @see #getServiceStrategy
     */
    protected AbstractService.ServiceStrategy getServiceStrategy(
        Class strategyClass) throws ServiceException {
        ServiceStrategy strategy = null;

        try {
            strategy = (ServiceStrategy) strategyClass.newInstance();
        } catch (InstantiationException ex) {
            throw new ServiceException(strategy
                + " is not a valid ServiceStrategy", ex);
        } catch (IllegalAccessException ex) {
            throw new ServiceException(strategy
                + " is not a valid ServiceStrategy", ex);
        }

        return strategy;
    }

    /**
     * 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.
     *
     * <p>
     * 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.)
     * </p>
     *
     * <p>
     * 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.
     * </p>
     *
     * @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 AbstractService.ServiceStrategy getServiceStrategy()
        throws ServiceException {
        ServletContext servContext = getServletContext();
        GeoServer geoServer = (GeoServer) servContext.getAttribute(GeoServer.WEB_CONTAINER_KEY);

        //If verbose exceptions is on then lets make sure they actually get the
        //exception by using the file strategy.
        if (geoServer.isVerboseExceptions()) {
            return getServiceStrategy(FileStrategy.class);
        } else {
            return getServiceStrategy(safetyMode);
        }
    }

    /**
     * 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").displayName();
        }
    }

    /**
     * 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.fine(ex.getMessage());
        }
    }

    /**
     * Send error produced during getService opperation.
     *
     * <p>
     * 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.
     * </p>
     *
     * <p>
     * This code says that it deals with UNCAUGHT EXCEPTIONS, so I think it
     * would be wise to explicitly catch ServiceExceptions.
     * </p>
     *
     * @param response DOCUMENT ME!
     * @param t DOCUMENT ME!
     */
    protected void sendError(HttpServletResponse response, Throwable t) {
        if (t instanceof ServiceException) {
            sendError(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(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(HttpServletResponse response, ServiceException se) {
        GeoServer geoServer = (GeoServer) this.getServletConfig()
                                              .getServletContext().getAttribute(GeoServer.WEB_CONTAINER_KEY);

        String mimeType = se.getMimeType(geoServer);

        send(response,
            se.getXmlResponse(geoServer.isVerboseExceptions(), curRequest),
            mimeType);
        se.printStackTrace();
    }

    /**
     * DOCUMENT ME!
     *
     * @param response DOCUMENT ME!
     * @param result DOCUMENT ME!
     */
    protected void send(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(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
     *         <code>request</code>, 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;
    }

    /**
     * Interface used for ServiceMode strategy objects.
     *
     * <p>
     * While this interface resembles the Enum idiom in that only three
     * instances are available SPEED, BUFFER and FILE, we are using this class
     * to plug-in the implementation for our doService request in the manner
     * of the strategy pattern.
     * </p>
     *
     * @author Jody Garnett, Refractions Research
     */
    static public interface ServiceStrategy {
        /**
         * Get a OutputStream we can use to add content.
         *
         * <p>
         * JG - Can we replace this with a Writer?
         * </p>
         *
         * @param response
         *
         * @return
         *
         * @throws IOException
         */
        public OutputStream getDestination(HttpServletResponse response)
            throws IOException;

        /**
         * Complete opperation in the positive.
         *
         * <p>
         * Gives service a chance to finish with destination, and clean up any
         * resources.
         * </p>
         *
         * @throws IOException DOCUMENT ME!
         */
        public void flush() throws IOException;

        /**
         * Complete opperation in the negative.
         *
         * <p>
         * Gives ServiceConfig a chance to clean up resources
         * </p>
         */
        public void abort();
    }
}


/**
* Fast and Dangeroud service strategy.
*
* <p>
* Will fail when a ServiceException is encountered on writeTo, and will not
* tell the user about it!
* </p>
*
* <p>
* This is the worst case scenario, you are trading speed for danger by using
* this ServiceStrategy.
* </p>
*
* @author jgarnett
*/
class SpeedStrategy implements AbstractService.ServiceStrategy {
    /** DOCUMENT ME!  */
    private OutputStream out = null;

    /**
     * Works against the real output stream provided by the response.
     *
     * <p>
     * This is dangerous of course, but fast and exciting.
     * </p>
     *
     * @param response Response provided by doService
     *
     * @return An OutputStream that works against, the response output stream.
     *
     * @throws IOException If response output stream could not be aquired
     */
    public OutputStream getDestination(HttpServletResponse response)
        throws IOException {
        out = response.getOutputStream();
        out = new BufferedOutputStream(out);

        return out;
    }

    /**
     * Completes writing to Response.getOutputStream.
     *
     * @throws IOException If Response.getOutputStream not available.
     */
    public void flush() throws IOException {
        if (out != null) {
            out.flush();
        }
    }

    /* (non-Javadoc)
     * @see org.vfny.geoserver.servlets.AbstractService.ServiceStrategy#abort()
     */
    public void abort() {
        // out.close();
    }
}


/**
* A safe Service strategy that buffers output until writeTo completes.
*
* <p>
* This strategy wastes memory, for saftey. It represents a middle ground
* between SpeedStrategy and FileStrategy
* </p>
*
* @author jgarnett
*/
class BufferStrategy implements AbstractService.ServiceStrategy {
    /** DOCUMENT ME!  */
    ByteArrayOutputStream buffer = null;

    /** DOCUMENT ME!  */
    private HttpServletResponse response;

    /**
     * Provides a ByteArrayOutputStream for writeTo.
     *
     * @param response Response being processed.
     *
     * @return A ByteArrayOutputStream for writeTo opperation.
     *
     * @throws IOException DOCUMENT ME!
     */
    public OutputStream getDestination(HttpServletResponse response)
        throws IOException {
        this.response = response;
        buffer = new ByteArrayOutputStream(1024 * 1024);

        return buffer;
    }

    /**
     * Copies Buffer to Response output output stream.
     *
     * @throws IOException If the response outputt stream is unavailable.
     */
    public void flush() throws IOException {
        if ((buffer == null) || (response == null)) {
            return; // should we throw an Exception here
        }

        OutputStream out = response.getOutputStream();
        BufferedOutputStream buffOut = new BufferedOutputStream(out, 1024 * 1024);
        buffer.writeTo(buffOut);
        buffOut.flush();
    }

    /**
     * Clears the buffer with out writing anything out to response.
     *
     * @see org.vfny.geoserver.servlets.AbstractService.ServiceStrategy#abort()
     */
    public void abort() {
        if (buffer == null) {
            return;
        }
    }
}


/**
* A safe ServiceConfig strategy that uses a temporary file until writeTo
* completes.
*
* @author $author$
* @version $Revision: 1.23 $
*/
class FileStrategy implements AbstractService.ServiceStrategy {
    /** Buffer size used to copy safe to response.getOutputStream() */
    private static int BUFF_SIZE = 4096;

    /** Temporary file number */
    static int sequence = 0;

    /** Class logger */
    protected static Logger LOGGER = Logger.getLogger(
            "org.vfny.geoserver.servlets");

    /** Response being targeted */
    private HttpServletResponse response;

    /** OutputStream provided to writeTo method */
    private OutputStream safe;

    /** Temporary file used by safe */
    private File temp;

    /**
     * Provides a outputs stream on a temporary file.
     *
     * <p>
     * I have changed this to use a BufferedWriter to agree with SpeedStrategy.
     * </p>
     *
     * @param response Response being handled
     *
     * @return Outputstream for a temporary file
     *
     * @throws IOException If temporary file could not be created.
     */
    public OutputStream getDestination(HttpServletResponse response)
        throws IOException {
        // REVISIT: Should do more than sequence here
        // (In case we are running two GeoServers at once)
        // - Could we use response.getHandle() in the filename?
        // - ProcessID is traditional, I don't know how to find that in Java
        this.response = response;
        sequence++;
        // lets check for file permissions first so we can throw a clear error
        try {
          temp = File.createTempFile("wfs" + sequence, "tmp");
          if (!temp.canRead() || !temp.canWrite())
          {
            String errorMsg = "Temporary-file permission problem for location: " + temp.getPath();
              throw new IOException(errorMsg);
          }
        } catch (IOException e) {
          String errorMsg = "Possible file permission problem. Root cause: \n" + e.toString();
          IOException newE = new IOException(errorMsg);
          throw newE;
        }
        temp.deleteOnExit();
        safe = new BufferedOutputStream(new FileOutputStream(temp));

        return safe;
    }

    /**
     * Closes safe output stream, copies resulting file to response.
     *
     * @throws IOException If temporay file or response is unavailable
     * @throws IllegalStateException if flush is called before getDestination
     */
    public void flush() throws IOException {
        if ((temp == null) || (response == null) || (safe == null)
                || !temp.exists()) {
            LOGGER.fine("temp is " + temp + ", response is " + response
                + " safe is " + safe + ", temp exists " + temp.exists());
            throw new IllegalStateException(
                "flush should only be called after getDestination");
        }

        InputStream copy = null;

        try {
            safe.flush();
            safe.close();
            safe = null;

            // service succeeded in producing a response!
            // copy result to the real output stream
            copy = new BufferedInputStream(new FileInputStream(temp));

            OutputStream out = response.getOutputStream();
            out = new BufferedOutputStream(out, 1024 * 1024);

            byte[] buffer = new byte[BUFF_SIZE];
            int b;

            while ((b = copy.read(buffer, 0, BUFF_SIZE)) > 0) {
                out.write(buffer, 0, b);
            }

            // Speed Writer closes output Stream
            // I would prefer to leave that up to doService...
            out.flush();

            // out.close();
        } catch (IOException ioe) {
            throw ioe;
        } finally {
            if (copy != null) {
                try {
                    copy.close();
                } catch (Exception ex) {
                }
            }

            copy = null;

            if ((temp != null) && temp.exists()) {
                temp.delete();
            }

            temp = null;
            response = null;
            safe = null;
        }
    }

    /**
     * Clean up after writeTo fails.
     *
     * @see org.vfny.geoserver.servlets.AbstractService.ServiceStrategy#abort()
     */
    public void abort() {
        if (safe != null) {
            try {
                safe.close();
            } catch (IOException ioException) {
            }

            safe = null;
        }

        if ((temp != null) && temp.exists()) {
            temp.delete();
        }

        temp = null;
        response = null;
    }
   
}

/**
* <b>PartialBufferStrategy</b><br>
* Oct 19, 2005<br>
*
* <b>Purpose:</b><br>
* This strategy will buffer the response before it starts streaming it to the user. This
* will allow for errors to be caught early so a proper error message can be sent to the
* user. Right now it buffers the first 20KB, enough for a full getCapabilities document.
*
* @author Brent Owens (The Open Planning Project)
* @version
*/
class PartialBufferStrategy implements AbstractService.ServiceStrategy
{
    /** Class logger */
    protected static Logger LOGGER = Logger.getLogger(
            "org.vfny.geoserver.servlets");

    private PartialBufferedOutputStream out = null;

  /* (non-Javadoc)
   * @see org.vfny.geoserver.servlets.AbstractService.ServiceStrategy#getDestination(javax.servlet.http.HttpServletResponse)
   */
  public OutputStream getDestination(HttpServletResponse response) throws IOException
  {
    out = new PartialBufferedOutputStream(response, AbstractService.BUFFER_SIZE);
    return out;
  }

  public static int DEFAULT_BUFFER_SIZE() {
    return PartialBufferedOutputStream.DEFAULT_BUFFER_SIZE;
  }

  /* (non-Javadoc)
   * @see org.vfny.geoserver.servlets.AbstractService.ServiceStrategy#flush()
   */
  public void flush() throws IOException
  {
    if (out != null)
    {
            out.forceFlush();
            out = null;
    }
  }

  /* (non-Javadoc)
   * @see org.vfny.geoserver.servlets.AbstractService.ServiceStrategy#abort()
   */
  public void abort()
  {
    if (out != null)
    {
      try {
        if (out.abort())
          LOGGER.info("OutputStream was successfully aborted.");
        else
          LOGGER.warning("OutputStream could not be aborted in time. An error has occurred and could not be sent to the user.");
      } catch (IOException e) {
        LOGGER.warning("Error aborting OutputStream");
        e.printStackTrace();
      }
    }
  }
 
 
 
 
 
}
TOP

Related Classes of org.vfny.geoserver.servlets.PartialBufferStrategy

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.