Package org.geoserver.gwc.layer

Source Code of org.geoserver.gwc.layer.CatalogConfiguration

/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.gwc.layer;

import static com.google.common.base.Objects.equal;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.collect.Maps.newConcurrentMap;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.catalog.LayerGroupInfo;
import org.geoserver.catalog.LayerInfo;
import org.geoserver.gwc.GWC;
import org.geotools.util.logging.Logging;
import org.geowebcache.config.Configuration;
import org.geowebcache.config.XMLGridSubset;
import org.geowebcache.config.meta.ServiceInformation;
import org.geowebcache.grid.BoundingBox;
import org.geowebcache.grid.GridSetBroker;
import org.geowebcache.layer.TileLayer;
import org.geowebcache.layer.TileLayerDispatcher;
import org.geowebcache.locks.LockProvider;

import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.UncheckedExecutionException;

/**
* A GWC's {@link Configuration} implementation that provides {@link TileLayer}s directly from the
* GeoServer {@link Catalog}'s {@link LayerInfo}s and {@link LayerGroupInfo}s.
* <p>
* The sole responsibility of the class is to provide the {@link GeoServerTileLayer}s out of the
* geoserver catalog for {@link TileLayerDispatcher}
* </p>
*
* @see #createLayer(LayerInfo)
* @see #createLayer(LayerGroupInfo)
* @see #getTileLayers(boolean)
* @see CatalogStyleChangeListener
*/
public class CatalogConfiguration implements Configuration {

    /**
     * {@link GeoServerTileLayer} cache loader
     *
     */
    private final class TileLayerLoader extends CacheLoader<String, GeoServerTileLayer> {
        private final TileLayerCatalog tileLayerCatalog;

        private TileLayerLoader(TileLayerCatalog tileLayerCatalog) {
            this.tileLayerCatalog = tileLayerCatalog;
        }

        @Override
        public GeoServerTileLayer load(String layerId) throws Exception {
            GeoServerTileLayer tileLayer = null;
            final GridSetBroker gridSetBroker = CatalogConfiguration.this.gridSetBroker;

            lock.readLock().lock();
            try {
                if (pendingDeletes.contains(layerId)) {
                    throw new IllegalArgumentException("Tile layer '" + layerId + "' was deleted.");
                }
                GeoServerTileLayerInfo tileLayerInfo = pendingModications.get(layerId);
                if (tileLayerInfo == null) {
                    tileLayerInfo = tileLayerCatalog.getLayerById(layerId);
                }
                if (tileLayerInfo == null) {
                    throw new IllegalArgumentException("GeoServerTileLayerInfo '" + layerId
                            + "' does not exist.");
                }

                LayerInfo layerInfo = geoServerCatalog.getLayer(layerId);
                if (layerInfo != null) {
                    tileLayer = new GeoServerTileLayer(layerInfo, gridSetBroker, tileLayerInfo);
                } else {
                    LayerGroupInfo lgi = geoServerCatalog.getLayerGroup(layerId);
                    if (lgi != null) {
                        tileLayer = new GeoServerTileLayer(lgi, gridSetBroker, tileLayerInfo);
                    }
                }
            } finally {
                lock.readLock().unlock();
            }
            if (null == tileLayer) {
                throw new IllegalArgumentException("GeoServer layer or layer group '" + layerId
                        + "' does not exist");
            }
            return tileLayer;
        }
    }

    private static final Logger LOGGER = Logging.getLogger(CatalogConfiguration.class);

    private TileLayerCatalog tileLayerCatalog;

    private Catalog geoServerCatalog;

    private GridSetBroker gridSetBroker;

    /**
     * Maps pending modifications by {@link GeoServerTileLayerInfo#getId()}
     */
    private final Map<String, GeoServerTileLayerInfo> pendingModications = newConcurrentMap();

    private final LoadingCache<String, GeoServerTileLayer> layerCache;

    /**
     * Ids of pending deletes
     */
    private final Set<String> pendingDeletes = new CopyOnWriteArraySet<String>();

