/* (c) 2014 - 2016 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.web.demo; import java.util.ArrayList; import java.io.Serializable; import java.util.Arrays; import static org.geoserver.catalog.Predicates.*; import java.util.Iterator; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; import org.apache.wicket.model.IModel; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.LayerGroupInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.Predicates; import org.geoserver.catalog.PublishedInfo; import org.geoserver.catalog.util.CloseableIterator; import org.geoserver.catalog.util.CloseableIteratorAdapter; import org.geoserver.web.wicket.GeoServerDataProvider; import org.opengis.filter.Filter; import org.opengis.filter.sort.SortBy; import com.google.common.base.Function; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.Lists; /** * Provides a filtered, sorted view over the catalog layers. * * @author Andrea Aime - OpenGeo */ @SuppressWarnings("serial") public class PreviewLayerProvider extends GeoServerDataProvider<PreviewLayer> { public static final long DEFAULT_CACHE_TIME = 1; public static final String KEY_SIZE = "key.size"; public static final String KEY_FULL_SIZE = "key.fullsize"; private final Cache<String,Integer> cache; private SizeCallable sizeCaller; private FullSizeCallable fullSizeCaller; public PreviewLayerProvider(){ super(); // Initialization of an inner cache in order to avoid to calculate two times // the size() method in a time minor than a second CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); cache = builder.expireAfterWrite(DEFAULT_CACHE_TIME, TimeUnit.SECONDS).build(); // Callable which internally calls the size method sizeCaller = new SizeCallable(); // Callable which internally calls the fullSize() method fullSizeCaller = new FullSizeCallable(); } public static final Property<PreviewLayer> TYPE = new BeanProperty<PreviewLayer>( "type", "type"); public static final AbstractProperty<PreviewLayer> NAME = new AbstractProperty<PreviewLayer>("name") { @Override public Object getPropertyValue(PreviewLayer item) { if (item.layerInfo != null) { return item.layerInfo.prefixedName(); } if (item.groupInfo != null) { return item.groupInfo.prefixedName(); } return null; } }; public static final Property<PreviewLayer> TITLE = new BeanProperty<PreviewLayer>( "title", "title"); public static final Property<PreviewLayer> ABSTRACT = new BeanProperty<PreviewLayer>( "abstract", "abstract", false); public static final Property<PreviewLayer> KEYWORDS = new BeanProperty<PreviewLayer>( "keywords", "keywords", false); public static final Property<PreviewLayer> COMMON = new PropertyPlaceholder<PreviewLayer>( "commonFormats"); public static final Property<PreviewLayer> ALL = new PropertyPlaceholder<PreviewLayer>( "allFormats"); public static final List<Property<PreviewLayer>> PROPERTIES = Arrays.asList(TYPE, TITLE, NAME, ABSTRACT, KEYWORDS, COMMON, ALL); @Override protected List<PreviewLayer> getItems() { // forced to implement this method as its abstract in the super class throw new UnsupportedOperationException( "This method should not be being called! " + "We use the catalog streaming API"); } @Override protected List<Property<PreviewLayer>> getProperties() { return PROPERTIES; } @Override protected IModel<PreviewLayer> newModel(PreviewLayer object) { return new PreviewLayerModel(object); } @Override public long size() { try { return cache.get(KEY_SIZE, sizeCaller); } catch (ExecutionException e) { throw new RuntimeException(e); } } private int sizeInternal() { Filter filter = getFilter(); int result = getCatalog().count(PublishedInfo.class, filter); return result; } @Override public int fullSize() { try { return cache.get(KEY_FULL_SIZE, fullSizeCaller); } catch (ExecutionException e) { throw new RuntimeException(e); } } private int fullSizeInternal() { Filter filter = Predicates.acceptAll(); return getCatalog().count(PublishedInfo.class, filter); } @Override public Iterator<PreviewLayer> iterator(final long first, final long count) { Iterator<PreviewLayer> iterator = filteredItems(first, count); if (iterator instanceof CloseableIterator) { // don't know how to force wicket to close the iterator, lets return // a copy. Shouldn't be much overhead as we're paging try { return Lists.newArrayList(iterator).iterator(); } finally { CloseableIteratorAdapter.close(iterator); } } else { return iterator; } } /** * Returns the requested page of layer objects after applying any keyword * filtering set on the page */ @SuppressWarnings("resource") private Iterator<PreviewLayer> filteredItems(long first, long count) { final Catalog catalog = getCatalog(); // global sorting final SortParam sort = getSort(); final Property<PreviewLayer> property = getProperty(sort); SortBy sortOrder = null; if (sort != null) { if (property instanceof BeanProperty) { final String sortProperty = ((BeanProperty<PreviewLayer>) property) .getPropertyPath(); sortOrder = sortBy(sortProperty, sort.isAscending()); } else if (property == NAME) { sortOrder = sortBy("prefixedName", sort.isAscending()); } } Filter filter = getFilter(); CloseableIterator<PublishedInfo> pi = catalog.list(PublishedInfo.class, filter, (int) first, (int) count, sortOrder); return CloseableIteratorAdapter.transform(pi, new Function<PublishedInfo, PreviewLayer>() { @Override public PreviewLayer apply(PublishedInfo input) { if (input instanceof LayerInfo) { return new PreviewLayer((LayerInfo) input); } else if (input instanceof LayerGroupInfo) { return new PreviewLayer((LayerGroupInfo) input); } return null; } }); } @Override protected Filter getFilter() { Filter filter = super.getFilter(); // need to get only advertised and enabled layers Filter isLayerInfo = Predicates.isInstanceOf(LayerInfo.class); Filter isLayerGroupInfo = Predicates.isInstanceOf(LayerGroupInfo.class); Filter enabledFilter = Predicates.equal("resource.enabled", true); Filter storeEnabledFilter = Predicates.equal("resource.store.enabled", true); Filter advertisedFilter = Predicates.equal("resource.advertised", true); // return only layer groups that are not containers Filter nonContainerGroup = Predicates.or(Predicates.equal("mode", LayerGroupInfo.Mode.EO), Predicates.equal("mode", LayerGroupInfo.Mode.NAMED), Predicates.equal("mode", LayerGroupInfo.Mode.OPAQUE_CONTAINER), Predicates.equal("mode", LayerGroupInfo.Mode.SINGLE)); // Filter for the Layers Filter layerFilter = Predicates.and(isLayerInfo, enabledFilter, storeEnabledFilter, advertisedFilter); // Filter for the LayerGroups Filter layerGroupFilter = Predicates.and(isLayerGroupInfo, nonContainerGroup); // Or filter for merging them Filter orFilter = Predicates.or(layerFilter, layerGroupFilter); // And between the new filter and the initial filter return Predicates.and(filter, orFilter); } /** * Inner class which calls the sizeInternal() method * * @author Nicpla Lagomarsini geosolutions * */ class SizeCallable implements Callable<Integer>, Serializable { @Override public Integer call() throws Exception { return sizeInternal(); } } /** * Inner class which calls the fullsizeInternal() method * * @author Nicpla Lagomarsini geosolutions * */ class FullSizeCallable implements Callable<Integer>, Serializable { @Override public Integer call() throws Exception { return fullSizeInternal(); } } }