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