    private final ReadWriteLock lock = new ReentrantReadWriteLock();

    public CatalogConfiguration(final Catalog catalog, final TileLayerCatalog tileLayerCatalog,
            final GridSetBroker gridSetBroker) {
        checkNotNull(catalog);
        checkNotNull(tileLayerCatalog);
        checkNotNull(gridSetBroker);
        this.tileLayerCatalog = tileLayerCatalog;
        this.geoServerCatalog = catalog;
        this.gridSetBroker = gridSetBroker;

        this.layerCache = CacheBuilder.newBuilder()//
                .concurrencyLevel(10)//
                .expireAfterAccess(10, TimeUnit.MINUTES)//
                .initialCapacity(10)//
                .maximumSize(100)//
                .build(new TileLayerLoader(tileLayerCatalog));
    }

    /**
     *
     * @see org.geowebcache.config.Configuration#getIdentifier()
     */
    @Override
    public String getIdentifier() {
        return "GeoServer Catalog Configuration";
    }

    /**
     * @see org.geowebcache.config.Configuration#getServiceInformation()
     * @return {@code null}
     */
    @Override
    public ServiceInformation getServiceInformation() {
        return null;
    }

    /**
     * @return {@code true}
     * @see org.geowebcache.config.Configuration#isRuntimeStatsEnabled()
     */
    @Override
    public boolean isRuntimeStatsEnabled() {
        return true;
    }

    /**
     * Returns the list of {@link GeoServerTileLayer} objects matching the GeoServer ones.
     * <p>
     * The list is built dynamically on each call.
     * </p>
     *
     * @see org.geowebcache.config.Configuration#getTileLayers(boolean)
     * @see org.geowebcache.config.Configuration#getTileLayers()
     * @deprecated
     */
    @Override
    public List<GeoServerTileLayer> getTileLayers() {
        Iterable<GeoServerTileLayer> layers = getLayers();
        return Lists.newArrayList(layers);
    }

    /**
     * @see org.geowebcache.config.Configuration#getLayers()
     */
    @Override
    public Iterable<GeoServerTileLayer> getLayers() {
        lock.readLock().lock();
        try {
            final Set<String> layerIds = tileLayerCatalog.getLayerIds();

            Function<String, GeoServerTileLayer> lazyLayerFetch = new Function<String, GeoServerTileLayer>() {
                @Override
                public GeoServerTileLayer apply(final String layerId) {
                    return CatalogConfiguration.this.getTileLayerById(layerId);
                }
            };

            return Iterables.transform(layerIds, lazyLayerFetch);
        } finally {
            lock.readLock().unlock();
        }
    }

    /**
     * Returns a dynamic list of cached layer names out of the GeoServer {@link Catalog}
     *
     * @see org.geowebcache.config.Configuration#getTileLayerNames()
     */
    @Override
    public Set<String> getTileLayerNames() {
        lock.readLock().lock();
        try {
            final Set<String> storedNames = tileLayerCatalog.getLayerNames();
            Set<String> names = null;
            if (!pendingDeletes.isEmpty()) {
                names = new HashSet<String>(storedNames);
                for (String id : pendingDeletes) {
                    GeoServerTileLayerInfo old = tileLayerCatalog.getLayerById(id);
                    names.remove(old.getName());
                }
            }
            if (!pendingModications.isEmpty()) {
                for (Map.Entry<String, GeoServerTileLayerInfo> e : pendingModications.entrySet()) {
                    GeoServerTileLayerInfo old = tileLayerCatalog.getLayerById(e.getKey());
                    if (old != null) {
                        // it's a modification, not an addition. Make sure the name is not outdated
                        String oldName = old.getName();
                        String newName = e.getValue().getName();
                        if (!Objects.equal(oldName, newName)) {
                            if (names == null) {
                                names = new HashSet<String>(storedNames);
                            }
                            names.remove(oldName);
                            names.add(newName);
                        }
                    }
                }
            }
            return names == null ? storedNames : Collections.unmodifiableSet(names);
        } finally {
            lock.readLock().unlock();
        }
    }

