001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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 }