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