    @Override
    public boolean containsLayer(String layerId) {
        checkNotNull(layerId, "layer id is null");
        lock.readLock().lock();
        try {
            if (pendingDeletes.contains(layerId)) {
                return false;
            }
            Set<String> layerIds = tileLayerCatalog.getLayerIds();
            boolean hasLayer = layerIds.contains(layerId);
            return hasLayer;
        } finally {
            lock.readLock().unlock();
        }
    }

    @Override
    public GeoServerTileLayer getTileLayerById(final String layerId) {
        checkNotNull(layerId, "layer id is null");

        GeoServerTileLayer layer;
        try {
            layer = layerCache.get(layerId);
        } catch (ExecutionException e) {
            throw propagate(e.getCause());
        } catch (UncheckedExecutionException e) {
            throw propagate(e.getCause());
        }

        return layer;
    }

    /**
     * @see org.geowebcache.config.Configuration#getTileLayer(java.lang.String)
     */
    @Override
    public GeoServerTileLayer getTileLayer(final String layerName) {
        checkNotNull(layerName, "layer name is null");

        final String layerId;

        lock.readLock().lock();
        try {
            layerId = getLayerId(layerName);
            if (layerId == null) {
                return null;
            }
        } finally {
            lock.readLock().unlock();
        }
        return getTileLayerById(layerId);
    }

    private String getLayerId(final String layerName) {

        String storedName = layerName;
        // check pending modifs first in case name changed
        if (!pendingModications.isEmpty()) {
            for (GeoServerTileLayerInfo info : pendingModications.values()) {
                String name = info.getName();
                if (name.equals(layerName)) {
                    storedName = info.getName();
                    break;
                }
            }
        }

        final String layerId = tileLayerCatalog.getLayerId(storedName);
        if (layerId == null || pendingDeletes.contains(layerId)) {
            return null;
        }
        // name changed?
        GeoServerTileLayerInfo modifiedState = pendingModications.get(layerId);
        if (modifiedState != null && !layerName.equals(modifiedState.getName())) {
            return null;
        }
        return layerId;
    }

    private GeoServerTileLayerInfo getTileLayerInfoByName(final String layerName) {
        GeoServerTileLayerInfo tileLayerInfo = null;

        // check pending modifs first in case name changed
        if (!pendingModications.isEmpty()) {
            for (GeoServerTileLayerInfo info : pendingModications.values()) {
                String name = info.getName();
                if (name.equals(layerName)) {
                    tileLayerInfo = info;
                    break;
                }
            }
        }

        if (null == tileLayerInfo) {
            tileLayerInfo = tileLayerCatalog.getLayerByName(layerName);
            if (null == tileLayerInfo) {
                return null;
            }
            if (pendingDeletes.contains(tileLayerInfo.getId())) {
                return null;
            }
            if (pendingModications.containsKey(tileLayerInfo.getId())) {
                // found in catalog but not in pending modifications, means name changed
                return null;
            }
        }
        return tileLayerInfo;
    }

    /**
     * @see org.geowebcache.config.Configuration#getTileLayerCount()
     */
    @Override
    public int getTileLayerCount() {
        int count = 0;
        lock.readLock().lock();
        try {
            Set<String> layerIds = tileLayerCatalog.getLayerIds();
            if (pendingDeletes.isEmpty()) {
                count = layerIds.size();
            } else {
                for (String layerId : layerIds) {
                    if (pendingDeletes.contains(layerId)) {
                        continue;
                    }
                    ++count;
                }
            }
        } finally {
            lock.readLock().unlock();
        }
        return count;
    }

