Package org.geowebcache.layer

Source Code of org.geowebcache.layer.TileLayer

/**
* 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.layer;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.geowebcache.GeoWebCacheException;
import org.geowebcache.conveyor.ConveyorTile;
import org.geowebcache.filter.parameters.ParameterFilter;
import org.geowebcache.filter.request.RequestFilter;
import org.geowebcache.filter.request.RequestFilterException;
import org.geowebcache.grid.BoundingBox;
import org.geowebcache.grid.GridMismatchException;
import org.geowebcache.grid.GridSetBroker;
import org.geowebcache.grid.GridSubset;
import org.geowebcache.grid.OutsideCoverageException;
import org.geowebcache.grid.SRS;
import org.geowebcache.io.ByteArrayResource;
import org.geowebcache.io.Resource;
import org.geowebcache.layer.meta.LayerMetaInformation;
import org.geowebcache.layer.updatesource.UpdateSourceDefinition;
import org.geowebcache.mime.FormatModifier;
import org.geowebcache.mime.MimeType;
import org.geowebcache.storage.StorageException;
import org.geowebcache.storage.TileObject;
import org.geowebcache.util.GWCVars;
import org.geowebcache.util.ServletUtils;

/**
* "Pure virtual" base class for Layers.
* <p>
* Represents at the same time the configuration of a tiled layer and a way to access each stored
* tile
* </p>
*/
public abstract class TileLayer {

    private static Log log = LogFactory.getLog(org.geowebcache.layer.TileLayer.class);

    protected static final ThreadLocal<ByteArrayResource> WMS_BUFFER = new ThreadLocal<ByteArrayResource>();

    protected static final ThreadLocal<ByteArrayResource> WMS_BUFFER2 = new ThreadLocal<ByteArrayResource>();

    // cached default parameter filter values
    protected transient Map<String, String> defaultParameterFilterValues;

    /**
     * Registers a layer listener to be notified of layer events
     *
     * @see #getTile(ConveyorTile)
     * @see #seedTile(ConveyorTile, boolean)
     */
    public abstract void addLayerListener(TileLayerListener listener);

    /**
     * Removes a layer listener from this layer's set of listeners
     *
     * @param listener
     * @return
     */
    public abstract boolean removeLayerListener(TileLayerListener listener);

    /**
     * Then name of the layer
     *
     * @return
     */
    public abstract String getName();

    /**
     * @return {@code true} if the layer is enabled, {@code false} otherwise
     */
    public abstract boolean isEnabled();

    /**
     * @param enabled
     *            whether to enabled caching for this layer
     */
    public abstract void setEnabled(boolean enabled);

    /**
     * Layer meta information
     *
     * @return
     */
    public abstract LayerMetaInformation getMetaInformation();

    /**
     * Retrieves a list of Grids for this layer
     *
     * @return
     */
    public abstract Map<String, GridSubset> getGridSubsets();

    /**
     * @return possibly empty list of update sources for this layer
     */
    public abstract List<UpdateSourceDefinition> getUpdateSources();

    /**
     * Whether to use ETags for this layer
     *
     * @return
     */
    public abstract boolean useETags();

    /**
     * The normal way of getting a single tile from the layer. Under the hood, this may result in
     * several tiles being requested and stored before returning.
     *
     * @param tileRequest
     * @param servReq
     * @param response
     * @return
     * @throws GeoWebCacheException
     * @throws IOException
     * @throws OutsideCoverageException
     */
    public abstract ConveyorTile getTile(ConveyorTile tile) throws GeoWebCacheException,
            IOException, OutsideCoverageException;

    /**
     * Makes a non-metatiled request to backend, bypassing the cache before and after
     *
     * @param tile
     * @param requestTiled
     *            whether to use tiled=true or not
     * @return
     * @throws GeoWebCacheException
     * @throws IOException
     */
    public abstract ConveyorTile getNoncachedTile(ConveyorTile tile) throws GeoWebCacheException;

    /**
     *
     * @param tile
     * @param tryCache
     * @throws GeoWebCacheException
     * @throws IOException
     */
    public abstract void seedTile(ConveyorTile tile, boolean tryCache) throws GeoWebCacheException,
            IOException;

