This page last changed on Jun 11, 2007 by aaime.

Overview

WFS Transaction handling is the core of the transactional WFS operation. So far it has been a monolith block of code. This proposal tries to make it extensible in the elements it can handle, and it the "side" processing and verification that can be triggered by a transaction.

Proposed By

Andrea Aime

Proposal Type

Module change

Assigned to release

1.6.0

State

being discussed, in progress, complete, rejected, deferred

Links

JIRA task: GEOS-1071

Email discussion:

Other wiki discussions:

Voting history

Andrea Aime +1
[~rob_cto_sco] +1
Justin Deoliveira +1
Jody Garnett +1
Chris Holmes +1
Gabriel Roldán +1

Motivations

WFS versioning needs to process a few new transaction elements: this is main driver for this change.
At the same time, this is a good occasion to refactor the Transaction.execute() method, that has grown to become an unmanageably big block of code.
On the other side, it's well known that we may want to extend transaction processing in other directions, so we need ways to listen and verify the transaction processing itself:

  • a validation subsystem needs to listen to changes and eventually veto them by failing the transaction
  • a georss subsystem needs to be informed of any change in order to publish them
  • a tile cache subsystem needs to listen to changes in order to decide which tiles are to be expired

Both element processing and transaction listening need to be open ended.

Proposal

Create a small framework where transaction processing elements, as well as transaction listeners, can be looked up in the Spring context and hooked to the transaction.

Implementation

Static view

Implementation boils down to having a transaction listener interface, a transaction plugin interface, and an element handler interface.

The transaction listening interface allows to listen to changes the element processing is making, either before and after the change occurs (depending on change nature).
In particular, insert events will be notified only before they happen, udpate both before and after (with before and after states), and delete only after.
Geotools has two similar interfaces:

  • the FeatureListener one, which cannot be used since it only allows to gather the bounds of the change
  • the CollectionListener one, that provides a list of changed features, which cannot handle big changes since it relies on storing changed features in memory and returning them as an array. A quick scan seems to suggest the collection notification mechanism is not implemented properly, too (for example, subclasses of DataFeatureCollection do not seem to use the notification mechanism, whilst DefaultFeatureCollection apparently use them properly but it's only used for in memory collections).
/**
 * The enumeration of transaction event types
 */
public class TransactionEventType extends SimpleEnumerationType {
    private static final long serialVersionUID = -4218786755116808448L;

    private static final List VALUES = new ArrayList(5);

    /**
     * Notification of inserted features, after insertion occurred
     * (collection contains newly inserted features)
     */
    public TransactionEventType POST_INSERT = new TransactionEventType("PostInsert",
            "Features just inserted");

    /**
     * Notification of updated features, before update occurs 
     * (collection contains original values)
     */
    public TransactionEventType PRE_UPDATE = new TransactionEventType("PreUpdate",
            "Feature values before update");

    /**
     * Notification of updated features, after update occurs
     * (collection contains updated features)
     */
    public TransactionEventType POST_UPDATE = new TransactionEventType("PostUpdate",
            "Feature values after update");

    /**
     * Notificaiton of deleted features, before deletion occurs
     * (collection contains features that will be deleted)
     */
    public TransactionEventType POST_DELETE = new TransactionEventType("PostDelete",
            "Features just deleted");

    protected TransactionEventType(String name, String description) {
        super(VALUES, name, description);
    }

    public CodeList[] family() {
        synchronized (VALUES) {
            return (TransactionEventType[]) VALUES.toArray(new TransactionEventType[VALUES.size()]);
        }
    }
}

/**
 * Event carrying information about a change that happened/that is about to
 * occur. The feature collection may be an in-memory one, or may be based
 * on a real datastore with a filter.
 */
public interface TransactionEvent {
   /**
    * The type of change occurring
    */
   public TransactionEventType getType();
   
   /**
    * A collection of the features that are being manipulated. 
    */
   public FeatureCollection getAffectedFeatures();
}

/**
 * Implemented by classes needing to listen to datastore change events
 * during a WFS Transaction
 */
public interface TransactionListener {
   /**
    * Check/alter feature collections and filters before a 
    * change hits the datastores
    */
   void dataStoreChange(TransactionEvent event) throws WFSException;
}

The TransactionPlugin interface must be implemented by anyone needing to get involved in the trasaction processing and have a way to tell if the transaction need to be aborted, as well as get notified of transaction start and end, allowing for batch handling of changes:

/**
 * A transaction plugin is able to listen to a transaction evolution,
 * perform checks and throw exceptions, alter transaction requests, as 
 * well as 
interface TransactionPlugin extends TransactionListener {
   /**
    * Check/alter the transaction request elements
    */
   TransactionType beforeTransaction(TransactionType request) throws WFSExceptoin;

   /**
    * Say the last word before we actually commit the transaction
    */
   void beforeCommit() throws WFSEXception
   
   /**
    * Notification the transaction ended
    * @param committed true if the transaction was successful, false if the
    *        transaction was aborted for any reason
    */
   void afterTransaction(boolean committed);

   /**
    * Aspects gets called in a specific order. State your priority, the higher
    * the number, the earlier the plugin will be processed.
    */
   int getPriority();
}

