org.deegree.services.controller.OGCFrontController.java Source code

Java tutorial

Introduction

Here is the source code for org.deegree.services.controller.OGCFrontController.java

Source

//$HeadURL$
/*----------------------------------------------------------------------------
 This file is part of deegree, http://deegree.org/
 Copyright (C) 2001-2009 by:
 Department of Geography, University of Bonn
 and
 lat/lon GmbH
    
 This library is free software; you can redistribute it and/or modify it under
 the terms of the GNU Lesser General Public License as published by the Free
 Software Foundation; either version 2.1 of the License, or (at your option)
 any later version.
 This library is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 details.
 You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    
 Contact information:
    
 lat/lon GmbH
 Aennchenstr. 19, 53177 Bonn
 Germany
 http://lat-lon.de/
    
 Department of Geography, University of Bonn
 Prof. Dr. Klaus Greve
 Postfach 1147, 53001 Bonn
 Germany
 http://www.geographie.uni-bonn.de/deegree/
    
 e-mail: info@deegree.org
 ----------------------------------------------------------------------------*/
package org.deegree.services.controller;

import static java.io.File.createTempFile;
import static java.util.Collections.emptyList;
import static org.deegree.commons.ows.exception.OWSException.NO_APPLICABLE_CODE;
import static org.deegree.commons.tom.ows.Version.parseVersion;
import static org.reflections.util.ClasspathHelper.forClassLoader;
import static org.reflections.util.ClasspathHelper.forWebInfLib;
import static org.slf4j.LoggerFactory.getLogger;

import java.beans.Introspector;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.imageio.spi.IIORegistry;
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 javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamReader;

import org.apache.axiom.om.OMElement;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.SOAPFactory;
import org.apache.axiom.soap.impl.builder.StAXSOAPModelBuilder;
import org.apache.axiom.soap.impl.llom.soap11.SOAP11Factory;
import org.apache.axiom.soap.impl.llom.soap12.SOAP12Factory;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.LogManager;
import org.deegree.commons.annotations.LoggingNotes;
import org.deegree.commons.concurrent.Executor;
import org.deegree.commons.config.DeegreeWorkspace;
import org.deegree.commons.config.ResourceInitException;
import org.deegree.commons.ows.exception.OWSException;
import org.deegree.commons.tom.ows.Version;
import org.deegree.commons.utils.DeegreeAALogoUtils;
import org.deegree.commons.utils.io.LoggingInputStream;
import org.deegree.commons.utils.kvp.KVPUtils;
import org.deegree.commons.xml.XMLAdapter;
import org.deegree.commons.xml.XMLProcessingException;
import org.deegree.commons.xml.stax.XMLInputFactoryUtils;
import org.deegree.commons.xml.stax.XMLStreamUtils;
import org.deegree.feature.stream.ThreadedFeatureInputStream;
import org.deegree.services.OWS;
import org.deegree.services.OWSProvider;
import org.deegree.services.OwsManager;
import org.deegree.services.authentication.SecurityException;
import org.deegree.services.controller.exception.serializer.XMLExceptionSerializer;
import org.deegree.services.controller.security.SecurityConfiguration;
import org.deegree.services.controller.utils.HttpResponseBuffer;
import org.deegree.services.controller.utils.LoggingHttpResponseWrapper;
import org.deegree.services.controller.watchdog.RequestWatchdog;
import org.deegree.services.jaxb.controller.DeegreeServiceControllerType;
import org.deegree.services.jaxb.controller.DeegreeServiceControllerType.RequestTimeoutMilliseconds;
import org.deegree.services.ows.OWS110ExceptionReportSerializer;
import org.deegree.services.resources.ResourcesServlet;
import org.deegree.workspace.standard.ModuleInfo;
import org.slf4j.Logger;

/**
 * Servlet that acts as OWS-HTTP communication end point and dispatcher to the {@link OWS} instances configured in the
 * active {@link DeegreeWorkspace}.
 * <p>
 * Calls to {@link #doGet(HttpServletRequest, HttpServletResponse)} and
 * {@link #doPost(HttpServletRequest, HttpServletResponse)} are processed as follows:
 * <nl>
 * <li>The DCP-type of the incoming request is determined. This must be one of the following:
 * <ul>
 * <li>KVP</li>
 * <li>XML</li>
 * <li>SOAP (OGC style, the XML request is the child element of the SOAP body)</li>
 * </ul>
 * </li>
 * <li>The responsible {@link OWS} instance is determined and one of the following methods is called:
 * <ul>
 * <li>{@link OWS#doKVP(Map, HttpServletRequest, HttpResponseBuffer, List)}</li>
 * <li>{@link OWS#doXML(XMLStreamReader, HttpServletRequest, HttpResponseBuffer, List)}</li>
 * <li>
 * {@link OWS#doSOAP(SOAPEnvelope, HttpServletRequest, HttpResponseBuffer, List, SOAPFactory)}</li>
 * </ul>
 * </li>
 * </nl>
 * </p>
 * 
 * @see OWS
 * 
 * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema </a>
 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
 * @author last edited by: $Author$
 * 
 * @version $Revision$, $Date$
 */
@LoggingNotes(debug = "logs the server startup, incoming requests and timing info, also enables enhanced request logging in $HOME/.deegree")
public class OGCFrontController extends HttpServlet {

    private static final Logger LOG = getLogger(OGCFrontController.class);

    private static final long serialVersionUID = -1379869403008798932L;

    /** used to decode (already URL-decoded) query strings */
    private static final String DEFAULT_ENCODING = "UTF-8";

    private static final String defaultTMPDir = System.getProperty("java.io.tmpdir");

    // file name that stores active workspaces (per webapp)
    private static final String ACTIVE_WS_CONFIG_FILE = "webapps.properties";

    private static OGCFrontController instance;

    // make fields transient, serialized servlets are a bad idea IMHO
    private transient DeegreeServiceControllerType mainConfig;

    private transient String hardcodedServicesUrl;

    private transient String hardcodedResourcesUrl;

    private transient final ThreadLocal<RequestContext> CONTEXT = new ThreadLocal<RequestContext>();

    private transient RequestWatchdog requestWatchdog;

    private transient SecurityConfiguration securityConfiguration;

    private transient OwsManager serviceConfiguration;

    private transient DeegreeWorkspace workspace;

    private transient String ctxPath;

    private transient Collection<ModuleInfo> modulesInfo;

    private transient String version;

    /**
     * Returns the only instance of this class.
     * 
     * @return the only instance of this class, never <code>null</code>
     * @throws RuntimeException
     *             if {@link #init()} has not been called
     */
    public static synchronized OGCFrontController getInstance() {
        if (instance == null) {
            throw new RuntimeException("OGCFrontController has not been initialized yet.");
        }
        return instance;
    }

    /**
     * Returns the {@link RequestContext} associated with the calling thread.
     * <p>
     * NOTE: This method will only return a correct result if the calling thread originated in the
     * {@link #doGet(HttpServletRequest, HttpServletResponse)} or
     * {@link #doPost(HttpServletRequest, HttpServletResponse)} of this class (or has been spawned as a child thread by
     * such a thread).
     * </p>
     * 
     * @return the {@link RequestContext} associated with the calling thread
     */
    public static RequestContext getContext() {
        RequestContext context = instance.CONTEXT.get();
        LOG.debug("Retrieving RequestContext for current thread " + Thread.currentThread() + ": " + context);
        return context;
    }

