    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/io/datastore/sql/transaction/insert/InsertRow.java $
    002    /*----------------------------------------------------------------------------
    003     This file is part of deegree, http://deegree.org/
    004     Copyright (C) 2001-2009 by:
    005       Department of Geography, University of Bonn
    006     and
    007       lat/lon GmbH
    008
    009     This library is free software; you can redistribute it and/or modify it under
    010     the terms of the GNU Lesser General Public License as published by the Free
    011     Software Foundation; either version 2.1 of the License, or (at your option)
    012     any later version.
    013     This library is distributed in the hope that it will be useful, but WITHOUT
    014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
    016     details.
    017     You should have received a copy of the GNU Lesser General Public License
    018     along with this library; if not, write to the Free Software Foundation, Inc.,
    019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    020
    021     Contact information:
    022
    023     lat/lon GmbH
    024     Aennchenstr. 19, 53177 Bonn
    025     Germany
    026     http://lat-lon.de/
    027
    028     Department of Geography, University of Bonn
    029     Prof. Dr. Klaus Greve
    030     Postfach 1147, 53001 Bonn
    031     Germany
    032     http://www.geographie.uni-bonn.de/deegree/
    033
    034     e-mail: info@deegree.org
    035    ----------------------------------------------------------------------------*/
    036    package org.deegree.io.datastore.sql.transaction.insert;
    037
    038    import java.util.ArrayList;
    039    import java.util.Collection;
    040    import java.util.HashMap;
    041    import java.util.HashSet;
    042    import java.util.Iterator;
    043    import java.util.LinkedHashMap;
    044    import java.util.List;
    045    import java.util.Map;
    046    import java.util.Set;
    047
    048    import org.deegree.framework.log.ILogger;
    049    import org.deegree.framework.log.LoggerFactory;
    050    import org.deegree.i18n.Messages;
    051    import org.deegree.io.datastore.TransactionException;
    052    import org.deegree.ogcwebservices.wfs.operation.transaction.Insert;
    053
    054    /**
    055     * Represents a table row (columns + values) which has to be inserted as part of an {@link Insert} operation.
    056     *
    057     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
    058     * @author last edited by: $Author: mschneider $
    059     *
    060     * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
    061     */
    062    public class InsertRow {
    063
    064        private static final ILogger LOG = LoggerFactory.getLogger( InsertRow.class );
    065
    066        /** Database table */
    067        protected String table;
    068
    069        /** Maps column names to their corresponding {@link InsertField} instance. */
    070        protected Map<String, InsertField> columnMap = new LinkedHashMap<String, InsertField>();
    071
    072        /**
    073         * Creates a new <code>InsertRow</code> instance for the given table.
    074         *
    075         * @param table
    076         */
    077        public InsertRow( String table ) {
    078            this.table = table;
    079        }
    080
    081        /**
    082         * Returns the name of table.
    083         *
    084         * @return the name of table
    085         */
    086        public String getTable() {
    087            return this.table;
    088        }
    089
    090        /**
    091         * Sets the value to be inserted in the given table column.
    092         * <p>
    093         * In complex + erroneous mappings (or feature instances), it may happen that different property values (mapped to
    094         * the same column) imply different values. This is checked for and in case an {@link TransactionException} is
    095         * thrown.
    096         *
    097         * @param column
    098         * @param value
    099         * @param sqlType
    100         * @param isPK
    101         * @return column + value to be set
    102         * @throws TransactionException
    103         *             if the value for the column clashes
    104         */
    105        public InsertField setColumn( String column, Object value, int sqlType, boolean isPK )
    106                                throws TransactionException {
    107
    108            InsertField field = new InsertField( this, column, sqlType, value, isPK );
    109            InsertField presentField = columnMap.get( column );
    110            if ( presentField != null && ( !presentField.getValue().toString().equals( value.toString() ) ) ) {
    111                Object[] params = new Object[] { column, this.table, presentField.getValue().toString(), value.toString() };
    112                String msg = Messages.getMessage( "DATASTORE_AMBIGOUS_COLUMN_VALUES", params );
    113                throw new TransactionException( msg );
    114            }
    115            if ( presentField == null ) {
    116                this.columnMap.put( column, field );
    117            }
    118            // TODO type check
    119            return field;
    120        }
    121
    122        /**
    123         * Sets the value to be inserted in the given geometry column.
    124         * <p>
    125         * In complex + erroneous mappings (or feature instances), it may happen that different property values (mapped to
    126         * the same column) imply different values. This is checked for and in case an {@link TransactionException} is
    127         * thrown.
    128         *
    129         * @param column
    130         * @param value
    131         * @param sqlType
    132         * @param isPK
    133         * @param internalSrsCode
    134         * @return column + value to be set
    135         * @throws TransactionException
    136         *             if the value for the column clashes
    137         */
    138        public InsertGeometryField setGeometryColumn( String column, Object value, int sqlType, boolean isPK,
    139                                                      int internalSrsCode )
    140                                throws TransactionException {
    141
    142            InsertGeometryField field = new InsertGeometryField( this, column, sqlType, value, isPK, internalSrsCode );
    143            InsertField presentField = columnMap.get( column );
    144            if ( presentField != null && ( !presentField.getValue().toString().equals( value.toString() ) ) ) {
    145                Object[] params = new Object[] { column, this.table, presentField.getValue().toString(), value.toString() };
    146                String msg = Messages.getMessage( "DATASTORE_AMBIGOUS_COLUMN_VALUES", params );
    147                throw new TransactionException( msg );
    148            }
    149            if ( presentField == null ) {
    150                this.columnMap.put( column, field );
    151            }
    152            // TODO type check
    153            return field;
    154        }
    155
    156        /**
    157         * Sets the value to be inserted in the given table column - the column references the given {@link InsertField} and
    158         * thus must have the same value as the referenced column.
    159         * <p>
    160         * In complex + erroneous mappings (or feature instances), it may happen that different property values (mapped to
    161         * the same column) imply different values. This is checked for and in case an {@link TransactionException} is
    162         * thrown.
    163         *
    164         * @param column
    165         * @param referencedField
    166         * @throws TransactionException
    167         *             if the value for the column clashes
    168         */
    169        public void linkColumn( String column, InsertField referencedField )
    170                                throws TransactionException {
    171
    172            if ( referencedField.getValue() == null ) {
    173                LOG.logError( "linkColumn (): referencedField is null" );
    174                throw new TransactionException( "linkColumn (): referenced field is null!" );
    175            }
    176
    177            InsertField presentField = this.columnMap.get( column );
    178            if ( presentField != null
    179                 && ( !presentField.getValue().toString().equals( referencedField.getValue().toString() ) ) ) {
    180                Object[] params = new Object[] { column, this.table, presentField.getValue().toString(),
    181                                                referencedField.getValue().toString() };
    182                String msg = Messages.getMessage( "DATASTORE_AMBIGOUS_COLUMN_VALUES", params );
    183                throw new TransactionException( msg );
    184            }
    185
    186            InsertField field = presentField;
    187            if ( presentField != null ) {
    188                presentField.relinkField( referencedField );
    189            } else {
    190                field = new InsertField( this, column, referencedField );
    191                this.columnMap.put( column, field );
    192            }
    193            referencedField.addReferencingField( field );
    194        }
    195
    196        /**
    197         * Returns all {@link InsertField}s.
    198         *
    199         * @return all InsertFields
    200         */
    201        public Collection<InsertField> getColumns() {
    202            return this.columnMap.values();
    203        }
    204
    205        /**
    206         * Returns the {@link InsertField} for the given column name.
    207         *
    208         * @param column
    209         *            requested column name
    210         * @return the InsertField for the given column name, or null if it does not exist
    211         */
    212        public InsertField getColumn( String column ) {
    213            return this.columnMap.get( column );
    214        }
    215
    216        /**
    217         * Returns the {@link InsertField} for the primary key column.
    218         *
    219         * @return the InsertField for the primary key column
    220         */
    221        public InsertField getPKColumn() {
    222            InsertField pkField = null;
    223            for ( InsertField field : this.columnMap.values() ) {
    224                if ( field.isPK() ) {
    225                    pkField = field;
    226                    break;
    227                }
    228            }
    229            return pkField;
    230        }
    231
    232        /**
    233         * Returns all {@link InsertField} that reference a column in this <code>InsertRow</code>. The fields may well be
    234         * from other tables.
    235         *
    236         * @return all InsertFields that reference a column in this InsertRow
    237         */
    238        public List<InsertField> getReferencingFields() {
    239            List<InsertField> referencingFields = new ArrayList<InsertField>();
    240            Iterator<InsertField> iter = this.columnMap.values().iterator();
    241            while ( iter.hasNext() ) {
    242                InsertField field = iter.next();
    243                referencingFields.addAll( field.getReferencingFields() );
    244            }
    245            return referencingFields;
    246        }
    247
    248        /**
    249         * Returns all {@link InsertRow}s that reference a column in this <code>InsertRow</code>. The rows may well be from
    250         * other tables.
    251         *
    252         * @return all InsertRows that reference a column in this InsertRow
    253         */
    254        Collection<InsertRow> getReferencingRows() {
    255            Set<InsertRow> referencingRows = new HashSet<InsertRow>();
    256            Iterator<InsertField> iter = this.columnMap.values().iterator();
    257            while ( iter.hasNext() ) {
    258                InsertField field = iter.next();
    259                referencingRows.addAll( field.getReferencingRows() );
    260            }
    261            return referencingRows;
    262        }
    263
    264        /**
    265         * Returns all {@link InsertField}s that are referenced by a field from this <code>InsertRow</code>. The fields may
    266         * well be from other tables.
    267         *
    268         * @return all InsertField that are referenced by a field from this InsertRow
    269         */
    270        List<InsertField> getReferencedFields() {
    271            List<InsertField> referencedFields = new ArrayList<InsertField>();
    272            Iterator<InsertField> iter = this.columnMap.values().iterator();
    273            while ( iter.hasNext() ) {
    274                InsertField field = iter.next();
    275                InsertField referencedField = field.getReferencedField();
    276                if ( referencedField != null ) {
    277                    referencedFields.add( referencedField );
    278                }
    279            }
    280            return referencedFields;
    281        }
    282
    283        /**
    284         * Returns all {@link InsertRow}s that are referenced by a field from this <code>InsertRow</code>. The rows may well
    285         * be from other tables.
    286         *
    287         * @return all InsertRows that are referenced by a field from this InsertRow
    288         */
    289        private Collection<InsertRow> getReferencedRows() {
    290            Set<InsertRow> referencedRows = new HashSet<InsertRow>();
    291            Iterator<InsertField> iter = this.columnMap.values().iterator();
    292            while ( iter.hasNext() ) {
    293                InsertField field = iter.next();
    294                InsertRow referencedRow = field.getReferencedRow();
    295                if ( referencedRow != null ) {
    296                    referencedRows.add( referencedRow );
    297                }
    298            }
    299            return referencedRows;
    300        }
    301
    302        /**
    303         * Sorts the given <code>InsertRow</code>s topologically (respecting the foreign key constraints), so they can be
    304         * inserted in the resulting order without causing foreign key violations.
    305         * <p>
    306         * Number of precedessors (pre): number of fields that *are referenced by* this row Number of successors (post) :
    307         * number of fields that *reference* this row
    308         *
    309         * @param inserts
    310         *            insert rows to sort
    311         * @return the nodes of the graph in topological order if no cycle is present, else in arbitrary order
    312         */
    313        public static List<InsertRow> getInsertOrder( List<InsertRow> inserts ) {
    314
    315            List<InsertRow> result = new ArrayList<InsertRow>();
    316
    317            // key: inserts, value: number of fields that are referenced by this row
    318            Map<InsertRow, Integer> preMap = new HashMap<InsertRow, Integer>();
    319
    320            // key: inserts with no foreign key constraints
    321            List<InsertRow> noPre = new ArrayList<InsertRow>();
    322
    323            // build map
    324            Iterator<InsertRow> insertIter = inserts.iterator();
    325            while ( insertIter.hasNext() ) {
    326                InsertRow insertRow = insertIter.next();
    327                int pre = insertRow.getReferencedFields().size();
    328                LOG.logDebug( "Adding row to preMap: " + insertRow );
    329                preMap.put( insertRow, pre );
    330                if ( pre == 0 ) {
    331                    noPre.add( insertRow );
    332                }
    333            }
    334
    335            while ( !noPre.isEmpty() ) {
    336                // select an insert row that has no open fk constraints
    337                InsertRow insertRow = noPre.get( 0 );
    338                noPre.remove( 0 );
    339                result.add( insertRow );
    340
    341                // decrease the number of pending fk constraints for all insert rows that
    342                // reference the currently processed insert row
    343                Collection<InsertField> postList = insertRow.getReferencingFields();
    344                Iterator<InsertField> iter = postList.iterator();
    345                while ( iter.hasNext() ) {
    346                    InsertField postField = iter.next();
    347                    if ( preMap.get( postField.getRow() ) == null ) {
    348                        LOG.logDebug( "No pre info for: " + postField.getRow() );
    349                    }
    350                    int pre = preMap.get( postField.getRow() );
    351                    preMap.put( postField.getRow(), --pre );
    352                    if ( pre == 0 ) {
    353                        noPre.add( postField.getRow() );
    354                    }
    355                }
    356            }
    357
    358            if ( result.size() != inserts.size() ) {
    359                Collection<InsertRow> cycle = InsertRow.findCycle( inserts );
    360                Iterator<InsertRow> cycleIter = cycle.iterator();
    361                StringBuffer sb = new StringBuffer();
    362                while ( cycleIter.hasNext() ) {
    363                    sb.append( cycleIter.next() );
    364                    if ( cycle.iterator().hasNext() ) {
    365                        sb.append( " -> " );
    366                    }
    367                }
    368                String msg = Messages.getMessage( "DATASTORE_FK_CYCLE", sb.toString() );
    369                LOG.logWarning( msg );
    370                result.clear();
    371                result.addAll( inserts );
    372            }
    373            return result;
    374        }
    375
    376        /**
    377         * Checks if the given <code>InsertRow</code>s contain cyclic fk constraints.
    378         *
    379         * @param rows
    380         * @return steps of the cycle, or null if there is none
    381         */
    382        private static Collection<InsertRow> findCycle( Collection<InsertRow> rows ) {
    383
    384            Iterator<InsertRow> rowsIter = rows.iterator();
    385            while ( rowsIter.hasNext() ) {
    386                InsertRow begin = rowsIter.next();
    387                Iterator<InsertRow> refIter = begin.getReferencedRows().iterator();
    388                while ( refIter.hasNext() ) {
    389                    InsertRow referencedRow = refIter.next();
    390                    List<InsertRow> cycle = findCycleRecursion( referencedRow, begin, new ArrayList<InsertRow>() );
    391                    if ( cycle != null ) {
    392                        return cycle;
    393                    }
    394                }
    395            }
    396            return null;
    397        }
    398
    399        private static List<InsertRow> findCycleRecursion( InsertRow next, InsertRow begin, List<InsertRow> steps ) {
    400
    401            if ( steps.contains( next ) ) {
    402                steps.add( next );
    403                return steps;
    404            }
    405            steps.add( next );
    406
    407            Iterator<InsertRow> refIter = next.getReferencedRows().iterator();
    408            while ( refIter.hasNext() ) {
    409                InsertRow referencedRow = refIter.next();
    410                List<InsertRow> cycle = findCycleRecursion( referencedRow, begin, steps );
    411                if ( cycle != null ) {
    412                    return cycle;
    413                }
    414            }
    415            steps.remove( steps.size() - 1 );
    416            return null;
    417        }
    418
    419        /**
    420         * Returns a string representation of the object.
    421         *
    422         * @return a string representation of the object
    423         */
    424        @Override
    425        public String toString() {
    426            StringBuffer sb = new StringBuffer( "InsertRow, table: '" );
    427            sb.append( this.table );
    428            sb.append( "'" );
    429            Iterator<String> columnIter = this.columnMap.keySet().iterator();
    430            while ( columnIter.hasNext() ) {
    431                sb.append( ", " );
    432                String column = columnIter.next();
    433                InsertField field = this.columnMap.get( column );
    434                sb.append( field );
    435            }
    436            return sb.toString();
    437        }
    438    }