package org.argeo.geotools.jcr;
import java.io.IOException;
+import java.util.Collections;
import java.util.Date;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
+import java.util.concurrent.Executor;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;
+import javax.jcr.observation.ObservationManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.argeo.jcr.JcrUtils;
import org.argeo.jcr.gis.GisNames;
import org.argeo.jcr.gis.GisTypes;
-import org.argeo.security.SystemExecutionService;
+import org.argeo.jts.jcr.JtsJcrUtils;
import org.geotools.data.DataStore;
import org.geotools.data.DefaultTransaction;
import org.geotools.data.FeatureStore;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.identity.FeatureId;
-import org.opengis.geometry.DirectPosition;
+import org.opengis.referencing.FactoryException;
+import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.operation.MathTransform;
-import com.vividsolutions.jts.geom.Coordinate;
-import com.vividsolutions.jts.geom.GeometryFactory;
+import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.Point;
+import com.vividsolutions.jts.geom.Polygon;
-public class GeoJcrIndex implements EventListener {
- public final static SimpleFeatureType JCR_POINT;
-
- final static String JCR_POINT_NAME = "JCR_POINT";
+/** Index JCR nodes containing or referencing GIS data. */
+public class GeoJcrIndex implements EventListener, GisNames, GisTypes {
// PostGIS convention
final static String DEFAULT_GEOM_NAME = "the_geom";
private final static Log log = LogFactory.getLog(GeoJcrIndex.class);
- static {
- SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
- builder.setNamespaceURI(GisNames.GIS_NAMESPACE);
- builder.setName(JCR_POINT_NAME);
-
- builder.setDefaultGeometry(DEFAULT_GEOM_NAME);
- builder.add(DEFAULT_GEOM_NAME, Point.class);
-
- builder.add(JcrUtils.normalize(Property.JCR_UUID), String.class);
- builder.add(JcrUtils.normalize(Property.JCR_PATH), String.class);
- builder.add(JcrUtils.normalize(Property.JCR_PRIMARY_TYPE), String.class);
- // mix:created
- // builder.add(JcrUtils.normalize(Property.JCR_CREATED), Date.class);
- // builder.add(JcrUtils.normalize(Property.JCR_CREATED_BY),
- // String.class);
- // mix:lastModified
- builder.add(JcrUtils.normalize(Property.JCR_LAST_MODIFIED), Date.class);
- builder.add(JcrUtils.normalize(Property.JCR_LAST_MODIFIED_BY),
- String.class);
-
- JCR_POINT = builder.buildFeatureType();
- }
-
private DataStore dataStore;
- private FeatureStore<SimpleFeatureType, SimpleFeature> pointsIndex;
private Session session;
- private SystemExecutionService systemExecutionService;
+ private Executor systemExecutionService;
+
+ private String crs = "EPSG:4326";
+
+ /** The key is the workspace */
+ private Map<String, FeatureStore<SimpleFeatureType, SimpleFeature>> geoJcrIndexes = Collections
+ .synchronizedMap(new HashMap<String, FeatureStore<SimpleFeatureType, SimpleFeature>>());
// TODO: use common factory finder?
private FilterFactory2 ff = new FilterFactoryImpl();
- // TODO: use finder?
- private GeometryFactory geometryFactory = new GeometryFactory();
-
+ /** Expects to execute with system authentication */
public void init() {
- GeoToolsUtils.createSchemaIfNeeded(dataStore, JCR_POINT);
- pointsIndex = GeoToolsUtils.getFeatureStore(dataStore,
- JCR_POINT.getName());
-
- systemExecutionService.executeAsSystem(new Runnable() {
- public void run() {
- try {
- session.getWorkspace()
- .getObservationManager()
- .addEventListener(GeoJcrIndex.this,
- Event.NODE_ADDED | Event.NODE_REMOVED, "/",
- true, null,
- new String[] { GisTypes.GIS_GEOMETRY },
- false);
- } catch (RepositoryException e) {
- throw new ArgeoException("Cannot initialize GeoJcr index",
- e);
+ if (systemExecutionService != null)// legacy
+ systemExecutionService.execute(new Runnable() {
+ public void run() {
+ initGeoJcrIndex();
}
+ });
+ else
+ initGeoJcrIndex();
+ }
- }
- });
+ protected void initGeoJcrIndex() {
+ try {
+ // create GIS schema
+ SimpleFeatureType indexType = getWorkspaceGeoJcrIndexType(session
+ .getWorkspace().getName());
+ GeoToolsUtils.createSchemaIfNeeded(dataStore, indexType);
+
+ // register JCR listeners
+ ObservationManager om = session.getWorkspace()
+ .getObservationManager();
+ om.addEventListener(this, Event.NODE_ADDED | Event.NODE_REMOVED,
+ "/", true, null, null, false);
+
+ // FIXME: use a different listener for properties since it resets
+ // the filter
+ // om.addEventListener(this, Event.PROPERTY_CHANGED, "/",
+ // true, null, new String[] { GIS_LOCATED }, false);
+
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Cannot initialize GeoJcr index", e);
+ }
}
public void dispose() {
}
- public void onEvent(EventIterator events) {
- FeatureCollection<SimpleFeatureType, SimpleFeature> pointsToAdd = FeatureCollections
+ public void onEvent(final EventIterator events) {
+ final FeatureCollection<SimpleFeatureType, SimpleFeature> toAdd = FeatureCollections
.newCollection();
- Set<FeatureId> pointsToRemove = new HashSet<FeatureId>();
- while (events.hasNext()) {
- Event event = events.nextEvent();
- try {
- Integer eventType = event.getType();
- if (Event.NODE_ADDED == eventType) {
- Node node = session.getNodeByIdentifier(event
- .getIdentifier());
- if (node.isNodeType(GisTypes.GIS_POINT)) {
- Point point = nodeToPoint(node);
- SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(
- JCR_POINT);
- featureBuilder.set(DEFAULT_GEOM_NAME, point);
- mapNodeToFeature(node, featureBuilder);
- pointsToAdd.add(featureBuilder.buildFeature(node
- .getIdentifier()));
+ final Set<FeatureId> toRemove = new HashSet<FeatureId>();
+
+ // execute with system authentication so that JCR can be read
+ systemExecutionService.execute(new Runnable() {
+ public void run() {
+ while (events.hasNext()) {
+ Event event = events.nextEvent();
+ try {
+ Integer eventType = event.getType();
+ if (Event.NODE_ADDED == eventType) {
+ Node node = session.getNodeByIdentifier(event
+ .getIdentifier());
+ if (node.isNodeType(GIS_INDEXED)) {
+ SimpleFeature feature = mapNodeToFeature(node,
+ getGeoJcrIndex().getSchema());
+ toAdd.add(feature);
+ }
+ } else if (Event.NODE_REMOVED == eventType) {
+ // we have no way to check whether the node was
+ // actually
+ // geoindexed without querying the index, this is
+ // therefore
+ // more optimal to create a filter with all ideas
+ // and apply
+ // a remove later on
+ String id = event.getIdentifier();
+ toRemove.add(ff.featureId(id));
+ } else if (Event.PROPERTY_CHANGED == eventType) {
+ // TODO: monitor changes to SRS, BBOX AND CENTROID
+ }
+ } catch (Exception e) {
+ log.error("Cannot process event " + event, e);
}
- } else if (Event.NODE_REMOVED == eventType) {
- String id = event.getIdentifier();
- pointsToRemove.add(ff.featureId(id));
}
- } catch (Exception e) {
- log.error("Cannot process event " + event, e);
}
- }
+ });
- // persist
- // TODO: this may be more optimal to persist in one single transaction,
- // but we will loose modifications on all nodes if one fails
+ // TODO: this may be more optimal to persist in one single
+ // transaction,
+ // but we will loose modifications on all nodes if a single one
+ // fails
try {
Transaction transaction = new DefaultTransaction();
- pointsIndex.setTransaction(transaction);
+ getGeoJcrIndex().setTransaction(transaction);
try {
// points
- pointsIndex.addFeatures(pointsToAdd);
- if (pointsToRemove.size() != 0)
- pointsIndex.removeFeatures(ff.id(pointsToRemove));
+ getGeoJcrIndex().addFeatures(toAdd);
+ if (toRemove.size() != 0)
+ getGeoJcrIndex().removeFeatures(ff.id(toRemove));
transaction.commit();
} catch (Exception e) {
transaction.rollback();
}
}
- protected void mapNodeToFeature(Node node,
- SimpleFeatureBuilder featureBuilder) {
+ protected FeatureStore<SimpleFeatureType, SimpleFeature> getGeoJcrIndex() {
+ String workspaceName = session.getWorkspace().getName();
+ if (!geoJcrIndexes.containsKey(workspaceName)) {
+ SimpleFeatureType indexType = getWorkspaceGeoJcrIndexType(workspaceName);
+ FeatureStore<SimpleFeatureType, SimpleFeature> geoIndex = GeoToolsUtils
+ .getFeatureStore(dataStore, indexType.getName());
+ geoJcrIndexes.put(workspaceName, geoIndex);
+ }
+ return geoJcrIndexes.get(workspaceName);
+ }
+
+ protected SimpleFeatureType getWorkspaceGeoJcrIndexType(String workspaceName) {
+
+ SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
+ builder.setNamespaceURI(GIS_NAMESPACE);
+ builder.setName(workspaceName + "_geojcr_index");
+ try {
+ builder.setCRS(CRS.decode(crs));
+ } catch (Exception e) {
+ throw new ArgeoException("Cannot set CRS " + crs, e);
+ }
+
+ builder.setDefaultGeometry(JcrUtils.normalize(GIS_BBOX));
+ builder.add(JcrUtils.normalize(GIS_BBOX), Polygon.class);
+ builder.add(JcrUtils.normalize(GIS_CENTROID), Point.class);
+
+ builder.add(JcrUtils.normalize("jcr:uuid"), String.class);
+ builder.add(JcrUtils.normalize("jcr:path"), String.class);
+ builder.add(JcrUtils.normalize("jcr:primaryType"), String.class);
+ // mix:lastModified
+ builder.add(JcrUtils.normalize("jcr:lastModified"), Date.class);
+ builder.add(JcrUtils.normalize("jcr:lastModifiedBy"), String.class);
+
+ return builder.buildFeatureType();
+ }
+
+ protected SimpleFeature mapNodeToFeature(Node node, SimpleFeatureType type) {
try {
- featureBuilder.set(JcrUtils.normalize(Property.JCR_UUID),
- node.getIdentifier());
- featureBuilder.set(JcrUtils.normalize(Property.JCR_PATH),
- node.getPath());
- featureBuilder.set(JcrUtils.normalize(Property.JCR_PRIMARY_TYPE),
- node.getPrimaryNodeType().getName());
+ SimpleFeatureBuilder builder = new SimpleFeatureBuilder(type);
+
+ Node locatedNode;
+ if (node.isNodeType(GIS_LOCATED)) {
+ locatedNode = node;
+ } else if (node.isNodeType(GIS_INDEXED)) {
+ locatedNode = findLocatedParent(node);
+ } else {
+ throw new ArgeoException("Unsupported node " + node);
+ }
+
+ // TODO: reproject to the feature store SRS
+ Polygon bbox = (Polygon) JtsJcrUtils.readWkb(locatedNode
+ .getProperty(GIS_BBOX));
+ builder.set(JcrUtils.normalize(GIS_BBOX), bbox);
+ Point centroid = (Point) JtsJcrUtils.readWkb(locatedNode
+ .getProperty(GIS_CENTROID));
+ builder.set(JcrUtils.normalize(GIS_CENTROID), centroid);
+
+ builder.set(JcrUtils.normalize("jcr:uuid"), node.getIdentifier());
+ builder.set(JcrUtils.normalize("jcr:path"), node.getPath());
+ builder.set(JcrUtils.normalize("jcr:primaryType"), node
+ .getPrimaryNodeType().getName());
if (node.hasProperty(Property.JCR_LAST_MODIFIED))
- featureBuilder.set(
- JcrUtils.normalize(Property.JCR_LAST_MODIFIED), node
- .getProperty(Property.JCR_LAST_MODIFIED)
- .getDate().getTime());
+ builder.set(JcrUtils.normalize("jcr:lastModified"), node
+ .getProperty(Property.JCR_LAST_MODIFIED).getDate()
+ .getTime());
if (node.hasProperty(Property.JCR_LAST_MODIFIED_BY))
- featureBuilder
- .set(JcrUtils.normalize(Property.JCR_LAST_MODIFIED_BY),
- node.getProperty(Property.JCR_LAST_MODIFIED_BY)
- .getString());
+ builder.set(JcrUtils.normalize("jcr:lastModifiedBy"), node
+ .getProperty(Property.JCR_LAST_MODIFIED_BY).getString());
+ return builder.buildFeature(node.getIdentifier());
} catch (RepositoryException e) {
- throw new ArgeoException("Cannot map " + node + " to "
- + featureBuilder.getFeatureType(), e);
+ throw new ArgeoException("Cannot map " + node + " to " + type, e);
}
}
- /** Return the node as a point in the CRS of the related feature store. */
- protected Point nodeToPoint(Node node) {
- CoordinateReferenceSystem featureStoreCrs = pointsIndex.getSchema()
- .getCoordinateReferenceSystem();
- DirectPosition nodePosition = GeoJcrUtils.nodeToPosition(node);
- CoordinateReferenceSystem nodeCrs = nodePosition
- .getCoordinateReferenceSystem();
+ protected Node findLocatedParent(Node child) {
+ try {
+ if (child.getParent().isNodeType(GIS_LOCATED))
+ return child.getParent();
+ else
+ return findLocatedParent(child.getParent());
+ } catch (Exception e) {
+ // also if child is root node
+ throw new ArgeoException("Cannot find located parent", e);
+ }
+ }
+ /** Returns the node as a point in the CRS of the related feature store. */
+ protected Geometry reproject(CoordinateReferenceSystem crs,
+ Geometry geometry, CoordinateReferenceSystem targetCrs) {
// transform if not same CRS
- DirectPosition targetPosition;
- if (!featureStoreCrs.getIdentifiers().contains(nodeCrs.getName())) {
- MathTransform transform;
- try {
- transform = CRS.findMathTransform(nodeCrs, featureStoreCrs);
- targetPosition = transform.transform(nodePosition, null);
- } catch (Exception e) {
- throw new ArgeoException("Cannot transform from " + nodeCrs
- + " to " + featureStoreCrs, e);
- }
+ // FIXME: there is certainly a more standard way to reproject
+ if (!targetCrs.getIdentifiers().contains(crs.getName())) {
+ throw new ArgeoException("Reprojection not yet supported");
+ // MathTransform transform;
+ // try {
+ // transform = CRS.findMathTransform(nodeCrs, featureStoreCrs);
+ // if (geometry instanceof Point) {
+ // Point point = (Point) geometry;
+ // DirectPosition2D pos = new DirectPosition2D(nodeCrs,
+ // point.getX(), point.getY());
+ // DirectPosition targetPos = transform.transform(pos, null);
+ // return geometryFactory.createPoint(new Coordinate(targetPos
+ // .getCoordinate()[0], targetPos.getCoordinate()[1]));
+ // } else if (geometry instanceof Polygon) {
+ // Polygon polygon = (Polygon) geometry;
+ // List<Coordinate> coordinates = new ArrayList<Coordinate>();
+ // for (Coordinate coo : polygon.getExteriorRing()) {
+ // DirectPosition pos = new DirectPosition2D(nodeCrs,
+ // coo.x, coo.y);
+ // DirectPosition targetPos = transform.transform(pos,
+ // null);
+ // // coordinates.add(o)
+ // }
+ // LinearRing ring = geometryFactory
+ // .createLinearRing(coordinates
+ // .toArray(new Coordinate[coordinates.size()]));
+ // return geometryFactory.createPolygon(ring, null);
+ // }
+ // } catch (Exception e) {
+ // throw new ArgeoException("Cannot transform from " + nodeCrs
+ // + " to " + featureStoreCrs, e);
+ // }
} else {
- targetPosition = nodePosition;
+ return geometry;
}
- double[] coo = targetPosition.getCoordinate();
- return geometryFactory.createPoint(new Coordinate(coo[0], coo[1]));
}
public void setDataStore(DataStore dataStore) {
this.session = session;
}
- public void setSystemExecutionService(
- SystemExecutionService systemExecutionService) {
+ public void setSystemExecutionService(Executor systemExecutionService) {
this.systemExecutionService = systemExecutionService;
}