    /**
     * @return the service workspace
     */
    public static DeegreeWorkspace getServiceWorkspace() {
        return getInstance().workspace;
    }

    /**
     * @return the service configuration
     */
    public static OwsManager getServiceConfiguration() {
        return getInstance().serviceConfiguration;
    }

    /**
     * Returns the HTTP URL for accessing the {@link OGCFrontController}/{@link OWS} (using POST requests).
     * <p>
     * NOTE: This includes OGC service instance path info (service identifier), if available. This method will only
     * return a correct result if the calling thread originated in the
     * {@link #doGet(HttpServletRequest, HttpServletResponse)} or
     * {@link #doPost(HttpServletRequest, HttpServletResponse)} of this class (or has been spawned as a child thread by
     * such a thread).
     * </p>
     * 
     * @return URL, never <code>null</code> (without trailing slash or question mark)
     */
    public static String getHttpPostURL() {
        return getContext().getServiceUrl();
    }

    /**
     * Returns the HTTP URL for accessing the {@link OGCFrontController}/{@link OWS} (using GET requests).
     * <p>
     * NOTE: This includes OGC service instance path info (service identifier), if available. This method will only
     * return a correct result if the calling thread originated in the
     * {@link #doGet(HttpServletRequest, HttpServletResponse)} or
     * {@link #doPost(HttpServletRequest, HttpServletResponse)} of this class (or has been spawned as a child thread by
     * such a thread).
     * </p>
     * 
     * @return URL (for GET requests), never <code>null</code> (with trailing question mark)
     */
    public static String getHttpGetURL() {
        return getContext().getServiceUrl() + "?";
    }

    /**
     * Returns the HTTP URL for accessing the {@link ResourcesServlet}.
     * <p>
     * NOTE: This method will only return a correct result if the calling thread originated in the
     * {@link #doGet(HttpServletRequest, HttpServletResponse)} or
     * {@link #doPost(HttpServletRequest, HttpServletResponse)} of this class (or has been spawned as a child thread by
     * such a thread).
     * </p>
     * 
     * @return URL, never <code>null</code> (without trailing slash or question mark)
     */
    public static String getResourcesUrl() {
        return getContext().getResourcesUrl();
    }

    /**
     * Returns the {@link ModuleInfo}s for the deegree modules accessible by the webapp classloader.
     * 
     * @return module infos, never <code>null</code>, but can be empty
     */
    public static Collection<ModuleInfo> getModulesInfo() {
        return getInstance().modulesInfo;
    }

    private static void addHeaders(HttpServletResponse response) {
        // add cache control headers
        response.addHeader("Cache-Control", "no-cache, no-store");
        // add deegree header
        response.addHeader("deegree-version", getInstance().version);
    }