    /**
     * This is a more direct way of requesting a tile without invoking metatiling, and should not be
     * used in general. The method was exposed to let the KML service traverse the tree ahead of the
     * client, to avoid linking to empty tiles.
     *
     * @param gridLoc
     * @param idx
     * @param formatStr
     * @return
     * @throws GeoWebCacheException
     */
    public abstract ConveyorTile doNonMetatilingRequest(ConveyorTile tile)
            throws GeoWebCacheException;

    public abstract List<FormatModifier> getFormatModifiers();

    public abstract void setFormatModifiers(List<FormatModifier> formatModifiers);

    /**
     *
     * @return the styles configured for the layer, may be null
     */
    public abstract String getStyles();

    /**
     *
     * @return the {x,y} metatiling factors
     */
    public abstract int[] getMetaTilingFactors();

    /**
     * Whether clients may specify cache=false and go straight to source
     */
    public abstract Boolean isCacheBypassAllowed();

    public abstract void setCacheBypassAllowed(boolean allowed);

    public abstract boolean isQueryable();

    /**
     * The timeout used when querying the backend server. The same value is used for both the
     * connection and the data timeout, so in theory the timeout could be twice this value.
     */
    public abstract Integer getBackendTimeout();

    public abstract void setBackendTimeout(int seconds);

    /**
     *
     * @return array with supported MIME types
     */
    public abstract List<MimeType> getMimeTypes();

    public abstract int getExpireClients(int zoomLevel);

    public abstract int getExpireCache(int zoomLevel);

    public abstract List<ParameterFilter> getParameterFilters();

    public abstract List<RequestFilter> getRequestFilters();

    /**
     * Initializes the layer, creating internal structures for calculating grid location and so
     * forth.
     */
    public abstract boolean initialize(GridSetBroker gridSetBroker);

    /**
     * Acquire the global lock for the layer, primarily used for truncating
     *
     */
    public abstract void acquireLayerLock();

    /**
     * Release the global lock for the layer
     *
     */
    public abstract void releaseLayerLock();

    /**
     * Whether the layer supports the given projection
     *
     * @param srs
     *            Name of projection, for example "EPSG:4326"
     * @return
     * @throws GeoWebCacheException
     */
    public GridSubset getGridSubsetForSRS(SRS srs) {
        Map<String, GridSubset> gridSubsets = getGridSubsets();
        Iterator<GridSubset> iter = gridSubsets.values().iterator();
        while (iter.hasNext()) {
            GridSubset gridSubset = iter.next();
            if (gridSubset.getSRS().equals(srs)) {
                return gridSubset;
            }
        }

        return null;
    }

    /**
     * Whether the layer supports the given format string
     *
     * @param formatStr
     * @return
     * @throws GeoWebCacheException
     */
    public boolean supportsFormat(String strFormat) throws GeoWebCacheException {
        if (strFormat == null) {
            return true;// what the heck?!
        }

        for (MimeType mime : getMimeTypes()) {
            if (strFormat.equalsIgnoreCase(mime.getFormat())) {
                return true;
            }
        }

        // REVISIT: why not simply return false if this is a query method?!
        throw new GeoWebCacheException("Format " + strFormat + " is not supported by "
                + this.getName());
    }

    /**
     * GetFeatureInfo template, throws exception, subclasses must override if supported.
     *
     * @param convTile
     * @param bbox
     * @param height
     * @param width
     * @param x
     * @param y
     * @return
     * @throws GeoWebCacheException
     */
    public Resource getFeatureInfo(ConveyorTile convTile, BoundingBox bbox, int height, int width,
            int x, int y) throws GeoWebCacheException {
        throw new GeoWebCacheException("GetFeatureInfo is not supported by this layer ("
                + getName() + ")");
    }

    /**
     *
     * @param srsIdx
     * @return the resolutions (units/pixel) for the layer
     */
    public double[] getResolutions(String gridSetId) throws GeoWebCacheException {
        return getGridSubsets().get(gridSetId).getResolutions();
    }

    public FormatModifier getFormatModifier(MimeType responseFormat) {
        List<FormatModifier> formatModifiers = getFormatModifiers();
        if (formatModifiers == null || formatModifiers.size() == 0) {
            return null;
        }

        Iterator<FormatModifier> iter = formatModifiers.iterator();
        while (iter.hasNext()) {
            FormatModifier mod = iter.next();
            if (mod.getResponseFormat() == responseFormat) {
                return mod;
            }
        }

        return null;
    }

