Package org.apache.slide.event

Provides event support for Slide.

See:
          Description

Interface Summary
ContentListener Content listener interface
EventCollectionListener Event collection listener interface
GenericEventListener Generic event listener interface
GlobalListener Global listener interface
LockListener Lock listener interface
MacroListener Macro listener interface
RemoteInformation The RemoteInformation interface.
SearchListener Search listener interface
SecurityListener Security listener interface
StructureListener Structure listener interface
TransactionListener Transaction listener interface
UriModifiedListener Classes that implement this interface listen for UriModifiedEvents.
 

Class Summary
AbstractEventMethod  
ContentAdapter Content adapter class
ContentEvent Content event class
ContentEvent.Create  
ContentEvent.Fork  
ContentEvent.Merge  
ContentEvent.Remove  
ContentEvent.Retrieve  
ContentEvent.Store  
ContentModifiedNotifier Fires an UriModifiedEvent whenever an Uri is modified.
EventCollection Event collection class
EventCollection.Collected  
EventCollection.VetoableCollected  
EventCollectionFilter  
EventDispatcher Event dispatcher class
EventMethod Event firer interface
GenericEvent The GenericEvent class
GenericEvent.EventFired  
GenericEvent.VetoableEventFired  
LockAdapter Lock adapter class
LockEvent Lock event class
LockEvent.Kill  
LockEvent.Lock  
LockEvent.Renew  
LockEvent.Unlock  
MacroAdapter Macro adapter class
MacroEvent Macro event class
MacroEvent.Copy  
MacroEvent.Delete  
MacroEvent.Move  
ResourceEvent  
SearchEvent Search event class
SearchEvent.Search  
SecurityAdapter Security adapter class
SecurityEvent Security event class
SecurityEvent.DenyPermission  
SecurityEvent.GrantPermission  
SecurityEvent.RevokePermission  
StructureAdapter Structure adapter class
StructureEvent Structure event class
StructureEvent.AddBinding  
StructureEvent.Create  
StructureEvent.CreateLink  
StructureEvent.Remove  
StructureEvent.RemoveBinding  
StructureEvent.Retrieve  
StructureEvent.Store  
TransactionEvent Transaction event class
TransactionEvent.Begin  
TransactionEvent.Commit  
TransactionEvent.Commited  
TransactionEvent.Rollback  
TransientEventCollector Transient event collector class
UncacheModifiedUriListener In response to an UriModifiedEvent this listener asks the Uri's Store to remove the Uri from its cache.
UriModifiedEvent Indicates that an Uri has been somehow modified.
UriModifiedEvent.UriModified  
VetoableEventCollector Vetoable event collector class
VetoableEventMethod Vetoable event firer interface
 

Exception Summary
VetoException Vetoable event firer interface
 

Package org.apache.slide.event Description

Provides event support for Slide.

Introduction

Event handling in Slide is similar to the way it is done in awt or swing. So if you are not familiar with the concept of event listeners in general, you should have a look at the appropriate sections in The Java Tutorial

Event dispatching

The event dispatching is done by the EventDispatcher. The event dispatcher is implemented using the singleton pattern, so only one instance can exists per VM.

Adding event listeners

Event listeners can be added by configuring them in the Domain.xml file. Every event listener must implement the EventListener interface or one of its subinterfaces. So if you want to add a listener that wants to receive content events, you have to register a class that implements the ContentListener interface or extends the ContentAdapter, a class that provides an empty implementation of every method so that you just have to override the methods you are interested in.

Event listeners must either follow the singleton pattern or provide a default constructor. So if you want to implement a service that shall be accessed from within the VM you should use the singleton pattern. If this is not necessary just provide a default constructor.

Example

The following example will implement an event listener that will log a message everytime a resource is removed:
public class ExampleListener extends ContentAdapter {
    protected static final String LOG_CHANNEL = ExampleListener.class.getName();

    public void remove(ContentEvent event) {
        Domain.log("Recieved remove event");
    }
}

After implenenting the listener it must be registered. This is done by adding the following lines to the Domain.xml file.

<slide>

 ...

    <!-- Event configuration -->
    <events>

        <!-- Adding our event listener -->
        <listener classname="com.mycompany.listener.ExampleListener" />
    </events>
</slide>
        

Enabling events

The registered logger will not log anything until the events you are listening to are enabled. The events may consume some processing power so they can be enabled very fine grained to achieve maximum performance.

Enabling all events

