1 package org
.argeo
.geotools
.jcr
;
3 import java
.io
.IOException
;
4 import java
.util
.Collections
;
6 import java
.util
.HashMap
;
7 import java
.util
.HashSet
;
10 import java
.util
.concurrent
.Executor
;
12 import javax
.jcr
.Node
;
13 import javax
.jcr
.Property
;
14 import javax
.jcr
.RepositoryException
;
15 import javax
.jcr
.Session
;
16 import javax
.jcr
.observation
.Event
;
17 import javax
.jcr
.observation
.EventIterator
;
18 import javax
.jcr
.observation
.EventListener
;
19 import javax
.jcr
.observation
.ObservationManager
;
21 import org
.apache
.commons
.logging
.Log
;
22 import org
.apache
.commons
.logging
.LogFactory
;
23 import org
.argeo
.ArgeoException
;
24 import org
.argeo
.geotools
.GeoToolsUtils
;
25 import org
.argeo
.jcr
.JcrUtils
;
26 import org
.argeo
.jcr
.gis
.GisNames
;
27 import org
.argeo
.jcr
.gis
.GisTypes
;
28 import org
.argeo
.jts
.jcr
.JtsJcrUtils
;
29 import org
.argeo
.security
.SystemExecutionService
;
30 import org
.geotools
.data
.DataStore
;
31 import org
.geotools
.data
.DefaultTransaction
;
32 import org
.geotools
.data
.FeatureStore
;
33 import org
.geotools
.data
.Transaction
;
34 import org
.geotools
.feature
.FeatureCollection
;
35 import org
.geotools
.feature
.FeatureCollections
;
36 import org
.geotools
.feature
.simple
.SimpleFeatureBuilder
;
37 import org
.geotools
.feature
.simple
.SimpleFeatureTypeBuilder
;
38 import org
.geotools
.filter
.FilterFactoryImpl
;
39 import org
.opengis
.feature
.simple
.SimpleFeature
;
40 import org
.opengis
.feature
.simple
.SimpleFeatureType
;
41 import org
.opengis
.filter
.FilterFactory2
;
42 import org
.opengis
.filter
.identity
.FeatureId
;
43 import org
.opengis
.referencing
.crs
.CoordinateReferenceSystem
;
45 import com
.vividsolutions
.jts
.geom
.Geometry
;
46 import com
.vividsolutions
.jts
.geom
.Point
;
47 import com
.vividsolutions
.jts
.geom
.Polygon
;
49 public class GeoJcrIndex
implements EventListener
, GisNames
, GisTypes
{
51 final static String DEFAULT_GEOM_NAME
= "the_geom";
53 private final static Log log
= LogFactory
.getLog(GeoJcrIndex
.class);
55 private DataStore dataStore
;
56 private Session session
;
57 private Executor systemExecutionService
;
59 /** The key is the workspace */
60 private Map
<String
, FeatureStore
<SimpleFeatureType
, SimpleFeature
>> geoJcrIndexes
= Collections
61 .synchronizedMap(new HashMap
<String
, FeatureStore
<SimpleFeatureType
, SimpleFeature
>>());
63 // TODO: use common factory finder?
64 private FilterFactory2 ff
= new FilterFactoryImpl();
67 systemExecutionService
.execute(new Runnable() {
74 protected void initGeoJcrIndex() {
77 SimpleFeatureType indexType
= getWorkspaceGeoJcrIndexType(session
78 .getWorkspace().getName());
79 GeoToolsUtils
.createSchemaIfNeeded(dataStore
, indexType
);
81 // register JCR listeners
82 ObservationManager om
= session
.getWorkspace()
83 .getObservationManager();
84 om
.addEventListener(this, Event
.NODE_ADDED
| Event
.NODE_REMOVED
,
85 "/", true, null, null, false);
87 // FIXME: use a different listener for properties since it resets
89 // om.addEventListener(this, Event.PROPERTY_CHANGED, "/",
90 // true, null, new String[] { GIS_LOCATED }, false);
92 } catch (RepositoryException e
) {
93 throw new ArgeoException("Cannot initialize GeoJcr index", e
);
97 public void dispose() {
100 public void onEvent(final EventIterator events
) {
101 final FeatureCollection
<SimpleFeatureType
, SimpleFeature
> toAdd
= FeatureCollections
103 final Set
<FeatureId
> toRemove
= new HashSet
<FeatureId
>();
105 // execute with system authentication so that JCR can be read
106 systemExecutionService
.execute(new Runnable() {
108 while (events
.hasNext()) {
109 Event event
= events
.nextEvent();
111 Integer eventType
= event
.getType();
112 if (Event
.NODE_ADDED
== eventType
) {
113 Node node
= session
.getNodeByIdentifier(event
115 if (node
.isNodeType(GIS_INDEXED
)) {
116 SimpleFeature feature
= mapNodeToFeature(node
,
117 getGeoJcrIndex().getSchema());
120 } else if (Event
.NODE_REMOVED
== eventType
) {
121 // we have no way to check whether the node was
123 // geoindexed without querying the index, this is
125 // more optimal to create a filter with all ideas
128 String id
= event
.getIdentifier();
129 toRemove
.add(ff
.featureId(id
));
130 } else if (Event
.PROPERTY_CHANGED
== eventType
) {
131 // TODO: monitor changes to SRS, BBOX AND CENTROID
133 } catch (Exception e
) {
134 log
.error("Cannot process event " + event
, e
);
140 // TODO: this may be more optimal to persist in one single
142 // but we will loose modifications on all nodes if a single one
145 Transaction transaction
= new DefaultTransaction();
146 getGeoJcrIndex().setTransaction(transaction
);
149 getGeoJcrIndex().addFeatures(toAdd
);
150 if (toRemove
.size() != 0)
151 getGeoJcrIndex().removeFeatures(ff
.id(toRemove
));
152 transaction
.commit();
153 } catch (Exception e
) {
154 transaction
.rollback();
155 throw new ArgeoException("Cannot persist changes", e
);
159 } catch (ArgeoException e
) {
161 } catch (IOException e
) {
162 throw new ArgeoException("Unexpected issue with the transaction", e
);
166 protected FeatureStore
<SimpleFeatureType
, SimpleFeature
> getGeoJcrIndex() {
167 String workspaceName
= session
.getWorkspace().getName();
168 if (!geoJcrIndexes
.containsKey(workspaceName
)) {
169 SimpleFeatureType indexType
= getWorkspaceGeoJcrIndexType(workspaceName
);
170 FeatureStore
<SimpleFeatureType
, SimpleFeature
> geoIndex
= GeoToolsUtils
171 .getFeatureStore(dataStore
, indexType
.getName());
172 geoJcrIndexes
.put(workspaceName
, geoIndex
);
174 return geoJcrIndexes
.get(workspaceName
);
177 protected SimpleFeatureType
getWorkspaceGeoJcrIndexType(String workspaceName
) {
178 SimpleFeatureTypeBuilder builder
= new SimpleFeatureTypeBuilder();
179 builder
.setNamespaceURI(GIS_NAMESPACE
);
180 builder
.setName(workspaceName
+ "_geojcr_index");
182 builder
.setDefaultGeometry(JcrUtils
.normalize(GIS_BBOX
));
183 builder
.add(JcrUtils
.normalize(GIS_BBOX
), Polygon
.class);
184 builder
.add(JcrUtils
.normalize(GIS_CENTROID
), Point
.class);
186 builder
.add(JcrUtils
.normalize("jcr:uuid"), String
.class);
187 builder
.add(JcrUtils
.normalize("jcr:path"), String
.class);
188 builder
.add(JcrUtils
.normalize("jcr:primaryType"), String
.class);
190 builder
.add(JcrUtils
.normalize("jcr:lastModified"), Date
.class);
191 builder
.add(JcrUtils
.normalize("jcr:lastModifiedBy"), String
.class);
193 return builder
.buildFeatureType();
196 protected SimpleFeature
mapNodeToFeature(Node node
, SimpleFeatureType type
) {
198 SimpleFeatureBuilder builder
= new SimpleFeatureBuilder(type
);
201 if (node
.isNodeType(GIS_LOCATED
)) {
203 } else if (node
.isNodeType(GIS_INDEXED
)) {
204 locatedNode
= findLocatedparent(node
);
206 throw new ArgeoException("Unsupported node " + node
);
209 // TODO: reproject to the feature store SRS
210 Polygon bbox
= (Polygon
) JtsJcrUtils
.readWkb(locatedNode
211 .getProperty(GIS_BBOX
));
212 builder
.set(JcrUtils
.normalize(GIS_BBOX
), bbox
);
213 Point centroid
= (Point
) JtsJcrUtils
.readWkb(locatedNode
214 .getProperty(GIS_CENTROID
));
215 builder
.set(JcrUtils
.normalize(GIS_CENTROID
), centroid
);
217 builder
.set(JcrUtils
.normalize("jcr:uuid"), node
.getIdentifier());
218 builder
.set(JcrUtils
.normalize("jcr:path"), node
.getPath());
219 builder
.set(JcrUtils
.normalize("jcr:primaryType"), node
220 .getPrimaryNodeType().getName());
221 if (node
.hasProperty(Property
.JCR_LAST_MODIFIED
))
222 builder
.set(JcrUtils
.normalize("jcr:lastModified"), node
223 .getProperty(Property
.JCR_LAST_MODIFIED
).getDate()
225 if (node
.hasProperty(Property
.JCR_LAST_MODIFIED_BY
))
226 builder
.set(JcrUtils
.normalize("jcr:lastModifiedBy"), node
227 .getProperty(Property
.JCR_LAST_MODIFIED_BY
).getString());
228 return builder
.buildFeature(node
.getIdentifier());
229 } catch (RepositoryException e
) {
230 throw new ArgeoException("Cannot map " + node
+ " to " + type
, e
);
234 protected Node
findLocatedparent(Node child
) {
236 if (child
.getParent().isNodeType(GIS_LOCATED
))
237 return child
.getParent();
239 return findLocatedparent(child
.getParent());
240 } catch (Exception e
) {
241 // also if child is root node
242 throw new ArgeoException("Cannot find located parent", e
);
246 /** Returns the node as a point in the CRS of the related feature store. */
247 protected Geometry
reproject(CoordinateReferenceSystem crs
,
248 Geometry geometry
, CoordinateReferenceSystem targetCrs
) {
249 // transform if not same CRS
250 // FIXME: there is certainly a more standard way to reproject
251 if (!targetCrs
.getIdentifiers().contains(crs
.getName())) {
252 throw new ArgeoException("Reprojection not yet supported");
253 // MathTransform transform;
255 // transform = CRS.findMathTransform(nodeCrs, featureStoreCrs);
256 // if (geometry instanceof Point) {
257 // Point point = (Point) geometry;
258 // DirectPosition2D pos = new DirectPosition2D(nodeCrs,
259 // point.getX(), point.getY());
260 // DirectPosition targetPos = transform.transform(pos, null);
261 // return geometryFactory.createPoint(new Coordinate(targetPos
262 // .getCoordinate()[0], targetPos.getCoordinate()[1]));
263 // } else if (geometry instanceof Polygon) {
264 // Polygon polygon = (Polygon) geometry;
265 // List<Coordinate> coordinates = new ArrayList<Coordinate>();
266 // for (Coordinate coo : polygon.getExteriorRing()) {
267 // DirectPosition pos = new DirectPosition2D(nodeCrs,
269 // DirectPosition targetPos = transform.transform(pos,
271 // // coordinates.add(o)
273 // LinearRing ring = geometryFactory
274 // .createLinearRing(coordinates
275 // .toArray(new Coordinate[coordinates.size()]));
276 // return geometryFactory.createPolygon(ring, null);
278 // } catch (Exception e) {
279 // throw new ArgeoException("Cannot transform from " + nodeCrs
280 // + " to " + featureStoreCrs, e);
287 public void setDataStore(DataStore dataStore
) {
288 this.dataStore
= dataStore
;
291 public void setSession(Session session
) {
292 this.session
= session
;
295 public void setSystemExecutionService(
296 Executor systemExecutionService
) {
297 this.systemExecutionService
= systemExecutionService
;