    /**
     * The default MIME type is the first one in the configuration
     *
     * @return
     */
    public MimeType getDefaultMimeType() {
        return getMimeTypes().get(0);
    }

    /**
     *
     * Converts the given bounding box into the closest location on the grid supported by the
     * reference system.
     *
     * @param srsIdx
     * @param bounds
     * @return
     * @throws GeoWebCacheException
     * @throws GeoWebCacheException
     * @throws GridMismatchException
     */
    public long[] indexFromBounds(String gridSetId, BoundingBox tileBounds)
            throws GridMismatchException {
        return getGridSubsets().get(gridSetId).closestIndex(tileBounds);
    }

    /**
     *
     * @param srsIdx
     * @param gridLoc
     * @return
     * @throws GeoWebCacheException
     */
    public BoundingBox boundsFromIndex(String gridSetId, long[] gridLoc) {
        return getGridSubsets().get(gridSetId).boundsFromIndex(gridLoc);
    }

    /**
     * Uses the HTTP 1.1 spec to set expiration headers
     *
     * @param response
     */
    public void setExpirationHeader(HttpServletResponse response, int zoomLevel) {
        int expireValue = getExpireClients(zoomLevel);

        // Fixed value
        if (expireValue == GWCVars.CACHE_VALUE_UNSET) {
            return;
        }

        // TODO move to TileResponse
        if (expireValue > 0) {
            response.setHeader("Cache-Control", "max-age=" + expireValue + ", must-revalidate");
            response.setHeader("Expires", ServletUtils.makeExpiresHeader(expireValue));
        } else if (expireValue == GWCVars.CACHE_NEVER_EXPIRE) {
            long oneYear = 3600 * 24 * 365;
            response.setHeader("Cache-Control", "max-age=" + oneYear);
            response.setHeader("Expires", ServletUtils.makeExpiresHeader((int) oneYear));
        } else if (expireValue == GWCVars.CACHE_DISABLE_CACHE) {
            response.setHeader("Cache-Control", "no-cache");
        } else if (expireValue == GWCVars.CACHE_USE_WMS_BACKEND_VALUE) {
            int seconds = 3600;
            response.setHeader("geowebcache-error", "No real CacheControl information available");
            response.setHeader("Cache-Control", "max-age=" + seconds);
            response.setHeader("Expires", ServletUtils.makeExpiresHeader(seconds));
        }
    }

    /**
     * Loops over all the request filters and applies them successively.
     *
     * @param convTile
     * @throws RequestFilterException
     */
    public void applyRequestFilters(ConveyorTile convTile) throws RequestFilterException {
        List<RequestFilter> requestFilters = getRequestFilters();
        if (requestFilters == null)
            return;

        Iterator<RequestFilter> iter = requestFilters.iterator();
        while (iter.hasNext()) {
            RequestFilter filter = iter.next();
            filter.apply(convTile);
        }
    }

    /**
     * @return default parameter filters, with keys normalized to upper case, or an empty map if no
     *         parameter filters are defined
     */
    public Map<String, String> getDefaultParameterFilters() {
        if (defaultParameterFilterValues == null) {
            List<ParameterFilter> parameterFilters = getParameterFilters();
            if (parameterFilters == null || parameterFilters.size() == 0) {
                defaultParameterFilterValues = Collections.emptyMap();
            } else {
                Map<String, String> defaults = new HashMap<String, String>();
                for (ParameterFilter parameterFilter : parameterFilters) {
                    String key = parameterFilter.getKey().toUpperCase();
                    String defaultValue = decodeDimensionValue(parameterFilter.getDefaultValue());
                    defaults.put(key, defaultValue);
                }
                defaultParameterFilterValues = Collections.unmodifiableMap(defaults);
            }
        }
        return defaultParameterFilterValues;
    }

