]> git.argeo.org Git - lgpl/argeo-commons.git/blob - gis/runtime/org.argeo.gis.geotools/src/main/java/org/argeo/geotools/jcr/GeoJcrIndex.java
a1175abc72a384daec388dc3618623a7eb6c84a7
[lgpl/argeo-commons.git] / gis / runtime / org.argeo.gis.geotools / src / main / java / org / argeo / geotools / jcr / GeoJcrIndex.java
1 package org.argeo.geotools.jcr;
2
3 import java.io.IOException;
4 import java.util.Collections;
5 import java.util.Date;
6 import java.util.HashMap;
7 import java.util.HashSet;
8 import java.util.Map;
9 import java.util.Set;
10
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;
19
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;
43
44 import com.vividsolutions.jts.geom.Geometry;
45 import com.vividsolutions.jts.geom.Point;
46 import com.vividsolutions.jts.geom.Polygon;
47
48 public class GeoJcrIndex implements EventListener, GisNames, GisTypes {
49 // PostGIS convention
50 final static String DEFAULT_GEOM_NAME = "the_geom";
51
52 private final static Log log = LogFactory.getLog(GeoJcrIndex.class);
53
54 private DataStore dataStore;
55 private Session session;
56 private SystemExecutionService systemExecutionService;
57
58 /** The key is the workspace */
59 private Map<String, FeatureStore<SimpleFeatureType, SimpleFeature>> geoJcrIndexes = Collections
60 .synchronizedMap(new HashMap<String, FeatureStore<SimpleFeatureType, SimpleFeature>>());
61
62 // TODO: use common factory finder?
63 private FilterFactory2 ff = new FilterFactoryImpl();
64
65 public void init() {
66 systemExecutionService.executeAsSystem(new Runnable() {
67 public void run() {
68 initGeoJcrIndex();
69 }
70 });
71 }
72
73 protected void initGeoJcrIndex() {
74 try {
75 // create GIS schema
76 SimpleFeatureType indexType = getWorkspaceGeoJcrIndexType(session
77 .getWorkspace().getName());
78 GeoToolsUtils.createSchemaIfNeeded(dataStore, indexType);
79
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);
85
86 // FIXME: use a different listener for properties since it resets
87 // the filter
88 // om.addEventListener(this, Event.PROPERTY_CHANGED, "/",
89 // true, null, new String[] { GIS_LOCATED }, false);
90
91 } catch (RepositoryException e) {
92 throw new ArgeoException("Cannot initialize GeoJcr index", e);
93 }
94 }
95
96 public void dispose() {
97 }
98
99 public void onEvent(final EventIterator events) {
100 final FeatureCollection<SimpleFeatureType, SimpleFeature> toAdd = FeatureCollections
101 .newCollection();
102 final Set<FeatureId> toRemove = new HashSet<FeatureId>();
103
104 // execute with system authentication so that JCR can be read
105 systemExecutionService.executeAsSystem(new Runnable() {
106 public void run() {
107 while (events.hasNext()) {
108 Event event = events.nextEvent();
109 try {
110 Integer eventType = event.getType();
111 if (Event.NODE_ADDED == eventType) {
112 Node node = session.getNodeByIdentifier(event
113 .getIdentifier());
114 if (node.isNodeType(GIS_INDEXED)) {
115 SimpleFeature feature = mapNodeToFeature(node,
116 getGeoJcrIndex().getSchema());
117 toAdd.add(feature);
118 }
119 } else if (Event.NODE_REMOVED == eventType) {
120 // we have no way to check whether the node was
121 // actually
122 // geoindexed without querying the index, this is
123 // therefore
124 // more optimal to create a filter with all ideas
125 // and apply
126 // a remove later on
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
131 }
132 } catch (Exception e) {
133 log.error("Cannot process event " + event, e);
134 }
135 }
136 }
137 });
138
139 // TODO: this may be more optimal to persist in one single
140 // transaction,
141 // but we will loose modifications on all nodes if a single one
142 // fails
143 try {
144 Transaction transaction = new DefaultTransaction();
145 getGeoJcrIndex().setTransaction(transaction);
146 try {
147 // points
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);
155 } finally {
156 transaction.close();
157 }
158 } catch (ArgeoException e) {
159 throw e;
160 } catch (IOException e) {
161 throw new ArgeoException("Unexpected issue with the transaction", e);
162 }
163 }
164
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);
172 }
173 return geoJcrIndexes.get(workspaceName);
174 }
175
176 protected SimpleFeatureType getWorkspaceGeoJcrIndexType(String workspaceName) {
177 SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
178 builder.setNamespaceURI(GIS_NAMESPACE);
179 builder.setName(workspaceName + "_geojcr_index");
180
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);
184
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);
188 // mix:lastModified
189 builder.add(JcrUtils.normalize("jcr:lastModified"), Date.class);
190 builder.add(JcrUtils.normalize("jcr:lastModifiedBy"), String.class);
191
192 return builder.buildFeatureType();
193 }
194
195 protected SimpleFeature mapNodeToFeature(Node node, SimpleFeatureType type) {
196 try {
197 SimpleFeatureBuilder builder = new SimpleFeatureBuilder(type);
198
199 Node locatedNode;
200 if (node.isNodeType(GIS_LOCATED)) {
201 locatedNode = node;
202 } else if (node.isNodeType(GIS_INDEXED)) {
203 locatedNode = findLocatedparent(node);
204 } else {
205 throw new ArgeoException("Unsupported node " + node);
206 }
207
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);
215
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()
223 .getTime());
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);
230 }
231 }
232
233 protected Node findLocatedparent(Node child) {
234 try {
235 if (child.getParent().isNodeType(GIS_LOCATED))
236 return child.getParent();
237 else
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);
242 }
243 }
244
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;
253 // try {
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,
267 // coo.x, coo.y);
268 // DirectPosition targetPos = transform.transform(pos,
269 // null);
270 // // coordinates.add(o)
271 // }
272 // LinearRing ring = geometryFactory
273 // .createLinearRing(coordinates
274 // .toArray(new Coordinate[coordinates.size()]));
275 // return geometryFactory.createPolygon(ring, null);
276 // }
277 // } catch (Exception e) {
278 // throw new ArgeoException("Cannot transform from " + nodeCrs
279 // + " to " + featureStoreCrs, e);
280 // }
281 } else {
282 return geometry;
283 }
284 }
285
286 public void setDataStore(DataStore dataStore) {
287 this.dataStore = dataStore;
288 }
289
290 public void setSession(Session session) {
291 this.session = session;
292 }
293
294 public void setSystemExecutionService(
295 SystemExecutionService systemExecutionService) {
296 this.systemExecutionService = systemExecutionService;
297 }
298
299 }