    /**
     * @see org.geowebcache.config.Configuration#initialize(org.geowebcache.grid.GridSetBroker)
     */
    @Override
    public int initialize(GridSetBroker gridSetBroker) {
        lock.writeLock().lock();
        try {
            LOGGER.info("Initializing GWC configuration based on GeoServer's Catalog");
            this.gridSetBroker = gridSetBroker;
            this.layerCache.invalidateAll();
            this.tileLayerCatalog.initialize();

            // startup sanity check
            for (String layerId : tileLayerCatalog.getLayerIds()) {
                final String layerName = tileLayerCatalog.getLayerName(layerId);
                try {
                    getTileLayerById(layerId);
                } catch (Exception e) {
                    String msg = "GeoServer TileLayer named '" + layerName + "' with id '"
                            + layerId + "' can't be loaded. "
                            + "It will be removed from the configuration but you'll need"
                            + " to delete its cache manually (if any). Original error message: "
                            + e.getMessage();
                    LOGGER.log(Level.SEVERE, msg, e);
                    tileLayerCatalog.delete(layerId);
                }
            }
            LOGGER.info("GWC configuration based on GeoServer's Catalog loaded successfuly");
        } finally {
            lock.writeLock().unlock();
        }
        return getTileLayerCount();
    }

    /**
     * @return {@code true} only if {@code tl instanceof} {@link GeoServerTileLayer} .
     * @see org.geowebcache.config.Configuration#canSave(org.geowebcache.layer.TileLayer)
     */
    @Override
    public boolean canSave(TileLayer tl) {
        return tl instanceof GeoServerTileLayer;
    }
   
    public static boolean isLayerExposable(LayerInfo layer) {
        assert layer!=null;
        // TODO: this was copied from WMS 1.1 GetCapabilitesTransformer.handleLayerTree and is
        // replicated again in the WMS 1.3 implementation.  Should be refactored to eliminate
        // duplication.
       
        // no sense in exposing a geometryless layer through wms...
        boolean wmsExposable = false;
        if (layer.getType() == LayerInfo.Type.RASTER || layer.getType() == LayerInfo.Type.WMS) {
            wmsExposable = true;
        } else {
            try {
                wmsExposable = layer.getType() == LayerInfo.Type.VECTOR
                        && ((FeatureTypeInfo) layer.getResource()).getFeatureType()
                                .getGeometryDescriptor() != null;
            } catch (Exception e) {
                LOGGER.log(Level.SEVERE, "An error occurred trying to determine if"
                        + " the layer is geometryless", e);
            }
        }
       
        return wmsExposable;
    }
    @Override
    public synchronized void addLayer(final TileLayer tl) {
        checkNotNull(tl);
        checkArgument(canSave(tl), "Can't save TileLayer of type ", tl.getClass());
        GeoServerTileLayer tileLayer = (GeoServerTileLayer) tl;
        checkNotNull(tileLayer.getInfo(), "GeoServerTileLayerInfo is null");
        checkNotNull(tileLayer.getInfo().getId(), "id is null");
        checkNotNull(tileLayer.getInfo().getName(), "name is null");

        GeoServerTileLayerInfo info = tileLayer.getInfo();
       
        LayerInfo layerInfo = tileLayer.getLayerInfo();
        if(layerInfo!=null && !isLayerExposable(layerInfo)) {
            LOGGER.warning("Requested layer " + layerInfo.getName() + " has no geometry. Won't create TileLayer");
            return;
        }

        lock.writeLock().lock();
        try {
            boolean pending = pendingModications.containsKey(info.getId());
            boolean exists = null != tileLayerCatalog.getLayerById(info.getId());
            boolean notExists = !pending && !exists;

            checkArgument(notExists, "A GeoServerTileLayer named '" + info.getName()
                    + "' already exists");
            if (pendingDeletes.remove(info.getId())) {
                LOGGER.finer("Adding a new layer " + info.getName()
                        + " before saving the deleted one with the same name");
            }
            pendingModications.put(info.getId(), info);
        } finally {
            lock.writeLock().unlock();
        }
    }

