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