Skip to content

GSIP 69 Use Case Code Migration

Jody Garnett edited this page Jul 12, 2017 · 1 revision

GSIP 69 - Use Case Code Migration

Summary

In this section we will go through updating the Catalog client code identified as exemplary performance/scalability offenders in the Use Cases section, to the new API, in order to validate it in terms of usability.

{quote} Please not that all the usage of Guava utility classes is anecdotal implementation detail here. No such requirement exists at the API level. {quote}

  • Solving Use Case 1 : Lower the memory footprint and CPU utilization of SecureCatalogImpl *# Implement new methods : Implement new methods in SecureCatalogImpl in a way that the filtering of catalog objects not accessible to the current user is pushed back to the CatalogFacade, thus avoiding double creation of in-memory list of objects and wrapping all objects in a secure decorator just to throw away the ones not needed. Short version:

    import static org.geoserver.catalog.Predicates.*;

    class SecureCatalogImpl implements Catalog { .... @Override public int count(Class of, Filter filter) { Filter securityFilter = securityFilter(of, filter); final int count = delegate.count(of, securityFilter); return count; }

      @Override
      public <T extends CatalogInfo> T get(Class<T> type, Filter filter)
              throws IllegalArgumentException {
          Filter securityFilter = securityFilter(type, filter);
          T result = delegate.get(type, securityFilter);
          return result;
      }
    
      @Override
      public <T extends CatalogInfo> CloseableIterator<T> list(Class<T> of, Filter filter) {
          return list(of, filter, null, null, null);
      }
    
      @Override
      public <T extends CatalogInfo> CloseableIterator<T> list(Class<T> of, Filter filter,
              Integer offset, Integer count, SortBy sortBy) {
    
          Filter securityFilter = securityFilter(of, filter);
    
          CloseableIterator<T> filtered;
          filtered = delegate.list(of, securityFilter, offset, count, sortBy);
    
          // create secured decorators on-demand
          final Function<T, T> securityWrapper = securityWrapper(of);
          final CloseableIterator<T> filteredWrapped;
          filteredWrapped = CloseableIteratorAdapter.transform(filtered, securityWrapper);
    
          return filteredWrapped;
      }
    
      /**
       * @return a Function that applies a security wrapper over the catalog
       *         object given to it as input
       */
      private <T extends CatalogInfo> Function<T, T> securityWrapper(final Class<T> forClass) {
        ...
      }
    
      /**
       * Returns a predicate that checks whether the current user has access to a given object of type
       * {@code infoType}.
       */
      private <T extends CatalogInfo> Filter securityFilter(final Class<T> infoType,
              final Filter filter) {
          ...
      }
    

    }

*# Leverage new API : Leverage new API in SecureCatalogImpl’s existing code so that current bulk query methods avoid double creation of a } and in-process filtering of current user’s accessible objects. Short version:

import static org.geoserver.catalog.Predicates.*;
class SecureCatalogImpl implements Catalog {
    ...
    //BEFORE
    public List<LayerInfo> getLayers() {
        return filterLayers(user(), delegate.getLayers());
    }
    //AFTER
    public List<LayerInfo> getLayers() {
        return filterLayers(acceptAll());
    }
    //BEFORE
    public List<LayerInfo> getLayers(ResourceInfo resource) {
        return filterLayers(user(), delegate.getLayers(unwrap(resource)));
    }
    //AFTER
    public List<LayerInfo> getLayers(ResourceInfo resource) {
        return filterLayers(propertyEquals("resource.id", resource.getId()));
    }
    //BEFORE
    public List<LayerInfo> getLayers(StyleInfo style) {
        return filterLayers(user(), delegate.getLayers(style));
    }
    //AFTER
    public List<LayerInfo> getLayers(StyleInfo style) {
        Filter filter = or(
                             propertyEquals("defaultStyle.id", style.getId()),
                             propertyEquals("styles.id", style.getId()));
        return filterLayers(filter);
    }
    //BEFORE
    protected List<LayerInfo> filterLayers(Authentication user, 
                                           List<LayerInfo> layers) {
        List<LayerInfo> result = new ArrayList<LayerInfo>();
        for (LayerInfo original : layers) {
            LayerInfo secured = checkAccess(user, original);
            if (secured != null)
                result.add(secured);
        }
        return result;
    }
    //AFTER
    private List<LayerInfo> filterLayers(final Filter filter) {
        CloseableIterator<LayerInfo> iterator;
        iterator = list(LayerInfo.class, filter, null, null);
        try {
            return ImmutableList.copyOf(iterator);
        } finally {
            iterator.close();
        }
    }
    ...
}
  1. Solving Use Case 2 : Leverage Catalog filtering, sorting and paging on LayerPage

