0001: //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/trunk/src/org/deegree/io/datastore/sql/transaction/insert/InsertHandler.java $
0002: /*---------------- FILE HEADER ------------------------------------------
0003:
0004: This file is part of deegree.
0005: Copyright (C) 2001-2008 by:
0006: EXSE, Department of Geography, University of Bonn
0007: http://www.giub.uni-bonn.de/deegree/
0008: lat/lon GmbH
0009: http://www.lat-lon.de
0010:
0011: This library is free software; you can redistribute it and/or
0012: modify it under the terms of the GNU Lesser General Public
0013: License as published by the Free Software Foundation; either
0014: version 2.1 of the License, or (at your option) any later version.
0015:
0016: This library is distributed in the hope that it will be useful,
0017: but WITHOUT ANY WARRANTY; without even the implied warranty of
0018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0019: Lesser General Public License for more details.
0020:
0021: You should have received a copy of the GNU Lesser General Public
0022: License along with this library; if not, write to the Free Software
0023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0024:
0025: Contact:
0026:
0027: Andreas Poth
0028: lat/lon GmbH
0029: Aennchenstraße 19
0030: 53177 Bonn
0031: Germany
0032: E-Mail: poth@lat-lon.de
0033:
0034: Prof. Dr. Klaus Greve
0035: Department of Geography
0036: University of Bonn
0037: Meckenheimer Allee 166
0038: 53115 Bonn
0039: Germany
0040: E-Mail: greve@giub.uni-bonn.de
0041:
0042: ---------------------------------------------------------------------------*/
0043: package org.deegree.io.datastore.sql.transaction.insert;
0044:
0045: import java.sql.Connection;
0046: import java.sql.PreparedStatement;
0047: import java.sql.ResultSet;
0048: import java.sql.SQLException;
0049: import java.util.ArrayList;
0050: import java.util.Collection;
0051: import java.util.HashMap;
0052: import java.util.Iterator;
0053: import java.util.List;
0054: import java.util.Map;
0055:
0056: import org.deegree.datatypes.Types;
0057: import org.deegree.datatypes.UnknownTypeException;
0058: import org.deegree.framework.log.ILogger;
0059: import org.deegree.framework.log.LoggerFactory;
0060: import org.deegree.i18n.Messages;
0061: import org.deegree.io.datastore.DatastoreException;
0062: import org.deegree.io.datastore.FeatureId;
0063: import org.deegree.io.datastore.TransactionException;
0064: import org.deegree.io.datastore.idgenerator.FeatureIdAssigner;
0065: import org.deegree.io.datastore.idgenerator.IdGenerationException;
0066: import org.deegree.io.datastore.idgenerator.ParentIDGenerator;
0067: import org.deegree.io.datastore.schema.MappedFeaturePropertyType;
0068: import org.deegree.io.datastore.schema.MappedFeatureType;
0069: import org.deegree.io.datastore.schema.MappedGeometryPropertyType;
0070: import org.deegree.io.datastore.schema.MappedPropertyType;
0071: import org.deegree.io.datastore.schema.MappedSimplePropertyType;
0072: import org.deegree.io.datastore.schema.TableRelation;
0073: import org.deegree.io.datastore.schema.content.MappingField;
0074: import org.deegree.io.datastore.schema.content.MappingGeometryField;
0075: import org.deegree.io.datastore.schema.content.SimpleContent;
0076: import org.deegree.io.datastore.sql.AbstractRequestHandler;
0077: import org.deegree.io.datastore.sql.StatementBuffer;
0078: import org.deegree.io.datastore.sql.TableAliasGenerator;
0079: import org.deegree.io.datastore.sql.transaction.SQLTransaction;
0080: import org.deegree.model.feature.Feature;
0081: import org.deegree.model.feature.FeatureProperty;
0082: import org.deegree.model.feature.schema.FeaturePropertyType;
0083: import org.deegree.model.feature.schema.GeometryPropertyType;
0084: import org.deegree.model.feature.schema.SimplePropertyType;
0085: import org.deegree.model.spatialschema.Geometry;
0086: import org.deegree.ogcwebservices.wfs.operation.transaction.Insert;
0087: import org.deegree.ogcwebservices.wfs.operation.transaction.Transaction;
0088:
0089: /**
0090: * Handler for {@link Insert} operations (usually contained in {@link Transaction} requests).
0091: *
0092: * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
0093: * @author last edited by: $Author: aschmitz $
0094: *
0095: * @version $Revision: 10506 $, $Date: 2008-03-06 08:50:33 -0800 (Thu, 06 Mar 2008) $
0096: */
0097: public class InsertHandler extends AbstractRequestHandler {
0098:
0099: private static final ILogger LOG = LoggerFactory
0100: .getLogger(InsertHandler.class);
0101:
0102: // features that are currently being processed
0103: private Map<FeatureId, FeatureRow> featuresInInsertion = new HashMap<FeatureId, FeatureRow>();
0104:
0105: // contains only property rows and join table rows (but no feature rows)
0106: private List<InsertRow> insertRows = new ArrayList<InsertRow>();
0107:
0108: private SQLTransaction dsTa;
0109:
0110: /**
0111: * Creates a new <code>InsertHandler</code> from the given parameters.
0112: *
0113: * @param dsTa
0114: * @param aliasGenerator
0115: * @param conn
0116: */
0117: public InsertHandler(SQLTransaction dsTa,
0118: TableAliasGenerator aliasGenerator, Connection conn) {
0119: super (dsTa.getDatastore(), aliasGenerator, conn);
0120: this .dsTa = dsTa;
0121: }
0122:
0123: /**
0124: * Inserts the given feature instance into the datastore.
0125: *
0126: * @param features
0127: * (which have a MappedFeatureType as feature type)
0128: * @return feature ids of inserted (root) feature instances
0129: * @throws DatastoreException
0130: * if the insert could not be performed
0131: */
0132: public List<FeatureId> performInsert(List<Feature> features)
0133: throws DatastoreException {
0134:
0135: List<FeatureId> fids = new ArrayList<FeatureId>();
0136: for (int i = 0; i < features.size(); i++) {
0137: Feature feature = features.get(i);
0138:
0139: MappedFeatureType ft = (MappedFeatureType) feature
0140: .getFeatureType();
0141: if (feature.getId().startsWith(
0142: FeatureIdAssigner.EXISTS_MARKER)) {
0143: String msg = Messages.getMessage(
0144: "DATASTORE_FEATURE_EXISTS", feature.getName(),
0145: feature.getId().substring(1));
0146: throw new TransactionException(msg);
0147: }
0148: LOG.logDebug("Inserting root feature '" + feature.getId()
0149: + "'...");
0150: insertFeature(feature);
0151: FeatureId fid = new FeatureId(ft, feature.getId());
0152: fids.add(fid);
0153:
0154: }
0155:
0156: // merge inserts rows that are identical (except their pks)
0157: this .insertRows = mergeInsertRows(this .insertRows);
0158:
0159: // add featureRows to insertRows
0160: Iterator<FeatureRow> iter = this .featuresInInsertion.values()
0161: .iterator();
0162: while (iter.hasNext()) {
0163: this .insertRows.add(iter.next());
0164: }
0165:
0166: // check for cyclic fk constraints
0167: Collection<InsertRow> cycle = InsertRow
0168: .findCycle(this .insertRows);
0169: if (cycle != null) {
0170: Iterator<InsertRow> cycleIter = cycle.iterator();
0171: StringBuffer sb = new StringBuffer();
0172: while (cycleIter.hasNext()) {
0173: sb.append(cycleIter.next());
0174: if (cycle.iterator().hasNext()) {
0175: sb.append(" -> ");
0176: }
0177: }
0178: String msg = Messages.getMessage("DATASTORE_FK_CYCLE", sb
0179: .toString());
0180: throw new TransactionException(msg);
0181: }
0182:
0183: // sort the insert rows topologically
0184: List<InsertRow> sortedInserts = InsertRow
0185: .sortInsertRows(this .insertRows);
0186:
0187: if (LOG.getLevel() == ILogger.LOG_DEBUG) {
0188: LOG.logDebug(sortedInserts.size()
0189: + " rows to be inserted: ");
0190: for (InsertRow row : sortedInserts) {
0191: LOG.logDebug(row.toString());
0192: }
0193: }
0194:
0195: executeInserts(sortedInserts);
0196: return fids;
0197: }
0198:
0199: /**
0200: * Builds the <code>InsertRows</code> that are necessary to insert the given feature instance
0201: * (including all properties + subfeatures).
0202: *
0203: * @param feature
0204: * @return InsertRows that are necessary to insert the given feature instance
0205: * @throws TransactionException
0206: */
0207: private FeatureRow insertFeature(Feature feature)
0208: throws TransactionException {
0209:
0210: MappedFeatureType ft = (MappedFeatureType) feature
0211: .getFeatureType();
0212: if (!ft.isInsertable()) {
0213: String msg = Messages.getMessage(
0214: "DATASTORE_FT_NOT_INSERTABLE", ft.getName());
0215: throw new TransactionException(msg);
0216: }
0217:
0218: LOG
0219: .logDebug("Creating InsertRow for feature with type '"
0220: + ft.getName() + "' and id: '"
0221: + feature.getId() + "'.");
0222:
0223: // extract feature id column value
0224: MappingField[] fidFields = ft.getGMLId().getIdFields();
0225: if (fidFields.length > 1) {
0226: throw new TransactionException(
0227: "Insertion of features with compound feature "
0228: + "ids is not supported.");
0229: }
0230: FeatureId fid = null;
0231: try {
0232: fid = new FeatureId(ft, feature.getId());
0233: } catch (IdGenerationException e) {
0234: throw new TransactionException(e.getMessage(), e);
0235: }
0236:
0237: // check if the feature id is already being inserted (happens for cyclic features)
0238: FeatureRow insertRow = this .featuresInInsertion.get(fid);
0239: if (insertRow != null) {
0240: return insertRow;
0241: }
0242:
0243: insertRow = new FeatureRow(ft.getTable());
0244: this .featuresInInsertion.put(fid, insertRow);
0245:
0246: // add column value for fid (primary key)
0247: String fidColumn = fidFields[0].getField();
0248: insertRow.setColumn(fidColumn, fid.getValue(0), ft.getGMLId()
0249: .getIdFields()[0].getType(), true);
0250:
0251: // process properties
0252: FeatureProperty[] properties = feature.getProperties();
0253: for (int i = 0; i < properties.length; i++) {
0254: FeatureProperty property = properties[i];
0255: MappedPropertyType propertyType = (MappedPropertyType) ft
0256: .getProperty(property.getName());
0257: if (propertyType == null) {
0258: String msg = Messages.getMessage(
0259: "DATASTORE_PROPERTY_TYPE_NOT_KNOWN", property
0260: .getName());
0261: LOG.logDebug(msg);
0262: throw new TransactionException(msg);
0263: }
0264: insertProperty(property, propertyType, insertRow);
0265: }
0266: return insertRow;
0267: }
0268:
0269: /**
0270: * Builds the <code>InsertRow</code>s that are necessary to insert the given property
0271: * instance (including all it's subfeatures).
0272: *
0273: * @param property
0274: * property instance to be inserted
0275: * @param propertyType
0276: * property type of the property
0277: * @param featureRow
0278: * table row of the parent feature instance
0279: * @throws TransactionException
0280: */
0281: private void insertProperty(FeatureProperty property,
0282: MappedPropertyType propertyType, InsertRow featureRow)
0283: throws TransactionException {
0284:
0285: if (propertyType instanceof SimplePropertyType) {
0286: LOG.logDebug("- Simple property '" + propertyType.getName()
0287: + "', value='" + getPropertyValue(property) + "'.");
0288: insertProperty((MappedSimplePropertyType) propertyType,
0289: property, featureRow);
0290: } else if (propertyType instanceof GeometryPropertyType) {
0291: LOG.logDebug("- Geometry property: '"
0292: + propertyType.getName() + "'");
0293: insertProperty((MappedGeometryPropertyType) propertyType,
0294: property, featureRow);
0295: } else if (propertyType instanceof FeaturePropertyType) {
0296: LOG.logDebug("- Feature property: '"
0297: + propertyType.getName() + "'");
0298: insertProperty((MappedFeaturePropertyType) propertyType,
0299: property, featureRow);
0300: } else {
0301: throw new TransactionException("Unhandled property type '"
0302: + propertyType.getClass().getName() + "'.");
0303: }
0304: }
0305:
0306: /**
0307: * Inserts the given simple property (stored in feature table or in related table).
0308: *
0309: * @param pt
0310: * @param property
0311: * @param featureRow
0312: * @throws TransactionException
0313: */
0314: private void insertProperty(MappedSimplePropertyType pt,
0315: FeatureProperty property, InsertRow featureRow)
0316: throws TransactionException {
0317:
0318: SimpleContent content = pt.getContent();
0319: if (content.isUpdateable()) {
0320: if (content instanceof MappingField) {
0321: MappingField mf = (MappingField) content;
0322: String propertyColumn = mf.getField();
0323: Object propertyValue = property.getValue();
0324: int propertyType = mf.getType();
0325: TableRelation[] relations = pt.getTableRelations();
0326: insertProperty(propertyColumn, propertyValue,
0327: propertyType, relations, featureRow);
0328: }
0329: }
0330: }
0331:
0332: /**
0333: * Inserts the given geometry property (stored in feature table or in related table).
0334: *
0335: * @param pt
0336: * @param property
0337: * @param featureRow
0338: * @throws TransactionException
0339: */
0340: private void insertProperty(MappedGeometryPropertyType pt,
0341: FeatureProperty property, InsertRow featureRow)
0342: throws TransactionException {
0343:
0344: String propertyColumn = pt.getMappingField().getField();
0345: MappingGeometryField dbField = pt.getMappingField();
0346: Geometry deegreeGeometry = (Geometry) property.getValue();
0347: Object dbGeometry;
0348:
0349: int createSrsCode = dbField.getSRS();
0350: int targetSrsCode = -1;
0351: if (deegreeGeometry.getCoordinateSystem() == null) {
0352: LOG
0353: .logDebug("No SRS information for geometry available. Assuming '"
0354: + pt.getSRS() + "'.");
0355: } else if (!pt.getSRS().toString().equals(
0356: deegreeGeometry.getCoordinateSystem().getIdentifier())) {
0357: String msg = "Insert-Transformation: geometry srs: "
0358: + deegreeGeometry.getCoordinateSystem()
0359: .getIdentifier() + " -> property srs: "
0360: + pt.getSRS();
0361: LOG.logDebug(msg);
0362: if (createSrsCode == -1) {
0363: msg = Messages.getMessage(
0364: "DATASTORE_SRS_NOT_SPECIFIED", pt.getName(),
0365: deegreeGeometry.getCoordinateSystem(), pt
0366: .getSRS());
0367: throw new TransactionException(msg);
0368: }
0369: try {
0370: createSrsCode = datastore
0371: .getNativeSRSCode(deegreeGeometry
0372: .getCoordinateSystem().getIdentifier());
0373: } catch (DatastoreException e) {
0374: throw new TransactionException(e.getMessage(), e);
0375: }
0376: targetSrsCode = dbField.getSRS();
0377: }
0378:
0379: try {
0380: dbGeometry = this .datastore.convertDeegreeToDBGeometry(
0381: deegreeGeometry, createSrsCode, this .conn);
0382: } catch (DatastoreException e) {
0383: throw new TransactionException(e.getMessage(), e);
0384: }
0385:
0386: int propertyType = pt.getMappingField().getType();
0387:
0388: // TODO remove this Oracle hack
0389: if (this .datastore.getClass().getName().contains(
0390: "OracleDatastore")) {
0391: propertyType = Types.STRUCT;
0392: }
0393:
0394: TableRelation[] relations = pt.getTableRelations();
0395: insertProperty(propertyColumn, dbGeometry, propertyType,
0396: relations, featureRow, targetSrsCode);
0397: }
0398:
0399: /**
0400: * Inserts the given simple property (stored in feature table or in related table).
0401: *
0402: * @param propertyColumn
0403: * @param propertyValue
0404: * @param propertyType
0405: * @param featureRow
0406: * @throws TransactionException
0407: */
0408: private void insertProperty(String propertyColumn,
0409: Object propertyValue, int propertyType,
0410: TableRelation[] relations, InsertRow featureRow)
0411: throws TransactionException {
0412:
0413: if (relations == null || relations.length == 0) {
0414: // property is stored in feature table
0415: featureRow.setColumn(propertyColumn, propertyValue,
0416: propertyType, false);
0417: } else {
0418: // property is stored in related table
0419: if (relations.length > 1) {
0420: throw new TransactionException(Messages
0421: .getMessage("DATASTORE_SIMPLE_PROPERTY_JOIN"));
0422: }
0423:
0424: if (!relations[0].isFromFK()) {
0425: // fk is in property table
0426: MappingField[] pkFields = relations[0].getFromFields();
0427: MappingField[] fkFields = relations[0].getToFields();
0428:
0429: for (int i = 0; i < pkFields.length; i++) {
0430: InsertField pkField = featureRow
0431: .getColumn(pkFields[i].getField());
0432: if (pkField == null) {
0433: String msg = Messages.getMessage(
0434: "DATASTORE_NO_FK_VALUE", pkField
0435: .getColumnName(), pkFields[i]
0436: .getTable());
0437: throw new TransactionException(msg);
0438: }
0439: int pkColumnType = pkField.getSQLType();
0440: int fkColumnType = fkFields[i].getType();
0441: if (pkColumnType != fkColumnType) {
0442: String fkType = "" + fkColumnType;
0443: String pkType = "" + pkColumnType;
0444: try {
0445: fkType = Types
0446: .getTypeNameForSQLTypeCode(fkColumnType);
0447: pkType = Types
0448: .getTypeNameForSQLTypeCode(pkColumnType);
0449: } catch (UnknownTypeException e) {
0450: LOG.logError(e.getMessage(), e);
0451: }
0452: Object[] params = new Object[] {
0453: relations[0].getToTable(),
0454: fkFields[i].getField(), fkType,
0455: featureRow.getTable(),
0456: pkFields[i].getField(), pkType };
0457: String msg = Messages
0458: .getMessage(
0459: "DATASTORE_FK_PK_TYPE_MISMATCH",
0460: params);
0461: throw new TransactionException(msg);
0462: }
0463: InsertRow insertRow = new InsertRow(relations[0]
0464: .getToTable());
0465: insertRow.linkColumn(fkFields[i].getField(),
0466: pkField);
0467: insertRow.setColumn(propertyColumn, propertyValue,
0468: propertyType, false);
0469: this .insertRows.add(insertRow);
0470: }
0471: } else {
0472: // fk is in feature table
0473: MappingField[] pkFields = relations[0].getToFields();
0474: MappingField[] fkFields = relations[0].getFromFields();
0475:
0476: // generate necessary primary key value
0477: InsertField pkField = null;
0478: try {
0479: Object pk = null;
0480: // TODO remove hack!!!
0481: if (relations[0].getIdGenerator() instanceof ParentIDGenerator) {
0482: InsertField field = featureRow.getColumn("ID");
0483: if (field == null) {
0484: throw new TransactionException(
0485: "No value for ID available!");
0486: }
0487: pk = field.getValue();
0488: } else {
0489: pk = relations[0].getNewPK(this .dsTa);
0490: }
0491: InsertRow insertRow = findOrCreateRow(relations[0]
0492: .getToTable(), pkFields[0].getField(), pk);
0493: pkField = insertRow.setColumn(pkFields[0]
0494: .getField(), pk, pkFields[0].getType(),
0495: true);
0496: insertRow.setColumn(propertyColumn, propertyValue,
0497: propertyType, false);
0498: } catch (IdGenerationException e) {
0499: throw new TransactionException(e.getMessage(), e);
0500: }
0501: featureRow.linkColumn(fkFields[0].getField(), pkField);
0502: }
0503: }
0504: }
0505:
0506: /**
0507: * Inserts the given geometry property (stored in feature table or in related table).
0508: *
0509: * @param propertyColumn
0510: * @param propertyValue
0511: * @param propertyType
0512: * @param featureRow
0513: * @throws TransactionException
0514: */
0515: private void insertProperty(String propertyColumn,
0516: Object propertyValue, int propertyType,
0517: TableRelation[] relations, InsertRow featureRow,
0518: int targetSrsCode) throws TransactionException {
0519:
0520: if (relations == null || relations.length == 0) {
0521: // property is stored in feature table
0522: featureRow.setGeometryColumn(propertyColumn, propertyValue,
0523: propertyType, false, targetSrsCode);
0524: } else {
0525: // property is stored in related table
0526: if (relations.length > 1) {
0527: throw new TransactionException(Messages
0528: .getMessage("DATASTORE_SIMPLE_PROPERTY_JOIN"));
0529: }
0530:
0531: if (!relations[0].isFromFK()) {
0532: // fk is in property table
0533: MappingField[] pkFields = relations[0].getFromFields();
0534: MappingField[] fkFields = relations[0].getToFields();
0535:
0536: for (int i = 0; i < pkFields.length; i++) {
0537: InsertField pkField = featureRow
0538: .getColumn(pkFields[i].getField());
0539: if (pkField == null) {
0540: String msg = Messages.getMessage(
0541: "DATASTORE_NO_FK_VALUE", pkField
0542: .getColumnName(), pkFields[i]
0543: .getTable());
0544: throw new TransactionException(msg);
0545: }
0546: int pkColumnType = pkField.getSQLType();
0547: int fkColumnType = fkFields[i].getType();
0548: if (pkColumnType != fkColumnType) {
0549: String fkType = "" + fkColumnType;
0550: String pkType = "" + pkColumnType;
0551: try {
0552: fkType = Types
0553: .getTypeNameForSQLTypeCode(fkColumnType);
0554: pkType = Types
0555: .getTypeNameForSQLTypeCode(pkColumnType);
0556: } catch (UnknownTypeException e) {
0557: LOG.logError(e.getMessage(), e);
0558: }
0559: Object[] params = new Object[] {
0560: relations[0].getToTable(),
0561: fkFields[i].getField(), fkType,
0562: featureRow.getTable(),
0563: pkFields[i].getField(), pkType };
0564: String msg = Messages
0565: .getMessage(
0566: "DATASTORE_FK_PK_TYPE_MISMATCH",
0567: params);
0568: throw new TransactionException(msg);
0569: }
0570: InsertRow insertRow = new InsertRow(relations[0]
0571: .getToTable());
0572: insertRow.linkColumn(fkFields[i].getField(),
0573: pkField);
0574: insertRow.setGeometryColumn(propertyColumn,
0575: propertyValue, propertyType, false,
0576: targetSrsCode);
0577: this .insertRows.add(insertRow);
0578: }
0579: } else {
0580: // fk is in feature table
0581: MappingField[] pkFields = relations[0].getToFields();
0582: MappingField[] fkFields = relations[0].getFromFields();
0583:
0584: // generate necessary primary key value
0585: InsertField pkField = null;
0586: try {
0587: Object pk = null;
0588: // TODO remove hack!!!
0589: if (relations[0].getIdGenerator() instanceof ParentIDGenerator) {
0590: InsertField field = featureRow.getColumn("ID");
0591: if (field == null) {
0592: throw new TransactionException(
0593: "No value for ID available!");
0594: }
0595: pk = field.getValue();
0596: } else {
0597: pk = relations[0].getNewPK(this .dsTa);
0598: }
0599: InsertRow insertRow = findOrCreateRow(relations[0]
0600: .getToTable(), pkFields[0].getField(), pk);
0601: pkField = insertRow.setColumn(pkFields[0]
0602: .getField(), pk, pkFields[0].getType(),
0603: true);
0604: insertRow.setGeometryColumn(propertyColumn,
0605: propertyValue, propertyType, false,
0606: targetSrsCode);
0607: } catch (IdGenerationException e) {
0608: throw new TransactionException(e.getMessage(), e);
0609: }
0610: featureRow.linkColumn(fkFields[0].getField(), pkField);
0611: }
0612: }
0613: }
0614:
0615: /**
0616: * Inserts the given feature property.
0617: *
0618: * @param pt
0619: * @param property
0620: * @param featureRow
0621: * @throws TransactionException
0622: */
0623: private void insertProperty(MappedFeaturePropertyType pt,
0624: FeatureProperty property, InsertRow featureRow)
0625: throws TransactionException {
0626:
0627: // find (concrete) subfeature type for the given property instance
0628: MappedFeatureType propertyFeatureType = pt
0629: .getFeatureTypeReference().getFeatureType();
0630: MappedFeatureType[] substitutions = propertyFeatureType
0631: .getConcreteSubstitutions();
0632: Feature subFeature = (Feature) property.getValue();
0633: MappedFeatureType subFeatureType = null;
0634: for (int i = 0; i < substitutions.length; i++) {
0635: if (substitutions[i].getName().equals(subFeature.getName())) {
0636: subFeatureType = substitutions[i];
0637: break;
0638: }
0639: }
0640: if (subFeatureType == null) {
0641: String msg = Messages
0642: .getMessage("DATASTORE_FEATURE_NOT_SUBSTITUTABLE",
0643: propertyFeatureType.getName(), subFeature
0644: .getName());
0645: throw new TransactionException(msg);
0646: }
0647: boolean needsDisambiguation = propertyFeatureType
0648: .hasSeveralImplementations();
0649:
0650: TableRelation[] relations = pt.getTableRelations();
0651: if (relations == null || relations.length < 1) {
0652: throw new TransactionException(
0653: "Invalid feature property definition, feature property "
0654: + "mappings must use at least one 'TableRelation' element.");
0655: }
0656:
0657: // workaround for links to dummy InsertRows (of already stored features)
0658: boolean cutLink = subFeature.getId().startsWith(
0659: FeatureIdAssigner.EXISTS_MARKER);
0660: InsertRow subFeatureRow = null;
0661: if (cutLink) {
0662: try {
0663: Object fidValue = FeatureId.removeFIDPrefix(subFeature
0664: .getId().substring(1), subFeatureType
0665: .getGMLId());
0666: subFeatureRow = new FeatureRow(subFeatureType
0667: .getTable());
0668: // add column value for fid (primary key)
0669: String fidColumn = subFeatureType.getGMLId()
0670: .getIdFields()[0].getField();
0671: subFeatureRow.setColumn(fidColumn, fidValue,
0672: subFeatureType.getGMLId().getIdFields()[0]
0673: .getType(), true);
0674: } catch (DatastoreException e) {
0675: throw new TransactionException(e);
0676: }
0677: } else {
0678: // insert sub feature (if it is not already stored)
0679: subFeatureRow = insertFeature(subFeature);
0680: }
0681:
0682: if (relations.length == 1) {
0683: if (relations[0].isFromFK()) {
0684: // fk is in feature table
0685: MappingField[] pkFields = relations[0].getToFields();
0686: MappingField[] fkFields = relations[0].getFromFields();
0687:
0688: for (int i = 0; i < pkFields.length; i++) {
0689: InsertField pkField = subFeatureRow
0690: .getColumn(pkFields[i].getField());
0691: if (pkField == null) {
0692: String msg = Messages.getMessage(
0693: "DATASTORE_NO_FK_VALUE", pkField
0694: .getColumnName(), pkField
0695: .getTable());
0696: throw new TransactionException(msg);
0697: }
0698: int pkColumnType = pkField.getSQLType();
0699: int fkColumnType = fkFields[i].getType();
0700: if (pkColumnType != fkColumnType) {
0701: String fkType = "" + fkColumnType;
0702: String pkType = "" + pkColumnType;
0703: try {
0704: fkType = Types
0705: .getTypeNameForSQLTypeCode(fkColumnType);
0706: pkType = Types
0707: .getTypeNameForSQLTypeCode(pkColumnType);
0708: } catch (UnknownTypeException e) {
0709: LOG.logError(e.getMessage(), e);
0710: }
0711: Object[] params = new Object[] {
0712: featureRow.getTable(),
0713: fkFields[i].getField(), fkType,
0714: subFeatureRow.getTable(),
0715: pkFields[i].getField(), pkType };
0716: String msg = Messages
0717: .getMessage(
0718: "DATASTORE_FK_PK_TYPE_MISMATCH",
0719: params);
0720: throw new TransactionException(msg);
0721: }
0722:
0723: if (!cutLink) {
0724: featureRow.linkColumn(fkFields[i].getField(),
0725: pkField);
0726: } else {
0727: featureRow.setColumn(fkFields[i].getField(),
0728: pkField.getValue(), pkField
0729: .getSQLType(), false);
0730: }
0731: }
0732:
0733: if (needsDisambiguation) {
0734: String typeField = FT_PREFIX
0735: + relations[0].getFromFields()[0]
0736: .getField();
0737: featureRow.setColumn(typeField, subFeatureType
0738: .getName().getLocalName(), Types.VARCHAR,
0739: false);
0740: }
0741: } else {
0742: // fk is in subfeature table
0743: MappingField[] pkFields = relations[0].getFromFields();
0744: MappingField[] fkFields = relations[0].getToFields();
0745:
0746: if (pkFields[0] != null) {
0747: LOG.logDebug("Getting column "
0748: + pkFields[0].getField() + "from table: "
0749: + pkFields[0].getTable()
0750: + " of the featureRow: "
0751: + featureRow.getTable());
0752: }
0753:
0754: InsertField pkField = featureRow.getColumn(pkFields[0]
0755: .getField());
0756:
0757: if (pkField == null) {
0758: String msg = null;
0759:
0760: if (pkFields[0] != null) {
0761: msg = Messages.getMessage(
0762: "DATASTORE_NO_FK_VALUE", pkFields[0]
0763: .getField(), pkFields[0]
0764: .getTable());
0765: } else {
0766: if (relations[0] != null) {
0767: msg = Messages
0768: .getMessage(
0769: "DATASTORE_NO_FK_VALUE",
0770: "unknown primary keys in 'from'-fields",
0771: relations[0].getFromTable());
0772: } else {
0773: msg = Messages
0774: .getMessage(
0775: "DATASTORE_NO_FK_VALUE",
0776: "unknown primary keys in 'from'-fields",
0777: "unknown 'from'-table");
0778: }
0779: }
0780:
0781: throw new TransactionException(msg);
0782: }
0783: int pkColumnType = pkField.getSQLType();
0784: int fkColumnType = fkFields[0].getType();
0785: if (pkColumnType != fkColumnType) {
0786: String fkType = "" + fkColumnType;
0787: String pkType = "" + pkColumnType;
0788: try {
0789: fkType = Types
0790: .getTypeNameForSQLTypeCode(fkColumnType);
0791: pkType = Types
0792: .getTypeNameForSQLTypeCode(pkColumnType);
0793: } catch (UnknownTypeException e) {
0794: LOG.logError(e.getMessage(), e);
0795: }
0796: Object[] params = new Object[] {
0797: subFeatureRow.getTable(),
0798: fkFields[0].getField(), fkType,
0799: featureRow.getTable(),
0800: pkField.getColumnName(), pkType };
0801: String msg = Messages.getMessage(
0802: "DATASTORE_FK_PK_TYPE_MISMATCH", params);
0803: throw new TransactionException(msg);
0804: }
0805:
0806: if (!cutLink) {
0807: subFeatureRow.linkColumn(fkFields[0].getField(),
0808: pkField);
0809: } else {
0810: subFeatureRow.setColumn(fkFields[0].getField(),
0811: pkField.getValue(), pkField.getSQLType(),
0812: false);
0813: }
0814: }
0815: } else if (relations.length == 2) {
0816:
0817: // insert into join table
0818: String joinTable = relations[0].getToTable();
0819: MappingField[] leftKeyFields = relations[0].getToFields();
0820: MappingField[] rightKeyFields = relations[1]
0821: .getFromFields();
0822:
0823: InsertRow jtRow = new InsertRow(joinTable);
0824: if (needsDisambiguation) {
0825: jtRow.setColumn(FT_COLUMN, subFeatureType.getName()
0826: .getLocalName(), Types.VARCHAR, false);
0827: }
0828:
0829: if (!relations[0].isFromFK()) {
0830: // left key field in join table is fk
0831: MappingField[] pkFields = relations[0].getFromFields();
0832: InsertField pkField = featureRow.getColumn(pkFields[0]
0833: .getField());
0834: if (pkField == null) {
0835: String columnName = null;
0836: if (pkFields[0] != null) {
0837: columnName = pkFields[0].getField();
0838: } else {
0839: columnName = "unknown primary keys in 'from'-fields";
0840: }
0841: throw new TransactionException(
0842: "Insertion of feature property using join table failed: "
0843: + "no value for join table key column '"
0844: + columnName + "'.");
0845: }
0846: jtRow.linkColumn(leftKeyFields[0].getField(), pkField);
0847: } else {
0848: // left key field in join table is pk
0849: MappingField[] pkFields = relations[0].getToFields();
0850: // generate necessary primary key value
0851: InsertField pkField = null;
0852: try {
0853: Object pk = relations[0].getNewPK(this .dsTa);
0854: pkField = jtRow.setColumn(pkFields[0].getField(),
0855: pk, pkFields[0].getType(), true);
0856: } catch (IdGenerationException e) {
0857: throw new TransactionException(e.getMessage(), e);
0858: }
0859: featureRow.linkColumn(relations[0].getFromFields()[0]
0860: .getField(), pkField);
0861: }
0862:
0863: if (relations[1].isFromFK()) {
0864: // right key field in join table is fk
0865: MappingField[] pkFields = relations[1].getToFields();
0866: InsertField pkField = subFeatureRow
0867: .getColumn(pkFields[0].getField());
0868: if (pkField == null) {
0869: throw new TransactionException(
0870: "Insertion of feature property using join table failed: "
0871: + "no value for join table key column '"
0872: + pkField.getColumnName() + "'.");
0873: }
0874: if (!cutLink) {
0875: jtRow.linkColumn(rightKeyFields[0].getField(),
0876: pkField);
0877: } else {
0878: jtRow.setColumn(rightKeyFields[0].getField(),
0879: pkField.getValue(), pkField.getSQLType(),
0880: false);
0881: }
0882: } else {
0883: // right key field in join table is pk
0884: MappingField[] pkFields = relations[1].getFromFields();
0885: // generate necessary primary key value
0886: InsertField pkField = null;
0887: try {
0888: Object pk = relations[1].getNewPK(this .dsTa);
0889: pkField = jtRow.setColumn(pkFields[0].getField(),
0890: pk, pkFields[0].getType(), true);
0891: } catch (IdGenerationException e) {
0892: throw new TransactionException(e.getMessage(), e);
0893: }
0894: if (!cutLink) {
0895: subFeatureRow.linkColumn(
0896: relations[1].getToFields()[0].getField(),
0897: pkField);
0898: }
0899: }
0900: this .insertRows.add(jtRow);
0901: } else {
0902: throw new TransactionException(
0903: "Insertion of feature properties stored in related tables "
0904: + "connected via more than one join table is not supported.");
0905: }
0906: }
0907:
0908: /**
0909: * Checks whether the feature that corresponds to the given FeatureRow is already stored in the
0910: * database.
0911: *
0912: * @param featureRow
0913: * @return true, if feature is already stored, false otherwise
0914: * @throws DatastoreException
0915: */
0916: private boolean doesFeatureExist(FeatureRow featureRow)
0917: throws DatastoreException {
0918:
0919: boolean exists = false;
0920:
0921: InsertField pkField = featureRow.getPKColumn();
0922:
0923: StatementBuffer query = buildFeatureSelect(pkField
0924: .getColumnName(), pkField.getSQLType(), pkField
0925: .getValue(), featureRow.getTable());
0926: LOG.logDebug("Feature existence query: '" + query + "'");
0927:
0928: PreparedStatement stmt = null;
0929: ResultSet rs = null;
0930: try {
0931: stmt = this .datastore.prepareStatement(this .conn, query);
0932: rs = stmt.executeQuery();
0933: if (rs.next()) {
0934: exists = true;
0935: }
0936: if (rs.next()) {
0937: String msg = Messages.getMessage(
0938: "DATASTORE_FEATURE_QUERY_MORE_THAN_ONE_RESULT",
0939: query.getQueryString());
0940: LOG.logError(msg);
0941: throw new TransactionException(msg);
0942: }
0943: } catch (SQLException e) {
0944: throw new TransactionException(e);
0945: } finally {
0946: try {
0947: if (rs != null) {
0948: rs.close();
0949: }
0950: } catch (SQLException e) {
0951: throw new TransactionException(e);
0952: } finally {
0953: if (stmt != null) {
0954: try {
0955: stmt.close();
0956: } catch (SQLException e) {
0957: throw new TransactionException(e);
0958: }
0959: }
0960: }
0961: }
0962: return exists;
0963: }
0964:
0965: /**
0966: * Builds a SELECT statement that checks for the existence of a feature with the given id.
0967: *
0968: * @param fidColumn
0969: * @param typeCode
0970: * @param fidValue
0971: * @param table
0972: * @return the statement
0973: */
0974: private StatementBuffer buildFeatureSelect(String fidColumn,
0975: int typeCode, Object fidValue, String table) {
0976:
0977: StatementBuffer query = new StatementBuffer();
0978: query.append("SELECT * FROM ");
0979: query.append(table);
0980: query.append(" WHERE ");
0981:
0982: // append feature id constraints
0983: query.append(fidColumn);
0984: query.append("=?");
0985: query.addArgument(fidValue, typeCode);
0986: return query;
0987: }
0988:
0989: private InsertRow findOrCreateRow(String table, String pkColumn,
0990: Object value) {
0991: Iterator<InsertRow> rowIter = this .insertRows.iterator();
0992: boolean found = false;
0993: InsertRow row = null;
0994: while (rowIter.hasNext()) {
0995: row = rowIter.next();
0996: if (row.getTable().equals(table)) {
0997: InsertField field = row.getColumn(pkColumn);
0998: if (value.equals(field.getValue())) {
0999: found = true;
1000: LOG.logDebug("Found matching row " + row);
1001: break;
1002: }
1003: }
1004: }
1005: if (!found) {
1006: row = new InsertRow(table);
1007: this .insertRows.add(row);
1008: }
1009: return row;
1010: }
1011:
1012: private String getPropertyValue(FeatureProperty property) {
1013: Object value = property.getValue();
1014: StringBuffer sb = new StringBuffer();
1015: if (value instanceof Object[]) {
1016: Object[] objects = (Object[]) value;
1017: for (int i = 0; i < objects.length; i++) {
1018: sb.append(objects[i]);
1019: }
1020: } else {
1021: sb.append(value);
1022: }
1023: return sb.toString();
1024: }
1025:
1026: /**
1027: * Transforms the given <code>List</code> of <code>InsertRows</code> into SQL INSERT
1028: * statements and executes them using the underlying JDBC connection.
1029: *
1030: * @param inserts
1031: * @throws DatastoreException
1032: */
1033: private void executeInserts(List<InsertRow> inserts)
1034: throws DatastoreException {
1035:
1036: PreparedStatement stmt = null;
1037:
1038: for (InsertRow row : inserts) {
1039: if (row instanceof FeatureRow) {
1040: if (doesFeatureExist((FeatureRow) row)) {
1041: LOG
1042: .logDebug("Skipping feature row. Already present in db.");
1043: continue;
1044: }
1045: }
1046: try {
1047: stmt = null;
1048: StatementBuffer insert = createStatementBuffer(row);
1049: LOG.logDebug(insert.toString());
1050: stmt = this .datastore.prepareStatement(this .conn,
1051: insert);
1052: stmt.execute();
1053: } catch (SQLException e) {
1054: String msg = "Error performing insert: "
1055: + e.getMessage();
1056: LOG.logError(msg, e);
1057: throw new TransactionException(msg, e);
1058: } finally {
1059: if (stmt != null) {
1060: try {
1061: stmt.close();
1062: } catch (SQLException e) {
1063: String msg = "Error closing statement: "
1064: + e.getMessage();
1065: LOG.logError(msg, e);
1066: }
1067: }
1068: }
1069: }
1070: }
1071:
1072: private StatementBuffer createStatementBuffer(InsertRow row)
1073: throws DatastoreException {
1074: StatementBuffer insert = new StatementBuffer();
1075: insert.append("INSERT INTO ");
1076: insert.append(row.table);
1077: insert.append(" (");
1078: Iterator<InsertField> columnsIter = row.getColumns().iterator();
1079: while (columnsIter.hasNext()) {
1080: insert.append(columnsIter.next().getColumnName());
1081: if (columnsIter.hasNext()) {
1082: insert.append(',');
1083: }
1084: }
1085: insert.append(") VALUES(");
1086: columnsIter = row.getColumns().iterator();
1087: while (columnsIter.hasNext()) {
1088: String placeHolder = "?";
1089: InsertField field = columnsIter.next();
1090: if (field instanceof InsertGeometryField) {
1091: int targetSrsCode = ((InsertGeometryField) field)
1092: .getTargetSrsCode();
1093: if (targetSrsCode != -1) {
1094: placeHolder = this .datastore.buildSRSTransformCall(
1095: "?", targetSrsCode);
1096: }
1097: }
1098: insert.append(placeHolder);
1099: insert.addArgument(field.getValue(), field.getSQLType());
1100: if (columnsIter.hasNext()) {
1101: insert.append(',');
1102: }
1103: }
1104: insert.append(")");
1105: return insert;
1106: }
1107:
1108: /**
1109: * Merges the given <code>InsertRow</code>s by eliminating rows that have identical content
1110: * (except for their primary keys).
1111: * <p>
1112: * This only applies to non-FeatureRows: there are never two FeatureRows that may be treated as
1113: * identical, because unique feature ids have been assigned to them before.
1114: *
1115: * @see FeatureIdAssigner
1116: *
1117: * @param insertRows
1118: * @return merged List of InsertRows
1119: */
1120: private List<InsertRow> mergeInsertRows(List<InsertRow> insertRows) {
1121:
1122: List<InsertRow> result = new ArrayList<InsertRow>();
1123:
1124: // keys: table names, values: inserts into the table
1125: Map<String, Collection<InsertRow>> tableMap = new HashMap<String, Collection<InsertRow>>();
1126:
1127: // build table lookup map
1128: Iterator<InsertRow> iter = insertRows.iterator();
1129: while (iter.hasNext()) {
1130: InsertRow insertRow = iter.next();
1131: Collection<InsertRow> tableInserts = tableMap.get(insertRow
1132: .getTable());
1133: if (tableInserts == null) {
1134: tableInserts = new ArrayList<InsertRow>();
1135: tableMap.put(insertRow.getTable(), tableInserts);
1136: }
1137: tableInserts.add(insertRow);
1138: }
1139:
1140: iter = insertRows.iterator();
1141: while (iter.hasNext()) {
1142: InsertRow insertRow = iter.next();
1143: boolean insert = true;
1144: if (!(insertRow instanceof FeatureRow)) {
1145: Collection<InsertRow> tableInserts = tableMap
1146: .get(insertRow.getTable());
1147: Iterator<InsertRow> candidatesIter = tableInserts
1148: .iterator();
1149: while (candidatesIter.hasNext()) {
1150: InsertRow candidate = candidatesIter.next();
1151: if (insertRow != candidate) {
1152: if (compareInsertRows(insertRow, candidate)) {
1153: LOG.logDebug("Removing InsertRow: "
1154: + insertRow.hashCode() + " "
1155: + insertRow + " - duplicate of: "
1156: + candidate);
1157: replaceInsertRow(insertRow, candidate);
1158: insert = false;
1159: tableInserts.remove(insertRow);
1160: break;
1161: }
1162: }
1163: }
1164: }
1165: if (insert) {
1166: result.add(insertRow);
1167: }
1168: }
1169: return result;
1170: }
1171:
1172: private boolean compareInsertRows(InsertRow row1, InsertRow row2) {
1173: Collection<InsertField> fields1 = row1.getColumns();
1174: Iterator<InsertField> iter = fields1.iterator();
1175: while (iter.hasNext()) {
1176: InsertField field1 = iter.next();
1177: if (!field1.isPK()) {
1178: InsertField field2 = row2.getColumn(field1
1179: .getColumnName());
1180: Object value1 = field1.getValue();
1181: Object value2 = null;
1182: if (field2 != null)
1183: value2 = field2.getValue();
1184: if (value1 == null) {
1185: if (value2 == null) {
1186: continue;
1187: }
1188: return false;
1189: }
1190: try {
1191: if (!value1.equals(value2)) {
1192: return false;
1193: }
1194: } catch (NullPointerException e) {
1195: LOG
1196: .logWarning("A null pointer exception occurred while comparing features/attributes. Assuming they're not equal...");
1197: return false;
1198: }
1199: }
1200: }
1201: return true;
1202: }
1203:
1204: private void replaceInsertRow(InsertRow oldRow, InsertRow newRow) {
1205:
1206: Collection<InsertField> oldFields = oldRow.getColumns();
1207: for (InsertField field : oldFields) {
1208: InsertField toField = field.getReferencedField();
1209: if (toField != null) {
1210: LOG.logDebug("Removing reference to field '" + toField
1211: + "'");
1212: toField.removeReferencingField(field);
1213: }
1214: }
1215:
1216: Collection<InsertField> referencingFields = oldRow
1217: .getReferencingFields();
1218: for (InsertField fromField : referencingFields) {
1219: LOG.logDebug("Replacing reference for field '" + fromField
1220: + "'");
1221: InsertField field = newRow.getColumn(fromField
1222: .getReferencedField().getColumnName());
1223: LOG.logDebug("" + field);
1224: fromField.relinkField(field);
1225: }
1226: }
1227: }
|