001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/io/datastore/sql/transaction/UpdateHandler.java $
002 /*----------------------------------------------------------------------------
003 This file is part of deegree, http://deegree.org/
004 Copyright (C) 2001-2009 by:
005 Department of Geography, University of Bonn
006 and
007 lat/lon GmbH
008
009 This library is free software; you can redistribute it and/or modify it under
010 the terms of the GNU Lesser General Public License as published by the Free
011 Software Foundation; either version 2.1 of the License, or (at your option)
012 any later version.
013 This library is distributed in the hope that it will be useful, but WITHOUT
014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016 details.
017 You should have received a copy of the GNU Lesser General Public License
018 along with this library; if not, write to the Free Software Foundation, Inc.,
019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020
021 Contact information:
022
023 lat/lon GmbH
024 Aennchenstr. 19, 53177 Bonn
025 Germany
026 http://lat-lon.de/
027
028 Department of Geography, University of Bonn
029 Prof. Dr. Klaus Greve
030 Postfach 1147, 53001 Bonn
031 Germany
032 http://www.geographie.uni-bonn.de/deegree/
033
034 e-mail: info@deegree.org
035 ----------------------------------------------------------------------------*/
036 package org.deegree.io.datastore.sql.transaction;
037
038 import java.sql.Connection;
039 import java.sql.PreparedStatement;
040 import java.sql.ResultSet;
041 import java.sql.SQLException;
042 import java.util.ArrayList;
043 import java.util.Enumeration;
044 import java.util.Hashtable;
045 import java.util.LinkedHashMap;
046 import java.util.List;
047 import java.util.Map;
048
049 import org.deegree.datatypes.QualifiedName;
050 import org.deegree.datatypes.Types;
051 import org.deegree.framework.log.ILogger;
052 import org.deegree.framework.log.LoggerFactory;
053 import org.deegree.i18n.Messages;
054 import org.deegree.io.datastore.DatastoreException;
055 import org.deegree.io.datastore.FeatureId;
056 import org.deegree.io.datastore.TransactionException;
057 import org.deegree.io.datastore.idgenerator.FeatureIdAssigner;
058 import org.deegree.io.datastore.schema.MappedFeaturePropertyType;
059 import org.deegree.io.datastore.schema.MappedFeatureType;
060 import org.deegree.io.datastore.schema.MappedGeometryPropertyType;
061 import org.deegree.io.datastore.schema.MappedPropertyType;
062 import org.deegree.io.datastore.schema.MappedSimplePropertyType;
063 import org.deegree.io.datastore.schema.TableRelation;
064 import org.deegree.io.datastore.schema.TableRelation.FK_INFO;
065 import org.deegree.io.datastore.schema.content.MappingField;
066 import org.deegree.io.datastore.schema.content.MappingGeometryField;
067 import org.deegree.io.datastore.schema.content.SimpleContent;
068 import org.deegree.io.datastore.sql.AbstractRequestHandler;
069 import org.deegree.io.datastore.sql.StatementBuffer;
070 import org.deegree.io.datastore.sql.TableAliasGenerator;
071 import org.deegree.io.datastore.sql.transaction.delete.DeleteHandler;
072 import org.deegree.io.datastore.sql.transaction.insert.InsertHandler;
073 import org.deegree.model.feature.Feature;
074 import org.deegree.model.feature.FeatureProperty;
075 import org.deegree.model.feature.schema.FeaturePropertyType;
076 import org.deegree.model.feature.schema.PropertyType;
077 import org.deegree.model.filterencoding.Filter;
078 import org.deegree.model.spatialschema.Geometry;
079 import org.deegree.ogcbase.ElementStep;
080 import org.deegree.ogcbase.PropertyPath;
081 import org.deegree.ogcbase.PropertyPathStep;
082 import org.deegree.ogcwebservices.wfs.operation.transaction.Insert;
083 import org.deegree.ogcwebservices.wfs.operation.transaction.Transaction;
084 import org.deegree.ogcwebservices.wfs.operation.transaction.Update;
085
086 /**
087 * Handler for {@link Update} operations (usually contained in {@link Transaction} requests).
088 *
089 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
090 * @author last edited by: $Author: rbezema $
091 *
092 * @version $Revision: 19436 $, $Date: 2009-08-31 16:37:44 +0200 (Mo, 31. Aug 2009) $
093 */
094 public class UpdateHandler extends AbstractRequestHandler {
095
096 private static final ILogger LOG = LoggerFactory.getLogger( UpdateHandler.class );
097
098 private SQLTransaction dsTa;
099
100 private String lockId;
101
102 /**
103 * Creates a new <code>UpdateHandler</code> from the given parameters.
104 *
105 * @param dsTa
106 * @param aliasGenerator
107 * @param conn
108 * @param lockId
109 * optional id of associated lock (may be null)
110 */
111 public UpdateHandler( SQLTransaction dsTa, TableAliasGenerator aliasGenerator, Connection conn, String lockId ) {
112 super( dsTa.getDatastore(), aliasGenerator, conn );
113 this.dsTa = dsTa;
114 this.lockId = lockId;
115 }
116
117 /**
118 * Performs an update operation against the associated datastore.
119 *
120 * @param ft
121 * @param replacementProps
122 * @param filter
123 * @return number of updated (root) feature instances
124 * @throws DatastoreException
125 */
126 public int performUpdate( MappedFeatureType ft, Map<PropertyPath, FeatureProperty> replacementProps, Filter filter )
127 throws DatastoreException {
128
129 List<FeatureId> fids = determineAffectedAndModifiableFIDs( ft, filter, this.lockId );
130
131 LOG.logDebug( "Updating: " + ft );
132 for ( FeatureId fid : fids ) {
133 Hashtable<String, Hashtable<FeatureId, StatementBuffer>> tableToFeatureUpdate = new Hashtable<String, Hashtable<FeatureId, StatementBuffer>>();
134
135 for ( PropertyPath property : replacementProps.keySet() ) {
136 LOG.logDebug( "Updating feature: " + fid );
137 FeatureProperty propertyValue = replacementProps.get( property );
138 performUpdate( fid, ft, property, propertyValue, tableToFeatureUpdate );
139 }
140
141 for ( Hashtable<FeatureId, StatementBuffer> featureStatementBuffers : tableToFeatureUpdate.values() ) {
142 for ( Enumeration<FeatureId> it = featureStatementBuffers.keys(); it.hasMoreElements(); ) {
143 FeatureId statementFid = it.nextElement();
144 StatementBuffer query = featureStatementBuffers.get( statementFid );
145
146 query.append( " WHERE " );
147 appendFIDWhereCondition( query, statementFid );
148
149 PreparedStatement stmt = null;
150 LOG.logDebug( "Performing aggregate update of in-table properties: " + query.getQueryString() );
151 try {
152 stmt = this.datastore.prepareStatement( conn, query );
153 stmt.execute();
154 } catch ( SQLException e ) {
155 throw new DatastoreException( "Error in performUpdate(): " + e.getMessage() );
156 } finally {
157 if ( stmt != null ) {
158 try {
159 stmt.close();
160 } catch ( SQLException e ) {
161 LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
162 }
163 }
164 }
165 }
166 }
167 }
168 return fids.size();
169 }
170
171 /**
172 * Performs an update operation (replace-style) against the associated datastore.
173 * <p>
174 * All features matched by the given filter are altered, so their properties are identical to those of the specified
175 * replacement feature.
176 * </p>
177 * <p>
178 * NOTE: Currently, the contained feature must not contain any feature-valued properties or multi-properties.
179 * </p>
180 *
181 * @param mappedFeatureType
182 * @param replacementFeature
183 * @param filter
184 * @return number of updated (root) feature instances
185 * @throws DatastoreException
186 */
187 public int performUpdate( MappedFeatureType mappedFeatureType, Feature replacementFeature, Filter filter )
188 throws DatastoreException {
189 LOG.logDebug( "Updating (replace): " + mappedFeatureType );
190 if ( filter != null ) {
191 LOG.logDebug( " filter: " + filter.to110XML() );
192 }
193
194 PropertyType[] replPropertyTypes = replacementFeature.getFeatureType().getProperties();
195 int result = 0;
196 // rb: updating complex features is a needed feature, gml:id reservation as well, therefore let's see if we are
197 // updating a complex feature, we do the old school, delete->insert (loosing gml:id) if updating a simple
198 // feature, do a 'new school' update.
199 if ( replPropertyTypes != null ) {
200 boolean oldSchool = false;
201 for ( int i = 0; i < replPropertyTypes.length && !oldSchool; ++i ) {
202 PropertyType rpt = replPropertyTypes[i];
203 oldSchool = rpt != null
204 && ( rpt instanceof FeaturePropertyType || rpt instanceof MappedFeaturePropertyType );
205
206 }
207 if ( oldSchool ) {
208 LOG.logWarning( "The given featuretype, is a complex feature type, updating this feature will result in the loss of gml:id's in the update feature and it's references." );
209 result = performUpdateWithFeatures( mappedFeatureType, replacementFeature, filter );
210 } else {
211 LOG.logDebug( "Updating feature with correct gml:id handling (replace): " + mappedFeatureType );
212 result = performUpdateCorrectForProperties( mappedFeatureType, replacementFeature, filter );
213 }
214 }
215 return result;
216 }
217
218 /**
219 * Performs an update operation (replace-style) against the associated datastore.
220 * <p>
221 * All features matched by the given filter are altered, so their properties are identical to those of the specified
222 * replacement feature.
223 * </p>
224 * <p>
225 * NOTE: Currently, the contained feature must not contain any feature-valued properties or multi-properties.
226 * </p>
227 *
228 * @param mappedFeatureType
229 * @param replacementFeature
230 * @param filter
231 * @return number of updated (root) feature instances
232 * @throws DatastoreException
233 */
234 private int performUpdateCorrectForProperties( MappedFeatureType mappedFeatureType, Feature replacementFeature,
235 Filter filter )
236 throws DatastoreException {
237
238 Map<PropertyPath, FeatureProperty> replaceProperties = new LinkedHashMap<PropertyPath, FeatureProperty>();
239 FeatureProperty[] featureProps = replacementFeature.getProperties();
240 for ( FeatureProperty featureProperty : featureProps ) {
241 List<PropertyPathStep> steps = new ArrayList<PropertyPathStep>( 1 );
242 steps.add( new ElementStep( featureProperty.getName() ) );
243 PropertyPath path = new PropertyPath( steps );
244 replaceProperties.put( path, featureProperty );
245 }
246 return performUpdate( mappedFeatureType, replaceProperties, filter );
247 }
248
249 /**
250 * Performs an update operation against the associated datastore.
251 * <p>
252 * The filter must match exactly one feature instance (or none) which is then replaced by the specified replacement
253 * feature.
254 *
255 * @param mappedFeatureType
256 * @param replacementFeature
257 * @param filter
258 * @return number of updated (root) feature instances (0 or 1)
259 * @throws DatastoreException
260 */
261 private int performUpdateWithFeatures( MappedFeatureType mappedFeatureType, Feature replacementFeature,
262 Filter filter )
263 throws DatastoreException {
264
265 List<FeatureId> fids = determineAffectedAndModifiableFIDs( mappedFeatureType, filter, this.lockId );
266
267 if ( fids.size() > 1 ) {
268 String msg = Messages.getMessage( "DATASTORE_MORE_THAN_ONE_FEATURE" );
269 throw new DatastoreException( msg );
270 }
271 DeleteHandler deleteHandler = new DeleteHandler( this.dsTa, this.aliasGenerator, this.conn, this.lockId );
272 deleteHandler.performDelete( mappedFeatureType, filter );
273
274 // identify stored subfeatures / assign feature ids
275 FeatureIdAssigner fidAssigner = new FeatureIdAssigner( Insert.ID_GEN.GENERATE_NEW );
276 fidAssigner.assignFID( replacementFeature, this.dsTa );
277 // TODO remove this hack
278 fidAssigner.markStoredFeatures();
279
280 InsertHandler insertHandler = new InsertHandler( this.dsTa, this.aliasGenerator, this.conn );
281 List<Feature> features = new ArrayList<Feature>();
282 features.add( replacementFeature );
283 insertHandler.performInsert( features );
284
285 return fids.size();
286 }
287
288 /**
289 * Performs the update (replacing of a property) of the given feature instance.
290 * <p>
291 * If the selected property is a direct property of the feature, the root feature is updated, otherwise the targeted
292 * subfeatures have to be determined first.
293 *
294 * @param fid
295 * @param ft
296 * @param propertyName
297 * @param replacementProperty
298 * @param statementBuffers
299 * @throws DatastoreException
300 */
301 private void performUpdate( FeatureId fid, MappedFeatureType ft, PropertyPath propertyName,
302 FeatureProperty replacementProperty,
303 Hashtable<String, Hashtable<FeatureId, StatementBuffer>> statementBuffers )
304 throws DatastoreException {
305
306 Object replacementValue = replacementProperty.getValue();
307 LOG.logDebug( "Updating fid: " + fid + ", propertyName: " + propertyName + " -> " + replacementValue );
308
309 int steps = propertyName.getSteps();
310 QualifiedName propName = propertyName.getStep( steps - 1 ).getPropertyName();
311 if ( steps > 2 ) {
312 QualifiedName subFtName = propertyName.getStep( steps - 2 ).getPropertyName();
313 MappedFeatureType subFt = this.datastore.getFeatureType( subFtName );
314 MappedPropertyType pt = (MappedPropertyType) subFt.getProperty( propName );
315 List<TableRelation> tablePath = getTablePath( ft, propertyName );
316 List<FeatureId> subFids = determineAffectedFIDs( fid, subFt, tablePath );
317 for ( FeatureId subFid : subFids ) {
318 updateProperty( subFid, subFt, pt, replacementValue, statementBuffers );
319 }
320 } else {
321 MappedPropertyType pt = (MappedPropertyType) ft.getProperty( propName );
322 updateProperty( fid, ft, pt, replacementValue, statementBuffers );
323 }
324 }
325
326 /**
327 * Determines the subfeature instances that are targeted by the given PropertyName.
328 *
329 * @param fid
330 * @param subFt
331 * @param path
332 * @return the matched feature ids
333 * @throws DatastoreException
334 */
335 private List<FeatureId> determineAffectedFIDs( FeatureId fid, MappedFeatureType subFt, List<TableRelation> path )
336 throws DatastoreException {
337
338 List<FeatureId> subFids = new ArrayList<FeatureId>();
339
340 this.aliasGenerator.reset();
341 String[] tableAliases = this.aliasGenerator.generateUniqueAliases( path.size() + 1 );
342 String toTableAlias = tableAliases[tableAliases.length - 1];
343 StatementBuffer query = new StatementBuffer();
344 query.append( "SELECT " );
345 appendFeatureIdColumns( subFt, toTableAlias, query );
346 query.append( " FROM " );
347 query.append( path.get( 0 ).getFromTable() );
348 query.append( " " );
349 query.append( tableAliases[0] );
350 // append joins
351 for ( int i = 0; i < path.size(); i++ ) {
352 query.append( " JOIN " );
353 query.append( path.get( i ).getToTable() );
354 query.append( " " );
355 query.append( tableAliases[i + 1] );
356 query.append( " ON " );
357 MappingField[] fromFields = path.get( i ).getFromFields();
358 MappingField[] toFields = path.get( i ).getToFields();
359 for ( int j = 0; j < fromFields.length; j++ ) {
360 query.append( tableAliases[i] );
361 query.append( '.' );
362 query.append( fromFields[j].getField() );
363 query.append( '=' );
364 query.append( tableAliases[i + 1] );
365 query.append( '.' );
366 query.append( toFields[j].getField() );
367 }
368 }
369 query.append( " WHERE " );
370 MappingField[] fidFields = fid.getFidDefinition().getIdFields();
371 for ( int i = 0; i < fidFields.length; i++ ) {
372 query.append( tableAliases[0] );
373 query.append( '.' );
374 query.append( fidFields[i].getField() );
375 query.append( "=?" );
376 query.addArgument( fid.getValue( i ), fidFields[i].getType() );
377 if ( i != fidFields.length - 1 ) {
378 query.append( " AND " );
379 }
380 }
381
382 PreparedStatement stmt = null;
383 ResultSet rs = null;
384 try {
385 stmt = this.datastore.prepareStatement( conn, query );
386 rs = stmt.executeQuery();
387 subFids = extractFeatureIds( rs, subFt );
388 } catch ( SQLException e ) {
389 throw new DatastoreException( "Error in determineAffectedFIDs(): " + e.getMessage() );
390 } finally {
391 try {
392 if ( rs != null ) {
393 try {
394 rs.close();
395 } catch ( SQLException e ) {
396 LOG.logError( "Error closing result set: '" + e.getMessage() + "'.", e );
397 }
398 }
399 } finally {
400 if ( stmt != null ) {
401 try {
402 stmt.close();
403 } catch ( SQLException e ) {
404 LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
405 }
406 }
407 }
408 }
409 return subFids;
410 }
411
412 /**
413 * Returns the relations (the "path") that lead from the feature type's table to the subfeature table which is
414 * targeted by the specified property name.
415 *
416 * @param ft
417 * source feature type
418 * @param path
419 * property name
420 * @return relations that lead from the feature type's table to the subfeature table
421 */
422 private List<TableRelation> getTablePath( MappedFeatureType ft, PropertyPath path ) {
423 List<TableRelation> relations = new ArrayList<TableRelation>();
424 for ( int i = 1; i < path.getSteps() - 2; i += 2 ) {
425 QualifiedName propName = path.getStep( i ).getPropertyName();
426 MappedFeaturePropertyType pt = (MappedFeaturePropertyType) ft.getProperty( propName );
427 TableRelation[] tableRelations = pt.getTableRelations();
428 for ( int j = 0; j < tableRelations.length; j++ ) {
429 relations.add( tableRelations[j] );
430 }
431 ft = pt.getFeatureTypeReference().getFeatureType();
432 }
433 return relations;
434 }
435
436 /**
437 * Replaces the specified feature's property with the given value.
438 *
439 * @param fid
440 * @param ft
441 * @param pt
442 * @param replacementValue
443 * @throws DatastoreException
444 */
445 private void updateProperty( FeatureId fid, MappedFeatureType ft, MappedPropertyType pt, Object replacementValue,
446 Hashtable<String, Hashtable<FeatureId, StatementBuffer>> statementBuffers )
447 throws DatastoreException {
448
449 LOG.logDebug( "Updating property '" + pt.getName() + "' of feature '" + fid + "'." );
450 if ( !ft.isUpdatable() ) {
451 String msg = Messages.getMessage( "DATASTORE_FT_NOT_UPDATABLE", ft.getName() );
452 throw new DatastoreException( msg );
453 }
454 TableRelation[] tablePath = pt.getTableRelations();
455 if ( pt instanceof MappedSimplePropertyType ) {
456 SimpleContent content = ( (MappedSimplePropertyType) pt ).getContent();
457 if ( content.isUpdateable() ) {
458 if ( content instanceof MappingField ) {
459 updateSimpleProperty( fid, tablePath, (MappingField) content, replacementValue, statementBuffers );
460 }
461 } else {
462 LOG.logInfo( "Ignoring property '" + pt.getName() + "' in update - content is virtual." );
463 }
464 } else if ( pt instanceof MappedGeometryPropertyType ) {
465 MappedGeometryPropertyType geomPt = (MappedGeometryPropertyType) pt;
466 MappingGeometryField dbField = geomPt.getMappingField();
467 Geometry deegreeGeometry = (Geometry) replacementValue;
468 Object dbGeometry;
469
470 int createSrsCode = dbField.getSRS();
471 int targetSrsCode = -1;
472
473 if ( deegreeGeometry.getCoordinateSystem() == null ) {
474 LOG.logDebug( "No SRS information for geometry available. Assuming '" + geomPt.getSRS() + "'." );
475 } else if ( !geomPt.getSRS().toString().equals( deegreeGeometry.getCoordinateSystem().getIdentifier() ) ) {
476 String msg = "Insert-Transformation: geometry srs: "
477 + deegreeGeometry.getCoordinateSystem().getIdentifier() + " -> property srs: "
478 + geomPt.getSRS();
479 LOG.logDebug( msg );
480 if ( createSrsCode == -1 ) {
481 msg = Messages.getMessage( "DATASTORE_SRS_NOT_SPECIFIED", pt.getName(),
482 deegreeGeometry.getCoordinateSystem(), geomPt.getSRS() );
483 throw new TransactionException( msg );
484 }
485 try {
486 createSrsCode = datastore.getNativeSRSCode( deegreeGeometry.getCoordinateSystem().getIdentifier() );
487 } catch ( DatastoreException e ) {
488 throw new TransactionException( e.getMessage(), e );
489 }
490 targetSrsCode = dbField.getSRS();
491 }
492
493 try {
494 dbGeometry = this.datastore.convertDeegreeToDBGeometry( deegreeGeometry, createSrsCode, this.conn );
495 } catch ( DatastoreException e ) {
496 throw new TransactionException( e.getMessage(), e );
497 }
498
499 // TODO remove this Oracle hack
500 if ( this.datastore.getClass().getName().contains( "OracleDatastore" ) ) {
501 dbField = new MappingGeometryField( dbField.getTable(), dbField.getField(), Types.STRUCT,
502 dbField.getSRS() );
503 }
504
505 updateGeometryProperty( fid, tablePath, dbField, dbGeometry, targetSrsCode, statementBuffers );
506 } else if ( pt instanceof FeaturePropertyType ) {
507 updateProperty( fid, ft, (MappedFeaturePropertyType) pt, (Feature) replacementValue );
508 } else {
509 throw new DatastoreException( "Internal error: Properties with type '" + pt.getClass()
510 + "' are not handled in UpdateHandler." );
511 }
512 }
513
514 /**
515 * Updates a simple property of the specified feature.
516 * <p>
517 * Three cases are distinguished (which all have to be handled differently):
518 * <ol>
519 * <li>property value stored in feature table</li>
520 * <li>property value stored in property table, fk in property table</li>
521 * <li>property value stored in property table, fk in feature table</li>
522 * </ol>
523 *
524 * @param fid
525 * @param tablePath
526 * @param dbField
527 * @param replacementValue
528 * @throws DatastoreException
529 */
530 private void updateSimpleProperty( FeatureId fid, TableRelation[] tablePath, MappingField dbField,
531 Object replacementValue,
532 Hashtable<String, Hashtable<FeatureId, StatementBuffer>> statementBuffers )
533 throws DatastoreException {
534
535 if ( tablePath.length == 0 ) {
536 updateSimpleProperty( fid, dbField, replacementValue, statementBuffers );
537 } else if ( tablePath.length == 1 ) {
538 TableRelation relation = tablePath[0];
539 if ( tablePath[0].getFKInfo() == FK_INFO.fkIsToField ) {
540 Object[] keyValues = determineKeyValues( fid, relation );
541 if ( keyValues != null ) {
542 deletePropertyRows( relation, keyValues );
543 }
544 if ( replacementValue != null ) {
545 insertPropertyRow( relation, keyValues, dbField, replacementValue );
546 }
547 } else {
548 Object[] oldKeyValues = determineKeyValues( fid, relation );
549 Object[] newKeyValues = findOrInsertPropertyRow( relation, dbField, replacementValue );
550 updateFeatureRow( fid, relation, newKeyValues );
551 if ( oldKeyValues != null ) {
552 deleteOrphanedPropertyRows( relation, oldKeyValues );
553 }
554 }
555 } else {
556 throw new DatastoreException( "Updating of properties that are stored in "
557 + "related tables using join tables is not " + "supported." );
558 }
559 }
560
561 /**
562 * Updates a geometry property of the specified feature.
563 * <p>
564 * Three cases are distinguished (which all have to be handled differently):
565 * <ol>
566 * <li>property value stored in feature table</li>
567 * <li>property value stored in property table, fk in property table</li>
568 * <li>property value stored in property table, fk in feature table</li>
569 * </ol>
570 *
571 * @param fid
572 * @param tablePath
573 * @param dbField
574 * @param replacementValue
575 * @param targetSrsCode
576 * @throws DatastoreException
577 */
578 private void updateGeometryProperty( FeatureId fid, TableRelation[] tablePath, MappingGeometryField dbField,
579 Object replacementValue, int targetSrsCode,
580 Hashtable<String, Hashtable<FeatureId, StatementBuffer>> statementBuffers )
581 throws DatastoreException {
582
583 if ( tablePath.length == 0 ) {
584 updateGeometryProperty( fid, dbField, replacementValue, targetSrsCode, statementBuffers );
585 } else if ( tablePath.length == 1 ) {
586 throw new DatastoreException( "Updating of geometry properties that are stored in "
587 + "related tables is not supported." );
588 } else {
589 throw new DatastoreException( "Updating of properties that are stored in "
590 + "related tables using join tables is not " + "supported." );
591 }
592 }
593
594 private void updateFeatureRow( FeatureId fid, TableRelation relation, Object[] newKeyValues )
595 throws DatastoreException {
596
597 StatementBuffer query = new StatementBuffer();
598 query.append( "UPDATE " );
599 query.append( relation.getFromTable() );
600 query.append( " SET " );
601 MappingField[] fromFields = relation.getFromFields();
602 for ( int i = 0; i < newKeyValues.length; i++ ) {
603 query.append( fromFields[i].getField() );
604 query.append( "=?" );
605 query.addArgument( newKeyValues[i], fromFields[i].getType() );
606 }
607 query.append( " WHERE " );
608 appendFIDWhereCondition( query, fid );
609
610 LOG.logDebug( "Performing update: " + query.getQueryString() );
611
612 PreparedStatement stmt = null;
613 try {
614 stmt = this.datastore.prepareStatement( conn, query );
615 stmt.execute();
616 } catch ( SQLException e ) {
617 throw new DatastoreException( "Error in performUpdate(): " + e.getMessage() );
618 } finally {
619 if ( stmt != null ) {
620 try {
621 stmt.close();
622 } catch ( SQLException e ) {
623 LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
624 }
625 }
626 }
627 }
628
629 /**
630 * Updates a simple property of the specified feature.
631 * <p>
632 * This method handles the case where the property is stored in the feature table itself, so a single UPDATE
633 * statement is sufficient.
634 *
635 * If there is already another UPDATE statement created for this feature and table than a column=value fragment is
636 * added to this statement.
637 *
638 * @param fid
639 * @param dbField
640 * @param replacementValue
641 * @throws DatastoreException
642 */
643 private void updateSimpleProperty( FeatureId fid, MappingField dbField, Object replacementValue,
644 Hashtable<String, Hashtable<FeatureId, StatementBuffer>> statementBuffers ) {
645
646 Hashtable<FeatureId, StatementBuffer> featureStatementBuffers = statementBuffers.get( dbField.getTable() );
647 StatementBuffer query = null;
648 if ( featureStatementBuffers == null ) {
649 featureStatementBuffers = new Hashtable<FeatureId, StatementBuffer>();
650 statementBuffers.put( dbField.getTable(), featureStatementBuffers );
651 } else {
652 query = featureStatementBuffers.get( fid );
653 }
654
655 if ( query == null ) {
656 query = new StatementBuffer();
657 query.append( "UPDATE " );
658 query.append( dbField.getTable() );
659 query.append( " SET " );
660
661 featureStatementBuffers.put( fid, query );
662 } else {
663 query.append( ", " );
664 }
665
666 query.append( dbField.getField() );
667 query.append( "=?" );
668 query.addArgument( replacementValue, dbField.getType() );
669 }
670
671 /**
672 * Updates a geometry property of the specified feature.
673 * <p>
674 * This method handles the case where the property is stored in the feature table itself, so a single UPDATE
675 * statement is sufficient.
676 *
677 * If there is already another UPDATE statement created for this feature and table than a column=value fragment is
678 * added to this statement.
679 *
680 * @param fid
681 * @param dbField
682 * @param replacementValue
683 * @param targetSrsCode
684 * @throws DatastoreException
685 */
686 private void updateGeometryProperty( FeatureId fid, MappingGeometryField dbField, Object replacementValue,
687 int targetSrsCode,
688 Hashtable<String, Hashtable<FeatureId, StatementBuffer>> statementBuffers )
689 throws DatastoreException {
690
691 Hashtable<FeatureId, StatementBuffer> featureStatementBuffers = statementBuffers.get( dbField.getTable() );
692 StatementBuffer query = null;
693 if ( featureStatementBuffers == null ) {
694 featureStatementBuffers = new Hashtable<FeatureId, StatementBuffer>();
695 statementBuffers.put( dbField.getTable(), featureStatementBuffers );
696 } else {
697 query = featureStatementBuffers.get( fid );
698 }
699
700 if ( query == null ) {
701 query = new StatementBuffer();
702 query.append( "UPDATE " );
703 query.append( dbField.getTable() );
704 query.append( " SET " );
705
706 featureStatementBuffers.put( fid, query );
707 } else {
708 query.append( ", " );
709 }
710
711 query.append( dbField.getField() );
712 query.append( "=" );
713 String placeHolder = "?";
714 if ( targetSrsCode != -1 ) {
715 placeHolder = this.datastore.buildSRSTransformCall( "?", targetSrsCode );
716 }
717 query.append( placeHolder );
718 query.addArgument( replacementValue, dbField.getType() );
719 }
720
721 /**
722 * Determines the values for the key columns that are referenced by the given table relation (as from fields).
723 *
724 * @param fid
725 * @param relation
726 * @return the values for the key columns
727 * @throws DatastoreException
728 */
729 private Object[] determineKeyValues( FeatureId fid, TableRelation relation )
730 throws DatastoreException {
731
732 StatementBuffer query = new StatementBuffer();
733 query.append( "SELECT " );
734 MappingField[] fromFields = relation.getFromFields();
735 for ( int i = 0; i < fromFields.length; i++ ) {
736 query.append( fromFields[i].getField() );
737 if ( i != fromFields.length - 1 ) {
738 query.append( ',' );
739 }
740 }
741 query.append( " FROM " );
742 query.append( relation.getFromTable() );
743 query.append( " WHERE " );
744 appendFIDWhereCondition( query, fid );
745
746 Object[] keyValues = new Object[fromFields.length];
747 LOG.logDebug( "determineKeyValues: " + query.getQueryString() );
748 PreparedStatement stmt = null;
749 try {
750 stmt = this.datastore.prepareStatement( conn, query );
751 ResultSet rs = stmt.executeQuery();
752 if ( rs.next() ) {
753 for ( int i = 0; i < keyValues.length; i++ ) {
754 Object value = rs.getObject( i + 1 );
755 if ( value != null ) {
756 keyValues[i] = value;
757 } else {
758 keyValues = null;
759 break;
760 }
761 }
762 } else {
763 LOG.logError( "Internal error. Result is empty (no rows)." );
764 throw new SQLException();
765 }
766 } catch ( SQLException e ) {
767 throw new DatastoreException( "Error in performUpdate(): " + e.getMessage() );
768 } finally {
769 if ( stmt != null ) {
770 try {
771 stmt.close();
772 } catch ( SQLException e ) {
773 LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
774 }
775 }
776 }
777 return keyValues;
778 }
779
780 private void deletePropertyRows( TableRelation relation, Object[] keyValues )
781 throws DatastoreException {
782
783 StatementBuffer query = new StatementBuffer();
784 query.append( "DELETE FROM " );
785 query.append( relation.getToTable() );
786 query.append( " WHERE " );
787 MappingField[] toFields = relation.getToFields();
788 for ( int i = 0; i < toFields.length; i++ ) {
789 query.append( toFields[i].getField() );
790 query.append( "=?" );
791 query.addArgument( keyValues[i], toFields[i].getType() );
792 if ( i != toFields.length - 1 ) {
793 query.append( " AND " );
794 }
795 }
796
797 PreparedStatement stmt = null;
798 LOG.logDebug( "deletePropertyRows: " + query.getQueryString() );
799 try {
800 stmt = this.datastore.prepareStatement( conn, query );
801 stmt.execute();
802 } catch ( SQLException e ) {
803 throw new DatastoreException( "Error in performUpdate(): " + e.getMessage() );
804 } finally {
805 if ( stmt != null ) {
806 try {
807 stmt.close();
808 } catch ( SQLException e ) {
809 LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
810 }
811 }
812 }
813 }
814
815 private void insertPropertyRow( TableRelation relation, Object[] keyValues, MappingField dbField,
816 Object replacementValue )
817 throws DatastoreException {
818
819 if ( keyValues == null ) {
820 if ( relation.getFromFields().length > 1 ) {
821 throw new DatastoreException( "Key generation for compound keys is not supported." );
822 }
823 // generate new primary key
824 keyValues = new Object[1];
825 keyValues[0] = relation.getIdGenerator().getNewId( dsTa );
826 }
827
828 StatementBuffer query = new StatementBuffer();
829 query.append( "INSERT INTO " );
830 query.append( relation.getToTable() );
831 query.append( " (" );
832 MappingField[] toFields = relation.getToFields();
833 for ( int i = 0; i < toFields.length; i++ ) {
834 query.append( toFields[i].getField() );
835 query.append( ',' );
836 }
837 query.append( dbField.getField() );
838 query.append( ") VALUES (" );
839 for ( int i = 0; i < toFields.length; i++ ) {
840 query.append( '?' );
841 query.addArgument( keyValues[i], toFields[i].getType() );
842 query.append( ',' );
843 }
844 query.append( "?)" );
845 query.addArgument( replacementValue, dbField.getType() );
846
847 PreparedStatement stmt = null;
848 LOG.logDebug( "insertPropertyRow: " + query.getQueryString() );
849 try {
850 stmt = this.datastore.prepareStatement( conn, query );
851 stmt.execute();
852 } catch ( SQLException e ) {
853 throw new DatastoreException( "Error in performUpdate(): " + e.getMessage() );
854 } finally {
855 if ( stmt != null ) {
856 try {
857 stmt.close();
858 } catch ( SQLException e ) {
859 LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
860 }
861 }
862 }
863 }
864
865 /**
866 * Returns the foreign key value(s) for the row that stores the given property.
867 * <p>
868 * If the row already exists, the existing key is returned, otherwise a new row for the property is inserted first.
869 *
870 * @param relation
871 * @param dbField
872 * @param replacementValue
873 * @return foreign key value(s) for the row that stores the given property
874 * @throws DatastoreException
875 */
876 private Object[] findOrInsertPropertyRow( TableRelation relation, MappingField dbField, Object replacementValue )
877 throws DatastoreException {
878
879 Object[] keyValues = null;
880
881 if ( dbField.getType() != Types.GEOMETRY ) {
882 StatementBuffer query = new StatementBuffer();
883 query.append( "SELECT " );
884 MappingField[] toFields = relation.getToFields();
885 for ( int i = 0; i < toFields.length; i++ ) {
886 query.append( toFields[i].getField() );
887 if ( i != toFields.length - 1 ) {
888 query.append( ',' );
889 }
890 }
891 query.append( " FROM " );
892 query.append( relation.getToTable() );
893 query.append( " WHERE " );
894 query.append( dbField.getField() );
895 query.append( "=?" );
896 query.addArgument( replacementValue, dbField.getType() );
897
898 PreparedStatement stmt = null;
899 LOG.logDebug( "findOrInsertPropertyRow: " + query.getQueryString() );
900 try {
901 stmt = this.datastore.prepareStatement( conn, query );
902 ResultSet rs = stmt.executeQuery();
903 if ( rs.next() ) {
904 keyValues = new Object[toFields.length];
905 for ( int i = 0; i < toFields.length; i++ ) {
906 keyValues[i] = rs.getObject( i + 1 );
907 }
908 }
909 } catch ( SQLException e ) {
910 throw new DatastoreException( "Error in findOrInsertPropertyRow(): " + e.getMessage() );
911 } finally {
912 if ( stmt != null ) {
913 try {
914 stmt.close();
915 } catch ( SQLException e ) {
916 LOG.logError( "Error closing statement: '" + e.getMessage() + "'.", e );
917 }
918 }
919 }
920 if ( keyValues != null ) {
921 return keyValues;
922 }
923 }
924
925 if ( relation.getToFields().length > 1 ) {
926 throw new DatastoreException( "Key generation for compound keys is not supported." );
927 }
928
929 // property does not yet exist (or it's a geometry)
930 keyValues = new Object[1];
931 // generate new PK
932 keyValues[0] = relation.getNewPK( this.dsTa );
933 insertPropertyRow( relation, keyValues, dbField, replacementValue );
934
935 return keyValues;
936 }
937
938 private void deleteOrphanedPropertyRows( TableRelation relation, Object[] keyValues )
939 throws DatastoreException {
940 DeleteHandler deleteHandler = new DeleteHandler( this.dsTa, this.aliasGenerator, this.conn, this.lockId );
941 deleteHandler.deleteOrphanedPropertyRows( relation, keyValues );
942 }
943
944 private void updateProperty( @SuppressWarnings("unused")
945 FeatureId fid, @SuppressWarnings("unused")
946 MappedFeatureType ft, @SuppressWarnings("unused")
947 MappedFeaturePropertyType pt, @SuppressWarnings("unused")
948 Feature replacementFeature ) {
949 throw new UnsupportedOperationException( "Updating of feature properties is not implemented yet." );
950 }
951
952 private void appendFIDWhereCondition( StatementBuffer query, FeatureId fid ) {
953 MappingField[] fidFields = fid.getFidDefinition().getIdFields();
954 for ( int i = 0; i < fidFields.length; i++ ) {
955 query.append( fidFields[i].getField() );
956 query.append( "=?" );
957 query.addArgument( fid.getValue( i ), fidFields[i].getType() );
958 if ( i != fidFields.length - 1 ) {
959 query.append( " AND " );
960 }
961 }
962 }
963 }