/**
* 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.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.geowebcache.GeoWebCacheException;
import org.geowebcache.config.XMLGridSubset;
import org.geowebcache.config.XMLOldGrid;
import org.geowebcache.conveyor.ConveyorTile;
import org.geowebcache.filter.parameters.ParameterFilter;
import org.geowebcache.filter.request.RequestFilter;
import org.geowebcache.grid.GridSetBroker;
import org.geowebcache.grid.GridSubset;
import org.geowebcache.grid.GridSubsetFactory;
import org.geowebcache.grid.SRS;
import org.geowebcache.layer.meta.LayerMetaInformation;
import org.geowebcache.layer.meta.MetadataURL;
import org.geowebcache.layer.updatesource.UpdateSourceDefinition;
import org.geowebcache.mime.FormatModifier;
import org.geowebcache.mime.MimeType;
import org.geowebcache.util.GWCVars;
/**
* Default statefull base class for {@link TileLayer} implementations
*/
public abstract class AbstractTileLayer extends TileLayer {
private static Log log = LogFactory.getLog(org.geowebcache.layer.AbstractTileLayer.class);
private static final int[] DEFAULT_METATILING_FACTORS = { 1, 1 };
@Nullable
protected String blobStoreId;
protected Boolean enabled;
protected Boolean advertised;
protected Boolean transientLayer;
protected String name;
protected LayerMetaInformation metaInformation;
protected List<MetadataURL> metadataURLs;
protected List<String> mimeFormats;
protected List<String> infoMimeFormats;
protected List<FormatModifier> formatModifiers;
// 1.1.x compatibility
protected Hashtable<SRS, XMLOldGrid> grids;
protected List<XMLGridSubset> gridSubsets;
protected List<UpdateSourceDefinition> updateSources;
protected List<RequestFilter> requestFilters;
protected Boolean useETags;
protected int[] metaWidthHeight;
protected String expireCache;
protected ArrayList<ExpirationRule> expireCacheList;
protected String expireClients;
protected ArrayList<ExpirationRule> expireClientsList;
protected Integer backendTimeout;
protected Boolean cacheBypassAllowed;
protected Boolean queryable;
protected List<ParameterFilter> parameterFilters;
protected transient boolean saveExpirationHeaders;
protected transient List<MimeType> formats;
protected transient List<MimeType> infoFormats;
protected transient Map<String, GridSubset> subSets;
private transient LayerListenerList listeners;
// Styles?
protected AbstractTileLayer readResolve() {
if (gridSubsets == null) {
gridSubsets = new ArrayList<XMLGridSubset>();
}
return this;
}
/**
* Registers a layer listener to be notified of layer events
*
* @see #getTile(ConveyorTile)
* @see #seedTile(ConveyorTile, boolean)
*/
@Override
public void addLayerListener(TileLayerListener listener) {
if (listeners == null) {
listeners = new LayerListenerList();
}
listeners.addListener(listener);
}
/**
* Removes a layer listener from this layer's set of listeners
*
* @param listener
* @return
*/
@Override
public boolean removeLayerListener(TileLayerListener listener) {
return listeners == null ? false : listeners.removeListener(listener);
}
protected final void sendTileRequestedEvent(ConveyorTile tile) {
if (listeners != null) {
listeners.sendTileRequested(this, tile);
}
}
@Override
public String getId(){
return getName();
}
@Override
@Nullable
public String getBlobStoreId() {
return blobStoreId;
}
@Override
public void setBlobStoreId(@Nullable String blobStoreId){
this.blobStoreId = blobStoreId;
}
/**
* Then name of the layer
*
* @return
*/
@Override
public String getName() {
return this.name;
}
@Override
public boolean isEnabled() {
return enabled == null ? true : enabled.booleanValue();
}
@Override
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@Override
public boolean isAdvertised(){
return advertised == null ? true : advertised.booleanValue();
}
@Override
public void setAdvertised(boolean advertised){
this.advertised = advertised;
}
@Override
public boolean isTransientLayer(){
return transientLayer == null ? false : transientLayer.booleanValue();
}
@Override
public void setTransientLayer(boolean transientLayer){
this.transientLayer = transientLayer;
}
/**
* Layer meta information
*
* @return
*/
@Override
public LayerMetaInformation getMetaInformation() {
return this.metaInformation;
}
@Override
public List<MetadataURL> getMetadataURLs() {
return metadataURLs == null ? null : new ArrayList<MetadataURL>(metadataURLs);
}
/**
* Retrieves a list of Grids for this layer
*
* @return
*/
@Override
public Set<String> getGridSubsets() {
return Collections.unmodifiableSet(this.subSets.keySet());
}
/**
* Initializes the layer, creating internal structures for calculating grid location and so
* forth.
* <p>
* Subclasses shall implement {@link #initializeInternal(GridSetBroker)} for anything else
* </p>
*/
@Override
public final boolean initialize(GridSetBroker gridSetBroker) {
if (this.expireCacheList == null) {
this.expireCacheList = new ArrayList<ExpirationRule>(1);
if (this.expireCache == null) {
expireCacheList.add(new ExpirationRule(0, GWCVars.CACHE_NEVER_EXPIRE));
} else {
int expireCacheInt = Integer.parseInt(expireCache);
if (expireCacheInt == GWCVars.CACHE_USE_WMS_BACKEND_VALUE) {
saveExpirationHeaders = true;
}
expireCacheList.add(new ExpirationRule(0, expireCacheInt));
}
}
if (this.expireClientsList == null) {
this.expireClientsList = new ArrayList<ExpirationRule>(1);
if (this.expireClients == null) {
expireClientsList.add(new ExpirationRule(0, 7200));
} else {
int expireClientsInt = Integer.parseInt(expireClients);
if (expireClientsInt == GWCVars.CACHE_USE_WMS_BACKEND_VALUE) {
saveExpirationHeaders = true;
} else if (expireClientsInt == GWCVars.CACHE_NEVER_EXPIRE) {
// One year should do
expireClientsInt = 3600 * 24 * 365;
}
expireClientsList.add(new ExpirationRule(0, expireClientsInt));
}
}
try {
// mimetypes
this.formats = new ArrayList<MimeType>();
if (mimeFormats != null) {
for (String fmt : mimeFormats) {
formats.add(MimeType.createFromFormat(fmt));
}
}
if (formats.size() == 0) {
formats.add(0, MimeType.createFromFormat("image/png"));
formats.add(1, MimeType.createFromFormat("image/jpeg"));
}
} catch (GeoWebCacheException gwce) {
log.error(gwce.getMessage());
gwce.printStackTrace();
}
try {
// mimetypes for info
this.infoFormats = new ArrayList<MimeType>();
if (infoMimeFormats != null) {
for (String fmt : infoMimeFormats) {
infoFormats.add(MimeType.createFromFormat(fmt));
}
}
if (infoFormats.size() == 0) {
infoFormats.add(MimeType.createFromFormat("text/plain"));
infoFormats.add(MimeType.createFromFormat("text/html"));
infoFormats.add(MimeType.createFromFormat("application/vnd.ogc.gml"));
}
} catch (GeoWebCacheException gwce) {
log.error(gwce.getMessage());
gwce.printStackTrace();
}
if (subSets == null) {
subSets = new HashMap<String, GridSubset>();
}
if (this.gridSubsets == null) {
this.gridSubsets = new ArrayList<XMLGridSubset>();
}
for (XMLGridSubset xmlGridSubset : gridSubsets) {
GridSubset gridSubset = xmlGridSubset.getGridSubSet(gridSetBroker);
if (gridSubset == null) {
log.error(xmlGridSubset.getGridSetName()
+ " is not known by the GridSetBroker, skipping for layer " + name);
} else {
subSets.put(gridSubset.getName(), gridSubset);
}
}
// Convert version 1.1.x and 1.0.x grid objects
if (grids != null && !grids.isEmpty()) {
Iterator<XMLOldGrid> iter = grids.values().iterator();
while (iter.hasNext()) {
GridSubset converted = iter.next().convertToGridSubset(gridSetBroker);
subSets.put(converted.getSRS().toString(), converted);
// hold it in case the layer is to be saved again
gridSubsets.add(new XMLGridSubset(converted));
}
// Null it for the garbage collector
grids = null;
}
if (this.subSets.size() == 0) {
subSets.put(gridSetBroker.WORLD_EPSG4326.getName(),
GridSubsetFactory.createGridSubSet(gridSetBroker.WORLD_EPSG4326));
subSets.put(gridSetBroker.WORLD_EPSG3857.getName(),
GridSubsetFactory.createGridSubSet(gridSetBroker.WORLD_EPSG3857));
}
return initializeInternal(gridSetBroker);
}
protected abstract boolean initializeInternal(GridSetBroker gridSetBroker);
/**
* @return possibly empty list of update sources for this layer
*/
@Override
public List<UpdateSourceDefinition> getUpdateSources() {
List<UpdateSourceDefinition> sources;
if (updateSources == null) {
sources = Collections.emptyList();
} else {
sources = updateSources;
}
return sources;
}
/**
* Whether to use ETags for this layer
*
* @return
*/
@Override
public boolean useETags() {
return useETags == null ? false : useETags.booleanValue();
}
@Override
public List<FormatModifier> getFormatModifiers() {
return formatModifiers;
}
@Override
public void setFormatModifiers(List<FormatModifier> formatModifiers) {
this.formatModifiers = formatModifiers;
}
/**
*
* @return the styles configured for the layer, may be null
*/
@Override
public abstract String getStyles();
/**
*
* @return the {x,y} metatiling factors
*/
@Override
public int[] getMetaTilingFactors() {
return metaWidthHeight == null ? DEFAULT_METATILING_FACTORS : metaWidthHeight;
}
/**
* Whether clients may specify cache=false and go straight to source
*/
@Override
public Boolean isCacheBypassAllowed() {
return cacheBypassAllowed;
}
@Override
public void setCacheBypassAllowed(boolean allowed) {
cacheBypassAllowed = Boolean.valueOf(allowed);
}
@Override
public boolean isQueryable() {
return queryable == null ? false : queryable.booleanValue();
}
/**
* 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.
*/
@Override
public Integer getBackendTimeout() {
return backendTimeout;
}
@Override
public void setBackendTimeout(int seconds) {
backendTimeout = seconds;
}
public List<String> getMimeFormats() {
return mimeFormats == null ? null : new ArrayList<String>(mimeFormats);
}
/**
*
* @return array with supported MIME types
*/
@Override
public List<MimeType> getMimeTypes() {
return formats;
}
public List<String> getInfoMimeFormats() {
return infoMimeFormats == null ? null : new ArrayList<String>(infoMimeFormats);
}
/**
*
* @return array with supported MIME types for information
*/
@Override
public List<MimeType> getInfoMimeTypes() {
return infoFormats;
}
@Override
public int getExpireClients(int zoomLevel) {
return getExpiration(this.expireClientsList, zoomLevel);
}
@Override
public int getExpireCache(int zoomLevel) {
return getExpiration(this.expireCacheList, zoomLevel);
}
private int getExpiration(ArrayList<ExpirationRule> list, int zoomLevel) {
int retVal;
int length = list.size();
if (length == 1) {
retVal = list.get(0).getExpiration();
} else {
int i;
for (i = 1; i < length;) {
if (list.get(i).getMinZoom() > zoomLevel) {
break;
}
i++;
}
retVal = list.get(i - 1).getExpiration();
}
if (retVal == GWCVars.CACHE_USE_WMS_BACKEND_VALUE) {
return 7200;
}
return retVal;
}
public List<ParameterFilter> getParameterFilters() {
return parameterFilters;
}
public List<RequestFilter> getRequestFilters() {
return requestFilters;
}
@Override
public GridSubset getGridSubset(String gridSetId) {
return subSets.get(gridSetId);
}
@Override
public synchronized GridSubset removeGridSubset(String gridSetId) {
for (Iterator<XMLGridSubset> it = gridSubsets.iterator(); it.hasNext();) {
XMLGridSubset configSubset = it.next();
if (gridSetId.equals(configSubset.getGridSetName())) {
it.remove();
break;
}
}
return subSets.remove(gridSetId);
}
@Override
public synchronized void addGridSubset(GridSubset gridSubset) {
removeGridSubset(gridSubset.getName());
gridSubsets.add(new XMLGridSubset(gridSubset));
subSets.put(gridSubset.getName(), gridSubset);
}
}