Package org.geowebcache

Source Code of org.geowebcache.GeoWebCacheDispatcher

/**
* This program 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 3 of the License, or
* (at your option) any later version.
*
*  This program 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 General Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public License
*  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
* @author Arne Kepp, The Open Planning Project, Copyright 2008
*/

package org.geowebcache;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.geowebcache.config.Configuration;
import org.geowebcache.conveyor.Conveyor;
import org.geowebcache.conveyor.Conveyor.CacheResult;
import org.geowebcache.conveyor.ConveyorTile;
import org.geowebcache.demo.Demo;
import org.geowebcache.filter.request.RequestFilterException;
import org.geowebcache.grid.GridSetBroker;
import org.geowebcache.grid.OutsideCoverageException;
import org.geowebcache.io.ByteArrayResource;
import org.geowebcache.io.Resource;
import org.geowebcache.layer.BadTileException;
import org.geowebcache.layer.TileLayer;
import org.geowebcache.layer.TileLayerDispatcher;
import org.geowebcache.mime.ImageMime;
import org.geowebcache.service.HttpErrorCodeException;
import org.geowebcache.service.OWSException;
import org.geowebcache.service.Service;
import org.geowebcache.stats.RuntimeStats;
import org.geowebcache.storage.DefaultStorageFinder;
import org.geowebcache.storage.StorageBroker;
import org.geowebcache.util.ServletUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;

/**
* This is the main router for requests of all types.
*/
public class GeoWebCacheDispatcher extends AbstractController {
    private static Log log = LogFactory.getLog(org.geowebcache.GeoWebCacheDispatcher.class);

    public static final String TYPE_SERVICE = "service";

    public static final String TYPE_DEMO = "demo";

    public static final String TYPE_HOME = "home";

    private TileLayerDispatcher tileLayerDispatcher = null;

    private DefaultStorageFinder defaultStorageFinder = null;

    private GridSetBroker gridSetBroker = null;

    private StorageBroker storageBroker;

    private RuntimeStats runtimeStats;

    private Map<String, Service> services = null;

    private Resource blankTile = null;

    private String servletPrefix = null;

    /**
     * Should be invoked through Spring
     *
     * @param tileLayerDispatcher
     * @param gridSetBroker
     */
    public GeoWebCacheDispatcher(TileLayerDispatcher tileLayerDispatcher,
            GridSetBroker gridSetBroker, StorageBroker storageBroker,
            Configuration mainConfiguration, RuntimeStats runtimeStats) {
        super();
        this.tileLayerDispatcher = tileLayerDispatcher;
        this.gridSetBroker = gridSetBroker;
        this.runtimeStats = runtimeStats;
        this.storageBroker = storageBroker;

        if (mainConfiguration.isRuntimeStatsEnabled()) {
            this.runtimeStats.start();
        } else {
            runtimeStats = null;
        }
    }

    public void setStorageBroker() {
        // This is just to force initialization
        log.debug("GeoWebCacheDispatcher received StorageBroker : " + storageBroker.toString());
    }

    public void setDefaultStorageFinder(DefaultStorageFinder defaultStorageFinder) {
        this.defaultStorageFinder = defaultStorageFinder;
    }

    /**
     * GeoServer and other solutions that embedded this dispatcher will prepend a path, this is used
     * to remove it.
     *
     * @param servletPrefix
     */
    public void setServletPrefix(String servletPrefix) {
        if (!servletPrefix.startsWith("/")) {
            this.servletPrefix = "/" + servletPrefix;
        } else {
            this.servletPrefix = servletPrefix;
        }

        log.info("Invoked setServletPrefix(" + servletPrefix + ")");
    }

    /**
     * Services convert HTTP requests into the internal grid representation and specify what layer
     * the response should come from.
     *
     * The application context is scanned for objects extending Service, thereby making it easy to
     * add new services.
     *
     * @return
     */
    private Map<String, Service> loadServices() {

        List<Service> plugins = GeoWebCacheExtensions.extensions(Service.class);
        Map<String, Service> services = new HashMap<String, Service>();
        // Give all service objects direct access to the tileLayerDispatcher
        for (Service aService : plugins) {
            services.put(aService.getPathName(), aService);
        }
        return services;
    }

