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 }