    /**
     * Handles HTTP GET requests.
     * <p>
     * An HTTP GET request implies that input parameters are specified as key-value pairs. However, at least one OGC
     * service specification allows the sending of XML requests via GET (see WCS 1.0.0 specification, section 6.3.3). In
     * this case, the query string contains no <code>key=value</code> pairs, but the (URL encoded) xml. The encoding
     * ensures that no <code>=</code> char (parameter/value delimiters) occur in the string.
     * </p>
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        HttpResponseBuffer responseBuffer = createHttpResponseBuffer(request, response);

        try {
            long entryTime = System.currentTimeMillis();

            logHeaders(request);
            addHeaders(responseBuffer);
            responseBuffer = handleCompression(responseBuffer);

            String queryString = request.getQueryString();
            try {
                LOG.debug("doGet(), query string: '" + queryString + "'");

                if (queryString == null) {
                    OWSException ex = new OWSException("The request did not contain any parameters.",
                            "MissingParameterValue");
                    OWS ows = null;
                    try {
                        ows = determineOWSByPathQuirk(request);
                    } catch (OWSException e) {
                        sendException(null, e, responseBuffer, null);
                        return;
                    }
                    sendException(ows, ex, responseBuffer, null);
                    return;
                }

                // handle as XML, if the request starts with '<'
                boolean isXML = queryString.startsWith("<");
                List<FileItem> multiParts = checkAndRetrieveMultiparts(request);
                if (isXML) {
                    XMLStreamReader xmlStream = null;
                    String dummySystemId = "HTTP Get request from " + request.getRemoteAddr() + ":"
                            + request.getRemotePort();
                    if (multiParts != null && multiParts.size() > 0) {
                        InputStream is = multiParts.get(0).getInputStream();
                        xmlStream = XMLInputFactoryUtils.newSafeInstance().createXMLStreamReader(dummySystemId, is);
                    } else {
                        // decode query string
                        String decodedString = URLDecoder.decode(queryString, DEFAULT_ENCODING);
                        StringReader reader = new StringReader(decodedString);
                        xmlStream = XMLInputFactoryUtils.newSafeInstance().createXMLStreamReader(dummySystemId,
                                reader);
                    }
                    if (isSOAPRequest(xmlStream)) {
                        dispatchSOAPRequest(xmlStream, request, responseBuffer, multiParts);
                    } else {
                        dispatchXMLRequest(xmlStream, request, responseBuffer, multiParts);
                    }
                } else {
                    // for GET requests, there is no standard way for defining the used encoding
                    Map<String, String> normalizedKVPParams = KVPUtils.getNormalizedKVPMap(request.getQueryString(),
                            DEFAULT_ENCODING);
                    LOG.debug("parameter map: " + normalizedKVPParams);
                    dispatchKVPRequest(normalizedKVPParams, request, responseBuffer, multiParts, entryTime);
                }
            } catch (XMLProcessingException e) {
                // the message might be more meaningful
                OWSException ex = new OWSException(
                        "The request did not contain KVP parameters and no parseable XML.", "MissingParameterValue",
                        "request");
                sendException(null, ex, responseBuffer, null);
                return;
            } catch (Throwable e) {
                e.printStackTrace();
                LOG.debug("Handling HTTP-GET request took: " + (System.currentTimeMillis() - entryTime)
                        + " ms before sending exception.");
                LOG.debug(e.getMessage(), e);
                OWSException ex = new OWSException(e.getLocalizedMessage(), e, "InvalidRequest");
                sendException(null, ex, responseBuffer, null);
                return;
            }
            LOG.debug("Handling HTTP-GET request with status 'success' took: "
                    + (System.currentTimeMillis() - entryTime) + " ms.");
        } finally {
            getInstance().CONTEXT.remove();
            responseBuffer.flushBuffer();
            if (mainConfig.isValidateResponses() != null && mainConfig.isValidateResponses()) {
                validateResponse(responseBuffer);
            }
        }
    }

    private void logHeaders(HttpServletRequest request) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("HTTP headers:");
            Enumeration<String> headerEnum = request.getHeaderNames();
            while (headerEnum.hasMoreElements()) {
                String headerName = headerEnum.nextElement();
                LOG.debug("- " + headerName + "='" + request.getHeader(headerName) + "'");
            }
        }
    }

    /**
     * Handles HTTP POST requests.
     * <p>
     * An HTTP POST request specifies parameters in the request body. OGC service specifications use three different
     * ways to encode the parameters:
     * <ul>
     * <li><b>KVP</b>: Parameters are given as <code>key=value</code> pairs which are separated using the &amp;
     * character. This is equivalent to standard HTTP GET requests, except that the parameters are not part of the query
     * string, but the POST body. In this case, the <code>content-type</code> field in the header must be
     * <code>application/x-www-form-urlencoded</code>.</li>
     * <li><b>XML</b>: The POST body contains an XML document. In this case, the <code>content-type</code> field in the
     * header has to be <code>text/xml</code>, but the implementation does not rely on this in order to be more tolerant
     * to clients.</li>
     * <li><b>SOAP</b>: TODO</li>
     * <li><b>Multipart</b>: TODO</li>
     * </ul>
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        HttpResponseBuffer responseBuffer = createHttpResponseBuffer(request, response);

        try {
            logHeaders(request);
            addHeaders(responseBuffer);
            responseBuffer = handleCompression(responseBuffer);

            LOG.debug("doPost(), contentType: '" + request.getContentType() + "'");

            long entryTime = System.currentTimeMillis();
            try {
                // check if content-type implies that it's a KVP request
                String contentType = request.getContentType();
                boolean isKVP = false;
                if (contentType != null) {
                    isKVP = request.getContentType().startsWith("application/x-www-form-urlencoded");
                }
                List<FileItem> multiParts = checkAndRetrieveMultiparts(request);
                InputStream is = request.getInputStream();

                if (isKVP) {
                    String queryString = readPostBodyAsString(is);
                    LOG.debug("Treating POST input stream as KVP parameters. Raw input: '" + queryString + "'.");
                    Map<String, String> normalizedKVPParams = null;
                    String encoding = request.getCharacterEncoding();
                    if (encoding == null) {
                        LOG.debug("Request has no further encoding information. Defaulting to '" + DEFAULT_ENCODING
                                + "'.");
                        normalizedKVPParams = KVPUtils.getNormalizedKVPMap(queryString, DEFAULT_ENCODING);
                    } else {
                        LOG.debug("Client encoding information :" + encoding);
                        normalizedKVPParams = KVPUtils.getNormalizedKVPMap(queryString, encoding);
                    }
                    dispatchKVPRequest(normalizedKVPParams, request, responseBuffer, multiParts, entryTime);
                } else {
                    // if( handle multiparts, get first body from multipart (?)
                    // body->requestDoc

                    InputStream requestInputStream = null;
                    if (multiParts != null && multiParts.size() > 0) {
                        for (int i = 0; i < multiParts.size() && requestInputStream == null; ++i) {
                            FileItem item = multiParts.get(i);
                            if (item != null) {
                                LOG.debug("Using multipart item: " + i + " with contenttype: "
                                        + item.getContentType() + " as the request.");
                                requestInputStream = item.getInputStream();
                            }
                        }
                    } else {
                        requestInputStream = is;
                    }
                    if (requestInputStream == null) {
                        String msg = "Could not create a valid inputstream from request "
                                + ((multiParts != null && multiParts.size() > 0) ? "without" : "with")
                                + " multiparts.";
                        LOG.error(msg);
                        throw new IOException(msg);
                    }

                    String dummySystemId = "HTTP Post request from " + request.getRemoteAddr() + ":"
                            + request.getRemotePort();
                    XMLStreamReader xmlStream = XMLInputFactoryUtils.newSafeInstance()
                            .createXMLStreamReader(dummySystemId, requestInputStream);
                    // skip to start tag of root element
                    XMLStreamUtils.nextElement(xmlStream);
                    if (isSOAPRequest(xmlStream)) {
                        dispatchSOAPRequest(xmlStream, request, responseBuffer, multiParts);
                    } else {
                        dispatchXMLRequest(xmlStream, request, responseBuffer, multiParts);
                    }
                }
            } catch (Throwable e) {
                LOG.debug("Handling HTTP-POST request took: " + (System.currentTimeMillis() - entryTime)
                        + " ms before sending exception.");
                LOG.debug(e.getMessage(), e);
                OWSException ex = new OWSException(e.getLocalizedMessage(), "InvalidRequest");
                OWS ows = null;
                try {
                    ows = determineOWSByPath(request);
                } catch (OWSException e2) {
                    sendException(ows, e2, responseBuffer, null);
                    return;
                }
                sendException(ows, ex, responseBuffer, null);
            }
            LOG.debug("Handling HTTP-POST request with status 'success' took: "
                    + (System.currentTimeMillis() - entryTime) + " ms.");
        } finally {
            instance.CONTEXT.remove();
            responseBuffer.flushBuffer();
            if (mainConfig.isValidateResponses() != null && mainConfig.isValidateResponses()) {
                validateResponse(responseBuffer);
            }
        }
    }

    private HttpResponseBuffer createHttpResponseBuffer(HttpServletRequest request, HttpServletResponse response)
            throws FileNotFoundException, IOException {
        OwsGlobalConfigLoader loader = workspace.getNewWorkspace().getInitializable(OwsGlobalConfigLoader.class);
        if (loader.getRequestLogger() != null) {
            response = createLoggingResponseWrapper(request, response);
        }
        return new HttpResponseBuffer(response);
    }

    private HttpServletResponse createLoggingResponseWrapper(HttpServletRequest request,
            HttpServletResponse response) throws IOException, FileNotFoundException {
        OwsGlobalConfigLoader loader = workspace.getNewWorkspace().getInitializable(OwsGlobalConfigLoader.class);

        Boolean conf = mainConfig.getRequestLogging().isOnlySuccessful();
        boolean onlySuccessful = conf != null && conf;

        if ("POST".equals(request.getMethod()) && loader.getRequestLogger() != null) {
            String dir = mainConfig.getRequestLogging().getOutputDirectory();
            File file;
            if (dir == null) {
                file = createTempFile("request", ".body");
            } else {
                File directory = new File(dir);
                if (!directory.exists()) {
                    directory.mkdirs();
                }
                file = createTempFile("request", ".body", directory);
            }
            InputStream is = new LoggingInputStream(request.getInputStream(), new FileOutputStream(file));
            response = new LoggingHttpResponseWrapper(request.getRequestURL().toString(), response, file,
                    onlySuccessful, loader.getRequestLogger(), is);
        } else {
            response = new LoggingHttpResponseWrapper(response, request.getQueryString(), onlySuccessful,
                    loader.getRequestLogger(), null);
        }
        return response;
    }

    private OWS determineOWSByPath(HttpServletRequest request) throws OWSException {
        OWS ows = null;
        String pathInfo = request.getPathInfo();
        if (pathInfo != null) {
            // remove start "/"
            String serviceId = pathInfo.substring(1);
            ows = workspace.getNewWorkspace().getResource(OWSProvider.class, serviceId);
            if (ows == null && serviceConfiguration.isSingleServiceConfigured()) {
                ows = serviceConfiguration.getSingleConfiguredService();
            }
            if (ows == null) {
                String msg = "No service with identifier '" + serviceId + "' available.";
                OWSException e = new OWSException(msg, OWSException.NO_APPLICABLE_CODE);
                throw e;
            }
        }
        return ows;
    }

    private OWS determineOWSByPathQuirk(HttpServletRequest request) throws OWSException {
        OWS ows = null;
        String pathInfo = request.getPathInfo();
        if (pathInfo != null) {
            // remove start "/"
            String serviceId;
            // nice hack to work around the most stupid WFS 1.1.0 CITE tests
            // I'm sure there are a bazillion clients around that send out broken URLs, then validate the exception
            // responses, see the error of their ways and then send a proper request...
            if (pathInfo.indexOf("#") != -1) {
                serviceId = pathInfo.substring(1, pathInfo.indexOf("#"));
            } else if (pathInfo.indexOf("=") != -1) {
                serviceId = pathInfo.substring(1, pathInfo.indexOf("="));
            } else {
                serviceId = pathInfo.substring(1);
            }

            ows = workspace.getNewWorkspace().getResource(OWSProvider.class, serviceId);
            if (ows == null && serviceConfiguration.isSingleServiceConfigured()) {
                ows = serviceConfiguration.getSingleConfiguredService();
            }
            if (ows == null) {
                String msg = "No service with identifier '" + serviceId + "' available.";
                OWSException e = new OWSException(msg, OWSException.NO_APPLICABLE_CODE);
                // sendException( null, e, response, null );
                throw e;
            }
        }
        return ows;
    }

    private static HttpResponseBuffer handleCompression(HttpResponseBuffer response) {
        // TODO check if we should enable this in any case (XML, images, ...)
        // String encoding = request.getHeader( "Accept-Encoding" );
        // boolean supportsGzip = encoding != null && encoding.toLowerCase().contains( "gzip" );
        // if ( supportsGzip ) {
        // LOG.debug( "Using GZIP-compression" );
        // response = new GZipHttpServletResponse( response );
        // }
        return response;
    }

    private static String readPostBodyAsString(InputStream is) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        BufferedInputStream bis = new BufferedInputStream(is);
        byte[] readBuffer = new byte[1024];
        int numBytes = -1;
        while ((numBytes = bis.read(readBuffer)) != -1) {
            bos.write(readBuffer, 0, numBytes);
        }
        return bos.toString().trim();
    }

    /**
     * Checks if the given request is a multi part request. If so it will return the multiparts as a list of
     * {@link FileItem} else <code>null</code> will be returned.
     * 
     * @param request
     *            to check
     * @return a list of multiparts or <code>null</code> if it was not a multipart request.
     * @throws FileUploadException
     *             if there are problems reading/parsing the request or storing files.
     */
    private List<FileItem> checkAndRetrieveMultiparts(HttpServletRequest request) throws FileUploadException {
        List<FileItem> result = null;
        if (ServletFileUpload.isMultipartContent(request)) {
            // Create a factory for disk-based file items
            FileItemFactory factory = new DiskFileItemFactory();
            LOG.debug("The incoming request is a multipart request.");
            // Create a new file upload handler
            ServletFileUpload upload = new ServletFileUpload(factory);

            // Parse the request
            result = upload.parseRequest(request);
            LOG.debug("The multipart request contains: " + result.size() + " items.");
            OwsGlobalConfigLoader loader = workspace.getNewWorkspace()
                    .getInitializable(OwsGlobalConfigLoader.class);
            if (loader.getRequestLogger() != null) { // TODO, this is not actually something of the
                // request logger, what is
                // actually logged here?
                for (FileItem item : result) {
                    LOG.debug(item.toString());
                }
            }
        }
        return result;
    }