    private void loadBlankTile() {
        String blankTilePath = defaultStorageFinder
                .findEnvVar(DefaultStorageFinder.GWC_BLANK_TILE_PATH);

        if (blankTilePath != null) {
            File fh = new File(blankTilePath);
            if (fh.exists() && fh.canRead() && fh.isFile()) {
                long fileSize = fh.length();
                blankTile = new ByteArrayResource(new byte[(int) fileSize]);
                try {
                    loadBlankTile(blankTile, fh.toURI().toURL());
                } catch (IOException e) {
                    e.printStackTrace();
                }

                if (fileSize == blankTile.getSize()) {
                    log.info("Loaded blank tile from " + blankTilePath);
                } else {
                    log.error("Failed to load blank tile from " + blankTilePath);
                }

                return;
            } else {
                log.error("" + blankTilePath + " does not exist or is not readable.");
            }
        }

        // Use the built-in one:
        try {
            URL url = GeoWebCacheDispatcher.class.getResource("blank.png");
            blankTile = new ByteArrayResource();
            loadBlankTile(blankTile, url);
            int ret = (int) blankTile.getSize();
            log.info("Read " + ret + " from blank PNG file (expected 425).");
        } catch (IOException ioe) {
            log.error(ioe.getMessage());
        }
    }

    private void loadBlankTile(Resource blankTile, URL source) throws IOException {
        InputStream inputStream = source.openStream();
        ReadableByteChannel ch = Channels.newChannel(inputStream);
        try {
            blankTile.transferFrom(ch);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            ch.close();
        }
    }

