/**
* 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.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.geowebcache.GeoWebCacheException;
import org.geowebcache.GeoWebCacheExtensions;
import org.geowebcache.config.Configuration;
import org.geowebcache.config.XMLConfiguration;
import org.geowebcache.config.XMLGridSet;
import org.geowebcache.config.meta.ServiceInformation;
import org.geowebcache.grid.GridSet;
import org.geowebcache.grid.GridSetBroker;
import org.geowebcache.grid.GridSubset;
import org.geowebcache.util.CompositeIterable;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.util.Assert;
import com.google.common.base.Preconditions;
/**
* Serves tile layers from the {@link Configuration}s available in the application context.
*/
public class TileLayerDispatcher implements DisposableBean {
private static Log log = LogFactory.getLog(org.geowebcache.layer.TileLayerDispatcher.class);
private List<Configuration> configs;
private GridSetBroker gridSetBroker;
private ServiceInformation serviceInformation;
/**
* @deprecated use {@link #TileLayerDispatcher(GridSetBroker)} instead, configurations are
* loaded from the application context, this {@code config} parameter will be
* ignored
*/
public TileLayerDispatcher(GridSetBroker gridSetBroker, List<Configuration> configs) {
this.gridSetBroker = gridSetBroker;
this.configs = configs == null ? new ArrayList<Configuration>() : configs;
initialize();
}
public TileLayerDispatcher(GridSetBroker gridSetBroker) {
this.gridSetBroker = gridSetBroker;
reInit();
}
public void addConfiguration(Configuration config) {
initialize(config);
List<Configuration> newList = new ArrayList<Configuration>(configs);
newList.add(config);
this.configs = newList;
}
public boolean layerExists(final String layerName) {
for (int i = 0; i < configs.size(); i++) {
Configuration configuration = configs.get(i);
TileLayer layer = configuration.getTileLayer(layerName);
if (layer != null) {
return true;
}
}
return false;
}
/**
* Returns the layer named after the {@code layerName} parameter.
*
* @throws GeoWebCacheException
* if no such layer exists
*/
public TileLayer getTileLayer(final String layerName) throws GeoWebCacheException {
Preconditions.checkNotNull(layerName, "layerName is null");
for (int i = 0; i < configs.size(); i++) {
Configuration configuration = configs.get(i);
TileLayer layer = configuration.getTileLayer(layerName);
if (layer != null) {
return layer;
}
}
throw new GeoWebCacheException("Thread " + Thread.currentThread().getId()
+ " Unknown layer " + layerName + ". Check the logfiles,"
+ " it may not have loaded properly.");
}
/***
* Reinitialization is tricky, because we can't really just lock all the layers, because this
* would cause people to queue on something that we may not want to exist post reinit.
*
* So we'll just set the current layer set free, ready for garbage collection, and generate a
* new one.
*
*/
public void reInit() {
List<Configuration> extensions = GeoWebCacheExtensions.extensions(Configuration.class);
this.configs = new ArrayList<Configuration>(extensions);
initialize();
}
public int getLayerCount() {
int count = 0;
for (int i = 0; i < configs.size(); i++) {
Configuration configuration = configs.get(i);
count += configuration.getTileLayerCount();
}
return count;
}
public Set<String> getLayerNames() {
Set<String> names = new HashSet<String>();
for (int i = 0; i < configs.size(); i++) {
Configuration configuration = configs.get(i);
names.addAll(configuration.getTileLayerNames());
}
return names;
}
/**
* Returns a list of all the layers. The consumer may still have to initialize each layer!
* <p>
* Modifications to the returned layer do not change the internal list of layers, but layers ARE
* mutable.
* </p>
*
* @return a list view of this tile layer dispatcher's internal layers
*/
@SuppressWarnings("unchecked")
public Iterable<TileLayer> getLayerList() {
List<Iterable<TileLayer>> perConfigLayers = new ArrayList<Iterable<TileLayer>>(
configs.size());
for (Configuration config : configs) {
perConfigLayers.add((Iterable<TileLayer>) config.getLayers());
}
return new CompositeIterable<TileLayer>(perConfigLayers);
}
private void initialize() {
log.debug("Thread initLayers(), initializing");
for (Configuration config : configs) {
initialize(config);
}
}
private int initialize(Configuration config) {
if (config == null) {
throw new IllegalStateException(
"TileLayerDispatcher got a null GWC configuration object");
}
String configIdent = null;
try {
configIdent = config.getIdentifier();
} catch (Exception gwce) {
log.error("Error obtaining identify from Configuration " + config, gwce);
return 0;
}
if (configIdent == null) {
log.warn("Got a GWC configuration with no identity, ignoring it:" + config);
return 0;
}
int layerCount;
try {
layerCount = config.initialize(gridSetBroker);
} catch (GeoWebCacheException gwce) {
log.error("Failed to add layers from " + configIdent, gwce);
return 0;
}
if (layerCount <= 0) {
log.info("Configuration " + config.getIdentifier() + " contained no layers.");
}
// Check whether there is any general service information
if (this.serviceInformation == null) {
log.debug("Reading service information.");
this.serviceInformation = config.getServiceInformation();
}
return layerCount;
}
public ServiceInformation getServiceInformation() {
return this.serviceInformation;
}
/**
* @see org.springframework.beans.factory.DisposableBean#destroy()
*/
public void destroy() throws Exception {
//
}
/**
* Finds out which {@link Configuration} contains the given layer,
* {@link Configuration#removeLayer(String) removes} it, and returns the configuration, without
* saving it.
* <p>
* The calling code is responsible from calling {@link Configuration#save()} on the returned
* configuration object if the change is to be made persistent.
* </p>
*
* @param layerName
* the name of the layer to remove
* @return the Configuration from which the layer has been removed, or {@code null} if no
* configuration contained such a layer
*/
public synchronized Configuration removeLayer(final String layerName)
throws IllegalArgumentException {
for (Configuration config : configs) {
if (config.removeLayer(layerName)) {
return config;
}
}
return null;
}
/**
* Adds a layer and returns (but doesn't save) the {@link Configuration} to which the layer was
* added.
*
* @param tl
* the layer to add
* @return the configuration to which the layer was added; calling code is in charge of decising
* whether to {@link Configuration#save() save} the configuration permanently or not.
* @throws IllegalArgumentException
* if the given tile layer can't be added to any configuraion managed by this tile
* layer dispatcher.
*/
public synchronized Configuration addLayer(final TileLayer tl) throws IllegalArgumentException {
for (Configuration c : configs) {
if (c.canSave(tl)) {
c.addLayer(tl);
return c;
}
}
throw new IllegalArgumentException("No configuration found capable of saving " + tl);
}
/**
* Replaces the given layer and returns the Layer's configuration, does not save the
* configuration, the calling code shall do that if the change is to be made persistent.
*
* @param tl
* @throws IllegalArgumentException
*/
public synchronized Configuration modify(final TileLayer tl) throws IllegalArgumentException {
Configuration config = getConfiguration(tl);
config.modifyLayer(tl);
return config;
}
public Configuration getConfiguration(TileLayer tl) throws IllegalArgumentException {
Assert.notNull(tl, "layer is null");
return getConfiguration(tl.getId());
}
public Configuration getConfiguration(final String tileLayerId) throws IllegalArgumentException {
Assert.notNull(tileLayerId, "tileLayerId is null");
for (Configuration c : configs) {
if (c.containsLayer(tileLayerId)) {
return c;
}
}
throw new IllegalArgumentException("No configuration found containing layer " + tileLayerId);
}
/**
* Eliminates the gridset from the {@link GridSetBroker} and the {@link XMLConfiguration} only
* if no layer references the given GridSet.
* <p>
* NOTE this method does not save the configuration, it's up to the calling code to do that in
* order to make the change persistent.
* </p>
*
* @param gridSetName
* the gridset to remove.
* @return the configuration modified after removing the gridset, or {@code null}
* @throws IllegalStateException
* if there's any layer referencing the given GridSet
* @throws IOException
* @see {@link GridSetBroker#remove(String)}
*/
public synchronized Configuration removeGridset(final String gridSetName)
throws IllegalStateException, IOException {
GridSet gridSet = gridSetBroker.get(gridSetName);
if (gridSet == null) {
return null;
}
List<String> refereningLayers = new ArrayList<String>();
for (TileLayer layer : getLayerList()) {
GridSubset gridSubset = layer.getGridSubset(gridSetName);
if (gridSubset != null) {
refereningLayers.add(layer.getName());
}
}
if (refereningLayers.size() > 0) {
throw new IllegalStateException("There are TileLayers referencing gridset '"
+ gridSetName + "': " + refereningLayers.toString());
}
XMLConfiguration persistingConfig = getXmlConfiguration();
GridSet removed = gridSetBroker.remove(gridSetName);
Assert.notNull(removed != null);
Assert.notNull(persistingConfig.removeGridset(gridSetName));
return persistingConfig;
}
public synchronized void addGridSet(final GridSet gridSet) throws IllegalArgumentException,
IOException {
if (null != gridSetBroker.get(gridSet.getName())) {
throw new IllegalArgumentException("GridSet " + gridSet.getName() + " already exists");
}
saveGridSet(gridSet);
}
private void saveGridSet(final GridSet gridSet) throws IOException {
XMLConfiguration persistingConfig = getXmlConfiguration();
persistingConfig.addOrReplaceGridSet(new XMLGridSet(gridSet));
persistingConfig.save();
gridSetBroker.put(gridSet);
}
private XMLConfiguration getXmlConfiguration() throws IllegalStateException {
for (Configuration c : configs) {
if (c instanceof XMLConfiguration) {
return (XMLConfiguration) c;
}
}
throw new IllegalStateException("Found no configuration of type "
+ XMLConfiguration.class.getName());
}
}