    /**
     * @see org.geowebcache.config.Configuration#modifyLayer(org.geowebcache.layer.TileLayer)
     */
    @Override
    public synchronized void modifyLayer(TileLayer tl) throws NoSuchElementException {
        checkNotNull(tl, "TileLayer is null");
        checkArgument(canSave(tl), "Can't save TileLayer of type ", tl.getClass());

        GeoServerTileLayer tileLayer = (GeoServerTileLayer) tl;

        checkNotNull(tileLayer.getInfo(), "GeoServerTileLayerInfo is null");
        checkNotNull(tileLayer.getInfo().getId(), "id is null");
        checkNotNull(tileLayer.getInfo().getName(), "name is null");

        final GeoServerTileLayerInfo info = tileLayer.getInfo();
        lock.writeLock().lock();
        try {
            final String layerId = info.getId();
            // check pendingModifications too to catch unsaved adds
            boolean exists = pendingModications.containsKey(layerId)
                    || tileLayerCatalog.exists(layerId);
            checkArgument(exists, "No GeoServerTileLayer named '" + info.getName() + "' exists");
            pendingModications.put(layerId, info);
            layerCache.invalidate(layerId);
        } finally {
            lock.writeLock().unlock();
        }
    }

    /**
     * {@link TileLayerDispatcher} is requesting to remove the layer named after {@code layerName}
     *
     * @see org.geowebcache.config.Configuration#removeLayer(java.lang.String)
     * @return {@code true} if the layer was removed, false if it didn't exist
     */
    @Override
    public boolean removeLayer(final String layerName) {
        checkNotNull(layerName);
        lock.writeLock().lock();
        try {
            GeoServerTileLayerInfo tileLayerInfo = getTileLayerInfoByName(layerName);
            if (tileLayerInfo != null) {
                final String layerId = tileLayerInfo.getId();
                pendingModications.remove(layerId);
                pendingDeletes.add(layerId);
                layerCache.invalidate(layerId);
                return true;
            } else {
                return false;
            }
        } finally {
            lock.writeLock().unlock();
        }
    }

    /**
     * @see GWC#layerAdded(String)
     * @see GWC#layerRemoved(String)
     * @see GWC#layerRenamed(String, String)
     * @see GWC#truncateByLayerAndStyle(String, String)
     * @see GWC#truncate(String, String, String, BoundingBox, String)
     * @see org.geowebcache.config.Configuration#save()
     */
    @Override
    public synchronized void save() {

        final GWC mediator = GWC.get();

        final Set<String/* name */> deletedNames = Sets.newHashSet();
        final List<GeoServerTileLayerInfo[/* old, new */]> modifications = Lists.newLinkedList();

        lock.writeLock().lock();
        // perform the transaction while holding the write lock, then downgrade to the read lock and
        // issue the modification events (otherwise another thread asking for any changed layer
        // would lock)
        try {
            for (String deletedId : pendingDeletes) {
                try {
                    GeoServerTileLayerInfo info = tileLayerCatalog.delete(deletedId);
                    if (info != null) {
                        // remove it from stack copy to avoid notifying its deletion
                        deletedNames.add(info.getName());
                    }
                } catch (RuntimeException e) {
                    LOGGER.log(Level.SEVERE, "Error deleting tile layer '" + deletedId + "'", e);
                }
            }

            for (GeoServerTileLayerInfo modified : pendingModications.values()) {
                final GeoServerTileLayerInfo old;
                try {
                    old = tileLayerCatalog.save(modified);
                    modifications.add(new GeoServerTileLayerInfo[] { old, modified });
                } catch (RuntimeException e) {
                    LOGGER.log(Level.SEVERE,
                            "Error saving tile layer '" + modified.getName() + "'", e);
                }
            }
            this.pendingModications.clear();
            this.pendingDeletes.clear();
        } finally {
            // Downgrade by acquiring read lock before releasing write lock
            lock.readLock().lock();
            lock.writeLock().unlock(); // Unlock write, still hold read
            try {
                // issue notifications
                for (String deletedLayerName : deletedNames) {
                    try {
                        // let the mediator deal with gwc to get rid of all the caches
                        mediator.layerRemoved(deletedLayerName);
                    } catch (RuntimeException e) {
                        LOGGER.log(Level.SEVERE, "Error deleting tile layer '" + deletedLayerName
                                + "'", e);
                    }
                }

                for (GeoServerTileLayerInfo[] oldNew : modifications) {
                    final GeoServerTileLayerInfo old = oldNew[0];
                    final GeoServerTileLayerInfo modified = oldNew[1];
                    try {
                        if (old == null) {
                            // it's an addition
                            String layerName = modified.getName();
                            mediator.layerAdded(layerName);
                        } else {
                            // it's a modification
                            issueTileLayerInfoChangeNotifications(old, modified);
                        }
                    } catch (RuntimeException e) {
                        LOGGER.log(Level.SEVERE, "Error issuing change events for tile layer "
                                + modified +".  This may result in leaked tiles that will not be truncated.", e);
                    }
                }
            } finally {
                lock.readLock().unlock();
            }
        }
    }

