001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/io/datastore/sql/transaction/insert/InsertHandler.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.sql.transaction.insert;
044
045 import java.sql.Connection;
046 import java.sql.PreparedStatement;
047 import java.sql.ResultSet;
048 import java.sql.SQLException;
049 import java.util.ArrayList;
050 import java.util.Collection;
051 import java.util.HashMap;
052 import java.util.Iterator;
053 import java.util.List;
054 import java.util.Map;
055
056 import org.deegree.datatypes.Types;
057 import org.deegree.datatypes.UnknownTypeException;
058 import org.deegree.framework.log.ILogger;
059 import org.deegree.framework.log.LoggerFactory;
060 import org.deegree.i18n.Messages;
061 import org.deegree.io.datastore.DatastoreException;
062 import org.deegree.io.datastore.FeatureId;
063 import org.deegree.io.datastore.TransactionException;
064 import org.deegree.io.datastore.idgenerator.FeatureIdAssigner;
065 import org.deegree.io.datastore.idgenerator.IdGenerationException;
066 import org.deegree.io.datastore.idgenerator.ParentIDGenerator;
067 import org.deegree.io.datastore.schema.MappedFeaturePropertyType;
068 import org.deegree.io.datastore.schema.MappedFeatureType;
069 import org.deegree.io.datastore.schema.MappedGeometryPropertyType;
070 import org.deegree.io.datastore.schema.MappedPropertyType;
071 import org.deegree.io.datastore.schema.MappedSimplePropertyType;
072 import org.deegree.io.datastore.schema.TableRelation;
073 import org.deegree.io.datastore.schema.content.MappingField;
074 import org.deegree.io.datastore.schema.content.MappingGeometryField;
075 import org.deegree.io.datastore.schema.content.SimpleContent;
076 import org.deegree.io.datastore.sql.AbstractRequestHandler;
077 import org.deegree.io.datastore.sql.StatementBuffer;
078 import org.deegree.io.datastore.sql.TableAliasGenerator;
079 import org.deegree.io.datastore.sql.transaction.SQLTransaction;
080 import org.deegree.model.feature.Feature;
081 import org.deegree.model.feature.FeatureProperty;
082 import org.deegree.model.feature.schema.FeaturePropertyType;
083 import org.deegree.model.feature.schema.GeometryPropertyType;
084 import org.deegree.model.feature.schema.SimplePropertyType;
085 import org.deegree.model.spatialschema.Geometry;
086 import org.deegree.ogcwebservices.wfs.operation.transaction.Insert;
087 import org.deegree.ogcwebservices.wfs.operation.transaction.Transaction;
088
089 /**
090 * Handler for {@link Insert} operations (usually contained in {@link Transaction} requests).
091 *
092 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
093 * @author last edited by: $Author: apoth $
094 *
095 * @version $Revision: 9342 $, $Date: 2007-12-27 13:32:57 +0100 (Do, 27 Dez 2007) $
096 */
097 public class InsertHandler extends AbstractRequestHandler {
098
099 private static final ILogger LOG = LoggerFactory.getLogger( InsertHandler.class );
100
101 // features that are currently being processed
102 private Map<FeatureId, FeatureRow> featuresInInsertion = new HashMap<FeatureId, FeatureRow>();
103
104 // contains only property rows and join table rows (but no feature rows)
105 private List<InsertRow> insertRows = new ArrayList<InsertRow>();
106
107 private SQLTransaction dsTa;
108
109 /**
110 * Creates a new <code>InsertHandler</code> from the given parameters.
111 *
112 * @param dsTa
113 * @param aliasGenerator
114 * @param conn
115 */
116 public InsertHandler( SQLTransaction dsTa, TableAliasGenerator aliasGenerator, Connection conn ) {
117 super( dsTa.getDatastore(), aliasGenerator, conn );
118 this.dsTa = dsTa;
119 }
120
121 /**
122 * Inserts the given feature instance into the datastore.
123 *
124 * @param features
125 * (which have a MappedFeatureType as feature type)
126 * @return feature ids of inserted (root) feature instances
127 * @throws DatastoreException
128 * if the insert could not be performed
129 */
130 public List<FeatureId> performInsert( List<Feature> features )
131 throws DatastoreException {
132
133 List<FeatureId> fids = new ArrayList<FeatureId>();
134 for ( int i = 0; i < features.size(); i++ ) {
135 Feature feature = features.get( i );
136
137 MappedFeatureType ft = (MappedFeatureType) feature.getFeatureType();
138 if ( feature.getId().startsWith( FeatureIdAssigner.EXISTS_MARKER ) ) {
139 String msg = Messages.getMessage( "DATASTORE_FEATURE_EXISTS", feature.getName(),
140 feature.getId().substring( 1 ) );
141 throw new TransactionException( msg );
142 }
143 LOG.logDebug( "Inserting root feature '" + feature.getId() + "'..." );
144 insertFeature( feature );
145 FeatureId fid = new FeatureId( ft, feature.getId() );
146 fids.add( fid );
147
148 }
149
150 // merge inserts rows that are identical (except their pks)
151 this.insertRows = mergeInsertRows( this.insertRows );
152
153 // add featureRows to insertRows
154 Iterator<FeatureRow> iter = this.featuresInInsertion.values().iterator();
155 while ( iter.hasNext() ) {
156 this.insertRows.add( iter.next() );
157 }
158
159 // check for cyclic fk constraints
160 Collection<InsertRow> cycle = InsertRow.findCycle( this.insertRows );
161 if ( cycle != null ) {
162 Iterator<InsertRow> cycleIter = cycle.iterator();
163 StringBuffer sb = new StringBuffer();
164 while ( cycleIter.hasNext() ) {
165 sb.append( cycleIter.next() );
166 if ( cycle.iterator().hasNext() ) {
167 sb.append( " -> " );
168 }
169 }
170 String msg = Messages.getMessage( "DATASTORE_FK_CYCLE", sb.toString() );
171 throw new TransactionException( msg );
172 }
173
174 // sort the insert rows topologically
175 List<InsertRow> sortedInserts = InsertRow.sortInsertRows( this.insertRows );
176
177 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
178 LOG.logDebug( sortedInserts.size() + " rows to be inserted: " );
179 for ( InsertRow row : sortedInserts ) {
180 LOG.logDebug( row.toString() );
181 }
182 }
183
184 executeInserts( sortedInserts );
185 return fids;
186 }
187
188 /**
189 * Builds the <code>InsertRows</code> that are necessary to insert the given feature instance
190 * (including all properties + subfeatures).
191 *
192 * @param feature
193 * @return InsertRows that are necessary to insert the given feature instance
194 * @throws TransactionException
195 */
196 private FeatureRow insertFeature( Feature feature )
197 throws TransactionException {
198
199 MappedFeatureType ft = (MappedFeatureType) feature.getFeatureType();
200 if ( !ft.isInsertable() ) {
201 String msg = Messages.getMessage( "DATASTORE_FT_NOT_INSERTABLE", ft.getName() );
202 throw new TransactionException( msg );
203 }
204
205 LOG.logDebug( "Creating InsertRow for feature with type '" + ft.getName() + "' and id: '" + feature.getId()
206 + "'." );
207
208 // extract feature id column value
209 MappingField[] fidFields = ft.getGMLId().getIdFields();
210 if ( fidFields.length > 1 ) {
211 throw new TransactionException( "Insertion of features with compound feature " + "ids is not supported." );
212 }
213 FeatureId fid = null;
214 try {
215 fid = new FeatureId( ft, feature.getId() );
216 } catch ( IdGenerationException e ) {
217 throw new TransactionException( e.getMessage(), e );
218 }
219
220 // check if the feature id is already being inserted (happens for cyclic features)
221 FeatureRow insertRow = this.featuresInInsertion.get( fid );
222 if ( insertRow != null ) {
223 return insertRow;
224 }
225
226 insertRow = new FeatureRow( ft.getTable() );
227 this.featuresInInsertion.put( fid, insertRow );
228
229 // add column value for fid (primary key)
230 String fidColumn = fidFields[0].getField();
231 insertRow.setColumn( fidColumn, fid.getValue( 0 ), ft.getGMLId().getIdFields()[0].getType(), true );
232
233 // process properties
234 FeatureProperty[] properties = feature.getProperties();
235 for ( int i = 0; i < properties.length; i++ ) {
236 FeatureProperty property = properties[i];
237 MappedPropertyType propertyType = (MappedPropertyType) ft.getProperty( property.getName() );
238 if ( propertyType == null ) {
239 String msg = Messages.getMessage( "DATASTORE_PROPERTY_TYPE_NOT_KNOWN", property.getName() );
240 LOG.logDebug( msg );
241 throw new TransactionException( msg );
242 }
243 insertProperty( property, propertyType, insertRow );
244 }
245 return insertRow;
246 }
247
248 /**
249 * Builds the <code>InsertRow</code>s that are necessary to insert the given property
250 * instance (including all it's subfeatures).
251 *
252 * @param property
253 * property instance to be inserted
254 * @param propertyType
255 * property type of the property
256 * @param featureRow
257 * table row of the parent feature instance
258 * @throws TransactionException
259 */
260 private void insertProperty( FeatureProperty property, MappedPropertyType propertyType, InsertRow featureRow )
261 throws TransactionException {
262
263 if ( propertyType instanceof SimplePropertyType ) {
264 LOG.logDebug( "- Simple property '" + propertyType.getName() + "', value='" + getPropertyValue( property )
265 + "'." );
266 insertProperty( (MappedSimplePropertyType) propertyType, property, featureRow );
267 } else if ( propertyType instanceof GeometryPropertyType ) {
268 LOG.logDebug( "- Geometry property: '" + propertyType.getName() + "'" );
269 insertProperty( (MappedGeometryPropertyType) propertyType, property, featureRow );
270 } else if ( propertyType instanceof FeaturePropertyType ) {
271 LOG.logDebug( "- Feature property: '" + propertyType.getName() + "'" );
272 insertProperty( (MappedFeaturePropertyType) propertyType, property, featureRow );
273 } else {
274 throw new TransactionException( "Unhandled property type '" + propertyType.getClass().getName() + "'." );
275 }
276 }
277
278 /**
279 * Inserts the given simple property (stored in feature table or in related table).
280 *
281 * @param pt
282 * @param property
283 * @param featureRow
284 * @throws TransactionException
285 */
286 private void insertProperty( MappedSimplePropertyType pt, FeatureProperty property, InsertRow featureRow )
287 throws TransactionException {
288
289 SimpleContent content = pt.getContent();
290 if ( content.isUpdateable() ) {
291 if ( content instanceof MappingField ) {
292 MappingField mf = (MappingField) content;
293 String propertyColumn = mf.getField();
294 Object propertyValue = property.getValue();
295 int propertyType = mf.getType();
296 TableRelation[] relations = pt.getTableRelations();
297 insertProperty( propertyColumn, propertyValue, propertyType, relations, featureRow );
298 }
299 }
300 }
301
302 /**
303 * Inserts the given geometry property (stored in feature table or in related table).
304 *
305 * @param pt
306 * @param property
307 * @param featureRow
308 * @throws TransactionException
309 */
310 private void insertProperty( MappedGeometryPropertyType pt, FeatureProperty property, InsertRow featureRow )
311 throws TransactionException {
312
313 String propertyColumn = pt.getMappingField().getField();
314 MappingGeometryField dbField = pt.getMappingField();
315 Geometry deegreeGeometry = (Geometry) property.getValue();
316 Object dbGeometry;
317
318 int createSrsCode = dbField.getSRS();
319 int targetSrsCode = -1;
320 if ( deegreeGeometry.getCoordinateSystem() == null ) {
321 LOG.logDebug( "No SRS information for geometry available. Assuming '" + pt.getSRS() + "'." );
322 } else if ( !pt.getSRS().toString().equals( deegreeGeometry.getCoordinateSystem().getName() ) ) {
323 String msg = "Insert-Transformation: geometry srs: " + deegreeGeometry.getCoordinateSystem().getName()
324 + " -> property srs: " + pt.getSRS();
325 LOG.logDebug( msg );
326 if ( createSrsCode == -1 ) {
327 msg = Messages.getMessage( "DATASTORE_SRS_NOT_SPECIFIED", pt.getName(),
328 deegreeGeometry.getCoordinateSystem(), pt.getSRS() );
329 throw new TransactionException( msg );
330 }
331 try {
332 createSrsCode = datastore.getNativeSRSCode( deegreeGeometry.getCoordinateSystem().getName() );
333 } catch ( DatastoreException e ) {
334 throw new TransactionException( e.getMessage(), e );
335 }
336 targetSrsCode = dbField.getSRS();
337 }
338
339 try {
340 dbGeometry = this.datastore.convertDeegreeToDBGeometry( deegreeGeometry, createSrsCode, this.conn );
341 } catch ( DatastoreException e ) {
342 throw new TransactionException( e.getMessage(), e );
343 }
344
345 int propertyType = pt.getMappingField().getType();
346
347 // TODO remove this Oracle hack
348 if ( this.datastore.getClass().getName().contains( "OracleDatastore" ) ) {
349 propertyType = Types.STRUCT;
350 }
351
352 TableRelation[] relations = pt.getTableRelations();
353 insertProperty( propertyColumn, dbGeometry, propertyType, relations, featureRow, targetSrsCode );
354 }
355
356 /**
357 * Inserts the given simple property (stored in feature table or in related table).
358 *
359 * @param propertyColumn
360 * @param propertyValue
361 * @param propertyType
362 * @param featureRow
363 * @throws TransactionException
364 */
365 private void insertProperty( String propertyColumn, Object propertyValue, int propertyType,
366 TableRelation[] relations, InsertRow featureRow )
367 throws TransactionException {
368
369 if ( relations == null || relations.length == 0 ) {
370 // property is stored in feature table
371 featureRow.setColumn( propertyColumn, propertyValue, propertyType, false );
372 } else {
373 // property is stored in related table
374 if ( relations.length > 1 ) {
375 throw new TransactionException( Messages.getMessage( "DATASTORE_SIMPLE_PROPERTY_JOIN" ) );
376 }
377
378 if ( !relations[0].isFromFK() ) {
379 // fk is in property table
380 MappingField[] pkFields = relations[0].getFromFields();
381 MappingField[] fkFields = relations[0].getToFields();
382
383 for ( int i = 0; i < pkFields.length; i++ ) {
384 InsertField pkField = featureRow.getColumn( pkFields[i].getField() );
385 if ( pkField == null ) {
386 String msg = Messages.getMessage( "DATASTORE_NO_FK_VALUE", pkField.getColumnName(),
387 pkFields[i].getTable() );
388 throw new TransactionException( msg );
389 }
390 int pkColumnType = pkField.getSQLType();
391 int fkColumnType = fkFields[i].getType();
392 if ( pkColumnType != fkColumnType ) {
393 String fkType = "" + fkColumnType;
394 String pkType = "" + pkColumnType;
395 try {
396 fkType = Types.getTypeNameForSQLTypeCode( fkColumnType );
397 pkType = Types.getTypeNameForSQLTypeCode( pkColumnType );
398 } catch ( UnknownTypeException e ) {
399 LOG.logError( e.getMessage(), e );
400 }
401 Object[] params = new Object[] { relations[0].getToTable(), fkFields[i].getField(), fkType,
402 featureRow.getTable(), pkFields[i].getField(), pkType };
403 String msg = Messages.getMessage( "DATASTORE_FK_PK_TYPE_MISMATCH", params );
404 throw new TransactionException( msg );
405 }
406 InsertRow insertRow = new InsertRow( relations[0].getToTable() );
407 insertRow.linkColumn( fkFields[i].getField(), pkField );
408 insertRow.setColumn( propertyColumn, propertyValue, propertyType, false );
409 this.insertRows.add( insertRow );
410 }
411 } else {
412 // fk is in feature table
413 MappingField[] pkFields = relations[0].getToFields();
414 MappingField[] fkFields = relations[0].getFromFields();
415
416 // generate necessary primary key value
417 InsertField pkField = null;
418 try {
419 Object pk = null;
420 // TODO remove hack!!!
421 if ( relations[0].getIdGenerator() instanceof ParentIDGenerator ) {
422 InsertField field = featureRow.getColumn( "ID" );
423 if ( field == null ) {
424 throw new TransactionException( "No value for ID available!" );
425 }
426 pk = field.getValue();
427 } else {
428 pk = relations[0].getNewPK( this.dsTa );
429 }
430 InsertRow insertRow = findOrCreateRow( relations[0].getToTable(), pkFields[0].getField(), pk );
431 pkField = insertRow.setColumn( pkFields[0].getField(), pk, pkFields[0].getType(), true );
432 insertRow.setColumn( propertyColumn, propertyValue, propertyType, false );
433 } catch ( IdGenerationException e ) {
434 throw new TransactionException( e.getMessage(), e );
435 }
436 featureRow.linkColumn( fkFields[0].getField(), pkField );
437 }
438 }
439 }
440
441 /**
442 * Inserts the given geometry property (stored in feature table or in related table).
443 *
444 * @param propertyColumn
445 * @param propertyValue
446 * @param propertyType
447 * @param featureRow
448 * @throws TransactionException
449 */
450 private void insertProperty( String propertyColumn, Object propertyValue, int propertyType,
451 TableRelation[] relations, InsertRow featureRow, int targetSrsCode )
452 throws TransactionException {
453
454 if ( relations == null || relations.length == 0 ) {
455 // property is stored in feature table
456 featureRow.setGeometryColumn( propertyColumn, propertyValue, propertyType, false, targetSrsCode );
457 } else {
458 // property is stored in related table
459 if ( relations.length > 1 ) {
460 throw new TransactionException( Messages.getMessage( "DATASTORE_SIMPLE_PROPERTY_JOIN" ) );
461 }
462
463 if ( !relations[0].isFromFK() ) {
464 // fk is in property table
465 MappingField[] pkFields = relations[0].getFromFields();
466 MappingField[] fkFields = relations[0].getToFields();
467
468 for ( int i = 0; i < pkFields.length; i++ ) {
469 InsertField pkField = featureRow.getColumn( pkFields[i].getField() );
470 if ( pkField == null ) {
471 String msg = Messages.getMessage( "DATASTORE_NO_FK_VALUE", pkField.getColumnName(),
472 pkFields[i].getTable() );
473 throw new TransactionException( msg );
474 }
475 int pkColumnType = pkField.getSQLType();
476 int fkColumnType = fkFields[i].getType();
477 if ( pkColumnType != fkColumnType ) {
478 String fkType = "" + fkColumnType;
479 String pkType = "" + pkColumnType;
480 try {
481 fkType = Types.getTypeNameForSQLTypeCode( fkColumnType );
482 pkType = Types.getTypeNameForSQLTypeCode( pkColumnType );
483 } catch ( UnknownTypeException e ) {
484 LOG.logError( e.getMessage(), e );
485 }
486 Object[] params = new Object[] { relations[0].getToTable(), fkFields[i].getField(), fkType,
487 featureRow.getTable(), pkFields[i].getField(), pkType };
488 String msg = Messages.getMessage( "DATASTORE_FK_PK_TYPE_MISMATCH", params );
489 throw new TransactionException( msg );
490 }
491 InsertRow insertRow = new InsertRow( relations[0].getToTable() );
492 insertRow.linkColumn( fkFields[i].getField(), pkField );
493 insertRow.setGeometryColumn( propertyColumn, propertyValue, propertyType, false, targetSrsCode );
494 this.insertRows.add( insertRow );
495 }
496 } else {
497 // fk is in feature table
498 MappingField[] pkFields = relations[0].getToFields();
499 MappingField[] fkFields = relations[0].getFromFields();
500
501 // generate necessary primary key value
502 InsertField pkField = null;
503 try {
504 Object pk = null;
505 // TODO remove hack!!!
506 if ( relations[0].getIdGenerator() instanceof ParentIDGenerator ) {
507 InsertField field = featureRow.getColumn( "ID" );
508 if ( field == null ) {
509 throw new TransactionException( "No value for ID available!" );
510 }
511 pk = field.getValue();
512 } else {
513 pk = relations[0].getNewPK( this.dsTa );
514 }
515 InsertRow insertRow = findOrCreateRow( relations[0].getToTable(), pkFields[0].getField(), pk );
516 pkField = insertRow.setColumn( pkFields[0].getField(), pk, pkFields[0].getType(), true );
517 insertRow.setGeometryColumn( propertyColumn, propertyValue, propertyType, false, targetSrsCode );
518 } catch ( IdGenerationException e ) {
519 throw new TransactionException( e.getMessage(), e );
520 }
521 featureRow.linkColumn( fkFields[0].getField(), pkField );
522 }
523 }
524 }
525
526 /**
527 * Inserts the given feature property.
528 *
529 * @param pt
530 * @param property
531 * @param featureRow
532 * @throws TransactionException
533 */
534 private void insertProperty( MappedFeaturePropertyType pt, FeatureProperty property, InsertRow featureRow )
535 throws TransactionException {
536
537 // find (concrete) subfeature type for the given property instance
538 MappedFeatureType propertyFeatureType = pt.getFeatureTypeReference().getFeatureType();
539 MappedFeatureType[] substitutions = propertyFeatureType.getConcreteSubstitutions();
540 Feature subFeature = (Feature) property.getValue();
541 MappedFeatureType subFeatureType = null;
542 for ( int i = 0; i < substitutions.length; i++ ) {
543 if ( substitutions[i].getName().equals( subFeature.getName() ) ) {
544 subFeatureType = substitutions[i];
545 break;
546 }
547 }
548 if ( subFeatureType == null ) {
549 String msg = Messages.getMessage( "DATASTORE_FEATURE_NOT_SUBSTITUTABLE", propertyFeatureType.getName(),
550 subFeature.getName() );
551 throw new TransactionException( msg );
552 }
553 boolean needsDisambiguation = propertyFeatureType.hasSeveralImplementations();
554
555 TableRelation[] relations = pt.getTableRelations();
556 if ( relations == null || relations.length < 1 ) {
557 throw new TransactionException( "Invalid feature property definition, feature property "
558 + "mappings must use at least one 'TableRelation' element." );
559 }
560
561 // workaround for links to dummy InsertRows (of already stored features)
562 boolean cutLink = subFeature.getId().startsWith( FeatureIdAssigner.EXISTS_MARKER );
563 InsertRow subFeatureRow = null;
564 if ( cutLink ) {
565 try {
566 Object fidValue = FeatureId.removeFIDPrefix( subFeature.getId().substring( 1 ),
567 subFeatureType.getGMLId() );
568 subFeatureRow = new FeatureRow( subFeatureType.getTable() );
569 // add column value for fid (primary key)
570 String fidColumn = subFeatureType.getGMLId().getIdFields()[0].getField();
571 subFeatureRow.setColumn( fidColumn, fidValue, subFeatureType.getGMLId().getIdFields()[0].getType(),
572 true );
573 } catch ( DatastoreException e ) {
574 throw new TransactionException( e );
575 }
576 } else {
577 // insert sub feature (if it is not already stored)
578 subFeatureRow = insertFeature( subFeature );
579 }
580
581 if ( relations.length == 1 ) {
582 if ( relations[0].isFromFK() ) {
583 // fk is in feature table
584 MappingField[] pkFields = relations[0].getToFields();
585 MappingField[] fkFields = relations[0].getFromFields();
586
587 for ( int i = 0; i < pkFields.length; i++ ) {
588 InsertField pkField = subFeatureRow.getColumn( pkFields[i].getField() );
589 if ( pkField == null ) {
590 String msg = Messages.getMessage( "DATASTORE_NO_FK_VALUE", pkField.getColumnName(),
591 pkField.getTable() );
592 throw new TransactionException( msg );
593 }
594 int pkColumnType = pkField.getSQLType();
595 int fkColumnType = fkFields[i].getType();
596 if ( pkColumnType != fkColumnType ) {
597 String fkType = "" + fkColumnType;
598 String pkType = "" + pkColumnType;
599 try {
600 fkType = Types.getTypeNameForSQLTypeCode( fkColumnType );
601 pkType = Types.getTypeNameForSQLTypeCode( pkColumnType );
602 } catch ( UnknownTypeException e ) {
603 LOG.logError( e.getMessage(), e );
604 }
605 Object[] params = new Object[] { featureRow.getTable(), fkFields[i].getField(), fkType,
606 subFeatureRow.getTable(), pkFields[i].getField(), pkType };
607 String msg = Messages.getMessage( "DATASTORE_FK_PK_TYPE_MISMATCH", params );
608 throw new TransactionException( msg );
609 }
610
611 if ( !cutLink ) {
612 featureRow.linkColumn( fkFields[i].getField(), pkField );
613 } else {
614 featureRow.setColumn( fkFields[i].getField(), pkField.getValue(), pkField.getSQLType(), false );
615 }
616 }
617
618 if ( needsDisambiguation ) {
619 String typeField = FT_PREFIX + relations[0].getFromFields()[0].getField();
620 featureRow.setColumn( typeField, subFeatureType.getName().getLocalName(), Types.VARCHAR, false );
621 }
622 } else {
623 // fk is in subfeature table
624 MappingField[] pkFields = relations[0].getFromFields();
625 MappingField[] fkFields = relations[0].getToFields();
626
627 if ( pkFields[0] != null ) {
628 LOG.logDebug( "Getting column " + pkFields[0].getField() + "from table: " + pkFields[0].getTable()
629 + " of the featureRow: " + featureRow.getTable() );
630 }
631
632 InsertField pkField = featureRow.getColumn( pkFields[0].getField() );
633
634 if ( pkField == null ) {
635 String msg = null;
636
637 if ( pkFields[0] != null ) {
638 msg = Messages.getMessage( "DATASTORE_NO_FK_VALUE", pkFields[0].getField(),
639 pkFields[0].getTable() );
640 } else {
641 if ( relations[0] != null ) {
642 msg = Messages.getMessage( "DATASTORE_NO_FK_VALUE",
643 "unknown primary keys in 'from'-fields",
644 relations[0].getFromTable() );
645 } else {
646 msg = Messages.getMessage( "DATASTORE_NO_FK_VALUE",
647 "unknown primary keys in 'from'-fields", "unknown 'from'-table" );
648 }
649 }
650
651 throw new TransactionException( msg );
652 }
653 int pkColumnType = pkField.getSQLType();
654 int fkColumnType = fkFields[0].getType();
655 if ( pkColumnType != fkColumnType ) {
656 String fkType = "" + fkColumnType;
657 String pkType = "" + pkColumnType;
658 try {
659 fkType = Types.getTypeNameForSQLTypeCode( fkColumnType );
660 pkType = Types.getTypeNameForSQLTypeCode( pkColumnType );
661 } catch ( UnknownTypeException e ) {
662 LOG.logError( e.getMessage(), e );
663 }
664 Object[] params = new Object[] { subFeatureRow.getTable(), fkFields[0].getField(), fkType,
665 featureRow.getTable(), pkField.getColumnName(), pkType };
666 String msg = Messages.getMessage( "DATASTORE_FK_PK_TYPE_MISMATCH", params );
667 throw new TransactionException( msg );
668 }
669
670 if ( !cutLink ) {
671 subFeatureRow.linkColumn( fkFields[0].getField(), pkField );
672 } else {
673 subFeatureRow.setColumn( fkFields[0].getField(), pkField.getValue(), pkField.getSQLType(), false );
674 }
675 }
676 } else if ( relations.length == 2 ) {
677
678 // insert into join table
679 String joinTable = relations[0].getToTable();
680 MappingField[] leftKeyFields = relations[0].getToFields();
681 MappingField[] rightKeyFields = relations[1].getFromFields();
682
683 InsertRow jtRow = new InsertRow( joinTable );
684 if ( needsDisambiguation ) {
685 jtRow.setColumn( FT_COLUMN, subFeatureType.getName().getLocalName(), Types.VARCHAR, false );
686 }
687
688 if ( !relations[0].isFromFK() ) {
689 // left key field in join table is fk
690 MappingField[] pkFields = relations[0].getFromFields();
691 InsertField pkField = featureRow.getColumn( pkFields[0].getField() );
692 if ( pkField == null ) {
693 String columnName = null;
694 if ( pkFields[0] != null ) {
695 columnName = pkFields[0].getField();
696 } else {
697 columnName = "unknown primary keys in 'from'-fields";
698 }
699 throw new TransactionException( "Insertion of feature property using join table failed: "
700 + "no value for join table key column '" + columnName + "'." );
701 }
702 jtRow.linkColumn( leftKeyFields[0].getField(), pkField );
703 } else {
704 // left key field in join table is pk
705 MappingField[] pkFields = relations[0].getToFields();
706 // generate necessary primary key value
707 InsertField pkField = null;
708 try {
709 Object pk = relations[0].getNewPK( this.dsTa );
710 pkField = jtRow.setColumn( pkFields[0].getField(), pk, pkFields[0].getType(), true );
711 } catch ( IdGenerationException e ) {
712 throw new TransactionException( e.getMessage(), e );
713 }
714 featureRow.linkColumn( relations[0].getFromFields()[0].getField(), pkField );
715 }
716
717 if ( relations[1].isFromFK() ) {
718 // right key field in join table is fk
719 MappingField[] pkFields = relations[1].getToFields();
720 InsertField pkField = subFeatureRow.getColumn( pkFields[0].getField() );
721 if ( pkField == null ) {
722 throw new TransactionException( "Insertion of feature property using join table failed: "
723 + "no value for join table key column '" + pkField.getColumnName()
724 + "'." );
725 }
726 if ( !cutLink ) {
727 jtRow.linkColumn( rightKeyFields[0].getField(), pkField );
728 } else {
729 jtRow.setColumn( rightKeyFields[0].getField(), pkField.getValue(), pkField.getSQLType(), false );
730 }
731 } else {
732 // right key field in join table is pk
733 MappingField[] pkFields = relations[1].getFromFields();
734 // generate necessary primary key value
735 InsertField pkField = null;
736 try {
737 Object pk = relations[1].getNewPK( this.dsTa );
738 pkField = jtRow.setColumn( pkFields[0].getField(), pk, pkFields[0].getType(), true );
739 } catch ( IdGenerationException e ) {
740 throw new TransactionException( e.getMessage(), e );
741 }
742 if ( !cutLink ) {
743 subFeatureRow.linkColumn( relations[1].getToFields()[0].getField(), pkField );
744 }
745 }
746 this.insertRows.add( jtRow );
747 } else {
748 throw new TransactionException( "Insertion of feature properties stored in related tables "
749 + "connected via more than one join table is not supported." );
750 }
751 }
752
753 /**
754 * Checks whether the feature that corresponds to the given FeatureRow is already stored in the
755 * database.
756 *
757 * @param featureRow
758 * @return true, if feature is already stored, false otherwise
759 * @throws DatastoreException
760 */
761 private boolean doesFeatureExist( FeatureRow featureRow )
762 throws DatastoreException {
763
764 boolean exists = false;
765
766 InsertField pkField = featureRow.getPKColumn();
767
768 StatementBuffer query = buildFeatureSelect( pkField.getColumnName(), pkField.getSQLType(), pkField.getValue(),
769 featureRow.getTable() );
770 LOG.logDebug( "Feature existence query: '" + query + "'" );
771
772 PreparedStatement stmt = null;
773 ResultSet rs = null;
774 try {
775 stmt = this.datastore.prepareStatement( this.conn, query );
776 rs = stmt.executeQuery();
777 if ( rs.next() ) {
778 exists = true;
779 }
780 if ( rs.next() ) {
781 String msg = Messages.getMessage( "DATASTORE_FEATURE_QUERY_MORE_THAN_ONE_RESULT",
782 query.getQueryString() );
783 LOG.logError( msg );
784 throw new TransactionException( msg );
785 }
786 } catch ( SQLException e ) {
787 throw new TransactionException( e );
788 } finally {
789 try {
790 if ( rs != null ) {
791 rs.close();
792 }
793 } catch ( SQLException e ) {
794 throw new TransactionException( e );
795 } finally {
796 if ( stmt != null ) {
797 try {
798 stmt.close();
799 } catch ( SQLException e ) {
800 throw new TransactionException( e );
801 }
802 }
803 }
804 }
805 return exists;
806 }
807
808 /**
809 * Builds a SELECT statement that checks for the existence of a feature with the given id.
810 *
811 * @param fidColumn
812 * @param typeCode
813 * @param fidValue
814 * @param table
815 * @return the statement
816 */
817 private StatementBuffer buildFeatureSelect( String fidColumn, int typeCode, Object fidValue, String table ) {
818
819 StatementBuffer query = new StatementBuffer();
820 query.append( "SELECT * FROM " );
821 query.append( table );
822 query.append( " WHERE " );
823
824 // append feature id constraints
825 query.append( fidColumn );
826 query.append( "=?" );
827 query.addArgument( fidValue, typeCode );
828 return query;
829 }
830
831 private InsertRow findOrCreateRow( String table, String pkColumn, Object value ) {
832 Iterator<InsertRow> rowIter = this.insertRows.iterator();
833 boolean found = false;
834 InsertRow row = null;
835 while ( rowIter.hasNext() ) {
836 row = rowIter.next();
837 if ( row.getTable().equals( table ) ) {
838 InsertField field = row.getColumn( pkColumn );
839 if ( value.equals( field.getValue() ) ) {
840 found = true;
841 LOG.logDebug( "Found matching row " + row );
842 break;
843 }
844 }
845 }
846 if ( !found ) {
847 row = new InsertRow( table );
848 this.insertRows.add( row );
849 }
850 return row;
851 }
852
853 private String getPropertyValue( FeatureProperty property ) {
854 Object value = property.getValue();
855 StringBuffer sb = new StringBuffer();
856 if ( value instanceof Object[] ) {
857 Object[] objects = (Object[]) value;
858 for ( int i = 0; i < objects.length; i++ ) {
859 sb.append( objects[i] );
860 }
861 } else {
862 sb.append( value );
863 }
864 return sb.toString();
865 }
866
867 /**
868 * Transforms the given <code>List</code> of <code>InsertRows</code> into SQL INSERT
869 * statements and executes them using the underlying JDBC connection.
870 *
871 * @param inserts
872 * @throws DatastoreException
873 */
874 private void executeInserts( List<InsertRow> inserts )
875 throws DatastoreException {
876
877 PreparedStatement stmt = null;
878
879 for ( InsertRow row : inserts ) {
880 if ( row instanceof FeatureRow ) {
881 if ( doesFeatureExist( (FeatureRow) row ) ) {
882 LOG.logDebug( "Skipping feature row. Already present in db." );
883 continue;
884 }
885 }
886 try {
887 stmt = null;
888 StatementBuffer insert = createStatementBuffer( row );
889 LOG.logDebug( insert.toString() );
890 stmt = this.datastore.prepareStatement( this.conn, insert );
891 stmt.execute();
892 } catch ( SQLException e ) {
893 String msg = "Error performing insert: " + e.getMessage();
894 LOG.logError( msg, e );
895 throw new TransactionException( msg, e );
896 } finally {
897 if ( stmt != null ) {
898 try {
899 stmt.close();
900 } catch ( SQLException e ) {
901 String msg = "Error closing statement: " + e.getMessage();
902 LOG.logError( msg, e );
903 }
904 }
905 }
906 }
907 }
908
909 private StatementBuffer createStatementBuffer( InsertRow row )
910 throws DatastoreException {
911 StatementBuffer insert = new StatementBuffer();
912 insert.append( "INSERT INTO " );
913 insert.append( row.table );
914 insert.append( " (" );
915 Iterator<InsertField> columnsIter = row.getColumns().iterator();
916 while ( columnsIter.hasNext() ) {
917 insert.append( columnsIter.next().getColumnName() );
918 if ( columnsIter.hasNext() ) {
919 insert.append( ',' );
920 }
921 }
922 insert.append( ") VALUES(" );
923 columnsIter = row.getColumns().iterator();
924 while ( columnsIter.hasNext() ) {
925 String placeHolder = "?";
926 InsertField field = columnsIter.next();
927 if ( field instanceof InsertGeometryField ) {
928 int targetSrsCode = ( (InsertGeometryField) field ).getTargetSrsCode();
929 if ( targetSrsCode != -1 ) {
930 placeHolder = this.datastore.buildSRSTransformCall( "?", targetSrsCode );
931 }
932 }
933 insert.append( placeHolder );
934 insert.addArgument( field.getValue(), field.getSQLType() );
935 if ( columnsIter.hasNext() ) {
936 insert.append( ',' );
937 }
938 }
939 insert.append( ")" );
940 return insert;
941 }
942
943 /**
944 * Merges the given <code>InsertRow</code>s by eliminating rows that have identical content
945 * (except for their primary keys).
946 * <p>
947 * This only applies to non-FeatureRows: there are never two FeatureRows that may be treated as
948 * identical, because unique feature ids have been assigned to them before.
949 *
950 * @see FeatureIdAssigner
951 *
952 * @param insertRows
953 * @return merged List of InsertRows
954 */
955 private List<InsertRow> mergeInsertRows( List<InsertRow> insertRows ) {
956
957 List<InsertRow> result = new ArrayList<InsertRow>();
958
959 // keys: table names, values: inserts into the table
960 Map<String, Collection<InsertRow>> tableMap = new HashMap<String, Collection<InsertRow>>();
961
962 // build table lookup map
963 Iterator<InsertRow> iter = insertRows.iterator();
964 while ( iter.hasNext() ) {
965 InsertRow insertRow = iter.next();
966 Collection<InsertRow> tableInserts = tableMap.get( insertRow.getTable() );
967 if ( tableInserts == null ) {
968 tableInserts = new ArrayList<InsertRow>();
969 tableMap.put( insertRow.getTable(), tableInserts );
970 }
971 tableInserts.add( insertRow );
972 }
973
974 iter = insertRows.iterator();
975 while ( iter.hasNext() ) {
976 InsertRow insertRow = iter.next();
977 boolean insert = true;
978 if ( !( insertRow instanceof FeatureRow ) ) {
979 Collection<InsertRow> tableInserts = tableMap.get( insertRow.getTable() );
980 Iterator<InsertRow> candidatesIter = tableInserts.iterator();
981 while ( candidatesIter.hasNext() ) {
982 InsertRow candidate = candidatesIter.next();
983 if ( insertRow != candidate ) {
984 if ( compareInsertRows( insertRow, candidate ) ) {
985 LOG.logDebug( "Removing InsertRow: " + insertRow.hashCode() + " " + insertRow
986 + " - duplicate of: " + candidate );
987 replaceInsertRow( insertRow, candidate );
988 insert = false;
989 tableInserts.remove( insertRow );
990 break;
991 }
992 }
993 }
994 }
995 if ( insert ) {
996 result.add( insertRow );
997 }
998 }
999 return result;
1000 }
1001
1002 private boolean compareInsertRows( InsertRow row1, InsertRow row2 ) {
1003 Collection<InsertField> fields1 = row1.getColumns();
1004 Iterator<InsertField> iter = fields1.iterator();
1005 while ( iter.hasNext() ) {
1006 InsertField field1 = iter.next();
1007 if ( !field1.isPK() ) {
1008 InsertField field2 = row2.getColumn( field1.getColumnName() );
1009 Object value1 = field1.getValue();
1010 Object value2 = null;
1011 if ( field2 != null )
1012 value2 = field2.getValue();
1013 if ( value1 == null ) {
1014 if ( value2 == null ) {
1015 continue;
1016 }
1017 return false;
1018 }
1019 try {
1020 if ( !value1.equals( value2 ) ) {
1021 return false;
1022 }
1023 } catch ( NullPointerException e ) {
1024 LOG.logWarning( "A null pointer exception occurred while comparing features/attributes. Assuming they're not equal..." );
1025 return false;
1026 }
1027 }
1028 }
1029 return true;
1030 }
1031
1032 private void replaceInsertRow( InsertRow oldRow, InsertRow newRow ) {
1033
1034 Collection<InsertField> oldFields = oldRow.getColumns();
1035 for ( InsertField field : oldFields ) {
1036 InsertField toField = field.getReferencedField();
1037 if ( toField != null ) {
1038 LOG.logDebug( "Removing reference to field '" + toField + "'" );
1039 toField.removeReferencingField( field );
1040 }
1041 }
1042
1043 Collection<InsertField> referencingFields = oldRow.getReferencingFields();
1044 for ( InsertField fromField : referencingFields ) {
1045 LOG.logDebug( "Replacing reference for field '" + fromField + "'" );
1046 InsertField field = newRow.getColumn( fromField.getReferencedField().getColumnName() );
1047 LOG.logDebug( "" + field );
1048 fromField.relinkField( field );
1049 }
1050 }
1051 }