All events can be switched on by default if the following lines are added to the slide.properties file:

# Events
# Default: false
org.apache.slide.events=true

Customizing events

Now every event is fired! This causes a lot of event traffic, so this might no be exactly what we want. We can start from this situation and disable events that we don't like. Here is an example:

<slide>

    ...

    <!-- Event configuration -->
    <events>
        <!-- Disable all events that are fired, when webdav methods are invoked -->
        <event classname="org.apache.slide.webdav.event.WebdavEvent" enable="false"/>

        <!-- Disable all lock event -->
        <event classname="org.apache.slide.event.LockEvent" enable="false"/>

        <!-- Disable all other events... -->

        <!-- Disable all content event... -->
        <event classname="org.apache.slide.event.ContentEvent" enable="false"/>

        <!-- ...but enable the "remove" method -->
        <event classname="org.apache.slide.event.ContentEvent" method="remove" enable="true"/>

        <!-- Adding our event listener -->
        <listener classname="com.mycompany.listener.ExampleListener" />
    </events>
</slide>
        

You can of course do it the other way round: Simply disable all events be default:

# Events
# Default: false
org.apache.slide.events=false

Now we have to switch on the event we are interested in:

<slide>

    ...

    <!-- Event configuration -->
    <events>
        <!-- Enable only the "remove" method -->
        <event classname="org.apache.slide.event.ContentEvent" method="remove" enable="true"/>

        <!-- Adding our event listener -->
        <listener classname="com.mycompany.listener.ExampleListener" />
    </events>
</slide>
        

This approach is preferrable in our scenario because the configuration is a lot shorter and we will never overlook any events that might be fired from some module.

Configuring event listeners

There might be the need for configuring the event listeners. The can be done by implementing the Configurable interface. Let's do this with our listener to see how it works.

Example

The following example add the implementation of the Configurable to our listener. The message that will be logged shall be configurable:

public class ExampleListener extends ContentAdapter implements Configurable {
    protected static final String LOG_CHANNEL = ExampleListener.class.getName();

    private String logMessage;

    public void remove(ContentEvent event) {
        Domain.log(logMessage);
    }

    public void configure(Configuration configuration) throws ConfigurationException {
        configuration.getConfiguration("log-message").getValue();
    }
}

Now we can configure the log message of our listener by adding the following line to Domain.xml:

<slide>

    ...

    <!-- Event configuration -->
    <events>
        <!-- Enable only the "remove" method -->
        <event classname="org.apache.slide.event.ContentEvent" method="remove" enable="true"/>

        <!-- Adding our event listener -->
        <listener classname="com.mycompany.listener.ExampleListener" >
            <configuration>
                <log-message>Recieved another remove event</log-message>
            </configuration>
        </listener>
    </events>
</slide>
        

The configuration of listeners can be very sophisticated, because they use the configuration mechanism that is used by slide.

Vetoable events

Almost every event listener method that is called within a transaction is vetaoable. Event listeners can throw a VetoException, if they want to enforce the transaction to be rolled back.

This feature allows to implement sophisticated listeners. We want to extend our ExampleListener, so that it denies the storage of all documents that are of the content-type image/gif:

public class ExampleListener extends ContentAdapter implements Configurable {
    protected static final String LOG_CHANNEL = ExampleListener.class.getName();

    private String logMessage;

    public void remove(ContentEvent event) {
        Domain.log(logMessage);
    }

    public void store(ContentEvent event) throws VetoException {
        if ( event.getRevisionDescriptor().getContentType().equals("image/gif") ) {
            throw new VetoException("Storage of gif-images denied!");
        }
    }

    public void configure(Configuration configuration) throws ConfigurationException {
        configuration.getConfiguration("log-message").getValue();
    }
}

Event collections

In some cases it might be valuable to listen to all events that are fired in a transaction as a collection. This can be the case when for example referencial integrity shall be checked. What if a resource is deleted and recreated afterwards? If the references would be checked after each event, the removal of a used resource would fail, even if it will be recreated in the same transaciton. So this is a case, where listening on event collections is a must.

How to listen on collections?

First we have to register the event collector that we prefer. If we want to implement a reference checker, we need a vetoable collection, because we want to deny the deletion of used resources. This can be done by registering the VetoableEventCollector as an event listener.

If transaction events are disabled or no event collector is registered, no event collections are fired.

This event collector fires an EventCollection just before the transaction is commited. We can still abort the transaction by throwing a VetoException.