BEFORE

public class LayerProvider extends GeoServerDataProvider<LayerInfo> {
    @Override
    protected List<LayerInfo> getItems() {
        return getCatalog().getLayers();
    }   
}

AFTER

public class LayerProvider extends GeoServerDataProvider<LayerInfo> {

    @Override
    protected List<LayerInfo> getItems() {
        throw new UnsupportedOperationException(
                "This method should not be being called! "
              + "We use the catalog streaming API");
    }

    @Override
    public int size() {
        return getCatalog().count(LayerInfo.class, getFilter());
    }

    @Override
    public int fullSize() {
        return getCatalog().count(LayerInfo.class, acceptAll());
    }

    @Override
    public Iterator<LayerInfo> iterator(
                          final int first, final int count) {
        Iterator<LayerInfo> 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
     */
    private Iterator<LayerInfo> filteredItems(
                             Integer first, Integer count) {
        ...
    }

    private Filter getFilter() {
        ...
    }
}
  1. Solving Use Case 3 : Leverage Catalog filtering and sorting on WMS GetCapabilities generation.

BEFORE

        ...
        private void handleLayers() {
            start("Layer");

            final List<LayerInfo> layers;

            // filter the layers if a namespace filter has been set
            if (request.getNamespace() != null) {
                final List<LayerInfo> allLayers = wmsConfig.getLayers();
                layers = new ArrayList<LayerInfo>();
                String namespace = wmsConfig.getNamespaceByPrefix(
                                                 request.getNamespace());
                for (LayerInfo layer : allLayers) {
                    Name name = layer.getResource().getQualifiedName();
                    if (name.getNamespaceURI().equals(namespace)) {
                        layers.add(layer);
                    }
                }
            } else {
                layers = wmsConfig.getLayers();
            }
            ...
            handleRootBbox(layers);
            ...
            // now encode each layer individually
            LayerTree featuresLayerTree = new LayerTree(layers);
            handleLayerTree(featuresLayerTree);
            ...
            List<LayerGroupInfo> layerGroups = wmsConfig.getLayerGroups();
            handleLayerGroups(layerGroups.iterator());
            ...
            end("Layer");
        }
        private void handleLayerTree(final LayerTree layerTree) {
            ...
        }
    }

AFTER

        ...
        private void handleLayers() {
            start("Layer");

            //ask for enabled and advertised to start with
            Filter filter;
            {
                Filter enabled = equal("enabled", Boolean.TRUE);
                Filter advertised = equal("advertised", Boolean.TRUE);
                filter = Predicates.and(enabled, advertised);
            }

            // filter the layers if a namespace filter has been set
            if (request.getNamespace() != null) {
                //build a query predicate for the namespace prefix
                final String nsPrefix = request.getNamespace();
                final String nsProp = "resource.namespace.prefix";
                Filter equals = propertyEquals(nsProp, nsPrefix);
                filter = Predicates.and(filter, equals);
            }
            ...
            final Catalog catalog = wmsConfig.getCatalog();
            CloseableIterator<LayerInfo> layers;
            SortBy sortOrder = Predicates.sortBy("name", true);

            layers = catalog.list(LayerInfo.class, filter, null, null, sortOrder);
            try{
                handleRootBbox(layers);
            }finally{
                layers.close();
            }
            ...
            // now encode each layer individually
            layers = catalog.list(LayerInfo.class, filter);
            try {
                handleLayerTree(layers);
            } finally {
                layers.close();
            }

            final Filter lgFilter = acceptAll();
            CloseableIterator<LayerGroupInfo> layerGroups = catalog.list(LayerGroupInfo.class, lgFilter);
            try {
                handleLayerGroups(layerGroups);
            }finally{
                layerGroups.close();
            }

            end("Layer");
        }

