001 //$HeadURL: http://svn.wald.intevation.org/svn/deegree/base/trunk/src/org/deegree/io/datastore/sde/SDEInsertHandler.java $
002 /*---------------- FILE HEADER ------------------------------------------
003
004 This file is part of deegree.
005 Copyright (C) 2006 by: M.O.S.S. Computer Grafik Systeme GmbH
006 Hohenbrunner Weg 13
007 D-82024 Taufkirchen
008 http://www.moss.de/
009
010 This library is free software; you can redistribute it and/or
011 modify it under the terms of the GNU Lesser General Public
012 License as published by the Free Software Foundation; either
013 version 2.1 of the License, or (at your option) any later version.
014
015 This library is distributed in the hope that it will be useful,
016 but WITHOUT ANY WARRANTY; without even the implied warranty of
017 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
018 Lesser General Public License for more details.
019
020 You should have received a copy of the GNU Lesser General Public
021 License along with this library; if not, write to the Free Software
022 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
023
024 ---------------------------------------------------------------------------*/
025 package org.deegree.io.datastore.sde;
026
027 import java.util.ArrayList;
028 import java.util.Collection;
029 import java.util.HashMap;
030 import java.util.Iterator;
031 import java.util.List;
032 import java.util.Map;
033
034 import org.deegree.datatypes.Types;
035 import org.deegree.framework.log.ILogger;
036 import org.deegree.framework.log.LoggerFactory;
037 import org.deegree.framework.util.StringTools;
038 import org.deegree.io.datastore.DatastoreException;
039 import org.deegree.io.datastore.FeatureId;
040 import org.deegree.io.datastore.TransactionException;
041 import org.deegree.io.datastore.idgenerator.FeatureIdAssigner;
042 import org.deegree.io.datastore.idgenerator.IdGenerationException;
043 import org.deegree.io.datastore.idgenerator.ParentIDGenerator;
044 import org.deegree.io.datastore.schema.MappedFeaturePropertyType;
045 import org.deegree.io.datastore.schema.MappedFeatureType;
046 import org.deegree.io.datastore.schema.MappedGeometryPropertyType;
047 import org.deegree.io.datastore.schema.MappedPropertyType;
048 import org.deegree.io.datastore.schema.MappedSimplePropertyType;
049 import org.deegree.io.datastore.schema.TableRelation;
050 import org.deegree.io.datastore.schema.content.MappingField;
051 import org.deegree.io.datastore.schema.content.SimpleContent;
052 import org.deegree.io.datastore.sql.transaction.insert.FeatureRow;
053 import org.deegree.io.datastore.sql.transaction.insert.InsertField;
054 import org.deegree.io.datastore.sql.transaction.insert.InsertRow;
055 import org.deegree.io.sdeapi.SDEAdapter;
056 import org.deegree.model.feature.Feature;
057 import org.deegree.model.feature.FeatureProperty;
058 import org.deegree.model.feature.schema.FeaturePropertyType;
059 import org.deegree.model.feature.schema.GeometryPropertyType;
060 import org.deegree.model.feature.schema.SimplePropertyType;
061 import org.deegree.model.spatialschema.Geometry;
062
063 import com.esri.sde.sdk.client.SeException;
064 import com.esri.sde.sdk.client.SeInsert;
065 import com.esri.sde.sdk.client.SeObjectId;
066 import com.esri.sde.sdk.client.SeQuery;
067 import com.esri.sde.sdk.client.SeRow;
068 import com.esri.sde.sdk.client.SeSqlConstruct;
069 import com.esri.sde.sdk.client.SeState;
070
071 /**
072 * Handler for <code>Insert</code> operations (usually contained in <code>Transaction</code> requests).
073 *
074 * @author <a href="mailto:cpollmann@moss.de">Christoph Pollmann</a>
075 * @author last edited by: $Author: aschmitz $
076 *
077 * @version $Revision: 23585 $
078 */
079 public class SDEInsertHandler extends AbstractSDERequestHandler {
080
081 private static final ILogger LOG = LoggerFactory.getLogger( SDEInsertHandler.class );
082
083 // features that are currently being processed
084 private Map<FeatureId, FeatureRow> featuresInInsertion = new HashMap<FeatureId, FeatureRow>();
085
086 // contains only property rows and join table rows (but no feature rows)
087 private List<InsertRow> insertRows = new ArrayList<InsertRow>();
088
089 private SDETransaction dsTa;
090
091 /**
092 * Creates a new <code>InsertHandler</code> from the given parameters.
093 *
094 * @param dsTa
095 */
096 public SDEInsertHandler( SDETransaction dsTa ) {
097 super( dsTa.getDatastore(), dsTa.getAliasGenerator(), dsTa.getConnection() );
098 this.dsTa = dsTa;
099 }
100
101 /**
102 * Inserts the given feature instance into the datastore.
103 *
104 * @param features
105 * (which have a MappedFeatureType as feature type)
106 * @return feature ids of inserted (root) feature instances
107 * @throws DatastoreException
108 * if the insert could not be performed
109 */
110 public List<FeatureId> performInsert( List<Feature> features )
111 throws DatastoreException {
112
113 List<FeatureId> fids = new ArrayList<FeatureId>();
114
115 for ( int i = 0; i < features.size(); i++ ) {
116 Feature feature = features.get( i );
117 MappedFeatureType ft = (MappedFeatureType) feature.getFeatureType();
118 if ( feature.getId().startsWith( FeatureIdAssigner.EXISTS_MARKER ) ) {
119 String msg = "feature already exists " + feature.getName() + " id=" + feature.getId().substring( 1 );
120 throw new TransactionException( msg );
121 }
122 LOG.logDebug( "Inserting root feature '" + feature.getId() + "'..." );
123 insertFeature( feature );
124 FeatureId fid = new FeatureId( ft, feature.getId() );
125 fids.add( fid );
126 }
127
128 // merge inserts rows that are identical (except their pks)
129 this.insertRows = mergeInsertRows( this.insertRows );
130
131 // add featureRows to insertRows
132 Iterator<FeatureRow> iter = this.featuresInInsertion.values().iterator();
133 while ( iter.hasNext() ) {
134 this.insertRows.add( iter.next() );
135 }
136
137 // try to sort the insert rows topologically (but continue in original order, if not topological order is
138 // possible)
139 List<InsertRow> sortedInserts = InsertRow.getInsertOrder( this.insertRows );
140
141 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
142 Iterator<InsertRow> iter2 = sortedInserts.iterator();
143 LOG.logDebug( sortedInserts.size() + " rows to be inserted: " );
144 while ( iter2.hasNext() ) {
145 LOG.logDebug( iter2.next().toString() );
146 }
147 }
148
149 executeInserts( sortedInserts );
150
151 return fids;
152 }
153
154 /**
155 * Builds the <code>InsertRows</code> that are necessary to insert the given feature instance (including all
156 * properties + subfeatures).
157 *
158 * @param feature
159 * @return the row of the given feature
160 * @throws TransactionException
161 */
162 private FeatureRow insertFeature( Feature feature )
163 throws TransactionException {
164
165 MappedFeatureType ft = (MappedFeatureType) feature.getFeatureType();
166 if ( !ft.isInsertable() ) {
167 String msg = "featuretype can't be inserted " + ft.getName();
168 throw new TransactionException( msg );
169 }
170
171 LOG.logDebug( "Creating InsertRow for feature with type '" + ft.getName() + "' and id: '" + feature.getId()
172 + "'." );
173
174 // extract feature id column value
175 MappingField[] fidFields = ft.getGMLId().getIdFields();
176 if ( fidFields.length > 1 ) {
177 throw new TransactionException( "Insertion of features with compound feature ids is not " + "supported." );
178 }
179 Object fidValue = null;
180 try {
181 fidValue = FeatureId.removeFIDPrefix( feature.getId(), ft.getGMLId() );
182 } catch ( DatastoreException e ) {
183 e.printStackTrace();
184 throw new TransactionException( e.getMessage(), e );
185 }
186 FeatureId fid = new FeatureId( ft, new Object[] { fidValue } );
187
188 // check if the feature id is already being inserted (happens for cyclic features)
189 FeatureRow insertRow = this.featuresInInsertion.get( fid );
190 if ( insertRow != null ) {
191 return insertRow;
192 }
193
194 insertRow = new FeatureRow( ft.getTable() );
195 this.featuresInInsertion.put( fid, insertRow );
196
197 // add column value for fid (primary key)
198 String fidColumn = fidFields[0].getField();
199 insertRow.setColumn( fidColumn, fidValue, ft.getGMLId().getIdFields()[0].getType(), true );
200
201 // process properties
202 FeatureProperty[] properties = feature.getProperties();
203 for ( int i = 0; i < properties.length; i++ ) {
204 FeatureProperty property = properties[i];
205 MappedPropertyType propertyType = (MappedPropertyType) ft.getProperty( property.getName() );
206 if ( propertyType == null ) {
207 String msg = "Unknown propertytype " + property.getName();
208 LOG.logDebug( msg );
209 throw new TransactionException( msg );
210 }
211 insertProperty( property, propertyType, insertRow );
212 }
213 return insertRow;
214 }
215
216 /**
217 * Builds the <code>InsertRow</code>s that are necessary to insert the given property instance (including all it's
218 * subfeatures).
219 *
220 * @param property
221 * property instance to be inserted
222 * @param propertyType
223 * property type of the property
224 * @param featureRow
225 * table row of the parent feature instance
226 * @throws TransactionException
227 */
228 private void insertProperty( FeatureProperty property, MappedPropertyType propertyType, InsertRow featureRow )
229 throws TransactionException {
230
231 if ( propertyType instanceof SimplePropertyType ) {
232 String msg = StringTools.concat( 300, "- Simple property '", propertyType.getName(),
233 "', value='" + getPropertyValue( property ), "'." );
234 LOG.logDebug( msg );
235 insertProperty( (MappedSimplePropertyType) propertyType, property, featureRow );
236 } else if ( propertyType instanceof GeometryPropertyType ) {
237 LOG.logDebug( "- Geometry property: '" + propertyType.getName() + "'" );
238 insertProperty( (MappedGeometryPropertyType) propertyType, property, featureRow );
239 } else if ( propertyType instanceof FeaturePropertyType ) {
240 LOG.logDebug( "- Feature property: '" + propertyType.getName() + "'" );
241 insertProperty( (MappedFeaturePropertyType) propertyType, property, featureRow );
242 } else {
243 throw new TransactionException( "Unhandled property type '" + propertyType.getClass().getName() + "'." );
244 }
245 }
246
247 /**
248 * Inserts the given simple property (stored in feature table or in related table).
249 *
250 * @param pt
251 * @param property
252 * @param featureRow
253 * @throws TransactionException
254 */
255 private void insertProperty( MappedSimplePropertyType pt, FeatureProperty property, InsertRow featureRow )
256 throws TransactionException {
257
258 SimpleContent content = pt.getContent();
259 if ( content.isUpdateable() ) {
260 if ( content instanceof MappingField ) {
261 MappingField mf = (MappingField) content;
262 String propertyColumn = mf.getField();
263 Object propertyValue = property.getValue();
264 int propertyType = mf.getType();
265 TableRelation[] relations = pt.getTableRelations();
266 insertProperty( propertyColumn, propertyValue, propertyType, relations, featureRow );
267 }
268 }
269 }
270
271 /**
272 * Inserts the given geometry property (stored in feature table or in related table).
273 *
274 * @param pt
275 * @param property
276 * @param featureRow
277 * @throws TransactionException
278 */
279 private void insertProperty( MappedGeometryPropertyType pt, FeatureProperty property, InsertRow featureRow )
280 throws TransactionException {
281
282 String propertyColumn = pt.getMappingField().getField();
283
284 Geometry deegreeGeometry = (Geometry) property.getValue();
285 Object dbGeometry;
286
287 try {
288 dbGeometry = this.datastore.convertDegreeToDBGeometry( deegreeGeometry );
289 } catch ( DatastoreException e ) {
290 throw new TransactionException( e.getMessage(), e );
291 }
292
293 int propertyType = pt.getMappingField().getType();
294
295 TableRelation[] relations = pt.getTableRelations();
296 insertProperty( propertyColumn, dbGeometry, propertyType, relations, featureRow );
297 }
298
299 /**
300 * Inserts the given simple / geometry property (stored in feature table or in related table).
301 *
302 * @param propertyColumn
303 * @param propertyValue
304 * @param propertyType
305 * @param featureRow
306 * @throws TransactionException
307 */
308 private void insertProperty( String propertyColumn, Object propertyValue, int propertyType,
309 TableRelation[] relations, InsertRow featureRow )
310 throws TransactionException {
311
312 if ( relations == null || relations.length == 0 ) {
313 // property is stored in feature table
314 featureRow.setColumn( propertyColumn, propertyValue, propertyType, false );
315 } else {
316 // property is stored in related table
317 if ( relations.length > 1 ) {
318 throw new TransactionException( "properties in related tables are not allowed here" );
319 }
320
321 if ( !relations[0].isFromFK() ) {
322 // fk is in property table
323 MappingField[] pkFields = relations[0].getFromFields();
324 MappingField[] fkFields = relations[0].getToFields();
325
326 for ( int i = 0; i < pkFields.length; i++ ) {
327 InsertField pkField = featureRow.getColumn( pkFields[i].getField() );
328 if ( pkField == null ) {
329 String msg = "Missing foreign key " + pkFields[i].getField() + " / " + pkFields[i].getTable();
330 throw new TransactionException( msg );
331 }
332 int pkColumnType = pkField.getSQLType();
333 int fkColumnType = fkFields[i].getType();
334 if ( pkColumnType != fkColumnType ) {
335 String msg = "FK_PK_TYPE_MISMATCH";
336 throw new TransactionException( msg );
337 }
338 InsertRow insertRow = new InsertRow( relations[0].getToTable() );
339 insertRow.linkColumn( fkFields[i].getField(), pkField );
340 insertRow.setColumn( propertyColumn, propertyValue, propertyType, false );
341 this.insertRows.add( insertRow );
342 }
343 } else {
344 // fk is in feature table
345 MappingField[] pkFields = relations[0].getToFields();
346 MappingField[] fkFields = relations[0].getFromFields();
347
348 // generate necessary primary key value
349 InsertField pkField = null;
350 try {
351 Object pk = null;
352 // TODO remove hack!!!
353 if ( relations[0].getIdGenerator() instanceof ParentIDGenerator ) {
354 InsertField field = featureRow.getColumn( "ID" );
355 if ( field == null ) {
356 throw new TransactionException( "No value for ID available!" );
357 }
358 pk = field.getValue();
359 } else {
360 pk = relations[0].getNewPK( this.dsTa );
361 }
362 InsertRow insertRow = findOrCreateRow( relations[0].getToTable(), pkFields[0].getField(), pk );
363 pkField = insertRow.setColumn( pkFields[0].getField(), pk, pkFields[0].getType(), true );
364 insertRow.setColumn( propertyColumn, propertyValue, propertyType, false );
365 } catch ( IdGenerationException e ) {
366 throw new TransactionException( e.getMessage(), e );
367 }
368 featureRow.linkColumn( fkFields[0].getField(), pkField );
369 }
370 }
371 }
372
373 /**
374 * Inserts the given feature property.
375 *
376 * @param pt
377 * @param property
378 * @param featureRow
379 * @throws TransactionException
380 */
381 private void insertProperty( MappedFeaturePropertyType pt, FeatureProperty property, InsertRow featureRow )
382 throws TransactionException {
383
384 // find (concrete) subfeature type for the given property instance
385 MappedFeatureType propertyFeatureType = pt.getFeatureTypeReference().getFeatureType();
386 MappedFeatureType[] substitutions = propertyFeatureType.getConcreteSubstitutions();
387 Feature subFeature = (Feature) property.getValue();
388 MappedFeatureType subFeatureType = null;
389 for ( int i = 0; i < substitutions.length; i++ ) {
390 if ( substitutions[i].getName().equals( subFeature.getName() ) ) {
391 subFeatureType = substitutions[i];
392 break;
393 }
394 }
395 if ( subFeatureType == null ) {
396 String msg = "ERROR_FEATURE_NOT_SUBSTITUTABLE " + propertyFeatureType.getName() + "->"
397 + subFeature.getName();
398 throw new TransactionException( msg );
399 }
400 boolean ftIsAbstract = propertyFeatureType.isAbstract();
401
402 TableRelation[] relations = pt.getTableRelations();
403 if ( relations == null || relations.length < 1 ) {
404 throw new TransactionException( "Invalid feature property definition, feature property "
405 + "mappings must use at least one 'TableRelation' element." );
406 }
407
408 // workaround for links to dummy InsertRows (of already stored features)
409 boolean cutLink = subFeature.getId().startsWith( FeatureIdAssigner.EXISTS_MARKER );
410 InsertRow subFeatureRow = null;
411 if ( cutLink ) {
412 try {
413 Object fidValue = FeatureId.removeFIDPrefix( subFeature.getId().substring( 1 ),
414 subFeatureType.getGMLId() );
415 subFeatureRow = new FeatureRow( subFeatureType.getTable() );
416 // add column value for fid (primary key)
417 String fidColumn = subFeatureType.getGMLId().getIdFields()[0].getField();
418 subFeatureRow.setColumn( fidColumn, fidValue, subFeatureType.getGMLId().getIdFields()[0].getType(),
419 true );
420 } catch ( DatastoreException e ) {
421 throw new TransactionException( e );
422 }
423 } else {
424 // insert sub feature (if it is not already stored)
425 subFeatureRow = insertFeature( subFeature );
426 }
427
428 if ( relations.length == 1 ) {
429
430 if ( relations[0].isFromFK() ) {
431 // fk is in feature table
432 MappingField[] pkFields = relations[0].getToFields();
433 MappingField[] fkFields = relations[0].getFromFields();
434
435 for ( int i = 0; i < pkFields.length; i++ ) {
436 InsertField pkField = subFeatureRow.getColumn( pkFields[i].getField() );
437 if ( pkField == null ) {
438 String msg = "Missing foreign key.";
439 throw new TransactionException( msg );
440 }
441 int pkColumnType = pkField.getSQLType();
442 int fkColumnType = fkFields[i].getType();
443 if ( pkColumnType != fkColumnType ) {
444 String msg = "ERROR_FK_PK_TYPE_MISMATCH";
445 throw new TransactionException( msg );
446 }
447
448 if ( !cutLink ) {
449 featureRow.linkColumn( fkFields[i].getField(), pkField );
450 } else {
451 featureRow.setColumn( fkFields[i].getField(), pkField.getValue(), pkField.getSQLType(), false );
452 }
453
454 }
455
456 if ( ftIsAbstract ) {
457 String typeField = FT_PREFIX + relations[0].getToTable();
458 featureRow.setColumn( typeField, subFeatureType.getName().getLocalName(), Types.VARCHAR, false );
459 }
460 } else {
461 // fk is in subfeature table
462 MappingField[] pkFields = relations[0].getFromFields();
463 MappingField[] fkFields = relations[0].getToFields();
464
465 InsertField pkField = featureRow.getColumn( pkFields[0].getField() );
466
467 if ( pkField == null ) {
468 String msg = "Missing foreign key.";
469 throw new TransactionException( msg );
470 }
471 int pkColumnType = pkField.getSQLType();
472 int fkColumnType = fkFields[0].getType();
473 if ( pkColumnType != fkColumnType ) {
474 String msg = "ERROR_FK_PK_TYPE_MISMATCH";
475 throw new TransactionException( msg );
476 }
477
478 if ( !cutLink ) {
479 subFeatureRow.linkColumn( fkFields[0].getField(), pkField );
480 } else {
481 featureRow.setColumn( fkFields[0].getField(), pkField.getValue(), pkField.getSQLType(), false );
482 }
483 }
484 } else if ( relations.length == 2 ) {
485
486 // insert into join table
487 String joinTable = relations[0].getToTable();
488 MappingField[] leftKeyFields = relations[0].getToFields();
489 MappingField[] rightKeyFields = relations[1].getFromFields();
490
491 InsertRow jtRow = new InsertRow( joinTable );
492 if ( ftIsAbstract ) {
493 jtRow.setColumn( FT_COLUMN, subFeatureType.getName().getLocalName(), Types.VARCHAR, false );
494 }
495
496 if ( !relations[0].isFromFK() ) {
497 // left key field in join table is fk
498 MappingField[] pkFields = relations[0].getFromFields();
499 InsertField pkField = featureRow.getColumn( pkFields[0].getField() );
500 if ( pkField == null ) {
501 throw new TransactionException( "Insertion of feature property using join table failed: "
502 + "no value for join table key column." );
503 }
504 jtRow.linkColumn( leftKeyFields[0].getField(), pkField );
505 } else {
506 // left key field in join table is pk
507 MappingField[] pkFields = relations[0].getToFields();
508 // generate necessary primary key value
509 InsertField pkField = null;
510 try {
511 Object pk = relations[0].getNewPK( this.dsTa );
512 pkField = jtRow.setColumn( pkFields[0].getField(), pk, pkFields[0].getType(), true );
513 } catch ( IdGenerationException e ) {
514 throw new TransactionException( e.getMessage(), e );
515 }
516 featureRow.linkColumn( relations[0].getFromFields()[0].getField(), pkField );
517 }
518
519 if ( relations[1].isFromFK() ) {
520 // right key field in join table is fk
521 MappingField[] pkFields = relations[1].getToFields();
522 InsertField pkField = subFeatureRow.getColumn( pkFields[0].getField() );
523 if ( pkField == null ) {
524 throw new TransactionException( "Insertion of feature property using join table failed: "
525 + "no value for join table key column." );
526 }
527 if ( !cutLink ) {
528 jtRow.linkColumn( rightKeyFields[0].getField(), pkField );
529 } else {
530 jtRow.setColumn( rightKeyFields[0].getField(), pkField.getValue(), pkField.getSQLType(), false );
531 }
532 } else {
533 // right key field in join table is pk
534 MappingField[] pkFields = relations[1].getFromFields();
535 // generate necessary primary key value
536 InsertField pkField = null;
537 try {
538 Object pk = relations[1].getNewPK( this.dsTa );
539 pkField = jtRow.setColumn( pkFields[0].getField(), pk, pkFields[0].getType(), true );
540 } catch ( IdGenerationException e ) {
541 throw new TransactionException( e.getMessage(), e );
542 }
543 if ( !cutLink ) {
544 subFeatureRow.linkColumn( relations[1].getToFields()[0].getField(), pkField );
545 }
546 }
547
548 this.insertRows.add( jtRow );
549 } else {
550 throw new TransactionException( "Insertion of feature properties stored in related tables "
551 + "connected via more than one join table is not supported." );
552 }
553 }
554
555 /**
556 * Checks whether the feature that corresponds to the given FeatureRow is already stored in the database.
557 *
558 * @param featureRow
559 * @return true if the feature exists
560 * @throws TransactionException
561 */
562 private boolean doesFeatureExist( FeatureRow featureRow )
563 throws TransactionException {
564
565 boolean exists = false;
566
567 InsertField pkField = featureRow.getPKColumn();
568
569 SeQuery stmt = null;
570 try {
571 stmt = buildFeatureSelect( pkField.getColumnName(), pkField.getValue(), featureRow.getTable() );
572 stmt.execute();
573 SeRow row = stmt.fetch();
574 if ( null != row ) {
575 exists = true;
576 }
577 row = stmt.fetch();
578 if ( null != row ) {
579 String msg = "ERROR_FEATURE_QUERY_MORE_THAN_ONE_RESULT";
580 LOG.logError( msg );
581 throw new TransactionException( msg );
582 }
583 } catch ( Exception e ) {
584 throw new TransactionException( e );
585 } finally {
586 try {
587 stmt.close();
588 } catch ( Exception e ) {
589 LOG.logDebug( "Error in SDE command", e );
590 }
591 }
592 return exists;
593 }
594
595 /**
596 * Builds a SELECT statement that checks for the existence of a feature with the given id.
597 *
598 * @param fidColumn
599 * @param fidValue
600 * @param table
601 * @return the statement
602 */
603 private SeQuery buildFeatureSelect( String fidColumn, Object fidValue, String table ) {
604 SeQuery query = null;
605 try {
606 SeSqlConstruct constr = new SeSqlConstruct( table, fidColumn + "='" + fidValue.toString() + "'" );
607 String[] columns = new String[1];
608 columns[0] = fidColumn;
609 query = new SeQuery( getConnection().getConnection(), columns, constr );
610 query.setState( getConnection().getState().getId(), new SeObjectId( SeState.SE_NULL_STATE_ID ),
611 SeState.SE_STATE_DIFF_NOCHECK );
612 query.prepareQuery();
613 } catch ( Exception e ) {
614 LOG.logError( "Error building featureSelect", e );
615 }
616 return query;
617 }
618
619 private InsertRow findOrCreateRow( String table, String pkColumn, Object value ) {
620 Iterator<InsertRow> rowIter = this.insertRows.iterator();
621 boolean found = false;
622 InsertRow row = null;
623 while ( rowIter.hasNext() ) {
624 row = rowIter.next();
625 if ( row.getTable().equals( table ) ) {
626 InsertField field = row.getColumn( pkColumn );
627 if ( value.equals( field.getValue() ) ) {
628 found = true;
629 LOG.logDebug( "Found matching row " + row );
630 break;
631 }
632 }
633 }
634 if ( !found ) {
635 row = new InsertRow( table );
636 this.insertRows.add( row );
637 }
638 return row;
639 }
640
641 private String getPropertyValue( FeatureProperty property ) {
642 Object value = property.getValue();
643 StringBuffer sb = new StringBuffer();
644 if ( value instanceof Object[] ) {
645 Object[] objects = (Object[]) value;
646 for ( int i = 0; i < objects.length; i++ ) {
647 sb.append( objects[i] );
648 }
649 } else {
650 sb.append( value );
651 }
652 return sb.toString();
653 }
654
655 /**
656 * Transforms the given <code>List</code> of <code>InsertRows</code> into SQL INSERT statements and executes them
657 * using the underlying JDBC connection.
658 *
659 * @param inserts
660 * @throws TransactionException
661 * if an SQL error occurs
662 */
663 private void executeInserts( List<InsertRow> inserts )
664 throws TransactionException {
665
666 SeInsert stmt = null;
667
668 for ( InsertRow row : inserts ) {
669 if ( row instanceof FeatureRow ) {
670 if ( doesFeatureExist( (FeatureRow) row ) ) {
671 LOG.logDebug( "Skipping feature row. Already present in db." );
672 continue;
673 }
674 }
675 try {
676 stmt = createStatement( row );
677 stmt.execute();
678 } catch ( Exception e ) {
679 String msg = "Error performing insert: " + e.getMessage();
680 LOG.logError( msg, e );
681 throw new TransactionException( msg, e );
682 } finally {
683 if ( stmt != null ) {
684 try {
685 stmt.close();
686 } catch ( Exception e ) {
687 String msg = "Error closing statement: " + e.getMessage();
688 LOG.logError( msg, e );
689 }
690 }
691 }
692 }
693 }
694
695 private SeInsert createStatement( InsertRow row )
696 throws SeException {
697 SeInsert inserter = new SeInsert( conn.getConnection() );
698 inserter.setState( conn.getState().getId(), new SeObjectId( SeState.SE_NULL_STATE_ID ),
699 SeState.SE_STATE_DIFF_NOCHECK );
700 Collection<InsertField> fields = row.getColumns();
701 String[] columns = new String[fields.size()];
702 int i = 0;
703 for ( Iterator<InsertField> iter = fields.iterator(); iter.hasNext(); i++ ) {
704 InsertField field = iter.next();
705 columns[i] = field.getColumnName();
706 }
707 inserter.intoTable( row.getTable(), columns );
708 SeRow insertRow = inserter.getRowToSet();
709 for ( i = 0; i < columns.length; i++ ) {
710 InsertField field = row.getColumn( columns[i] );
711 SDEAdapter.setRowValue( insertRow, i, field.getValue(), SDEAdapter.mapSQL2SDE( field.getSQLType() ) );
712 }
713 return inserter;
714 }
715
716 /**
717 * Merges the given <code>InsertRow</code>s by eliminating rows that have identical content (except for their
718 * primary keys).
719 * <p>
720 * This only applies to non-FeatureRows: there are never two FeatureRows that may be treated as identical, because
721 * unique feature ids have been assigned to them before.
722 *
723 * @see FeatureIdAssigner
724 *
725 * @param insertRows
726 * @return the cleaned up list
727 */
728 private List<InsertRow> mergeInsertRows( List<InsertRow> insertRows ) {
729
730 List<InsertRow> result = new ArrayList<InsertRow>();
731
732 // keys: table names, values: inserts into the table
733 Map<String, Collection<InsertRow>> tableMap = new HashMap<String, Collection<InsertRow>>();
734
735 // build table lookup map
736 Iterator<InsertRow> iter = insertRows.iterator();
737 while ( iter.hasNext() ) {
738 InsertRow insertRow = iter.next();
739 Collection<InsertRow> tableInserts = tableMap.get( insertRow.getTable() );
740 if ( tableInserts == null ) {
741 tableInserts = new ArrayList<InsertRow>();
742 tableMap.put( insertRow.getTable(), tableInserts );
743 }
744 tableInserts.add( insertRow );
745 }
746
747 iter = insertRows.iterator();
748 while ( iter.hasNext() ) {
749 InsertRow insertRow = iter.next();
750 boolean insert = true;
751 if ( !( insertRow instanceof FeatureRow ) ) {
752 Collection<InsertRow> tableInserts = tableMap.get( insertRow.getTable() );
753 Iterator<InsertRow> candidatesIter = tableInserts.iterator();
754 while ( candidatesIter.hasNext() ) {
755 InsertRow candidate = candidatesIter.next();
756 if ( insertRow != candidate ) {
757 if ( compareInsertRows( insertRow, candidate ) ) {
758 LOG.logDebug( "Removing InsertRow: " + insertRow.hashCode() + " " + insertRow
759 + " - duplicate of: " + candidate );
760 replaceInsertRow( insertRow, candidate );
761 insert = false;
762 tableInserts.remove( insertRow );
763 break;
764 }
765 }
766 }
767 }
768 if ( insert ) {
769 result.add( insertRow );
770 }
771 }
772 return result;
773 }
774
775 private boolean compareInsertRows( InsertRow row1, InsertRow row2 ) {
776 Collection<InsertField> fields1 = row1.getColumns();
777 Iterator<InsertField> iter = fields1.iterator();
778 while ( iter.hasNext() ) {
779 InsertField field1 = iter.next();
780 if ( !field1.isPK() ) {
781 InsertField field2 = row2.getColumn( field1.getColumnName() );
782 Object value1 = field1.getValue();
783 Object value2 = null;
784 if ( field2 != null )
785 value2 = field2.getValue();
786 if ( value1 == null ) {
787 if ( value2 == null ) {
788 continue;
789 }
790 return false;
791 }
792 if ( !value1.equals( value2 ) ) {
793 return false;
794 }
795 }
796 }
797 return true;
798 }
799
800 private void replaceInsertRow( InsertRow oldRow, InsertRow newRow ) {
801
802 Collection<InsertField> oldFields = oldRow.getColumns();
803 for ( InsertField field : oldFields ) {
804 InsertField toField = field.getReferencedField();
805 if ( toField != null ) {
806 LOG.logDebug( "Removing reference to field '" + toField + "'" );
807 toField.removeReferencingField( field );
808 }
809 }
810
811 Collection<InsertField> referencingFields = oldRow.getReferencingFields();
812 for ( InsertField fromField : referencingFields ) {
813 LOG.logDebug( "Replacing reference for field '" + fromField + "'" );
814 InsertField field = newRow.getColumn( fromField.getReferencedField().getColumnName() );
815 LOG.logDebug( "" + field );
816 fromField.relinkField( field );
817 }
818 }
819 }