Now lets extend our listener, so that it will listen to this vetoable collection event:

public class ExampleListener extends ContentAdapter implements EventCollectionListener, Configurable {
    protected static final String LOG_CHANNEL = ExampleListener.class.getName();

    private String logMessage;

    public void remove(ContentEvent event) {
        Domain.log(logMessage);
    }

    public void store(ContentEvent event) throws VetoException {
        if ( event.getRevisionDescriptor().getContentType().equals("image/gif") ) {
            throw new VetoException("Storage of gif-images denied!");
        }
    }

    public void configure(Configuration configuration) throws ConfigurationException {
        configuration.getConfiguration("log-message").getValue();
    }


    public void vetoableCollected(EventCollection collection) throws VetoException {
        ContentEvent[] removedContents = EventCollectionFilter.getRemovedContents(collection);
        for ( int i = 0; i < removedContents.length; i++ ) {
            if ( ReferenceManager.isUsed(removedContents[i].getRevisionDescriptor())) {
                throw new VetoException("Content with Uri='"+removedContents[i].getRevisionDescriptors().getUri()+"' is used!");
            }
        }
    }

    public void collected(EventCollection collection) {
        // do nothing
    }
}

As state before we have not only to register the event collector, but also have to enable the transaction events, because the event collectors rely on transaction event!

So the Domain.xml might look like this

<slide>

    ...

    <!-- Event configuration -->
    <events>
        <!-- Enable transaction events -->
        <event classname="org.apache.slide.event.TransactionEvent" enable="true"/>

        <!-- Enable only the "remove" method -->
        <event classname="org.apache.slide.event.ContentEvent" method="remove" enable="true"/>

        <!-- Enable only the "store" method -->
        <event classname="org.apache.slide.event.ContentEvent" method="store" enable="true"/>

        <!-- Adding the vetoable event collector -->
        <listener classname="org.apache.slide.event.VetoableEventCollector" />

        <!-- Adding our event listener -->
        <listener classname="com.mycompany.listener.ExampleListener" >
            <configuration>
                <log-message>Recieved another remove event</log-message>
            </configuration>
        </listener>
    </events>
</slide>
        

Collection filters

If you have read the source code of our listener carefully, you might have seen that it makes use of the EventCollectionFilter. This class provides some useful static methods to filter a collection of events.

We want to check all removed resources, so the used method filters out all events that are revoked by a corresponding content creation event. In other words: If a resource is remove and recreated within the same transaction, this remove event will not be part of the given event array.

Global event listeners

In some cases it might be needed to listen to every event that occurs. If you want to implement a logging on basis of events, this could be the case.

For scenarios like this, there is an interface called GlobalListener. This interface provides methods for listening on every event.

We will extend our listener so that it logs every event that is enabled:

public class ExampleListener extends ContentAdapter implements GlobalListener, EventCollectionListener, Configurable {
    protected static final String LOG_CHANNEL = ExampleListener.class.getName();

    private String logMessage;

    public void remove(ContentEvent event) {
        Domain.log(logMessage);
    }

    public void store(ContentEvent event) throws VetoException {
        if ( event.getRevisionDescriptor().getContentType().equals("image/gif") ) {
            throw new VetoException("Storage of gif-images denied!");
        }
    }

    public void configure(Configuration configuration) throws ConfigurationException {
        configuration.getConfiguration("log-message").getValue();
    }


    public void vetoableCollected(EventCollection collection) throws VetoException {
        ContentEvent[] removedContents = EventCollectionFilter.getRemovedContents(collection);
        for ( int i = 0; i < removedContents.length; i++ ) {
            if ( ReferenceManager.isUsed(removedContents[i].getRevisionDescriptor())) {
                throw new VetoException("Content with Uri='"+removedContents[i].getRevisionDescriptors().getUri()+"' is used!");
            }
        }
    }

    public void collected(EventCollection collection) {
        // do nothing
    }

    public void vetoableEventFired(VetoableEventMethod method, EventObject event) throws VetoException {
        Domain.log("Recieved vetoable event with name '"+method.getId()+"': "+event, LOG_CHANNEL, Logger.INFO);
    }

    public void eventFired(EventMethod method, EventObject event) {
        Domain.log("Recieved event with name '"+method.getId()+"': "+event, LOG_CHANNEL, Logger.INFO);
    }
}

The method org.apache.slide.event.GlobalListener#vetoableEventFired(EventMethod,EventObject) is called, when a vetoable event occurs. The method GlobalListener.eventFired(EventMethod,EventObject) is called when non vetoable events are fired.