    /**
     * Dispatches a KVP request to the responsible {@link OWS}. Both GET and POST are handled by this method.
     * <p>
     * The responsible {@link OWS} is identified according to this strategy:
     * <nl>
     * <li>If a <code>SERVICE</code> attribute is present, it is used to determine the controller.</li>
     * <li>If no <code>SERVICE</code> attribute is present, the value of the <code>REQUEST</code> attribute is taken to
     * determine the controller.</li>
     * </nl>
     * </p>
     * 
     * @param normalizedKVPParams
     * @param requestWrapper
     * @param response
     * @param multiParts
     * @throws ServletException
     * @throws IOException
     */
    private void dispatchKVPRequest(Map<String, String> normalizedKVPParams, HttpServletRequest requestWrapper,
            HttpResponseBuffer response, List<FileItem> multiParts, long entryTime)
            throws ServletException, IOException {

        OWS ows = null;
        try {
            ows = determineOWSByPath(requestWrapper);
        } catch (OWSException e) {
            sendException(null, e, response, null);
            return;
        }

        CredentialsProvider credentialsProvider = securityConfiguration == null ? null
                : securityConfiguration.getCredentialsProvider();

        // extract (deegree specific) security information and bind to current thread
        try {
            // TODO handle multiple authentication methods
            Credentials cred = null;
            if (credentialsProvider != null) {
                cred = credentialsProvider.doKVP(normalizedKVPParams, requestWrapper, response);
                response.setCredentials(cred);
            }
            LOG.debug("credentials: " + cred);
            bindContextToThread(requestWrapper, cred);

            String service = normalizedKVPParams.get("SERVICE");
            String request = normalizedKVPParams.get("REQUEST");

            if (ows == null) {
                // first try service parameter, SERVICE-parameter is mandatory for each service and request (except WMS
                // 1.0.0)

                if (request != null && request.equalsIgnoreCase("getlogo")) {
                    response.setContentType("text/plain");
                    DeegreeAALogoUtils.print(response.getWriter());
                    return;
                }

                if (service != null) {
                    List<OWS> services = serviceConfiguration.getByServiceType(service);
                    if (services != null && !services.isEmpty()) {
                        if (services.size() > 1) {
                            String msg = "Cannot dispatch request for service '" + service
                                    + "' -- currently, multiple services of this type "
                                    + "are active. Please add the service id to the URL to "
                                    + "choose one of the services.";
                            throw new ServletException(msg);
                        }
                        ows = services.get(0);
                    }
                } else {
                    // dispatch according to REQUEST-parameter
                    if (request != null) {
                        List<OWS> services = serviceConfiguration.getByRequestName(request);
                        if (services != null && !services.isEmpty()) {
                            if (services.size() > 1) {
                                String msg = "Cannot dispatch KVP request of type '" + request
                                        + "' -- currently, multiple services for this request type "
                                        + "are active. Please add the service id to the URL to "
                                        + "choose one of the services.";
                                throw new ServletException(msg);
                            }
                            ows = services.get(0);
                        }
                    }
                }
            }

            if (service != null && serviceConfiguration.getByServiceType(service) == null) {
                OWSException ex = new OWSException(
                        "No service for service type '" + service + "' is configured / active.",
                        "InvalidParameterValue", "service");
                sendException(ows, ex, response, null);
                return;
            }

            if (request != null && !request.equalsIgnoreCase("GetCapabilities")
                    && serviceConfiguration.getByRequestName(request) == null) {
                OWSException ex = new OWSException(
                        "No service for request type '" + request + "' is configured / active.",
                        "InvalidParameterValue", "request");
                sendException(ows, ex, response, null);
                return;
            }

            if (ows == null) {
                OWSException ex = new OWSException("No service for service type '" + service
                        + "' and request type '" + request + "' is configured / active.", "MissingParameterValue",
                        "service");
                sendException(null, ex, response, null);
                return;
            }

            // Seems not all services test their incoming requests completely, so the check is done here.
            // Since for WMS the service parameter is not always mandatory, here's the exception.
            // Once all services properly check their requests (WFS and SOS have this problem), this workaround can be
            // removed.
            if (service == null && !(((OWSProvider) ows.getMetadata().getProvider()).getImplementationMetadata()
                    .getImplementedServiceName()[0].equalsIgnoreCase("WMS"))) {
                OWSException ex = new OWSException("The 'SERVICE' parameter is missing.", "MissingParameterValue",
                        "service");
                sendException(ows, ex, response, null);
                return;
            }

            if (request == null) {
                OWSException ex = new OWSException("The 'REQUEST' parameter is absent.", "MissingParameterValue",
                        "request");
                sendException(ows, ex, response, null);
                return;
            }

            LOG.debug("Dispatching request to OWS class: " + ows.getClass().getName());
            long dispatchTime = FrontControllerStats.requestDispatched();
            try {
                watchTimeout(ows, request);
                ows.doKVP(normalizedKVPParams, requestWrapper, response, multiParts);
            } finally {
                FrontControllerStats.requestFinished(dispatchTime);
                unwatchTimeout();
            }
        } catch (SecurityException e) {
            if (credentialsProvider != null) {
                LOG.debug("A security exception was thrown, let the credential provider handle the job.");
                credentialsProvider.handleException(response, e);
            } else {
                LOG.debug("A security exception was thrown ( " + e.getLocalizedMessage()
                        + " but no credentials provider was configured, sending generic ogc exception.");
                sendException(ows, new OWSException(e.getLocalizedMessage(), NO_APPLICABLE_CODE), response, null);
            }
        }
    }

