From 223d9f3fb084e36430029201bc99c2a2fccfe552 Mon Sep 17 00:00:00 2001 From: Nuno Oliveira Date: Sun, 23 Jul 2017 00:22:24 +0100 Subject: [PATCH 01/15] Add WFS time versioning --- src/community/nsg-profile/pom.xml | 86 ++++++ .../org/geoserver/nsg/TimeVersioning.java | 58 +++++ .../geoserver/nsg/TimeVersioningCallback.java | 244 ++++++++++++++++++ .../nsg/VersioningFilterAdapter.java | 110 ++++++++ .../nsg/web/WfsVersioningConfig.html | 32 +++ .../nsg/web/WfsVersioningConfig.java | 153 +++++++++++ .../resources/GeoServerApplication.properties | 4 + .../src/main/resources/applicationContext.xml | 19 ++ .../java/org/geoserver/nsg/TestsUtils.java | 39 +++ .../org/geoserver/nsg/TimeVersioningTest.java | 99 +++++++ .../nsg/web/WfsVersioningConfigTest.java | 106 ++++++++ .../org/geoserver/nsg/versioned.properties | 6 + .../test/resources/requests/get_request_1.xml | 13 + .../resources/requests/insert_request_1.xml | 22 ++ .../resources/requests/update_request_1.xml | 18 ++ src/community/pom.xml | 7 + src/community/release/ext-nsg-profile.xml | 16 ++ .../geoserver/catalog/FeatureTypeInfo.java | 7 + .../catalog/impl/FeatureTypeInfoImpl.java | 33 ++- .../decorators/DecoratingFeatureTypeInfo.java | 9 + .../decorators/SecuredFeatureTypeInfo.java | 3 +- src/web/app/pom.xml | 10 + .../security/web/user/UserPanel.java | 2 +- .../java/org/geoserver/wfs/GetFeature.java | 16 ++ .../org/geoserver/wfs/GetFeatureCallback.java | 20 ++ .../org/geoserver/wfs/GetFeatureContext.java | 44 ++++ .../wfs/GetFeatureContextBuilder.java | 54 ++++ .../geoserver/wfs/InsertElementHandler.java | 41 ++- .../java/org/geoserver/wfs/Transaction.java | 17 ++ .../geoserver/wfs/TransactionCallback.java | 49 ++++ .../org/geoserver/wfs/TransactionContext.java | 49 ++++ .../wfs/TransactionContextBuilder.java | 61 +++++ 32 files changed, 1425 insertions(+), 22 deletions(-) create mode 100644 src/community/nsg-profile/pom.xml create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioning.java create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioningCallback.java create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/VersioningFilterAdapter.java create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.html create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.java create mode 100644 src/community/nsg-profile/src/main/resources/GeoServerApplication.properties create mode 100644 src/community/nsg-profile/src/main/resources/applicationContext.xml create mode 100644 src/community/nsg-profile/src/test/java/org/geoserver/nsg/TestsUtils.java create mode 100644 src/community/nsg-profile/src/test/java/org/geoserver/nsg/TimeVersioningTest.java create mode 100644 src/community/nsg-profile/src/test/java/org/geoserver/nsg/web/WfsVersioningConfigTest.java create mode 100644 src/community/nsg-profile/src/test/resources/org/geoserver/nsg/versioned.properties create mode 100644 src/community/nsg-profile/src/test/resources/requests/get_request_1.xml create mode 100644 src/community/nsg-profile/src/test/resources/requests/insert_request_1.xml create mode 100644 src/community/nsg-profile/src/test/resources/requests/update_request_1.xml create mode 100644 src/community/release/ext-nsg-profile.xml create mode 100644 src/wfs/src/main/java/org/geoserver/wfs/GetFeatureCallback.java create mode 100644 src/wfs/src/main/java/org/geoserver/wfs/GetFeatureContext.java create mode 100644 src/wfs/src/main/java/org/geoserver/wfs/GetFeatureContextBuilder.java create mode 100644 src/wfs/src/main/java/org/geoserver/wfs/TransactionCallback.java create mode 100644 src/wfs/src/main/java/org/geoserver/wfs/TransactionContext.java create mode 100644 src/wfs/src/main/java/org/geoserver/wfs/TransactionContextBuilder.java diff --git a/src/community/nsg-profile/pom.xml b/src/community/nsg-profile/pom.xml new file mode 100644 index 00000000000..b9e26ef3fbc --- /dev/null +++ b/src/community/nsg-profile/pom.xml @@ -0,0 +1,86 @@ + + + 4.0.0 + + org.geoserver + community + 2.12-SNAPSHOT + + org.geoserver.community + gs-nsg-profile + jar + 2.12-SNAPSHOT + NSG Profile + + + + org.geoserver + gs-main + tests + test + + + org.geoserver + gs-wfs + ${project.version} + + + org.geoserver.web + gs-web-core + ${project.version} + + + + org.geoserver + gs-wfs + tests + ${project.version} + + + org.geoserver.web + gs-web-core + ${project.version} + tests + test + + + + org.hamcrest + hamcrest-library + test + + + org.springframework + spring-test + test + + + junit + junit + test + + + javax.servlet + javax.servlet-api + test + + + + + + ${basedir}/src/main/java + + **/*.html + + + + ${basedir}/src/main/resources + + **/* + + + + + + diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioning.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioning.java new file mode 100644 index 00000000000..ad891d29929 --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioning.java @@ -0,0 +1,58 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.nsg; + +import org.geoserver.catalog.FeatureTypeInfo; + +public final class TimeVersioning { + + public static final String ENABLED_KEY = "TIME_VERSIONING_ENABLED"; + public static final String ID_PROPERTY_KEY = "TIME_VERSIONING_ID_PROPERTY"; + public static final String TIME_PROPERTY_KEY = "TIME_VERSIONING_TIME_PROPERTY"; + + public static void enable(FeatureTypeInfo featureTypeInfo, String idProperty, String timeProperty) { + featureTypeInfo.putParameter(ENABLED_KEY, true); + featureTypeInfo.putParameter(ID_PROPERTY_KEY, idProperty); + featureTypeInfo.putParameter(TIME_PROPERTY_KEY, timeProperty); + } + + public static void disable(FeatureTypeInfo featureTypeInfo) { + featureTypeInfo.putParameter(ENABLED_KEY, false); + featureTypeInfo.putParameter(ID_PROPERTY_KEY, null); + featureTypeInfo.putParameter(TIME_PROPERTY_KEY, null); + } + + public static boolean isEnabled(FeatureTypeInfo featureTypeInfo) { + return featureTypeInfo.getParameter(ENABLED_KEY, Boolean.class, false); + } + + public static String getIdPropertyName(FeatureTypeInfo featureTypeInfo) { + String idPropertyName = featureTypeInfo.getParameter(ID_PROPERTY_KEY, String.class, null); + if (idPropertyName == null) { + throw new RuntimeException("No id property name was provided."); + } + return idPropertyName; + } + + public static String getTimePropertyName(FeatureTypeInfo featureTypeInfo) { + String timePropertyName = featureTypeInfo.getParameter(TIME_PROPERTY_KEY, String.class, null); + if (timePropertyName == null) { + throw new RuntimeException("No time property name was provided."); + } + return timePropertyName; + } + + public static void setEnable(FeatureTypeInfo featureTypeInfo, boolean enable) { + featureTypeInfo.putParameter(ENABLED_KEY, enable); + } + + public static void setIdAttribute(FeatureTypeInfo featureTypeInfo, String idAttributeName) { + featureTypeInfo.putParameter(ID_PROPERTY_KEY, idAttributeName); + } + + public static void setTimeAttribute(FeatureTypeInfo featureTypeInfo, String timeAttributeName) { + featureTypeInfo.putParameter(TIME_PROPERTY_KEY, timeAttributeName); + } +} diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioningCallback.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioningCallback.java new file mode 100644 index 00000000000..ee87f12f628 --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioningCallback.java @@ -0,0 +1,244 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.nsg; + +import org.geoserver.catalog.Catalog; +import org.geoserver.catalog.FeatureTypeInfo; +import org.geoserver.platform.GeoServerExtensions; +import org.geoserver.wfs.GetFeatureCallback; +import org.geoserver.wfs.GetFeatureContext; +import org.geoserver.wfs.InsertElementHandler; +import org.geoserver.wfs.TransactionCallback; +import org.geoserver.wfs.TransactionContext; +import org.geoserver.wfs.TransactionContextBuilder; +import org.geoserver.wfs.request.Insert; +import org.geoserver.wfs.request.RequestObject; +import org.geoserver.wfs.request.Update; +import org.geotools.data.DataUtilities; +import org.geotools.data.FeatureStore; +import org.geotools.data.Query; +import org.geotools.data.simple.SimpleFeatureCollection; +import org.geotools.data.simple.SimpleFeatureIterator; +import org.geotools.data.simple.SimpleFeatureStore; +import org.geotools.factory.CommonFactoryFinder; +import org.geotools.factory.GeoTools; +import org.geotools.feature.NameImpl; +import org.geotools.feature.simple.SimpleFeatureBuilder; +import org.geotools.util.Converters; +import org.opengis.feature.simple.SimpleFeature; +import org.opengis.feature.type.AttributeDescriptor; +import org.opengis.feature.type.FeatureType; +import org.opengis.feature.type.Name; +import org.opengis.filter.Filter; +import org.opengis.filter.FilterFactory2; +import org.opengis.filter.sort.SortBy; +import org.opengis.filter.sort.SortOrder; + +import javax.xml.namespace.QName; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +final class TimeVersioningCallback implements GetFeatureCallback, TransactionCallback { + + private static final FilterFactory2 FILTER_FACTORY = CommonFactoryFinder.getFilterFactory2(GeoTools.getDefaultHints()); + + private final Catalog catalog; + + TimeVersioningCallback(Catalog catalog) { + this.catalog = catalog; + } + + @Override + public GetFeatureContext beforeQuerying(GetFeatureContext context) { + if (!isWfs20(context.getRequest())) { + return context; + } + FeatureTypeInfo featureTypeInfo = context.getFeatureTypeInfo(); + if (!TimeVersioning.isEnabled(featureTypeInfo)) { + // time versioning is not enabled for this feature type or is not a WFS 2.0 request + return context; + } + VersioningFilterAdapter.adapt(featureTypeInfo, context.getQuery().getFilter()); + SortBy sort = FILTER_FACTORY.sort(TimeVersioning.getTimePropertyName(featureTypeInfo), SortOrder.DESCENDING); + SortBy[] sorts = context.getQuery().getSortBy(); + if (sorts == null) { + sorts = new SortBy[]{sort}; + } else { + sorts = Arrays.copyOf(sorts, sorts.length + 1); + sorts[sorts.length - 1] = sort; + } + context.getQuery().setSortBy(sorts); + return context; + } + + @Override + public TransactionContext beforeHandlerExecution(TransactionContext context) { + if (!isWfs20(context.getRequest())) { + return context; + } + if (context.getElement() instanceof Update) { + Insert insert = buildInsertForUpdate(context); + InsertElementHandler handler = GeoServerExtensions.bean(InsertElementHandler.class); + return new TransactionContextBuilder() + .withContext(context) + .withElement(insert) + .withHandler(handler).build(); + } + if (context.getElement() instanceof Insert) { + Insert insert = (Insert) context.getElement(); + for (Object element : insert.getFeatures()) { + if (element instanceof SimpleFeature) { + setTimeAttribute((SimpleFeature) element); + } + } + } + return context; + } + + @Override + public TransactionContext beforeInsertFeatures(TransactionContext context) { + return context; + } + + @Override + public TransactionContext beforeUpdateFeatures(TransactionContext context) { + return context; + } + + @Override + public TransactionContext beforeDeleteFeatures(TransactionContext context) { + return context; + } + + @Override + public TransactionContext beforeReplaceFeatures(TransactionContext context) { + return context; + } + + private void setTimeAttribute(SimpleFeature feature) { + FeatureType featureType = feature.getFeatureType(); + FeatureTypeInfo featureTypeInfo = getFeatureTypeInfo(featureType); + if (TimeVersioning.isEnabled(featureTypeInfo)) { + String timePropertyName = TimeVersioning.getTimePropertyName(featureTypeInfo); + AttributeDescriptor attributeDescriptor = feature.getType().getDescriptor(timePropertyName); + Object timeValue = Converters.convert(new Date(), attributeDescriptor.getType().getBinding()); + feature.setAttribute(timePropertyName, timeValue); + } + } + + private SimpleFeatureCollection getTransactionFeatures(TransactionContext context) { + QName typeName = context.getElement().getTypeName(); + Filter filter = context.getElement().getFilter(); + FeatureTypeInfo featureTypeInfo = getFeatureTypeInfo(new NameImpl(typeName)); + SimpleFeatureStore store = getTransactionStore(context); + try { + Query query = new Query(); + query.setFilter(VersioningFilterAdapter.adapt(featureTypeInfo, filter)); + SortBy sort = FILTER_FACTORY.sort(TimeVersioning.getTimePropertyName(featureTypeInfo), SortOrder.DESCENDING); + query.setSortBy(new SortBy[]{sort}); + return store.getFeatures(query); + } catch (Exception exception) { + throw new RuntimeException(String.format( + "Error getting features of type '%s'.", typeName), exception); + } + } + + private Comparator buildFeatureTimeComparator(FeatureTypeInfo featureTypeInfo) { + String timePropertyName = TimeVersioning.getTimePropertyName(featureTypeInfo); + return (featureA, featureB) -> { + Date timeA = Converters.convert(featureA.getAttribute(timePropertyName), Date.class); + Date timeB = Converters.convert(featureB.getAttribute(timePropertyName), Date.class); + if (timeA == null) { + return -1; + } + return timeA.compareTo(timeB); + }; + } + + private List getOnlyRecentFeatures(SimpleFeatureCollection features, FeatureTypeInfo featureTypeInfo) { + String idPropertyName = TimeVersioning.getIdPropertyName(featureTypeInfo); + Map> featuresIndexedById = new HashMap<>(); + SimpleFeatureIterator iterator = features.features(); + while (iterator.hasNext()) { + SimpleFeature feature = iterator.next(); + Object id = feature.getAttribute(idPropertyName); + List existing = featuresIndexedById.computeIfAbsent(id, key -> new ArrayList<>()); + existing.add(feature); + } + Comparator comparator = buildFeatureTimeComparator(featureTypeInfo); + List finalFeatures = new ArrayList<>(); + featuresIndexedById.values().forEach(indexed -> { + indexed.sort(comparator); + SimpleFeature feature = indexed.get(0); + SimpleFeatureBuilder builder = new SimpleFeatureBuilder(feature.getFeatureType()); + builder.init(feature); + finalFeatures.add(builder.buildFeature(null)); + }); + return finalFeatures; + } + + private Insert buildInsertForUpdate(TransactionContext context) { + Update update = (Update) context.getElement(); + FeatureTypeInfo featureTypeInfo = getFeatureTypeInfo(new NameImpl(update.getTypeName())); + SimpleFeatureCollection features = getTransactionFeatures(context); + List recent = getOnlyRecentFeatures(features, featureTypeInfo); + List newFeatures = recent.stream() + .map(this::prepareInsertFeature).collect(Collectors.toList()); + return new UpdateInsert(context.getRequest(), newFeatures); + } + + private SimpleFeature prepareInsertFeature(SimpleFeature feature) { + SimpleFeatureBuilder builder = new SimpleFeatureBuilder(feature.getFeatureType()); + builder.init(feature); + SimpleFeature versionedFeature = builder.buildFeature(null); + setTimeAttribute(versionedFeature); + return versionedFeature; + } + + private FeatureTypeInfo getFeatureTypeInfo(FeatureType featureType) { + Name featureTypeName = featureType.getName(); + return getFeatureTypeInfo(featureTypeName); + } + + private FeatureTypeInfo getFeatureTypeInfo(Name featureTypeName) { + FeatureTypeInfo featureTypeInfo = catalog.getFeatureTypeByName(featureTypeName); + if (featureTypeInfo == null) { + throw new RuntimeException(String.format( + "Couldn't find feature type info ''%s.", featureTypeName)); + } + return featureTypeInfo; + } + + private SimpleFeatureStore getTransactionStore(TransactionContext context) { + QName typeName = context.getElement().getTypeName(); + FeatureStore store = (FeatureStore) context.getFeatureStores().get(typeName); + return DataUtilities.simple(store); + } + + private boolean isWfs20(RequestObject request) { + return true; + } + + private static final class UpdateInsert extends Insert { + + private final List features; + + protected UpdateInsert(RequestObject request, List features) { + super(request.getAdaptee()); + this.features = features; + } + + @Override + public List getFeatures() { + return features; + } + } +} diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/VersioningFilterAdapter.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/VersioningFilterAdapter.java new file mode 100644 index 00000000000..6a0b1c68222 --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/VersioningFilterAdapter.java @@ -0,0 +1,110 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.nsg; + +import org.geoserver.catalog.FeatureTypeInfo; +import org.geotools.factory.CommonFactoryFinder; +import org.geotools.factory.GeoTools; +import org.geotools.filter.visitor.DuplicatingFilterVisitor; +import org.opengis.filter.Filter; +import org.opengis.filter.FilterFactory; +import org.opengis.filter.FilterFactory2; +import org.opengis.filter.Id; +import org.opengis.filter.expression.Expression; +import org.opengis.filter.identity.Identifier; +import org.opengis.filter.identity.ResourceId; +import org.opengis.filter.sort.SortOrder; + +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +final class VersioningFilterAdapter extends DuplicatingFilterVisitor { + + private final String idPropertyName; + private final String timePropertyName; + + private VersioningFilterAdapter(FeatureTypeInfo featureTypeInfo) { + this.idPropertyName = TimeVersioning.getIdPropertyName(featureTypeInfo); + this.timePropertyName = TimeVersioning.getTimePropertyName(featureTypeInfo); + } + + @Override + public Object visit(Id filter, Object extraData) { + FilterFactory filterFactory = getFactory(extraData); + Set ids = filter.getIdentifiers(); + Set finalIds = new HashSet<>(); + Filter versioningFilter = null; + for (Identifier id : ids) { + if (id instanceof ResourceId) { + Filter newFilter = buildVersioningFilter(filterFactory, (ResourceId) id); + versioningFilter = addFilter(filterFactory, versioningFilter, newFilter); + } else { + finalIds.add(id); + } + } + if (finalIds.isEmpty()) { + return versioningFilter; + } + Filter newIdFilter = getFactory(extraData).id(finalIds); + if (versioningFilter != null) { + return filterFactory.and(newIdFilter, versioningFilter); + } + return newIdFilter; + } + + private Filter buildVersioningFilter(FilterFactory filterFactory, ResourceId resourceId) { + Filter idFilter = buildIdFilter(filterFactory, resourceId.getID()); + Filter timeFilter = buildTimeFilter(filterFactory, resourceId.getStartTime(), resourceId.getEndTime()); + if (idFilter != null && timeFilter != null) { + return filterFactory.and(idFilter, timeFilter); + } + if (idFilter != null) { + return idFilter; + } + if (timeFilter != null) { + return timeFilter; + } + return null; + } + + private Filter buildIdFilter(FilterFactory factory, String id) { + if (id == null) { + return null; + } + return factory.equals(factory.property(idPropertyName), factory.literal(id)); + } + + private Filter buildTimeFilter(FilterFactory filterFactory, Date start, Date end) { + Expression timeProperty = filterFactory.property(timePropertyName); + Expression startLiteral = filterFactory.literal(start); + Expression endLiteral = filterFactory.literal(end); + Filter after = filterFactory.after(timeProperty, startLiteral); + Filter before = filterFactory.before(timeProperty, endLiteral); + if (start != null && end != null) { + return filterFactory.and(after, before); + } + if (start != null) { + return after; + } + if (end != null) { + return before; + } + return null; + } + + private Filter addFilter(FilterFactory filterFactory, Filter versioningFilter, Filter filter) { + if (versioningFilter != null) { + return filterFactory.and(versioningFilter, filter); + } + return filter; + } + + static Filter adapt(FeatureTypeInfo featureTypeInfo, Filter filter) { + String timePropertyName = TimeVersioning.getTimePropertyName(featureTypeInfo); + VersioningFilterAdapter adapter = new VersioningFilterAdapter(featureTypeInfo); + return (Filter) filter.accept(adapter, null); + } +} diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.html b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.html new file mode 100644 index 00000000000..b67d24989c7 --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.html @@ -0,0 +1,32 @@ + + + +
+ +

+ WFS Versioning +

+
    +
  • +
    + + +
    +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+ + diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.java new file mode 100644 index 00000000000..8ef574e602c --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.java @@ -0,0 +1,153 @@ +/* (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.nsg.web; + +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; +import org.apache.wicket.ajax.markup.html.form.AjaxCheckBox; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.form.CheckBox; +import org.apache.wicket.markup.html.form.DropDownChoice; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.StringResourceModel; +import org.geoserver.catalog.FeatureTypeInfo; +import org.geoserver.catalog.LayerInfo; +import org.geoserver.nsg.TimeVersioning; +import org.geoserver.web.publish.PublishedConfigurationPanel; + +import java.sql.Timestamp; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +public class WfsVersioningConfig extends PublishedConfigurationPanel { + + public WfsVersioningConfig(String id, IModel model) { + super(id, model); + // get the needed information from the model + FeatureTypeInfo featureTypeInfo = getFeatureTypeInfo(model); + boolean isVersioningActivated = TimeVersioning.isEnabled(featureTypeInfo); + String idAttributeName = isVersioningActivated ? TimeVersioning.getIdPropertyName(featureTypeInfo) : null; + String timeAttributeName = isVersioningActivated ? TimeVersioning.getTimePropertyName(featureTypeInfo) : null; + List attributesNames = getAttributesNames(featureTypeInfo); + List timeAttributesNames = getTimeAttributesNames(featureTypeInfo); + // create dropdown choice for the id attribute name + DropDownChoice idAttributeChoice = new DropDownChoice<>("idAttributeChoice", + new Model<>(idAttributeName), attributesNames); + idAttributeChoice.add(new AjaxFormComponentUpdatingBehavior("change") { + @Override + protected void onUpdate(AjaxRequestTarget target) { + String selected = idAttributeChoice.getModel().getObject(); + TimeVersioning.setIdAttribute(featureTypeInfo, selected); + } + }); + idAttributeChoice.setOutputMarkupId(true); + idAttributeChoice.setOutputMarkupPlaceholderTag(true); + idAttributeChoice.setRequired(true); + idAttributeChoice.setVisible(isVersioningActivated); + add(idAttributeChoice); + // add label for id attribute name dropdown choice + Label idAttributeChoiceLabel = new Label("idAttributeChoiceLabel", + new StringResourceModel("WfsVersioningConfig.idAttributeChoiceLabel")); + idAttributeChoiceLabel.setOutputMarkupId(true); + idAttributeChoiceLabel.setOutputMarkupPlaceholderTag(true); + idAttributeChoiceLabel.setVisible(isVersioningActivated); + add(idAttributeChoiceLabel); + // create dropdown choice for the time attribute name + DropDownChoice timeAttributeChoice = new DropDownChoice<>("timeAttributeChoice", + new Model<>(timeAttributeName), timeAttributesNames); + timeAttributeChoice.add(new AjaxFormComponentUpdatingBehavior("change") { + @Override + protected void onUpdate(AjaxRequestTarget target) { + String selected = timeAttributeChoice.getModel().getObject(); + TimeVersioning.setTimeAttribute(featureTypeInfo, selected); + } + }); + timeAttributeChoice.setOutputMarkupId(true); + timeAttributeChoice.setOutputMarkupPlaceholderTag(true); + timeAttributeChoice.setRequired(true); + timeAttributeChoice.setVisible(isVersioningActivated); + add(timeAttributeChoice); + // add label for id attribute name dropdown choice + Label timeAttributeChoiceLabel = new Label("timeAttributeChoiceLabel", + new StringResourceModel("WfsVersioningConfig.timeAttributeChoiceLabel")); + timeAttributeChoiceLabel.setOutputMarkupId(true); + timeAttributeChoiceLabel.setOutputMarkupPlaceholderTag(true); + timeAttributeChoiceLabel.setVisible(isVersioningActivated); + add(timeAttributeChoiceLabel); + // checkbox for activating versioning + CheckBox versioningActivateCheckBox = new AjaxCheckBox("versioningActivateCheckBox", + new Model<>(isVersioningActivated)) { + @Override + protected void onUpdate(AjaxRequestTarget target) { + boolean checked = getModelObject(); + if (checked) { + // activate versioning attributes selection + idAttributeChoice.setVisible(true); + idAttributeChoiceLabel.setVisible(true); + timeAttributeChoice.setVisible(true); + timeAttributeChoiceLabel.setVisible(true); + // enable time versioning + TimeVersioning.setEnable(featureTypeInfo, true); + } else { + // deactivate versioning attributes selection + idAttributeChoice.setVisible(false); + idAttributeChoiceLabel.setVisible(false); + timeAttributeChoice.setVisible(false); + timeAttributeChoiceLabel.setVisible(false); + // disable time versioning + TimeVersioning.setEnable(featureTypeInfo, false); + } + // update the dropdown choices and labels + target.add(idAttributeChoice); + target.add(idAttributeChoiceLabel); + target.add(timeAttributeChoice); + target.add(timeAttributeChoiceLabel); + } + }; + if (isVersioningActivated) { + versioningActivateCheckBox.setModelObject(true); + } + versioningActivateCheckBox.setEnabled(!timeAttributesNames.isEmpty()); + add(versioningActivateCheckBox); + // add versioning activating checkbox label + Label versioningActivateCheckBoxLabel = new Label("versioningActivateCheckBoxLabel", + new StringResourceModel("WfsVersioningConfig.versioningActivateCheckBoxLabel")); + add(versioningActivateCheckBoxLabel); + } + + private List getAttributesNames(FeatureTypeInfo featureTypeInfo) { + try { + return featureTypeInfo.getFeatureType().getDescriptors().stream() + .map(attribute -> attribute.getName().getLocalPart()) + .collect(Collectors.toList()); + } catch (Exception exception) { + throw new RuntimeException(String.format( + "Error processing attributes of feature type '%s'.", + featureTypeInfo.getName()), exception); + } + } + + private List getTimeAttributesNames(FeatureTypeInfo featureTypeInfo) { + try { + return featureTypeInfo.getFeatureType().getDescriptors().stream().filter(attribute -> { + Class binding = attribute.getType().getBinding(); + return Long.class.isAssignableFrom(binding) + || Date.class.isAssignableFrom(binding) + || Timestamp.class.isAssignableFrom(binding); + }).map(attribute -> attribute.getName().getLocalPart()).collect(Collectors.toList()); + } catch (Exception exception) { + throw new RuntimeException(String.format( + "Error processing attributes of feature type '%s'.", + featureTypeInfo.getName()), exception); + } + } + + private FeatureTypeInfo getFeatureTypeInfo(IModel model) { + return (FeatureTypeInfo) model.getObject().getResource(); + } +} diff --git a/src/community/nsg-profile/src/main/resources/GeoServerApplication.properties b/src/community/nsg-profile/src/main/resources/GeoServerApplication.properties new file mode 100644 index 00000000000..e8f4ddacbf6 --- /dev/null +++ b/src/community/nsg-profile/src/main/resources/GeoServerApplication.properties @@ -0,0 +1,4 @@ +WfsVersioningConfig.wfsVersioning=WFS Versioning +WfsVersioningConfig.versioningActivateCheckBoxLabel=Activate Versioning +WfsVersioningConfig.idAttributeChoiceLabel=Id Attribute +WfsVersioningConfig.timeAttributeChoiceLabel=Time Attribute \ No newline at end of file diff --git a/src/community/nsg-profile/src/main/resources/applicationContext.xml b/src/community/nsg-profile/src/main/resources/applicationContext.xml new file mode 100644 index 00000000000..a33c1190b39 --- /dev/null +++ b/src/community/nsg-profile/src/main/resources/applicationContext.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + org.geoserver.catalog.FeatureTypeInfo + + + + diff --git a/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TestsUtils.java b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TestsUtils.java new file mode 100644 index 00000000000..653ac8a8e4b --- /dev/null +++ b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TestsUtils.java @@ -0,0 +1,39 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.nsg; + +import org.geoserver.catalog.Catalog; +import org.geoserver.catalog.FeatureTypeInfo; +import org.geoserver.util.IOUtils; + +import java.io.InputStream; + +import static org.geoserver.nsg.TimeVersioning.disable; +import static org.geoserver.nsg.TimeVersioning.enable; + +final class TestsUtils { + + private TestsUtils() { + } + + static String readResource(String resourceName) { + try (InputStream input = TestsUtils.class.getResourceAsStream(resourceName)) { + return IOUtils.toString(input); + } catch (Exception exception) { + throw new RuntimeException(String.format("Error reading resource '%s'.", resourceName)); + } + } + + static void updateFeatureTypeTimeVersioning(Catalog catalog, String featureTypeName, + boolean enabled, String idProperty, String timeProperty) { + FeatureTypeInfo featureType = catalog.getFeatureTypeByName(featureTypeName); + if (enabled) { + enable(featureType, idProperty, timeProperty); + } else { + disable(featureType); + } + catalog.save(featureType); + } +} diff --git a/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TimeVersioningTest.java b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TimeVersioningTest.java new file mode 100644 index 00000000000..758d1fa3370 --- /dev/null +++ b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TimeVersioningTest.java @@ -0,0 +1,99 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.nsg; + +import org.custommonkey.xmlunit.SimpleNamespaceContext; +import org.custommonkey.xmlunit.XMLUnit; +import org.custommonkey.xmlunit.XpathEngine; +import org.geoserver.data.test.MockData; +import org.geoserver.data.test.SystemTestData; +import org.geoserver.test.GeoServerSystemTestSupport; +import org.geotools.geometry.jts.ReferencedEnvelope; +import org.geotools.referencing.crs.DefaultGeographicCRS; +import org.junit.Before; +import org.junit.Test; +import org.w3c.dom.Document; + +import javax.xml.namespace.QName; +import java.util.HashMap; +import java.util.Map; + +public final class TimeVersioningTest extends GeoServerSystemTestSupport { + + private XpathEngine WFS20_XPATH_ENGINE; + + @Override + protected void onSetUp(SystemTestData testData) throws Exception { + super.setUpTestData(testData); + // create bounding box definitions + ReferencedEnvelope envelope = new ReferencedEnvelope(-5, -5, 5, 5, DefaultGeographicCRS.WGS84); + Map properties = new HashMap<>(); + properties.put(SystemTestData.LayerProperty.LATLON_ENVELOPE, envelope); + properties.put(SystemTestData.LayerProperty.ENVELOPE, envelope); + properties.put(SystemTestData.LayerProperty.SRS, 4326); + // create versioned layer + QName versionedLayerName = new QName(MockData.DEFAULT_URI, "versioned", MockData.DEFAULT_PREFIX); + testData.addVectorLayer(versionedLayerName, properties, "versioned.properties", getClass(), getCatalog()); + // instantiate xpath engine + buildXpathEngine( + "wfs", "http://www.opengis.net/wfs/2.0", + "gml", "http://www.opengis.net/gml/3.2"); + } + + @Before + public void beforeTest() { + // activate versioning for versioned layer + TestsUtils.updateFeatureTypeTimeVersioning(getCatalog(), "gs:versioned", true, "ID", "TIME"); + } + + @Test + public void testGetFeatureVersioned() throws Exception { + Document result = postAsDOM("wfs", TestsUtils.readResource("/requests/get_request_1.xml")); + System.out.println("aa"); + } + + @Test + public void testInsertVersionedFeature() throws Exception { + Document result = postAsDOM("wfs", TestsUtils.readResource("/requests/insert_request_1.xml")); + System.out.println("aa"); + } + + @Test + public void testUpdateVersionedFeature() throws Exception { + Document result = postAsDOM("wfs", TestsUtils.readResource("/requests/update_request_1.xml")); + System.out.println("aa"); + } + + /** + * Helper method that builds a xpath engine using some predefined + * namespaces and all the catalog namespaces. The provided namespaces + * will be added overriding any existing namespace. + */ + private XpathEngine buildXpathEngine(String... namespaces) { + // build xpath engine + XpathEngine xpathEngine = XMLUnit.newXpathEngine(); + Map finalNamespaces = new HashMap<>(); + // add common namespaces + finalNamespaces.put("ows", "http://www.opengis.net/ows"); + finalNamespaces.put("ogc", "http://www.opengis.net/ogc"); + finalNamespaces.put("xs", "http://www.w3.org/2001/XMLSchema"); + finalNamespaces.put("xsd", "http://www.w3.org/2001/XMLSchema"); + finalNamespaces.put("xlink", "http://www.w3.org/1999/xlink"); + finalNamespaces.put("xsi", "http://www.w3.org/2001/XMLSchema-instance"); + // add al catalog namespaces + getCatalog().getNamespaces().forEach(namespace -> + finalNamespaces.put(namespace.getPrefix(), namespace.getURI())); + // add provided namespaces + if (namespaces.length % 2 != 0) { + throw new RuntimeException("Invalid number of namespaces provided."); + } + for (int i = 0; i < namespaces.length; i += 2) { + finalNamespaces.put(namespaces[i], namespaces[i + 1]); + } + // add namespaces to the xpath engine + xpathEngine.setNamespaceContext(new SimpleNamespaceContext(finalNamespaces)); + return xpathEngine; + } +} diff --git a/src/community/nsg-profile/src/test/java/org/geoserver/nsg/web/WfsVersioningConfigTest.java b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/web/WfsVersioningConfigTest.java new file mode 100644 index 00000000000..efe29b00e6a --- /dev/null +++ b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/web/WfsVersioningConfigTest.java @@ -0,0 +1,106 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.nsg.web; + +import org.apache.wicket.markup.html.form.CheckBox; +import org.apache.wicket.util.tester.FormTester; +import org.geoserver.web.GeoServerWicketTestSupport; +import org.geoserver.web.publish.PublishedConfigurationPage; +import org.geoserver.wfs.GMLInfo; +import org.geoserver.wfs.WFSInfo; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +public class WfsVersioningConfigTest extends GeoServerWicketTestSupport { + + @Test + public void testPageStart() throws Exception { + WFSInfo wfs = getGeoServerApplication().getGeoServer().getService(WFSInfo.class); + + login(); + tester.startPage(PublishedConfigurationPage.class); + } + + /*@Test + public void testChangesToValues() throws Exception { + String testValue1 = "100", testValue2 = "0"; + WFSInfo wfs = getGeoServerApplication().getGeoServer().getService(WFSInfo.class); + login(); + tester.startPage(WFSAdminPage.class); + FormTester ft = tester.newFormTester("form"); + ft.setValue("maxNumberOfFeaturesForPreview", (String)testValue1); + ft.submit("submit"); + wfs = getGeoServerApplication().getGeoServer().getService(WFSInfo.class); + assertEquals("testValue1 = 100", 100, (int)wfs.getMaxNumberOfFeaturesForPreview()); + tester.startPage(WFSAdminPage.class); + ft = tester.newFormTester("form"); + ft.setValue("maxNumberOfFeaturesForPreview", (String)testValue2); + ft.submit("submit"); + wfs = getGeoServerApplication().getGeoServer().getService(WFSInfo.class); + assertEquals("testValue2 = 0", 0, (int)wfs.getMaxNumberOfFeaturesForPreview()); + } + + @Test + public void testGML32ForceMimeType() throws Exception { + // make sure GML MIME type overriding is disabled + WFSInfo info = getGeoServer().getService(WFSInfo.class); + GMLInfo gmlInfo = info.getGML().get(WFSInfo.Version.V_20); + gmlInfo.setMimeTypeToForce(null); + getGeoServer().save(info); + // login with administrator privileges + login(); + // start WFS service administration page + tester.startPage(new WFSAdminPage()); + // check that GML MIME type overriding is disabled + tester.assertComponent("form:gml32:forceGmlMimeType", CheckBox.class); + CheckBox checkbox = (CheckBox) tester.getComponentFromLastRenderedPage("form:gml32:forceGmlMimeType"); + assertThat(checkbox.getModelObject(), is(false)); + // MIME type drop down choice should be invisible + tester.assertInvisible("form:gml32:mimeTypeToForce"); + // activate MIME type overriding by clicking in the checkbox + FormTester formTester = tester.newFormTester("form"); + formTester.setValue("gml32:forceGmlMimeType", true); + tester.executeAjaxEvent("form:gml32:forceGmlMimeType", "click"); + formTester = tester.newFormTester("form"); + formTester.submit("submit"); + // GML MIME typing overriding should be activated now + tester.startPage(new WFSAdminPage()); + assertThat(checkbox.getModelObject(), is(true)); + tester.assertVisible("form:gml32:mimeTypeToForce"); + // WFS global service configuration should have been updated too + info = getGeoServer().getService(WFSInfo.class); + gmlInfo = info.getGML().get(WFSInfo.Version.V_20); + assertThat(gmlInfo.getMimeTypeToForce().isPresent(), is(true)); + // select text / xml as MIME type to force + formTester = tester.newFormTester("form"); + formTester.select("gml32:mimeTypeToForce", 2); + tester.executeAjaxEvent("form:gml32:mimeTypeToForce", "change"); + formTester = tester.newFormTester("form"); + formTester.submit("submit"); + // WFS global service configuration should be forcing text / xml + info = getGeoServer().getService(WFSInfo.class); + gmlInfo = info.getGML().get(WFSInfo.Version.V_20); + assertThat(gmlInfo.getMimeTypeToForce().isPresent(), is(true)); + assertThat(gmlInfo.getMimeTypeToForce().get(), is("text/xml")); + // deactivate GML MIME type overriding by clicking in the checkbox + tester.startPage(new WFSAdminPage()); + formTester = tester.newFormTester("form"); + formTester.setValue("gml32:forceGmlMimeType", false); + tester.executeAjaxEvent("form:gml32:forceGmlMimeType", "click"); + formTester = tester.newFormTester("form"); + formTester.submit("submit"); + // GML MIME type overriding should be deactivated now + tester.startPage(new WFSAdminPage()); + assertThat(checkbox.getModelObject(), is(true)); + tester.assertInvisible("form:gml32:mimeTypeToForce"); + // WFS global service configuration should have been updated too + info = getGeoServer().getService(WFSInfo.class); + gmlInfo = info.getGML().get(WFSInfo.Version.V_20); + assertThat(gmlInfo.getMimeTypeToForce().isPresent(), is(false)); + }*/ +} diff --git a/src/community/nsg-profile/src/test/resources/org/geoserver/nsg/versioned.properties b/src/community/nsg-profile/src/test/resources/org/geoserver/nsg/versioned.properties new file mode 100644 index 00000000000..ad32708279a --- /dev/null +++ b/src/community/nsg-profile/src/test/resources/org/geoserver/nsg/versioned.properties @@ -0,0 +1,6 @@ +_=ID:String,NAME:String,TIME:java.sql.Timestamp,GEOMETRY:Geometry:srid=4326 +v.1=1|feature1|2017-06-25 14:30:00.0|POINT(-1 1) +v.2=1|feature1|2017-07-25 14:30:00.0|POINT(-1 -1) +v.3=1|feature1|2017-06-25 14:35:00.0|POINT(1 -1) +v.4=2|feature2|2017-04-10 13:30:00.0|POINT(-2 2) +v.5=2|feature2|2017-08-10 17:10:00.0|POINT(2 -2) \ No newline at end of file diff --git a/src/community/nsg-profile/src/test/resources/requests/get_request_1.xml b/src/community/nsg-profile/src/test/resources/requests/get_request_1.xml new file mode 100644 index 00000000000..bd69e4fdbeb --- /dev/null +++ b/src/community/nsg-profile/src/test/resources/requests/get_request_1.xml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/src/community/nsg-profile/src/test/resources/requests/insert_request_1.xml b/src/community/nsg-profile/src/test/resources/requests/insert_request_1.xml new file mode 100644 index 00000000000..c50429c8a86 --- /dev/null +++ b/src/community/nsg-profile/src/test/resources/requests/insert_request_1.xml @@ -0,0 +1,22 @@ + + + + + 1 + feature1 + 2018-01-25T13:30:00Z + + + 1.5 -1.5 + + + + + \ No newline at end of file diff --git a/src/community/nsg-profile/src/test/resources/requests/update_request_1.xml b/src/community/nsg-profile/src/test/resources/requests/update_request_1.xml new file mode 100644 index 00000000000..34ed22793ac --- /dev/null +++ b/src/community/nsg-profile/src/test/resources/requests/update_request_1.xml @@ -0,0 +1,18 @@ + + + + + NAME + feature2_updated + + + + + + \ No newline at end of file diff --git a/src/community/pom.xml b/src/community/pom.xml index f039b3bd6c5..327013956df 100644 --- a/src/community/pom.xml +++ b/src/community/pom.xml @@ -242,6 +242,7 @@ ows-simulate jdbc-metrics oseo + nsg-profile @@ -531,5 +532,11 @@ jdbc-metrics + + nsg-profile + + nsg-profile + + diff --git a/src/community/release/ext-nsg-profile.xml b/src/community/release/ext-nsg-profile.xml new file mode 100644 index 00000000000..11f21b27b56 --- /dev/null +++ b/src/community/release/ext-nsg-profile.xml @@ -0,0 +1,16 @@ + + nsg-profile + + zip + + false + + + release/target/dependency + + + gs-nsg-profile*.jar + + + + \ No newline at end of file diff --git a/src/main/src/main/java/org/geoserver/catalog/FeatureTypeInfo.java b/src/main/src/main/java/org/geoserver/catalog/FeatureTypeInfo.java index f780f80b9f5..3a2b0724b65 100644 --- a/src/main/src/main/java/org/geoserver/catalog/FeatureTypeInfo.java +++ b/src/main/src/main/java/org/geoserver/catalog/FeatureTypeInfo.java @@ -193,4 +193,11 @@ public interface FeatureTypeInfo extends ResourceInfo { void setCircularArcPresent(boolean arcsPresent); + default T getParameter(String parameterName, Class expectType, T fallback) { + return null; + } + + default void putParameter(String parameterName, Object parameterValue) { + // nothing to do + } } diff --git a/src/main/src/main/java/org/geoserver/catalog/impl/FeatureTypeInfoImpl.java b/src/main/src/main/java/org/geoserver/catalog/impl/FeatureTypeInfoImpl.java index 8fc00f86217..b1a5cd481c2 100644 --- a/src/main/src/main/java/org/geoserver/catalog/impl/FeatureTypeInfoImpl.java +++ b/src/main/src/main/java/org/geoserver/catalog/impl/FeatureTypeInfoImpl.java @@ -5,10 +5,6 @@ */ package org.geoserver.catalog.impl; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - import org.geoserver.catalog.AttributeTypeInfo; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.CatalogVisitor; @@ -20,11 +16,18 @@ import org.geotools.filter.text.cql2.CQLException; import org.geotools.filter.text.ecql.ECQL; import org.geotools.measure.Measure; +import org.geotools.util.Converters; import org.opengis.feature.Feature; import org.opengis.feature.type.FeatureType; import org.opengis.filter.Filter; import org.opengis.util.ProgressListener; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + @SuppressWarnings("serial") public class FeatureTypeInfoImpl extends ResourceInfoImpl implements FeatureTypeInfo { @@ -42,7 +45,9 @@ public class FeatureTypeInfoImpl extends ResourceInfoImpl implements boolean overridingServiceSRS; boolean skipNumberMatched = false; boolean circularArcPresent; - + + protected Map parameters = new HashMap<>(); + public boolean isCircularArcPresent() { return circularArcPresent; } @@ -236,5 +241,21 @@ public void setCqlFilter(String cqlFilter) { this.cqlFilter = cqlFilter; this.filter = null; } - + + @Override + public T getParameter(String parameterName, Class expectType, T fallback) { + if (parameters == null || parameters.isEmpty()) { + return fallback; + } + Object value = parameters.get(parameterName); + return value == null ? fallback : Converters.convert(value, expectType); + } + + @Override + public void putParameter(String parameterName, Object parameterValue) { + if (parameters == null) { + parameters = new HashMap<>(); + } + parameters.put(parameterName, parameterValue); + } } diff --git a/src/main/src/main/java/org/geoserver/security/decorators/DecoratingFeatureTypeInfo.java b/src/main/src/main/java/org/geoserver/security/decorators/DecoratingFeatureTypeInfo.java index 4f7da4f075c..40b50d647cc 100644 --- a/src/main/src/main/java/org/geoserver/security/decorators/DecoratingFeatureTypeInfo.java +++ b/src/main/src/main/java/org/geoserver/security/decorators/DecoratingFeatureTypeInfo.java @@ -329,4 +329,13 @@ public void setCqlFilter(String cqlFilter) { delegate.setCqlFilter(cqlFilter); } + @Override + public T getParameter(String parameterName, Class expectType, T fallback) { + return delegate.getParameter(parameterName, expectType, fallback); + } + + @Override + public void putParameter(String parameterName, Object parameterValue) { + delegate.putParameter(parameterName, parameterValue); + } } diff --git a/src/main/src/main/java/org/geoserver/security/decorators/SecuredFeatureTypeInfo.java b/src/main/src/main/java/org/geoserver/security/decorators/SecuredFeatureTypeInfo.java index bcc7c5ed884..9198b365cc3 100644 --- a/src/main/src/main/java/org/geoserver/security/decorators/SecuredFeatureTypeInfo.java +++ b/src/main/src/main/java/org/geoserver/security/decorators/SecuredFeatureTypeInfo.java @@ -111,5 +111,6 @@ public FeatureSource getFeatureSource(ProgressListener listener, Hints hints) public DataStoreInfo getStore() { return (DataStoreInfo) SecuredObjects.secure(delegate.getStore(), policy); } - + + } diff --git a/src/web/app/pom.xml b/src/web/app/pom.xml index 2930189850c..b92ca71d249 100644 --- a/src/web/app/pom.xml +++ b/src/web/app/pom.xml @@ -1522,5 +1522,15 @@ + + nsg-profile + + + org.geoserver.community + gs-nsg-profile + ${project.version} + + + diff --git a/src/web/security/core/src/main/java/org/geoserver/security/web/user/UserPanel.java b/src/web/security/core/src/main/java/org/geoserver/security/web/user/UserPanel.java index c10f9520432..fd5cb632beb 100644 --- a/src/web/security/core/src/main/java/org/geoserver/security/web/user/UserPanel.java +++ b/src/web/security/core/src/main/java/org/geoserver/security/web/user/UserPanel.java @@ -105,7 +105,7 @@ public void onClick() { }); //("addNew", NewUserPage.class)); - //add.setParameter(AbstractSecurityPage.ServiceNameKey, serviceName); + //add.putParameter(AbstractSecurityPage.ServiceNameKey, serviceName); add.setVisible(canCreateStore); // the removal button diff --git a/src/wfs/src/main/java/org/geoserver/wfs/GetFeature.java b/src/wfs/src/main/java/org/geoserver/wfs/GetFeature.java index 3e66b5a92ca..151444e61d0 100644 --- a/src/wfs/src/main/java/org/geoserver/wfs/GetFeature.java +++ b/src/wfs/src/main/java/org/geoserver/wfs/GetFeature.java @@ -34,6 +34,7 @@ import org.geoserver.ows.Request; import org.geoserver.ows.URLMangler.URLType; import org.geoserver.ows.util.KvpMap; +import org.geoserver.platform.GeoServerExtensions; import org.geoserver.wfs.request.FeatureCollectionResponse; import org.geoserver.wfs.request.GetFeatureRequest; import org.geoserver.wfs.request.Lock; @@ -506,6 +507,21 @@ public FeatureCollectionResponse run(GetFeatureRequest request) queryMaxFeatures, source, request, allPropNames.get(0), viewParam, joins, primaryTypeName, primaryAlias); + // extension point + GetFeatureContext context = new GetFeatureContextBuilder() + .withFeatureSource(source) + .withFeatureTypeInfo(meta) + .withQuery(gtQuery) + .withRequest(request).build(); + List callbacks = GeoServerExtensions.extensions(GetFeatureCallback.class); + for (GetFeatureCallback callback : callbacks) { + context = callback.beforeQuerying(context); + } + source = context.getFeatureSource(); + meta = context.getFeatureTypeInfo(); + gtQuery = context.getQuery(); + request = context.getRequest(); + LOGGER.fine("Query is " + query + "\n To gt2: " + gtQuery); FeatureCollection features = getFeatures(request, source, gtQuery); diff --git a/src/wfs/src/main/java/org/geoserver/wfs/GetFeatureCallback.java b/src/wfs/src/main/java/org/geoserver/wfs/GetFeatureCallback.java new file mode 100644 index 00000000000..c0b5c8ed58f --- /dev/null +++ b/src/wfs/src/main/java/org/geoserver/wfs/GetFeatureCallback.java @@ -0,0 +1,20 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.wfs; + +import org.geoserver.platform.ExtensionPriority; + +public interface GetFeatureCallback extends ExtensionPriority { + + default GetFeatureContext beforeQuerying(GetFeatureContext context) { + // by default nothing is done + return context; + } + + @Override + default int getPriority() { + return ExtensionPriority.LOWEST; + } +} diff --git a/src/wfs/src/main/java/org/geoserver/wfs/GetFeatureContext.java b/src/wfs/src/main/java/org/geoserver/wfs/GetFeatureContext.java new file mode 100644 index 00000000000..3f06b7c4325 --- /dev/null +++ b/src/wfs/src/main/java/org/geoserver/wfs/GetFeatureContext.java @@ -0,0 +1,44 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.wfs; + +import org.geoserver.catalog.FeatureTypeInfo; +import org.geoserver.wfs.request.GetFeatureRequest; +import org.geotools.data.FeatureSource; +import org.geotools.data.Query; +import org.opengis.feature.Feature; +import org.opengis.feature.type.FeatureType; + +public final class GetFeatureContext { + + private final GetFeatureRequest request; + private final FeatureSource featureSource; + private final Query query; + private final FeatureTypeInfo featureTypeInfo; + + GetFeatureContext(GetFeatureRequest request, FeatureSource featureSource, + Query query, FeatureTypeInfo featureTypeInfo) { + this.request = request; + this.featureSource = featureSource; + this.query = query; + this.featureTypeInfo = featureTypeInfo; + } + + public GetFeatureRequest getRequest() { + return request; + } + + public FeatureSource getFeatureSource() { + return featureSource; + } + + public Query getQuery() { + return query; + } + + public FeatureTypeInfo getFeatureTypeInfo() { + return featureTypeInfo; + } +} \ No newline at end of file diff --git a/src/wfs/src/main/java/org/geoserver/wfs/GetFeatureContextBuilder.java b/src/wfs/src/main/java/org/geoserver/wfs/GetFeatureContextBuilder.java new file mode 100644 index 00000000000..0c19934cf6c --- /dev/null +++ b/src/wfs/src/main/java/org/geoserver/wfs/GetFeatureContextBuilder.java @@ -0,0 +1,54 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.wfs; + +import org.geoserver.catalog.FeatureTypeInfo; +import org.geoserver.wfs.request.GetFeatureRequest; +import org.geotools.data.FeatureSource; +import org.geotools.data.Query; +import org.opengis.feature.Feature; +import org.opengis.feature.type.FeatureType; + +public final class GetFeatureContextBuilder { + + private GetFeatureRequest request; + private FeatureSource featureSource; + private Query query; + private FeatureTypeInfo featureTypeInfo; + + public GetFeatureContextBuilder() { + } + + public GetFeatureContextBuilder withRequest(GetFeatureRequest request) { + this.request = request; + return this; + } + + public GetFeatureContextBuilder withFeatureSource(FeatureSource featureSource) { + this.featureSource = featureSource; + return this; + } + + public GetFeatureContextBuilder withQuery(Query query) { + this.query = query; + return this; + } + + public GetFeatureContextBuilder withFeatureTypeInfo(FeatureTypeInfo featureTypeInfo) { + this.featureTypeInfo = featureTypeInfo; + return this; + } + + public GetFeatureContextBuilder withContext(GetFeatureContext context) { + return withRequest(context.getRequest()) + .withFeatureSource(context.getFeatureSource()) + .withQuery(context.getQuery()) + .withFeatureTypeInfo(context.getFeatureTypeInfo()); + } + + public GetFeatureContext build() { + return new GetFeatureContext(request, featureSource, query, featureTypeInfo); + } +} diff --git a/src/wfs/src/main/java/org/geoserver/wfs/InsertElementHandler.java b/src/wfs/src/main/java/org/geoserver/wfs/InsertElementHandler.java index 49ae7fa67be..e277dcdb16d 100644 --- a/src/wfs/src/main/java/org/geoserver/wfs/InsertElementHandler.java +++ b/src/wfs/src/main/java/org/geoserver/wfs/InsertElementHandler.java @@ -5,21 +5,11 @@ */ package org.geoserver.wfs; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.logging.Logger; - -import javax.xml.namespace.QName; - +import com.vividsolutions.jts.geom.Geometry; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.config.GeoServer; import org.geoserver.feature.ReprojectingFeatureCollection; +import org.geoserver.platform.GeoServerExtensions; import org.geoserver.wfs.request.Insert; import org.geoserver.wfs.request.TransactionElement; import org.geoserver.wfs.request.TransactionRequest; @@ -40,7 +30,16 @@ import org.opengis.filter.identity.FeatureId; import org.opengis.referencing.crs.CoordinateReferenceSystem; -import com.vividsolutions.jts.geom.Geometry; +import javax.xml.namespace.QName; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; /** @@ -71,7 +70,21 @@ public void checkValidity(TransactionElement element, Map callbacks = GeoServerExtensions.extensions(TransactionCallback.class); + try { for (Iterator it = elementHandlers.entrySet().iterator(); it.hasNext();) { Map.Entry entry = (Map.Entry) it.next(); TransactionElement element = (TransactionElement) entry.getKey(); TransactionElementHandler handler = (TransactionElementHandler) entry.getValue(); + TransactionContext context = new TransactionContextBuilder() + .withElement(element) + .withRequest(request) + .withFeatureStores(stores) + .withResponse(result) + .withHandler(handler).build(); + context = TransactionCallback.executeCallbacks( + context, callbacks, TransactionCallback::beforeHandlerExecution); + + element = context.getElement(); + request = context.getRequest(); + stores = context.getFeatureStores(); + result = context.getResponse(); + handler = context.getHandler(); + handler.execute(element, request, stores, result, multiplexer); } } catch (WFSTransactionException e) { diff --git a/src/wfs/src/main/java/org/geoserver/wfs/TransactionCallback.java b/src/wfs/src/main/java/org/geoserver/wfs/TransactionCallback.java new file mode 100644 index 00000000000..6193ab9dca4 --- /dev/null +++ b/src/wfs/src/main/java/org/geoserver/wfs/TransactionCallback.java @@ -0,0 +1,49 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.wfs; + +import java.util.List; + +public interface TransactionCallback { + + default TransactionContext beforeHandlerExecution(TransactionContext context) { + // by default nothing is done + return context; + } + + default TransactionContext beforeInsertFeatures(TransactionContext context) { + // by default nothing is done + return context; + } + + default TransactionContext beforeUpdateFeatures(TransactionContext context) { + // by default nothing is done + return context; + } + + default TransactionContext beforeDeleteFeatures(TransactionContext context) { + // by default nothing is done + return context; + } + + default TransactionContext beforeReplaceFeatures(TransactionContext context) { + // by default nothing is done + return context; + } + + @FunctionalInterface + interface Executor { + TransactionContext apply(TransactionCallback callback, TransactionContext context); + } + + static TransactionContext executeCallbacks(TransactionContext context, + List callbacks, + Executor executor) { + for (TransactionCallback callback : callbacks) { + context = executor.apply(callback, context); + } + return context; + } +} diff --git a/src/wfs/src/main/java/org/geoserver/wfs/TransactionContext.java b/src/wfs/src/main/java/org/geoserver/wfs/TransactionContext.java new file mode 100644 index 00000000000..469fcc1a558 --- /dev/null +++ b/src/wfs/src/main/java/org/geoserver/wfs/TransactionContext.java @@ -0,0 +1,49 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.wfs; + +import org.geoserver.wfs.request.TransactionElement; +import org.geoserver.wfs.request.TransactionRequest; +import org.geoserver.wfs.request.TransactionResponse; + +import java.util.Map; + +public final class TransactionContext { + + private final TransactionElement element; + private final TransactionRequest request; + private final Map featureStores; + private final TransactionResponse response; + private final TransactionElementHandler handler; + + TransactionContext(TransactionElement element, TransactionRequest request, Map featureStores, + TransactionResponse response, TransactionElementHandler handler) { + this.element = element; + this.request = request; + this.featureStores = featureStores; + this.response = response; + this.handler = handler; + } + + public TransactionElement getElement() { + return element; + } + + public TransactionRequest getRequest() { + return request; + } + + public Map getFeatureStores() { + return featureStores; + } + + public TransactionResponse getResponse() { + return response; + } + + public TransactionElementHandler getHandler() { + return handler; + } +} \ No newline at end of file diff --git a/src/wfs/src/main/java/org/geoserver/wfs/TransactionContextBuilder.java b/src/wfs/src/main/java/org/geoserver/wfs/TransactionContextBuilder.java new file mode 100644 index 00000000000..a1a2f018c96 --- /dev/null +++ b/src/wfs/src/main/java/org/geoserver/wfs/TransactionContextBuilder.java @@ -0,0 +1,61 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.wfs; + +import org.geoserver.wfs.request.TransactionElement; +import org.geoserver.wfs.request.TransactionRequest; +import org.geoserver.wfs.request.TransactionResponse; + +import java.util.Map; + +public final class TransactionContextBuilder { + + private TransactionElement element; + private TransactionRequest request; + private Map featureStores; + private TransactionResponse response; + private TransactionElementHandler handler; + + public TransactionContextBuilder() { + } + + public TransactionContextBuilder withElement(TransactionElement element) { + this.element = element; + return this; + } + + public TransactionContextBuilder withRequest(TransactionRequest request) { + this.request = request; + return this; + } + + public TransactionContextBuilder withFeatureStores(Map featureStores) { + this.featureStores = featureStores; + return this; + } + + public TransactionContextBuilder withResponse(TransactionResponse response) { + this.response = response; + return this; + } + + public TransactionContextBuilder withHandler(TransactionElementHandler handler) { + this.handler = handler; + return this; + } + + public TransactionContextBuilder withContext(TransactionContext context) { + this.element = context.getElement(); + this.featureStores = context.getFeatureStores(); + this.handler = context.getHandler(); + this.request = context.getRequest(); + this.response = context.getResponse(); + return this; + } + + public TransactionContext build() { + return new TransactionContext(element, request, featureStores, response, handler); + } +} From d4e1cae2896196e4db588c561d408178ea781e55 Mon Sep 17 00:00:00 2001 From: Nuno Oliveira Date: Thu, 3 Aug 2017 13:33:23 +0100 Subject: [PATCH 02/15] Add documentation index and reorganise packages --- doc/en/user/source/community/index.rst | 1 + doc/en/user/source/community/nsg-profile/index.rst | 12 ++++++++++++ .../nsg/{ => versioning}/TimeVersioning.java | 2 +- .../nsg/{ => versioning}/TimeVersioningCallback.java | 2 +- .../{ => versioning}/VersioningFilterAdapter.java | 6 +----- .../{ => versioning}/web/WfsVersioningConfig.html | 0 .../{ => versioning}/web/WfsVersioningConfig.java | 4 ++-- .../src/main/resources/applicationContext.xml | 2 +- .../src/test/java/org/geoserver/nsg/TestsUtils.java | 12 ++++++------ .../nsg/{ => versioning}/TimeVersioningTest.java | 3 ++- .../web/WfsVersioningConfigTest.java | 5 +---- 11 files changed, 28 insertions(+), 21 deletions(-) create mode 100644 doc/en/user/source/community/nsg-profile/index.rst rename src/community/nsg-profile/src/main/java/org/geoserver/nsg/{ => versioning}/TimeVersioning.java (98%) rename src/community/nsg-profile/src/main/java/org/geoserver/nsg/{ => versioning}/TimeVersioningCallback.java (99%) rename src/community/nsg-profile/src/main/java/org/geoserver/nsg/{ => versioning}/VersioningFilterAdapter.java (95%) rename src/community/nsg-profile/src/main/java/org/geoserver/nsg/{ => versioning}/web/WfsVersioningConfig.html (100%) rename src/community/nsg-profile/src/main/java/org/geoserver/nsg/{ => versioning}/web/WfsVersioningConfig.java (96%) rename src/community/nsg-profile/src/test/java/org/geoserver/nsg/{ => versioning}/TimeVersioningTest.java (98%) rename src/community/nsg-profile/src/test/java/org/geoserver/nsg/{ => versioning}/web/WfsVersioningConfigTest.java (96%) diff --git a/doc/en/user/source/community/index.rst b/doc/en/user/source/community/index.rst index f87a82b6b6b..9f9b639c386 100644 --- a/doc/en/user/source/community/index.rst +++ b/doc/en/user/source/community/index.rst @@ -45,3 +45,4 @@ officially part of the GeoServer releases. They are however built along with the onelogin/index wmts-multidimensional/index notification/index + nsg-profile/index diff --git a/doc/en/user/source/community/nsg-profile/index.rst b/doc/en/user/source/community/nsg-profile/index.rst new file mode 100644 index 00000000000..02490d6cbc5 --- /dev/null +++ b/doc/en/user/source/community/nsg-profile/index.rst @@ -0,0 +1,12 @@ +.. _community_nsg_profile: + +NSG Profile +=========== + + +Index Result Type +----------------- + + +PageResults Operation +--------------------- \ No newline at end of file diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioning.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/TimeVersioning.java similarity index 98% rename from src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioning.java rename to src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/TimeVersioning.java index ad891d29929..6abc73e47a4 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioning.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/TimeVersioning.java @@ -2,7 +2,7 @@ * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ -package org.geoserver.nsg; +package org.geoserver.nsg.versioning; import org.geoserver.catalog.FeatureTypeInfo; diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioningCallback.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/TimeVersioningCallback.java similarity index 99% rename from src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioningCallback.java rename to src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/TimeVersioningCallback.java index ee87f12f628..8b45559076a 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioningCallback.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/TimeVersioningCallback.java @@ -2,7 +2,7 @@ * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ -package org.geoserver.nsg; +package org.geoserver.nsg.versioning; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.FeatureTypeInfo; diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/VersioningFilterAdapter.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/VersioningFilterAdapter.java similarity index 95% rename from src/community/nsg-profile/src/main/java/org/geoserver/nsg/VersioningFilterAdapter.java rename to src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/VersioningFilterAdapter.java index 6a0b1c68222..42be444be5d 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/VersioningFilterAdapter.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/VersioningFilterAdapter.java @@ -2,20 +2,16 @@ * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ -package org.geoserver.nsg; +package org.geoserver.nsg.versioning; import org.geoserver.catalog.FeatureTypeInfo; -import org.geotools.factory.CommonFactoryFinder; -import org.geotools.factory.GeoTools; import org.geotools.filter.visitor.DuplicatingFilterVisitor; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory; -import org.opengis.filter.FilterFactory2; import org.opengis.filter.Id; import org.opengis.filter.expression.Expression; import org.opengis.filter.identity.Identifier; import org.opengis.filter.identity.ResourceId; -import org.opengis.filter.sort.SortOrder; import java.util.Date; import java.util.HashSet; diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.html b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/web/WfsVersioningConfig.html similarity index 100% rename from src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.html rename to src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/web/WfsVersioningConfig.html diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/web/WfsVersioningConfig.java similarity index 96% rename from src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.java rename to src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/web/WfsVersioningConfig.java index 8ef574e602c..f575a2c96ec 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/web/WfsVersioningConfig.java @@ -3,7 +3,7 @@ * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ -package org.geoserver.nsg.web; +package org.geoserver.nsg.versioning.web; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; @@ -16,7 +16,7 @@ import org.apache.wicket.model.StringResourceModel; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.LayerInfo; -import org.geoserver.nsg.TimeVersioning; +import org.geoserver.nsg.versioning.TimeVersioning; import org.geoserver.web.publish.PublishedConfigurationPanel; import java.sql.Timestamp; diff --git a/src/community/nsg-profile/src/main/resources/applicationContext.xml b/src/community/nsg-profile/src/main/resources/applicationContext.xml index a33c1190b39..875245237cd 100644 --- a/src/community/nsg-profile/src/main/resources/applicationContext.xml +++ b/src/community/nsg-profile/src/main/resources/applicationContext.xml @@ -9,7 +9,7 @@ - + org.geoserver.catalog.FeatureTypeInfo diff --git a/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TestsUtils.java b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TestsUtils.java index 653ac8a8e4b..3e64290a4fa 100644 --- a/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TestsUtils.java +++ b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TestsUtils.java @@ -10,15 +10,15 @@ import java.io.InputStream; -import static org.geoserver.nsg.TimeVersioning.disable; -import static org.geoserver.nsg.TimeVersioning.enable; +import static org.geoserver.nsg.versioning.TimeVersioning.disable; +import static org.geoserver.nsg.versioning.TimeVersioning.enable; -final class TestsUtils { +public final class TestsUtils { private TestsUtils() { } - static String readResource(String resourceName) { + public static String readResource(String resourceName) { try (InputStream input = TestsUtils.class.getResourceAsStream(resourceName)) { return IOUtils.toString(input); } catch (Exception exception) { @@ -26,8 +26,8 @@ static String readResource(String resourceName) { } } - static void updateFeatureTypeTimeVersioning(Catalog catalog, String featureTypeName, - boolean enabled, String idProperty, String timeProperty) { + public static void updateFeatureTypeTimeVersioning(Catalog catalog, String featureTypeName, + boolean enabled, String idProperty, String timeProperty) { FeatureTypeInfo featureType = catalog.getFeatureTypeByName(featureTypeName); if (enabled) { enable(featureType, idProperty, timeProperty); diff --git a/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TimeVersioningTest.java b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/versioning/TimeVersioningTest.java similarity index 98% rename from src/community/nsg-profile/src/test/java/org/geoserver/nsg/TimeVersioningTest.java rename to src/community/nsg-profile/src/test/java/org/geoserver/nsg/versioning/TimeVersioningTest.java index 758d1fa3370..d69d6fe33e5 100644 --- a/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TimeVersioningTest.java +++ b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/versioning/TimeVersioningTest.java @@ -2,13 +2,14 @@ * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ -package org.geoserver.nsg; +package org.geoserver.nsg.versioning; import org.custommonkey.xmlunit.SimpleNamespaceContext; import org.custommonkey.xmlunit.XMLUnit; import org.custommonkey.xmlunit.XpathEngine; import org.geoserver.data.test.MockData; import org.geoserver.data.test.SystemTestData; +import org.geoserver.nsg.TestsUtils; import org.geoserver.test.GeoServerSystemTestSupport; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.crs.DefaultGeographicCRS; diff --git a/src/community/nsg-profile/src/test/java/org/geoserver/nsg/web/WfsVersioningConfigTest.java b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/versioning/web/WfsVersioningConfigTest.java similarity index 96% rename from src/community/nsg-profile/src/test/java/org/geoserver/nsg/web/WfsVersioningConfigTest.java rename to src/community/nsg-profile/src/test/java/org/geoserver/nsg/versioning/web/WfsVersioningConfigTest.java index efe29b00e6a..b580d8e1f6e 100644 --- a/src/community/nsg-profile/src/test/java/org/geoserver/nsg/web/WfsVersioningConfigTest.java +++ b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/versioning/web/WfsVersioningConfigTest.java @@ -2,13 +2,10 @@ * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ -package org.geoserver.nsg.web; +package org.geoserver.nsg.versioning.web; -import org.apache.wicket.markup.html.form.CheckBox; -import org.apache.wicket.util.tester.FormTester; import org.geoserver.web.GeoServerWicketTestSupport; import org.geoserver.web.publish.PublishedConfigurationPage; -import org.geoserver.wfs.GMLInfo; import org.geoserver.wfs.WFSInfo; import org.junit.Test; From 62df0e32ad5bcba5906504a41f4e66ca5eca1466 Mon Sep 17 00:00:00 2001 From: Sandro Salari Date: Tue, 22 Aug 2017 15:57:46 +0200 Subject: [PATCH 03/15] Added dispatcher and output format --- .../pagination/random/IndexOutputFormat.java | 38 +++++++++++ .../IndexResultTypeDisapatcherCallback.java | 63 +++++++++++++++++++ .../src/main/resources/applicationContext.xml | 40 +++++++----- 3 files changed, 126 insertions(+), 15 deletions(-) create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDisapatcherCallback.java diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java new file mode 100644 index 00000000000..06498301884 --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java @@ -0,0 +1,38 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ + +package org.geoserver.nsg.pagination.random; + +import java.io.IOException; +import java.io.OutputStream; + +import org.geoserver.config.GeoServer; +import org.geoserver.platform.Operation; +import org.geoserver.platform.ServiceException; +import org.geoserver.wfs.response.v2_0.HitsOutputFormat; + +import net.opengis.wfs20.BaseRequestType; + +/** + * This output format handles requests if the original requested result type was "index"
+ * It checks {@link BaseRequestType#getExtendedProperties()} for + * {@link IndexResultTypeDisapatcherCallback#RESULT_TYPE_INDEX_PARAMETER} valued as true + * + * @author sandr + * + */ +public class IndexOutputFormat extends HitsOutputFormat { + + public IndexOutputFormat(GeoServer gs) { + super(gs); + } + + @Override + public void write(Object value, OutputStream output, Operation operation) + throws IOException, ServiceException { + super.write(value, output, operation); + } + +} diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDisapatcherCallback.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDisapatcherCallback.java new file mode 100644 index 00000000000..72847971fa6 --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDisapatcherCallback.java @@ -0,0 +1,63 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ + +package org.geoserver.nsg.pagination.random; + +import org.geoserver.config.GeoServer; +import org.geoserver.ows.AbstractDispatcherCallback; +import org.geoserver.ows.Request; +import org.geoserver.ows.Response; +import org.geoserver.platform.Operation; + +import net.opengis.wfs20.ResultTypeType; + +/** + *

+ * When a request that contains the "resultType" parameter arrives, if the parameter value is + * "index" it is substituted by "hits". + *

+ *

+ * A new entry named RESULT_TYPE_INDEX specifying that the original result type was "index" is added + * to KVP maps + *

+ */ + +public class IndexResultTypeDisapatcherCallback extends AbstractDispatcherCallback { + + private GeoServer gs; + + private static final String RESULT_TYPE_PARAMETER = "resultType"; + + private static final String RESULT_TYPE_INDEX = "index"; + + static final String RESULT_TYPE_INDEX_PARAMETER = "RESULT_TYPE_INDEX"; + + public IndexResultTypeDisapatcherCallback(GeoServer gs) { + this.gs = gs; + } + + @Override + @SuppressWarnings("unchecked") + public Request init(Request request) { + Object resultType = request.getKvp().get(RESULT_TYPE_PARAMETER); + if (resultType != null && resultType.toString().equals(RESULT_TYPE_INDEX)) { + request.getKvp().put(RESULT_TYPE_PARAMETER, ResultTypeType.HITS); + request.getKvp().put(RESULT_TYPE_INDEX_PARAMETER, true); + } + return super.init(request); + } + + @Override + public Response responseDispatched(Request request, Operation operation, Object result, + Response response) { + Response newResponse = response; + if (request.getKvp().get(RESULT_TYPE_INDEX_PARAMETER) != null + && (Boolean) request.getKvp().get(RESULT_TYPE_INDEX_PARAMETER)) { + newResponse = new IndexOutputFormat(this.gs); + } + return super.responseDispatched(request, operation, result, newResponse); + } + +} diff --git a/src/community/nsg-profile/src/main/resources/applicationContext.xml b/src/community/nsg-profile/src/main/resources/applicationContext.xml index 875245237cd..c54d58806ee 100644 --- a/src/community/nsg-profile/src/main/resources/applicationContext.xml +++ b/src/community/nsg-profile/src/main/resources/applicationContext.xml @@ -1,19 +1,29 @@ - - - - - - - - - - org.geoserver.catalog.FeatureTypeInfo - - - + + + + + + + + + + org.geoserver.catalog.FeatureTypeInfo + + + + + + When a request using the index result type comes in a + dispatcher callback + this switch the "index" value by the "hits" value + + + From b22d6b67332edc0229a28faeae5af0ac568da3bf Mon Sep 17 00:00:00 2001 From: Sandro Salari Date: Fri, 25 Aug 2017 15:25:05 +0200 Subject: [PATCH 04/15] Setup of GetFeature requests storage: - configuration file parser and change listerner - db storage - file storage - clean task --- src/community/nsg-profile/pom.xml | 5 + .../pagination/random/IndexConfiguration.java | 74 ++++ .../pagination/random/IndexInitializer.java | 344 ++++++++++++++++++ .../pagination/random/IndexOutputFormat.java | 116 +++++- .../IndexResultTypeDisapatcherCallback.java | 8 + .../src/main/resources/applicationContext.xml | 12 +- .../main/resources/configuration.properties | 6 + 7 files changed, 559 insertions(+), 6 deletions(-) create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java create mode 100644 src/community/nsg-profile/src/main/resources/configuration.properties diff --git a/src/community/nsg-profile/pom.xml b/src/community/nsg-profile/pom.xml index b9e26ef3fbc..b22b61e0b84 100644 --- a/src/community/nsg-profile/pom.xml +++ b/src/community/nsg-profile/pom.xml @@ -30,6 +30,11 @@ gs-web-core ${project.version} + + + org.geotools.jdbc + gt-jdbc-h2 + org.geoserver diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java new file mode 100644 index 00000000000..4b352213440 --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java @@ -0,0 +1,74 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ + +package org.geoserver.nsg.pagination.random; + +import java.util.Map; + +import org.geoserver.platform.resource.Resource; +import org.geotools.data.DataStore; + +/** + * + * Class used to store the index result type configuration managed by {@link IndexInitializer} + */ +public class IndexConfiguration { + + private static DataStore currentDataStore; + + private static Resource storageResource; + + private static Long timeToLive = 600l; + + private static Map currentDataStoreParams; + + /** + * Store the DB parameters and the relative {@link DataStore} + * + * @param currentDataStoreParams + * @param currentDataStore + */ + public static void setCurrentDataStore(Map currentDataStoreParams, + DataStore currentDataStore) { + IndexConfiguration.currentDataStoreParams = currentDataStoreParams; + IndexConfiguration.currentDataStore = currentDataStore; + } + + /** + * Store the reference to resource used to archive the serialized GetFeatureRequest + * + * @param currentDataStoreParams + * @param currentDataStore + */ + public static void setStorageResource(Resource storageResource) { + IndexConfiguration.storageResource = storageResource; + } + + /** + * Store the value of time to live of stored GetFeatureRequest + * + * @param timeToLive + */ + public static void setTimeToLive(Long timeToLive) { + IndexConfiguration.timeToLive = timeToLive; + } + + public static DataStore getCurrentDataStore() { + return currentDataStore; + } + + public static Map getCurrentDataStoreParams() { + return currentDataStoreParams; + } + + public static Resource getStorageResource() { + return storageResource; + } + + public static Long getTimeToLive() { + return timeToLive; + } + +} diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java new file mode 100644 index 00000000000..463772625dd --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java @@ -0,0 +1,344 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ + +package org.geoserver.nsg.pagination.random; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.geoserver.config.GeoServer; +import org.geoserver.config.GeoServerDataDirectory; +import org.geoserver.config.GeoServerInitializer; +import org.geoserver.platform.GeoServerExtensions; +import org.geoserver.platform.GeoServerResourceLoader; +import org.geoserver.platform.resource.FileSystemResourceStore; +import org.geoserver.platform.resource.Resource; +import org.geoserver.platform.resource.ResourceListener; +import org.geoserver.platform.resource.ResourceNotification; +import org.geoserver.platform.resource.ResourceNotification.Kind; +import org.geotools.data.DataStore; +import org.geotools.data.DataStoreFinder; +import org.geotools.data.DataUtilities; +import org.geotools.data.DefaultTransaction; +import org.geotools.data.Transaction; +import org.geotools.data.simple.SimpleFeatureCollection; +import org.geotools.data.simple.SimpleFeatureIterator; +import org.geotools.data.simple.SimpleFeatureSource; +import org.geotools.data.simple.SimpleFeatureStore; +import org.geotools.filter.text.cql2.CQL; +import org.geotools.jdbc.JDBCDataStoreFactory; +import org.geotools.util.logging.Logging; +import org.opengis.feature.simple.SimpleFeature; +import org.opengis.feature.simple.SimpleFeatureType; +import org.opengis.filter.Filter; + +/** + * + * Class used to parse the configuration properties stored in nsg-profile module folder: + *
    + *
  • resultSets.storage.path path where to store the serialized GetFeatureRequest with name + * of random UUID. + *
  • resultSets.timeToLive time to live value, all the stored requests that have not been + * used for a period of time bigger than this will be deleted. + *
  • resultSets.db.{@link JDBCDataStoreFactory#DBTYPE} + *
  • resultSets.db.{@link JDBCDataStoreFactory#DATABASE} + *
  • resultSets.db.{@link JDBCDataStoreFactory#HOST} + *
  • resultSets.db.{@link JDBCDataStoreFactory#PORT} + *
  • resultSets.db.{@link JDBCDataStoreFactory#SCHEMA} + *
  • resultSets.db.{@link JDBCDataStoreFactory#USER} + *
  • resultSets.db.{@link JDBCDataStoreFactory#PASSWD} + *
+ * All configuration properties is changeable at runtime so when this properties is updated the + * module take the appropriate action: + *
    + *
  • When the index DB is changed the new DB should be used and the content of the old table moved + * to the new table. If the new DB already has the index table it should be emptied, + *
  • When the storage path is changed, the new storage path should be used and the old storage + * path content should be moved to the new one, + *
  • When the the time to live is changed the {@link #clean()} procedure will update. + *
+ * + * The class is also responsible to {@link #clean()} the stored requests (result sets) that have not + * been used for a period of time bigger than the configured time to live value + *

+ * + * @author sandr + * + */ + +public class IndexInitializer implements GeoServerInitializer { + + static Logger LOGGER = Logging.getLogger(IndexInitializer.class); + + static final String PROPERTY_DB_PREFIX = "resultSets.db."; + + static final String PROPERTY_FILENAME = "configuration.properties"; + + static final String MODULE_DIR = "nsg-profile"; + + public static final String STORE_SCHEMA_NAME = "RESULT_SET"; + + public static final String STORE_SCHEMA = "ID:\"\",created:0,updated:0"; + + /* + * Lock to synchronize activity of clean task with listener that changes the DB and file + * resources + */ + private final Object lock = new Object(); + + @Override + public void initialize(GeoServer geoServer) throws Exception { + GeoServerResourceLoader loader = GeoServerExtensions.bean(GeoServerResourceLoader.class); + GeoServerDataDirectory dd = new GeoServerDataDirectory(loader); + Resource resource = dd.get(MODULE_DIR + "/" + PROPERTY_FILENAME); + if (loader != null) { + File directory = loader.findOrCreateDirectory(MODULE_DIR); + File file = new File(directory, PROPERTY_FILENAME); + // Create default configuration file + if (!file.exists()) { + InputStream stream = IndexInitializer.class + .getResourceAsStream("/" + PROPERTY_FILENAME); + Properties properties = new Properties(); + properties.load(stream); + // Replace GEOSERVER_DATA_DIR placeholder + properties.replaceAll((k, v) -> ((String) v).replace("${GEOSERVER_DATA_DIR}", + dd.root().getPath())); + // Create resource and save properties + OutputStream out = resource.out(); + properties.store(out, null); + out.close(); + } + loadConfigurations(resource); + // Listen for changes in configuration file and reload properties + resource.addListener(new ResourceListener() { + @Override + public void changed(ResourceNotification notify) { + if (notify.getKind() == Kind.ENTRY_MODIFY) { + try { + loadConfigurations(resource); + } catch (Exception exception) { + throw new RuntimeException("Error reload confiugrations.", exception); + } + } + } + }); + } + } + + /** + * Helper method that + */ + private void loadConfigurations(Resource resource) throws IOException { + synchronized (lock) { + Properties properties = new Properties(); + properties.load(resource.in()); + // Reload database + Map params = new HashMap<>(); + params.put(JDBCDataStoreFactory.DBTYPE.key, + properties.get(PROPERTY_DB_PREFIX + JDBCDataStoreFactory.DBTYPE.key)); + params.put(JDBCDataStoreFactory.DATABASE.key, + properties.get(PROPERTY_DB_PREFIX + JDBCDataStoreFactory.DATABASE.key)); + params.put(JDBCDataStoreFactory.HOST.key, + properties.get(PROPERTY_DB_PREFIX + JDBCDataStoreFactory.HOST.key)); + params.put(JDBCDataStoreFactory.PORT.key, + properties.get(PROPERTY_DB_PREFIX + JDBCDataStoreFactory.PORT.key)); + params.put(JDBCDataStoreFactory.SCHEMA.key, + properties.get(PROPERTY_DB_PREFIX + JDBCDataStoreFactory.SCHEMA.key)); + params.put(JDBCDataStoreFactory.USER.key, + properties.get(PROPERTY_DB_PREFIX + JDBCDataStoreFactory.USER.key)); + params.put(JDBCDataStoreFactory.PASSWD.key, + properties.get(PROPERTY_DB_PREFIX + JDBCDataStoreFactory.PASSWD.key)); + /** + * When the index DB is changed the new DB should be used and the content of the old + * table moved to the new table. If the new DB already has the index table it should be + * emptied + */ + manageDBChange(params); + /* + * If the storage path is changed, the new storage path should be used and the old + * storage path content should be moved to the new one + */ + manageStorageChange(resource, properties.get("resultSets.storage.path")); + /* + * Change time to live + */ + manageTimeToLiveChange(properties.get("resultSets.timeToLive")); + } + + } + + /** + * Helper method that + */ + private void manageTimeToLiveChange(Object timneToLive) { + try { + if (timneToLive != null) { + String timneToLiveStr = (String) timneToLive; + IndexConfiguration.setTimeToLive(Long.parseLong(timneToLiveStr)); + } + } catch (Exception exception) { + throw new RuntimeException("Error on change time to live", exception); + } + } + + /** + * Helper method that move resources files form current folder to the new one, current storage + * is deleted + */ + private void manageStorageChange(Resource resource, Object newStorage) { + try { + if (newStorage != null) { + String newStorageStr = (String) newStorage; + Resource newResource = new FileSystemResourceStore(new File(newStorageStr)).get(""); + Resource exResource = IndexConfiguration.getStorageResource(); + if (exResource != null && !newResource.dir().getAbsolutePath() + .equals(exResource.dir().getAbsolutePath())) { + exResource.delete(); + IndexConfiguration.setStorageResource(newResource); + } + } + } catch (Exception exception) { + throw new RuntimeException("Error on change store", exception); + } + } + + /** + * Helper method that move DB data from old store to new one + */ + private void manageDBChange(Map params) { + try { + DataStore exDataStore = IndexConfiguration.getCurrentDataStore(); + DataStore newDataStore = DataStoreFinder.getDataStore(params); + if (exDataStore != null) { + // New store is valid and is different from current one + if (newDataStore != null && !isStorageTheSame(params)) { + // Create table in new store + createTable(newDataStore, true); + // Move data to new store + moveData(exDataStore, newDataStore); + // Delete old store + exDataStore.dispose(); + } + } else { + // Create schema + createTable(newDataStore, false); + } + IndexConfiguration.setCurrentDataStore(params, newDataStore); + } catch (Exception exception) { + throw new RuntimeException("Error reload DB confiugrations.", exception); + } + } + + /** + * Helper method that check id the DB is the same, matching the JDBC configurations parameters. + */ + private Boolean isStorageTheSame(Map newParams) { + Map currentParams = IndexConfiguration.getCurrentDataStoreParams(); + return currentParams.get(JDBCDataStoreFactory.DBTYPE.key) + .equals(newParams.get(JDBCDataStoreFactory.DBTYPE.key)) + && currentParams.get(JDBCDataStoreFactory.DATABASE.key) + .equals(newParams.get(JDBCDataStoreFactory.DATABASE.key)) + && currentParams.get(JDBCDataStoreFactory.HOST.key) + .equals(newParams.get(JDBCDataStoreFactory.HOST.key)) + && currentParams.get(JDBCDataStoreFactory.PORT.key) + .equals(newParams.get(JDBCDataStoreFactory.PORT.key)) + && currentParams.get(JDBCDataStoreFactory.SCHEMA.key) + .equals(newParams.get(JDBCDataStoreFactory.SCHEMA.key)); + } + + /** + * Helper method that create a new table on DB to store resource informations + */ + private void createTable(DataStore dataStore, Boolean forceDelete) throws Exception { + SimpleFeatureType schema = dataStore.getSchema(STORE_SCHEMA_NAME); + // Schema exists + if (schema != null) { + // Delete of exist is required, and then create a new one + if (forceDelete) { + dataStore.removeSchema(STORE_SCHEMA_NAME); + schema = DataUtilities.createType(STORE_SCHEMA_NAME, STORE_SCHEMA); + dataStore.createSchema(schema); + } + // Schema not exists, create a new one + } else { + schema = DataUtilities.createType(STORE_SCHEMA_NAME, STORE_SCHEMA); + dataStore.createSchema(schema); + } + } + + /** + * Helper method that move resource informations from current DB to the new one + */ + private void moveData(DataStore exDataStore, DataStore newDataStore) throws Exception { + Transaction session = new DefaultTransaction("Adding"); + try { + SimpleFeatureSource exFs = exDataStore.getFeatureSource(STORE_SCHEMA_NAME); + SimpleFeatureStore newFs = (SimpleFeatureStore) newDataStore + .getFeatureSource(STORE_SCHEMA_NAME); + newFs.setTransaction(session); + newFs.addFeatures(exFs.getFeatures()); + session.commit(); + } catch (Throwable t) { + session.rollback(); + throw new RuntimeException("Error on move data", t); + } finally { + session.close(); + } + } + + /** + * Delete all the stored requests (result sets) that have not been used for a period of time + * bigger than the configured time to live value. Clean also related resource files. + *

+ * Executed by scheduler, for details see Spring XML configuration + */ + public void clean() throws Exception { + synchronized (lock) { + Transaction session = new DefaultTransaction("RemoveOld"); + try { + // Remove record + Long timeToLive = IndexConfiguration.getTimeToLive(); + DataStore currentDataStore = IndexConfiguration.getCurrentDataStore(); + SimpleFeatureStore store = (SimpleFeatureStore) currentDataStore + .getFeatureSource(STORE_SCHEMA_NAME); + Long now = new Date().getTime(); + Long liveTreshold = now - timeToLive * 1000; + Filter filter = CQL.toFilter("updated < " + liveTreshold); + SimpleFeatureCollection toRemoved = store.getFeatures(filter); + // Remove file + Resource currentResource = IndexConfiguration.getStorageResource(); + SimpleFeatureIterator iterator = toRemoved.features(); + try { + while (iterator.hasNext()) { + SimpleFeature feature = iterator.next(); + currentResource.get(feature.getID()).delete(); + } + } finally { + iterator.close(); + } + store.removeFeatures(filter); + if (LOGGER.isLoggable(Level.FINEST)) { + LOGGER.finest("CLEAN executed, removed stored requests older than " + + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") + .format(new Date(liveTreshold))); + } + } catch (Throwable t) { + session.rollback(); + throw new RuntimeException("Error on move data", t); + } finally { + session.close(); + } + } + } +} diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java index 06498301884..1a4a60ff53c 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java @@ -7,32 +7,140 @@ import java.io.IOException; import java.io.OutputStream; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.util.UUID; +import java.util.logging.Logger; + +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; import org.geoserver.config.GeoServer; +import org.geoserver.ows.util.OwsUtils; +import org.geoserver.ows.util.ResponseUtils; import org.geoserver.platform.Operation; import org.geoserver.platform.ServiceException; +import org.geoserver.wfs.WFSInfo; +import org.geoserver.wfs.request.FeatureCollectionResponse; import org.geoserver.wfs.response.v2_0.HitsOutputFormat; +import org.geotools.util.logging.Logging; +import org.geotools.wfs.v2_0.WFS; +import org.geotools.wfs.v2_0.WFSConfiguration; +import org.geotools.xml.Encoder; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; -import net.opengis.wfs20.BaseRequestType; +import net.opengis.wfs20.GetFeatureType; /** * This output format handles requests if the original requested result type was "index"
- * It checks {@link BaseRequestType#getExtendedProperties()} for - * {@link IndexResultTypeDisapatcherCallback#RESULT_TYPE_INDEX_PARAMETER} valued as true + * See {@link IndexResultTypeDisapatcherCallback} * * @author sandr * */ public class IndexOutputFormat extends HitsOutputFormat { + static Logger LOGGER = Logging.getLogger(IndexOutputFormat.class); + GetFeatureType request; + String resultSetId; + public IndexOutputFormat(GeoServer gs) { super(gs); } - + @Override public void write(Object value, OutputStream output, Operation operation) throws IOException, ServiceException { + // extract GetFeature request + request = (GetFeatureType) OwsUtils.parameter(operation.getParameters(), + GetFeatureType.class); + // generate an UUID (resultSetID) for this request + resultSetId = UUID.randomUUID().toString(); super.write(value, output, operation); } + @Override + protected void encode(FeatureCollectionResponse hits, OutputStream output, WFSInfo wfs) + throws IOException { + + hits.setNumberOfFeatures(BigInteger.ZERO); + // instantiate the XML encoder + Encoder encoder = new Encoder(new WFSConfiguration()); + encoder.setEncoding(Charset.forName(wfs.getGeoServer().getSettings().getCharset())); + encoder.setSchemaLocation(WFS.NAMESPACE, + ResponseUtils.appendPath(wfs.getSchemaBaseURL(), "wfs/2.0/wfs.xsd")); + Document document; + try { + // encode the HITS result using FeatureCollection as the root XML element + document = encoder.encodeAsDOM(hits.getAdaptee(), WFS.FeatureCollection); + } catch (Exception exception) { + throw new RuntimeException("Error encoding INDEX result.", exception); + } + // add the resultSetID attribute to the result + addResultSetIdElement(document, resultSetId); + // write the XML document to response output stream + writeDocument(document, output); + } + + /** + * Helper method that serialize GetFeature request, store it in the file system and associate it with resultSetId + */ + protected void storeGetFeature(){ + + } + + /** + * Helper method that adds the resultSetID attribute to XML result. If no FeatureCollection + * element can be found nothing will be done. + */ + private static void addResultSetIdElement(Document document, String resultSetId) { + // search FeatureCollection XML nodes + NodeList nodes = document.getElementsByTagName("wfs:FeatureCollection"); + if (nodes.getLength() != 1) { + // only one node should exists, let's log an warning an move on + LOGGER.warning( + "No feature collection element could be found, resultSetID attribute will not be added."); + return; + } + // get the FeatureCollection node + Node node = nodes.item(0); + if (node.getNodeType() == Node.ELEMENT_NODE) { + // the found node is a XML element so let's add the resultSetID attribute + Element element = (Element) node; + element.setAttribute("resultSetID", resultSetId); + } else { + // unlikely but we got a XML node that is not a XML element + LOGGER.warning( + "Feature collection node is not a XML element, resultSetID attribute will not be added."); + } + } + + /** + * Helper method that just writes a XML document to a given output stream. + */ + private static void writeDocument(Document document, OutputStream output) { + // instantiate a new XML transformer + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer; + try { + transformer = transformerFactory.newTransformer(); + } catch (Exception exception) { + throw new RuntimeException("Error creating XML transformer.", exception); + } + // write the XML document to the provided output stream + DOMSource source = new DOMSource(document); + StreamResult result = new StreamResult(output); + try { + transformer.transform(source, result); + } catch (Exception exception) { + throw new RuntimeException("Error writing INDEX result to the output stream.", + exception); + } + } + } diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDisapatcherCallback.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDisapatcherCallback.java index 72847971fa6..6a7f49e4cc2 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDisapatcherCallback.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDisapatcherCallback.java @@ -5,11 +5,14 @@ package org.geoserver.nsg.pagination.random; +import java.util.logging.Logger; + import org.geoserver.config.GeoServer; import org.geoserver.ows.AbstractDispatcherCallback; import org.geoserver.ows.Request; import org.geoserver.ows.Response; import org.geoserver.platform.Operation; +import org.geotools.util.logging.Logging; import net.opengis.wfs20.ResultTypeType; @@ -22,9 +25,14 @@ * A new entry named RESULT_TYPE_INDEX specifying that the original result type was "index" is added * to KVP maps *

+ *

+ * The object that manage response of type HitsOutputFormat is replaced with IndexOutputFormat before response has been dispatched + *

*/ public class IndexResultTypeDisapatcherCallback extends AbstractDispatcherCallback { + + static Logger LOGGER = Logging.getLogger(IndexOutputFormat.class); private GeoServer gs; diff --git a/src/community/nsg-profile/src/main/resources/applicationContext.xml b/src/community/nsg-profile/src/main/resources/applicationContext.xml index c54d58806ee..acdcb9c2caa 100644 --- a/src/community/nsg-profile/src/main/resources/applicationContext.xml +++ b/src/community/nsg-profile/src/main/resources/applicationContext.xml @@ -1,8 +1,9 @@ + xmlns:task="http://www.springframework.org/schema/task" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd"> @@ -26,4 +27,11 @@
+ + + + + + + diff --git a/src/community/nsg-profile/src/main/resources/configuration.properties b/src/community/nsg-profile/src/main/resources/configuration.properties new file mode 100644 index 00000000000..3d9f99c95c2 --- /dev/null +++ b/src/community/nsg-profile/src/main/resources/configuration.properties @@ -0,0 +1,6 @@ +resultSets.storage.path=${GEOSERVER_DATA_DIR}/nsg-profile/resultSets +resultSets.timeToLive=601 +resultSets.db.dbtype=h2 +resultSets.db.database=${GEOSERVER_DATA_DIR}/nsg-profile/db/resultSets +resultSets.db.user=sa +resultSets.db.password=sa \ No newline at end of file From abaf78feef0db3190f8c1ad133fbf83106a246e8 Mon Sep 17 00:00:00 2001 From: Sandro Salari Date: Fri, 25 Aug 2017 18:10:54 +0200 Subject: [PATCH 05/15] Added EMF and H2 save procedure to store GetFeature calls --- src/community/nsg-profile/pom.xml | 8 ++- .../pagination/random/IndexInitializer.java | 4 +- .../pagination/random/IndexOutputFormat.java | 71 ++++++++++++++++--- 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/src/community/nsg-profile/pom.xml b/src/community/nsg-profile/pom.xml index b22b61e0b84..b93c0c49d15 100644 --- a/src/community/nsg-profile/pom.xml +++ b/src/community/nsg-profile/pom.xml @@ -23,18 +23,22 @@ org.geoserver gs-wfs - ${project.version} org.geoserver.web gs-web-core - ${project.version} org.geotools.jdbc gt-jdbc-h2 + + + org.eclipse.emf + org.eclipse.emf.ecore.xmi + 2.12.0 + org.geoserver diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java index 463772625dd..0abacf2fe93 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java @@ -205,8 +205,8 @@ private void manageStorageChange(Resource resource, Object newStorage) { if (exResource != null && !newResource.dir().getAbsolutePath() .equals(exResource.dir().getAbsolutePath())) { exResource.delete(); - IndexConfiguration.setStorageResource(newResource); } + IndexConfiguration.setStorageResource(newResource); } } catch (Exception exception) { throw new RuntimeException("Error on change store", exception); @@ -281,7 +281,7 @@ private void createTable(DataStore dataStore, Boolean forceDelete) throws Except * Helper method that move resource informations from current DB to the new one */ private void moveData(DataStore exDataStore, DataStore newDataStore) throws Exception { - Transaction session = new DefaultTransaction("Adding"); + Transaction session = new DefaultTransaction("Moving"); try { SimpleFeatureSource exFs = exDataStore.getFeatureSource(STORE_SCHEMA_NAME); SimpleFeatureStore newFs = (SimpleFeatureStore) newDataStore diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java index 1a4a60ff53c..7d557607294 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java @@ -9,6 +9,9 @@ import java.io.OutputStream; import java.math.BigInteger; import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; import java.util.UUID; import java.util.logging.Logger; @@ -17,18 +20,29 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl; import org.geoserver.config.GeoServer; import org.geoserver.ows.util.OwsUtils; import org.geoserver.ows.util.ResponseUtils; import org.geoserver.platform.Operation; import org.geoserver.platform.ServiceException; +import org.geoserver.platform.resource.Resource; import org.geoserver.wfs.WFSInfo; import org.geoserver.wfs.request.FeatureCollectionResponse; import org.geoserver.wfs.response.v2_0.HitsOutputFormat; +import org.geotools.data.DataStore; +import org.geotools.data.collection.ListFeatureCollection; +import org.geotools.data.simple.SimpleFeatureCollection; +import org.geotools.data.simple.SimpleFeatureStore; +import org.geotools.feature.simple.SimpleFeatureBuilder; import org.geotools.util.logging.Logging; import org.geotools.wfs.v2_0.WFS; import org.geotools.wfs.v2_0.WFSConfiguration; import org.geotools.xml.Encoder; +import org.opengis.feature.simple.SimpleFeature; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -46,28 +60,39 @@ public class IndexOutputFormat extends HitsOutputFormat { static Logger LOGGER = Logging.getLogger(IndexOutputFormat.class); - GetFeatureType request; + String resultSetId; + private static ResourceSet resSet; + + static { + // Register XMI serializer + resSet = new ResourceSetImpl(); + resSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("feature", + new XMIResourceFactoryImpl()); + } + public IndexOutputFormat(GeoServer gs) { super(gs); } - + @Override public void write(Object value, OutputStream output, Operation operation) throws IOException, ServiceException { // extract GetFeature request - request = (GetFeatureType) OwsUtils.parameter(operation.getParameters(), + GetFeatureType request = (GetFeatureType) OwsUtils.parameter(operation.getParameters(), GetFeatureType.class); // generate an UUID (resultSetID) for this request resultSetId = UUID.randomUUID().toString(); + // store request and associate it to UUID + storeGetFeature(resultSetId, request); super.write(value, output, operation); } @Override protected void encode(FeatureCollectionResponse hits, OutputStream output, WFSInfo wfs) throws IOException { - + hits.setNumberOfFeatures(BigInteger.ZERO); // instantiate the XML encoder Encoder encoder = new Encoder(new WFSConfiguration()); @@ -86,12 +111,42 @@ protected void encode(FeatureCollectionResponse hits, OutputStream output, WFSIn // write the XML document to response output stream writeDocument(document, output); } - + /** - * Helper method that serialize GetFeature request, store it in the file system and associate it with resultSetId + * Helper method that serialize GetFeature request, store it in the file system and associate it + * with resultSetId + * + * @param request + * @param resultSetId + * @throws Exception */ - protected void storeGetFeature(){ - + protected void storeGetFeature(String resultSetId, GetFeatureType ft) throws RuntimeException { + try { + DataStore dataStore = IndexConfiguration.getCurrentDataStore(); + // Create and store new feature + SimpleFeatureStore featureStore = (SimpleFeatureStore) dataStore + .getFeatureSource(IndexInitializer.STORE_SCHEMA_NAME); + SimpleFeatureBuilder builder = new SimpleFeatureBuilder(featureStore.getSchema()); + Long now = new Date().getTime(); + builder.add(resultSetId); + builder.add(now); + builder.add(now); + SimpleFeature feature = builder.buildFeature(null); + SimpleFeatureCollection collection = new ListFeatureCollection(featureStore.getSchema(), + Arrays.asList(feature)); + featureStore.addFeatures(collection); + + // Create and store file + Resource storageResource = IndexConfiguration.getStorageResource(); + + org.eclipse.emf.ecore.resource.Resource emfRes = resSet + .createResource(URI.createFileURI(storageResource.dir().getAbsolutePath() + "\\" + + resultSetId + ".feature")); + emfRes.getContents().add(ft); + emfRes.save(Collections.EMPTY_MAP); + } catch (Exception exception) { + throw new RuntimeException("Error storing feature.", exception); + } } /** From d4fd0935182efa45437ab76363fccf712582e3ed Mon Sep 17 00:00:00 2001 From: Sandro Salari Date: Wed, 30 Aug 2017 11:40:29 +0200 Subject: [PATCH 06/15] Implemented PageResults operation --- .../pagination/random/IndexConfiguration.java | 3 + .../pagination/random/IndexInitializer.java | 51 ++++--- .../pagination/random/IndexOutputFormat.java | 6 +- ...=> IndexResultTypeDispatcherCallback.java} | 14 +- .../random/PageResultsDispatcherCallback.java | 52 +++++++ .../random/PageResultsWebFeatureService.java | 143 ++++++++++++++++++ .../random/ResultTypeKvpParser.java | 23 +++ .../src/main/resources/applicationContext.xml | 42 ++++- 8 files changed, 295 insertions(+), 39 deletions(-) rename src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/{IndexResultTypeDisapatcherCallback.java => IndexResultTypeDispatcherCallback.java} (86%) create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsDispatcherCallback.java create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsWebFeatureService.java create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/ResultTypeKvpParser.java diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java index 4b352213440..a886fcaa10a 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java @@ -13,6 +13,9 @@ /** * * Class used to store the index result type configuration managed by {@link IndexInitializer} + * + * @author sandr + * */ public class IndexConfiguration { diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java index 0abacf2fe93..2ece19553c2 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java @@ -36,6 +36,7 @@ import org.geotools.data.simple.SimpleFeatureIterator; import org.geotools.data.simple.SimpleFeatureSource; import org.geotools.data.simple.SimpleFeatureStore; +import org.geotools.feature.NameImpl; import org.geotools.filter.text.cql2.CQL; import org.geotools.jdbc.JDBCDataStoreFactory; import org.geotools.util.logging.Logging; @@ -89,7 +90,7 @@ public class IndexInitializer implements GeoServerInitializer { public static final String STORE_SCHEMA_NAME = "RESULT_SET"; - public static final String STORE_SCHEMA = "ID:\"\",created:0,updated:0"; + public static final String STORE_SCHEMA = "ID:java.lang.String,created:java.lang.Long,updated:java.lang.Long"; /* * Lock to synchronize activity of clean task with listener that changes the DB and file @@ -248,31 +249,31 @@ private Boolean isStorageTheSame(Map newParams) { return currentParams.get(JDBCDataStoreFactory.DBTYPE.key) .equals(newParams.get(JDBCDataStoreFactory.DBTYPE.key)) && currentParams.get(JDBCDataStoreFactory.DATABASE.key) - .equals(newParams.get(JDBCDataStoreFactory.DATABASE.key)) + .equals(newParams.get(JDBCDataStoreFactory.DATABASE.key)) && currentParams.get(JDBCDataStoreFactory.HOST.key) - .equals(newParams.get(JDBCDataStoreFactory.HOST.key)) + .equals(newParams.get(JDBCDataStoreFactory.HOST.key)) && currentParams.get(JDBCDataStoreFactory.PORT.key) - .equals(newParams.get(JDBCDataStoreFactory.PORT.key)) + .equals(newParams.get(JDBCDataStoreFactory.PORT.key)) && currentParams.get(JDBCDataStoreFactory.SCHEMA.key) - .equals(newParams.get(JDBCDataStoreFactory.SCHEMA.key)); + .equals(newParams.get(JDBCDataStoreFactory.SCHEMA.key)); } /** * Helper method that create a new table on DB to store resource informations */ private void createTable(DataStore dataStore, Boolean forceDelete) throws Exception { - SimpleFeatureType schema = dataStore.getSchema(STORE_SCHEMA_NAME); + Boolean exists = dataStore.getNames().contains(new NameImpl(STORE_SCHEMA_NAME)); // Schema exists - if (schema != null) { + if (exists) { // Delete of exist is required, and then create a new one if (forceDelete) { dataStore.removeSchema(STORE_SCHEMA_NAME); - schema = DataUtilities.createType(STORE_SCHEMA_NAME, STORE_SCHEMA); + SimpleFeatureType schema = DataUtilities.createType(STORE_SCHEMA_NAME, STORE_SCHEMA); dataStore.createSchema(schema); } // Schema not exists, create a new one } else { - schema = DataUtilities.createType(STORE_SCHEMA_NAME, STORE_SCHEMA); + SimpleFeatureType schema = DataUtilities.createType(STORE_SCHEMA_NAME, STORE_SCHEMA); dataStore.createSchema(schema); } } @@ -310,28 +311,30 @@ public void clean() throws Exception { // Remove record Long timeToLive = IndexConfiguration.getTimeToLive(); DataStore currentDataStore = IndexConfiguration.getCurrentDataStore(); - SimpleFeatureStore store = (SimpleFeatureStore) currentDataStore - .getFeatureSource(STORE_SCHEMA_NAME); Long now = new Date().getTime(); Long liveTreshold = now - timeToLive * 1000; - Filter filter = CQL.toFilter("updated < " + liveTreshold); - SimpleFeatureCollection toRemoved = store.getFeatures(filter); - // Remove file - Resource currentResource = IndexConfiguration.getStorageResource(); - SimpleFeatureIterator iterator = toRemoved.features(); - try { - while (iterator.hasNext()) { - SimpleFeature feature = iterator.next(); - currentResource.get(feature.getID()).delete(); + if(currentDataStore != null){ + SimpleFeatureStore store = (SimpleFeatureStore) currentDataStore + .getFeatureSource(STORE_SCHEMA_NAME); + Filter filter = CQL.toFilter("updated < " + liveTreshold); + SimpleFeatureCollection toRemoved = store.getFeatures(filter); + // Remove file + Resource currentResource = IndexConfiguration.getStorageResource(); + SimpleFeatureIterator iterator = toRemoved.features(); + try { + while (iterator.hasNext()) { + SimpleFeature feature = iterator.next(); + currentResource.get(feature.getID()).delete(); + } + } finally { + iterator.close(); } - } finally { - iterator.close(); + store.removeFeatures(filter); } - store.removeFeatures(filter); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("CLEAN executed, removed stored requests older than " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") - .format(new Date(liveTreshold))); + .format(new Date(liveTreshold))); } } catch (Throwable t) { session.rollback(); diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java index 7d557607294..ee2104b8ec6 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java @@ -52,7 +52,7 @@ /** * This output format handles requests if the original requested result type was "index"
- * See {@link IndexResultTypeDisapatcherCallback} + * See {@link IndexResultTypeDispatcherCallback} * * @author sandr * @@ -83,7 +83,7 @@ public void write(Object value, OutputStream output, Operation operation) GetFeatureType request = (GetFeatureType) OwsUtils.parameter(operation.getParameters(), GetFeatureType.class); // generate an UUID (resultSetID) for this request - resultSetId = UUID.randomUUID().toString(); + resultSetId = UUID.randomUUID().toString().replaceAll("-", ""); // store request and associate it to UUID storeGetFeature(resultSetId, request); super.write(value, output, operation); @@ -138,7 +138,7 @@ protected void storeGetFeature(String resultSetId, GetFeatureType ft) throws Run // Create and store file Resource storageResource = IndexConfiguration.getStorageResource(); - + org.eclipse.emf.ecore.resource.Resource emfRes = resSet .createResource(URI.createFileURI(storageResource.dir().getAbsolutePath() + "\\" + resultSetId + ".feature")); diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDisapatcherCallback.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDispatcherCallback.java similarity index 86% rename from src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDisapatcherCallback.java rename to src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDispatcherCallback.java index 6a7f49e4cc2..fc96a618f06 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDisapatcherCallback.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDispatcherCallback.java @@ -26,13 +26,17 @@ * to KVP maps *

*

- * The object that manage response of type HitsOutputFormat is replaced with IndexOutputFormat before response has been dispatched + * The object that manage response of type HitsOutputFormat is replaced with IndexOutputFormat + * before response has been dispatched *

+ * + * @author sandr + * */ -public class IndexResultTypeDisapatcherCallback extends AbstractDispatcherCallback { - - static Logger LOGGER = Logging.getLogger(IndexOutputFormat.class); +public class IndexResultTypeDispatcherCallback extends AbstractDispatcherCallback { + + static Logger LOGGER = Logging.getLogger(IndexResultTypeDispatcherCallback.class); private GeoServer gs; @@ -42,7 +46,7 @@ public class IndexResultTypeDisapatcherCallback extends AbstractDispatcherCallba static final String RESULT_TYPE_INDEX_PARAMETER = "RESULT_TYPE_INDEX"; - public IndexResultTypeDisapatcherCallback(GeoServer gs) { + public IndexResultTypeDispatcherCallback(GeoServer gs) { this.gs = gs; } diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsDispatcherCallback.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsDispatcherCallback.java new file mode 100644 index 00000000000..e2a0078f835 --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsDispatcherCallback.java @@ -0,0 +1,52 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ + +package org.geoserver.nsg.pagination.random; + +import java.util.Arrays; +import java.util.logging.Logger; + +import org.geoserver.config.GeoServer; +import org.geoserver.ows.AbstractDispatcherCallback; +import org.geoserver.ows.Request; +import org.geoserver.platform.Service; +import org.geoserver.platform.ServiceException; +import org.geotools.util.logging.Logging; + +/** + * + * This dispatcher manages service of type {@link PageResultsWebFeatureService} and sets the + * parameter ResultSetID present on KVP map. + *

+ * Dummy featureId value is added to KVP map to allow dispatcher to manage it as usual WFS 2.0 + * request. + * + * @author sandr + * + */ + +public class PageResultsDispatcherCallback extends AbstractDispatcherCallback { + + static Logger LOGGER = Logging.getLogger(PageResultsDispatcherCallback.class); + + private GeoServer gs; + + public PageResultsDispatcherCallback(GeoServer gs) { + this.gs = gs; + } + + @SuppressWarnings("unchecked") + @Override + public Service serviceDispatched(Request request, Service service) throws ServiceException { + if (service.getService() instanceof PageResultsWebFeatureService) { + PageResultsWebFeatureService prService = (PageResultsWebFeatureService) service + .getService(); + prService.setResultSetID((String) request.getKvp().get("resultSetID")); + request.getKvp().put("featureId", Arrays.asList("dummy")); + } + return super.serviceDispatched(request, service); + } + +} diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsWebFeatureService.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsWebFeatureService.java new file mode 100644 index 00000000000..e67ee835d8c --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsWebFeatureService.java @@ -0,0 +1,143 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ + +package org.geoserver.nsg.pagination.random; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.Date; +import java.util.logging.Logger; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl; +import org.geoserver.config.GeoServer; +import org.geoserver.platform.resource.Resource; +import org.geoserver.wfs.DefaultWebFeatureService20; +import org.geoserver.wfs.WFSException; +import org.geoserver.wfs.request.FeatureCollectionResponse; +import org.geotools.data.DataStore; +import org.geotools.data.DefaultTransaction; +import org.geotools.data.Transaction; +import org.geotools.data.simple.SimpleFeatureStore; +import org.geotools.filter.text.cql2.CQL; +import org.geotools.util.logging.Logging; +import org.opengis.filter.Filter; + +import net.opengis.wfs20.GetFeatureType; +import net.opengis.wfs20.ResultTypeType; + +/** + * This service supports the PageResults operation and manage it + * + * @author sandr + * + */ + +public class PageResultsWebFeatureService extends DefaultWebFeatureService20 { + + static Logger LOGGER = Logging.getLogger(PageResultsWebFeatureService.class); + + private static ResourceSet resSet; + + private static final String GML32_FORMAT = "application/gml+xml; version=3.2"; + + private static final BigInteger DEFAULT_START = new BigInteger("0"); + + private static final BigInteger DEFAULT_COUNT = new BigInteger("10"); + + private String resultSetID; + + public PageResultsWebFeatureService(GeoServer geoServer) { + super(geoServer); + // Register XMI serializer + resSet = new ResourceSetImpl(); + resSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("feature", + new XMIResourceFactoryImpl()); + } + + /** + * Recovers the stored request with associated {@link #resultSetID} and overrides the parameters + * using the ones provided with current operation or the default values: + *

    + *
  • {@link net.opengis.wfs20.GetFeatureType#getStartIndex StartIndex}
  • + *
  • {@link net.opengis.wfs20.GetFeatureType#getCount Count}
  • + *
  • {@link net.opengis.wfs20.GetFeatureType#getOutputFormat OutputFormat}
  • + *
  • {@link net.opengis.wfs20.GetFeatureType#getResultType ResultType}
  • + *
+ * Then executes the GetFeature operation using the WFS 2.0 service implementation and return is + * result. + * + * @param request + * @return + * @throws WFSException + * @throws IOException + */ + public FeatureCollectionResponse pageResults(GetFeatureType request) + throws WFSException, IOException { + // Retrieve stored request + GetFeatureType gft = getFeature(resultSetID); + // Update with incoming parameters or defaults + BigInteger startIndex = request.getStartIndex() != null ? request.getStartIndex() + : DEFAULT_START; + BigInteger count = request.getCount() != null ? request.getCount() : DEFAULT_COUNT; + String outputFormat = request.getOutputFormat() != null ? request.getOutputFormat() + : GML32_FORMAT; + ResultTypeType resultType = request.getResultType() != null ? request.getResultType() + : ResultTypeType.RESULTS; + gft.setStartIndex(startIndex); + gft.setCount(count); + gft.setOutputFormat(outputFormat); + gft.setResultType(resultType); + // Execute as getFeature + return super.getFeature(gft); + } + + /** + * Sets the resultSetID + * + * @param resultSetID + */ + public void setResultSetID(String resultSetID) { + this.resultSetID = resultSetID; + } + + /** + * Helper method that deserializes GetFeature request and updates its last utilization + * + * @param resultSetID + * @return + * @throws IOException + * @throws Exception + */ + private GetFeatureType getFeature(String resultSetID) throws IOException { + GetFeatureType feature = null; + Transaction transaction = new DefaultTransaction("Update"); + try { + // Update GetFeature utilization + DataStore currentDataStore = IndexConfiguration.getCurrentDataStore(); + SimpleFeatureStore store = (SimpleFeatureStore) currentDataStore + .getFeatureSource(IndexInitializer.STORE_SCHEMA_NAME); + store.setTransaction(transaction); + Filter filter = CQL.toFilter("ID = '" + resultSetID + "'"); + store.modifyFeatures("updated", new Date().getTime(), filter); + // Retrieve GetFeature from file + Resource storageResource = IndexConfiguration.getStorageResource(); + org.eclipse.emf.ecore.resource.Resource emfRes = resSet.getResource(URI.createFileURI( + storageResource.dir().getAbsolutePath() + "\\" + resultSetID + ".feature"), + true); + feature = (GetFeatureType) emfRes.getContents().get(0); + } catch (Exception t) { + transaction.rollback(); + throw new RuntimeException("Error on retrive feature", t); + } finally { + transaction.close(); + } + return feature; + + } + +} diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/ResultTypeKvpParser.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/ResultTypeKvpParser.java new file mode 100644 index 00000000000..bdc797dea78 --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/ResultTypeKvpParser.java @@ -0,0 +1,23 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ + +package org.geoserver.nsg.pagination.random; + +import org.geotools.util.Version; + +/** + * + * @author sandr + * + */ + +public class ResultTypeKvpParser extends org.geoserver.wfs.kvp.v2_0.ResultTypeKvpParser { + + public ResultTypeKvpParser() { + super(); + setVersion(new Version("2.0.2")); + } + +} diff --git a/src/community/nsg-profile/src/main/resources/applicationContext.xml b/src/community/nsg-profile/src/main/resources/applicationContext.xml index acdcb9c2caa..842c5b9ce9b 100644 --- a/src/community/nsg-profile/src/main/resources/applicationContext.xml +++ b/src/community/nsg-profile/src/main/resources/applicationContext.xml @@ -1,7 +1,6 @@ @@ -18,8 +17,9 @@ - + + When a request using the index result type comes in a dispatcher callback @@ -27,11 +27,39 @@ + + + + The PageResults operation will allow clients to query + random positions of an existing result set (stored GetFeature + request) that was previously created using the index result type + + + + + + + + + + + + + + + PageResults + + + + + - - + - + From a7d31493c25c74ddde4f73ce4b3c4b5154b51c7f Mon Sep 17 00:00:00 2001 From: Nuno Oliveira Date: Sun, 23 Jul 2017 00:22:24 +0100 Subject: [PATCH 07/15] Add WFS time versioning --- src/community/nsg-profile/pom.xml | 86 ++++++ .../org/geoserver/nsg/TimeVersioning.java | 58 +++++ .../geoserver/nsg/TimeVersioningCallback.java | 244 ++++++++++++++++++ .../nsg/VersioningFilterAdapter.java | 110 ++++++++ .../nsg/web/WfsVersioningConfig.html | 32 +++ .../nsg/web/WfsVersioningConfig.java | 153 +++++++++++ .../resources/GeoServerApplication.properties | 4 + .../src/main/resources/applicationContext.xml | 19 ++ .../java/org/geoserver/nsg/TestsUtils.java | 39 +++ .../org/geoserver/nsg/TimeVersioningTest.java | 99 +++++++ .../nsg/web/WfsVersioningConfigTest.java | 106 ++++++++ .../org/geoserver/nsg/versioned.properties | 6 + .../test/resources/requests/get_request_1.xml | 13 + .../resources/requests/insert_request_1.xml | 22 ++ .../resources/requests/update_request_1.xml | 18 ++ src/community/pom.xml | 10 +- src/community/release/ext-nsg-profile.xml | 16 ++ .../geoserver/catalog/FeatureTypeInfo.java | 7 + .../catalog/impl/FeatureTypeInfoImpl.java | 33 ++- .../decorators/DecoratingFeatureTypeInfo.java | 9 + .../decorators/SecuredFeatureTypeInfo.java | 3 +- src/web/app/pom.xml | 8 +- .../security/web/user/UserPanel.java | 2 +- .../java/org/geoserver/wfs/GetFeature.java | 16 ++ .../org/geoserver/wfs/GetFeatureCallback.java | 20 ++ .../org/geoserver/wfs/GetFeatureContext.java | 44 ++++ .../wfs/GetFeatureContextBuilder.java | 54 ++++ .../geoserver/wfs/InsertElementHandler.java | 41 ++- .../java/org/geoserver/wfs/Transaction.java | 17 ++ .../geoserver/wfs/TransactionCallback.java | 49 ++++ .../org/geoserver/wfs/TransactionContext.java | 49 ++++ .../wfs/TransactionContextBuilder.java | 61 +++++ 32 files changed, 1415 insertions(+), 33 deletions(-) create mode 100644 src/community/nsg-profile/pom.xml create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioning.java create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioningCallback.java create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/VersioningFilterAdapter.java create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.html create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.java create mode 100644 src/community/nsg-profile/src/main/resources/GeoServerApplication.properties create mode 100644 src/community/nsg-profile/src/main/resources/applicationContext.xml create mode 100644 src/community/nsg-profile/src/test/java/org/geoserver/nsg/TestsUtils.java create mode 100644 src/community/nsg-profile/src/test/java/org/geoserver/nsg/TimeVersioningTest.java create mode 100644 src/community/nsg-profile/src/test/java/org/geoserver/nsg/web/WfsVersioningConfigTest.java create mode 100644 src/community/nsg-profile/src/test/resources/org/geoserver/nsg/versioned.properties create mode 100644 src/community/nsg-profile/src/test/resources/requests/get_request_1.xml create mode 100644 src/community/nsg-profile/src/test/resources/requests/insert_request_1.xml create mode 100644 src/community/nsg-profile/src/test/resources/requests/update_request_1.xml create mode 100644 src/community/release/ext-nsg-profile.xml create mode 100644 src/wfs/src/main/java/org/geoserver/wfs/GetFeatureCallback.java create mode 100644 src/wfs/src/main/java/org/geoserver/wfs/GetFeatureContext.java create mode 100644 src/wfs/src/main/java/org/geoserver/wfs/GetFeatureContextBuilder.java create mode 100644 src/wfs/src/main/java/org/geoserver/wfs/TransactionCallback.java create mode 100644 src/wfs/src/main/java/org/geoserver/wfs/TransactionContext.java create mode 100644 src/wfs/src/main/java/org/geoserver/wfs/TransactionContextBuilder.java diff --git a/src/community/nsg-profile/pom.xml b/src/community/nsg-profile/pom.xml new file mode 100644 index 00000000000..b9e26ef3fbc --- /dev/null +++ b/src/community/nsg-profile/pom.xml @@ -0,0 +1,86 @@ + + + 4.0.0 + + org.geoserver + community + 2.12-SNAPSHOT + + org.geoserver.community + gs-nsg-profile + jar + 2.12-SNAPSHOT + NSG Profile + + + + org.geoserver + gs-main + tests + test + + + org.geoserver + gs-wfs + ${project.version} + + + org.geoserver.web + gs-web-core + ${project.version} + + + + org.geoserver + gs-wfs + tests + ${project.version} + + + org.geoserver.web + gs-web-core + ${project.version} + tests + test + + + + org.hamcrest + hamcrest-library + test + + + org.springframework + spring-test + test + + + junit + junit + test + + + javax.servlet + javax.servlet-api + test + + + + + + ${basedir}/src/main/java + + **/*.html + + + + ${basedir}/src/main/resources + + **/* + + + + + + diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioning.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioning.java new file mode 100644 index 00000000000..ad891d29929 --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioning.java @@ -0,0 +1,58 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.nsg; + +import org.geoserver.catalog.FeatureTypeInfo; + +public final class TimeVersioning { + + public static final String ENABLED_KEY = "TIME_VERSIONING_ENABLED"; + public static final String ID_PROPERTY_KEY = "TIME_VERSIONING_ID_PROPERTY"; + public static final String TIME_PROPERTY_KEY = "TIME_VERSIONING_TIME_PROPERTY"; + + public static void enable(FeatureTypeInfo featureTypeInfo, String idProperty, String timeProperty) { + featureTypeInfo.putParameter(ENABLED_KEY, true); + featureTypeInfo.putParameter(ID_PROPERTY_KEY, idProperty); + featureTypeInfo.putParameter(TIME_PROPERTY_KEY, timeProperty); + } + + public static void disable(FeatureTypeInfo featureTypeInfo) { + featureTypeInfo.putParameter(ENABLED_KEY, false); + featureTypeInfo.putParameter(ID_PROPERTY_KEY, null); + featureTypeInfo.putParameter(TIME_PROPERTY_KEY, null); + } + + public static boolean isEnabled(FeatureTypeInfo featureTypeInfo) { + return featureTypeInfo.getParameter(ENABLED_KEY, Boolean.class, false); + } + + public static String getIdPropertyName(FeatureTypeInfo featureTypeInfo) { + String idPropertyName = featureTypeInfo.getParameter(ID_PROPERTY_KEY, String.class, null); + if (idPropertyName == null) { + throw new RuntimeException("No id property name was provided."); + } + return idPropertyName; + } + + public static String getTimePropertyName(FeatureTypeInfo featureTypeInfo) { + String timePropertyName = featureTypeInfo.getParameter(TIME_PROPERTY_KEY, String.class, null); + if (timePropertyName == null) { + throw new RuntimeException("No time property name was provided."); + } + return timePropertyName; + } + + public static void setEnable(FeatureTypeInfo featureTypeInfo, boolean enable) { + featureTypeInfo.putParameter(ENABLED_KEY, enable); + } + + public static void setIdAttribute(FeatureTypeInfo featureTypeInfo, String idAttributeName) { + featureTypeInfo.putParameter(ID_PROPERTY_KEY, idAttributeName); + } + + public static void setTimeAttribute(FeatureTypeInfo featureTypeInfo, String timeAttributeName) { + featureTypeInfo.putParameter(TIME_PROPERTY_KEY, timeAttributeName); + } +} diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioningCallback.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioningCallback.java new file mode 100644 index 00000000000..ee87f12f628 --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioningCallback.java @@ -0,0 +1,244 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.nsg; + +import org.geoserver.catalog.Catalog; +import org.geoserver.catalog.FeatureTypeInfo; +import org.geoserver.platform.GeoServerExtensions; +import org.geoserver.wfs.GetFeatureCallback; +import org.geoserver.wfs.GetFeatureContext; +import org.geoserver.wfs.InsertElementHandler; +import org.geoserver.wfs.TransactionCallback; +import org.geoserver.wfs.TransactionContext; +import org.geoserver.wfs.TransactionContextBuilder; +import org.geoserver.wfs.request.Insert; +import org.geoserver.wfs.request.RequestObject; +import org.geoserver.wfs.request.Update; +import org.geotools.data.DataUtilities; +import org.geotools.data.FeatureStore; +import org.geotools.data.Query; +import org.geotools.data.simple.SimpleFeatureCollection; +import org.geotools.data.simple.SimpleFeatureIterator; +import org.geotools.data.simple.SimpleFeatureStore; +import org.geotools.factory.CommonFactoryFinder; +import org.geotools.factory.GeoTools; +import org.geotools.feature.NameImpl; +import org.geotools.feature.simple.SimpleFeatureBuilder; +import org.geotools.util.Converters; +import org.opengis.feature.simple.SimpleFeature; +import org.opengis.feature.type.AttributeDescriptor; +import org.opengis.feature.type.FeatureType; +import org.opengis.feature.type.Name; +import org.opengis.filter.Filter; +import org.opengis.filter.FilterFactory2; +import org.opengis.filter.sort.SortBy; +import org.opengis.filter.sort.SortOrder; + +import javax.xml.namespace.QName; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +final class TimeVersioningCallback implements GetFeatureCallback, TransactionCallback { + + private static final FilterFactory2 FILTER_FACTORY = CommonFactoryFinder.getFilterFactory2(GeoTools.getDefaultHints()); + + private final Catalog catalog; + + TimeVersioningCallback(Catalog catalog) { + this.catalog = catalog; + } + + @Override + public GetFeatureContext beforeQuerying(GetFeatureContext context) { + if (!isWfs20(context.getRequest())) { + return context; + } + FeatureTypeInfo featureTypeInfo = context.getFeatureTypeInfo(); + if (!TimeVersioning.isEnabled(featureTypeInfo)) { + // time versioning is not enabled for this feature type or is not a WFS 2.0 request + return context; + } + VersioningFilterAdapter.adapt(featureTypeInfo, context.getQuery().getFilter()); + SortBy sort = FILTER_FACTORY.sort(TimeVersioning.getTimePropertyName(featureTypeInfo), SortOrder.DESCENDING); + SortBy[] sorts = context.getQuery().getSortBy(); + if (sorts == null) { + sorts = new SortBy[]{sort}; + } else { + sorts = Arrays.copyOf(sorts, sorts.length + 1); + sorts[sorts.length - 1] = sort; + } + context.getQuery().setSortBy(sorts); + return context; + } + + @Override + public TransactionContext beforeHandlerExecution(TransactionContext context) { + if (!isWfs20(context.getRequest())) { + return context; + } + if (context.getElement() instanceof Update) { + Insert insert = buildInsertForUpdate(context); + InsertElementHandler handler = GeoServerExtensions.bean(InsertElementHandler.class); + return new TransactionContextBuilder() + .withContext(context) + .withElement(insert) + .withHandler(handler).build(); + } + if (context.getElement() instanceof Insert) { + Insert insert = (Insert) context.getElement(); + for (Object element : insert.getFeatures()) { + if (element instanceof SimpleFeature) { + setTimeAttribute((SimpleFeature) element); + } + } + } + return context; + } + + @Override + public TransactionContext beforeInsertFeatures(TransactionContext context) { + return context; + } + + @Override + public TransactionContext beforeUpdateFeatures(TransactionContext context) { + return context; + } + + @Override + public TransactionContext beforeDeleteFeatures(TransactionContext context) { + return context; + } + + @Override + public TransactionContext beforeReplaceFeatures(TransactionContext context) { + return context; + } + + private void setTimeAttribute(SimpleFeature feature) { + FeatureType featureType = feature.getFeatureType(); + FeatureTypeInfo featureTypeInfo = getFeatureTypeInfo(featureType); + if (TimeVersioning.isEnabled(featureTypeInfo)) { + String timePropertyName = TimeVersioning.getTimePropertyName(featureTypeInfo); + AttributeDescriptor attributeDescriptor = feature.getType().getDescriptor(timePropertyName); + Object timeValue = Converters.convert(new Date(), attributeDescriptor.getType().getBinding()); + feature.setAttribute(timePropertyName, timeValue); + } + } + + private SimpleFeatureCollection getTransactionFeatures(TransactionContext context) { + QName typeName = context.getElement().getTypeName(); + Filter filter = context.getElement().getFilter(); + FeatureTypeInfo featureTypeInfo = getFeatureTypeInfo(new NameImpl(typeName)); + SimpleFeatureStore store = getTransactionStore(context); + try { + Query query = new Query(); + query.setFilter(VersioningFilterAdapter.adapt(featureTypeInfo, filter)); + SortBy sort = FILTER_FACTORY.sort(TimeVersioning.getTimePropertyName(featureTypeInfo), SortOrder.DESCENDING); + query.setSortBy(new SortBy[]{sort}); + return store.getFeatures(query); + } catch (Exception exception) { + throw new RuntimeException(String.format( + "Error getting features of type '%s'.", typeName), exception); + } + } + + private Comparator buildFeatureTimeComparator(FeatureTypeInfo featureTypeInfo) { + String timePropertyName = TimeVersioning.getTimePropertyName(featureTypeInfo); + return (featureA, featureB) -> { + Date timeA = Converters.convert(featureA.getAttribute(timePropertyName), Date.class); + Date timeB = Converters.convert(featureB.getAttribute(timePropertyName), Date.class); + if (timeA == null) { + return -1; + } + return timeA.compareTo(timeB); + }; + } + + private List getOnlyRecentFeatures(SimpleFeatureCollection features, FeatureTypeInfo featureTypeInfo) { + String idPropertyName = TimeVersioning.getIdPropertyName(featureTypeInfo); + Map> featuresIndexedById = new HashMap<>(); + SimpleFeatureIterator iterator = features.features(); + while (iterator.hasNext()) { + SimpleFeature feature = iterator.next(); + Object id = feature.getAttribute(idPropertyName); + List existing = featuresIndexedById.computeIfAbsent(id, key -> new ArrayList<>()); + existing.add(feature); + } + Comparator comparator = buildFeatureTimeComparator(featureTypeInfo); + List finalFeatures = new ArrayList<>(); + featuresIndexedById.values().forEach(indexed -> { + indexed.sort(comparator); + SimpleFeature feature = indexed.get(0); + SimpleFeatureBuilder builder = new SimpleFeatureBuilder(feature.getFeatureType()); + builder.init(feature); + finalFeatures.add(builder.buildFeature(null)); + }); + return finalFeatures; + } + + private Insert buildInsertForUpdate(TransactionContext context) { + Update update = (Update) context.getElement(); + FeatureTypeInfo featureTypeInfo = getFeatureTypeInfo(new NameImpl(update.getTypeName())); + SimpleFeatureCollection features = getTransactionFeatures(context); + List recent = getOnlyRecentFeatures(features, featureTypeInfo); + List newFeatures = recent.stream() + .map(this::prepareInsertFeature).collect(Collectors.toList()); + return new UpdateInsert(context.getRequest(), newFeatures); + } + + private SimpleFeature prepareInsertFeature(SimpleFeature feature) { + SimpleFeatureBuilder builder = new SimpleFeatureBuilder(feature.getFeatureType()); + builder.init(feature); + SimpleFeature versionedFeature = builder.buildFeature(null); + setTimeAttribute(versionedFeature); + return versionedFeature; + } + + private FeatureTypeInfo getFeatureTypeInfo(FeatureType featureType) { + Name featureTypeName = featureType.getName(); + return getFeatureTypeInfo(featureTypeName); + } + + private FeatureTypeInfo getFeatureTypeInfo(Name featureTypeName) { + FeatureTypeInfo featureTypeInfo = catalog.getFeatureTypeByName(featureTypeName); + if (featureTypeInfo == null) { + throw new RuntimeException(String.format( + "Couldn't find feature type info ''%s.", featureTypeName)); + } + return featureTypeInfo; + } + + private SimpleFeatureStore getTransactionStore(TransactionContext context) { + QName typeName = context.getElement().getTypeName(); + FeatureStore store = (FeatureStore) context.getFeatureStores().get(typeName); + return DataUtilities.simple(store); + } + + private boolean isWfs20(RequestObject request) { + return true; + } + + private static final class UpdateInsert extends Insert { + + private final List features; + + protected UpdateInsert(RequestObject request, List features) { + super(request.getAdaptee()); + this.features = features; + } + + @Override + public List getFeatures() { + return features; + } + } +} diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/VersioningFilterAdapter.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/VersioningFilterAdapter.java new file mode 100644 index 00000000000..6a0b1c68222 --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/VersioningFilterAdapter.java @@ -0,0 +1,110 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.nsg; + +import org.geoserver.catalog.FeatureTypeInfo; +import org.geotools.factory.CommonFactoryFinder; +import org.geotools.factory.GeoTools; +import org.geotools.filter.visitor.DuplicatingFilterVisitor; +import org.opengis.filter.Filter; +import org.opengis.filter.FilterFactory; +import org.opengis.filter.FilterFactory2; +import org.opengis.filter.Id; +import org.opengis.filter.expression.Expression; +import org.opengis.filter.identity.Identifier; +import org.opengis.filter.identity.ResourceId; +import org.opengis.filter.sort.SortOrder; + +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +final class VersioningFilterAdapter extends DuplicatingFilterVisitor { + + private final String idPropertyName; + private final String timePropertyName; + + private VersioningFilterAdapter(FeatureTypeInfo featureTypeInfo) { + this.idPropertyName = TimeVersioning.getIdPropertyName(featureTypeInfo); + this.timePropertyName = TimeVersioning.getTimePropertyName(featureTypeInfo); + } + + @Override + public Object visit(Id filter, Object extraData) { + FilterFactory filterFactory = getFactory(extraData); + Set ids = filter.getIdentifiers(); + Set finalIds = new HashSet<>(); + Filter versioningFilter = null; + for (Identifier id : ids) { + if (id instanceof ResourceId) { + Filter newFilter = buildVersioningFilter(filterFactory, (ResourceId) id); + versioningFilter = addFilter(filterFactory, versioningFilter, newFilter); + } else { + finalIds.add(id); + } + } + if (finalIds.isEmpty()) { + return versioningFilter; + } + Filter newIdFilter = getFactory(extraData).id(finalIds); + if (versioningFilter != null) { + return filterFactory.and(newIdFilter, versioningFilter); + } + return newIdFilter; + } + + private Filter buildVersioningFilter(FilterFactory filterFactory, ResourceId resourceId) { + Filter idFilter = buildIdFilter(filterFactory, resourceId.getID()); + Filter timeFilter = buildTimeFilter(filterFactory, resourceId.getStartTime(), resourceId.getEndTime()); + if (idFilter != null && timeFilter != null) { + return filterFactory.and(idFilter, timeFilter); + } + if (idFilter != null) { + return idFilter; + } + if (timeFilter != null) { + return timeFilter; + } + return null; + } + + private Filter buildIdFilter(FilterFactory factory, String id) { + if (id == null) { + return null; + } + return factory.equals(factory.property(idPropertyName), factory.literal(id)); + } + + private Filter buildTimeFilter(FilterFactory filterFactory, Date start, Date end) { + Expression timeProperty = filterFactory.property(timePropertyName); + Expression startLiteral = filterFactory.literal(start); + Expression endLiteral = filterFactory.literal(end); + Filter after = filterFactory.after(timeProperty, startLiteral); + Filter before = filterFactory.before(timeProperty, endLiteral); + if (start != null && end != null) { + return filterFactory.and(after, before); + } + if (start != null) { + return after; + } + if (end != null) { + return before; + } + return null; + } + + private Filter addFilter(FilterFactory filterFactory, Filter versioningFilter, Filter filter) { + if (versioningFilter != null) { + return filterFactory.and(versioningFilter, filter); + } + return filter; + } + + static Filter adapt(FeatureTypeInfo featureTypeInfo, Filter filter) { + String timePropertyName = TimeVersioning.getTimePropertyName(featureTypeInfo); + VersioningFilterAdapter adapter = new VersioningFilterAdapter(featureTypeInfo); + return (Filter) filter.accept(adapter, null); + } +} diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.html b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.html new file mode 100644 index 00000000000..b67d24989c7 --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.html @@ -0,0 +1,32 @@ + + + +
+ +

+ WFS Versioning +

+
    +
  • +
    + + +
    +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+ + diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.java new file mode 100644 index 00000000000..8ef574e602c --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.java @@ -0,0 +1,153 @@ +/* (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.nsg.web; + +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; +import org.apache.wicket.ajax.markup.html.form.AjaxCheckBox; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.form.CheckBox; +import org.apache.wicket.markup.html.form.DropDownChoice; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.StringResourceModel; +import org.geoserver.catalog.FeatureTypeInfo; +import org.geoserver.catalog.LayerInfo; +import org.geoserver.nsg.TimeVersioning; +import org.geoserver.web.publish.PublishedConfigurationPanel; + +import java.sql.Timestamp; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +public class WfsVersioningConfig extends PublishedConfigurationPanel { + + public WfsVersioningConfig(String id, IModel model) { + super(id, model); + // get the needed information from the model + FeatureTypeInfo featureTypeInfo = getFeatureTypeInfo(model); + boolean isVersioningActivated = TimeVersioning.isEnabled(featureTypeInfo); + String idAttributeName = isVersioningActivated ? TimeVersioning.getIdPropertyName(featureTypeInfo) : null; + String timeAttributeName = isVersioningActivated ? TimeVersioning.getTimePropertyName(featureTypeInfo) : null; + List attributesNames = getAttributesNames(featureTypeInfo); + List timeAttributesNames = getTimeAttributesNames(featureTypeInfo); + // create dropdown choice for the id attribute name + DropDownChoice idAttributeChoice = new DropDownChoice<>("idAttributeChoice", + new Model<>(idAttributeName), attributesNames); + idAttributeChoice.add(new AjaxFormComponentUpdatingBehavior("change") { + @Override + protected void onUpdate(AjaxRequestTarget target) { + String selected = idAttributeChoice.getModel().getObject(); + TimeVersioning.setIdAttribute(featureTypeInfo, selected); + } + }); + idAttributeChoice.setOutputMarkupId(true); + idAttributeChoice.setOutputMarkupPlaceholderTag(true); + idAttributeChoice.setRequired(true); + idAttributeChoice.setVisible(isVersioningActivated); + add(idAttributeChoice); + // add label for id attribute name dropdown choice + Label idAttributeChoiceLabel = new Label("idAttributeChoiceLabel", + new StringResourceModel("WfsVersioningConfig.idAttributeChoiceLabel")); + idAttributeChoiceLabel.setOutputMarkupId(true); + idAttributeChoiceLabel.setOutputMarkupPlaceholderTag(true); + idAttributeChoiceLabel.setVisible(isVersioningActivated); + add(idAttributeChoiceLabel); + // create dropdown choice for the time attribute name + DropDownChoice timeAttributeChoice = new DropDownChoice<>("timeAttributeChoice", + new Model<>(timeAttributeName), timeAttributesNames); + timeAttributeChoice.add(new AjaxFormComponentUpdatingBehavior("change") { + @Override + protected void onUpdate(AjaxRequestTarget target) { + String selected = timeAttributeChoice.getModel().getObject(); + TimeVersioning.setTimeAttribute(featureTypeInfo, selected); + } + }); + timeAttributeChoice.setOutputMarkupId(true); + timeAttributeChoice.setOutputMarkupPlaceholderTag(true); + timeAttributeChoice.setRequired(true); + timeAttributeChoice.setVisible(isVersioningActivated); + add(timeAttributeChoice); + // add label for id attribute name dropdown choice + Label timeAttributeChoiceLabel = new Label("timeAttributeChoiceLabel", + new StringResourceModel("WfsVersioningConfig.timeAttributeChoiceLabel")); + timeAttributeChoiceLabel.setOutputMarkupId(true); + timeAttributeChoiceLabel.setOutputMarkupPlaceholderTag(true); + timeAttributeChoiceLabel.setVisible(isVersioningActivated); + add(timeAttributeChoiceLabel); + // checkbox for activating versioning + CheckBox versioningActivateCheckBox = new AjaxCheckBox("versioningActivateCheckBox", + new Model<>(isVersioningActivated)) { + @Override + protected void onUpdate(AjaxRequestTarget target) { + boolean checked = getModelObject(); + if (checked) { + // activate versioning attributes selection + idAttributeChoice.setVisible(true); + idAttributeChoiceLabel.setVisible(true); + timeAttributeChoice.setVisible(true); + timeAttributeChoiceLabel.setVisible(true); + // enable time versioning + TimeVersioning.setEnable(featureTypeInfo, true); + } else { + // deactivate versioning attributes selection + idAttributeChoice.setVisible(false); + idAttributeChoiceLabel.setVisible(false); + timeAttributeChoice.setVisible(false); + timeAttributeChoiceLabel.setVisible(false); + // disable time versioning + TimeVersioning.setEnable(featureTypeInfo, false); + } + // update the dropdown choices and labels + target.add(idAttributeChoice); + target.add(idAttributeChoiceLabel); + target.add(timeAttributeChoice); + target.add(timeAttributeChoiceLabel); + } + }; + if (isVersioningActivated) { + versioningActivateCheckBox.setModelObject(true); + } + versioningActivateCheckBox.setEnabled(!timeAttributesNames.isEmpty()); + add(versioningActivateCheckBox); + // add versioning activating checkbox label + Label versioningActivateCheckBoxLabel = new Label("versioningActivateCheckBoxLabel", + new StringResourceModel("WfsVersioningConfig.versioningActivateCheckBoxLabel")); + add(versioningActivateCheckBoxLabel); + } + + private List getAttributesNames(FeatureTypeInfo featureTypeInfo) { + try { + return featureTypeInfo.getFeatureType().getDescriptors().stream() + .map(attribute -> attribute.getName().getLocalPart()) + .collect(Collectors.toList()); + } catch (Exception exception) { + throw new RuntimeException(String.format( + "Error processing attributes of feature type '%s'.", + featureTypeInfo.getName()), exception); + } + } + + private List getTimeAttributesNames(FeatureTypeInfo featureTypeInfo) { + try { + return featureTypeInfo.getFeatureType().getDescriptors().stream().filter(attribute -> { + Class binding = attribute.getType().getBinding(); + return Long.class.isAssignableFrom(binding) + || Date.class.isAssignableFrom(binding) + || Timestamp.class.isAssignableFrom(binding); + }).map(attribute -> attribute.getName().getLocalPart()).collect(Collectors.toList()); + } catch (Exception exception) { + throw new RuntimeException(String.format( + "Error processing attributes of feature type '%s'.", + featureTypeInfo.getName()), exception); + } + } + + private FeatureTypeInfo getFeatureTypeInfo(IModel model) { + return (FeatureTypeInfo) model.getObject().getResource(); + } +} diff --git a/src/community/nsg-profile/src/main/resources/GeoServerApplication.properties b/src/community/nsg-profile/src/main/resources/GeoServerApplication.properties new file mode 100644 index 00000000000..e8f4ddacbf6 --- /dev/null +++ b/src/community/nsg-profile/src/main/resources/GeoServerApplication.properties @@ -0,0 +1,4 @@ +WfsVersioningConfig.wfsVersioning=WFS Versioning +WfsVersioningConfig.versioningActivateCheckBoxLabel=Activate Versioning +WfsVersioningConfig.idAttributeChoiceLabel=Id Attribute +WfsVersioningConfig.timeAttributeChoiceLabel=Time Attribute \ No newline at end of file diff --git a/src/community/nsg-profile/src/main/resources/applicationContext.xml b/src/community/nsg-profile/src/main/resources/applicationContext.xml new file mode 100644 index 00000000000..a33c1190b39 --- /dev/null +++ b/src/community/nsg-profile/src/main/resources/applicationContext.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + org.geoserver.catalog.FeatureTypeInfo + + + + diff --git a/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TestsUtils.java b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TestsUtils.java new file mode 100644 index 00000000000..653ac8a8e4b --- /dev/null +++ b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TestsUtils.java @@ -0,0 +1,39 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.nsg; + +import org.geoserver.catalog.Catalog; +import org.geoserver.catalog.FeatureTypeInfo; +import org.geoserver.util.IOUtils; + +import java.io.InputStream; + +import static org.geoserver.nsg.TimeVersioning.disable; +import static org.geoserver.nsg.TimeVersioning.enable; + +final class TestsUtils { + + private TestsUtils() { + } + + static String readResource(String resourceName) { + try (InputStream input = TestsUtils.class.getResourceAsStream(resourceName)) { + return IOUtils.toString(input); + } catch (Exception exception) { + throw new RuntimeException(String.format("Error reading resource '%s'.", resourceName)); + } + } + + static void updateFeatureTypeTimeVersioning(Catalog catalog, String featureTypeName, + boolean enabled, String idProperty, String timeProperty) { + FeatureTypeInfo featureType = catalog.getFeatureTypeByName(featureTypeName); + if (enabled) { + enable(featureType, idProperty, timeProperty); + } else { + disable(featureType); + } + catalog.save(featureType); + } +} diff --git a/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TimeVersioningTest.java b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TimeVersioningTest.java new file mode 100644 index 00000000000..758d1fa3370 --- /dev/null +++ b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TimeVersioningTest.java @@ -0,0 +1,99 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.nsg; + +import org.custommonkey.xmlunit.SimpleNamespaceContext; +import org.custommonkey.xmlunit.XMLUnit; +import org.custommonkey.xmlunit.XpathEngine; +import org.geoserver.data.test.MockData; +import org.geoserver.data.test.SystemTestData; +import org.geoserver.test.GeoServerSystemTestSupport; +import org.geotools.geometry.jts.ReferencedEnvelope; +import org.geotools.referencing.crs.DefaultGeographicCRS; +import org.junit.Before; +import org.junit.Test; +import org.w3c.dom.Document; + +import javax.xml.namespace.QName; +import java.util.HashMap; +import java.util.Map; + +public final class TimeVersioningTest extends GeoServerSystemTestSupport { + + private XpathEngine WFS20_XPATH_ENGINE; + + @Override + protected void onSetUp(SystemTestData testData) throws Exception { + super.setUpTestData(testData); + // create bounding box definitions + ReferencedEnvelope envelope = new ReferencedEnvelope(-5, -5, 5, 5, DefaultGeographicCRS.WGS84); + Map properties = new HashMap<>(); + properties.put(SystemTestData.LayerProperty.LATLON_ENVELOPE, envelope); + properties.put(SystemTestData.LayerProperty.ENVELOPE, envelope); + properties.put(SystemTestData.LayerProperty.SRS, 4326); + // create versioned layer + QName versionedLayerName = new QName(MockData.DEFAULT_URI, "versioned", MockData.DEFAULT_PREFIX); + testData.addVectorLayer(versionedLayerName, properties, "versioned.properties", getClass(), getCatalog()); + // instantiate xpath engine + buildXpathEngine( + "wfs", "http://www.opengis.net/wfs/2.0", + "gml", "http://www.opengis.net/gml/3.2"); + } + + @Before + public void beforeTest() { + // activate versioning for versioned layer + TestsUtils.updateFeatureTypeTimeVersioning(getCatalog(), "gs:versioned", true, "ID", "TIME"); + } + + @Test + public void testGetFeatureVersioned() throws Exception { + Document result = postAsDOM("wfs", TestsUtils.readResource("/requests/get_request_1.xml")); + System.out.println("aa"); + } + + @Test + public void testInsertVersionedFeature() throws Exception { + Document result = postAsDOM("wfs", TestsUtils.readResource("/requests/insert_request_1.xml")); + System.out.println("aa"); + } + + @Test + public void testUpdateVersionedFeature() throws Exception { + Document result = postAsDOM("wfs", TestsUtils.readResource("/requests/update_request_1.xml")); + System.out.println("aa"); + } + + /** + * Helper method that builds a xpath engine using some predefined + * namespaces and all the catalog namespaces. The provided namespaces + * will be added overriding any existing namespace. + */ + private XpathEngine buildXpathEngine(String... namespaces) { + // build xpath engine + XpathEngine xpathEngine = XMLUnit.newXpathEngine(); + Map finalNamespaces = new HashMap<>(); + // add common namespaces + finalNamespaces.put("ows", "http://www.opengis.net/ows"); + finalNamespaces.put("ogc", "http://www.opengis.net/ogc"); + finalNamespaces.put("xs", "http://www.w3.org/2001/XMLSchema"); + finalNamespaces.put("xsd", "http://www.w3.org/2001/XMLSchema"); + finalNamespaces.put("xlink", "http://www.w3.org/1999/xlink"); + finalNamespaces.put("xsi", "http://www.w3.org/2001/XMLSchema-instance"); + // add al catalog namespaces + getCatalog().getNamespaces().forEach(namespace -> + finalNamespaces.put(namespace.getPrefix(), namespace.getURI())); + // add provided namespaces + if (namespaces.length % 2 != 0) { + throw new RuntimeException("Invalid number of namespaces provided."); + } + for (int i = 0; i < namespaces.length; i += 2) { + finalNamespaces.put(namespaces[i], namespaces[i + 1]); + } + // add namespaces to the xpath engine + xpathEngine.setNamespaceContext(new SimpleNamespaceContext(finalNamespaces)); + return xpathEngine; + } +} diff --git a/src/community/nsg-profile/src/test/java/org/geoserver/nsg/web/WfsVersioningConfigTest.java b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/web/WfsVersioningConfigTest.java new file mode 100644 index 00000000000..efe29b00e6a --- /dev/null +++ b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/web/WfsVersioningConfigTest.java @@ -0,0 +1,106 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.nsg.web; + +import org.apache.wicket.markup.html.form.CheckBox; +import org.apache.wicket.util.tester.FormTester; +import org.geoserver.web.GeoServerWicketTestSupport; +import org.geoserver.web.publish.PublishedConfigurationPage; +import org.geoserver.wfs.GMLInfo; +import org.geoserver.wfs.WFSInfo; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +public class WfsVersioningConfigTest extends GeoServerWicketTestSupport { + + @Test + public void testPageStart() throws Exception { + WFSInfo wfs = getGeoServerApplication().getGeoServer().getService(WFSInfo.class); + + login(); + tester.startPage(PublishedConfigurationPage.class); + } + + /*@Test + public void testChangesToValues() throws Exception { + String testValue1 = "100", testValue2 = "0"; + WFSInfo wfs = getGeoServerApplication().getGeoServer().getService(WFSInfo.class); + login(); + tester.startPage(WFSAdminPage.class); + FormTester ft = tester.newFormTester("form"); + ft.setValue("maxNumberOfFeaturesForPreview", (String)testValue1); + ft.submit("submit"); + wfs = getGeoServerApplication().getGeoServer().getService(WFSInfo.class); + assertEquals("testValue1 = 100", 100, (int)wfs.getMaxNumberOfFeaturesForPreview()); + tester.startPage(WFSAdminPage.class); + ft = tester.newFormTester("form"); + ft.setValue("maxNumberOfFeaturesForPreview", (String)testValue2); + ft.submit("submit"); + wfs = getGeoServerApplication().getGeoServer().getService(WFSInfo.class); + assertEquals("testValue2 = 0", 0, (int)wfs.getMaxNumberOfFeaturesForPreview()); + } + + @Test + public void testGML32ForceMimeType() throws Exception { + // make sure GML MIME type overriding is disabled + WFSInfo info = getGeoServer().getService(WFSInfo.class); + GMLInfo gmlInfo = info.getGML().get(WFSInfo.Version.V_20); + gmlInfo.setMimeTypeToForce(null); + getGeoServer().save(info); + // login with administrator privileges + login(); + // start WFS service administration page + tester.startPage(new WFSAdminPage()); + // check that GML MIME type overriding is disabled + tester.assertComponent("form:gml32:forceGmlMimeType", CheckBox.class); + CheckBox checkbox = (CheckBox) tester.getComponentFromLastRenderedPage("form:gml32:forceGmlMimeType"); + assertThat(checkbox.getModelObject(), is(false)); + // MIME type drop down choice should be invisible + tester.assertInvisible("form:gml32:mimeTypeToForce"); + // activate MIME type overriding by clicking in the checkbox + FormTester formTester = tester.newFormTester("form"); + formTester.setValue("gml32:forceGmlMimeType", true); + tester.executeAjaxEvent("form:gml32:forceGmlMimeType", "click"); + formTester = tester.newFormTester("form"); + formTester.submit("submit"); + // GML MIME typing overriding should be activated now + tester.startPage(new WFSAdminPage()); + assertThat(checkbox.getModelObject(), is(true)); + tester.assertVisible("form:gml32:mimeTypeToForce"); + // WFS global service configuration should have been updated too + info = getGeoServer().getService(WFSInfo.class); + gmlInfo = info.getGML().get(WFSInfo.Version.V_20); + assertThat(gmlInfo.getMimeTypeToForce().isPresent(), is(true)); + // select text / xml as MIME type to force + formTester = tester.newFormTester("form"); + formTester.select("gml32:mimeTypeToForce", 2); + tester.executeAjaxEvent("form:gml32:mimeTypeToForce", "change"); + formTester = tester.newFormTester("form"); + formTester.submit("submit"); + // WFS global service configuration should be forcing text / xml + info = getGeoServer().getService(WFSInfo.class); + gmlInfo = info.getGML().get(WFSInfo.Version.V_20); + assertThat(gmlInfo.getMimeTypeToForce().isPresent(), is(true)); + assertThat(gmlInfo.getMimeTypeToForce().get(), is("text/xml")); + // deactivate GML MIME type overriding by clicking in the checkbox + tester.startPage(new WFSAdminPage()); + formTester = tester.newFormTester("form"); + formTester.setValue("gml32:forceGmlMimeType", false); + tester.executeAjaxEvent("form:gml32:forceGmlMimeType", "click"); + formTester = tester.newFormTester("form"); + formTester.submit("submit"); + // GML MIME type overriding should be deactivated now + tester.startPage(new WFSAdminPage()); + assertThat(checkbox.getModelObject(), is(true)); + tester.assertInvisible("form:gml32:mimeTypeToForce"); + // WFS global service configuration should have been updated too + info = getGeoServer().getService(WFSInfo.class); + gmlInfo = info.getGML().get(WFSInfo.Version.V_20); + assertThat(gmlInfo.getMimeTypeToForce().isPresent(), is(false)); + }*/ +} diff --git a/src/community/nsg-profile/src/test/resources/org/geoserver/nsg/versioned.properties b/src/community/nsg-profile/src/test/resources/org/geoserver/nsg/versioned.properties new file mode 100644 index 00000000000..ad32708279a --- /dev/null +++ b/src/community/nsg-profile/src/test/resources/org/geoserver/nsg/versioned.properties @@ -0,0 +1,6 @@ +_=ID:String,NAME:String,TIME:java.sql.Timestamp,GEOMETRY:Geometry:srid=4326 +v.1=1|feature1|2017-06-25 14:30:00.0|POINT(-1 1) +v.2=1|feature1|2017-07-25 14:30:00.0|POINT(-1 -1) +v.3=1|feature1|2017-06-25 14:35:00.0|POINT(1 -1) +v.4=2|feature2|2017-04-10 13:30:00.0|POINT(-2 2) +v.5=2|feature2|2017-08-10 17:10:00.0|POINT(2 -2) \ No newline at end of file diff --git a/src/community/nsg-profile/src/test/resources/requests/get_request_1.xml b/src/community/nsg-profile/src/test/resources/requests/get_request_1.xml new file mode 100644 index 00000000000..bd69e4fdbeb --- /dev/null +++ b/src/community/nsg-profile/src/test/resources/requests/get_request_1.xml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/src/community/nsg-profile/src/test/resources/requests/insert_request_1.xml b/src/community/nsg-profile/src/test/resources/requests/insert_request_1.xml new file mode 100644 index 00000000000..c50429c8a86 --- /dev/null +++ b/src/community/nsg-profile/src/test/resources/requests/insert_request_1.xml @@ -0,0 +1,22 @@ + + + + + 1 + feature1 + 2018-01-25T13:30:00Z + + + 1.5 -1.5 + + + + + \ No newline at end of file diff --git a/src/community/nsg-profile/src/test/resources/requests/update_request_1.xml b/src/community/nsg-profile/src/test/resources/requests/update_request_1.xml new file mode 100644 index 00000000000..34ed22793ac --- /dev/null +++ b/src/community/nsg-profile/src/test/resources/requests/update_request_1.xml @@ -0,0 +1,18 @@ + + + + + NAME + feature2_updated + + + + + + \ No newline at end of file diff --git a/src/community/pom.xml b/src/community/pom.xml index 8637f77d6ea..327013956df 100644 --- a/src/community/pom.xml +++ b/src/community/pom.xml @@ -62,7 +62,7 @@ release/ext-gwc-distributed.xml release/ext-geofence-server.xml - + release/ext-gwc-s3.xml release/ext-gdal-wcs.xml release/ext-gdal-wps.xml release/ext-wps-jdbc.xml @@ -225,7 +225,7 @@ gwc-distributed jdbcstore gdal - + gwc-s3 ncwms web-resource @@ -242,7 +242,7 @@ ows-simulate jdbc-metrics oseo - s3-geotiff + nsg-profile @@ -533,9 +533,9 @@ - s3-geotiff + nsg-profile - s3-geotiff + nsg-profile diff --git a/src/community/release/ext-nsg-profile.xml b/src/community/release/ext-nsg-profile.xml new file mode 100644 index 00000000000..11f21b27b56 --- /dev/null +++ b/src/community/release/ext-nsg-profile.xml @@ -0,0 +1,16 @@ + + nsg-profile + + zip + + false + + + release/target/dependency + + + gs-nsg-profile*.jar + + + + \ No newline at end of file diff --git a/src/main/src/main/java/org/geoserver/catalog/FeatureTypeInfo.java b/src/main/src/main/java/org/geoserver/catalog/FeatureTypeInfo.java index f780f80b9f5..3a2b0724b65 100644 --- a/src/main/src/main/java/org/geoserver/catalog/FeatureTypeInfo.java +++ b/src/main/src/main/java/org/geoserver/catalog/FeatureTypeInfo.java @@ -193,4 +193,11 @@ public interface FeatureTypeInfo extends ResourceInfo { void setCircularArcPresent(boolean arcsPresent); + default T getParameter(String parameterName, Class expectType, T fallback) { + return null; + } + + default void putParameter(String parameterName, Object parameterValue) { + // nothing to do + } } diff --git a/src/main/src/main/java/org/geoserver/catalog/impl/FeatureTypeInfoImpl.java b/src/main/src/main/java/org/geoserver/catalog/impl/FeatureTypeInfoImpl.java index 8fc00f86217..b1a5cd481c2 100644 --- a/src/main/src/main/java/org/geoserver/catalog/impl/FeatureTypeInfoImpl.java +++ b/src/main/src/main/java/org/geoserver/catalog/impl/FeatureTypeInfoImpl.java @@ -5,10 +5,6 @@ */ package org.geoserver.catalog.impl; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - import org.geoserver.catalog.AttributeTypeInfo; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.CatalogVisitor; @@ -20,11 +16,18 @@ import org.geotools.filter.text.cql2.CQLException; import org.geotools.filter.text.ecql.ECQL; import org.geotools.measure.Measure; +import org.geotools.util.Converters; import org.opengis.feature.Feature; import org.opengis.feature.type.FeatureType; import org.opengis.filter.Filter; import org.opengis.util.ProgressListener; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + @SuppressWarnings("serial") public class FeatureTypeInfoImpl extends ResourceInfoImpl implements FeatureTypeInfo { @@ -42,7 +45,9 @@ public class FeatureTypeInfoImpl extends ResourceInfoImpl implements boolean overridingServiceSRS; boolean skipNumberMatched = false; boolean circularArcPresent; - + + protected Map parameters = new HashMap<>(); + public boolean isCircularArcPresent() { return circularArcPresent; } @@ -236,5 +241,21 @@ public void setCqlFilter(String cqlFilter) { this.cqlFilter = cqlFilter; this.filter = null; } - + + @Override + public T getParameter(String parameterName, Class expectType, T fallback) { + if (parameters == null || parameters.isEmpty()) { + return fallback; + } + Object value = parameters.get(parameterName); + return value == null ? fallback : Converters.convert(value, expectType); + } + + @Override + public void putParameter(String parameterName, Object parameterValue) { + if (parameters == null) { + parameters = new HashMap<>(); + } + parameters.put(parameterName, parameterValue); + } } diff --git a/src/main/src/main/java/org/geoserver/security/decorators/DecoratingFeatureTypeInfo.java b/src/main/src/main/java/org/geoserver/security/decorators/DecoratingFeatureTypeInfo.java index 4f7da4f075c..40b50d647cc 100644 --- a/src/main/src/main/java/org/geoserver/security/decorators/DecoratingFeatureTypeInfo.java +++ b/src/main/src/main/java/org/geoserver/security/decorators/DecoratingFeatureTypeInfo.java @@ -329,4 +329,13 @@ public void setCqlFilter(String cqlFilter) { delegate.setCqlFilter(cqlFilter); } + @Override + public T getParameter(String parameterName, Class expectType, T fallback) { + return delegate.getParameter(parameterName, expectType, fallback); + } + + @Override + public void putParameter(String parameterName, Object parameterValue) { + delegate.putParameter(parameterName, parameterValue); + } } diff --git a/src/main/src/main/java/org/geoserver/security/decorators/SecuredFeatureTypeInfo.java b/src/main/src/main/java/org/geoserver/security/decorators/SecuredFeatureTypeInfo.java index bcc7c5ed884..9198b365cc3 100644 --- a/src/main/src/main/java/org/geoserver/security/decorators/SecuredFeatureTypeInfo.java +++ b/src/main/src/main/java/org/geoserver/security/decorators/SecuredFeatureTypeInfo.java @@ -111,5 +111,6 @@ public FeatureSource getFeatureSource(ProgressListener listener, Hints hints) public DataStoreInfo getStore() { return (DataStoreInfo) SecuredObjects.secure(delegate.getStore(), policy); } - + + } diff --git a/src/web/app/pom.xml b/src/web/app/pom.xml index aee4265be9e..b92ca71d249 100644 --- a/src/web/app/pom.xml +++ b/src/web/app/pom.xml @@ -141,10 +141,6 @@ org.geotools.jdbc gt-jdbc-postgis
- - org.geotools - gt-geopkg - org.geotools gt-wfs-ng @@ -1527,11 +1523,11 @@ - s3-geotiff + nsg-profile org.geoserver.community - gs-s3-geotiff + gs-nsg-profile ${project.version} diff --git a/src/web/security/core/src/main/java/org/geoserver/security/web/user/UserPanel.java b/src/web/security/core/src/main/java/org/geoserver/security/web/user/UserPanel.java index c10f9520432..fd5cb632beb 100644 --- a/src/web/security/core/src/main/java/org/geoserver/security/web/user/UserPanel.java +++ b/src/web/security/core/src/main/java/org/geoserver/security/web/user/UserPanel.java @@ -105,7 +105,7 @@ public void onClick() { }); //("addNew", NewUserPage.class)); - //add.setParameter(AbstractSecurityPage.ServiceNameKey, serviceName); + //add.putParameter(AbstractSecurityPage.ServiceNameKey, serviceName); add.setVisible(canCreateStore); // the removal button diff --git a/src/wfs/src/main/java/org/geoserver/wfs/GetFeature.java b/src/wfs/src/main/java/org/geoserver/wfs/GetFeature.java index 3e66b5a92ca..151444e61d0 100644 --- a/src/wfs/src/main/java/org/geoserver/wfs/GetFeature.java +++ b/src/wfs/src/main/java/org/geoserver/wfs/GetFeature.java @@ -34,6 +34,7 @@ import org.geoserver.ows.Request; import org.geoserver.ows.URLMangler.URLType; import org.geoserver.ows.util.KvpMap; +import org.geoserver.platform.GeoServerExtensions; import org.geoserver.wfs.request.FeatureCollectionResponse; import org.geoserver.wfs.request.GetFeatureRequest; import org.geoserver.wfs.request.Lock; @@ -506,6 +507,21 @@ public FeatureCollectionResponse run(GetFeatureRequest request) queryMaxFeatures, source, request, allPropNames.get(0), viewParam, joins, primaryTypeName, primaryAlias); + // extension point + GetFeatureContext context = new GetFeatureContextBuilder() + .withFeatureSource(source) + .withFeatureTypeInfo(meta) + .withQuery(gtQuery) + .withRequest(request).build(); + List callbacks = GeoServerExtensions.extensions(GetFeatureCallback.class); + for (GetFeatureCallback callback : callbacks) { + context = callback.beforeQuerying(context); + } + source = context.getFeatureSource(); + meta = context.getFeatureTypeInfo(); + gtQuery = context.getQuery(); + request = context.getRequest(); + LOGGER.fine("Query is " + query + "\n To gt2: " + gtQuery); FeatureCollection features = getFeatures(request, source, gtQuery); diff --git a/src/wfs/src/main/java/org/geoserver/wfs/GetFeatureCallback.java b/src/wfs/src/main/java/org/geoserver/wfs/GetFeatureCallback.java new file mode 100644 index 00000000000..c0b5c8ed58f --- /dev/null +++ b/src/wfs/src/main/java/org/geoserver/wfs/GetFeatureCallback.java @@ -0,0 +1,20 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.wfs; + +import org.geoserver.platform.ExtensionPriority; + +public interface GetFeatureCallback extends ExtensionPriority { + + default GetFeatureContext beforeQuerying(GetFeatureContext context) { + // by default nothing is done + return context; + } + + @Override + default int getPriority() { + return ExtensionPriority.LOWEST; + } +} diff --git a/src/wfs/src/main/java/org/geoserver/wfs/GetFeatureContext.java b/src/wfs/src/main/java/org/geoserver/wfs/GetFeatureContext.java new file mode 100644 index 00000000000..3f06b7c4325 --- /dev/null +++ b/src/wfs/src/main/java/org/geoserver/wfs/GetFeatureContext.java @@ -0,0 +1,44 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.wfs; + +import org.geoserver.catalog.FeatureTypeInfo; +import org.geoserver.wfs.request.GetFeatureRequest; +import org.geotools.data.FeatureSource; +import org.geotools.data.Query; +import org.opengis.feature.Feature; +import org.opengis.feature.type.FeatureType; + +public final class GetFeatureContext { + + private final GetFeatureRequest request; + private final FeatureSource featureSource; + private final Query query; + private final FeatureTypeInfo featureTypeInfo; + + GetFeatureContext(GetFeatureRequest request, FeatureSource featureSource, + Query query, FeatureTypeInfo featureTypeInfo) { + this.request = request; + this.featureSource = featureSource; + this.query = query; + this.featureTypeInfo = featureTypeInfo; + } + + public GetFeatureRequest getRequest() { + return request; + } + + public FeatureSource getFeatureSource() { + return featureSource; + } + + public Query getQuery() { + return query; + } + + public FeatureTypeInfo getFeatureTypeInfo() { + return featureTypeInfo; + } +} \ No newline at end of file diff --git a/src/wfs/src/main/java/org/geoserver/wfs/GetFeatureContextBuilder.java b/src/wfs/src/main/java/org/geoserver/wfs/GetFeatureContextBuilder.java new file mode 100644 index 00000000000..0c19934cf6c --- /dev/null +++ b/src/wfs/src/main/java/org/geoserver/wfs/GetFeatureContextBuilder.java @@ -0,0 +1,54 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.wfs; + +import org.geoserver.catalog.FeatureTypeInfo; +import org.geoserver.wfs.request.GetFeatureRequest; +import org.geotools.data.FeatureSource; +import org.geotools.data.Query; +import org.opengis.feature.Feature; +import org.opengis.feature.type.FeatureType; + +public final class GetFeatureContextBuilder { + + private GetFeatureRequest request; + private FeatureSource featureSource; + private Query query; + private FeatureTypeInfo featureTypeInfo; + + public GetFeatureContextBuilder() { + } + + public GetFeatureContextBuilder withRequest(GetFeatureRequest request) { + this.request = request; + return this; + } + + public GetFeatureContextBuilder withFeatureSource(FeatureSource featureSource) { + this.featureSource = featureSource; + return this; + } + + public GetFeatureContextBuilder withQuery(Query query) { + this.query = query; + return this; + } + + public GetFeatureContextBuilder withFeatureTypeInfo(FeatureTypeInfo featureTypeInfo) { + this.featureTypeInfo = featureTypeInfo; + return this; + } + + public GetFeatureContextBuilder withContext(GetFeatureContext context) { + return withRequest(context.getRequest()) + .withFeatureSource(context.getFeatureSource()) + .withQuery(context.getQuery()) + .withFeatureTypeInfo(context.getFeatureTypeInfo()); + } + + public GetFeatureContext build() { + return new GetFeatureContext(request, featureSource, query, featureTypeInfo); + } +} diff --git a/src/wfs/src/main/java/org/geoserver/wfs/InsertElementHandler.java b/src/wfs/src/main/java/org/geoserver/wfs/InsertElementHandler.java index 49ae7fa67be..e277dcdb16d 100644 --- a/src/wfs/src/main/java/org/geoserver/wfs/InsertElementHandler.java +++ b/src/wfs/src/main/java/org/geoserver/wfs/InsertElementHandler.java @@ -5,21 +5,11 @@ */ package org.geoserver.wfs; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.logging.Logger; - -import javax.xml.namespace.QName; - +import com.vividsolutions.jts.geom.Geometry; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.config.GeoServer; import org.geoserver.feature.ReprojectingFeatureCollection; +import org.geoserver.platform.GeoServerExtensions; import org.geoserver.wfs.request.Insert; import org.geoserver.wfs.request.TransactionElement; import org.geoserver.wfs.request.TransactionRequest; @@ -40,7 +30,16 @@ import org.opengis.filter.identity.FeatureId; import org.opengis.referencing.crs.CoordinateReferenceSystem; -import com.vividsolutions.jts.geom.Geometry; +import javax.xml.namespace.QName; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; /** @@ -71,7 +70,21 @@ public void checkValidity(TransactionElement element, Map callbacks = GeoServerExtensions.extensions(TransactionCallback.class); + try { for (Iterator it = elementHandlers.entrySet().iterator(); it.hasNext();) { Map.Entry entry = (Map.Entry) it.next(); TransactionElement element = (TransactionElement) entry.getKey(); TransactionElementHandler handler = (TransactionElementHandler) entry.getValue(); + TransactionContext context = new TransactionContextBuilder() + .withElement(element) + .withRequest(request) + .withFeatureStores(stores) + .withResponse(result) + .withHandler(handler).build(); + context = TransactionCallback.executeCallbacks( + context, callbacks, TransactionCallback::beforeHandlerExecution); + + element = context.getElement(); + request = context.getRequest(); + stores = context.getFeatureStores(); + result = context.getResponse(); + handler = context.getHandler(); + handler.execute(element, request, stores, result, multiplexer); } } catch (WFSTransactionException e) { diff --git a/src/wfs/src/main/java/org/geoserver/wfs/TransactionCallback.java b/src/wfs/src/main/java/org/geoserver/wfs/TransactionCallback.java new file mode 100644 index 00000000000..6193ab9dca4 --- /dev/null +++ b/src/wfs/src/main/java/org/geoserver/wfs/TransactionCallback.java @@ -0,0 +1,49 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.wfs; + +import java.util.List; + +public interface TransactionCallback { + + default TransactionContext beforeHandlerExecution(TransactionContext context) { + // by default nothing is done + return context; + } + + default TransactionContext beforeInsertFeatures(TransactionContext context) { + // by default nothing is done + return context; + } + + default TransactionContext beforeUpdateFeatures(TransactionContext context) { + // by default nothing is done + return context; + } + + default TransactionContext beforeDeleteFeatures(TransactionContext context) { + // by default nothing is done + return context; + } + + default TransactionContext beforeReplaceFeatures(TransactionContext context) { + // by default nothing is done + return context; + } + + @FunctionalInterface + interface Executor { + TransactionContext apply(TransactionCallback callback, TransactionContext context); + } + + static TransactionContext executeCallbacks(TransactionContext context, + List callbacks, + Executor executor) { + for (TransactionCallback callback : callbacks) { + context = executor.apply(callback, context); + } + return context; + } +} diff --git a/src/wfs/src/main/java/org/geoserver/wfs/TransactionContext.java b/src/wfs/src/main/java/org/geoserver/wfs/TransactionContext.java new file mode 100644 index 00000000000..469fcc1a558 --- /dev/null +++ b/src/wfs/src/main/java/org/geoserver/wfs/TransactionContext.java @@ -0,0 +1,49 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.wfs; + +import org.geoserver.wfs.request.TransactionElement; +import org.geoserver.wfs.request.TransactionRequest; +import org.geoserver.wfs.request.TransactionResponse; + +import java.util.Map; + +public final class TransactionContext { + + private final TransactionElement element; + private final TransactionRequest request; + private final Map featureStores; + private final TransactionResponse response; + private final TransactionElementHandler handler; + + TransactionContext(TransactionElement element, TransactionRequest request, Map featureStores, + TransactionResponse response, TransactionElementHandler handler) { + this.element = element; + this.request = request; + this.featureStores = featureStores; + this.response = response; + this.handler = handler; + } + + public TransactionElement getElement() { + return element; + } + + public TransactionRequest getRequest() { + return request; + } + + public Map getFeatureStores() { + return featureStores; + } + + public TransactionResponse getResponse() { + return response; + } + + public TransactionElementHandler getHandler() { + return handler; + } +} \ No newline at end of file diff --git a/src/wfs/src/main/java/org/geoserver/wfs/TransactionContextBuilder.java b/src/wfs/src/main/java/org/geoserver/wfs/TransactionContextBuilder.java new file mode 100644 index 00000000000..a1a2f018c96 --- /dev/null +++ b/src/wfs/src/main/java/org/geoserver/wfs/TransactionContextBuilder.java @@ -0,0 +1,61 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.wfs; + +import org.geoserver.wfs.request.TransactionElement; +import org.geoserver.wfs.request.TransactionRequest; +import org.geoserver.wfs.request.TransactionResponse; + +import java.util.Map; + +public final class TransactionContextBuilder { + + private TransactionElement element; + private TransactionRequest request; + private Map featureStores; + private TransactionResponse response; + private TransactionElementHandler handler; + + public TransactionContextBuilder() { + } + + public TransactionContextBuilder withElement(TransactionElement element) { + this.element = element; + return this; + } + + public TransactionContextBuilder withRequest(TransactionRequest request) { + this.request = request; + return this; + } + + public TransactionContextBuilder withFeatureStores(Map featureStores) { + this.featureStores = featureStores; + return this; + } + + public TransactionContextBuilder withResponse(TransactionResponse response) { + this.response = response; + return this; + } + + public TransactionContextBuilder withHandler(TransactionElementHandler handler) { + this.handler = handler; + return this; + } + + public TransactionContextBuilder withContext(TransactionContext context) { + this.element = context.getElement(); + this.featureStores = context.getFeatureStores(); + this.handler = context.getHandler(); + this.request = context.getRequest(); + this.response = context.getResponse(); + return this; + } + + public TransactionContext build() { + return new TransactionContext(element, request, featureStores, response, handler); + } +} From 39427d263d1a5263f6ff7d260980c56002ff4056 Mon Sep 17 00:00:00 2001 From: Nuno Oliveira Date: Thu, 3 Aug 2017 13:33:23 +0100 Subject: [PATCH 08/15] Add documentation index and reorganise packages --- doc/en/user/source/community/index.rst | 3 +-- doc/en/user/source/community/nsg-profile/index.rst | 12 ++++++++++++ .../nsg/{ => versioning}/TimeVersioning.java | 2 +- .../nsg/{ => versioning}/TimeVersioningCallback.java | 2 +- .../{ => versioning}/VersioningFilterAdapter.java | 6 +----- .../{ => versioning}/web/WfsVersioningConfig.html | 0 .../{ => versioning}/web/WfsVersioningConfig.java | 4 ++-- .../src/main/resources/applicationContext.xml | 2 +- .../src/test/java/org/geoserver/nsg/TestsUtils.java | 12 ++++++------ .../nsg/{ => versioning}/TimeVersioningTest.java | 3 ++- .../web/WfsVersioningConfigTest.java | 5 +---- 11 files changed, 28 insertions(+), 23 deletions(-) create mode 100644 doc/en/user/source/community/nsg-profile/index.rst rename src/community/nsg-profile/src/main/java/org/geoserver/nsg/{ => versioning}/TimeVersioning.java (98%) rename src/community/nsg-profile/src/main/java/org/geoserver/nsg/{ => versioning}/TimeVersioningCallback.java (99%) rename src/community/nsg-profile/src/main/java/org/geoserver/nsg/{ => versioning}/VersioningFilterAdapter.java (95%) rename src/community/nsg-profile/src/main/java/org/geoserver/nsg/{ => versioning}/web/WfsVersioningConfig.html (100%) rename src/community/nsg-profile/src/main/java/org/geoserver/nsg/{ => versioning}/web/WfsVersioningConfig.java (96%) rename src/community/nsg-profile/src/test/java/org/geoserver/nsg/{ => versioning}/TimeVersioningTest.java (98%) rename src/community/nsg-profile/src/test/java/org/geoserver/nsg/{ => versioning}/web/WfsVersioningConfigTest.java (96%) diff --git a/doc/en/user/source/community/index.rst b/doc/en/user/source/community/index.rst index 10430c8850c..9f9b639c386 100644 --- a/doc/en/user/source/community/index.rst +++ b/doc/en/user/source/community/index.rst @@ -45,5 +45,4 @@ officially part of the GeoServer releases. They are however built along with the onelogin/index wmts-multidimensional/index notification/index - opensearch-eo/index - s3-geotiff/index + nsg-profile/index diff --git a/doc/en/user/source/community/nsg-profile/index.rst b/doc/en/user/source/community/nsg-profile/index.rst new file mode 100644 index 00000000000..02490d6cbc5 --- /dev/null +++ b/doc/en/user/source/community/nsg-profile/index.rst @@ -0,0 +1,12 @@ +.. _community_nsg_profile: + +NSG Profile +=========== + + +Index Result Type +----------------- + + +PageResults Operation +--------------------- \ No newline at end of file diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioning.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/TimeVersioning.java similarity index 98% rename from src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioning.java rename to src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/TimeVersioning.java index ad891d29929..6abc73e47a4 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioning.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/TimeVersioning.java @@ -2,7 +2,7 @@ * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ -package org.geoserver.nsg; +package org.geoserver.nsg.versioning; import org.geoserver.catalog.FeatureTypeInfo; diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioningCallback.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/TimeVersioningCallback.java similarity index 99% rename from src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioningCallback.java rename to src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/TimeVersioningCallback.java index ee87f12f628..8b45559076a 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/TimeVersioningCallback.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/TimeVersioningCallback.java @@ -2,7 +2,7 @@ * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ -package org.geoserver.nsg; +package org.geoserver.nsg.versioning; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.FeatureTypeInfo; diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/VersioningFilterAdapter.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/VersioningFilterAdapter.java similarity index 95% rename from src/community/nsg-profile/src/main/java/org/geoserver/nsg/VersioningFilterAdapter.java rename to src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/VersioningFilterAdapter.java index 6a0b1c68222..42be444be5d 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/VersioningFilterAdapter.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/VersioningFilterAdapter.java @@ -2,20 +2,16 @@ * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ -package org.geoserver.nsg; +package org.geoserver.nsg.versioning; import org.geoserver.catalog.FeatureTypeInfo; -import org.geotools.factory.CommonFactoryFinder; -import org.geotools.factory.GeoTools; import org.geotools.filter.visitor.DuplicatingFilterVisitor; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory; -import org.opengis.filter.FilterFactory2; import org.opengis.filter.Id; import org.opengis.filter.expression.Expression; import org.opengis.filter.identity.Identifier; import org.opengis.filter.identity.ResourceId; -import org.opengis.filter.sort.SortOrder; import java.util.Date; import java.util.HashSet; diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.html b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/web/WfsVersioningConfig.html similarity index 100% rename from src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.html rename to src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/web/WfsVersioningConfig.html diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/web/WfsVersioningConfig.java similarity index 96% rename from src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.java rename to src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/web/WfsVersioningConfig.java index 8ef574e602c..f575a2c96ec 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/web/WfsVersioningConfig.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/versioning/web/WfsVersioningConfig.java @@ -3,7 +3,7 @@ * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ -package org.geoserver.nsg.web; +package org.geoserver.nsg.versioning.web; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; @@ -16,7 +16,7 @@ import org.apache.wicket.model.StringResourceModel; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.LayerInfo; -import org.geoserver.nsg.TimeVersioning; +import org.geoserver.nsg.versioning.TimeVersioning; import org.geoserver.web.publish.PublishedConfigurationPanel; import java.sql.Timestamp; diff --git a/src/community/nsg-profile/src/main/resources/applicationContext.xml b/src/community/nsg-profile/src/main/resources/applicationContext.xml index a33c1190b39..875245237cd 100644 --- a/src/community/nsg-profile/src/main/resources/applicationContext.xml +++ b/src/community/nsg-profile/src/main/resources/applicationContext.xml @@ -9,7 +9,7 @@ - + org.geoserver.catalog.FeatureTypeInfo diff --git a/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TestsUtils.java b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TestsUtils.java index 653ac8a8e4b..3e64290a4fa 100644 --- a/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TestsUtils.java +++ b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TestsUtils.java @@ -10,15 +10,15 @@ import java.io.InputStream; -import static org.geoserver.nsg.TimeVersioning.disable; -import static org.geoserver.nsg.TimeVersioning.enable; +import static org.geoserver.nsg.versioning.TimeVersioning.disable; +import static org.geoserver.nsg.versioning.TimeVersioning.enable; -final class TestsUtils { +public final class TestsUtils { private TestsUtils() { } - static String readResource(String resourceName) { + public static String readResource(String resourceName) { try (InputStream input = TestsUtils.class.getResourceAsStream(resourceName)) { return IOUtils.toString(input); } catch (Exception exception) { @@ -26,8 +26,8 @@ static String readResource(String resourceName) { } } - static void updateFeatureTypeTimeVersioning(Catalog catalog, String featureTypeName, - boolean enabled, String idProperty, String timeProperty) { + public static void updateFeatureTypeTimeVersioning(Catalog catalog, String featureTypeName, + boolean enabled, String idProperty, String timeProperty) { FeatureTypeInfo featureType = catalog.getFeatureTypeByName(featureTypeName); if (enabled) { enable(featureType, idProperty, timeProperty); diff --git a/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TimeVersioningTest.java b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/versioning/TimeVersioningTest.java similarity index 98% rename from src/community/nsg-profile/src/test/java/org/geoserver/nsg/TimeVersioningTest.java rename to src/community/nsg-profile/src/test/java/org/geoserver/nsg/versioning/TimeVersioningTest.java index 758d1fa3370..d69d6fe33e5 100644 --- a/src/community/nsg-profile/src/test/java/org/geoserver/nsg/TimeVersioningTest.java +++ b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/versioning/TimeVersioningTest.java @@ -2,13 +2,14 @@ * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ -package org.geoserver.nsg; +package org.geoserver.nsg.versioning; import org.custommonkey.xmlunit.SimpleNamespaceContext; import org.custommonkey.xmlunit.XMLUnit; import org.custommonkey.xmlunit.XpathEngine; import org.geoserver.data.test.MockData; import org.geoserver.data.test.SystemTestData; +import org.geoserver.nsg.TestsUtils; import org.geoserver.test.GeoServerSystemTestSupport; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.crs.DefaultGeographicCRS; diff --git a/src/community/nsg-profile/src/test/java/org/geoserver/nsg/web/WfsVersioningConfigTest.java b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/versioning/web/WfsVersioningConfigTest.java similarity index 96% rename from src/community/nsg-profile/src/test/java/org/geoserver/nsg/web/WfsVersioningConfigTest.java rename to src/community/nsg-profile/src/test/java/org/geoserver/nsg/versioning/web/WfsVersioningConfigTest.java index efe29b00e6a..b580d8e1f6e 100644 --- a/src/community/nsg-profile/src/test/java/org/geoserver/nsg/web/WfsVersioningConfigTest.java +++ b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/versioning/web/WfsVersioningConfigTest.java @@ -2,13 +2,10 @@ * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ -package org.geoserver.nsg.web; +package org.geoserver.nsg.versioning.web; -import org.apache.wicket.markup.html.form.CheckBox; -import org.apache.wicket.util.tester.FormTester; import org.geoserver.web.GeoServerWicketTestSupport; import org.geoserver.web.publish.PublishedConfigurationPage; -import org.geoserver.wfs.GMLInfo; import org.geoserver.wfs.WFSInfo; import org.junit.Test; From 3c7dfc06a376c5cb80fabfc75436ddf86e60f7f8 Mon Sep 17 00:00:00 2001 From: Sandro Salari Date: Tue, 22 Aug 2017 15:57:46 +0200 Subject: [PATCH 09/15] Added dispatcher and output format --- .../pagination/random/IndexOutputFormat.java | 38 +++++++++++ .../IndexResultTypeDisapatcherCallback.java | 63 +++++++++++++++++++ .../src/main/resources/applicationContext.xml | 40 +++++++----- 3 files changed, 126 insertions(+), 15 deletions(-) create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDisapatcherCallback.java diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java new file mode 100644 index 00000000000..06498301884 --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java @@ -0,0 +1,38 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ + +package org.geoserver.nsg.pagination.random; + +import java.io.IOException; +import java.io.OutputStream; + +import org.geoserver.config.GeoServer; +import org.geoserver.platform.Operation; +import org.geoserver.platform.ServiceException; +import org.geoserver.wfs.response.v2_0.HitsOutputFormat; + +import net.opengis.wfs20.BaseRequestType; + +/** + * This output format handles requests if the original requested result type was "index"
+ * It checks {@link BaseRequestType#getExtendedProperties()} for + * {@link IndexResultTypeDisapatcherCallback#RESULT_TYPE_INDEX_PARAMETER} valued as true + * + * @author sandr + * + */ +public class IndexOutputFormat extends HitsOutputFormat { + + public IndexOutputFormat(GeoServer gs) { + super(gs); + } + + @Override + public void write(Object value, OutputStream output, Operation operation) + throws IOException, ServiceException { + super.write(value, output, operation); + } + +} diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDisapatcherCallback.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDisapatcherCallback.java new file mode 100644 index 00000000000..72847971fa6 --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDisapatcherCallback.java @@ -0,0 +1,63 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ + +package org.geoserver.nsg.pagination.random; + +import org.geoserver.config.GeoServer; +import org.geoserver.ows.AbstractDispatcherCallback; +import org.geoserver.ows.Request; +import org.geoserver.ows.Response; +import org.geoserver.platform.Operation; + +import net.opengis.wfs20.ResultTypeType; + +/** + *

+ * When a request that contains the "resultType" parameter arrives, if the parameter value is + * "index" it is substituted by "hits". + *

+ *

+ * A new entry named RESULT_TYPE_INDEX specifying that the original result type was "index" is added + * to KVP maps + *

+ */ + +public class IndexResultTypeDisapatcherCallback extends AbstractDispatcherCallback { + + private GeoServer gs; + + private static final String RESULT_TYPE_PARAMETER = "resultType"; + + private static final String RESULT_TYPE_INDEX = "index"; + + static final String RESULT_TYPE_INDEX_PARAMETER = "RESULT_TYPE_INDEX"; + + public IndexResultTypeDisapatcherCallback(GeoServer gs) { + this.gs = gs; + } + + @Override + @SuppressWarnings("unchecked") + public Request init(Request request) { + Object resultType = request.getKvp().get(RESULT_TYPE_PARAMETER); + if (resultType != null && resultType.toString().equals(RESULT_TYPE_INDEX)) { + request.getKvp().put(RESULT_TYPE_PARAMETER, ResultTypeType.HITS); + request.getKvp().put(RESULT_TYPE_INDEX_PARAMETER, true); + } + return super.init(request); + } + + @Override + public Response responseDispatched(Request request, Operation operation, Object result, + Response response) { + Response newResponse = response; + if (request.getKvp().get(RESULT_TYPE_INDEX_PARAMETER) != null + && (Boolean) request.getKvp().get(RESULT_TYPE_INDEX_PARAMETER)) { + newResponse = new IndexOutputFormat(this.gs); + } + return super.responseDispatched(request, operation, result, newResponse); + } + +} diff --git a/src/community/nsg-profile/src/main/resources/applicationContext.xml b/src/community/nsg-profile/src/main/resources/applicationContext.xml index 875245237cd..c54d58806ee 100644 --- a/src/community/nsg-profile/src/main/resources/applicationContext.xml +++ b/src/community/nsg-profile/src/main/resources/applicationContext.xml @@ -1,19 +1,29 @@ - - - - - - - - - - org.geoserver.catalog.FeatureTypeInfo - - - + + + + + + + + + + org.geoserver.catalog.FeatureTypeInfo + + + + + + When a request using the index result type comes in a + dispatcher callback + this switch the "index" value by the "hits" value + + + From 500a364a5c3195e37c30dde5d70d319f3534cd23 Mon Sep 17 00:00:00 2001 From: Sandro Salari Date: Fri, 25 Aug 2017 15:25:05 +0200 Subject: [PATCH 10/15] Setup of GetFeature requests storage: - configuration file parser and change listerner - db storage - file storage - clean task --- src/community/nsg-profile/pom.xml | 5 + .../pagination/random/IndexConfiguration.java | 74 ++++ .../pagination/random/IndexInitializer.java | 344 ++++++++++++++++++ .../pagination/random/IndexOutputFormat.java | 116 +++++- .../IndexResultTypeDisapatcherCallback.java | 8 + .../src/main/resources/applicationContext.xml | 12 +- .../main/resources/configuration.properties | 6 + 7 files changed, 559 insertions(+), 6 deletions(-) create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java create mode 100644 src/community/nsg-profile/src/main/resources/configuration.properties diff --git a/src/community/nsg-profile/pom.xml b/src/community/nsg-profile/pom.xml index b9e26ef3fbc..b22b61e0b84 100644 --- a/src/community/nsg-profile/pom.xml +++ b/src/community/nsg-profile/pom.xml @@ -30,6 +30,11 @@ gs-web-core ${project.version}
+ + + org.geotools.jdbc + gt-jdbc-h2 + org.geoserver diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java new file mode 100644 index 00000000000..4b352213440 --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java @@ -0,0 +1,74 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ + +package org.geoserver.nsg.pagination.random; + +import java.util.Map; + +import org.geoserver.platform.resource.Resource; +import org.geotools.data.DataStore; + +/** + * + * Class used to store the index result type configuration managed by {@link IndexInitializer} + */ +public class IndexConfiguration { + + private static DataStore currentDataStore; + + private static Resource storageResource; + + private static Long timeToLive = 600l; + + private static Map currentDataStoreParams; + + /** + * Store the DB parameters and the relative {@link DataStore} + * + * @param currentDataStoreParams + * @param currentDataStore + */ + public static void setCurrentDataStore(Map currentDataStoreParams, + DataStore currentDataStore) { + IndexConfiguration.currentDataStoreParams = currentDataStoreParams; + IndexConfiguration.currentDataStore = currentDataStore; + } + + /** + * Store the reference to resource used to archive the serialized GetFeatureRequest + * + * @param currentDataStoreParams + * @param currentDataStore + */ + public static void setStorageResource(Resource storageResource) { + IndexConfiguration.storageResource = storageResource; + } + + /** + * Store the value of time to live of stored GetFeatureRequest + * + * @param timeToLive + */ + public static void setTimeToLive(Long timeToLive) { + IndexConfiguration.timeToLive = timeToLive; + } + + public static DataStore getCurrentDataStore() { + return currentDataStore; + } + + public static Map getCurrentDataStoreParams() { + return currentDataStoreParams; + } + + public static Resource getStorageResource() { + return storageResource; + } + + public static Long getTimeToLive() { + return timeToLive; + } + +} diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java new file mode 100644 index 00000000000..463772625dd --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java @@ -0,0 +1,344 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ + +package org.geoserver.nsg.pagination.random; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.geoserver.config.GeoServer; +import org.geoserver.config.GeoServerDataDirectory; +import org.geoserver.config.GeoServerInitializer; +import org.geoserver.platform.GeoServerExtensions; +import org.geoserver.platform.GeoServerResourceLoader; +import org.geoserver.platform.resource.FileSystemResourceStore; +import org.geoserver.platform.resource.Resource; +import org.geoserver.platform.resource.ResourceListener; +import org.geoserver.platform.resource.ResourceNotification; +import org.geoserver.platform.resource.ResourceNotification.Kind; +import org.geotools.data.DataStore; +import org.geotools.data.DataStoreFinder; +import org.geotools.data.DataUtilities; +import org.geotools.data.DefaultTransaction; +import org.geotools.data.Transaction; +import org.geotools.data.simple.SimpleFeatureCollection; +import org.geotools.data.simple.SimpleFeatureIterator; +import org.geotools.data.simple.SimpleFeatureSource; +import org.geotools.data.simple.SimpleFeatureStore; +import org.geotools.filter.text.cql2.CQL; +import org.geotools.jdbc.JDBCDataStoreFactory; +import org.geotools.util.logging.Logging; +import org.opengis.feature.simple.SimpleFeature; +import org.opengis.feature.simple.SimpleFeatureType; +import org.opengis.filter.Filter; + +/** + * + * Class used to parse the configuration properties stored in nsg-profile module folder: + *
    + *
  • resultSets.storage.path path where to store the serialized GetFeatureRequest with name + * of random UUID. + *
  • resultSets.timeToLive time to live value, all the stored requests that have not been + * used for a period of time bigger than this will be deleted. + *
  • resultSets.db.{@link JDBCDataStoreFactory#DBTYPE} + *
  • resultSets.db.{@link JDBCDataStoreFactory#DATABASE} + *
  • resultSets.db.{@link JDBCDataStoreFactory#HOST} + *
  • resultSets.db.{@link JDBCDataStoreFactory#PORT} + *
  • resultSets.db.{@link JDBCDataStoreFactory#SCHEMA} + *
  • resultSets.db.{@link JDBCDataStoreFactory#USER} + *
  • resultSets.db.{@link JDBCDataStoreFactory#PASSWD} + *
+ * All configuration properties is changeable at runtime so when this properties is updated the + * module take the appropriate action: + *
    + *
  • When the index DB is changed the new DB should be used and the content of the old table moved + * to the new table. If the new DB already has the index table it should be emptied, + *
  • When the storage path is changed, the new storage path should be used and the old storage + * path content should be moved to the new one, + *
  • When the the time to live is changed the {@link #clean()} procedure will update. + *
+ * + * The class is also responsible to {@link #clean()} the stored requests (result sets) that have not + * been used for a period of time bigger than the configured time to live value + *

+ * + * @author sandr + * + */ + +public class IndexInitializer implements GeoServerInitializer { + + static Logger LOGGER = Logging.getLogger(IndexInitializer.class); + + static final String PROPERTY_DB_PREFIX = "resultSets.db."; + + static final String PROPERTY_FILENAME = "configuration.properties"; + + static final String MODULE_DIR = "nsg-profile"; + + public static final String STORE_SCHEMA_NAME = "RESULT_SET"; + + public static final String STORE_SCHEMA = "ID:\"\",created:0,updated:0"; + + /* + * Lock to synchronize activity of clean task with listener that changes the DB and file + * resources + */ + private final Object lock = new Object(); + + @Override + public void initialize(GeoServer geoServer) throws Exception { + GeoServerResourceLoader loader = GeoServerExtensions.bean(GeoServerResourceLoader.class); + GeoServerDataDirectory dd = new GeoServerDataDirectory(loader); + Resource resource = dd.get(MODULE_DIR + "/" + PROPERTY_FILENAME); + if (loader != null) { + File directory = loader.findOrCreateDirectory(MODULE_DIR); + File file = new File(directory, PROPERTY_FILENAME); + // Create default configuration file + if (!file.exists()) { + InputStream stream = IndexInitializer.class + .getResourceAsStream("/" + PROPERTY_FILENAME); + Properties properties = new Properties(); + properties.load(stream); + // Replace GEOSERVER_DATA_DIR placeholder + properties.replaceAll((k, v) -> ((String) v).replace("${GEOSERVER_DATA_DIR}", + dd.root().getPath())); + // Create resource and save properties + OutputStream out = resource.out(); + properties.store(out, null); + out.close(); + } + loadConfigurations(resource); + // Listen for changes in configuration file and reload properties + resource.addListener(new ResourceListener() { + @Override + public void changed(ResourceNotification notify) { + if (notify.getKind() == Kind.ENTRY_MODIFY) { + try { + loadConfigurations(resource); + } catch (Exception exception) { + throw new RuntimeException("Error reload confiugrations.", exception); + } + } + } + }); + } + } + + /** + * Helper method that + */ + private void loadConfigurations(Resource resource) throws IOException { + synchronized (lock) { + Properties properties = new Properties(); + properties.load(resource.in()); + // Reload database + Map params = new HashMap<>(); + params.put(JDBCDataStoreFactory.DBTYPE.key, + properties.get(PROPERTY_DB_PREFIX + JDBCDataStoreFactory.DBTYPE.key)); + params.put(JDBCDataStoreFactory.DATABASE.key, + properties.get(PROPERTY_DB_PREFIX + JDBCDataStoreFactory.DATABASE.key)); + params.put(JDBCDataStoreFactory.HOST.key, + properties.get(PROPERTY_DB_PREFIX + JDBCDataStoreFactory.HOST.key)); + params.put(JDBCDataStoreFactory.PORT.key, + properties.get(PROPERTY_DB_PREFIX + JDBCDataStoreFactory.PORT.key)); + params.put(JDBCDataStoreFactory.SCHEMA.key, + properties.get(PROPERTY_DB_PREFIX + JDBCDataStoreFactory.SCHEMA.key)); + params.put(JDBCDataStoreFactory.USER.key, + properties.get(PROPERTY_DB_PREFIX + JDBCDataStoreFactory.USER.key)); + params.put(JDBCDataStoreFactory.PASSWD.key, + properties.get(PROPERTY_DB_PREFIX + JDBCDataStoreFactory.PASSWD.key)); + /** + * When the index DB is changed the new DB should be used and the content of the old + * table moved to the new table. If the new DB already has the index table it should be + * emptied + */ + manageDBChange(params); + /* + * If the storage path is changed, the new storage path should be used and the old + * storage path content should be moved to the new one + */ + manageStorageChange(resource, properties.get("resultSets.storage.path")); + /* + * Change time to live + */ + manageTimeToLiveChange(properties.get("resultSets.timeToLive")); + } + + } + + /** + * Helper method that + */ + private void manageTimeToLiveChange(Object timneToLive) { + try { + if (timneToLive != null) { + String timneToLiveStr = (String) timneToLive; + IndexConfiguration.setTimeToLive(Long.parseLong(timneToLiveStr)); + } + } catch (Exception exception) { + throw new RuntimeException("Error on change time to live", exception); + } + } + + /** + * Helper method that move resources files form current folder to the new one, current storage + * is deleted + */ + private void manageStorageChange(Resource resource, Object newStorage) { + try { + if (newStorage != null) { + String newStorageStr = (String) newStorage; + Resource newResource = new FileSystemResourceStore(new File(newStorageStr)).get(""); + Resource exResource = IndexConfiguration.getStorageResource(); + if (exResource != null && !newResource.dir().getAbsolutePath() + .equals(exResource.dir().getAbsolutePath())) { + exResource.delete(); + IndexConfiguration.setStorageResource(newResource); + } + } + } catch (Exception exception) { + throw new RuntimeException("Error on change store", exception); + } + } + + /** + * Helper method that move DB data from old store to new one + */ + private void manageDBChange(Map params) { + try { + DataStore exDataStore = IndexConfiguration.getCurrentDataStore(); + DataStore newDataStore = DataStoreFinder.getDataStore(params); + if (exDataStore != null) { + // New store is valid and is different from current one + if (newDataStore != null && !isStorageTheSame(params)) { + // Create table in new store + createTable(newDataStore, true); + // Move data to new store + moveData(exDataStore, newDataStore); + // Delete old store + exDataStore.dispose(); + } + } else { + // Create schema + createTable(newDataStore, false); + } + IndexConfiguration.setCurrentDataStore(params, newDataStore); + } catch (Exception exception) { + throw new RuntimeException("Error reload DB confiugrations.", exception); + } + } + + /** + * Helper method that check id the DB is the same, matching the JDBC configurations parameters. + */ + private Boolean isStorageTheSame(Map newParams) { + Map currentParams = IndexConfiguration.getCurrentDataStoreParams(); + return currentParams.get(JDBCDataStoreFactory.DBTYPE.key) + .equals(newParams.get(JDBCDataStoreFactory.DBTYPE.key)) + && currentParams.get(JDBCDataStoreFactory.DATABASE.key) + .equals(newParams.get(JDBCDataStoreFactory.DATABASE.key)) + && currentParams.get(JDBCDataStoreFactory.HOST.key) + .equals(newParams.get(JDBCDataStoreFactory.HOST.key)) + && currentParams.get(JDBCDataStoreFactory.PORT.key) + .equals(newParams.get(JDBCDataStoreFactory.PORT.key)) + && currentParams.get(JDBCDataStoreFactory.SCHEMA.key) + .equals(newParams.get(JDBCDataStoreFactory.SCHEMA.key)); + } + + /** + * Helper method that create a new table on DB to store resource informations + */ + private void createTable(DataStore dataStore, Boolean forceDelete) throws Exception { + SimpleFeatureType schema = dataStore.getSchema(STORE_SCHEMA_NAME); + // Schema exists + if (schema != null) { + // Delete of exist is required, and then create a new one + if (forceDelete) { + dataStore.removeSchema(STORE_SCHEMA_NAME); + schema = DataUtilities.createType(STORE_SCHEMA_NAME, STORE_SCHEMA); + dataStore.createSchema(schema); + } + // Schema not exists, create a new one + } else { + schema = DataUtilities.createType(STORE_SCHEMA_NAME, STORE_SCHEMA); + dataStore.createSchema(schema); + } + } + + /** + * Helper method that move resource informations from current DB to the new one + */ + private void moveData(DataStore exDataStore, DataStore newDataStore) throws Exception { + Transaction session = new DefaultTransaction("Adding"); + try { + SimpleFeatureSource exFs = exDataStore.getFeatureSource(STORE_SCHEMA_NAME); + SimpleFeatureStore newFs = (SimpleFeatureStore) newDataStore + .getFeatureSource(STORE_SCHEMA_NAME); + newFs.setTransaction(session); + newFs.addFeatures(exFs.getFeatures()); + session.commit(); + } catch (Throwable t) { + session.rollback(); + throw new RuntimeException("Error on move data", t); + } finally { + session.close(); + } + } + + /** + * Delete all the stored requests (result sets) that have not been used for a period of time + * bigger than the configured time to live value. Clean also related resource files. + *

+ * Executed by scheduler, for details see Spring XML configuration + */ + public void clean() throws Exception { + synchronized (lock) { + Transaction session = new DefaultTransaction("RemoveOld"); + try { + // Remove record + Long timeToLive = IndexConfiguration.getTimeToLive(); + DataStore currentDataStore = IndexConfiguration.getCurrentDataStore(); + SimpleFeatureStore store = (SimpleFeatureStore) currentDataStore + .getFeatureSource(STORE_SCHEMA_NAME); + Long now = new Date().getTime(); + Long liveTreshold = now - timeToLive * 1000; + Filter filter = CQL.toFilter("updated < " + liveTreshold); + SimpleFeatureCollection toRemoved = store.getFeatures(filter); + // Remove file + Resource currentResource = IndexConfiguration.getStorageResource(); + SimpleFeatureIterator iterator = toRemoved.features(); + try { + while (iterator.hasNext()) { + SimpleFeature feature = iterator.next(); + currentResource.get(feature.getID()).delete(); + } + } finally { + iterator.close(); + } + store.removeFeatures(filter); + if (LOGGER.isLoggable(Level.FINEST)) { + LOGGER.finest("CLEAN executed, removed stored requests older than " + + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") + .format(new Date(liveTreshold))); + } + } catch (Throwable t) { + session.rollback(); + throw new RuntimeException("Error on move data", t); + } finally { + session.close(); + } + } + } +} diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java index 06498301884..1a4a60ff53c 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java @@ -7,32 +7,140 @@ import java.io.IOException; import java.io.OutputStream; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.util.UUID; +import java.util.logging.Logger; + +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; import org.geoserver.config.GeoServer; +import org.geoserver.ows.util.OwsUtils; +import org.geoserver.ows.util.ResponseUtils; import org.geoserver.platform.Operation; import org.geoserver.platform.ServiceException; +import org.geoserver.wfs.WFSInfo; +import org.geoserver.wfs.request.FeatureCollectionResponse; import org.geoserver.wfs.response.v2_0.HitsOutputFormat; +import org.geotools.util.logging.Logging; +import org.geotools.wfs.v2_0.WFS; +import org.geotools.wfs.v2_0.WFSConfiguration; +import org.geotools.xml.Encoder; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; -import net.opengis.wfs20.BaseRequestType; +import net.opengis.wfs20.GetFeatureType; /** * This output format handles requests if the original requested result type was "index"
- * It checks {@link BaseRequestType#getExtendedProperties()} for - * {@link IndexResultTypeDisapatcherCallback#RESULT_TYPE_INDEX_PARAMETER} valued as true + * See {@link IndexResultTypeDisapatcherCallback} * * @author sandr * */ public class IndexOutputFormat extends HitsOutputFormat { + static Logger LOGGER = Logging.getLogger(IndexOutputFormat.class); + GetFeatureType request; + String resultSetId; + public IndexOutputFormat(GeoServer gs) { super(gs); } - + @Override public void write(Object value, OutputStream output, Operation operation) throws IOException, ServiceException { + // extract GetFeature request + request = (GetFeatureType) OwsUtils.parameter(operation.getParameters(), + GetFeatureType.class); + // generate an UUID (resultSetID) for this request + resultSetId = UUID.randomUUID().toString(); super.write(value, output, operation); } + @Override + protected void encode(FeatureCollectionResponse hits, OutputStream output, WFSInfo wfs) + throws IOException { + + hits.setNumberOfFeatures(BigInteger.ZERO); + // instantiate the XML encoder + Encoder encoder = new Encoder(new WFSConfiguration()); + encoder.setEncoding(Charset.forName(wfs.getGeoServer().getSettings().getCharset())); + encoder.setSchemaLocation(WFS.NAMESPACE, + ResponseUtils.appendPath(wfs.getSchemaBaseURL(), "wfs/2.0/wfs.xsd")); + Document document; + try { + // encode the HITS result using FeatureCollection as the root XML element + document = encoder.encodeAsDOM(hits.getAdaptee(), WFS.FeatureCollection); + } catch (Exception exception) { + throw new RuntimeException("Error encoding INDEX result.", exception); + } + // add the resultSetID attribute to the result + addResultSetIdElement(document, resultSetId); + // write the XML document to response output stream + writeDocument(document, output); + } + + /** + * Helper method that serialize GetFeature request, store it in the file system and associate it with resultSetId + */ + protected void storeGetFeature(){ + + } + + /** + * Helper method that adds the resultSetID attribute to XML result. If no FeatureCollection + * element can be found nothing will be done. + */ + private static void addResultSetIdElement(Document document, String resultSetId) { + // search FeatureCollection XML nodes + NodeList nodes = document.getElementsByTagName("wfs:FeatureCollection"); + if (nodes.getLength() != 1) { + // only one node should exists, let's log an warning an move on + LOGGER.warning( + "No feature collection element could be found, resultSetID attribute will not be added."); + return; + } + // get the FeatureCollection node + Node node = nodes.item(0); + if (node.getNodeType() == Node.ELEMENT_NODE) { + // the found node is a XML element so let's add the resultSetID attribute + Element element = (Element) node; + element.setAttribute("resultSetID", resultSetId); + } else { + // unlikely but we got a XML node that is not a XML element + LOGGER.warning( + "Feature collection node is not a XML element, resultSetID attribute will not be added."); + } + } + + /** + * Helper method that just writes a XML document to a given output stream. + */ + private static void writeDocument(Document document, OutputStream output) { + // instantiate a new XML transformer + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer; + try { + transformer = transformerFactory.newTransformer(); + } catch (Exception exception) { + throw new RuntimeException("Error creating XML transformer.", exception); + } + // write the XML document to the provided output stream + DOMSource source = new DOMSource(document); + StreamResult result = new StreamResult(output); + try { + transformer.transform(source, result); + } catch (Exception exception) { + throw new RuntimeException("Error writing INDEX result to the output stream.", + exception); + } + } + } diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDisapatcherCallback.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDisapatcherCallback.java index 72847971fa6..6a7f49e4cc2 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDisapatcherCallback.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDisapatcherCallback.java @@ -5,11 +5,14 @@ package org.geoserver.nsg.pagination.random; +import java.util.logging.Logger; + import org.geoserver.config.GeoServer; import org.geoserver.ows.AbstractDispatcherCallback; import org.geoserver.ows.Request; import org.geoserver.ows.Response; import org.geoserver.platform.Operation; +import org.geotools.util.logging.Logging; import net.opengis.wfs20.ResultTypeType; @@ -22,9 +25,14 @@ * A new entry named RESULT_TYPE_INDEX specifying that the original result type was "index" is added * to KVP maps *

+ *

+ * The object that manage response of type HitsOutputFormat is replaced with IndexOutputFormat before response has been dispatched + *

*/ public class IndexResultTypeDisapatcherCallback extends AbstractDispatcherCallback { + + static Logger LOGGER = Logging.getLogger(IndexOutputFormat.class); private GeoServer gs; diff --git a/src/community/nsg-profile/src/main/resources/applicationContext.xml b/src/community/nsg-profile/src/main/resources/applicationContext.xml index c54d58806ee..acdcb9c2caa 100644 --- a/src/community/nsg-profile/src/main/resources/applicationContext.xml +++ b/src/community/nsg-profile/src/main/resources/applicationContext.xml @@ -1,8 +1,9 @@ + xmlns:task="http://www.springframework.org/schema/task" + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd"> @@ -26,4 +27,11 @@ + + + + + + + diff --git a/src/community/nsg-profile/src/main/resources/configuration.properties b/src/community/nsg-profile/src/main/resources/configuration.properties new file mode 100644 index 00000000000..3d9f99c95c2 --- /dev/null +++ b/src/community/nsg-profile/src/main/resources/configuration.properties @@ -0,0 +1,6 @@ +resultSets.storage.path=${GEOSERVER_DATA_DIR}/nsg-profile/resultSets +resultSets.timeToLive=601 +resultSets.db.dbtype=h2 +resultSets.db.database=${GEOSERVER_DATA_DIR}/nsg-profile/db/resultSets +resultSets.db.user=sa +resultSets.db.password=sa \ No newline at end of file From 50f6260d52a14dbd7e136036d632ab0189a67444 Mon Sep 17 00:00:00 2001 From: Sandro Salari Date: Fri, 25 Aug 2017 18:10:54 +0200 Subject: [PATCH 11/15] Added EMF and H2 save procedure to store GetFeature calls --- src/community/nsg-profile/pom.xml | 8 ++- .../pagination/random/IndexInitializer.java | 4 +- .../pagination/random/IndexOutputFormat.java | 71 ++++++++++++++++--- 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/src/community/nsg-profile/pom.xml b/src/community/nsg-profile/pom.xml index b22b61e0b84..b93c0c49d15 100644 --- a/src/community/nsg-profile/pom.xml +++ b/src/community/nsg-profile/pom.xml @@ -23,18 +23,22 @@ org.geoserver gs-wfs - ${project.version} org.geoserver.web gs-web-core - ${project.version} org.geotools.jdbc gt-jdbc-h2 + + + org.eclipse.emf + org.eclipse.emf.ecore.xmi + 2.12.0 + org.geoserver diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java index 463772625dd..0abacf2fe93 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java @@ -205,8 +205,8 @@ private void manageStorageChange(Resource resource, Object newStorage) { if (exResource != null && !newResource.dir().getAbsolutePath() .equals(exResource.dir().getAbsolutePath())) { exResource.delete(); - IndexConfiguration.setStorageResource(newResource); } + IndexConfiguration.setStorageResource(newResource); } } catch (Exception exception) { throw new RuntimeException("Error on change store", exception); @@ -281,7 +281,7 @@ private void createTable(DataStore dataStore, Boolean forceDelete) throws Except * Helper method that move resource informations from current DB to the new one */ private void moveData(DataStore exDataStore, DataStore newDataStore) throws Exception { - Transaction session = new DefaultTransaction("Adding"); + Transaction session = new DefaultTransaction("Moving"); try { SimpleFeatureSource exFs = exDataStore.getFeatureSource(STORE_SCHEMA_NAME); SimpleFeatureStore newFs = (SimpleFeatureStore) newDataStore diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java index 1a4a60ff53c..7d557607294 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java @@ -9,6 +9,9 @@ import java.io.OutputStream; import java.math.BigInteger; import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; import java.util.UUID; import java.util.logging.Logger; @@ -17,18 +20,29 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl; import org.geoserver.config.GeoServer; import org.geoserver.ows.util.OwsUtils; import org.geoserver.ows.util.ResponseUtils; import org.geoserver.platform.Operation; import org.geoserver.platform.ServiceException; +import org.geoserver.platform.resource.Resource; import org.geoserver.wfs.WFSInfo; import org.geoserver.wfs.request.FeatureCollectionResponse; import org.geoserver.wfs.response.v2_0.HitsOutputFormat; +import org.geotools.data.DataStore; +import org.geotools.data.collection.ListFeatureCollection; +import org.geotools.data.simple.SimpleFeatureCollection; +import org.geotools.data.simple.SimpleFeatureStore; +import org.geotools.feature.simple.SimpleFeatureBuilder; import org.geotools.util.logging.Logging; import org.geotools.wfs.v2_0.WFS; import org.geotools.wfs.v2_0.WFSConfiguration; import org.geotools.xml.Encoder; +import org.opengis.feature.simple.SimpleFeature; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -46,28 +60,39 @@ public class IndexOutputFormat extends HitsOutputFormat { static Logger LOGGER = Logging.getLogger(IndexOutputFormat.class); - GetFeatureType request; + String resultSetId; + private static ResourceSet resSet; + + static { + // Register XMI serializer + resSet = new ResourceSetImpl(); + resSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("feature", + new XMIResourceFactoryImpl()); + } + public IndexOutputFormat(GeoServer gs) { super(gs); } - + @Override public void write(Object value, OutputStream output, Operation operation) throws IOException, ServiceException { // extract GetFeature request - request = (GetFeatureType) OwsUtils.parameter(operation.getParameters(), + GetFeatureType request = (GetFeatureType) OwsUtils.parameter(operation.getParameters(), GetFeatureType.class); // generate an UUID (resultSetID) for this request resultSetId = UUID.randomUUID().toString(); + // store request and associate it to UUID + storeGetFeature(resultSetId, request); super.write(value, output, operation); } @Override protected void encode(FeatureCollectionResponse hits, OutputStream output, WFSInfo wfs) throws IOException { - + hits.setNumberOfFeatures(BigInteger.ZERO); // instantiate the XML encoder Encoder encoder = new Encoder(new WFSConfiguration()); @@ -86,12 +111,42 @@ protected void encode(FeatureCollectionResponse hits, OutputStream output, WFSIn // write the XML document to response output stream writeDocument(document, output); } - + /** - * Helper method that serialize GetFeature request, store it in the file system and associate it with resultSetId + * Helper method that serialize GetFeature request, store it in the file system and associate it + * with resultSetId + * + * @param request + * @param resultSetId + * @throws Exception */ - protected void storeGetFeature(){ - + protected void storeGetFeature(String resultSetId, GetFeatureType ft) throws RuntimeException { + try { + DataStore dataStore = IndexConfiguration.getCurrentDataStore(); + // Create and store new feature + SimpleFeatureStore featureStore = (SimpleFeatureStore) dataStore + .getFeatureSource(IndexInitializer.STORE_SCHEMA_NAME); + SimpleFeatureBuilder builder = new SimpleFeatureBuilder(featureStore.getSchema()); + Long now = new Date().getTime(); + builder.add(resultSetId); + builder.add(now); + builder.add(now); + SimpleFeature feature = builder.buildFeature(null); + SimpleFeatureCollection collection = new ListFeatureCollection(featureStore.getSchema(), + Arrays.asList(feature)); + featureStore.addFeatures(collection); + + // Create and store file + Resource storageResource = IndexConfiguration.getStorageResource(); + + org.eclipse.emf.ecore.resource.Resource emfRes = resSet + .createResource(URI.createFileURI(storageResource.dir().getAbsolutePath() + "\\" + + resultSetId + ".feature")); + emfRes.getContents().add(ft); + emfRes.save(Collections.EMPTY_MAP); + } catch (Exception exception) { + throw new RuntimeException("Error storing feature.", exception); + } } /** From 0585f094f388508dc432813b2ec4b2a264da87cd Mon Sep 17 00:00:00 2001 From: Sandro Salari Date: Wed, 30 Aug 2017 11:40:29 +0200 Subject: [PATCH 12/15] Implemented PageResults operation --- .../pagination/random/IndexConfiguration.java | 3 + .../pagination/random/IndexInitializer.java | 51 ++++--- .../pagination/random/IndexOutputFormat.java | 6 +- ...=> IndexResultTypeDispatcherCallback.java} | 14 +- .../random/PageResultsDispatcherCallback.java | 52 +++++++ .../random/PageResultsWebFeatureService.java | 143 ++++++++++++++++++ .../random/ResultTypeKvpParser.java | 23 +++ .../src/main/resources/applicationContext.xml | 42 ++++- 8 files changed, 295 insertions(+), 39 deletions(-) rename src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/{IndexResultTypeDisapatcherCallback.java => IndexResultTypeDispatcherCallback.java} (86%) create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsDispatcherCallback.java create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsWebFeatureService.java create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/ResultTypeKvpParser.java diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java index 4b352213440..a886fcaa10a 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java @@ -13,6 +13,9 @@ /** * * Class used to store the index result type configuration managed by {@link IndexInitializer} + * + * @author sandr + * */ public class IndexConfiguration { diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java index 0abacf2fe93..2ece19553c2 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java @@ -36,6 +36,7 @@ import org.geotools.data.simple.SimpleFeatureIterator; import org.geotools.data.simple.SimpleFeatureSource; import org.geotools.data.simple.SimpleFeatureStore; +import org.geotools.feature.NameImpl; import org.geotools.filter.text.cql2.CQL; import org.geotools.jdbc.JDBCDataStoreFactory; import org.geotools.util.logging.Logging; @@ -89,7 +90,7 @@ public class IndexInitializer implements GeoServerInitializer { public static final String STORE_SCHEMA_NAME = "RESULT_SET"; - public static final String STORE_SCHEMA = "ID:\"\",created:0,updated:0"; + public static final String STORE_SCHEMA = "ID:java.lang.String,created:java.lang.Long,updated:java.lang.Long"; /* * Lock to synchronize activity of clean task with listener that changes the DB and file @@ -248,31 +249,31 @@ private Boolean isStorageTheSame(Map newParams) { return currentParams.get(JDBCDataStoreFactory.DBTYPE.key) .equals(newParams.get(JDBCDataStoreFactory.DBTYPE.key)) && currentParams.get(JDBCDataStoreFactory.DATABASE.key) - .equals(newParams.get(JDBCDataStoreFactory.DATABASE.key)) + .equals(newParams.get(JDBCDataStoreFactory.DATABASE.key)) && currentParams.get(JDBCDataStoreFactory.HOST.key) - .equals(newParams.get(JDBCDataStoreFactory.HOST.key)) + .equals(newParams.get(JDBCDataStoreFactory.HOST.key)) && currentParams.get(JDBCDataStoreFactory.PORT.key) - .equals(newParams.get(JDBCDataStoreFactory.PORT.key)) + .equals(newParams.get(JDBCDataStoreFactory.PORT.key)) && currentParams.get(JDBCDataStoreFactory.SCHEMA.key) - .equals(newParams.get(JDBCDataStoreFactory.SCHEMA.key)); + .equals(newParams.get(JDBCDataStoreFactory.SCHEMA.key)); } /** * Helper method that create a new table on DB to store resource informations */ private void createTable(DataStore dataStore, Boolean forceDelete) throws Exception { - SimpleFeatureType schema = dataStore.getSchema(STORE_SCHEMA_NAME); + Boolean exists = dataStore.getNames().contains(new NameImpl(STORE_SCHEMA_NAME)); // Schema exists - if (schema != null) { + if (exists) { // Delete of exist is required, and then create a new one if (forceDelete) { dataStore.removeSchema(STORE_SCHEMA_NAME); - schema = DataUtilities.createType(STORE_SCHEMA_NAME, STORE_SCHEMA); + SimpleFeatureType schema = DataUtilities.createType(STORE_SCHEMA_NAME, STORE_SCHEMA); dataStore.createSchema(schema); } // Schema not exists, create a new one } else { - schema = DataUtilities.createType(STORE_SCHEMA_NAME, STORE_SCHEMA); + SimpleFeatureType schema = DataUtilities.createType(STORE_SCHEMA_NAME, STORE_SCHEMA); dataStore.createSchema(schema); } } @@ -310,28 +311,30 @@ public void clean() throws Exception { // Remove record Long timeToLive = IndexConfiguration.getTimeToLive(); DataStore currentDataStore = IndexConfiguration.getCurrentDataStore(); - SimpleFeatureStore store = (SimpleFeatureStore) currentDataStore - .getFeatureSource(STORE_SCHEMA_NAME); Long now = new Date().getTime(); Long liveTreshold = now - timeToLive * 1000; - Filter filter = CQL.toFilter("updated < " + liveTreshold); - SimpleFeatureCollection toRemoved = store.getFeatures(filter); - // Remove file - Resource currentResource = IndexConfiguration.getStorageResource(); - SimpleFeatureIterator iterator = toRemoved.features(); - try { - while (iterator.hasNext()) { - SimpleFeature feature = iterator.next(); - currentResource.get(feature.getID()).delete(); + if(currentDataStore != null){ + SimpleFeatureStore store = (SimpleFeatureStore) currentDataStore + .getFeatureSource(STORE_SCHEMA_NAME); + Filter filter = CQL.toFilter("updated < " + liveTreshold); + SimpleFeatureCollection toRemoved = store.getFeatures(filter); + // Remove file + Resource currentResource = IndexConfiguration.getStorageResource(); + SimpleFeatureIterator iterator = toRemoved.features(); + try { + while (iterator.hasNext()) { + SimpleFeature feature = iterator.next(); + currentResource.get(feature.getID()).delete(); + } + } finally { + iterator.close(); } - } finally { - iterator.close(); + store.removeFeatures(filter); } - store.removeFeatures(filter); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("CLEAN executed, removed stored requests older than " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") - .format(new Date(liveTreshold))); + .format(new Date(liveTreshold))); } } catch (Throwable t) { session.rollback(); diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java index 7d557607294..ee2104b8ec6 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java @@ -52,7 +52,7 @@ /** * This output format handles requests if the original requested result type was "index"
- * See {@link IndexResultTypeDisapatcherCallback} + * See {@link IndexResultTypeDispatcherCallback} * * @author sandr * @@ -83,7 +83,7 @@ public void write(Object value, OutputStream output, Operation operation) GetFeatureType request = (GetFeatureType) OwsUtils.parameter(operation.getParameters(), GetFeatureType.class); // generate an UUID (resultSetID) for this request - resultSetId = UUID.randomUUID().toString(); + resultSetId = UUID.randomUUID().toString().replaceAll("-", ""); // store request and associate it to UUID storeGetFeature(resultSetId, request); super.write(value, output, operation); @@ -138,7 +138,7 @@ protected void storeGetFeature(String resultSetId, GetFeatureType ft) throws Run // Create and store file Resource storageResource = IndexConfiguration.getStorageResource(); - + org.eclipse.emf.ecore.resource.Resource emfRes = resSet .createResource(URI.createFileURI(storageResource.dir().getAbsolutePath() + "\\" + resultSetId + ".feature")); diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDisapatcherCallback.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDispatcherCallback.java similarity index 86% rename from src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDisapatcherCallback.java rename to src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDispatcherCallback.java index 6a7f49e4cc2..fc96a618f06 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDisapatcherCallback.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDispatcherCallback.java @@ -26,13 +26,17 @@ * to KVP maps *

*

- * The object that manage response of type HitsOutputFormat is replaced with IndexOutputFormat before response has been dispatched + * The object that manage response of type HitsOutputFormat is replaced with IndexOutputFormat + * before response has been dispatched *

+ * + * @author sandr + * */ -public class IndexResultTypeDisapatcherCallback extends AbstractDispatcherCallback { - - static Logger LOGGER = Logging.getLogger(IndexOutputFormat.class); +public class IndexResultTypeDispatcherCallback extends AbstractDispatcherCallback { + + static Logger LOGGER = Logging.getLogger(IndexResultTypeDispatcherCallback.class); private GeoServer gs; @@ -42,7 +46,7 @@ public class IndexResultTypeDisapatcherCallback extends AbstractDispatcherCallba static final String RESULT_TYPE_INDEX_PARAMETER = "RESULT_TYPE_INDEX"; - public IndexResultTypeDisapatcherCallback(GeoServer gs) { + public IndexResultTypeDispatcherCallback(GeoServer gs) { this.gs = gs; } diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsDispatcherCallback.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsDispatcherCallback.java new file mode 100644 index 00000000000..e2a0078f835 --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsDispatcherCallback.java @@ -0,0 +1,52 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ + +package org.geoserver.nsg.pagination.random; + +import java.util.Arrays; +import java.util.logging.Logger; + +import org.geoserver.config.GeoServer; +import org.geoserver.ows.AbstractDispatcherCallback; +import org.geoserver.ows.Request; +import org.geoserver.platform.Service; +import org.geoserver.platform.ServiceException; +import org.geotools.util.logging.Logging; + +/** + * + * This dispatcher manages service of type {@link PageResultsWebFeatureService} and sets the + * parameter ResultSetID present on KVP map. + *

+ * Dummy featureId value is added to KVP map to allow dispatcher to manage it as usual WFS 2.0 + * request. + * + * @author sandr + * + */ + +public class PageResultsDispatcherCallback extends AbstractDispatcherCallback { + + static Logger LOGGER = Logging.getLogger(PageResultsDispatcherCallback.class); + + private GeoServer gs; + + public PageResultsDispatcherCallback(GeoServer gs) { + this.gs = gs; + } + + @SuppressWarnings("unchecked") + @Override + public Service serviceDispatched(Request request, Service service) throws ServiceException { + if (service.getService() instanceof PageResultsWebFeatureService) { + PageResultsWebFeatureService prService = (PageResultsWebFeatureService) service + .getService(); + prService.setResultSetID((String) request.getKvp().get("resultSetID")); + request.getKvp().put("featureId", Arrays.asList("dummy")); + } + return super.serviceDispatched(request, service); + } + +} diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsWebFeatureService.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsWebFeatureService.java new file mode 100644 index 00000000000..e67ee835d8c --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsWebFeatureService.java @@ -0,0 +1,143 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ + +package org.geoserver.nsg.pagination.random; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.Date; +import java.util.logging.Logger; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl; +import org.geoserver.config.GeoServer; +import org.geoserver.platform.resource.Resource; +import org.geoserver.wfs.DefaultWebFeatureService20; +import org.geoserver.wfs.WFSException; +import org.geoserver.wfs.request.FeatureCollectionResponse; +import org.geotools.data.DataStore; +import org.geotools.data.DefaultTransaction; +import org.geotools.data.Transaction; +import org.geotools.data.simple.SimpleFeatureStore; +import org.geotools.filter.text.cql2.CQL; +import org.geotools.util.logging.Logging; +import org.opengis.filter.Filter; + +import net.opengis.wfs20.GetFeatureType; +import net.opengis.wfs20.ResultTypeType; + +/** + * This service supports the PageResults operation and manage it + * + * @author sandr + * + */ + +public class PageResultsWebFeatureService extends DefaultWebFeatureService20 { + + static Logger LOGGER = Logging.getLogger(PageResultsWebFeatureService.class); + + private static ResourceSet resSet; + + private static final String GML32_FORMAT = "application/gml+xml; version=3.2"; + + private static final BigInteger DEFAULT_START = new BigInteger("0"); + + private static final BigInteger DEFAULT_COUNT = new BigInteger("10"); + + private String resultSetID; + + public PageResultsWebFeatureService(GeoServer geoServer) { + super(geoServer); + // Register XMI serializer + resSet = new ResourceSetImpl(); + resSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("feature", + new XMIResourceFactoryImpl()); + } + + /** + * Recovers the stored request with associated {@link #resultSetID} and overrides the parameters + * using the ones provided with current operation or the default values: + *

    + *
  • {@link net.opengis.wfs20.GetFeatureType#getStartIndex StartIndex}
  • + *
  • {@link net.opengis.wfs20.GetFeatureType#getCount Count}
  • + *
  • {@link net.opengis.wfs20.GetFeatureType#getOutputFormat OutputFormat}
  • + *
  • {@link net.opengis.wfs20.GetFeatureType#getResultType ResultType}
  • + *
+ * Then executes the GetFeature operation using the WFS 2.0 service implementation and return is + * result. + * + * @param request + * @return + * @throws WFSException + * @throws IOException + */ + public FeatureCollectionResponse pageResults(GetFeatureType request) + throws WFSException, IOException { + // Retrieve stored request + GetFeatureType gft = getFeature(resultSetID); + // Update with incoming parameters or defaults + BigInteger startIndex = request.getStartIndex() != null ? request.getStartIndex() + : DEFAULT_START; + BigInteger count = request.getCount() != null ? request.getCount() : DEFAULT_COUNT; + String outputFormat = request.getOutputFormat() != null ? request.getOutputFormat() + : GML32_FORMAT; + ResultTypeType resultType = request.getResultType() != null ? request.getResultType() + : ResultTypeType.RESULTS; + gft.setStartIndex(startIndex); + gft.setCount(count); + gft.setOutputFormat(outputFormat); + gft.setResultType(resultType); + // Execute as getFeature + return super.getFeature(gft); + } + + /** + * Sets the resultSetID + * + * @param resultSetID + */ + public void setResultSetID(String resultSetID) { + this.resultSetID = resultSetID; + } + + /** + * Helper method that deserializes GetFeature request and updates its last utilization + * + * @param resultSetID + * @return + * @throws IOException + * @throws Exception + */ + private GetFeatureType getFeature(String resultSetID) throws IOException { + GetFeatureType feature = null; + Transaction transaction = new DefaultTransaction("Update"); + try { + // Update GetFeature utilization + DataStore currentDataStore = IndexConfiguration.getCurrentDataStore(); + SimpleFeatureStore store = (SimpleFeatureStore) currentDataStore + .getFeatureSource(IndexInitializer.STORE_SCHEMA_NAME); + store.setTransaction(transaction); + Filter filter = CQL.toFilter("ID = '" + resultSetID + "'"); + store.modifyFeatures("updated", new Date().getTime(), filter); + // Retrieve GetFeature from file + Resource storageResource = IndexConfiguration.getStorageResource(); + org.eclipse.emf.ecore.resource.Resource emfRes = resSet.getResource(URI.createFileURI( + storageResource.dir().getAbsolutePath() + "\\" + resultSetID + ".feature"), + true); + feature = (GetFeatureType) emfRes.getContents().get(0); + } catch (Exception t) { + transaction.rollback(); + throw new RuntimeException("Error on retrive feature", t); + } finally { + transaction.close(); + } + return feature; + + } + +} diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/ResultTypeKvpParser.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/ResultTypeKvpParser.java new file mode 100644 index 00000000000..bdc797dea78 --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/ResultTypeKvpParser.java @@ -0,0 +1,23 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ + +package org.geoserver.nsg.pagination.random; + +import org.geotools.util.Version; + +/** + * + * @author sandr + * + */ + +public class ResultTypeKvpParser extends org.geoserver.wfs.kvp.v2_0.ResultTypeKvpParser { + + public ResultTypeKvpParser() { + super(); + setVersion(new Version("2.0.2")); + } + +} diff --git a/src/community/nsg-profile/src/main/resources/applicationContext.xml b/src/community/nsg-profile/src/main/resources/applicationContext.xml index acdcb9c2caa..842c5b9ce9b 100644 --- a/src/community/nsg-profile/src/main/resources/applicationContext.xml +++ b/src/community/nsg-profile/src/main/resources/applicationContext.xml @@ -1,7 +1,6 @@ @@ -18,8 +17,9 @@ - + + When a request using the index result type comes in a dispatcher callback @@ -27,11 +27,39 @@ + + + + The PageResults operation will allow clients to query + random positions of an existing result set (stored GetFeature + request) that was previously created using the index result type + + + + + + + + + + + + + + + PageResults + + + + + - - + - + From 7e15e647dd5d5cae4f595a911bbfb326dbbe7507 Mon Sep 17 00:00:00 2001 From: Sandro Salari Date: Wed, 20 Sep 2017 16:27:52 +0200 Subject: [PATCH 13/15] Fixed review issues Implemented final response of PageResult operation --- src/community/nsg-profile/pom.xml | 179 +++++++++--------- .../pagination/random/IndexConfiguration.java | 15 +- .../pagination/random/IndexInitializer.java | 148 ++++++++------- .../pagination/random/IndexOutputFormat.java | 87 ++++++--- .../IndexResultTypeDispatcherCallback.java | 11 +- .../random/PageResultsDispatcherCallback.java | 21 +- .../random/PageResultsWebFeatureService.java | 87 ++++++--- .../nsg/pagination/random/RequestData.java | 30 +++ 8 files changed, 347 insertions(+), 231 deletions(-) create mode 100644 src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/RequestData.java diff --git a/src/community/nsg-profile/pom.xml b/src/community/nsg-profile/pom.xml index b93c0c49d15..4fcfea4c77c 100644 --- a/src/community/nsg-profile/pom.xml +++ b/src/community/nsg-profile/pom.xml @@ -1,95 +1,92 @@ - 4.0.0 - - org.geoserver - community - 2.12-SNAPSHOT - - org.geoserver.community - gs-nsg-profile - jar - 2.12-SNAPSHOT - NSG Profile - - - - org.geoserver - gs-main - tests - test - - - org.geoserver - gs-wfs - - - org.geoserver.web - gs-web-core - - - - org.geotools.jdbc - gt-jdbc-h2 - - - - org.eclipse.emf - org.eclipse.emf.ecore.xmi - 2.12.0 - - - - org.geoserver - gs-wfs - tests - ${project.version} - - - org.geoserver.web - gs-web-core - ${project.version} - tests - test - - - - org.hamcrest - hamcrest-library - test - - - org.springframework - spring-test - test - - - junit - junit - test - - - javax.servlet - javax.servlet-api - test - - - - - - ${basedir}/src/main/java - - **/*.html - - - - ${basedir}/src/main/resources - - **/* - - - - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + 4.0.0 + + org.geoserver + community + 2.12-SNAPSHOT + + org.geoserver.community + gs-nsg-profile + jar + NSG Profile + + + + org.geoserver + gs-wfs + + + org.geoserver.web + gs-web-core + + + + org.geotools.jdbc + gt-jdbc-h2 + + + com.google.code.gson + gson + + + + org.geoserver + gs-main + tests + test + + + org.geoserver + gs-wfs + tests + ${project.version} + + + org.geoserver.web + gs-web-core + ${project.version} + tests + test + + + + org.hamcrest + hamcrest-library + test + + + org.springframework + spring-test + test + + + junit + junit + test + + + javax.servlet + javax.servlet-api + test + + + + + + ${basedir}/src/main/java + + **/*.html + + + + ${basedir}/src/main/resources + + **/* + + + + diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java index a886fcaa10a..4c5abb0cbba 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java @@ -6,6 +6,7 @@ package org.geoserver.nsg.pagination.random; import java.util.Map; +import java.util.concurrent.TimeUnit; import org.geoserver.platform.resource.Resource; import org.geotools.data.DataStore; @@ -23,7 +24,7 @@ public class IndexConfiguration { private static Resource storageResource; - private static Long timeToLive = 600l; + private static Long timeToLiveInSec = 600l; private static Map currentDataStoreParams; @@ -42,8 +43,7 @@ public static void setCurrentDataStore(Map currentDataStoreParam /** * Store the reference to resource used to archive the serialized GetFeatureRequest * - * @param currentDataStoreParams - * @param currentDataStore + * @param storageResource */ public static void setStorageResource(Resource storageResource) { IndexConfiguration.storageResource = storageResource; @@ -53,9 +53,10 @@ public static void setStorageResource(Resource storageResource) { * Store the value of time to live of stored GetFeatureRequest * * @param timeToLive + * @param timeUnit */ - public static void setTimeToLive(Long timeToLive) { - IndexConfiguration.timeToLive = timeToLive; + public static void setTimeToLive(Long timeToLive, TimeUnit timeUnit) { + IndexConfiguration.timeToLiveInSec = timeUnit.toSeconds(timeToLive); } public static DataStore getCurrentDataStore() { @@ -70,8 +71,8 @@ public static Resource getStorageResource() { return storageResource; } - public static Long getTimeToLive() { - return timeToLive; + public static Long getTimeToLiveInSec() { + return timeToLiveInSec; } } diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java index 2ece19553c2..5e2337ec7be 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java @@ -14,6 +14,9 @@ import java.util.HashMap; import java.util.Map; import java.util.Properties; +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; @@ -24,8 +27,6 @@ import org.geoserver.platform.GeoServerResourceLoader; import org.geoserver.platform.resource.FileSystemResourceStore; import org.geoserver.platform.resource.Resource; -import org.geoserver.platform.resource.ResourceListener; -import org.geoserver.platform.resource.ResourceNotification; import org.geoserver.platform.resource.ResourceNotification.Kind; import org.geotools.data.DataStore; import org.geotools.data.DataStoreFinder; @@ -78,7 +79,7 @@ * */ -public class IndexInitializer implements GeoServerInitializer { +public final class IndexInitializer implements GeoServerInitializer { static Logger LOGGER = Logging.getLogger(IndexInitializer.class); @@ -96,7 +97,7 @@ public class IndexInitializer implements GeoServerInitializer { * Lock to synchronize activity of clean task with listener that changes the DB and file * resources */ - private final Object lock = new Object(); + protected static final ReadWriteLock READ_WRITE_LOCK = new ReentrantReadWriteLock(); @Override public void initialize(GeoServer geoServer) throws Exception { @@ -115,22 +116,24 @@ public void initialize(GeoServer geoServer) throws Exception { // Replace GEOSERVER_DATA_DIR placeholder properties.replaceAll((k, v) -> ((String) v).replace("${GEOSERVER_DATA_DIR}", dd.root().getPath())); + stream.close(); // Create resource and save properties - OutputStream out = resource.out(); - properties.store(out, null); - out.close(); + try { + OutputStream out = resource.out(); + properties.store(out, null); + out.close(); + } catch (Exception exception) { + throw new RuntimeException("Error to initialize configurations.", exception); + } } loadConfigurations(resource); // Listen for changes in configuration file and reload properties - resource.addListener(new ResourceListener() { - @Override - public void changed(ResourceNotification notify) { - if (notify.getKind() == Kind.ENTRY_MODIFY) { - try { - loadConfigurations(resource); - } catch (Exception exception) { - throw new RuntimeException("Error reload confiugrations.", exception); - } + resource.addListener(notify -> { + if (notify.getKind() == Kind.ENTRY_MODIFY) { + try { + loadConfigurations(resource); + } catch (Exception exception) { + throw new RuntimeException("Error reload configurations.", exception); } } }); @@ -138,12 +141,15 @@ public void changed(ResourceNotification notify) { } /** - * Helper method that + * Helper method that loads configuration file and changes environment setup */ private void loadConfigurations(Resource resource) throws IOException { - synchronized (lock) { + try { + IndexInitializer.READ_WRITE_LOCK.writeLock().lock(); Properties properties = new Properties(); - properties.load(resource.in()); + InputStream is = resource.in(); + properties.load(is); + is.close(); // Reload database Map params = new HashMap<>(); params.put(JDBCDataStoreFactory.DBTYPE.key, @@ -175,18 +181,19 @@ private void loadConfigurations(Resource resource) throws IOException { * Change time to live */ manageTimeToLiveChange(properties.get("resultSets.timeToLive")); + } finally { + IndexInitializer.READ_WRITE_LOCK.writeLock().unlock(); } - } /** - * Helper method that + * Helper method that store the time to live value */ private void manageTimeToLiveChange(Object timneToLive) { try { if (timneToLive != null) { String timneToLiveStr = (String) timneToLive; - IndexConfiguration.setTimeToLive(Long.parseLong(timneToLiveStr)); + IndexConfiguration.setTimeToLive(Long.parseLong(timneToLiveStr), TimeUnit.SECONDS); } } catch (Exception exception) { throw new RuntimeException("Error on change time to live", exception); @@ -222,13 +229,13 @@ private void manageDBChange(Map params) { DataStore exDataStore = IndexConfiguration.getCurrentDataStore(); DataStore newDataStore = DataStoreFinder.getDataStore(params); if (exDataStore != null) { - // New store is valid and is different from current one - if (newDataStore != null && !isStorageTheSame(params)) { - // Create table in new store + // New database is valid and is different from current one + if (newDataStore != null && !isDBTheSame(params)) { + // Create table in new database createTable(newDataStore, true); - // Move data to new store + // Move data to new database moveData(exDataStore, newDataStore); - // Delete old store + // Delete old database exDataStore.dispose(); } } else { @@ -237,38 +244,39 @@ private void manageDBChange(Map params) { } IndexConfiguration.setCurrentDataStore(params, newDataStore); } catch (Exception exception) { - throw new RuntimeException("Error reload DB confiugrations.", exception); + throw new RuntimeException("Error reload DB configurations.", exception); } } /** * Helper method that check id the DB is the same, matching the JDBC configurations parameters. */ - private Boolean isStorageTheSame(Map newParams) { + private Boolean isDBTheSame(Map newParams) { Map currentParams = IndexConfiguration.getCurrentDataStoreParams(); return currentParams.get(JDBCDataStoreFactory.DBTYPE.key) .equals(newParams.get(JDBCDataStoreFactory.DBTYPE.key)) && currentParams.get(JDBCDataStoreFactory.DATABASE.key) - .equals(newParams.get(JDBCDataStoreFactory.DATABASE.key)) + .equals(newParams.get(JDBCDataStoreFactory.DATABASE.key)) && currentParams.get(JDBCDataStoreFactory.HOST.key) - .equals(newParams.get(JDBCDataStoreFactory.HOST.key)) + .equals(newParams.get(JDBCDataStoreFactory.HOST.key)) && currentParams.get(JDBCDataStoreFactory.PORT.key) - .equals(newParams.get(JDBCDataStoreFactory.PORT.key)) + .equals(newParams.get(JDBCDataStoreFactory.PORT.key)) && currentParams.get(JDBCDataStoreFactory.SCHEMA.key) - .equals(newParams.get(JDBCDataStoreFactory.SCHEMA.key)); + .equals(newParams.get(JDBCDataStoreFactory.SCHEMA.key)); } /** * Helper method that create a new table on DB to store resource informations */ - private void createTable(DataStore dataStore, Boolean forceDelete) throws Exception { - Boolean exists = dataStore.getNames().contains(new NameImpl(STORE_SCHEMA_NAME)); + private void createTable(DataStore dataStore, boolean forceDelete) throws Exception { + boolean exists = dataStore.getNames().contains(new NameImpl(STORE_SCHEMA_NAME)); // Schema exists if (exists) { // Delete of exist is required, and then create a new one if (forceDelete) { dataStore.removeSchema(STORE_SCHEMA_NAME); - SimpleFeatureType schema = DataUtilities.createType(STORE_SCHEMA_NAME, STORE_SCHEMA); + SimpleFeatureType schema = DataUtilities.createType(STORE_SCHEMA_NAME, + STORE_SCHEMA); dataStore.createSchema(schema); } // Schema not exists, create a new one @@ -305,43 +313,47 @@ private void moveData(DataStore exDataStore, DataStore newDataStore) throws Exce * Executed by scheduler, for details see Spring XML configuration */ public void clean() throws Exception { - synchronized (lock) { - Transaction session = new DefaultTransaction("RemoveOld"); - try { - // Remove record - Long timeToLive = IndexConfiguration.getTimeToLive(); - DataStore currentDataStore = IndexConfiguration.getCurrentDataStore(); - Long now = new Date().getTime(); - Long liveTreshold = now - timeToLive * 1000; - if(currentDataStore != null){ - SimpleFeatureStore store = (SimpleFeatureStore) currentDataStore - .getFeatureSource(STORE_SCHEMA_NAME); - Filter filter = CQL.toFilter("updated < " + liveTreshold); - SimpleFeatureCollection toRemoved = store.getFeatures(filter); - // Remove file - Resource currentResource = IndexConfiguration.getStorageResource(); - SimpleFeatureIterator iterator = toRemoved.features(); - try { - while (iterator.hasNext()) { - SimpleFeature feature = iterator.next(); - currentResource.get(feature.getID()).delete(); - } - } finally { - iterator.close(); + Transaction session = new DefaultTransaction("RemoveOld"); + try { + IndexInitializer.READ_WRITE_LOCK.writeLock().lock(); + // Remove record + Long timeToLive = IndexConfiguration.getTimeToLiveInSec(); + DataStore currentDataStore = IndexConfiguration.getCurrentDataStore(); + Long liveTreshold = System.currentTimeMillis() - timeToLive * 1000; + long featureRemoved = 0; + if (currentDataStore != null) { + SimpleFeatureStore store = (SimpleFeatureStore) currentDataStore + .getFeatureSource(STORE_SCHEMA_NAME); + Filter filter = CQL.toFilter("updated < " + liveTreshold); + SimpleFeatureCollection toRemoved = store.getFeatures(filter); + // Remove file + Resource currentResource = IndexConfiguration.getStorageResource(); + SimpleFeatureIterator iterator = toRemoved.features(); + try { + while (iterator.hasNext()) { + SimpleFeature feature = iterator.next(); + currentResource.get(feature.getID()).delete(); + featureRemoved++; } - store.removeFeatures(filter); + } finally { + iterator.close(); } - if (LOGGER.isLoggable(Level.FINEST)) { - LOGGER.finest("CLEAN executed, removed stored requests older than " + store.removeFeatures(filter); + } + if (LOGGER.isLoggable(Level.FINEST)) { + if (featureRemoved > 0) { + LOGGER.finest("CLEAN executed, removed " + featureRemoved + + " stored requests older than " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") - .format(new Date(liveTreshold))); + .format(new Date(liveTreshold))); } - } catch (Throwable t) { - session.rollback(); - throw new RuntimeException("Error on move data", t); - } finally { - session.close(); } + } catch (Throwable t) { + session.rollback(); + throw new RuntimeException("Error on move data", t); + } finally { + session.close(); + IndexInitializer.READ_WRITE_LOCK.writeLock().unlock(); } } } diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java index ee2104b8ec6..d0e732c179f 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java @@ -5,13 +5,15 @@ package org.geoserver.nsg.pagination.random; +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.ObjectOutputStream; import java.io.OutputStream; import java.math.BigInteger; import java.nio.charset.Charset; import java.util.Arrays; -import java.util.Collections; -import java.util.Date; +import java.util.Map; import java.util.UUID; import java.util.logging.Logger; @@ -20,11 +22,8 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; -import org.eclipse.emf.common.util.URI; -import org.eclipse.emf.ecore.resource.ResourceSet; -import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; -import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl; import org.geoserver.config.GeoServer; +import org.geoserver.ows.Request; import org.geoserver.ows.util.OwsUtils; import org.geoserver.ows.util.ResponseUtils; import org.geoserver.platform.Operation; @@ -59,33 +58,31 @@ */ public class IndexOutputFormat extends HitsOutputFormat { - static Logger LOGGER = Logging.getLogger(IndexOutputFormat.class); + private final static Logger LOGGER = Logging.getLogger(IndexOutputFormat.class); - String resultSetId; + private String resultSetId; - private static ResourceSet resSet; - - static { - // Register XMI serializer - resSet = new ResourceSetImpl(); - resSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("feature", - new XMIResourceFactoryImpl()); - } + private Request request; public IndexOutputFormat(GeoServer gs) { super(gs); } + public void setRequest(Request request) { + this.request = request; + } + @Override public void write(Object value, OutputStream output, Operation operation) throws IOException, ServiceException { // extract GetFeature request - GetFeatureType request = (GetFeatureType) OwsUtils.parameter(operation.getParameters(), + GetFeatureType getFeatureType = OwsUtils.parameter(operation.getParameters(), GetFeatureType.class); - // generate an UUID (resultSetID) for this request + // generate an UUID (resultSetID) for this request, GeoTools complained about the - in the + // ID resultSetId = UUID.randomUUID().toString().replaceAll("-", ""); // store request and associate it to UUID - storeGetFeature(resultSetId, request); + storeGetFeature(resultSetId, this.request); super.write(value, output, operation); } @@ -116,36 +113,66 @@ protected void encode(FeatureCollectionResponse hits, OutputStream output, WFSIn * Helper method that serialize GetFeature request, store it in the file system and associate it * with resultSetId * - * @param request * @param resultSetId - * @throws Exception + * @param getFeatureType + * @throws RuntimeException */ - protected void storeGetFeature(String resultSetId, GetFeatureType ft) throws RuntimeException { + private void storeGetFeature(String resultSetId, Request request) throws RuntimeException { try { + IndexInitializer.READ_WRITE_LOCK.writeLock().lock(); DataStore dataStore = IndexConfiguration.getCurrentDataStore(); // Create and store new feature SimpleFeatureStore featureStore = (SimpleFeatureStore) dataStore .getFeatureSource(IndexInitializer.STORE_SCHEMA_NAME); SimpleFeatureBuilder builder = new SimpleFeatureBuilder(featureStore.getSchema()); - Long now = new Date().getTime(); + Long now = System.currentTimeMillis(); + // Add ID field value (see IndexInitializer.STORE_SCHEMA) builder.add(resultSetId); + // Add created field value (see IndexInitializer.STORE_SCHEMA) builder.add(now); + // Add updated field value (see IndexInitializer.STORE_SCHEMA) builder.add(now); SimpleFeature feature = builder.buildFeature(null); SimpleFeatureCollection collection = new ListFeatureCollection(featureStore.getSchema(), Arrays.asList(feature)); featureStore.addFeatures(collection); - // Create and store file Resource storageResource = IndexConfiguration.getStorageResource(); - - org.eclipse.emf.ecore.resource.Resource emfRes = resSet - .createResource(URI.createFileURI(storageResource.dir().getAbsolutePath() + "\\" - + resultSetId + ".feature")); - emfRes.getContents().add(ft); - emfRes.save(Collections.EMPTY_MAP); + + // Serialize KVP parameters and the POST content + Map kvp = request.getKvp(); + Map rawKvp = request.getRawKvp(); + RequestData data = new RequestData(); + data.setKvp(kvp); + data.setRawKvp(rawKvp); + // Gson gson = new GsonBuilder().setPrettyPrinting().create(); + // Writer writer = new FileWriter( + // storageResource.dir().getAbsolutePath() + "\\" + resultSetId + ".feature"); + + FileOutputStream fos = new FileOutputStream( + storageResource.dir().getAbsolutePath() + "\\" + resultSetId + ".feature"); + BufferedOutputStream bos = new BufferedOutputStream(fos); + // ObjectOutputStream oos = new ObjectOutputStream(bos); + // oos.writeObject(getFeatureType); + // oos.close(); + + /* + * Kryo kryo = new Kryo(); kryo.setInstantiatorStrategy( new + * Kryo.DefaultInstantiatorStrategy(new StdInstantiatorStrategy())); + * kryo.getFieldSerializerConfig().setCopyTransient(false); Output output = new + * Output(bos); kryo.writeObject(output, data); + */ + + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(data); + oos.close(); + + // gson.toJson(paramMap, writer); + // writer.close(); } catch (Exception exception) { throw new RuntimeException("Error storing feature.", exception); + } finally { + IndexInitializer.READ_WRITE_LOCK.writeLock().unlock(); } } diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDispatcherCallback.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDispatcherCallback.java index fc96a618f06..fa20ac40d83 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDispatcherCallback.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDispatcherCallback.java @@ -31,12 +31,12 @@ *

* * @author sandr - * + * */ public class IndexResultTypeDispatcherCallback extends AbstractDispatcherCallback { - static Logger LOGGER = Logging.getLogger(IndexResultTypeDispatcherCallback.class); + private final static Logger LOGGER = Logging.getLogger(IndexResultTypeDispatcherCallback.class); private GeoServer gs; @@ -64,12 +64,13 @@ public Request init(Request request) { @Override public Response responseDispatched(Request request, Operation operation, Object result, Response response) { - Response newResponse = response; if (request.getKvp().get(RESULT_TYPE_INDEX_PARAMETER) != null && (Boolean) request.getKvp().get(RESULT_TYPE_INDEX_PARAMETER)) { - newResponse = new IndexOutputFormat(this.gs); + IndexOutputFormat newResponse = new IndexOutputFormat(this.gs); + newResponse.setRequest(request); + return super.responseDispatched(request, operation, result, newResponse); } - return super.responseDispatched(request, operation, result, newResponse); + return super.responseDispatched(request, operation, result, response); } } diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsDispatcherCallback.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsDispatcherCallback.java index e2a0078f835..ad3ef0adc1f 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsDispatcherCallback.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsDispatcherCallback.java @@ -5,12 +5,13 @@ package org.geoserver.nsg.pagination.random; -import java.util.Arrays; +import java.util.Collections; import java.util.logging.Logger; import org.geoserver.config.GeoServer; import org.geoserver.ows.AbstractDispatcherCallback; import org.geoserver.ows.Request; +import org.geoserver.platform.Operation; import org.geoserver.platform.Service; import org.geoserver.platform.ServiceException; import org.geotools.util.logging.Logging; @@ -29,7 +30,7 @@ public class PageResultsDispatcherCallback extends AbstractDispatcherCallback { - static Logger LOGGER = Logging.getLogger(PageResultsDispatcherCallback.class); + private final static Logger LOGGER = Logging.getLogger(PageResultsDispatcherCallback.class); private GeoServer gs; @@ -43,10 +44,22 @@ public Service serviceDispatched(Request request, Service service) throws Servic if (service.getService() instanceof PageResultsWebFeatureService) { PageResultsWebFeatureService prService = (PageResultsWebFeatureService) service .getService(); - prService.setResultSetID((String) request.getKvp().get("resultSetID")); - request.getKvp().put("featureId", Arrays.asList("dummy")); + String resultSetId = (String) request.getKvp().get("resultSetID"); + prService.setResultSetID(resultSetId); + request.getKvp().put("featureId", Collections.singletonList("dummy")); + } return super.serviceDispatched(request, service); } + @Override + public Operation operationDispatched(Request request, Operation operation) { + Operation newOperation = operation; + if (operation.getId().equals("PageResults")) { + newOperation = new Operation("GetFeature", operation.getService(), + operation.getMethod(), operation.getParameters()); + } + return super.operationDispatched(request, newOperation); + } + } diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsWebFeatureService.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsWebFeatureService.java index e67ee835d8c..70a89794df0 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsWebFeatureService.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsWebFeatureService.java @@ -5,19 +5,21 @@ package org.geoserver.nsg.pagination.random; +import java.io.BufferedInputStream; +import java.io.FileInputStream; import java.io.IOException; +import java.io.ObjectInputStream; +import java.lang.reflect.Method; import java.math.BigInteger; import java.util.Date; import java.util.logging.Logger; -import org.eclipse.emf.common.util.URI; -import org.eclipse.emf.ecore.resource.ResourceSet; -import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; -import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl; import org.geoserver.config.GeoServer; +import org.geoserver.ows.Dispatcher; +import org.geoserver.ows.KvpRequestReader; +import org.geoserver.ows.util.OwsUtils; import org.geoserver.platform.resource.Resource; import org.geoserver.wfs.DefaultWebFeatureService20; -import org.geoserver.wfs.WFSException; import org.geoserver.wfs.request.FeatureCollectionResponse; import org.geotools.data.DataStore; import org.geotools.data.DefaultTransaction; @@ -41,25 +43,20 @@ public class PageResultsWebFeatureService extends DefaultWebFeatureService20 { static Logger LOGGER = Logging.getLogger(PageResultsWebFeatureService.class); - private static ResourceSet resSet; - private static final String GML32_FORMAT = "application/gml+xml; version=3.2"; private static final BigInteger DEFAULT_START = new BigInteger("0"); private static final BigInteger DEFAULT_COUNT = new BigInteger("10"); - private String resultSetID; + private String resultSetId; public PageResultsWebFeatureService(GeoServer geoServer) { super(geoServer); - // Register XMI serializer - resSet = new ResourceSetImpl(); - resSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("feature", - new XMIResourceFactoryImpl()); } /** + * * Recovers the stored request with associated {@link #resultSetID} and overrides the parameters * using the ones provided with current operation or the default values: *
    @@ -73,14 +70,15 @@ public PageResultsWebFeatureService(GeoServer geoServer) { * * @param request * @return - * @throws WFSException - * @throws IOException + * @throws Exception */ - public FeatureCollectionResponse pageResults(GetFeatureType request) - throws WFSException, IOException { + public FeatureCollectionResponse pageResults(GetFeatureType request) throws Exception { // Retrieve stored request - GetFeatureType gft = getFeature(resultSetID); + GetFeatureType gft = getFeature(this.resultSetId); + // Update with incoming parameters or defaults + Method setBaseUrl = OwsUtils.setter(gft.getClass(), "baseUrl", String.class); + setBaseUrl.invoke(gft, new Object[] { request.getBaseUrl() }); BigInteger startIndex = request.getStartIndex() != null ? request.getStartIndex() : DEFAULT_START; BigInteger count = request.getCount() != null ? request.getCount() : DEFAULT_COUNT; @@ -101,8 +99,8 @@ public FeatureCollectionResponse pageResults(GetFeatureType request) * * @param resultSetID */ - public void setResultSetID(String resultSetID) { - this.resultSetID = resultSetID; + public void setResultSetID(String resultSetId) { + this.resultSetId = resultSetId; } /** @@ -110,31 +108,68 @@ public void setResultSetID(String resultSetID) { * * @param resultSetID * @return - * @throws IOException * @throws Exception */ - private GetFeatureType getFeature(String resultSetID) throws IOException { + private GetFeatureType getFeature(String resultSetId) throws IOException { GetFeatureType feature = null; Transaction transaction = new DefaultTransaction("Update"); try { + IndexInitializer.READ_WRITE_LOCK.writeLock().lock(); // Update GetFeature utilization DataStore currentDataStore = IndexConfiguration.getCurrentDataStore(); SimpleFeatureStore store = (SimpleFeatureStore) currentDataStore .getFeatureSource(IndexInitializer.STORE_SCHEMA_NAME); store.setTransaction(transaction); - Filter filter = CQL.toFilter("ID = '" + resultSetID + "'"); + Filter filter = CQL.toFilter("ID = '" + resultSetId + "'"); store.modifyFeatures("updated", new Date().getTime(), filter); // Retrieve GetFeature from file Resource storageResource = IndexConfiguration.getStorageResource(); - org.eclipse.emf.ecore.resource.Resource emfRes = resSet.getResource(URI.createFileURI( - storageResource.dir().getAbsolutePath() + "\\" + resultSetID + ".feature"), - true); - feature = (GetFeatureType) emfRes.getContents().get(0); + + // Deserialize KVP parameters and the POST content + // Gson gson = new GsonBuilder().setPrettyPrinting().create(); + // Reader reader = new FileReader( + // storageResource.dir().getAbsolutePath() + "\\" + resultSetId + ".feature"); + // Map data = gson.fromJson(reader, Map.class); + // reader.close(); + /* + * Kryo kryo = new Kryo(); kryo.setInstantiatorStrategy( new + * Kryo.DefaultInstantiatorStrategy(new StdInstantiatorStrategy())); + * kryo.getFieldSerializerConfig().setCopyTransient(false); + */ + + FileInputStream fis = new FileInputStream( + storageResource.dir().getAbsolutePath() + "\\" + resultSetId + ".feature"); + BufferedInputStream bis = new BufferedInputStream(fis); + + ObjectInputStream objectinputstream = new ObjectInputStream(bis); + RequestData data = (RequestData) objectinputstream.readObject(); + + /* + * Input input = new Input(bis); + * + * RequestData data = kryo.readObject(input, RequestData.class); + */ + + objectinputstream.close(); + + KvpRequestReader kvpReader = Dispatcher.findKvpRequestReader(GetFeatureType.class); + Object requestBean = kvpReader.createRequest(); + feature = (GetFeatureType) kvpReader.read(requestBean, data.getKvp(), data.getRawKvp()); + + /* + * Map kvp = data.get("kvp"); Map rawKvp = data.get("rawKvp"); + * + * KvpRequestReader kvpReader = Dispatcher.findKvpRequestReader(GetFeatureType.class); + * Object requestBean = kvpReader.createRequest(); feature = (GetFeatureType) + * kvpReader.read(requestBean, kvp, rawKvp); + */ + } catch (Exception t) { transaction.rollback(); throw new RuntimeException("Error on retrive feature", t); } finally { transaction.close(); + IndexInitializer.READ_WRITE_LOCK.writeLock().unlock(); } return feature; diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/RequestData.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/RequestData.java new file mode 100644 index 00000000000..3ca196bf82a --- /dev/null +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/RequestData.java @@ -0,0 +1,30 @@ +package org.geoserver.nsg.pagination.random; + +import java.io.Serializable; +import java.util.Map; + +public class RequestData implements Serializable { + + private static final long serialVersionUID = 6687946816946977568L; + + private Map kvp; + + private Map rawKvp; + + public Map getKvp() { + return kvp; + } + + public void setKvp(Map kvp) { + this.kvp = kvp; + } + + public Map getRawKvp() { + return rawKvp; + } + + public void setRawKvp(Map rawKvp) { + this.rawKvp = rawKvp; + } + +} From 21754e0e0aa8a99e9d26ac029af06435c7346797 Mon Sep 17 00:00:00 2001 From: Sandro Salari Date: Fri, 22 Sep 2017 16:41:42 +0200 Subject: [PATCH 14/15] Refactoring and test --- .../pagination/random/IndexConfiguration.java | 30 +-- .../pagination/random/IndexInitializer.java | 76 ++++--- .../pagination/random/IndexOutputFormat.java | 24 +-- .../IndexResultTypeDispatcherCallback.java | 14 +- .../random/PageResultsDispatcherCallback.java | 4 +- .../random/PageResultsWebFeatureService.java | 49 ++--- .../nsg/pagination/random/RequestData.java | 13 +- .../src/main/resources/applicationContext.xml | 5 + .../main/resources/configuration.properties | 2 +- .../random/GetFeatureRequestStorageTest.java | 189 ++++++++++++++++++ .../random/IndexResultTypeTest.java | 131 ++++++++++++ 11 files changed, 438 insertions(+), 99 deletions(-) create mode 100644 src/community/nsg-profile/src/test/java/org/geoserver/nsg/pagination/random/GetFeatureRequestStorageTest.java create mode 100644 src/community/nsg-profile/src/test/java/org/geoserver/nsg/pagination/random/IndexResultTypeTest.java diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java index 4c5abb0cbba..d7c33bb703a 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexConfiguration.java @@ -20,13 +20,13 @@ */ public class IndexConfiguration { - private static DataStore currentDataStore; + private DataStore currentDataStore; - private static Resource storageResource; + private Resource storageResource; - private static Long timeToLiveInSec = 600l; + private Long timeToLiveInSec = 600l; - private static Map currentDataStoreParams; + private Map currentDataStoreParams; /** * Store the DB parameters and the relative {@link DataStore} @@ -34,10 +34,10 @@ public class IndexConfiguration { * @param currentDataStoreParams * @param currentDataStore */ - public static void setCurrentDataStore(Map currentDataStoreParams, + public void setCurrentDataStore(Map currentDataStoreParams, DataStore currentDataStore) { - IndexConfiguration.currentDataStoreParams = currentDataStoreParams; - IndexConfiguration.currentDataStore = currentDataStore; + this.currentDataStoreParams = currentDataStoreParams; + this.currentDataStore = currentDataStore; } /** @@ -45,8 +45,8 @@ public static void setCurrentDataStore(Map currentDataStoreParam * * @param storageResource */ - public static void setStorageResource(Resource storageResource) { - IndexConfiguration.storageResource = storageResource; + public void setStorageResource(Resource storageResource) { + this.storageResource = storageResource; } /** @@ -55,23 +55,23 @@ public static void setStorageResource(Resource storageResource) { * @param timeToLive * @param timeUnit */ - public static void setTimeToLive(Long timeToLive, TimeUnit timeUnit) { - IndexConfiguration.timeToLiveInSec = timeUnit.toSeconds(timeToLive); + public void setTimeToLive(Long timeToLive, TimeUnit timeUnit) { + this.timeToLiveInSec = timeUnit.toSeconds(timeToLive); } - public static DataStore getCurrentDataStore() { + public DataStore getCurrentDataStore() { return currentDataStore; } - public static Map getCurrentDataStoreParams() { + public Map getCurrentDataStoreParams() { return currentDataStoreParams; } - public static Resource getStorageResource() { + public Resource getStorageResource() { return storageResource; } - public static Long getTimeToLiveInSec() { + public Long getTimeToLiveInSec() { return timeToLiveInSec; } diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java index 5e2337ec7be..035d1520f90 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexInitializer.java @@ -6,7 +6,6 @@ package org.geoserver.nsg.pagination.random; import java.io.File; -import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.SimpleDateFormat; @@ -93,6 +92,8 @@ public final class IndexInitializer implements GeoServerInitializer { public static final String STORE_SCHEMA = "ID:java.lang.String,created:java.lang.Long,updated:java.lang.Long"; + private IndexConfiguration indexConfiguration; + /* * Lock to synchronize activity of clean task with listener that changes the DB and file * resources @@ -101,6 +102,7 @@ public final class IndexInitializer implements GeoServerInitializer { @Override public void initialize(GeoServer geoServer) throws Exception { + indexConfiguration = GeoServerExtensions.bean(IndexConfiguration.class); GeoServerResourceLoader loader = GeoServerExtensions.bean(GeoServerResourceLoader.class); GeoServerDataDirectory dd = new GeoServerDataDirectory(loader); Resource resource = dd.get(MODULE_DIR + "/" + PROPERTY_FILENAME); @@ -143,7 +145,7 @@ public void initialize(GeoServer geoServer) throws Exception { /** * Helper method that loads configuration file and changes environment setup */ - private void loadConfigurations(Resource resource) throws IOException { + private void loadConfigurations(Resource resource) throws Exception { try { IndexInitializer.READ_WRITE_LOCK.writeLock().lock(); Properties properties = new Properties(); @@ -181,6 +183,8 @@ private void loadConfigurations(Resource resource) throws IOException { * Change time to live */ manageTimeToLiveChange(properties.get("resultSets.timeToLive")); + } catch (Exception exception) { + throw new RuntimeException("Error reload configurations.", exception); } finally { IndexInitializer.READ_WRITE_LOCK.writeLock().unlock(); } @@ -193,7 +197,7 @@ private void manageTimeToLiveChange(Object timneToLive) { try { if (timneToLive != null) { String timneToLiveStr = (String) timneToLive; - IndexConfiguration.setTimeToLive(Long.parseLong(timneToLiveStr), TimeUnit.SECONDS); + indexConfiguration.setTimeToLive(Long.parseLong(timneToLiveStr), TimeUnit.SECONDS); } } catch (Exception exception) { throw new RuntimeException("Error on change time to live", exception); @@ -209,12 +213,12 @@ private void manageStorageChange(Resource resource, Object newStorage) { if (newStorage != null) { String newStorageStr = (String) newStorage; Resource newResource = new FileSystemResourceStore(new File(newStorageStr)).get(""); - Resource exResource = IndexConfiguration.getStorageResource(); + Resource exResource = indexConfiguration.getStorageResource(); if (exResource != null && !newResource.dir().getAbsolutePath() .equals(exResource.dir().getAbsolutePath())) { exResource.delete(); } - IndexConfiguration.setStorageResource(newResource); + indexConfiguration.setStorageResource(newResource); } } catch (Exception exception) { throw new RuntimeException("Error on change store", exception); @@ -226,7 +230,7 @@ private void manageStorageChange(Resource resource, Object newStorage) { */ private void manageDBChange(Map params) { try { - DataStore exDataStore = IndexConfiguration.getCurrentDataStore(); + DataStore exDataStore = indexConfiguration.getCurrentDataStore(); DataStore newDataStore = DataStoreFinder.getDataStore(params); if (exDataStore != null) { // New database is valid and is different from current one @@ -235,14 +239,14 @@ private void manageDBChange(Map params) { createTable(newDataStore, true); // Move data to new database moveData(exDataStore, newDataStore); - // Delete old database + // Dispose old database exDataStore.dispose(); } } else { // Create schema createTable(newDataStore, false); } - IndexConfiguration.setCurrentDataStore(params, newDataStore); + indexConfiguration.setCurrentDataStore(params, newDataStore); } catch (Exception exception) { throw new RuntimeException("Error reload DB configurations.", exception); } @@ -252,17 +256,42 @@ private void manageDBChange(Map params) { * Helper method that check id the DB is the same, matching the JDBC configurations parameters. */ private Boolean isDBTheSame(Map newParams) { - Map currentParams = IndexConfiguration.getCurrentDataStoreParams(); - return currentParams.get(JDBCDataStoreFactory.DBTYPE.key) - .equals(newParams.get(JDBCDataStoreFactory.DBTYPE.key)) - && currentParams.get(JDBCDataStoreFactory.DATABASE.key) - .equals(newParams.get(JDBCDataStoreFactory.DATABASE.key)) - && currentParams.get(JDBCDataStoreFactory.HOST.key) - .equals(newParams.get(JDBCDataStoreFactory.HOST.key)) - && currentParams.get(JDBCDataStoreFactory.PORT.key) - .equals(newParams.get(JDBCDataStoreFactory.PORT.key)) - && currentParams.get(JDBCDataStoreFactory.SCHEMA.key) - .equals(newParams.get(JDBCDataStoreFactory.SCHEMA.key)); + Map currentParams = indexConfiguration.getCurrentDataStoreParams(); + boolean isTheSame = (currentParams.get(JDBCDataStoreFactory.DBTYPE.key) == null + && newParams.get(JDBCDataStoreFactory.DBTYPE.key) == null) + || (currentParams.get(JDBCDataStoreFactory.DBTYPE.key) != null + && newParams.get(JDBCDataStoreFactory.DBTYPE.key) != null + && currentParams.get(JDBCDataStoreFactory.DBTYPE.key) + .equals(newParams.get(JDBCDataStoreFactory.DBTYPE.key))); + isTheSame = isTheSame + && (currentParams.get(JDBCDataStoreFactory.DATABASE.key) == null + && newParams.get(JDBCDataStoreFactory.DATABASE.key) == null) + || (currentParams.get(JDBCDataStoreFactory.DATABASE.key) != null + && newParams.get(JDBCDataStoreFactory.DATABASE.key) != null + && currentParams.get(JDBCDataStoreFactory.DATABASE.key) + .equals(newParams.get(JDBCDataStoreFactory.DATABASE.key))); + isTheSame = isTheSame + && (currentParams.get(JDBCDataStoreFactory.HOST.key) == null + && newParams.get(JDBCDataStoreFactory.HOST.key) == null) + || (currentParams.get(JDBCDataStoreFactory.HOST.key) != null + && newParams.get(JDBCDataStoreFactory.HOST.key) != null + && currentParams.get(JDBCDataStoreFactory.HOST.key) + .equals(newParams.get(JDBCDataStoreFactory.HOST.key))); + isTheSame = isTheSame + && (currentParams.get(JDBCDataStoreFactory.PORT.key) == null + && newParams.get(JDBCDataStoreFactory.PORT.key) == null) + || (currentParams.get(JDBCDataStoreFactory.PORT.key) != null + && newParams.get(JDBCDataStoreFactory.PORT.key) != null + && currentParams.get(JDBCDataStoreFactory.PORT.key) + .equals(newParams.get(JDBCDataStoreFactory.PORT.key))); + isTheSame = isTheSame + && (currentParams.get(JDBCDataStoreFactory.SCHEMA.key) == null + && newParams.get(JDBCDataStoreFactory.SCHEMA.key) == null) + || (currentParams.get(JDBCDataStoreFactory.SCHEMA.key) != null + && newParams.get(JDBCDataStoreFactory.SCHEMA.key) != null + && currentParams.get(JDBCDataStoreFactory.SCHEMA.key) + .equals(newParams.get(JDBCDataStoreFactory.SCHEMA.key))); + return isTheSame; } /** @@ -317,17 +346,18 @@ public void clean() throws Exception { try { IndexInitializer.READ_WRITE_LOCK.writeLock().lock(); // Remove record - Long timeToLive = IndexConfiguration.getTimeToLiveInSec(); - DataStore currentDataStore = IndexConfiguration.getCurrentDataStore(); + Long timeToLive = indexConfiguration.getTimeToLiveInSec(); + DataStore currentDataStore = indexConfiguration.getCurrentDataStore(); Long liveTreshold = System.currentTimeMillis() - timeToLive * 1000; long featureRemoved = 0; if (currentDataStore != null) { SimpleFeatureStore store = (SimpleFeatureStore) currentDataStore .getFeatureSource(STORE_SCHEMA_NAME); Filter filter = CQL.toFilter("updated < " + liveTreshold); + SimpleFeatureCollection toRemoved = store.getFeatures(filter); // Remove file - Resource currentResource = IndexConfiguration.getStorageResource(); + Resource currentResource = indexConfiguration.getStorageResource(); SimpleFeatureIterator iterator = toRemoved.features(); try { while (iterator.hasNext()) { @@ -350,7 +380,7 @@ public void clean() throws Exception { } } catch (Throwable t) { session.rollback(); - throw new RuntimeException("Error on move data", t); + LOGGER.warning("Error on clean data"); } finally { session.close(); IndexInitializer.READ_WRITE_LOCK.writeLock().unlock(); diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java index d0e732c179f..2ce258f4408 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexOutputFormat.java @@ -64,8 +64,11 @@ public class IndexOutputFormat extends HitsOutputFormat { private Request request; - public IndexOutputFormat(GeoServer gs) { + private IndexConfiguration indexConfiguration; + + public IndexOutputFormat(GeoServer gs, IndexConfiguration indexConfiguration) { super(gs); + this.indexConfiguration = indexConfiguration; } public void setRequest(Request request) { @@ -120,7 +123,7 @@ protected void encode(FeatureCollectionResponse hits, OutputStream output, WFSIn private void storeGetFeature(String resultSetId, Request request) throws RuntimeException { try { IndexInitializer.READ_WRITE_LOCK.writeLock().lock(); - DataStore dataStore = IndexConfiguration.getCurrentDataStore(); + DataStore dataStore = this.indexConfiguration.getCurrentDataStore(); // Create and store new feature SimpleFeatureStore featureStore = (SimpleFeatureStore) dataStore .getFeatureSource(IndexInitializer.STORE_SCHEMA_NAME); @@ -137,7 +140,7 @@ private void storeGetFeature(String resultSetId, Request request) throws Runtime Arrays.asList(feature)); featureStore.addFeatures(collection); // Create and store file - Resource storageResource = IndexConfiguration.getStorageResource(); + Resource storageResource = this.indexConfiguration.getStorageResource(); // Serialize KVP parameters and the POST content Map kvp = request.getKvp(); @@ -145,30 +148,15 @@ private void storeGetFeature(String resultSetId, Request request) throws Runtime RequestData data = new RequestData(); data.setKvp(kvp); data.setRawKvp(rawKvp); - // Gson gson = new GsonBuilder().setPrettyPrinting().create(); - // Writer writer = new FileWriter( - // storageResource.dir().getAbsolutePath() + "\\" + resultSetId + ".feature"); FileOutputStream fos = new FileOutputStream( storageResource.dir().getAbsolutePath() + "\\" + resultSetId + ".feature"); BufferedOutputStream bos = new BufferedOutputStream(fos); - // ObjectOutputStream oos = new ObjectOutputStream(bos); - // oos.writeObject(getFeatureType); - // oos.close(); - - /* - * Kryo kryo = new Kryo(); kryo.setInstantiatorStrategy( new - * Kryo.DefaultInstantiatorStrategy(new StdInstantiatorStrategy())); - * kryo.getFieldSerializerConfig().setCopyTransient(false); Output output = new - * Output(bos); kryo.writeObject(output, data); - */ ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(data); oos.close(); - // gson.toJson(paramMap, writer); - // writer.close(); } catch (Exception exception) { throw new RuntimeException("Error storing feature.", exception); } finally { diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDispatcherCallback.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDispatcherCallback.java index fa20ac40d83..7cd244c55ce 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDispatcherCallback.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/IndexResultTypeDispatcherCallback.java @@ -40,14 +40,17 @@ public class IndexResultTypeDispatcherCallback extends AbstractDispatcherCallbac private GeoServer gs; + private IndexConfiguration indexConfiguration; + private static final String RESULT_TYPE_PARAMETER = "resultType"; private static final String RESULT_TYPE_INDEX = "index"; static final String RESULT_TYPE_INDEX_PARAMETER = "RESULT_TYPE_INDEX"; - public IndexResultTypeDispatcherCallback(GeoServer gs) { + public IndexResultTypeDispatcherCallback(GeoServer gs, IndexConfiguration indexConfiguration) { this.gs = gs; + this.indexConfiguration = indexConfiguration; } @Override @@ -64,13 +67,14 @@ public Request init(Request request) { @Override public Response responseDispatched(Request request, Operation operation, Object result, Response response) { + Response newResponse = response; if (request.getKvp().get(RESULT_TYPE_INDEX_PARAMETER) != null && (Boolean) request.getKvp().get(RESULT_TYPE_INDEX_PARAMETER)) { - IndexOutputFormat newResponse = new IndexOutputFormat(this.gs); - newResponse.setRequest(request); - return super.responseDispatched(request, operation, result, newResponse); + IndexOutputFormat r = new IndexOutputFormat(this.gs, this.indexConfiguration); + r.setRequest(request); + newResponse = r; } - return super.responseDispatched(request, operation, result, response); + return super.responseDispatched(request, operation, result, newResponse); } } diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsDispatcherCallback.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsDispatcherCallback.java index ad3ef0adc1f..e6a25ce785c 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsDispatcherCallback.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsDispatcherCallback.java @@ -45,7 +45,7 @@ public Service serviceDispatched(Request request, Service service) throws Servic PageResultsWebFeatureService prService = (PageResultsWebFeatureService) service .getService(); String resultSetId = (String) request.getKvp().get("resultSetID"); - prService.setResultSetID(resultSetId); + prService.setResultSetId(resultSetId); request.getKvp().put("featureId", Collections.singletonList("dummy")); } @@ -55,6 +55,8 @@ public Service serviceDispatched(Request request, Service service) throws Servic @Override public Operation operationDispatched(Request request, Operation operation) { Operation newOperation = operation; + // Change operation from PageResults to GetFeature to allow management of request as + // standard get feature if (operation.getId().equals("PageResults")) { newOperation = new Operation("GetFeature", operation.getService(), operation.getMethod(), operation.getParameters()); diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsWebFeatureService.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsWebFeatureService.java index 70a89794df0..db7e5979c3c 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsWebFeatureService.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/PageResultsWebFeatureService.java @@ -51,8 +51,12 @@ public class PageResultsWebFeatureService extends DefaultWebFeatureService20 { private String resultSetId; - public PageResultsWebFeatureService(GeoServer geoServer) { + private IndexConfiguration indexConfiguration; + + public PageResultsWebFeatureService(GeoServer geoServer, + IndexConfiguration indexConfiguration) { super(geoServer); + this.indexConfiguration = indexConfiguration; } /** @@ -76,12 +80,13 @@ public FeatureCollectionResponse pageResults(GetFeatureType request) throws Exce // Retrieve stored request GetFeatureType gft = getFeature(this.resultSetId); - // Update with incoming parameters or defaults + // Update with incoming parameters or index request or defaults Method setBaseUrl = OwsUtils.setter(gft.getClass(), "baseUrl", String.class); setBaseUrl.invoke(gft, new Object[] { request.getBaseUrl() }); BigInteger startIndex = request.getStartIndex() != null ? request.getStartIndex() - : DEFAULT_START; - BigInteger count = request.getCount() != null ? request.getCount() : DEFAULT_COUNT; + : gft.getStartIndex() != null ? gft.getStartIndex() : DEFAULT_START; + BigInteger count = request.getCount() != null ? request.getCount() + : gft.getCount() != null ? gft.getCount() : DEFAULT_COUNT; String outputFormat = request.getOutputFormat() != null ? request.getOutputFormat() : GML32_FORMAT; ResultTypeType resultType = request.getResultType() != null ? request.getResultType() @@ -95,11 +100,11 @@ public FeatureCollectionResponse pageResults(GetFeatureType request) throws Exce } /** - * Sets the resultSetID + * Sets the resultSetId * - * @param resultSetID + * @param resultSetId */ - public void setResultSetID(String resultSetId) { + public void setResultSetId(String resultSetId) { this.resultSetId = resultSetId; } @@ -116,26 +121,14 @@ private GetFeatureType getFeature(String resultSetId) throws IOException { try { IndexInitializer.READ_WRITE_LOCK.writeLock().lock(); // Update GetFeature utilization - DataStore currentDataStore = IndexConfiguration.getCurrentDataStore(); + DataStore currentDataStore = this.indexConfiguration.getCurrentDataStore(); SimpleFeatureStore store = (SimpleFeatureStore) currentDataStore .getFeatureSource(IndexInitializer.STORE_SCHEMA_NAME); store.setTransaction(transaction); Filter filter = CQL.toFilter("ID = '" + resultSetId + "'"); store.modifyFeatures("updated", new Date().getTime(), filter); // Retrieve GetFeature from file - Resource storageResource = IndexConfiguration.getStorageResource(); - - // Deserialize KVP parameters and the POST content - // Gson gson = new GsonBuilder().setPrettyPrinting().create(); - // Reader reader = new FileReader( - // storageResource.dir().getAbsolutePath() + "\\" + resultSetId + ".feature"); - // Map data = gson.fromJson(reader, Map.class); - // reader.close(); - /* - * Kryo kryo = new Kryo(); kryo.setInstantiatorStrategy( new - * Kryo.DefaultInstantiatorStrategy(new StdInstantiatorStrategy())); - * kryo.getFieldSerializerConfig().setCopyTransient(false); - */ + Resource storageResource = this.indexConfiguration.getStorageResource(); FileInputStream fis = new FileInputStream( storageResource.dir().getAbsolutePath() + "\\" + resultSetId + ".feature"); @@ -144,26 +137,12 @@ private GetFeatureType getFeature(String resultSetId) throws IOException { ObjectInputStream objectinputstream = new ObjectInputStream(bis); RequestData data = (RequestData) objectinputstream.readObject(); - /* - * Input input = new Input(bis); - * - * RequestData data = kryo.readObject(input, RequestData.class); - */ - objectinputstream.close(); KvpRequestReader kvpReader = Dispatcher.findKvpRequestReader(GetFeatureType.class); Object requestBean = kvpReader.createRequest(); feature = (GetFeatureType) kvpReader.read(requestBean, data.getKvp(), data.getRawKvp()); - /* - * Map kvp = data.get("kvp"); Map rawKvp = data.get("rawKvp"); - * - * KvpRequestReader kvpReader = Dispatcher.findKvpRequestReader(GetFeatureType.class); - * Object requestBean = kvpReader.createRequest(); feature = (GetFeatureType) - * kvpReader.read(requestBean, kvp, rawKvp); - */ - } catch (Exception t) { transaction.rollback(); throw new RuntimeException("Error on retrive feature", t); diff --git a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/RequestData.java b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/RequestData.java index 3ca196bf82a..5613d142bf2 100644 --- a/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/RequestData.java +++ b/src/community/nsg-profile/src/main/java/org/geoserver/nsg/pagination/random/RequestData.java @@ -1,9 +1,20 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ + package org.geoserver.nsg.pagination.random; import java.io.Serializable; import java.util.Map; -public class RequestData implements Serializable { +/** + * This class is used to store the data to serialize to recreate previous get feature request + * + * @author sandr + * + */ +class RequestData implements Serializable { private static final long serialVersionUID = 6687946816946977568L; diff --git a/src/community/nsg-profile/src/main/resources/applicationContext.xml b/src/community/nsg-profile/src/main/resources/applicationContext.xml index 842c5b9ce9b..b3f815b3a10 100644 --- a/src/community/nsg-profile/src/main/resources/applicationContext.xml +++ b/src/community/nsg-profile/src/main/resources/applicationContext.xml @@ -17,6 +17,9 @@ + + @@ -26,6 +29,7 @@ this switch the "index" value by the "hits" value + + diff --git a/src/community/nsg-profile/src/main/resources/configuration.properties b/src/community/nsg-profile/src/main/resources/configuration.properties index 3d9f99c95c2..a1bf0a47f87 100644 --- a/src/community/nsg-profile/src/main/resources/configuration.properties +++ b/src/community/nsg-profile/src/main/resources/configuration.properties @@ -1,5 +1,5 @@ resultSets.storage.path=${GEOSERVER_DATA_DIR}/nsg-profile/resultSets -resultSets.timeToLive=601 +resultSets.timeToLive=600 resultSets.db.dbtype=h2 resultSets.db.database=${GEOSERVER_DATA_DIR}/nsg-profile/db/resultSets resultSets.db.user=sa diff --git a/src/community/nsg-profile/src/test/java/org/geoserver/nsg/pagination/random/GetFeatureRequestStorageTest.java b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/pagination/random/GetFeatureRequestStorageTest.java new file mode 100644 index 00000000000..43a0de6e65c --- /dev/null +++ b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/pagination/random/GetFeatureRequestStorageTest.java @@ -0,0 +1,189 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.nsg.pagination.random; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.apache.wicket.util.file.File; +import org.geoserver.config.GeoServerDataDirectory; +import org.geoserver.data.test.SystemTestData; +import org.geoserver.platform.GeoServerExtensions; +import org.geoserver.platform.GeoServerResourceLoader; +import org.geoserver.platform.resource.Resource; +import org.geoserver.wfs.v2_0.WFS20TestSupport; +import org.geotools.data.DataStore; +import org.geotools.data.simple.SimpleFeatureStore; +import org.geotools.filter.text.cql2.CQL; +import org.geotools.jdbc.JDBCDataStoreFactory; +import org.junit.Test; +import org.opengis.feature.simple.SimpleFeature; +import org.w3c.dom.Document; + +public class GetFeatureRequestStorageTest extends WFS20TestSupport { + + @Override + protected void onTearDown(SystemTestData testData) throws Exception { + IndexConfiguration ic = applicationContext.getBean(IndexConfiguration.class); + DataStore dataStore = ic.getCurrentDataStore(); + dataStore.dispose(); + super.onTearDown(testData); + } + + @Test + public void testCleanOldRequest() throws Exception { + Document doc = getAsDOM( + "ows?service=WFS&version=2.0.0&request=GetFeature&typeNames=cdf:Fifteen&resultType=index"); + String resultSetId = doc.getDocumentElement().getAttribute("resultSetID"); + IndexConfiguration ic = applicationContext.getBean(IndexConfiguration.class); + DataStore dataStore = ic.getCurrentDataStore(); + SimpleFeatureStore featureStore = (SimpleFeatureStore) dataStore + .getFeatureSource(IndexInitializer.STORE_SCHEMA_NAME); + SimpleFeature feature = featureStore.getFeatures(CQL.toFilter("ID='" + resultSetId + "'")) + .features().next(); + assertNotNull(feature); + + // Change timeout from default to 5 seconds + GeoServerResourceLoader loader = GeoServerExtensions.bean(GeoServerResourceLoader.class); + GeoServerDataDirectory dd = new GeoServerDataDirectory(loader); + Properties properties = new Properties(); + Resource resource = dd + .get(IndexInitializer.MODULE_DIR + "/" + IndexInitializer.PROPERTY_FILENAME); + InputStream is = resource.in(); + properties.load(is); + is.close(); + properties.put("resultSets.timeToLive", "5"); + OutputStream out = resource.out(); + properties.store(out, null); + out.close(); + final CountDownLatch done1 = new CountDownLatch(1); + new Thread(new Runnable() { + @Override + public void run() { + while (true) { + try { + Long ttl = ic.getTimeToLiveInSec(); + if (ttl == 5) { + done1.countDown(); + break; + } + Thread.sleep(100); + } catch (Exception e) { + } + } + } + }).start(); + done1.await(100, TimeUnit.SECONDS); + assertEquals(new Long(5), ic.getTimeToLiveInSec()); + // Check that feature not used is deleted + final CountDownLatch done2 = new CountDownLatch(1); + new Thread(new Runnable() { + @Override + public void run() { + while (true) { + try { + IndexInitializer.READ_WRITE_LOCK.readLock().lock(); + boolean exists = featureStore + .getFeatures(CQL.toFilter("ID='" + resultSetId + "'")).features() + .hasNext(); + IndexInitializer.READ_WRITE_LOCK.readLock().unlock(); + if (!exists) { + done2.countDown(); + break; + } + Thread.sleep(100); + } catch (Exception e) { + } + } + } + }).start(); + done2.await(100, TimeUnit.SECONDS); + boolean exists = featureStore.getFeatures(CQL.toFilter("ID='" + resultSetId + "'")) + .features().hasNext(); + assertFalse(exists); + } + + @Test + public void testConcurrentChangeDatabaseParameters() throws Exception { + GeoServerResourceLoader loader = GeoServerExtensions.bean(GeoServerResourceLoader.class); + GeoServerDataDirectory dd = new GeoServerDataDirectory(loader); + Resource resource = dd + .get(IndexInitializer.MODULE_DIR + "/" + IndexInitializer.PROPERTY_FILENAME); + Properties properties = new Properties(); + InputStream is = resource.in(); + properties.load(is); + is.close(); + properties.put(IndexInitializer.PROPERTY_DB_PREFIX + JDBCDataStoreFactory.DATABASE.key, + dd.root().getPath() + "/nsg-profile/db/resultSets2"); + IndexConfiguration ic = applicationContext.getBean(IndexConfiguration.class); + ExecutorCompletionService es = new ExecutorCompletionService<>( + Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())); + final int REQUESTS = 100; + for (int i = 0; i < REQUESTS; i++) { + int count = i; + es.submit(() -> { + getAsDOM( + "ows?service=WFS&version=2.0.0&request=GetFeature&typeNames=cdf:Fifteen&resultType=index"); + if (count == REQUESTS / 2) { + // Change database + OutputStream out = resource.out(); + properties.store(out, null); + out.close(); + } + return null; + }); + } + // just check there are no exceptions + for (int i = 0; i < REQUESTS; i++) { + es.take().get(); + } + // wait for listener receive database notification changes + File dbDataFile = new File(dd.root().getPath() + "/nsg-profile/db/resultSets2.data.db"); + final CountDownLatch done = new CountDownLatch(1); + new Thread(new Runnable() { + @Override + public void run() { + while (true) { + Map params = ic.getCurrentDataStoreParams(); + boolean condition = (dd.root().getPath() + "/nsg-profile/db/resultSets2") + .equals(params.get(JDBCDataStoreFactory.DATABASE.key)) + && dbDataFile.exists(); + if (condition) { + done.countDown(); + break; + } + try { + Thread.sleep(100); + } catch (Exception e) { + } + } + } + }).start(); + done.await(100, TimeUnit.SECONDS); + + DataStore dataStore = ic.getCurrentDataStore(); + assertTrue(dbDataFile.exists()); + + Map params = ic.getCurrentDataStoreParams(); + assertEquals(dd.root().getPath() + "/nsg-profile/db/resultSets2", + params.get(JDBCDataStoreFactory.DATABASE.key)); + + SimpleFeatureStore featureStore = (SimpleFeatureStore) dataStore + .getFeatureSource(IndexInitializer.STORE_SCHEMA_NAME); + assertEquals(REQUESTS, featureStore.getFeatures().size()); + } + +} diff --git a/src/community/nsg-profile/src/test/java/org/geoserver/nsg/pagination/random/IndexResultTypeTest.java b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/pagination/random/IndexResultTypeTest.java new file mode 100644 index 00000000000..131c939db26 --- /dev/null +++ b/src/community/nsg-profile/src/test/java/org/geoserver/nsg/pagination/random/IndexResultTypeTest.java @@ -0,0 +1,131 @@ +/* (c) 2017 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ + +package org.geoserver.nsg.pagination.random; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.custommonkey.xmlunit.XMLAssert; +import org.geoserver.data.test.SystemTestData; +import org.geoserver.wfs.v2_0.WFS20TestSupport; +import org.geotools.data.DataStore; +import org.geotools.data.simple.SimpleFeatureStore; +import org.geotools.filter.text.cql2.CQL; +import org.junit.Test; +import org.opengis.feature.simple.SimpleFeature; +import org.w3c.dom.Document; + +public class IndexResultTypeTest extends WFS20TestSupport { + + @Override + protected void onTearDown(SystemTestData testData) throws Exception { + IndexConfiguration ic = applicationContext.getBean(IndexConfiguration.class); + DataStore dataStore = ic.getCurrentDataStore(); + dataStore.dispose(); + super.onTearDown(testData); + } + + @Test + public void testIndexGet() throws Exception { + Document doc = getAsDOM( + "ows?service=WFS&version=2.0.0&request=GetFeature&typeNames=cdf:Fifteen&resultType=index"); + assertGML32(doc); + assertEquals("15", doc.getDocumentElement().getAttribute("numberMatched")); + assertEquals("0", doc.getDocumentElement().getAttribute("numberReturned")); + XMLAssert.assertXpathEvaluatesTo("0", "count(//cdf:Fifteen)", doc); + IndexConfiguration ic = applicationContext.getBean(IndexConfiguration.class); + DataStore dataStore = ic.getCurrentDataStore(); + SimpleFeatureStore featureStore = (SimpleFeatureStore) dataStore + .getFeatureSource(IndexInitializer.STORE_SCHEMA_NAME); + String resultSetId = doc.getDocumentElement().getAttribute("resultSetID"); + SimpleFeature feature = featureStore.getFeatures(CQL.toFilter("ID='" + resultSetId + "'")) + .features().next(); + assertNotNull(feature); + } + + @Test + public void testIndexPaginationGet() throws Exception { + Document doc = getAsDOM( + "ows?service=WFS&version=2.0.0&request=GetFeature&typeNames=cdf:Fifteen&resultType=index&count=2&startIndex=6"); + assertGML32(doc); + assertEquals("15", doc.getDocumentElement().getAttribute("numberMatched")); + assertEquals("0", doc.getDocumentElement().getAttribute("numberReturned")); + assertEquals( + "http://localhost:8080/geoserver/wfs?REQUEST=GetFeature&RESULTTYPE=index&VERSION=2.0.0&TYPENAMES=cdf%3AFifteen&SERVICE=WFS&COUNT=2&STARTINDEX=4", + doc.getDocumentElement().getAttribute("previous")); + assertEquals( + "http://localhost:8080/geoserver/wfs?REQUEST=GetFeature&RESULTTYPE=index&VERSION=2.0.0&TYPENAMES=cdf%3AFifteen&SERVICE=WFS&COUNT=2&STARTINDEX=8", + doc.getDocumentElement().getAttribute("next")); + XMLAssert.assertXpathEvaluatesTo("0", "count(//cdf:Fifteen)", doc); + IndexConfiguration ic = applicationContext.getBean(IndexConfiguration.class); + DataStore dataStore = ic.getCurrentDataStore(); + SimpleFeatureStore featureStore = (SimpleFeatureStore) dataStore + .getFeatureSource(IndexInitializer.STORE_SCHEMA_NAME); + String resultSetId = doc.getDocumentElement().getAttribute("resultSetID"); + SimpleFeature feature = featureStore.getFeatures(CQL.toFilter("ID='" + resultSetId + "'")) + .features().next(); + assertNotNull(feature); + } + + @Test + public void testPageResultIndexPaginationGet() throws Exception { + Document docIndex = getAsDOM( + "ows?service=WFS&version=2.0.0&request=GetFeature&typeNames=cdf:Fifteen&resultType=index&count=2&startIndex=6"); + String resultSetId = docIndex.getDocumentElement().getAttribute("resultSetID"); + Document docResutl = getAsDOM( + "ows?service=WFS&version=2.0.2&request=PageResults&resultSetID=" + resultSetId); + XMLAssert.assertXpathEvaluatesTo("2", "count(//cdf:Fifteen)", docResutl); + assertStartIndexCount(docResutl, "previous", 4, 2); + assertStartIndexCount(docResutl, "next", 8, 2); + } + + @Test + public void testPageResultDefaultPaginationGet() throws Exception { + Document docIndex = getAsDOM( + "ows?service=WFS&version=2.0.0&request=GetFeature&typeNames=cdf:Fifteen&resultType=index"); + String resultSetId = docIndex.getDocumentElement().getAttribute("resultSetID"); + Document docResutl = getAsDOM( + "ows?service=WFS&version=2.0.2&request=PageResults&resultSetID=" + resultSetId); + XMLAssert.assertXpathEvaluatesTo("10", "count(//cdf:Fifteen)", docResutl); + } + + @Test + public void testPageResultOverridePaginationGet() throws Exception { + Document docIndex = getAsDOM( + "ows?service=WFS&version=2.0.0&request=GetFeature&typeNames=cdf:Fifteen&resultType=index&count=2&startIndex=6"); + String resultSetId = docIndex.getDocumentElement().getAttribute("resultSetID"); + Document docResutl = getAsDOM( + "ows?service=WFS&version=2.0.2&request=PageResults&count=3&startIndex=5&resultSetID=" + + resultSetId); + XMLAssert.assertXpathEvaluatesTo("3", "count(//cdf:Fifteen)", docResutl); + assertStartIndexCount(docResutl, "previous", 2, 3); + assertStartIndexCount(docResutl, "next", 8, 3); + } + + void assertStartIndexCount(Document doc, String att, int startIndex, int count) { + assertTrue(doc.getDocumentElement().hasAttribute(att)); + String s = doc.getDocumentElement().getAttribute(att); + String[] kvp = s.split("\\?")[1].split("&"); + int actualStartIndex = -1; + int actualCount = -1; + + for (int i = 0; i < kvp.length; i++) { + String k = kvp[i].split("=")[0]; + String v = kvp[i].split("=")[1]; + if ("startIndex".equalsIgnoreCase(k)) { + actualStartIndex = Integer.parseInt(v); + } + if ("count".equalsIgnoreCase(k)) { + actualCount = Integer.parseInt(v); + } + } + + assertEquals(startIndex, actualStartIndex); + assertEquals(count, actualCount); + } + +} From 0d594db98f06e8bfec32d50df0a53bb039676ec2 Mon Sep 17 00:00:00 2001 From: Sandro Salari Date: Sat, 23 Sep 2017 11:12:51 +0200 Subject: [PATCH 15/15] Added documentation --- .../source/community/nsg-profile/index.rst | 202 +++++++++++++++++- 1 file changed, 201 insertions(+), 1 deletion(-) diff --git a/doc/en/user/source/community/nsg-profile/index.rst b/doc/en/user/source/community/nsg-profile/index.rst index 02490d6cbc5..9c8af4798d2 100644 --- a/doc/en/user/source/community/nsg-profile/index.rst +++ b/doc/en/user/source/community/nsg-profile/index.rst @@ -2,11 +2,211 @@ NSG Profile =========== +NSG Profile introduces a new operation for WFS 2.0.2 named PageResults. This operation will allow clients to access paginated results using random positions. +The current WFS 2.0.2 OGC specification defines a basic pagination support that can been used to navigate through features responses results. + +Pagination is activated when parameters count and startIndex are used in the query, for example: + + :: + + http:///geoserver/ows?service=WFS&version=2.0.0&request=GetFeature&typeNames=topp%3Atasmania_roads&count=5&startIndex=0 + + + +In this case each page will contain five features. +The returned feature collection will have the next and previous attributes which will contain an URL that will allow clients to navigate through the results pages, i.e. previous page and next page: + + :: + + + + +This means that this type of navigation will always be sequential, if the client is showing page two and the user wants to see page five the client will have to: + +#. request page three and use the provided next URL to retrieve page four +#. request page four and use the provided next URI to retrieve page five + +This is not an ideal solution to access random pages, which is common action. +PageResults operation will improve this by allowing clients to request random pages directly. + +Installing the extension +------------------------ + +#. Download the NSG Profile extension from the nightly GeoServer community module builds. + +#. Place the JARs into the ``WEB-INF/lib`` directory of the GeoServer installation. + +Configure the extension +----------------------- + +The root directory inside the GeoServer data directory for the nsg-profile community module is named nsg-profile and all the configurations properties are stored in a file named **configuration.properties**. + +All configuration properties are changeable at runtime, which means that if a property is updated the module take it into account. + +When the application starts if no configuration file exists one with the default values is created. + +The GetFeature requests representations associated with an index result type is serialized and stored in the file system in a location that is configurable. + +The default location, relative to the GeoServer data directory, is nsg-profie/resultSets. + +The GetFeature request to resultSetID mapping is stored by default in an H2 DB in nsg-profie/resultSets folder; for details on database configuration see `GeoTools JDBCDataStore syntax `_ + +The configuration properties are the follows: + + +.. list-table:: + :widths: 20 30 50 + :header-rows: 1 + + * - Name + - Default Value + - Description + * - resultSets.storage.path + - ${GEOSERVER_DATA_DIR}/nsg-profile/resultSets + - Path where to store GetFeature requests representations + * - resultSets.timeToLive + - 600 + - How long a GetFeature request should be maintained by the server (in seconds) + * - resultSets.db.dbtype + - h2 + - DB type used to store GetFeature request to resultSetID mapping + * - resultSets.db.database + - ${GEOSERVER_DATA_DIR}/nsg-profile/db/resultSets + - path where to store GetFeature request to resultSetID mapping + * - resultSets.db.user + - sa + - database user username + * - resultSets.db.password + - sa + - database user password + * - resultSets.db.port + - + - database port to connect to + * - resultSets.db.schema + - + - database schema + * - resultSets.db.host + - + - server to connect to + Index Result Type ----------------- +The **index result type** extends the WFS **hits result type** by adding an extra attribute named **resultSetID** to the response. +The **resultSetID** attribute can then be used by the **PageResults operation** to navigate randomly through the results. + +A GetFeature request that uses the index result type should look like this: + + :: + + http:///geoserver/ows?service=WFS&version=2.0.0&request=GetFeature&typeNames=topp%3Atasmania_roads&resultType=index + +The response of a GetFeature operation when the index result type is used should look like this: + + :: + + + + +The **resultSetID** is an unique identifier that identifies the original request. + +Clients will use the **resultSetID** with the PageResults operation to reference the original request. + +If pagination is used, the previous and next attributes should appear as in hits result type request. PageResults Operation ---------------------- \ No newline at end of file +--------------------- + +The **PageResults operation** allows clients to query random positions of an existing result set (stored GetFeature request) that was previously created using the **index result type** request. + +The available parameters are this ones: + +.. list-table:: + :widths: 40 20 40 + :header-rows: 1 + + * - Name + - Mandotry + - Default Value + * - service + - YES + - WFS + * - version + - YES + - 2.0.2 + * - request + - YES + - PageResults + * - resultSetID + - YES + - + * - startIndex + - NO + - 0 + * - count + - NO + - 10 + * - outputFormat + - NO + - application/gml+xml; version=3.2 + * - resultType + - NO + - results + * - timeout + - NO + - 300 + + +The two parameters that are not already supported by the GetFeature operation are the **resultSetID** parameter and the **timeout** parameter. + +#. The **resultSetID** parameter should reference an existing result set (stored GetFeature request). + + A typical PageResults request will look like this: + + :: + + http:///geoserver/ows?service=WFS&version=2.0.2&request=PageResults&resultSetID=ef35292477a011e7b5a5be2e44b06b34&startIndex=5&count=10&outputFormat=application/gml+xml; version=3.2&resultType=results + + + This looks like a GetFeature request where the **query expression was substituted by the resultSetID parameter**. + +#. The **timeout** parameter is not implemented yet. + +The following parameters of index request are override using the ones provided with the PageResults operation or the default values: + +#. startIndex +#. count +#. outputFormat +#. resultType + +and finally the GetFeature response is returned.