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 }