Beside the event object itself, this method gets the EventMethod that occured.

We can use this method to identify which method on the appropriate listener would be called. Let's use this feature handle all events without extending the content adapter anymore:

public class ExampleListener implements GlobalListener, EventCollectionListener, Configurable {
    protected static final String LOG_CHANNEL = ExampleListener.class.getName();

    private String logMessage;

    public void configure(Configuration configuration) throws ConfigurationException {
        configuration.getConfiguration("log-message").getValue();
    }


    public void vetoableCollected(EventCollection collection) throws VetoException {
        ContentEvent[] removedContents = EventCollectionFilter.getRemovedContents(collection);
        for ( int i = 0; i < removedContents.length; i++ ) {
            if ( ReferenceManager.isUsed(removedContents[i].getRevisionDescriptor())) {
                throw new VetoException("Content with Uri='"+removedContents[i].getRevisionDescriptors().getUri()+"' is used!");
            }
        }
    }

    public void collected(EventCollection collection) {
        // do nothing
    }

    public void vetoableEventFired(VetoableEventMethod method, EventObject event) throws VetoException {
        if ( method == ContentEvent.STORE ) && event.getRevisionDescriptor().getContentType().equals("image/gif") ) {
            throw new VetoException("Storage of gif-images denied!");
        } else if ( method == ContentEvent.REMOVE ) {
            Domain.log(logMessage);
        }
        Domain.log("Recieved vetoable event with name '"+method.getId()+"': "+event, LOG_CHANNEL, Logger.INFO);
    }

    public void eventFired(EventMethod method, EventObject event) {
        Domain.log("Recieved event with name '"+method.getId()+"': "+event, LOG_CHANNEL, Logger.INFO);
    }
}

In some scenarios it makes sense to handle events in this way.

Custom events

It is possible to introduce new events and listener types without the need to modify any class in the slide kernel. This feature is for example used in the webdav part. The slide core doesn't know anything about the exisitence of the webdav events, but they can be used and configured in the same way as core events.

Let's implement a new event to see how this works. Imagine a web portal where users can log in to access a registered user area. We want to create a session event that will be fired, when a user tries to log in or a user has logged out.

We want to give listeners the ability to veto the login, for example to be able to build a pluggable user authentication.

The logout event will be fired after logout, so this one is not vetoable

The code for our event looks like this:

public class SessionEvent extends EventObject {
    public final static Login LOGIN = new Login();
    public final static LoggedOut LOGOUT = new Logout();

    public final static String GROUP = "session";
    public final static AbstractEventMethod[] methods = new AbstractEventMethod[] { LOGIN, LOGOUT };

    private Principal user;

    public TransactionEvent(Object source, Principal user) {
        super(source);
        this.user = user;
    }

    public Principal getPrincipal() {
        return user;
    }

    public static class Login extends VetoableEventMethod {
        public Login() {
            super(GROUP, "login");
        }

        public void fireVetaoableEvent(EventListener listener, EventObject event) throws VetoException {
            if ( listener instanceof SessionListener ) ((SessionListener)listener).login((SessionEvent)event);
        }
    }

    public static class Logout extends EventMethod {
        public Logout() {
            super(GROUP, "logout");
        }

        public void fireEvent(EventListener listener, EventObject event) {
            if ( listener instanceof SessionListener ) ((SessionListener)listener).logout((SessionEvent)event);
        }
    }
}

Use this code as a template if you want to implement custom events.

Notes:

After implenting the event we also need a corresponding event listener. The interface for our session event looks like this:

public interface SessionListener extends EventListener {
    public void login(SessionEvent event) throws VetoException;

    public void logout(SessionEvent event);
}

Now we have to fire the events at the appropriate location in our application. This might look like this:

// somewhere in the login method of our application
try {
    if ( SessionEvent.LOGIN.isEnabled() ) {
        EventDispatcher.getInstance().fireVetoableEvent(SessionEvent.LOGIN, new SessionEvent(this, principal));
    }
} catch ( VetoException exception ) {
    // Abort the login process
}

...

// somewhere after user logged out
if ( SessionEvent.LOGOUT.isEnabled() ) {
    EventDispatcher.getInstance().fireEvent(SessionEvent.LOGOUT, new SessionEvent(this, principal));
}

Now the events are fired and session listeners can be implemented and configured in the same way as described above.