Migrating code from [SecureCatalogImpl Use Case](GSIP 69 - Use Cases#SecureCatalogImpl)

org.geoserver.security.SecureCatalogImpl is a security decorator for org.geoserver.catalog.impl.CatalogImpl. So first step is to implement the new count and list methods in SecureCatalogImpl in a way that the hiding of inaccessible objects to the current user is pushed back to the catalog backend:

Implement new methods

...
import org.geoserver.catalog.Predicates;
import org.geoserver.platform.CloseableIterator;
import org.geoserver.platform.CloseableIteratorAdapter;
import com.google.common.base.Function;
class SecureCatalogImpl extends AbstractDecorator<Catalog> implements Catalog {
  ....
    @Override
    public <T extends CatalogInfo> int count(Class<T> of, Filter filter) {
        Filter securityFilter = securityFilter(of, filter);
        final int count = delegate.count(of, securityFilter);
        return count;
    }

    @Override
    public <T extends CatalogInfo> T get(Class<T> type, Filter filter)
            throws IllegalArgumentException {

        Filter securityFilter = securityFilter(type, filter);
        T result = delegate.get(type, securityFilter);
        return result;
    }

    @Override
    public <T extends CatalogInfo> CloseableIterator<T> list(Class<T> of,
            Filter filter) {
        return list(of, filter, null, null, null);
    }

    @Override
    public <T extends CatalogInfo> CloseableIterator<T> list(Class<T> of,
            Filter filter, Integer offset, Integer count, SortBy sortBy) {

        Filter securityFilter = securityFilter(of, filter);

        CloseableIterator<T> filtered;
        filtered = delegate.list(of, securityFilter, offset, count, sortBy);

        // create secured decorators on-demand
        final Function<T, T> securityWrapper = securityWrapper(of);
        final CloseableIterator<T> filteredWrapped;
        filteredWrapped = CloseableIteratorAdapter.transform(filtered,
                securityWrapper);

        return filteredWrapped;
    }

    /**
     * @return a Function that applies a security wrapper over the catalog
     *         object given to it as input
     * @see #checkAccess(Authentication, CatalogInfo)
     */
    private <T extends CatalogInfo> Function<T, T> securityWrapper(
            final Class<T> forClass) {

        final Authentication user = user();
        return new Function<T, T>() {

            @Override
            public T apply(T input) {
                T checked = checkAccess(user, input);
                return checked;
            }
        };
    }

    /**
     * Returns a predicate that checks whether the current user has access to a
     * given object of type {@code infoType}.
     * <p>
     * IMPLEMENTATION NOTE: the predicate returned evaluates in-process and
     * hence can't be encoded to the catalog's native query language, if any. It
     * calls {@link #buildWrapperPolicy(Authentication, CatalogInfo)} to check
     * if the returned access level is not "hidden" on a case by case basis.
     * Perhaps, the check for whether a given resource is accessible to the
     * current user can be encoded as a "well known" predicate that uses one or
     * a combination of the property equals/isnull/contains/exists verbs in the
     * {@link Predicates} utility. I (GR), at the time of writing, don't know
     * how to do that, so any help would be much appreciated. Nonetheless, this
     * predicate is meant to be "and'ed" with any other predicate this catalog
     * wrapper is called with, giving the Catalog backend a chance to at least
     * encode the "well known" part of the resulting filter, and separate out
     * the in-process evaluation of access credentials from the construction of
     * the security wrapper for each object.
     * 
     * @return a catalog Predicate that evaluates if an object of the required
     *         type is accessible to the given user
     */
    private <T extends CatalogInfo> Filter securityFilter(
            final Class<T> infoType, final Filter filter) {

        final Authentication user = user();
        if (isAdmin(user)) {
            // no need to check for credentials if user is _the_ administrator
            return filter;
        }

        if (StyleInfo.class.isAssignableFrom(infoType)
                || MapInfo.class.isAssignableFrom(infoType)) {
            // these kind of objects are not secured
            return filter;
        }

        org.opengis.filter.expression.Function visible = new InternalVolatileFunction() {
            /**
             * Returns {@code false} if the catalog info shall be hidden, {@code true} otherwise.
             */
            @Override
            public Object evaluate(Object object) {
                WrapperPolicy policy = buildWrapperPolicy(user, (CatalogInfo)object);
                AccessLevel accessLevel = policy.getAccessLevel();
                boolean visible = !AccessLevel.HIDDEN.equals(accessLevel);
                return Boolean.valueOf(visible);
            }
        };

        FilterFactory factory = Predicates.factory;

        // create a filter combined with the security credentials check
        Filter securityFilter = factory.equals(factory.literal(Boolean.TRUE),
                visible);
        return Predicates.and(filter, securityFilter);
    }

    /**
     * Checks if the current user is authenticated and is the administrator
     */
    private boolean isAdmin(Authentication authentication) {
        if (authentication == null || !authentication.isAuthenticated())
            return false;

        for (GrantedAuthority authority : authentication.getAuthorities()) {
            if ("ROLE_ADMINISTRATOR".equals(authority.getAuthority()))
                return true;
        }
        return false;
    }
}

Leverage new API in existing code

The following code snippets show a before/after scenario for SecureCatalogImpl. We’ll see on the before snippet that the three getLayers methods force the creation of two lists of layers, one by the decorated catalog, and another one the filterLayers method, so that in-process filtering of layers accessible to the current user is performed.

In the after snippet we see how the same three methods call the filterLayers method with an org.geoserver.catalog.Predicate filter instead, which in turn calls to the new list method obtaining an Iterator over the back-end filtered layers. So the back-end CatalogFacade is free not to create a full list of resources, as well as it’s obliged to perform the filtering of layers for us.

BEFORE

class SecureCatalogImpl extends AbstractDecorator<Catalog> implements Catalog {
    ...
    @Override
    public List<LayerInfo> getLayers() {
        return filterLayers(user(), delegate.getLayers());
    }

    @Override
    public List<LayerInfo> getLayers(ResourceInfo resource) {
        return filterLayers(user(), delegate.getLayers(unwrap(resource)));
    }

    @Override
    public List<LayerInfo> getLayers(StyleInfo style) {
        return filterLayers(user(), delegate.getLayers(style));
    }

    protected List<LayerInfo> filterLayers(Authentication user, List<LayerInfo> layers) {
        List<LayerInfo> result = new ArrayList<LayerInfo>();
        for (LayerInfo original : layers) {
            LayerInfo secured = checkAccess(user, original);
            if (secured != null)
                result.add(secured);
        }
        return result;
    }
    ...
}

AFTER

import static org.geoserver.catalog.Predicates.*;
...
class SecureCatalogImpl extends AbstractDecorator<Catalog> implements Catalog {
    ...
    @Override
    public List<LayerInfo> getLayers() {
        return filterLayers(acceptAll());
    }

    @Override
    public List<LayerInfo> getLayers(ResourceInfo resource) {
        return filterLayers(Predicates.equal("resource.id", resource.getId()));
    }

    @Override
    public List<LayerInfo> getLayers(StyleInfo style) {
        String id = style.getId();
        Filter filter = or(Predicates.equal("defaultStyle.id", id),
                Predicates.equal("styles.id", id));

        return filterLayers(filter);
    }

    private List<LayerInfo> filterLayers(final Predicate<LayerInfo> filter) {
        CloseableIterator<LayerInfo> iterator;
        iterator = list(LayerInfo.class, filter);
        try {
            return ImmutableList.copyOf(iterator);
        } finally {
            iterator.close();
        }
    }

    @Override
    public <T extends CatalogInfo> CloseableIterator<T> list(Class<T> of,
            Filter filter) {
        return list(of, filter, null, null, null);
    }

    @Override
    public <T extends CatalogInfo> CloseableIterator<T> list(Class<T> of,
            Filter filter, Integer offset, Integer count, SortBy sortBy) {

        Filter securityFilter = securityFilter(of, filter);

        CloseableIterator<T> filtered;
        filtered = delegate.list(of, securityFilter, offset, count, sortBy);

        // create secured decorators on-demand
        final Function<T, T> securityWrapper = securityWrapper(of);
        final CloseableIterator<T> filteredWrapped;
        filteredWrapped = CloseableIteratorAdapter.transform(filtered,
                securityWrapper);

        return filteredWrapped;
    }

    /**
     * @return a Function that applies a security wrapper over the catalog
     *         object given to it as input
     * @see #checkAccess(Authentication, CatalogInfo)
     */
    private <T extends CatalogInfo> Function<T, T> securityWrapper(
            final Class<T> forClass) {

        final Authentication user = user();
        return new Function<T, T>() {

            @Override
            public T apply(T input) {
                T checked = checkAccess(user, input);
                return checked;
            }
        };
    }

    /**
     * Returns a predicate that checks whether the current user has access to a
     * given object of type {@code infoType}.
     * <p>
     * IMPLEMENTATION NOTE: the predicate returned evaluates in-process and
     * hence can't be encoded to the catalog's native query language, if any. It
     * calls {@link #buildWrapperPolicy(Authentication, CatalogInfo)} to check
     * if the returned access level is not "hidden" on a case by case basis.
     * Perhaps, the check for whether a given resource is accessible to the
     * current user can be encoded as a "well known" predicate that uses one or
     * a combination of the property equals/isnull/contains/exists verbs in the
     * {@link Predicates} utility. I (GR), at the time of writing, don't know
     * how to do that, so any help would be much appreciated. Nonetheless, this
     * predicate is meant to be "and'ed" with any other predicate this catalog
     * wrapper is called with, giving the Catalog backend a chance to at least
     * encode the "well known" part of the resulting filter, and separate out
     * the in-process evaluation of access credentials from the construction of
     * the security wrapper for each object.
     * 
     * @return a catalog Predicate that evaluates if an object of the required
     *         type is accessible to the given user
     */
    private <T extends CatalogInfo> Filter securityFilter(
            final Class<T> infoType, final Filter filter) {

        final Authentication user = user();
        if (isAdmin(user)) {
            // no need to check for credentials if user is _the_ administrator
            return filter;
        }

        if (StyleInfo.class.isAssignableFrom(infoType)
                || MapInfo.class.isAssignableFrom(infoType)) {
            // these kind of objects are not secured
            return filter;
        }

        org.opengis.filter.expression.Function visible = new InternalVolatileFunction() {
            /**
             * Returns {@code false} if the catalog info shall be hidden, {@code true} otherwise.
             */
            @Override
            public Object evaluate(Object object) {
                WrapperPolicy policy = buildWrapperPolicy(user, (CatalogInfo)object);
                AccessLevel accessLevel = policy.getAccessLevel();
                boolean visible = !AccessLevel.HIDDEN.equals(accessLevel);
                return Boolean.valueOf(visible);
            }
        };

        FilterFactory factory = Predicates.factory;

        // create a filter combined with the security credentials check
        Filter securityFilter = factory.equals(factory.literal(Boolean.TRUE),
                visible);
        return Predicates.and(filter, securityFilter);
    }

    /**
     * Checks if the current user is authenticated and is the administrator
     */
    private boolean isAdmin(Authentication authentication) {
        if (authentication == null || !authentication.isAuthenticated())
            return false;

        for (GrantedAuthority authority : authentication.getAuthorities()) {
            if ("ROLE_ADMINISTRATOR".equals(authority.getAuthority()))
                return true;
        }
        return false;
    }
}

Migrating code from [Wicket User Interface Use Case](GSIP 69 - Use Cases#wicket)

In the following snippets we see a before/after scenario for the layers list Wicket page. The page, based on GeoServer’s GeoServerDataProvider template class, supports listing, sorting, and filtering configured layers. In its current state it leverages on the super class’ default behavior which is getting the full list of resources and applying in-process filtering and sorting as appropriate, as well as using the full list of resources to count the total and filtered number of objects.

The AFTER snippet breaks this dependency on default behavior to leverage the new Catalog API to obtain element count, filtering, paging, and sorting directly from the Catalog. Rest assured this could be abstracted out to a super class that does this all for all pages that present a list of Catalog objects (such as stores, workspaces, styles, layergroups, and workspaces).

BEFORE

public class LayerProvider extends GeoServerDataProvider<LayerInfo> {
    @Override
    protected List<LayerInfo> getItems() {
        return getCatalog().getLayers();
    }   
}

AFTER

package org.geoserver.web.data.layer;
import static org.geoserver.catalog.Predicates.*;
/**
 * Provides a filtered, sorted view over the catalog layers.
 * <p>
 * <!-- Implementation detail: This class overrides the following methods in
 * order to leverage the Catalog filtering and paging support:
 * <ul>
 * <li> {@link #size()}: in order to call {@link Catalog#count(Class, Predicate)}
 * with any filter criteria set on the page
 * <li> {@link #fullSize()}: in order to call
 * {@link Catalog#count(Class, Predicate)} with {@link Predicates#acceptAll()}
 * <li>{@link #iterator}: in order to ask the catalog for paged and sorted
 * contents directly through
 * {@link Catalog#list(Class, Predicate, Integer, Integer, OrderBy)}
 * <li> {@link #getItems()} throws an unsupported operation exception, as given
 * the above it should not be called
 * </ul>
 * -->
 */
public class LayerProvider extends GeoServerDataProvider<LayerInfo> {

    @Override
    protected List<LayerInfo> 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
    public int size() {
        Predicate<LayerInfo> filter = getFilter();
        int count = getCatalog().count(LayerInfo.class, filter);
        return count;
    }

    @Override
    public int fullSize() {
        Predicate<LayerInfo> filter = Predicates.acceptAll();
        int count = getCatalog().count(LayerInfo.class, filter);
        return count;
    }

    @Override
    public Iterator<LayerInfo> iterator(
                          final int first, final int count) {
        Iterator<LayerInfo> 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
     */
    private Iterator<LayerInfo> filteredItems(Integer first, Integer count) {
        final Catalog catalog = getCatalog();

        // global sorting
        final SortParam sort = getSort();
        final Property<LayerInfo> property = getProperty(sort);

        SortBy sortOrder = null;
        if (sort != null) {
            if (property instanceof BeanProperty) {
                final String sortProperty = ((BeanProperty<LayerInfo>) property)
                        .getPropertyPath();
                sortOrder = sortBy(sortProperty, sort.isAscending());
            } else if (property == ENABLED) {
                sortOrder = sortBy("enabled", sort.isAscending());
            }
        }

        final Filter filter = getFilter();
        // our already filtered and closeable iterator
        Iterator<LayerInfo> items = catalog.list(LayerInfo.class, filter,
                first, count, sortOrder);

        return items;
    }

    private Filter getFilter() {
        final String[] keywords = getKeywords();
        Filter filter = acceptAll();
        if (null != keywords) {
            for (String keyword : keywords) {
                Filter propContains = Predicates.fullTextSearch(keyword);
                // chain the filters together
                if (Filter.INCLUDE == filter) {
                    filter = propContains;
                } else {
                    filter = or(filter, propContains);
                }
            }
        }
        return filter;
    }

Migrating code from [WMS GetCapabilities Use Case](GSIP 69 - Use Cases#wmscapabilities)

BEFORE

public class Capabilities_1_3_0_Transformer extends TransformerBase {
    ...
    private static class Capabilities_1_3_0_Translator 
                                          extends TranslatorSupport {
        ...
        private void handleLayers() {
            start("Layer");

            final List<LayerInfo> layers;

            // filter the layers if a namespace filter has been set
            if (request.getNamespace() != null) {
                final List<LayerInfo> allLayers = wmsConfig.getLayers();
                layers = new ArrayList<LayerInfo>();

                String namespace = wmsConfig.getNamespaceByPrefix(
                                                 request.getNamespace());
                for (LayerInfo layer : allLayers) {
                    Name name = layer.getResource().getQualifiedName();
                    if (name.getNamespaceURI().equals(namespace)) {
                        layers.add(layer);
                    }
                }
            } else {
                layers = wmsConfig.getLayers();
            }
            ...
            handleRootBbox(layers);
            ...
            // now encode each layer individually
            LayerTree featuresLayerTree = new LayerTree(layers);
            handleLayerTree(featuresLayerTree);
            ...
            List<LayerGroupInfo> layerGroups = wmsConfig.getLayerGroups();
            handleLayerGroups(layerGroups.iterator());
            ...
            end("Layer");
        }

        private void handleLayerTree(final LayerTree layerTree) {
            ...
        }
    }
}

AFTER

import static org.geoserver.catalog.Predicates.*;
public class Capabilities_1_3_0_Transformer extends TransformerBase {
    ...
    private static class Capabilities_1_3_0_Translator 
                                          extends TranslatorSupport {
        ...
        private void handleLayers() {
            start("Layer");

            //ask for enabled and advertised to start with
            Filter filter;
            {
                Filter enabled = equal("enabled", Boolean.TRUE);
                Filter advertised = equal("advertised", Boolean.TRUE);
                filter = and(enabled, advertised);
            }

            // filter the layers if a namespace filter has been set
            if (request.getNamespace() != null) {
                //build a query predicate for the namespace prefix
                final String nsPrefix = request.getNamespace();
                final String nsProp = "resource.namespace.prefix";
                Filter equals = equal(nsProp, nsPrefix);
                filter = and(filter, equals);
            }

            final Catalog catalog = wmsConfig.getCatalog();
            ...
            CloseableIterator<LayerInfo> layers;
            layers = catalog.list(LayerInfo.class, filter);
            try{
                handleRootBbox(layers);
            }finally{
                layers.close();
            }
            ...
            // now encode each layer individually
            SortBy layerOrder = asc("name");
            layers = catalog.list(LayerInfo.class, filter, null, null, layerOrder);
            try {
                handleLayerTree(layers);
            } finally {
                layers.close();
            }

            CloseableIterator<LayerGroupInfo> layerGroups;
            {
                final Filter lgFilter = Predicates.acceptAll();
                SortBy layerGroupOrder = asc("name");
                layerGroups = catalog.list(LayerGroupInfo.class, lgFilter, null, null,
                        layerGroupOrder);
            }
            try {
                handleLayerGroups(layerGroups);
            } catch (FactoryException e) {
                throw new RuntimeException("Can't obtain Envelope of Layer-Groups: "
                        + e.getMessage(), e);
            } catch (TransformException e) {
                throw new RuntimeException("Can't obtain Envelope of Layer-Groups: "
                        + e.getMessage(), e);
            }finally{
                layerGroups.close();
            }

            end("Layer");
        }

        private void handleLayerTree(final Iterator<LayerInfo> layers) {
            // Build a LayerTree only for the layers that have a wms path set.
            // Process the ones that
            // don't first
            LayerTree nestedLayers = new LayerTree();

            // handle non nested layers
            while (layers.hasNext()) {
                LayerInfo layer = layers.next();
                if (!isExposable(layer)) {
                    continue;
                }
                final String path = layer.getPath();
                if (path != null && path.length() > 0 && !"/".equals(path)) {
                    nestedLayers.add(layer);
                    continue;
                }

                try {
                    handleLayer(layer);
                } catch (Exception e) {
                    // report what layer we failed on to help the admin locate
                    // and fix it
                    throw new ServiceException(
                            "Error occurred trying to write out metadata for layer: "
                                    + layer.getName(), e);
                }
            }

            // handle nested layers
            handleLayerTree(nestedLayers);
        }

        private void handleLayerTree(final LayerTree layerTree) {
            ...
        }
    }
}

{html}


{html} {quote} Return to the [main proposal page|GSIP 69 - Catalog scalability enhancements] {quote}
Clone this wiki locally