    private void issueTileLayerInfoChangeNotifications(final GeoServerTileLayerInfo oldInfo,
            final GeoServerTileLayerInfo newInfo) {

        checkNotNull(oldInfo);
        checkNotNull(newInfo);
        checkNotNull(oldInfo.getName());
        checkNotNull(newInfo.getName());
        checkNotNull(oldInfo.getId());
        checkNotNull(newInfo.getId());
        checkArgument(equal(oldInfo.getId(), newInfo.getId()));

        final GWC mediator = GWC.get();

        final String oldLayerName = oldInfo.getName();
        final String layerName = newInfo.getName();

        final boolean isRename = !equal(oldLayerName, layerName);
        if (isRename) {
            mediator.layerRenamed(oldLayerName, layerName);
        }
        // FIXME: There should be a way to ask GWC to "truncate redundant caches" rather than doing
        //         all this detective work.

        // First, remove the entire layer cache for any removed gridset
        Set<XMLGridSubset> oldGridSubsets = oldInfo.getGridSubsets();
        Set<XMLGridSubset> newGridSubsets = newInfo.getGridSubsets();

        Set<String> oldGridSubsetNames = gridsetNames(oldGridSubsets);
        Set<String> newGridSubsetNames = gridsetNames(newGridSubsets);

        Set<String> removedGridSets = new HashSet<String>(oldGridSubsetNames);
        removedGridSets.removeAll(newGridSubsetNames);
        for (String removedGridset : removedGridSets) {
            mediator.deleteCacheByGridSetId(layerName, removedGridset);
        }

        // then proceed with any removed cache format/style on the remaining gridsets
        Set<String> oldFormats = new HashSet<String>(oldInfo.getMimeFormats());
        Set<String> newFormats = new HashSet<String>(newInfo.getMimeFormats());
        if (!oldFormats.equals(newFormats)) {
            oldFormats.removeAll(newFormats);
            for (String removedFormat : oldFormats) {
                String styleName = null;
                String gridSetName = null;
                BoundingBox bounds = null;
                mediator.truncate(layerName, styleName, gridSetName, bounds, removedFormat);
            }
        }

        Set<String> oldStyles = oldInfo.cachedStyles();
        Set<String> newStyles = newInfo.cachedStyles();
       
        if (!newStyles.equals(oldStyles)) {
            oldStyles = new HashSet<String>(oldStyles);
            oldStyles.removeAll(newStyles);
            for (String removedStyle : oldStyles) {
                mediator.truncateByLayerAndStyle(layerName, removedStyle);
            }
        }
    }

    private Set<String> gridsetNames(Set<XMLGridSubset> gridSubsets) {
        Set<String> names = new HashSet<String>();
        for (XMLGridSubset gridSubset : gridSubsets) {
            names.add(gridSubset.getGridSetName());
        }
        return names;
    }

    public void reset() {
        lock.writeLock().lock();
        try {
            this.layerCache.invalidateAll();
            this.tileLayerCatalog.reset();
        } finally {
            lock.writeLock().unlock();
        }
    }
}
TOP

Related Classes of org.geoserver.gwc.layer.CatalogConfiguration

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.