001: /* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
002: * This code is licensed under the GPL 2.0 license, availible at the root
003: * application directory.
004: */
005: package org.geoserver.wfs;
006:
007: import net.opengis.wfs.AllSomeType;
008: import net.opengis.wfs.LockFeatureResponseType;
009: import net.opengis.wfs.LockFeatureType;
010: import net.opengis.wfs.LockType;
011: import net.opengis.wfs.WfsFactory;
012: import org.geotools.data.DataStore;
013: import org.geotools.data.DefaultQuery;
014: import org.geotools.data.DefaultTransaction;
015: import org.geotools.data.FeatureLock;
016: import org.geotools.data.FeatureLockFactory;
017: import org.geotools.data.FeatureLocking;
018: import org.geotools.data.FeatureSource;
019: import org.geotools.data.LockingManager;
020: import org.geotools.data.Query;
021: import org.geotools.data.Transaction;
022: import org.geotools.factory.CommonFactoryFinder;
023: import org.geotools.factory.GeoTools;
024: import org.geotools.feature.Feature;
025: import org.geotools.feature.FeatureCollection;
026: import org.opengis.filter.Filter;
027: import org.opengis.filter.FilterFactory;
028: import org.opengis.filter.FilterFactory2;
029: import org.opengis.filter.Id;
030: import org.opengis.filter.identity.FeatureId;
031: import org.opengis.referencing.crs.CoordinateReferenceSystem;
032: import org.vfny.geoserver.global.Data;
033: import org.vfny.geoserver.global.DataStoreInfo;
034: import org.vfny.geoserver.global.FeatureTypeInfo;
035: import java.io.IOException;
036: import java.math.BigInteger;
037: import java.util.HashSet;
038: import java.util.Iterator;
039: import java.util.List;
040: import java.util.Set;
041: import java.util.logging.Level;
042: import java.util.logging.Logger;
043: import javax.xml.namespace.QName;
044:
045: /**
046: * Web Feature Service 1.0 LockFeature Operation.
047: *
048: * @author Justin Deoliveira, The Open Planning Project
049: *
050: */
051: public class LockFeature {
052: /**
053: * The logger
054: */
055: static Logger LOGGER = org.geotools.util.logging.Logging
056: .getLogger("org.geoserver.wfs");
057:
058: /**
059: * Web Feature Service configuration
060: */
061: WFS wfs;
062:
063: /**
064: * The catalog
065: */
066: Data catalog;
067:
068: /**
069: * Filter factory
070: */
071: FilterFactory filterFactory;
072:
073: /**
074: *
075: * @param wfs
076: * @param catalog
077: */
078: public LockFeature(WFS wfs, Data catalog) {
079: this (wfs, catalog, null);
080: }
081:
082: public LockFeature(WFS wfs, Data catalog,
083: FilterFactory filterFactory) {
084: this .wfs = wfs;
085: this .catalog = catalog;
086: this .filterFactory = filterFactory;
087: }
088:
089: public void setFilterFactory(FilterFactory filterFactory) {
090: this .filterFactory = filterFactory;
091: }
092:
093: /**
094: * Locks features according to the request.
095: *
096: * @param request
097: * @return the WFS 1.1 required response
098: * @throws WFSException
099: * if a lock failed and the lock specified all locks, or if an
100: * another error occurred processing the lock operation
101: */
102: public LockFeatureResponseType lockFeature(LockFeatureType request)
103: throws WFSException {
104: FeatureLock fLock = null;
105:
106: try {
107: // check we are dealing with a well formed request, there is at
108: // least on lock request?
109: List locks = request.getLock();
110:
111: if ((locks == null) || locks.isEmpty()) {
112: String msg = "A LockFeature request must contain at least one LOCK element";
113: throw new WFSException(msg);
114: }
115:
116: LOGGER.info("locks size is " + locks.size());
117:
118: // create a new lock (token used to manage locks across datastores)
119: fLock = newFeatureLock(request);
120:
121: // prepare the response object
122: LockFeatureResponseType response = WfsFactory.eINSTANCE
123: .createLockFeatureResponseType();
124: response.setLockId(fLock.getAuthorization());
125: response.setFeaturesLocked(WfsFactory.eINSTANCE
126: .createFeaturesLockedType());
127: response.setFeaturesNotLocked(WfsFactory.eINSTANCE
128: .createFeaturesNotLockedType());
129:
130: // go thru each lock request, and try to perform locks on a feature
131: // by feature basis
132: // in order to allow for both "all" and "some" lock behaviour
133: // TODO: if the lock is the default this default, lock the whole
134: // query directly, should be a lot faster
135: for (int i = 0, n = locks.size(); i < n; i++) {
136: LockType lock = (LockType) locks.get(i);
137: LOGGER.info("curLock is " + lock);
138:
139: QName typeName = lock.getTypeName();
140:
141: // get out the filter, and default to no filtering if none was
142: // provided
143: Filter filter = (Filter) lock.getFilter();
144:
145: if (filter == null) {
146: filter = Filter.INCLUDE;
147: }
148:
149: FeatureTypeInfo meta;
150: FeatureSource source;
151: FeatureCollection features;
152:
153: try {
154: meta = catalog
155: .getFeatureTypeInfo(
156: typeName.getLocalPart(), typeName
157: .getNamespaceURI());
158:
159: if (meta == null) {
160: throw new WFSException("Unknown feature type "
161: + typeName.getPrefix() + ":"
162: + typeName.getLocalPart());
163: }
164:
165: source = meta.getFeatureSource();
166:
167: // make sure all geometric elements in the filter have a crs, and that the filter
168: // is reprojected to store's native crs as well
169: CoordinateReferenceSystem declaredCRS = WFSReprojectionUtil
170: .getDeclaredCrs(source.getSchema(), request
171: .getVersion());
172: filter = WFSReprojectionUtil.normalizeFilterCRS(
173: filter, source.getSchema(), declaredCRS);
174:
175: // now gather the features
176: features = source.getFeatures(filter);
177:
178: if (source instanceof FeatureLocking) {
179: ((FeatureLocking) source).setFeatureLock(fLock);
180: }
181: } catch (IOException e) {
182: throw new WFSException(e);
183: }
184:
185: Iterator reader = null;
186: int numberLocked = -1;
187:
188: try {
189: for (reader = features.iterator(); reader.hasNext();) {
190: Feature feature = (Feature) reader.next();
191:
192: FeatureId fid = fid(feature.getID());
193: Id fidFilter = fidFilter(fid);
194:
195: if (!(source instanceof FeatureLocking)) {
196: LOGGER
197: .fine("Lock "
198: + fid
199: + " not supported by data store (authID:"
200: + fLock.getAuthorization()
201: + ")");
202:
203: response.getFeaturesNotLocked()
204: .getFeatureId().add(fid);
205:
206: // lockFailedFids.add(fid);
207: } else {
208: // DEFQuery is just some indirection, should be in
209: // the locking interface.
210: // int numberLocked =
211: // ((DEFQueryFeatureLocking)source).lockFeature(feature);
212: // HACK: Query.NO_NAMES isn't working in postgis
213: // right now,
214: // so we'll just use all.
215: Query query = new DefaultQuery(meta
216: .getTypeName(), (Filter) fidFilter,
217: Query.DEFAULT_MAX, Query.ALL_NAMES,
218: lock.getHandle());
219:
220: numberLocked = ((FeatureLocking) source)
221: .lockFeatures(query);
222:
223: if (numberLocked == 1) {
224: LOGGER.fine("Lock " + fid + " (authID:"
225: + fLock.getAuthorization()
226: + ")");
227: response.getFeaturesLocked()
228: .getFeatureId().add(fid);
229:
230: // lockedFids.add(fid);
231: } else if (numberLocked == 0) {
232: LOGGER.fine("Lock " + fid
233: + " conflict (authID:"
234: + fLock.getAuthorization()
235: + ")");
236: response.getFeaturesNotLocked()
237: .getFeatureId().add(fid);
238:
239: // lockFailedFids.add(fid);
240: } else {
241: LOGGER.warning("Lock " + numberLocked
242: + " " + fid + " (authID:"
243: + fLock.getAuthorization()
244: + ") duplicated FeatureID!");
245: response.getFeaturesLocked()
246: .getFeatureId().add(fid);
247:
248: // lockedFids.add(fid);
249: }
250: }
251: }
252: } catch (IOException ioe) {
253: throw new WFSException(ioe);
254: } finally {
255: if (reader != null) {
256: features.close(reader);
257: }
258: }
259:
260: // refresh lock times, so they all start the same instant and we
261: // are nearer
262: // to the spec when it says the expiry should start when the
263: // lock
264: // feature response has been totally written
265: if (numberLocked > 0) {
266: Transaction t = new DefaultTransaction();
267:
268: try {
269: try {
270: t.addAuthorization(response.getLockId());
271: source.getDataStore().getLockingManager()
272: .refresh(response.getLockId(), t);
273: } finally {
274: t.commit();
275: }
276: } catch (IOException e) {
277: throw new WFSException(e);
278: }
279: }
280: }
281:
282: // should we releas all? if not set default to true
283: boolean lockAll = !(request.getLockAction() == AllSomeType.SOME_LITERAL);
284:
285: if (lockAll
286: && !response.getFeaturesNotLocked().getFeatureId()
287: .isEmpty()) {
288: // I think we need to release and fail when lockAll fails
289: //
290: // abort will release the locks
291: throw new WFSException("Could not aquire locks for:"
292: + response.getFeaturesNotLocked());
293: }
294:
295: //remove empty parts of the response object
296: if (response.getFeaturesLocked().getFeatureId().isEmpty()) {
297: response.setFeaturesLocked(null);
298: }
299:
300: if (response.getFeaturesNotLocked().getFeatureId()
301: .isEmpty()) {
302: response.setFeaturesNotLocked(null);
303: }
304:
305: return response;
306: } catch (WFSException e) {
307: // release locks when something fails
308: if (fLock != null) {
309: try {
310: release(fLock.getAuthorization());
311: } catch (WFSException e1) {
312: // log it
313: LOGGER.log(Level.SEVERE,
314: "Error occured releasing locks", e1);
315: }
316: }
317:
318: throw e;
319: }
320: }
321:
322: /**
323: * Release lock by authorization
324: *
325: * @param lockID
326: */
327: public void release(String lockId) throws WFSException {
328: try {
329: boolean refresh = false;
330:
331: Set dataStores = catalog.getDataStores();
332:
333: for (Iterator i = dataStores.iterator(); i.hasNext();) {
334: DataStoreInfo meta = (DataStoreInfo) i.next();
335:
336: if (!meta.isEnabled()) {
337: continue; // disabled
338: }
339:
340: DataStore dataStore;
341:
342: try {
343: dataStore = meta.getDataStore();
344: } catch (IllegalStateException notAvailable) {
345: continue; // not available
346: }
347:
348: LockingManager lockingManager = dataStore
349: .getLockingManager();
350:
351: if (lockingManager == null) {
352: continue; // locks not supported
353: }
354:
355: org.geotools.data.Transaction t = new DefaultTransaction(
356: "Refresh " + meta.getNamesSpacePrefix());
357:
358: try {
359: t.addAuthorization(lockId);
360:
361: if (lockingManager.release(lockId, t)) {
362: refresh = true;
363: }
364: } catch (IOException e) {
365: LOGGER.log(Level.WARNING, e.getMessage(), e);
366: } finally {
367: try {
368: t.close();
369: } catch (IOException closeException) {
370: LOGGER.log(Level.FINEST, closeException
371: .getMessage(), closeException);
372: }
373: }
374: }
375:
376: if (!refresh) {
377: // throw exception? or ignore...
378: }
379: } catch (Exception e) {
380: throw new WFSException(e);
381: }
382: }
383:
384: /**
385: * Release all feature locks currently held.
386: *
387: * <p>
388: * This is the implementation for the Admin "free lock" action, transaction
389: * locks are not released.
390: * </p>
391: *
392: * @return Number of locks released
393: */
394: public void releaseAll() throws WFSException {
395: try {
396: Set dataStores = catalog.getDataStores();
397:
398: for (Iterator i = dataStores.iterator(); i.hasNext();) {
399: DataStoreInfo meta = (DataStoreInfo) i.next();
400:
401: if (!meta.isEnabled()) {
402: continue; // disabled
403: }
404:
405: DataStore dataStore;
406:
407: try {
408: dataStore = meta.getDataStore();
409: } catch (IllegalStateException notAvailable) {
410: continue; // not available
411: } catch (Throwable huh) {
412: continue; // not even working
413: }
414:
415: LockingManager lockingManager = dataStore
416: .getLockingManager();
417:
418: if (lockingManager == null) {
419: continue; // locks not supported
420: }
421:
422: // TODO: implement LockingManger.releaseAll()
423: // count += lockingManager.releaseAll();
424: }
425: } catch (Exception e) {
426: throw new WFSException(e);
427: }
428: }
429:
430: public boolean exists(String lockId) throws WFSException {
431: try {
432: Set dataStores = catalog.getDataStores();
433:
434: for (Iterator i = dataStores.iterator(); i.hasNext();) {
435: DataStoreInfo meta = (DataStoreInfo) i.next();
436:
437: if (!meta.isEnabled()) {
438: continue; // disabled
439: }
440:
441: DataStore dataStore;
442:
443: try {
444: dataStore = meta.getDataStore();
445: } catch (IllegalStateException notAvailable) {
446: continue; // not available
447: }
448:
449: LockingManager lockingManager = dataStore
450: .getLockingManager();
451:
452: if (lockingManager == null) {
453: continue; // locks not supported
454: }
455:
456: if (lockingManager.exists(lockId)) {
457: return true;
458: }
459: }
460:
461: return false;
462: } catch (Exception e) {
463: throw new WFSException(e);
464: }
465: }
466:
467: public void refresh(String lockId) throws WFSException {
468: try {
469: boolean refresh = false;
470:
471: Set dataStores = catalog.getDataStores();
472:
473: for (Iterator i = dataStores.iterator(); i.hasNext();) {
474: DataStoreInfo meta = (DataStoreInfo) i.next();
475:
476: if (!meta.isEnabled()) {
477: continue; // disabled
478: }
479:
480: DataStore dataStore;
481:
482: try {
483: dataStore = meta.getDataStore();
484: } catch (IllegalStateException notAvailable) {
485: continue; // not available
486: }
487:
488: LockingManager lockingManager = dataStore
489: .getLockingManager();
490:
491: if (lockingManager == null) {
492: continue; // locks not supported
493: }
494:
495: org.geotools.data.Transaction t = new DefaultTransaction(
496: "Refresh " + meta.getNamesSpacePrefix());
497:
498: try {
499: t.addAuthorization(lockId);
500:
501: if (lockingManager.refresh(lockId, t)) {
502: refresh = true;
503: }
504: } catch (IOException e) {
505: LOGGER.log(Level.WARNING, e.getMessage(), e);
506: } finally {
507: try {
508: t.close();
509: } catch (IOException closeException) {
510: LOGGER.log(Level.FINEST, closeException
511: .getMessage(), closeException);
512: }
513: }
514: }
515:
516: if (!refresh) {
517: // throw exception? or ignore...
518: }
519: } catch (Exception e) {
520: throw new WFSException(e);
521: }
522: }
523:
524: private FeatureId fid(String fid) {
525: return filterFactory.featureId(fid);
526: }
527:
528: private Id fidFilter(FeatureId fid) {
529: HashSet ids = new HashSet();
530: ids.add(fid);
531:
532: return filterFactory.id(ids);
533: }
534:
535: protected FeatureLock newFeatureLock(LockFeatureType request) {
536: if ((request.getHandle() == null)
537: || request.getHandle().equals("")) {
538: request.setHandle("GeoServer");
539: }
540:
541: if (request.getExpiry() == null) {
542: request.setExpiry(BigInteger.valueOf(0));
543: }
544:
545: int lockExpiry = request.getExpiry().intValue();
546:
547: if (lockExpiry < 0) {
548: // negative time used to query if lock is available!
549: return FeatureLockFactory.generate(request.getHandle(),
550: lockExpiry);
551: }
552:
553: if (lockExpiry == 0) {
554: // perma lock with no expiry!
555: return FeatureLockFactory.generate(request.getHandle(), 0);
556: }
557:
558: // FeatureLock is specified in minutes
559: return FeatureLockFactory.generate(request.getHandle(),
560: lockExpiry * 60 * 1000);
561: }
562: }
|