    /**
     * Spring function for MVC, this is the entry point for the application.
     *
     * If a tile is requested the request will be handed off to handleServiceRequest.
     *
     */
    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request,
            HttpServletResponse response) throws Exception {

        // Break the request into components, {type, service name}
        String[] requestComps = null;
        try {
            String normalizedURI = request.getRequestURI().replaceFirst(request.getContextPath(),
                    "");

            if (servletPrefix != null) {
                normalizedURI = normalizedURI.replaceFirst(servletPrefix, ""); // getRequestURI().replaceFirst(request.getContextPath()+,
                                                                               // "");
            }
            requestComps = parseRequest(normalizedURI);
            // requestComps = parseRequest(request.getRequestURI());
        } catch (GeoWebCacheException gwce) {
            writeError(response, 400, gwce.getMessage());
            return null;
        }

        try {
            if (requestComps == null || requestComps[0].equalsIgnoreCase(TYPE_HOME)) {
                handleFrontPage(request, response);
            } else if (requestComps[0].equalsIgnoreCase(TYPE_SERVICE)) {
                handleServiceRequest(requestComps[1], request, response);
            } else if (requestComps[0].equalsIgnoreCase(TYPE_DEMO)
                    || requestComps[0].equalsIgnoreCase(TYPE_DEMO + "s")) {
                handleDemoRequest(requestComps[1], request, response);
            } else {
                writeError(response, 404, "Unknown path: " + requestComps[0]);
            }
        } catch (HttpErrorCodeException e) {
            writeFixedResponse(response, e.getErrorCode(), "text/plain", new ByteArrayResource(e
                    .getMessage().getBytes()), CacheResult.OTHER);
        } catch (RequestFilterException e) {

            RequestFilterException reqE = (RequestFilterException) e;
            reqE.setHttpInfoHeader(response);

            writeFixedResponse(response, reqE.getResponseCode(), reqE.getContentType(),
                    reqE.getResponse(), CacheResult.OTHER);
        } catch (OWSException e) {
            OWSException owsE = (OWSException) e;
            writeFixedResponse(response, owsE.getResponseCode(), owsE.getContentType(),
                    owsE.getResponse(), CacheResult.OTHER);
        } catch (Exception e) {
            if (!(e instanceof BadTileException) || log.isDebugEnabled()) {
                log.error(e.getMessage() + " " + request.getRequestURL().toString());
            }

            writeError(response, 400, e.getMessage());

            if (!(e instanceof GeoWebCacheException) || log.isDebugEnabled()) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * Destroy function, has to be referenced in bean declaration: <bean ...
     * destroy="destroy">...</bean>
     */
    public void destroy() {
        log.info("GeoWebCacheDispatcher.destroy() was invoked, shutting down.");
    }

    /**
     * Essentially this slices away the prefix, leaving type and request
     *
     * @param servletPath
     * @return {type, service}ervletPrefix
     */
    private String[] parseRequest(String servletPath) throws GeoWebCacheException {
        String[] retStrs = new String[2];
        String[] splitStr = servletPath.split("/");

        if (splitStr == null || splitStr.length < 2) {
            return null;
        }

        retStrs[0] = splitStr[1];
        if (splitStr.length > 2) {
            retStrs[1] = splitStr[2];
        }
        return retStrs;
    }

    /**
     * This is the main method for handling service requests. See comments in the code.
     *
     * @param request
     * @param response
     * @throws Exception
     */
    private void handleServiceRequest(String serviceStr, HttpServletRequest request,
            HttpServletResponse response) throws Exception {

        Conveyor conv = null;

        // 1) Figure out what Service should handle this request
        Service service = findService(serviceStr);

        // 2) Find out what layer will be used and how
        conv = service.getConveyor(request, response);
        final String layerName = conv.getLayerId();
        if (layerName != null && !tileLayerDispatcher.getTileLayer(layerName).isEnabled()) {
            throw new GeoWebCacheException("Layer '" + layerName + "' is disabled");
        }

        // Check where this should be dispatched
        if (conv.reqHandler == Conveyor.RequestHandler.SERVICE) {
            // A3 The service object takes it from here
            service.handleRequest(conv);

        } else {
            ConveyorTile convTile = (ConveyorTile) conv;

            // B3) Get the configuration that has to respond to this request
            TileLayer layer = tileLayerDispatcher.getTileLayer(layerName);

            // Save it for later
            convTile.setTileLayer(layer);

            // Apply the filters
            layer.applyRequestFilters(convTile);

            // Keep the URI
            // tile.requestURI = request.getRequestURI();

            try {
                // A5) Ask the layer to provide the content for the tile
                convTile = layer.getTile(convTile);

                // A6) Write response
                writeData(convTile);

                // Alternatively:
            } catch (OutsideCoverageException e) {
                writeEmpty(convTile, e.getMessage());
            }
        }
    }

    private void handleDemoRequest(String action, HttpServletRequest request,
            HttpServletResponse response) throws GeoWebCacheException {
        Demo.makeMap(tileLayerDispatcher, gridSetBroker, action, request, response);
    }

    /**
     * Helper function for looking up the service that should handle the request.
     *
     * @param request
     *            full HttpServletRequest
     * @return
     */
    private Service findService(String serviceStr) throws GeoWebCacheException {
        if (this.services == null) {
            this.services = loadServices();
            loadBlankTile();
        }

        // E.g. /wms/test -> /wms
        Service service = (Service) services.get(serviceStr);
        if (service == null) {
            if (serviceStr == null || serviceStr.length() == 0) {
                serviceStr = ", try service/&lt;name of service&gt;";
            } else {
                serviceStr = " \"" + serviceStr + "\"";
            }
            throw new GeoWebCacheException("Unable to find handler for service" + serviceStr);
        }
        return service;
    }

    /**
     * Create a minimalistic frontpage
     *
     * @param request
     * @param response
     */
    private void handleFrontPage(HttpServletRequest request, HttpServletResponse response) {

        String baseUrl = null;

        if (request.getRequestURL().toString().endsWith("/")
                || request.getRequestURL().toString().endsWith("home")) {
            baseUrl = "";
        } else {
            String[] strs = request.getRequestURL().toString().split("/");
            baseUrl = strs[strs.length - 1] + "/";
        }

        StringBuilder str = new StringBuilder();

        Package versionInfo = Package.getPackage("org.geowebcache");
        String version = versionInfo.getSpecificationVersion();
        String build = versionInfo.getImplementationVersion();
        if (version == null) {
            version = "{NO VERSION INFO IN MANIFEST}";
        }
        if (build == null) {
            build = "{NO BUILD INFO IN MANIFEST}";
        }

        str.append("<html>\n" + ServletUtils.gwcHtmlHeader("GWC Home") + "<body>\n"
                + ServletUtils.gwcHtmlLogoLink(baseUrl));
        str.append("<h3>Welcome to GeoWebCache version " + version + ", built " + build + "</h3>\n");
        str.append("<p><a href=\"http://geowebcache.org\">GeoWebCache</a> is an advanced tile cache for WMS servers.");
        str.append("It supports a large variety of protocols and formats, including WMS-C, WMTS, KML, Google Maps and Virtual Earth.</p>");
        str.append("<h3>Automatically Generated Demos:</h3>\n");
        str.append("<ul><li><a href=\"" + baseUrl
                + "demo\">A list of all the layers and automatic demos</a></li></ul>\n");
        str.append("<h3>GetCapabilities:</h3>\n");
        str.append("<ul><li><a href=\""
                + baseUrl
                + "service/wmts?REQUEST=getcapabilities\">WMTS 1.0.0 GetCapabilities document</a></li>");
        str.append("<li><a href=\""
                + baseUrl
                + "service/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=getcapabilities&TILED=true\">WMS 1.1.1 GetCapabilities document</a></li>");
        str.append("<li><a href=\"" + baseUrl + "service/tms/1.0.0\">TMS 1.0.0 document</a></li>");
        str.append("<li>Note that the latter will only work with clients that are ");
        str.append("<a href=\"http://wiki.osgeo.org/wiki/WMS_Tiling_Client_Recommendation\">WMS-C capable</a>.</li>\n");
        str.append("<li>Omitting tiled=true from the URL will omit the TileSet elements.</li></ul>\n");
        if (runtimeStats != null) {
            str.append("<h3>Runtime Statistics</h3>\n");
            str.append(runtimeStats.getHTMLStats());
        }
        str.append("</body></html>\n");

        writePage(response, 200, str.toString());
    }

    /**
     * Wrapper method for writing an error back to the client, and logging it at the same time.
     *
     * @param response
     *            where to write to
     * @param httpCode
     *            the HTTP code to provide
     * @param errorMsg
     *            the actual error message, human readable
     */
    private void writeError(HttpServletResponse response, int httpCode, String errorMsg) {
        log.debug(errorMsg);

        errorMsg = "<html>\n" + ServletUtils.gwcHtmlHeader("GWC Error") + "<body>\n"
                + ServletUtils.gwcHtmlLogoLink("../") + "<h4>" + httpCode + ": "
                + ServletUtils.disableHTMLTags(errorMsg) + "</h4>" + "</body></html>\n";
        writePage(response, httpCode, errorMsg);
    }

    private void writePage(HttpServletResponse response, int httpCode, String message) {
        Resource res = new ByteArrayResource(message.getBytes());
        writeFixedResponse(response, httpCode, "text/html", res, CacheResult.OTHER);
    }

    /**
     * Happy ending, sets the headers and writes the response back to the client.
     */
    private void writeData(ConveyorTile tile) throws IOException {
        if (tile.getLayer().useETags()) {
            String ifNoneMatch = tile.servletReq.getHeader("If-None-Match");
            String hexTag = Long.toHexString(tile.getTSCreated());

            if (ifNoneMatch != null) {
                if (ifNoneMatch.equals(hexTag)) {
                    tile.servletResp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                    return;
                }
            }

            // If we get here, we want ETags but the client did not have the tile.
            tile.servletResp.setHeader("ETag", hexTag);
        }

        writeFixedResponse(tile.servletResp, 200, tile.getMimeType().getMimeType(), tile.getBlob(),
                tile.getCacheResult());
    }

    /**
     * Writes a transparent, 8 bit PNG to avoid having clients like OpenLayers showing lots of pink
     * tiles
     */
    private void writeEmpty(ConveyorTile tile, String message) {
        tile.servletResp.setHeader("geowebcache-message", message);
        TileLayer layer = tile.getLayer();
        if (layer != null) {
            layer.setExpirationHeader(tile.servletResp, (int) tile.getTileIndex()[2]);

            if (layer.useETags()) {
                String ifNoneMatch = tile.servletReq.getHeader("If-None-Match");
                if (ifNoneMatch != null && ifNoneMatch.equals("gwc-blank-tile")) {
                    tile.servletResp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                    return;
                } else {
                    tile.servletResp.setHeader("ETag", "gwc-blank-tile");
                }
            }
        }

        writeFixedResponse(tile.servletResp, 200, ImageMime.png.getMimeType(), this.blankTile,
                CacheResult.OTHER);
    }

    private void writeFixedResponse(HttpServletResponse response, int httpCode, String contentType,
            Resource resource, CacheResult cacheRes) {
        response.setStatus(httpCode);
        response.setContentType(contentType);

        if (resource != null) {
            int size = (int) resource.getSize();
            if (size > -1) {
                response.setContentLength(size);
            }

            try {
                OutputStream os = response.getOutputStream();
                resource.transferTo(Channels.newChannel(os));

                runtimeStats.log(size, cacheRes);

            } catch (IOException ioe) {
                log.debug("Caught IOException: " + ioe.getMessage() + "\n\n" + ioe.toString());
            }
        }
    }
}
TOP

Related Classes of org.geowebcache.GeoWebCacheDispatcher

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.