001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2004-2006, Geotools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.data.wfs;
017:
018: import java.io.IOException;
019: import java.io.InputStream;
020: import java.io.OutputStream;
021: import java.io.OutputStreamWriter;
022: import java.io.Writer;
023: import java.net.HttpURLConnection;
024: import java.net.URL;
025: import java.util.AbstractList;
026: import java.util.ArrayList;
027: import java.util.Collection;
028: import java.util.HashMap;
029: import java.util.HashSet;
030: import java.util.Iterator;
031: import java.util.LinkedList;
032: import java.util.List;
033: import java.util.Map;
034: import java.util.Set;
035: import java.util.Map.Entry;
036:
037: import java.util.logging.Level;
038: import java.util.logging.Logger;
039:
040: import javax.naming.OperationNotSupportedException;
041:
042: import org.geotools.data.Transaction;
043: import org.geotools.data.Transaction.State;
044: import org.geotools.data.wfs.Action.DeleteAction;
045: import org.geotools.data.wfs.Action.InsertAction;
046: import org.geotools.data.wfs.Action.UpdateAction;
047: import org.geotools.filter.FidFilter;
048: import org.geotools.util.logging.Logging;
049: import org.opengis.filter.Filter;
050: import org.geotools.xml.DocumentFactory;
051: import org.geotools.xml.DocumentWriter;
052: import org.geotools.xml.wfs.WFSSchema;
053: import org.xml.sax.SAXException;
054:
055: /**
056: * DOCUMENT ME!
057: *
058: * @author dzwiers TODO To change the template for this generated type comment go to Window -
059: * Preferences - Java - Code Style - Code Templates
060: * @source $URL:
061: * http://svn.geotools.org/geotools/branches/2.2.x/plugin/wfs/src/org/geotools/data/wfs/WFSTransactionState.java $
062: */
063: public class WFSTransactionState implements State {
064: private WFSDataStore ds = null;
065: /**
066: * A map of <String, String[]>. String is the typename and String[] are the fids returned by
067: * Transaction Results during the last commit. They are the fids of the inserted elements.
068: */
069: private Map fids = new HashMap();
070: /**
071: * A Map of <String, List<Action>> where string is the typeName of the feature type and the
072: * list is the list of actions that have modified the feature type
073: */
074: Map actionMap = new HashMap();
075:
076: private long latestFid = Long.MAX_VALUE;
077:
078: private WFSTransactionState() {
079: // should not be used
080: }
081:
082: /**
083: * @param ds
084: */
085: public WFSTransactionState(WFSDataStore ds) {
086: this .ds = ds;
087: }
088:
089: /**
090: * @see org.geotools.data.Transaction.State#setTransaction(org.geotools.data.Transaction)
091: */
092: public void setTransaction(Transaction transaction) {
093: if (transaction != null) {
094: synchronized (actionMap) {
095: synchronized (fids) {
096: fids.clear();
097: }
098: actionMap.clear();
099: }
100: }
101: }
102:
103: /**
104: * @see org.geotools.data.Transaction.State#addAuthorization(java.lang.String)
105: */
106: public void addAuthorization(String AuthID) {
107: // authId = AuthID;
108: }
109:
110: /**
111: * Not implemented
112: *
113: * @return String
114: */
115: public String getLockId() {
116: return null; // add this later
117: }
118:
119: /**
120: * @see org.geotools.data.Transaction.State#commit()
121: */
122: public void commit() throws IOException {
123: // TODO deal with authID and locking ... WFS only allows one authID / transaction ...
124: TransactionResult tr = null;
125:
126: Map copiedActions;
127: synchronized (actionMap) {
128: combineActions();
129: copiedActions = copy(actionMap);
130: }
131: Iterator iter = copiedActions.entrySet().iterator();
132: while (iter.hasNext()) {
133: Map.Entry entry = (Entry) iter.next();
134: List actions = (List) entry.getValue();
135: String typeName = (String) entry.getKey();
136:
137: if (actions.isEmpty())
138: continue;
139:
140: if (((ds.protocol & WFSDataStore.POST_PROTOCOL) == WFSDataStore.POST_PROTOCOL)
141: && (tr == null)) {
142: try {
143: tr = commitPost(actions);
144: } catch (OperationNotSupportedException e) {
145: WFSDataStoreFactory.logger.warning(e.toString());
146: tr = null;
147: } catch (SAXException e) {
148: WFSDataStoreFactory.logger.warning(e.toString());
149: tr = null;
150: }
151: }
152:
153: // if (((ds.protocol & WFSDataStore.GET_PROTOCOL) == WFSDataStore.GET_PROTOCOL)
154: // && (tr == null)) {
155: // try {
156: // tr = commitPost();
157: // } catch (OperationNotSupportedException e) {
158: // WFSDataStoreFactory.logger.warning(e.toString());
159: // tr = null;
160: // } catch (SAXException e) {
161: // WFSDataStoreFactory.logger.warning(e.toString());
162: // tr = null;
163: // }
164: // }
165:
166: if (tr == null) {
167: throw new IOException(
168: "An error occured while committing.");
169: }
170:
171: if (tr.getStatus() == TransactionResult.FAILED) {
172: throw new IOException(tr.getError().toString());
173: }
174:
175: List newFids = tr.getInsertResult();
176: int currentInsertIndex = 0;
177: for (Iterator iter2 = actions.iterator(); iter2.hasNext();) {
178: Object action = iter2.next();
179: if (action instanceof InsertAction) {
180: InsertAction insertAction = (InsertAction) action;
181: if (currentInsertIndex >= newFids.size()) {
182: Logging
183: .getLogger("org.geotools.data.wfs")
184: .severe(
185: "Expected more fids to be returned by "
186: + "TransactionResponse!");
187: break;
188: }
189: ds.addFidMapping(insertAction.getFeature().getID(),
190: (String) newFids.get(currentInsertIndex));
191: currentInsertIndex++;
192: }
193: }
194: synchronized (this .fids) {
195: this .fids.put(typeName, (String[]) newFids
196: .toArray(new String[0]));
197: }
198:
199: if (currentInsertIndex != newFids.size()) {
200: Logging.getLogger("org.geotools.data.wfs").severe(
201: "number of fids inserted do not match number of fids returned "
202: + "by Transaction Response. Got:"
203: + newFids.size() + " expected: "
204: + currentInsertIndex);
205: }
206: synchronized (actionMap) {
207: ((List) actionMap.get(typeName)).removeAll(actions);
208: }
209: }
210: }
211:
212: private Map copy(Map actionMap2) {
213: Map newMap = new HashMap();
214: Iterator entries = actionMap2.entrySet().iterator();
215: while (entries.hasNext()) {
216: Map.Entry entry = (Entry) entries.next();
217: List list = (List) entry.getValue();
218: newMap.put(entry.getKey(), new ArrayList(list));
219: }
220: return newMap;
221: }
222:
223: private TransactionResult commitPost(List toCommit)
224: throws OperationNotSupportedException, IOException,
225: SAXException {
226: URL postUrl = ds.capabilities.getTransaction().getPost();
227:
228: // System.out.println("POST Commit URL = "+postUrl);
229:
230: if (postUrl == null) {
231: return null;
232: }
233:
234: HttpURLConnection hc = ds.getConnection(postUrl, ds.auth, true);
235: // System.out.println("connection to commit");
236: Map hints = new HashMap();
237: hints.put(DocumentWriter.BASE_ELEMENT, WFSSchema.getInstance()
238: .getElements()[24]); // Transaction
239: Set fts = new HashSet();
240: Iterator i = toCommit.iterator();
241: while (i.hasNext()) {
242: Action a = (Action) i.next();
243: fts.add(a.getTypeName());
244: }
245: Set ns = new HashSet();
246: ns.add(WFSSchema.NAMESPACE.toString());
247: i = fts.iterator();
248: while (i.hasNext()) {
249: ns.add(ds.getSchema((String) i.next()).getNamespace()
250: .toString());
251: }
252: hints.put(DocumentWriter.SCHEMA_ORDER, ns.toArray(new String[ns
253: .size()])); // Transaction
254:
255: // System.out.println("Ready to print Debug");
256: // // DEBUG
257: // StringWriter debugw = new StringWriter();
258: // DocumentWriter.writeDocument(this, WFSSchema.getInstance(), debugw, hints);
259: // System.out.println("TRANSACTION \n\n");
260: // System.out.println(debugw.getBuffer());
261: // // END DEBUG
262:
263: OutputStream os = hc.getOutputStream();
264:
265: // write request
266: Writer w = new OutputStreamWriter(os);
267: Logger logger = Logging.getLogger("org.geotools.data.wfs");
268: if (logger.isLoggable(Level.FINE)) {
269: w = new LogWriterDecorator(w, logger, Level.FINE);
270: }
271: // special logger for communication information only.
272: logger = Logging.getLogger("org.geotools.data.communication");
273: if (logger.isLoggable(Level.FINE)) {
274: w = new LogWriterDecorator(w, logger, Level.FINE);
275: }
276:
277: DocumentWriter.writeDocument(this , WFSSchema.getInstance(), w,
278: hints);
279: w.flush();
280: w.close();
281:
282: InputStream is = this .ds.getInputStream(hc);
283:
284: hints = new HashMap();
285:
286: TransactionResult ft = (TransactionResult) DocumentFactory
287: .getInstance(is, hints, Level.WARNING);
288: return ft;
289: }
290:
291: /**
292: * @see org.geotools.data.Transaction.State#rollback()
293: */
294: public void rollback() {
295: synchronized (actionMap) {
296: actionMap.clear();
297: }
298: }
299:
300: /**
301: * @return Fid Set
302: */
303: public String[] getFids(String typeName) {
304: synchronized (fids) {
305: return (String[]) fids.get(typeName);
306: }
307: }
308:
309: /**
310: * @param a
311: */
312: public void addAction(String typeName, Action a) {
313: synchronized (actionMap) {
314: List list = (List) actionMap.get(typeName);
315: if (list == null) {
316: list = new ArrayList();
317: actionMap.put(typeName, list);
318: }
319: list.add(a);
320: }
321: }
322:
323: /**
324: * @return List of Actions
325: */
326: public List getActions(String typeName) {
327: synchronized (actionMap) {
328: Collection collection = (Collection) actionMap
329: .get(typeName);
330: if (collection == null || collection.isEmpty())
331: return new ArrayList();
332: return new ArrayList(collection);
333: }
334: }
335:
336: /**
337: * Returns all the actions for all FeatureTypes
338: *
339: * @return all the actions for all FeatureTypes
340: */
341: public List getAllActions() {
342: synchronized (actionMap) {
343: List all = new ArrayList();
344: for (Iterator iter = actionMap.values().iterator(); iter
345: .hasNext();) {
346: List actions = (List) iter.next();
347: all.addAll(actions);
348: }
349: return all;
350: }
351: }
352:
353: /**
354: * Combines updates and inserts reducing the number of actions in the commit.
355: * <p>
356: * This is in response to an issue where the FID is not known until after the commit so if a
357: * Feature is inserted then later updated(using a FID filter to identify the feature to update)
358: * within a single transactin then the commit will fail because the fid filter will be not apply
359: * once the insert action is processed.
360: * </p>
361: * <p>
362: * For Example:
363: * <ol>
364: * <li>Insert Feature.
365: * <p>
366: * Transaction assigns it the id: NewFeature.
367: * </p>
368: * </li>
369: * <li>Update Feature.
370: * <p>
371: * Fid filter is used to update NewFeature.
372: * </p>
373: * </li>
374: * <li>Commit.
375: * <p>
376: * Update will fail because when the Insert action is processed NewFeature will not refer to any
377: * feature.
378: * </p>
379: * </li>
380: * </ol>
381: * </p>
382: * <p>
383: * The algorithm is essentially foreach( insertAction ){ Apply each update and Delete action
384: * that applies to the inserted feature move insertAction to end of list }
385: * </p>
386: * <p>
387: * Mind you this only works assuming there aren't any direct dependencies between the actions
388: * beyond the ones specified by the API. For example if the value of an update depends directly
389: * on an earlier feature object (which is bad practice and should never be done). Then we may
390: * have problems with this solution. But I think that this solution is better than doing nothing
391: * because at least in the proper use of the API the correct result will be obtained. Whereas
392: * before the correct use of the API could obtain incorrect results.
393: * </p>
394: */
395: protected void combineActions() {
396: EACH_FEATURE_TYPE: for (Iterator iter = actionMap.values()
397: .iterator(); iter.hasNext();) {
398: List actions = (List) iter.next();
399:
400: removeFilterAllActions(actions);
401: InsertAction firstAction = null;
402: while (firstAction == null
403: || !actions.contains(firstAction)) {
404: firstAction = findFirstInsertAction(actions);
405: if (firstAction == null)
406: continue EACH_FEATURE_TYPE;
407: processInsertAction(actions, firstAction);
408: }
409: InsertAction current = findFirstInsertAction(actions);
410: while (current != null && firstAction != current) {
411: processInsertAction(actions, current);
412: current = findFirstInsertAction(actions);
413: }
414: }
415: }
416:
417: /**
418: * Removes all actions whose filter is Filter.EXCLUDE
419: */
420: private void removeFilterAllActions(List actions) {
421: for (Iterator iter = actions.iterator(); iter.hasNext();) {
422: Action element = (Action) iter.next();
423: if (Filter.EXCLUDE.equals(element.getFilter()))
424: iter.remove();
425: }
426: }
427:
428: private InsertAction findFirstInsertAction(List actions) {
429: int i = 0;
430: for (Iterator iter = actions.iterator(); iter.hasNext(); i++) {
431: Object action = iter.next();
432: if (action instanceof InsertAction) {
433: return (InsertAction) action;
434: }
435: }
436: return null;
437: }
438:
439: private void processInsertAction(List actions, InsertAction action) {
440: int indexOf = actions.indexOf(action);
441: while (indexOf + 1 < actions.size() && indexOf != -1) {
442: moveUpdateAndMoveInsertAction(actions, indexOf, action);
443: indexOf = actions.indexOf(action);
444: }
445: }
446:
447: private void moveUpdateAndMoveInsertAction(List actions, int i,
448: InsertAction action) {
449: if (i + 1 < actions.size()) {
450: Object nextAction = actions.get(i + 1);
451: if (nextAction instanceof DeleteAction) {
452: handleDeleteAction(actions, i, action,
453: (DeleteAction) nextAction);
454: } else if (nextAction instanceof UpdateAction) {
455: handleUpdateAction(actions, i, action,
456: (UpdateAction) nextAction);
457: } else
458: swap(actions, i);
459: }
460: }
461:
462: private void handleDeleteAction(List actions, int i,
463: InsertAction action, DeleteAction deleteAction) {
464: // if inserted action has been deleted then remove action
465: if (deleteAction.getFilter().evaluate(action.getFeature())) {
466: actions.remove(i);
467: // if filter is a fid filter of size 1 then it only contains the inserted feature which
468: // no longer exists since it has been deleted. so remove that action as well.
469: if (deleteAction.getFilter() instanceof FidFilter
470: && ((FidFilter) deleteAction.getFilter()).getFids().length == 1) {
471: actions.remove(i);
472: }
473: } else {
474: swap(actions, i);
475: }
476: }
477:
478: private int handleUpdateAction(List actions, int i,
479: InsertAction action, UpdateAction updateAction) {
480: // if update action applies to feature then update feature
481: if (updateAction.getFilter().evaluate(action.getFeature())) {
482: updateAction.update(action.getFeature());
483: // if filter is a fid filter and there is only 1 fid then filter uniquely identifies
484: // only the
485: // one features so remove it.
486: if (updateAction.getFilter() instanceof FidFilter
487: && ((FidFilter) updateAction.getFilter()).getFids().length == 1) {
488: actions.remove(i + 1);
489: return i;
490: }
491: }
492: swap(actions, i);
493: return i + 1;
494: }
495:
496: /**
497: * swaps the action at location i with the item at location i+1
498: *
499: * @param i item to swap
500: */
501: private void swap(List actions, int i) {
502: Object item = actions.remove(i);
503: actions.add(i + 1, item);
504: }
505:
506: public String nextFid(String typeName) {
507: long fid;
508: synchronized (this ) {
509: fid = latestFid;
510: latestFid--;
511: }
512: return "new" + typeName + "." + fid;
513: }
514:
515: }
|