    private static void validateResponse(HttpResponseBuffer responseWrapper) {
        responseWrapper.validate();
    }

    /**
     * Dispatches an XML request to the responsible {@link OWS}. Both GET and POST are handled by this method.
     * <p>
     * The responsible {@link OWS} is identified by the namespace of the root element.
     * 
     * @param xmlStream
     *            provides access to the XML request, cursor points to the START_ELEMENT event of the root element
     * @param requestWrapper
     * @param response
     * @param multiParts
     * @throws ServletException
     * @throws IOException
     */
    private void dispatchXMLRequest(XMLStreamReader xmlStream, HttpServletRequest requestWrapper,
            HttpResponseBuffer response, List<FileItem> multiParts) throws ServletException, IOException {

        OWS ows = null;
        try {
            ows = determineOWSByPath(requestWrapper);
        } catch (OWSException e) {
            sendException(null, e, response, null);
            return;
        }

        CredentialsProvider credentialsProvider = securityConfiguration == null ? null
                : securityConfiguration.getCredentialsProvider();

        try {
            // TODO handle multiple authentication methods
            Credentials cred = null;
            if (credentialsProvider != null) {
                cred = credentialsProvider.doXML(xmlStream, requestWrapper, response);
                response.setCredentials(cred);
            }
            LOG.debug("credentials: " + cred);
            bindContextToThread(requestWrapper, cred);

            // AbstractOGCServiceController subController = null;
            // extract (deegree specific) security information and bind to current thread
            // String user = xmlStream.getAttributeValue( XMLConstants.NULL_NS_URI, "user" );
            // String password = xmlStream.getAttributeValue( XMLConstants.NULL_NS_URI, "password" );
            // String sessionId = xmlStream.getAttributeValue( XMLConstants.NULL_NS_URI, "sessionId" );

            String ns = xmlStream.getNamespaceURI();
            if (ows == null) {
                List<OWS> services = serviceConfiguration.getByRequestNS(ns);
                if (services == null || services.isEmpty()) {
                    String msg = "Cannot dispatch XML request with namespace '" + ns
                            + "' -- currently, no service for this request namespace is configured / active.";
                    throw new ServletException(msg);
                }
                if (services.size() > 1) {
                    String msg = "Cannot dispatch XML request with namespace '" + ns
                            + "' -- currently, multiple services for this namespace "
                            + "are active. Please add the service id to the URL to "
                            + "choose one of the services.";
                    throw new ServletException(msg);
                }
                ows = services.get(0);
            }
            if (ows != null) {
                LOG.debug("Dispatching request to OWS: " + ows.getClass().getName());
                long dispatchTime = FrontControllerStats.requestDispatched();
                try {
                    watchTimeout(ows, xmlStream.getLocalName());
                    ows.doXML(xmlStream, requestWrapper, response, multiParts);
                } finally {
                    FrontControllerStats.requestFinished(dispatchTime);
                    unwatchTimeout();
                }
            }
        } catch (SecurityException e) {
            if (credentialsProvider != null) {
                LOG.debug("A security exception was thrown, let the credential provider handle the job.");
                credentialsProvider.handleException(response, e);
            } else {
                LOG.debug("A security exception was thrown ( " + e.getLocalizedMessage()
                        + " but no credentials provider was configured, sending generic ogc exception.");
                sendException(ows, new OWSException(e.getLocalizedMessage(), OWSException.NO_APPLICABLE_CODE),
                        response, null);
            }
        }
    }

    /**
     * Dispatches a SOAP request to the responsible {@link OWS}. Both GET and POST are handled by this method.
     * <p>
     * The responsible {@link OWS} is identified by the namespace of the first child of the SOAP body element.
     * 
     * @param xmlStream
     *            provides access to the SOAP request, cursor points to the START_ELEMENT event of the root element
     * @param requestWrapper
     * @param response
     * @param multiParts
     * @throws ServletException
     * @throws IOException
     */
    private void dispatchSOAPRequest(XMLStreamReader xmlStream, HttpServletRequest requestWrapper,
            HttpResponseBuffer response, List<FileItem> multiParts) throws ServletException, IOException {

        OWS ows = null;
        try {
            ows = determineOWSByPath(requestWrapper);
        } catch (OWSException e) {
            sendException(null, e, response, null);
            return;
        }

        // TODO integrate authentication handling (CredentialsProvider)
        LOG.debug("Handling SOAP request.");
        XMLAdapter requestDoc = new XMLAdapter(xmlStream);
        OMElement root = requestDoc.getRootElement();
        SOAPFactory factory = null;
        String ns = root.getNamespace().getNamespaceURI();
        if ("http://schemas.xmlsoap.org/soap/envelope/".equals(ns)) {
            factory = new SOAP11Factory();
        } else {
            factory = new SOAP12Factory();
        }

        StAXSOAPModelBuilder soap = new StAXSOAPModelBuilder(root.getXMLStreamReaderWithoutCaching(), factory,
                factory.getSoapVersionURI());

        SOAPEnvelope env = soap.getSOAPEnvelope();

        CredentialsProvider credentialsProvider = securityConfiguration == null ? null
                : securityConfiguration.getCredentialsProvider();

        try {
            // TODO handle multiple authentication methods
            Credentials creds = null;
            if (credentialsProvider != null) {
                creds = credentialsProvider.doSOAP(env, requestWrapper);
                response.setCredentials(creds);
            }
            LOG.debug("credentials: " + creds);
            bindContextToThread(requestWrapper, creds);

            // extract (deegree specific) security information and bind to current thread
            // String user = null;
            // String password = null;
            // String sessionId = null;
            // SOAPHeader header = envelope.getHeader();
            // if ( header != null ) {
            // OMElement userElement = header.getFirstChildWithName( new QName( "http://www.deegree.org/security",
            // "user" ) );
            // if ( userElement != null ) {
            // user = userElement.getText();
            // }
            // OMElement passwordElement = header.getFirstChildWithName( new QName( "http://www.deegree.org/security",
            // "password" ) );
            // if ( passwordElement != null ) {
            // password = passwordElement.getText();
            // }
            // OMElement sessionIdElement = header.getFirstChildWithName( new QName( "http://www.deegree.org/security",
            // "sessionId" ) );
            // if ( sessionIdElement != null ) {
            // sessionId = sessionIdElement.getText();
            // }
            // }

            if (ows == null) {
                String requestNs = env.getSOAPBodyFirstElementNS().getNamespaceURI();
                List<OWS> services = serviceConfiguration.getByRequestNS(requestNs);
                if (services == null || services.isEmpty()) {
                    String msg = "Cannot dispatch SOAP request with body namespace '" + requestNs
                            + "' -- currently, no service for this namespace '" + requestNs
                            + "' is configured / active.";
                    throw new ServletException(msg);
                }
                if (services.size() > 1) {
                    String msg = "Cannot dispatch SOAP request with body namespace '" + requestNs
                            + "' -- currently, multiple services for this namespace '" + requestNs
                            + "' are active. Please add the service id to the URL to "
                            + "choose one of the services.";
                    throw new ServletException(msg);
                }
                ows = services.get(0);
            }

            LOG.debug("Dispatching request to OWS class: " + ows.getClass().getName());
            long dispatchTime = FrontControllerStats.requestDispatched();
            try {
                watchTimeout(ows, env.getSOAPBodyFirstElementLocalName());
                ows.doSOAP(env, requestWrapper, response, multiParts, factory);
            } finally {
                FrontControllerStats.requestFinished(dispatchTime);
            }
        } catch (SecurityException e) {
            if (credentialsProvider != null) {
                LOG.debug("A security exception was thrown, let the credential provider handle the job.");
                credentialsProvider.handleException(response, e);
            } else {
                LOG.debug("A security exception was thrown ( " + e.getLocalizedMessage()
                        + " but no credentials provider was configured, sending generic ogc exception.");
                sendException(ows, new OWSException(e.getLocalizedMessage(), NO_APPLICABLE_CODE), response, null);
            }
        }
    }