    /**
     *
     * @param map
     *            keys are parameter names, values are either a single string or an array of strings
     *            as they come form httpservletrequest
     * @return Set of parameter filter keys and values, with keys normalized to upper case, or empty
     *         map if they match the layer's parameter filters default values
     * @throws GeoWebCacheException
     *             if {@link ParameterFilter#apply(String)} does
     */
    public Map<String, String> getModifiableParameters(Map<String, ?> map, String encoding)
            throws GeoWebCacheException {
        final List<ParameterFilter> parameterFilters = getParameterFilters();
        if (parameterFilters == null) {
            return Collections.emptyMap();
        }

        Map<String, String> fullParameters = new HashMap<String, String>();

        final String[] keys = new String[parameterFilters.size()];
        for (int i = 0; i < parameterFilters.size(); i++) {
            ParameterFilter parameterFilter = parameterFilters.get(i);
            keys[i] = parameterFilter.getKey();
        }

        final Map<String, String> requestValues;
        requestValues = ServletUtils.selectedStringsFromMap(map, encoding, keys);

        final Map<String, String> defaultValues = getDefaultParameterFilters();

        for (ParameterFilter parameterFilter : parameterFilters) {
            String key = parameterFilter.getKey().toUpperCase();
            String value = requestValues.get(key);
            value = decodeDimensionValue(value);

            String defaultValue = defaultValues.get(key);
            if (value == null || value.length() == 0
                    || (defaultValue != null && defaultValue.equals(value))) {
                fullParameters.put(key, defaultValue);
            } else {
                String appliedValue = parameterFilter.apply(value);
                fullParameters.put(key, appliedValue);
            }
        }
        if (defaultValues.equals(fullParameters)) {
            return Collections.emptyMap();
        }
        return fullParameters;
    }

    protected static String decodeDimensionValue(String value) {
        if (value != null && value.startsWith("_")) {
            if (value.equals("_null")) {
                return null;
            } else if (value.equals("_empty")) {
                return "";
            } else {
                return value;
            }
        } else {
            return value;
        }
    }

    public static String encodeDimensionValue(String value) {
        if (value == null) {
            return "_null";
        } else if (value.length() == 0) {
            return "_empty";
        } else {
            return value;
        }
    }

    public GridSubset getGridSubset(String gridSetId) {
        return getGridSubsets().get(gridSetId);
    }

    protected ByteArrayResource getImageBuffer(ThreadLocal<ByteArrayResource> tl) {
        ByteArrayResource buffer = tl.get();
        if (buffer == null) {
            buffer = new ByteArrayResource(16 * 1024);
            tl.set(buffer);
        }
        buffer.truncate();
        return buffer;
    }

    /**
     * Loops over the gridPositions, generates cache keys and saves to cache
     *
     * @param gridPositions
     * @param metaTile
     * @param imageFormat
     */
    protected void saveTiles(MetaTile metaTile, ConveyorTile tileProto) throws GeoWebCacheException {

        final long[][] gridPositions = metaTile.getTilesGridPositions();
        final long[] gridLoc = tileProto.getTileIndex();
        final GridSubset gridSubset = getGridSubset(tileProto.getGridSetId());

        final int zoomLevel = (int) gridLoc[2];
        final boolean store = this.getExpireCache(zoomLevel) != GWCVars.CACHE_DISABLE_CACHE;

        Resource resource;
        boolean encode;
        for (int i = 0; i < gridPositions.length; i++) {
            final long[] gridPos = gridPositions[i];
            if (Arrays.equals(gridLoc, gridPos)) {
                // Is this the one we need to save? then don't use the buffer or it'll be overridden
                // by the next tile
                resource = getImageBuffer(WMS_BUFFER2);
                tileProto.setBlob(resource);
                encode = true;
            } else {
                resource = getImageBuffer(WMS_BUFFER);
                encode = store;
            }

            if (encode) {
                if (!gridSubset.covers(gridPos)) {
                    // edge tile outside coverage, do not store it
                    continue;
                }

                try {
                    boolean completed = metaTile.writeTileToStream(i, resource);
                    if (!completed) {
                        log.error("metaTile.writeTileToStream returned false, no tiles saved");
                    }

                    if (store) {
                        long[] idx = { gridPos[0], gridPos[1], gridPos[2] };

                        TileObject tile = TileObject.createCompleteTileObject(this.getName(), idx,
                                tileProto.getGridSetId(), tileProto.getMimeType().getFormat(),
                                tileProto.getParameters(), resource);

                        try {
                            tileProto.getStorageBroker().put(tile);
                        } catch (StorageException e) {
                            throw new GeoWebCacheException(e);
                        }
                    }
                } catch (IOException ioe) {
                    log.error("Unable to write image tile to " + "ByteArrayOutputStream: "
                            + ioe.getMessage());
                    ioe.printStackTrace();
                }
            }
        }
    }

}
TOP

Related Classes of org.geowebcache.layer.TileLayer

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.