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 }