    private static boolean isSOAPRequest(XMLStreamReader xmlStream) {
        String ns = xmlStream.getNamespaceURI();
        String localName = xmlStream.getLocalName();
        return ("http://schemas.xmlsoap.org/soap/envelope/".equals(ns)
                || "http://www.w3.org/2003/05/soap-envelope".equals(ns)) && "Envelope".equals(localName);
    }

    @Override
    public void init(ServletConfig config) throws ServletException {

        instance = this;

        try {
            super.init(config);
            ctxPath = config.getServletContext().getContextPath();
            LOG.info("--------------------------------------------------------------------------------");
            DeegreeAALogoUtils.logInfo(LOG);
            LOG.info("--------------------------------------------------------------------------------");
            LOG.info("deegree modules");
            LOG.info("--------------------------------------------------------------------------------");
            LOG.info("");
            try {
                modulesInfo = extractModulesInfo(config.getServletContext());
            } catch (Throwable t) {
                LOG.error("Unable to extract deegree module information: " + t.getMessage());
                modulesInfo = emptyList();
            }
            for (ModuleInfo moduleInfo : modulesInfo) {
                LOG.info("- " + moduleInfo.toString());
                if ("deegree-services-commons".equals(moduleInfo.getArtifactId())) {
                    version = moduleInfo.getVersion();
                }
            }
            if (version == null) {
                version = "unknown";
            }
            LOG.info("");
            LOG.info("--------------------------------------------------------------------------------");
            LOG.info("System info");
            LOG.info("--------------------------------------------------------------------------------");
            LOG.info("");
            LOG.info("- java version       " + System.getProperty("java.version") + " ("
                    + System.getProperty("java.vendor") + ")");
            LOG.info("- operating system   " + System.getProperty("os.name") + " ("
                    + System.getProperty("os.version") + ", " + System.getProperty("os.arch") + ")");
            LOG.info("- container          " + config.getServletContext().getServerInfo());
            LOG.info("- webapp path        " + ctxPath);
            LOG.info("- default encoding   " + DEFAULT_ENCODING);
            LOG.info("- system encoding    " + Charset.defaultCharset().displayName());
            LOG.info("- temp directory     " + defaultTMPDir);
            LOG.info("- XMLOutputFactory   " + XMLOutputFactory.newInstance().getClass().getCanonicalName());
            LOG.info("- XMLInputFactory    " + XMLInputFactory.newInstance().getClass().getCanonicalName());
            LOG.info("");

            initWorkspace();

        } catch (NoClassDefFoundError e) {
            LOG.error("Initialization failed!");
            LOG.error("You probably forgot to add a required .jar to the WEB-INF/lib directory.");
            LOG.error("The resource that could not be found was '{}'.", e.getMessage());
            LOG.debug("Stack trace:", e);

            throw new ServletException(e);
        } catch (Exception e) {
            LOG.error("Initialization failed!");
            LOG.error("An unexpected error was caught, stack trace:", e);

            throw new ServletException(e);
        } finally {
            CONTEXT.remove();
        }
    }

    private Collection<ModuleInfo> extractModulesInfo(ServletContext servletContext)
            throws IOException, URISyntaxException {

        if (servletContext.getServerInfo() != null && servletContext.getServerInfo().contains("WebLogic")) {
            LOG.debug("Running on weblogic. Not extracting module info from classpath, but from WEB-INF/lib.");
            return ModuleInfo.extractModulesInfo(forWebInfLib(servletContext));
        }
        return ModuleInfo.extractModulesInfo(forClassLoader());
    }

    private void initWorkspace() throws IOException, URISyntaxException, ResourceInitException {

        LOG.info("--------------------------------------------------------------------------------");
        LOG.info("Initializing workspace");
        LOG.info("--------------------------------------------------------------------------------");
        LOG.info("");
        LOG.info("- deegree workspace root  " + DeegreeWorkspace.getWorkspaceRoot());
        File wsRoot = new File(DeegreeWorkspace.getWorkspaceRoot());
        if (!wsRoot.isDirectory() && !wsRoot.mkdirs()) {
            LOG.warn("*** The workspace root is not a directory and could not be created. ***");
            LOG.warn("*** This will lead to problems when you'll try to download workspaces. ***");
        }
        if (wsRoot.isDirectory() && !wsRoot.canWrite()) {
            LOG.warn("*** The workspace root is not writable. ***");
            LOG.warn("*** This will lead to problems when you'll try to download workspaces. ***");
        }

        workspace = getActiveWorkspace();
        workspace.initAll();
        serviceConfiguration = workspace.getNewWorkspace().getResourceManager(OwsManager.class);
        OwsGlobalConfigLoader loader = workspace.getNewWorkspace().getInitializable(OwsGlobalConfigLoader.class);
        mainConfig = loader.getMainConfig();
        if (mainConfig != null) {
            initHardcodedUrls(mainConfig);
        }
        if (mainConfig != null && !mainConfig.getRequestTimeoutMilliseconds().isEmpty()) {
            LOG.info("Initializing request watchdog.");
            initRequestWatchdog(mainConfig.getRequestTimeoutMilliseconds());
        } else {
            LOG.info("Not initializing request watchdog. No request time-outs configured.");
        }
        LOG.info("");
    }

