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
;
11 import javax
.jcr
.Node
;
12 import javax
.jcr
.Property
;
13 import javax
.jcr
.RepositoryException
;
14 import javax
.jcr
.Session
;
15 import javax
.jcr
.observation
.Event
;
16 import javax
.jcr
.observation
.EventIterator
;
17 import javax
.jcr
.observation
.EventListener
;
18 import javax
.jcr
.observation
.ObservationManager
;
20 import org
.apache
.commons
.logging
.Log
;
21 import org
.apache
.commons
.logging
.LogFactory
;
22 import org
.argeo
.ArgeoException
;
23 import org
.argeo
.geotools
.GeoToolsUtils
;
24 import org
.argeo
.jcr
.JcrUtils
;
25 import org
.argeo
.jcr
.gis
.GisNames
;
26 import org
.argeo
.jcr
.gis
.GisTypes
;
27 import org
.argeo
.jts
.jcr
.JtsJcrUtils
;
28 import org
.argeo
.security
.SystemExecutionService
;
29 import org
.geotools
.data
.DataStore
;
30 import org
.geotools
.data
.DefaultTransaction
;
31 import org
.geotools
.data
.FeatureStore
;
32 import org
.geotools
.data
.Transaction
;
33 import org
.geotools
.feature
.FeatureCollection
;
34 import org
.geotools
.feature
.FeatureCollections
;
35 import org
.geotools
.feature
.simple
.SimpleFeatureBuilder
;
36 import org
.geotools
.feature
.simple
.SimpleFeatureTypeBuilder
;
37 import org
.geotools
.filter
.FilterFactoryImpl
;
38 import org
.opengis
.feature
.simple
.SimpleFeature
;
39 import org
.opengis
.feature
.simple
.SimpleFeatureType
;
40 import org
.opengis
.filter
.FilterFactory2
;
41 import org
.opengis
.filter
.identity
.FeatureId
;
42 import org
.opengis
.referencing
.crs
.CoordinateReferenceSystem
;
44 import com
.vividsolutions
.jts
.geom
.Geometry
;
45 import com
.vividsolutions
.jts
.geom
.Point
;
46 import com
.vividsolutions
.jts
.geom
.Polygon
;
48 public class GeoJcrIndex
implements EventListener
, GisNames
, GisTypes
{
50 final static String DEFAULT_GEOM_NAME
= "the_geom";
52 private final static Log log
= LogFactory
.getLog(GeoJcrIndex
.class);
54 private DataStore dataStore
;
55 private Session session
;
56 private SystemExecutionService systemExecutionService
;
58 /** The key is the workspace */
59 private Map
<String
, FeatureStore
<SimpleFeatureType
, SimpleFeature
>> geoJcrIndexes
= Collections
60 .synchronizedMap(new HashMap
<String
, FeatureStore
<SimpleFeatureType
, SimpleFeature
>>());
62 // TODO: use common factory finder?
63 private FilterFactory2 ff
= new FilterFactoryImpl();
66 systemExecutionService
.executeAsSystem(new Runnable() {
73 protected void initGeoJcrIndex() {
76 SimpleFeatureType indexType
= getWorkspaceGeoJcrIndexType(session
77 .getWorkspace().getName());
78 GeoToolsUtils
.createSchemaIfNeeded(dataStore
, indexType
);
80 // register JCR listeners
81 ObservationManager om
= session
.getWorkspace()
82 .getObservationManager();
83 om
.addEventListener(this, Event
.NODE_ADDED
| Event
.NODE_REMOVED
,
84 "/", true, null, null, false);
86 // FIXME: use a different listener for properties since it resets
88 // om.addEventListener(this, Event.PROPERTY_CHANGED, "/",
89 // true, null, new String[] { GIS_LOCATED }, false);
91 } catch (RepositoryException e
) {
92 throw new ArgeoException("Cannot initialize GeoJcr index", e
);
96 public void dispose() {
99 public void onEvent(final EventIterator events
) {
100 final FeatureCollection
<SimpleFeatureType
, SimpleFeature
> toAdd
= FeatureCollections
102 final Set
<FeatureId
> toRemove
= new HashSet
<FeatureId
>();
104 // execute with system authentication so that JCR can be read
105 systemExecutionService
.executeAsSystem(new Runnable() {
107 while (events
.hasNext()) {
108 Event event
= events
.nextEvent();
110 Integer eventType
= event
.getType();
111 if (Event
.NODE_ADDED
== eventType
) {
112 Node node
= session
.getNodeByIdentifier(event
114 if (node
.isNodeType(GIS_INDEXED
)) {
115 SimpleFeature feature
= mapNodeToFeature(node
,
116 getGeoJcrIndex().getSchema());
119 } else if (Event
.NODE_REMOVED
== eventType
) {
120 // we have no way to check whether the node was
122 // geoindexed without querying the index, this is
124 // more optimal to create a filter with all ideas
127 String id
= event
.getIdentifier();
128 toRemove
.add(ff
.featureId(id
));
129 } else if (Event
.PROPERTY_CHANGED
== eventType
) {
130 // TODO: monitor changes to SRS, BBOX AND CENTROID
132 } catch (Exception e
) {
133 log
.error("Cannot process event " + event
, e
);
139 // TODO: this may be more optimal to persist in one single
141 // but we will loose modifications on all nodes if a single one
144 Transaction transaction
= new DefaultTransaction();
145 getGeoJcrIndex().setTransaction(transaction
);
148 getGeoJcrIndex().addFeatures(toAdd
);
149 if (toRemove
.size() != 0)
150 getGeoJcrIndex().removeFeatures(ff
.id(toRemove
));
151 transaction
.commit();
152 } catch (Exception e
) {
153 transaction
.rollback();
154 throw new ArgeoException("Cannot persist changes", e
);
158 } catch (ArgeoException e
) {
160 } catch (IOException e
) {
161 throw new ArgeoException("Unexpected issue with the transaction", e
);
165 protected FeatureStore
<SimpleFeatureType
, SimpleFeature
> getGeoJcrIndex() {
166 String workspaceName
= session
.getWorkspace().getName();
167 if (!geoJcrIndexes
.containsKey(workspaceName
)) {
168 SimpleFeatureType indexType
= getWorkspaceGeoJcrIndexType(workspaceName
);
169 FeatureStore
<SimpleFeatureType
, SimpleFeature
> geoIndex
= GeoToolsUtils
170 .getFeatureStore(dataStore
, indexType
.getName());
171 geoJcrIndexes
.put(workspaceName
, geoIndex
);
173 return geoJcrIndexes
.get(workspaceName
);
176 protected SimpleFeatureType
getWorkspaceGeoJcrIndexType(String workspaceName
) {
177 SimpleFeatureTypeBuilder builder
= new SimpleFeatureTypeBuilder();
178 builder
.setNamespaceURI(GIS_NAMESPACE
);
179 builder
.setName(workspaceName
+ "_geojcr_index");
181 builder
.setDefaultGeometry(JcrUtils
.normalize(GIS_BBOX
));
182 builder
.add(JcrUtils
.normalize(GIS_BBOX
), Polygon
.class);
183 builder
.add(JcrUtils
.normalize(GIS_CENTROID
), Point
.class);
185 builder
.add(JcrUtils
.normalize("jcr:uuid"), String
.class);
186 builder
.add(JcrUtils
.normalize("jcr:path"), String
.class);
187 builder
.add(JcrUtils
.normalize("jcr:primaryType"), String
.class);
189 builder
.add(JcrUtils
.normalize("jcr:lastModified"), Date
.class);
190 builder
.add(JcrUtils
.normalize("jcr:lastModifiedBy"), String
.class);
192 return builder
.buildFeatureType();
195 protected SimpleFeature
mapNodeToFeature(Node node
, SimpleFeatureType type
) {
197 SimpleFeatureBuilder builder
= new SimpleFeatureBuilder(type
);
200 if (node
.isNodeType(GIS_LOCATED
)) {
202 } else if (node
.isNodeType(GIS_INDEXED
)) {
203 locatedNode
= findLocatedparent(node
);
205 throw new ArgeoException("Unsupported node " + node
);
208 // TODO: reproject to the feature store SRS
209 Polygon bbox
= (Polygon
) JtsJcrUtils
.readWkb(locatedNode
210 .getProperty(GIS_BBOX
));
211 builder
.set(JcrUtils
.normalize(GIS_BBOX
), bbox
);
212 Point centroid
= (Point
) JtsJcrUtils
.readWkb(locatedNode
213 .getProperty(GIS_CENTROID
));
214 builder
.set(JcrUtils
.normalize(GIS_CENTROID
), centroid
);
216 builder
.set(JcrUtils
.normalize("jcr:uuid"), node
.getIdentifier());
217 builder
.set(JcrUtils
.normalize("jcr:path"), node
.getPath());
218 builder
.set(JcrUtils
.normalize("jcr:primaryType"), node
219 .getPrimaryNodeType().getName());
220 if (node
.hasProperty(Property
.JCR_LAST_MODIFIED
))
221 builder
.set(JcrUtils
.normalize("jcr:lastModified"), node
222 .getProperty(Property
.JCR_LAST_MODIFIED
).getDate()
224 if (node
.hasProperty(Property
.JCR_LAST_MODIFIED_BY
))
225 builder
.set(JcrUtils
.normalize("jcr:lastModifiedBy"), node
226 .getProperty(Property
.JCR_LAST_MODIFIED_BY
).getString());
227 return builder
.buildFeature(node
.getIdentifier());
228 } catch (RepositoryException e
) {
229 throw new ArgeoException("Cannot map " + node
+ " to " + type
, e
);
233 protected Node
findLocatedparent(Node child
) {
235 if (child
.getParent().isNodeType(GIS_LOCATED
))
236 return child
.getParent();
238 return findLocatedparent(child
.getParent());
239 } catch (Exception e
) {
240 // also if child is root node
241 throw new ArgeoException("Cannot find located parent", e
);
245 /** Returns the node as a point in the CRS of the related feature store. */
246 protected Geometry
reproject(CoordinateReferenceSystem crs
,
247 Geometry geometry
, CoordinateReferenceSystem targetCrs
) {
248 // transform if not same CRS
249 // FIXME: there is certainly a more standard way to reproject
250 if (!targetCrs
.getIdentifiers().contains(crs
.getName())) {
251 throw new ArgeoException("Reprojection not yet supported");
252 // MathTransform transform;
254 // transform = CRS.findMathTransform(nodeCrs, featureStoreCrs);
255 // if (geometry instanceof Point) {
256 // Point point = (Point) geometry;
257 // DirectPosition2D pos = new DirectPosition2D(nodeCrs,
258 // point.getX(), point.getY());
259 // DirectPosition targetPos = transform.transform(pos, null);
260 // return geometryFactory.createPoint(new Coordinate(targetPos
261 // .getCoordinate()[0], targetPos.getCoordinate()[1]));
262 // } else if (geometry instanceof Polygon) {
263 // Polygon polygon = (Polygon) geometry;
264 // List<Coordinate> coordinates = new ArrayList<Coordinate>();
265 // for (Coordinate coo : polygon.getExteriorRing()) {
266 // DirectPosition pos = new DirectPosition2D(nodeCrs,
268 // DirectPosition targetPos = transform.transform(pos,
270 // // coordinates.add(o)
272 // LinearRing ring = geometryFactory
273 // .createLinearRing(coordinates
274 // .toArray(new Coordinate[coordinates.size()]));
275 // return geometryFactory.createPolygon(ring, null);
277 // } catch (Exception e) {
278 // throw new ArgeoException("Cannot transform from " + nodeCrs
279 // + " to " + featureStoreCrs, e);
286 public void setDataStore(DataStore dataStore
) {
287 this.dataStore
= dataStore
;
290 public void setSession(Session session
) {
291 this.session
= session
;
294 public void setSystemExecutionService(
295 SystemExecutionService systemExecutionService
) {
296 this.systemExecutionService
= systemExecutionService
;