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