    private void initHardcodedUrls(DeegreeServiceControllerType mainConfig) {
        if (mainConfig.getReportedUrls() != null) {
            hardcodedServicesUrl = mainConfig.getReportedUrls().getServices();
            hardcodedResourcesUrl = mainConfig.getReportedUrls().getResources();
            hardcodedServicesUrl = removeTrailingSlashOrQuestionMark(hardcodedServicesUrl);
            hardcodedResourcesUrl = removeTrailingSlashOrQuestionMark(hardcodedResourcesUrl);
        } else if (mainConfig.getDCP() != null) {
            if (mainConfig.getDCP().getHTTPGet() != null) {
                hardcodedServicesUrl = mainConfig.getDCP().getHTTPGet();
            } else if (mainConfig.getDCP().getHTTPPost() != null) {
                hardcodedServicesUrl = mainConfig.getDCP().getHTTPPost();
            } else if (mainConfig.getDCP().getSOAP() != null) {
                hardcodedServicesUrl = mainConfig.getDCP().getSOAP();
            }
            if (hardcodedServicesUrl != null) {
                hardcodedServicesUrl = removeTrailingSlashOrQuestionMark(hardcodedServicesUrl);
                hardcodedResourcesUrl = hardcodedServicesUrl + "/../resources";
            }
        }
    }

    private void initRequestWatchdog(final List<RequestTimeoutMilliseconds> timeoutConfigs) {
        requestWatchdog = new RequestWatchdog(timeoutConfigs);
        requestWatchdog.init();
    }

    private void destroyWorkspace() {
        LOG.info("--------------------------------------------------------------------------------");
        LOG.info("Destroying workspace");
        LOG.info("--------------------------------------------------------------------------------");
        workspace.destroyAll();
        if (requestWatchdog != null) {
            requestWatchdog.destroy();
        }
        LOG.info("");
    }

    /**
     * Re-initializes the whole workspace, effectively reloading the whole configuration.
     * 
     * @throws URISyntaxException
     * @throws IOException
     * @throws ServletException
     */
    public void reload() throws IOException, URISyntaxException, ServletException {
        destroyWorkspace();
        try {
            initWorkspace();
        } catch (ResourceInitException e) {
            throw new ServletException(e.getLocalizedMessage(), e.getCause());
        }
    }

    /**
     * Determines the active {@link DeegreeWorkspace} for this webapp.
     * <p>
     * The following steps are performed to find it (in order). Strategy stops on first match:
     * <nl>
     * <li>Analyse file $DEEGREE_WORKSPACE_ROOT/webapps.properties (and lookup entry for this webapp context name)</li>
     * <li>Match workspace by webapp context name</li>
     * <li>Read WEB-INF/workspace_name (in webapp directory)</li>
     * </nl>
     * If this doesn't lead to an existing workspace directory, the following additional steps are performed:
     * <nl>
     * <li>Check for WEB-INF/workspace</li>
     * <li>Check for WEB-INF/conf</li>
     * </nl>
     * </p>
     * 
     * @return workspace (uninitialized), never <code>null</code>
     * @throws IOException
     * @throws URISyntaxException
     */
    private DeegreeWorkspace getActiveWorkspace() throws IOException, URISyntaxException {

        String wsName = null;
        File wsRoot = new File(DeegreeWorkspace.getWorkspaceRoot());
        if (wsRoot.isDirectory()) {
            File activeWsConfigFile = new File(wsRoot, ACTIVE_WS_CONFIG_FILE);

            Properties props = loadWebappToWsMappings(activeWsConfigFile);
            wsName = props.getProperty(ctxPath);

            if (wsName != null) {
                LOG.info("Active workspace determined by webapp-to-workspace mapping file: " + activeWsConfigFile);
            } else {
                LOG.debug("No webapp-to-workspace mappings file. Trying alternative methods.");
                if (!ctxPath.isEmpty()) {
                    String webappName = ctxPath;
                    if (webappName.startsWith("/")) {
                        webappName = webappName.substring(1);
                    }
                    if (!webappName.isEmpty()) {
                        File file = new File(wsRoot, webappName);
                        LOG.debug("Matching by webapp name ('" + file + "'). Checking for workspace directory '"
                                + file + "'");
                        if (file.exists()) {
                            wsName = webappName;
                            LOG.info("Active workspace determined by matching webapp name (" + webappName
                                    + ") with available workspaces.");
                        }
                    }
                }
            }
        } else {
            String msg = "Workspace root directory ('" + wsRoot
                    + "') does not exist or does not denote a directory.";
            LOG.info(msg);
        }

        File fallbackDir = null;
        if (wsName == null) {
            wsName = getActiveWorkspaceName();
            if (wsName != null && new File(wsRoot, wsName).exists()) {
                LOG.info("Active workspace determined by matching workspace name from WEB-INF/workspace_name ("
                        + wsName + ") with available workspaces.");
            } else {
                LOG.info("Active workspace in webapp.");
                fallbackDir = new File(resolveFileLocation("WEB-INF/workspace", getServletContext()).toURI());
                if (!fallbackDir.exists()) {
                    LOG.debug("Trying legacy-style workspace directory (WEB-INF/conf)");
                    fallbackDir = new File(resolveFileLocation("WEB-INF/conf", getServletContext()).toURI());
                } else {
                    LOG.debug("Using new-style workspace directory (WEB-INF/workspace)");
                }
            }
        }

        DeegreeWorkspace ws = DeegreeWorkspace.getInstance(wsName, fallbackDir);
        LOG.info("Using workspace '{}' at '{}'", ws.getName(), ws.getLocation());
        return ws;
    }

    private String getActiveWorkspaceName() throws URISyntaxException, IOException {
        String wsName = null;
        File wsNameFile = new File(resolveFileLocation("WEB-INF/workspace_name", getServletContext()).toURI());
        if (wsNameFile.exists()) {
            BufferedReader reader = null;
            try {
                reader = new BufferedReader(new FileReader(wsNameFile));
                wsName = reader.readLine();
                if (wsName != null) {
                    wsName = wsName.trim();
                }
            } finally {
                IOUtils.closeQuietly(reader);
            }
            LOG.info("Using workspace name {} (defined in WEB-INF/workspace_name)", wsName, wsNameFile);
        } else {
            LOG.info("Using default workspace (WEB-INF/workspace_name does not exist)");
        }
        return wsName;
    }

    /**
     * Associates the specified workspace with the current webapp.
     * <p>
     * Setting is saved in properties file <code>webapps.properties</code> in the deegree workspace root folder.
     * </p>
     * 
     * TODO file locking to prevent race conditions when this method gets called simultaneously in different webapps
     * 
     * @param wsName
     *            name of the workspace, must not be <code>null</code>
     */
    public void setActiveWorkspaceName(String wsName) throws IOException {

        File wsRoot = new File(DeegreeWorkspace.getWorkspaceRoot());
        if (!wsRoot.exists() || !wsRoot.isDirectory()) {
            String msg = "Workspace root directory ('" + wsRoot
                    + "') does not exist or does not denote a directory.";
            LOG.error(msg);
            throw new IOException(msg);
        }

        File activeWsConfigFile = new File(wsRoot, ACTIVE_WS_CONFIG_FILE);
        Properties props = loadWebappToWsMappings(activeWsConfigFile);
        String oldWsName = props.getProperty(ctxPath);
        if (oldWsName == null || !oldWsName.equals(wsName)) {
            props.put(ctxPath, wsName);
            writeWebappToWsMappings(props, activeWsConfigFile);
        }
    }