Finally, transaction element handlers need to implement this interface:

/**
 * Transaction elements are an open ended set, both thanks to the Native element
 * type, and to the XSD sustitution group concept (xsd inheritance). Element
 * handlers know how to process a certain element in a wfs transaction request.
 * 
 * @author Andrea Aime - TOPP
 * 
 */
public interface TransactionElementHandler {
    /**
     * Returns the element class this handler can proces
     */
    Class getElementClass();

    /**
     * Returns the qualified names of feature types needed to handle this
     * element
     */
    QName[] getTypeNames(EObject element) throws WFSTransactionException;

    /**
     * Checks the element content is valid, throws an exception otherwise
     * 
     * @param element
     *            the transaction element we're checking
     * @param featureTypeInfos
     *            a map from {@link QName} to {@link FeatureTypeInfo}, where
     *            the keys contain all the feature type names reported by
     *            {@link #getTypeNames(EObject)}
     */
    void checkValidity(EObject element, Map featureTypeInfos) throws WFSTransactionException;

    /**
     * Executes the element against the provided feature sources
     * 
     * @param element
     *            the tranaction element to be executed
     * @param request
     *            the transaction request
     * @param featureStores
     *            map from {@link QName} to {@link FeatureStore}, where the
     *            keys do contain all the feature type names reported by
     *            {@link #getTypeNames(EObject)}
     * @param response
     *            the transaction response, that the element will update
     *            according to the processing done
     * @param listener
     *            a transaction listener that will be called before and after
     *            each change performed against the data stores
     */
    void execute(EObject element, TransactionType request, Map featureStores,
           TransactionListener listener, TransactionResponseType response) throws WFSTransactionException;
}

Same as above, depicted as a class diagram:

Dyamic view

When transaction processing starts the Transaction class will:

  • Look up every TransactionListener and TransactionPlugin in the Spring context, store them in two lists, sort plugin according to priority
  • Make plugins process the beforeTransaction() event
  • Look up TransactionElementHandler for each transaction element, making sure each XML element is matched by the most appropriate handler (using nearest superclass approach)
  • Ask each TransactionElementHandler for used feature type names, and look up the FeatureTypeInfo
  • Ask each TransactionElementHandler to check its validity
  • Lookup the feature stores needed to process the transaction
  • Check lock authorizations
  • Have each element do its own processing. A single multiplexing transaction linstender is provided to element handlers, and it will take care of bouncing the event on each transaction linstener and plugin.
  • If all goes fine, notify plugins we're about to commit
  • Commit
  • Notify plugins the transaction succesfully committed.

In the event of failure, plugins will be notified the transaction did not complete successfully.

Open issues and observations

Observations:

  • Security may be thought as a transaction plugin, but it's better to avoid this temptation. Security restrictions may be imposed on the read side too, either by making some feature types invisible, or by removing some prorties, or by filtering out data. This is not a transaction specific issue, but a system wide one dealing with generic data access, as such, it must be handled at a catalog level (eventually, as wrapper or as a plugin of catalog handling).

Risks

This is a significant change in transaction processing, as such it may introduce bugs. WFS 1.0 and WFS 1.1 CITE tests will mitigate this risk.

Participants

Andrea Aime


Document generated by Confluence on May 14, 2014 23:00