Hi,

Thanks again for your comments.

Here's the second version of my template class. It should resolves the concurrency issues you mentionned :

package app;

import javax.jcr.Credentials;
import javax.jcr.LoginException;
import javax.jcr.Node;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.lock.LockException;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;

public abstract class SerializableTemplate {

    private Session session;
    private Node scope;
    private boolean done = false;
    private EventListener el;

public SerializableTemplate(Repository repository, Credentials cr, String scopePath) throws LoginException, RepositoryException {
        session = repository.login(cr);
        scope = session.getRootNode().getNode(scopePath);
        //scope = session.getNodeByUUID(scope.getUUID());
    }

public abstract void doInTransaction(Session session) throws RepositoryException;

    public void execute() throws RepositoryException {
        if (tryLock()) {
            return;
        }

        this.el = new EventListener() {
            public void onEvent(EventIterator events) {
                try {
                    tryLock();
                } catch (RepositoryException e) {
                    throw new RuntimeException(e);
                }
            }
        };
session.getWorkspace().getObservationManager().addEventListener(el, Event.PROPERTY_REMOVED, scope.getPath(), true, null, null, false);

//Try again, in case the lock is removed before observer could be put in place
        tryLock();
    }

    private synchronized boolean tryLock() throws RepositoryException {
        try {
            if (done) {
                return false;
            }

            if (!scope.isLocked()) {
                scope.lock(true, true);
                try {
                    if (el != null) {
                        
session.getWorkspace().getObservationManager().removeEventListener(el);
                    }
                    doInTransaction(session);
                } finally {
                    done = true;
                    if (session.isLive()) {
                        session.logout();
                    }
                }
                return true;
            }
        } catch (LockException e) {
            e.printStackTrace();
        }
        return false;
    }
}

Here's how to use it :

SerializableTemplate sTemplate = new SerializableTemplate(repository, new SimpleCredentials("user", "password".toCharArray()), "node/path") {
        //@Override
public void doInTransaction(Session session) throws RepositoryException {
                //Do your favorite transaction...
        };
sTemplate.execute();


For the constructor you suggested, I actually came up with a similiar design at first, but found a problem with it : since the template class might use an EventListener the class should be responsible for closing the session (the EventListener can wait a while...). Else, the event could be removed by the user before being invoked. That's the reason for my ugly constructor.

I welcome your comments again...

Regards,

Nicolas


Le 05:07 2005-07-15, vous avez écrit:
Hi Nicolas,

I can see where you are heading for ;)

I think the constructor take the node that acts as the lock scope. It should provide all that is needed:

public SerializableTemplate(Node scope) {
        session = scope.getSession();
        this.scope = scope;
}

you may then also ommit the session parameter in doInTransaction. And just as a minor improvement the method should be allowed to throw a RepositoryException.

After checking whether a node is locked it is not guaranteed that you can then lock the node. A similar concurrency problem can arise when isLocked() returns true, between that call and the listener registration the node might get unlocked. so, you don't get an event for that and keep waiting.

regards
 marcel

Nicolas Belisle wrote:
I just thought about something like this (Note that I've only done a few tests on that class.) :
public abstract class SerializableTemplate {
    private Session session;
    private Node scope;
public SerializableTemplate(Repository repository, Credentials cr, String scopePath) throws LoginException, RepositoryException {
        session = repository.login(cr);
        scope = session.getRootNode().getNode(scopePath);
    }
    public abstract void doInTransaction(Session session);
    public void execute() {
        try {
            if (!scope.isLocked()) {
                scope.lock(true, true);
                doInTransaction(session);
                if (session.isLive()) {
                    session.logout();
                }
            } else {
                EventListener el = new EventListener() {
                    public void onEvent(EventIterator events) {
                        try {
                            if (!scope.isLocked()) {
                                scope.lock(true, true);
                                doInTransaction(session);
                                if (session.isLive()) {
                                    session.logout();
                                }
                            }
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }
                };

session.getWorkspace().getObservationManager().addEventListener(el, Event.PROPERTY_REMOVED, scope.getPath(), true, null, null, false);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
I don't like a few things about that class, especially the constructor...
What do you think overall ?
> Again I think this always depends on the application on top of the repository. > Setting an isolation level as a global property does not seems to be a good idea to me. Well, if many applications need control of their isolation level, maybe that feature should be implemented in one place or documented in a worked example...
Regards,
Nicolas

Reply via email to