-
Notifications
You must be signed in to change notification settings - Fork 1
GSIP 69 Use Case Code Migration
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 inSecureCatalogImpl
in a way that the filtering of catalog objects not accessible to the current user is pushed back to theCatalogFacade
, 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();
}
}
...
}
-
Solving Use Case
2
: Leverage Catalog filtering, sorting and paging on
LayerPage
public class LayerProvider extends GeoServerDataProvider<LayerInfo> {
@Override
protected List<LayerInfo> getItems() {
return getCatalog().getLayers();
}
}
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() {
...
}
}
- Solving Use Case 3 : Leverage Catalog filtering and sorting on WMS GetCapabilities generation.
...
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) {
...
}
}
...
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");
}
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:
...
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;
}
}
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.
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;
}
...
}
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;
}
}
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).
public class LayerProvider extends GeoServerDataProvider<LayerInfo> {
@Override
protected List<LayerInfo> getItems() {
return getCatalog().getLayers();
}
}
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;
}
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) {
...
}
}
}
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}
©2020 Open Source Geospatial Foundation