    private static void writeWebappToWsMappings(Properties props, File file) throws IOException {

        if (file.exists() && !file.canWrite()) {
            String msg = "Webapp-to-workspace mappings file ('" + file + "') is not a writable file.";
            LOG.error(msg);
        }

        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(file);
            props.store(fos, null);
        } catch (IOException e) {
            String msg = "Error writing webapp-to-workspace mappings to file '" + file + "': " + e.getMessage();
            LOG.error(msg);
            throw new IOException(msg, e);
        } finally {
            IOUtils.closeQuietly(fos);
        }
        LOG.info("Successfully saved webapp-to-workspace mapping in file '" + file + "'.");
    }

    private static Properties loadWebappToWsMappings(File file) throws IOException {

        Properties props = new Properties();
        if (file.exists()) {
            LOG.info("Loading webapp-to-workspace mappings from file '" + file + "'");
            FileInputStream fis = null;
            try {
                fis = new FileInputStream(file);
                props = new Properties();
                props.load(fis);
            } catch (IOException e) {
                String msg = "Error reading webapp-to-workspace mappings from file '" + file + "': "
                        + e.getMessage();
                LOG.error(msg);
                throw new IOException(msg, e);
            } finally {
                IOUtils.closeQuietly(fis);
            }
        }
        return props;
    }

    @Override
    public void destroy() {
        super.destroy();
        destroyWorkspace();
        if (mainConfig.isPreventClassloaderLeaks() == null || mainConfig.isPreventClassloaderLeaks()) {
            plugClassLoaderLeaks();
        }
    }

    /**
     * Apply workarounds for classloader leaks, see eg. <a
     * href="http://java.jiderhamn.se/2012/02/26/classloader-leaks-v-common-mistakes-and-known-offenders/">this blog
     * post</a>.
     */
    private void plugClassLoaderLeaks() {
        // if the feature store manager does this, it breaks
        try {
            ThreadedFeatureInputStream.shutdown();
        } catch (Throwable e) {
            // just eat it
        }
        Executor.getInstance().shutdown();

        LogFactory.releaseAll();
        LogManager.shutdown();

        // image io
        Iterator<Class<?>> i = IIORegistry.getDefaultInstance().getCategories();
        while (i.hasNext()) {
            Class<?> c = i.next();
            Iterator<?> k = IIORegistry.getDefaultInstance().getServiceProviders(c, false);
            while (k.hasNext()) {
                Object o = k.next();
                if (o.getClass().getClassLoader() == getClass().getClassLoader()) {
                    IIORegistry.getDefaultInstance().deregisterServiceProvider(o);
                    LOG.debug("Deregistering " + o);
                    k = IIORegistry.getDefaultInstance().getServiceProviders(c, false);
                }
            }
        }

        // JSF
        Introspector.flushCaches();

        // Batik
        try {
            Class<?> cls = Class.forName("org.apache.batik.util.CleanerThread");
            if (cls != null) {
                Field field = cls.getDeclaredField("thread");
                field.setAccessible(true);
                Object obj = field.get(null);
                if (obj != null) {
                    // interrupt is ignored by the thread
                    ((Thread) obj).stop();
                }
            }
        } catch (Exception ex) {
            LOG.warn("Problem when trying to fix batik class loader leak.");
        }
    }

    /**
     * 'Heuristical' method to retrieve the {@link URL} for a file referenced from an init-param of a webapp config file
     * which may be:
     * <ul>
     * <li>a (absolute) <code>URL</code></li>
     * <li>a file location</li>
     * <li>a (relative) URL which in turn is resolved using <code>ServletContext.getRealPath</code></li>
     * </ul>
     * 
     * @param location
     * @param context
     * @return the full (and whitespace-escaped) URL
     * @throws MalformedURLException
     */
    public static URL resolveFileLocation(String location, ServletContext context) throws MalformedURLException {
        URL serviceConfigurationURL = null;

        LOG.debug("Resolving configuration file location: '" + location + "'...");
        try {
            // construction of URI performs whitespace escaping
            serviceConfigurationURL = new URI(location).toURL();
        } catch (Exception e) {
            LOG.debug("No valid (absolute) URL. Trying context.getRealPath() now.");
            String realPath = context.getRealPath(location);
            if (realPath == null) {
                LOG.debug("No 'real path' available. Trying to parse as a file location now.");
                serviceConfigurationURL = new File(location).toURI().toURL();
            } else {
                try {
                    // realPath may either be a URL or a File
                    serviceConfigurationURL = new URI(realPath).toURL();
                } catch (Exception e2) {
                    LOG.debug("'Real path' cannot be parsed as URL. Trying to parse as a file location now.");
                    // construction of URI performs whitespace escaping
                    serviceConfigurationURL = new File(realPath).toURI().toURL();
                    LOG.debug("configuration URL: " + serviceConfigurationURL);
                }
            }
        }
        return serviceConfigurationURL;
    }

    private void bindContextToThread(HttpServletRequest request, Credentials credentials) {
        RequestContext context = new RequestContext(request, credentials, hardcodedServicesUrl,
                hardcodedResourcesUrl);
        CONTEXT.set(context);
        LOG.debug("Initialized RequestContext for Thread " + Thread.currentThread() + "=" + context);
    }

    private String removeTrailingSlashOrQuestionMark(String url) {
        if (url.endsWith("?") || url.endsWith("/")) {
            return url.substring(0, url.length() - 1);
        }
        return url;
    }

    /**
     * Sends an exception report to the client.
     * <p>
     * NOTE: Usually, exception reports are generated by the specific service controller. This method is only used when
     * the request is so broken that it cannot be dispatched.
     * </p>
     * 
     * @param ows
     *            if not null, it will be used to determine the responsible controller for exception serializing
     * @param e
     *            exception to be serialized
     * @param res
     *            response object
     * @throws ServletException
     */
    private void sendException(OWS ows, OWSException e, HttpResponseBuffer res, Version requestVersion)
            throws ServletException {
        String userAgent = null;
        if (OGCFrontController.getContext() != null) {
            userAgent = OGCFrontController.getContext().getUserAgent();
        }

        if (ows == null) {
            Collection<List<OWS>> values = serviceConfiguration.getAll().values();
            if (values.size() > 0 && !values.iterator().next().isEmpty()) {
                ows = values.iterator().next().get(0);
            }
        }
        if (ows != null) {
            // use exception serializer / mime type from first registered controller (fair chance that this will be
            // correct)
            XMLExceptionSerializer serializer = ows.getExceptionSerializer(requestVersion);
            ((AbstractOWS) ows).sendException(null, serializer, e, res);
        } else {
            // use the most common serializer (OWS 1.1.0)
            XMLExceptionSerializer serializer = null;
            if (requestVersion == null) {
                serializer = new OWS110ExceptionReportSerializer(parseVersion("1.1.0"));
            } else {
                serializer = new OWS110ExceptionReportSerializer(requestVersion);
            }

            if (!res.isCommitted()) {
                try {
                    res.reset();
                } catch (IllegalStateException e2) {
                    // rb: the illegal state exception occurred.
                    throw new ServletException(e2);
                }
                try {
                    serializer.serializeException(res, e);
                } catch (Exception e2) {
                    LOG.error("An error occurred while trying to send an exception: " + e2.getLocalizedMessage(),
                            e);
                    throw new ServletException(e2);
                }
                res.setExceptionSent();
            }
        }

        if (userAgent != null && userAgent.toLowerCase().contains("mozilla")) {
            res.setContentType("application/xml");
        }
    }

    private void watchTimeout(final OWS ows, final String requestName) {
        if (requestWatchdog != null) {
            final String serviceId = ows.getMetadata().getIdentifier().getId();
            requestWatchdog.watchCurrentThread(serviceId, requestName);
        }
    }

    private void unwatchTimeout() {
        if (requestWatchdog != null) {
            requestWatchdog.unwatchCurrentThread();
        }
    }

}