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