001: //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/trunk/src/org/deegree/io/datastore/idgenerator/FeatureIdAssigner.java $
002: /*---------------- FILE HEADER ------------------------------------------
003:
004: This file is part of deegree.
005: Copyright (C) 2001-2008 by:
006: EXSE, Department of Geography, University of Bonn
007: http://www.giub.uni-bonn.de/deegree/
008: lat/lon GmbH
009: http://www.lat-lon.de
010:
011: This library is free software; you can redistribute it and/or
012: modify it under the terms of the GNU Lesser General Public
013: License as published by the Free Software Foundation; either
014: version 2.1 of the License, or (at your option) any later version.
015:
016: This library is distributed in the hope that it will be useful,
017: but WITHOUT ANY WARRANTY; without even the implied warranty of
018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019: Lesser General Public License for more details.
020:
021: You should have received a copy of the GNU Lesser General Public
022: License along with this library; if not, write to the Free Software
023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024:
025: Contact:
026:
027: Andreas Poth
028: lat/lon GmbH
029: Aennchenstraße 19
030: 53177 Bonn
031: Germany
032: E-Mail: poth@lat-lon.de
033:
034: Prof. Dr. Klaus Greve
035: Department of Geography
036: University of Bonn
037: Meckenheimer Allee 166
038: 53115 Bonn
039: Germany
040: E-Mail: greve@giub.uni-bonn.de
041:
042: ---------------------------------------------------------------------------*/
043: package org.deegree.io.datastore.idgenerator;
044:
045: import java.util.ArrayList;
046: import java.util.Date;
047: import java.util.HashMap;
048: import java.util.HashSet;
049: import java.util.List;
050: import java.util.Map;
051: import java.util.Set;
052:
053: import org.deegree.datatypes.QualifiedName;
054: import org.deegree.framework.log.ILogger;
055: import org.deegree.framework.log.LoggerFactory;
056: import org.deegree.framework.util.TimeTools;
057: import org.deegree.io.datastore.Datastore;
058: import org.deegree.io.datastore.DatastoreException;
059: import org.deegree.io.datastore.DatastoreTransaction;
060: import org.deegree.io.datastore.FeatureId;
061: import org.deegree.io.datastore.schema.MappedFeatureType;
062: import org.deegree.io.datastore.schema.MappedGMLId;
063: import org.deegree.io.datastore.schema.MappedPropertyType;
064: import org.deegree.io.datastore.schema.MappedSimplePropertyType;
065: import org.deegree.io.datastore.schema.content.MappingField;
066: import org.deegree.io.datastore.schema.content.SimpleContent;
067: import org.deegree.model.crs.UnknownCRSException;
068: import org.deegree.model.feature.Feature;
069: import org.deegree.model.feature.FeatureCollection;
070: import org.deegree.model.feature.FeatureProperty;
071: import org.deegree.model.filterencoding.ComplexFilter;
072: import org.deegree.model.filterencoding.FeatureFilter;
073: import org.deegree.model.filterencoding.Filter;
074: import org.deegree.model.filterencoding.Literal;
075: import org.deegree.model.filterencoding.LogicalOperation;
076: import org.deegree.model.filterencoding.Operation;
077: import org.deegree.model.filterencoding.OperationDefines;
078: import org.deegree.model.filterencoding.PropertyIsCOMPOperation;
079: import org.deegree.model.filterencoding.PropertyName;
080: import org.deegree.model.spatialschema.Geometry;
081: import org.deegree.ogcbase.CommonNamespaces;
082: import org.deegree.ogcbase.PropertyPath;
083: import org.deegree.ogcbase.PropertyPathFactory;
084: import org.deegree.ogcwebservices.wfs.operation.GetFeature;
085: import org.deegree.ogcwebservices.wfs.operation.Query;
086: import org.deegree.ogcwebservices.wfs.operation.transaction.Insert;
087: import org.deegree.ogcwebservices.wfs.operation.transaction.Insert.ID_GEN;
088:
089: /**
090: * Responsible for the assigning of valid {@link FeatureId}s which are a prerequisite to the insertion of features in a
091: * {@link Datastore}. For each {@link Insert} operation, a new <code>FeatureIdAssigner</code> instance is created.
092: * <p>
093: * The behaviour of {@link #assignFID(Feature, DatastoreTransaction)}} depends on the {@link ID_GEN} mode in use:
094: * <table>
095: * <tr>
096: * <td>GenerateNew</td>
097: * <td>Prior to the assigning of new feature ids, "equal" features are looked up in the datastore and their feature ids
098: * are used.</td>
099: * </tr>
100: * <tr>
101: * <td>UseExisting</td>
102: * <td>
103: * <ol>
104: * <li>For every root feature, it is checked that a feature id is present and that no feature with the same id already
105: * exists in the datastore.</li>
106: * <li>"Equal" subfeatures are looked up in the datastore and their feature ids are used instead of the given fids --
107: * if however an "equal" root feature is identified, an exception is thrown.</li>
108: * </ol>
109: * </td>
110: * </tr>
111: * <tr>
112: * <td>ReplaceDuplicate</td>
113: * <td>not supported yet</td>
114: * </tr>
115: * </table>
116: *
117: * @see DatastoreTransaction#performInsert(List)
118: *
119: * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
120: * @author last edited by: $Author: apoth $
121: *
122: * @version $Revision: 9342 $, $Date: 2007-12-27 04:32:57 -0800 (Thu, 27 Dec 2007) $
123: */
124: public class FeatureIdAssigner {
125:
126: /** if an assigned feature id starts with this, it is already stored */
127: public static final String EXISTS_MARKER = "!";
128:
129: private static final ILogger LOG = LoggerFactory
130: .getLogger(FeatureIdAssigner.class);
131:
132: private ID_GEN idGenMode;
133:
134: private Map<String, FeatureId> oldFid2NewFidMap = new HashMap<String, FeatureId>();
135:
136: private Set<Feature> reassignedFeatures = new HashSet<Feature>();
137:
138: private Set<Feature> storedFeatures = new HashSet<Feature>();
139:
140: /**
141: * Creates a new <code>FeatureIdAssigner</code> instance that generates new feature ids as specified.
142: *
143: * @param idGenMode
144: */
145: public FeatureIdAssigner(ID_GEN idGenMode) {
146: this .idGenMode = idGenMode;
147: }
148:
149: /**
150: * Assigns valid {@link FeatureId}s to the given feature instance and it's subfeatures.
151: *
152: * @param feature
153: * @param ta
154: * @throws IdGenerationException
155: */
156: public void assignFID(Feature feature, DatastoreTransaction ta)
157: throws IdGenerationException {
158:
159: switch (this .idGenMode) {
160: case GENERATE_NEW: {
161: identifyStoredFeatures(feature, ta, new HashSet<Feature>());
162: generateAndAssignNewFIDs(feature, null, ta);
163: break;
164: }
165: case REPLACE_DUPLICATE: {
166: LOG
167: .logInfo("Idgen mode 'ReplaceDuplicate' is not implemented!");
168: break;
169: }
170: case USE_EXISTING: {
171: checkForExistingFid(feature, ta);
172: String oldFid = feature.getId();
173: String equalFeature = identifyStoredFeatures(feature, ta,
174: new HashSet<Feature>());
175: if (equalFeature != null) {
176: String msg = "Cannot perform insert: a feature equal to a feature to be inserted (fid: '"
177: + oldFid
178: + "') already exists in the datastore (existing fid: '"
179: + equalFeature + "').";
180: throw new IdGenerationException(msg);
181: }
182: break;
183: }
184: default: {
185: throw new IdGenerationException(
186: "Internal error: Unhandled fid generation mode: "
187: + this .idGenMode);
188: }
189: }
190: }
191:
192: /**
193: * TODO mark stored features a better way
194: */
195: public void markStoredFeatures() {
196: // hack: mark stored features (with "!")
197: for (Feature f : this .storedFeatures) {
198: String fid = f.getId();
199: if (!fid.startsWith(EXISTS_MARKER)) {
200: f.setId(EXISTS_MARKER + fid);
201: }
202: }
203: }
204:
205: private String identifyStoredFeatures(Feature feature,
206: DatastoreTransaction ta, Set<Feature> inProcessing)
207: throws IdGenerationException {
208:
209: if (this .reassignedFeatures.contains(feature)) {
210: return feature.getId();
211: }
212:
213: inProcessing.add(feature);
214:
215: boolean maybeEqual = true;
216: String existingFID = null;
217:
218: LOG
219: .logDebug("Checking for existing feature that equals feature with type: '"
220: + feature.getName()
221: + "' and fid: '"
222: + feature.getId() + "'.");
223:
224: // build the comparison operations that are needed to select "equal" feature instances
225: List<Operation> compOperations = new ArrayList<Operation>();
226:
227: FeatureProperty[] properties = feature.getProperties();
228: MappedFeatureType ft = (MappedFeatureType) feature
229: .getFeatureType();
230:
231: for (int i = 0; i < properties.length; i++) {
232: QualifiedName propertyName = properties[i].getName();
233: MappedPropertyType propertyType = (MappedPropertyType) ft
234: .getProperty(propertyName);
235:
236: Object propertyValue = properties[i].getValue();
237: if (propertyValue instanceof Feature) {
238:
239: if (inProcessing.contains(propertyValue)) {
240: LOG
241: .logDebug("Stopping recursion at property with '"
242: + propertyName
243: + "'. Cycle detected.");
244: continue;
245: }
246:
247: LOG.logDebug("Recursing on feature property: "
248: + properties[i].getName());
249: String subFeatureId = identifyStoredFeatures(
250: (Feature) propertyValue, ta, inProcessing);
251: if (propertyType.isIdentityPart()) {
252: if (subFeatureId == null) {
253: maybeEqual = false;
254: } else {
255: LOG
256: .logDebug("Need to check for feature property '"
257: + propertyName
258: + "' with fid '"
259: + subFeatureId + "'.");
260:
261: // build path that selects subfeature 'gml:id' attribute
262: PropertyPath fidSelectPath = PropertyPathFactory
263: .createPropertyPath(feature.getName());
264: fidSelectPath.append(PropertyPathFactory
265: .createPropertyPathStep(propertyName));
266: fidSelectPath
267: .append(PropertyPathFactory
268: .createPropertyPathStep(((Feature) propertyValue)
269: .getName()));
270: QualifiedName qn = new QualifiedName(
271: CommonNamespaces.GML_PREFIX, "id",
272: CommonNamespaces.GMLNS);
273: fidSelectPath.append(PropertyPathFactory
274: .createAttributePropertyPathStep(qn));
275:
276: // hack that remove's the gml id prefix
277: MappedFeatureType subFeatureType = (MappedFeatureType) ((Feature) propertyValue)
278: .getFeatureType();
279: MappedGMLId gmlId = subFeatureType.getGMLId();
280: String prefix = gmlId.getPrefix();
281: if (subFeatureId.indexOf(prefix) != 0) {
282: throw new IdGenerationException(
283: "Internal error: subfeature id '"
284: + subFeatureId
285: + "' does not begin with the expected prefix.");
286: }
287: String plainIdValue = subFeatureId
288: .substring(prefix.length());
289: PropertyIsCOMPOperation propertyTestOperation = new PropertyIsCOMPOperation(
290: OperationDefines.PROPERTYISEQUALTO,
291: new PropertyName(fidSelectPath),
292: new Literal(plainIdValue));
293:
294: compOperations.add(propertyTestOperation);
295: }
296: } else
297: LOG
298: .logDebug("Skipping property '"
299: + propertyName
300: + "': not a part of the feature type's identity.");
301: } else if (propertyValue instanceof Geometry) {
302:
303: if (propertyType.isIdentityPart()) {
304: throw new IdGenerationException(
305: "Check for equal geometry properties "
306: + "is not implemented yet. Do not set "
307: + "identityPart to true for geometry properties.");
308: }
309:
310: } else {
311: if (propertyType.isIdentityPart()) {
312: LOG.logDebug("Need to check for simple property '"
313: + propertyName + "' with value '"
314: + propertyValue + "'.");
315:
316: String value = propertyValue.toString();
317: if (propertyValue instanceof Date) {
318: value = TimeTools
319: .getISOFormattedTime((Date) propertyValue);
320: }
321:
322: PropertyIsCOMPOperation propertyTestOperation = new PropertyIsCOMPOperation(
323: OperationDefines.PROPERTYISEQUALTO,
324: new PropertyName(propertyName),
325: new Literal(value));
326: compOperations.add(propertyTestOperation);
327: } else {
328: LOG
329: .logDebug("Skipping property '"
330: + propertyName
331: + "': not a part of the feature type's identity.");
332: }
333: }
334: }
335:
336: if (ft.getGMLId().isIdentityPart()) {
337: maybeEqual = false;
338: LOG
339: .logDebug("Skipping check for identical features: feature id is part of "
340: + "the feature identity.");
341: }
342: if (maybeEqual) {
343: // build the filter from the comparison operations
344: Filter filter = null;
345: if (compOperations.size() == 0) {
346: // no constraints, so any feature of this type will do
347: } else if (compOperations.size() == 1) {
348: filter = new ComplexFilter(compOperations.get(0));
349: } else {
350: LogicalOperation andOperation = new LogicalOperation(
351: OperationDefines.AND, compOperations);
352: filter = new ComplexFilter(andOperation);
353: }
354: if (filter != null) {
355: LOG.logDebug("Performing query with filter: "
356: + filter.toXML());
357: } else {
358: LOG.logDebug("Performing unrestricted query.");
359: }
360: Query query = Query.create(new PropertyPath[0], null, null,
361: null, null,
362: new QualifiedName[] { feature.getName() }, null,
363: null, filter, 1, 0, GetFeature.RESULT_TYPE.RESULTS);
364:
365: try {
366: FeatureCollection fc = ft.performQuery(query, ta);
367: if (fc.size() > 0) {
368: existingFID = fc.getFeature(0).getId();
369: LOG
370: .logDebug("Found existing + matching feature with fid: '"
371: + existingFID + "'.");
372: } else {
373: LOG.logDebug("No matching feature found.");
374: }
375: } catch (DatastoreException e) {
376: throw new IdGenerationException(
377: "Could not perform query to check for "
378: + "existing feature instances: "
379: + e.getMessage(), e);
380: } catch (UnknownCRSException e) {
381: LOG.logError(e.getMessage(), e);
382: }
383: }
384:
385: if (existingFID != null) {
386: LOG.logDebug("Feature '" + feature.getName() + "', FID '"
387: + feature.getId() + "' -> existing FID '"
388: + existingFID + "'");
389: feature.setId(existingFID);
390: this .storedFeatures.add(feature);
391: this .reassignedFeatures.add(feature);
392: changeValueForMappedIDProperties(ft, feature);
393: }
394:
395: return existingFID;
396: }
397:
398: /**
399: * TODO: remove parentFID hack
400: *
401: * @param feature
402: * @param parentFID
403: * @throws IdGenerationException
404: */
405: private void generateAndAssignNewFIDs(Feature feature,
406: FeatureId parentFID, DatastoreTransaction ta)
407: throws IdGenerationException {
408:
409: FeatureId newFid = null;
410: MappedFeatureType ft = (MappedFeatureType) feature
411: .getFeatureType();
412:
413: if (this .reassignedFeatures.contains(feature)) {
414: LOG.logDebug("Skipping feature with fid '"
415: + feature.getId() + "'. Already reassigned.");
416: return;
417: }
418:
419: this .reassignedFeatures.add(feature);
420: String oldFidValue = feature.getId();
421: if (oldFidValue == null || "".equals(oldFidValue)) {
422: LOG.logDebug("Feature has no FID. Assigning a new one.");
423: } else {
424: newFid = this .oldFid2NewFidMap.get(oldFidValue);
425: }
426: if (newFid == null) {
427: // TODO remove these hacks
428: if (ft.getGMLId().getIdGenerator() instanceof ParentIDGenerator) {
429: newFid = new FeatureId(ft, parentFID.getValues());
430: } else {
431: newFid = ft.generateFid(ta);
432: }
433: this .oldFid2NewFidMap.put(oldFidValue, newFid);
434: }
435:
436: LOG.logDebug("Feature '" + feature.getName() + "', FID '"
437: + oldFidValue + "' -> new FID '" + newFid + "'");
438: // TODO use FeatureId, not it's String value
439: feature.setId(newFid.getAsString());
440: changeValueForMappedIDProperties(ft, feature);
441:
442: FeatureProperty[] properties = feature.getProperties();
443: for (int i = 0; i < properties.length; i++) {
444: Object propertyValue = properties[i].getValue();
445: if (propertyValue instanceof Feature) {
446: generateAndAssignNewFIDs((Feature) propertyValue,
447: newFid, ta);
448: }
449: }
450: }
451:
452: /**
453: * After reassigning a feature id, this method updates all properties of the feature that are mapped to the same
454: * column as the feature id.
455: *
456: * TODO: find a better way to do this
457: *
458: * @param ft
459: * @param feature
460: */
461: private void changeValueForMappedIDProperties(MappedFeatureType ft,
462: Feature feature) {
463: // TODO remove this hack as well
464: String pkColumn = ft.getGMLId().getIdFields()[0].getField();
465:
466: FeatureProperty[] properties = feature.getProperties();
467: for (int i = 0; i < properties.length; i++) {
468: MappedPropertyType propertyType = (MappedPropertyType) ft
469: .getProperty(properties[i].getName());
470: if (propertyType instanceof MappedSimplePropertyType) {
471: SimpleContent content = ((MappedSimplePropertyType) propertyType)
472: .getContent();
473: if (content.isUpdateable()) {
474: if (content instanceof MappingField) {
475: String column = ((MappingField) content)
476: .getField();
477: if (column.equalsIgnoreCase(pkColumn)) {
478: Object fid = null;
479: try {
480: fid = FeatureId.removeFIDPrefix(feature
481: .getId(), ft.getGMLId());
482: } catch (DatastoreException e) {
483: e.printStackTrace();
484: }
485: properties[i].setValue(fid);
486: }
487: }
488: }
489: }
490: }
491: }
492:
493: /**
494: * Checks that the {@link Datastore} contains no feature with the same id as the given feature.
495: *
496: * @param feature
497: * @param ta
498: * @throws IdGenerationException
499: */
500: private void checkForExistingFid(Feature feature,
501: DatastoreTransaction ta) throws IdGenerationException {
502:
503: MappedFeatureType ft = (MappedFeatureType) feature
504: .getFeatureType();
505: LOG.logDebug("Checking for existing feature of type: '"
506: + ft.getName() + "' and with fid: '" + feature.getId()
507: + "'.");
508:
509: // build a filter that matches the feature id
510: FeatureFilter filter = new FeatureFilter();
511: filter
512: .addFeatureId(new org.deegree.model.filterencoding.FeatureId(
513: feature.getId()));
514: Query query = Query.create(new PropertyPath[0], null, null,
515: null, null, new QualifiedName[] { ft.getName() }, null,
516: null, filter, 1, 0, GetFeature.RESULT_TYPE.HITS);
517: try {
518: FeatureCollection fc = ft.performQuery(query, ta);
519: int numFeatures = Integer.parseInt(fc
520: .getAttribute("numberOfFeatures"));
521: if (numFeatures > 0) {
522: LOG.logInfo("Found existing feature with fid '"
523: + feature.getId() + "'.");
524: String msg = "Cannot perform insert: a feature with fid '"
525: + feature.getId()
526: + "' already exists in the datastore (and idGen='UseExisting').";
527: throw new IdGenerationException(msg);
528: }
529: LOG.logDebug("No feature with fid '" + feature.getId()
530: + "' found.");
531: } catch (IdGenerationException e) {
532: throw e;
533: } catch (DatastoreException e) {
534: throw new IdGenerationException(
535: "Could not perform query to check for existing feature instance: "
536: + e.getMessage(), e);
537: } catch (UnknownCRSException e) {
538: LOG.logDebug(e.getMessage(), e);
539: }
540: }
541: }
|