add transaction related classes

git-svn-id: https://svn.apache.org/repos/asf/lenya/trunk@1034507 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/ConcurrentModificationException.java b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/ConcurrentModificationException.java
new file mode 100644
index 0000000..35b2bf8
--- /dev/null
+++ b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/ConcurrentModificationException.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.transaction;
+
+import org.apache.commons.lang.Validate;
+
+/**
+ * Exception which is thrown when a different identity changed a transactionable
+ * object in an optimistic offline lock scenario.
+ */
+public class ConcurrentModificationException extends TransactionException {
+
+    private Transactionable transactionable;
+
+    public ConcurrentModificationException(Transactionable t) {
+        super();
+        Validate.notNull(t);
+        this.transactionable = t;
+    }
+
+    public String getMessage() {
+        return "The object [" + this.transactionable + "] was modified after it has been locked.";
+    }
+
+    /**
+     * @return The transactionable that was modified by a different identity.
+     */
+    public Transactionable getTransactionable() {
+        return this.transactionable;
+    }
+
+}
diff --git a/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/IdentifiableFactory.java b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/IdentifiableFactory.java
new file mode 100644
index 0000000..2a4b6b8
--- /dev/null
+++ b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/IdentifiableFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.transaction;
+
+/**
+ * Factory for identifiables.
+ *
+ * @version $Id$
+ */
+public interface IdentifiableFactory {
+
+    /**
+     * Builds an identifiable.
+     * @param map The identity map.
+     * @param key The key.
+     * @return An identifiable object.
+     * @throws Exception if an error occurs.
+     */
+    Object build(IdentityMap map, String key) throws Exception;
+    
+    /**
+     * @return The type of the identifables to produce.
+     */
+    String getType();
+    
+}
diff --git a/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/Identity.java b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/Identity.java
new file mode 100644
index 0000000..cbe0044
--- /dev/null
+++ b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/Identity.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.transaction;
+
+public interface Identity {
+    
+    String getUserId();
+
+}
diff --git a/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/IdentityMap.java b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/IdentityMap.java
new file mode 100644
index 0000000..4e354d3
--- /dev/null
+++ b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/IdentityMap.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.transaction;
+
+/**
+ * Identity map.
+ * 
+ * @version $Id$
+ */
+public interface IdentityMap {
+
+    /**
+     * Retrieve an instance from the map. If no instance exists
+     * for the given key, the factory is used to build one.
+     * @param factory The factory that produces the identifable.
+     * @param key The key for the identifiable.
+     * @return An identifiable.
+     */
+    Object get(IdentifiableFactory factory, String key);
+
+    /**
+     * Returns the unit of work. This maybe <code>null</code> if the identity map is not involved
+     * in a transaction.
+     * @return The unit of work.
+     */
+    UnitOfWork getUnitOfWork();
+
+    /**
+     * @param unit The unit of work to use.
+     */
+    void setUnitOfWork(UnitOfWork unit);
+    
+    /**
+     * @return All objects in this map.
+     */
+    Object[] getObjects();
+
+}
diff --git a/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/IdentityMapImpl.java b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/IdentityMapImpl.java
new file mode 100644
index 0000000..b7b8c01
--- /dev/null
+++ b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/IdentityMapImpl.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.transaction;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Identity map implementation.
+ * 
+ * @version $Id$
+ */
+public final class IdentityMapImpl implements IdentityMap {
+    
+    private static final Log logger = LogFactory.getLog(IdentityMapImpl.class);
+
+    private Map maps = Collections.synchronizedMap(new HashMap());
+    
+    public Object get(IdentifiableFactory factory, String key) {
+        String type = factory.getType();
+        Map map = (Map) this.maps.get(type);
+        if (map == null) {
+            map = Collections.synchronizedMap(new HashMap());
+            this.maps.put(type, map);
+        }
+        Object object = map.get(key);
+
+        if (logger.isDebugEnabled())
+            logger.debug("IdentityMapImpl::get() looked up type [" + type + "], key [" + key
+                    + "] in map, is it there ? " + (object != null));
+
+        if (object == null) {
+            try {
+                object = factory.build(this, key);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+            map.put(key, object);
+        }
+        return object;
+    }
+
+    private UnitOfWork unitOfWork;
+
+    /**
+     * @see org.apache.lenya.transaction.IdentityMap#getUnitOfWork()
+     */
+    public UnitOfWork getUnitOfWork() {
+        return this.unitOfWork;
+    }
+
+    /**
+     * @see org.apache.lenya.transaction.IdentityMap#setUnitOfWork(org.apache.lenya.transaction.UnitOfWork)
+     */
+    public void setUnitOfWork(UnitOfWork unit) {
+        this.unitOfWork = unit;
+    }
+
+    /**
+     * @see org.apache.lenya.transaction.IdentityMap#getObjects()
+     */
+    public Object[] getObjects() {
+        Set objects = new HashSet();
+        for (Iterator i = this.maps.values().iterator(); i.hasNext();) {
+            Map map = (Map) i.next();
+            for (Iterator j = map.values().iterator(); j.hasNext();) {
+                objects.add(j.next());
+            }
+        }
+        return (Object[]) objects.toArray(new Object[objects.size()]);
+    }
+
+}
diff --git a/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/Lock.java b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/Lock.java
new file mode 100644
index 0000000..1be4397
--- /dev/null
+++ b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/Lock.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.transaction;
+
+/**
+ * A lock on a transactionable.
+ *
+ * @version $Id$
+ */
+public class Lock {
+
+    /**
+     * Ctor.
+     * @param version The version of the transactionable when it was locked.
+     */
+    protected Lock(int version) {
+        this.version = version;
+    }
+    
+    private int version;
+    
+    /**
+     * @return The version number.
+     */
+    public int getVersion() {
+        return this.version;
+    }
+    
+}
diff --git a/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/LockException.java b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/LockException.java
new file mode 100644
index 0000000..2a46cad
--- /dev/null
+++ b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/LockException.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.transaction;
+
+/**
+ * Lock exception.
+ *
+ * @version $Id$
+ */
+public class LockException extends TransactionException {
+
+    /**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+	/**
+     * Ctor.
+     */
+    public LockException() {
+        super();
+    }
+    /**
+     * Ctor.
+     * @param message The message.
+     */
+    public LockException(String message) {
+        super(message);
+    }
+    /**
+     * Ctor.
+     * @param message The message.
+     * @param cause The cause.
+     */
+    public LockException(String message, Throwable cause) {
+        super(message, cause);
+    }
+    /**
+     * Ctor.
+     * @param cause The cause.
+     */
+    public LockException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/Lockable.java b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/Lockable.java
new file mode 100644
index 0000000..130885c
--- /dev/null
+++ b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/Lockable.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.transaction;
+
+/**
+ * Object that can be locked.
+ *
+ * @version $Id$
+ */
+public interface Lockable {
+    
+    /**
+     * Locks this object.
+     * @throws TransactionException if an error occurs.
+     */
+    void lock() throws TransactionException;
+
+    /**
+     * Unlocks this object.
+     * @throws TransactionException if an error occurs.
+     */
+    void unlock() throws TransactionException;
+
+    /**
+     * @return if this object is locked.
+     * @throws TransactionException if an error occurs.
+     */
+    boolean isLocked() throws TransactionException;
+
+    /**
+     * @return The lock which is held by this object.
+     */
+    Lock getLock();
+
+}
\ No newline at end of file
diff --git a/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/TransactionException.java b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/TransactionException.java
new file mode 100644
index 0000000..238f846
--- /dev/null
+++ b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/TransactionException.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.transaction;
+
+/**
+ * Transaction exception.
+ *
+ * @version $Id$
+ */
+public class TransactionException extends Exception {
+
+    /**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+	/**
+     * Ctor.
+     */
+    public TransactionException() {
+        super();
+    }
+    /**
+     * Ctor.
+     * @param message The message.
+     */
+    public TransactionException(String message) {
+        super(message);
+    }
+    /**
+     * Ctor.
+     * @param message The message.
+     * @param cause The cause.
+     */
+    public TransactionException(String message, Throwable cause) {
+        super(message, cause);
+    }
+    /**
+     * Ctor.
+     * @param cause The cause.
+     */
+    public TransactionException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/TransactionLock.java b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/TransactionLock.java
new file mode 100644
index 0000000..14c273b
--- /dev/null
+++ b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/TransactionLock.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.transaction;
+
+/**
+ * Global transaction lock.
+ */
+public class TransactionLock {
+
+    /**
+     * The global transaction lock.
+     */
+    public static final Object LOCK = TransactionLock.class;
+    
+}
diff --git a/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/Transactionable.java b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/Transactionable.java
new file mode 100644
index 0000000..4cbecb2
--- /dev/null
+++ b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/Transactionable.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.transaction;
+
+/**
+ * Object to take part in a transaction.
+ *
+ * @version $Id$
+ */
+public interface Transactionable extends Versionable {
+
+    /**
+     * Saves the object.
+     * @throws TransactionException if an error occurs.
+     */
+    void saveTransactionable() throws TransactionException;
+
+    /**
+     * Deletes the object.
+     * @throws TransactionException if an error occurs.
+     */
+    void deleteTransactionable() throws TransactionException;
+    
+    /**
+     * Creates the object.
+     * @throws TransactionException if an error occurs.
+     */
+    void createTransactionable() throws TransactionException;
+    
+}
diff --git a/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/UnitOfWork.java b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/UnitOfWork.java
new file mode 100644
index 0000000..2f5d13c
--- /dev/null
+++ b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/UnitOfWork.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.transaction;
+
+/**
+ * This is a "Unit of Work" object (see "Unit of Work" pattern by Martin Fowler, 
+ * <a href="http://www.martinfowler.com/eaaCatalog/unitOfWork.html">
+ *   http://www.martinfowler.com/eaaCatalog/unitOfWork.html
+ * </a>: the unit of work "maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems".
+ * 
+ * <p>In the current design, this interface allows a use case to generate documents, while ensuring that only one instance of a document is created. This access is provided by the DocumentIdentityMap's DocumentFactory.</p>
+ *
+ * <p>This interface may be extended in the future to allow for access to further types of business objects.</p>
+ * 
+ * @version $Id$
+ */
+public interface UnitOfWork {
+
+    /**
+     * Registers an object as new.
+     * @param object The object.
+     * @throws TransactionException if an error occurs.
+     */
+    void registerNew(Transactionable object) throws TransactionException;
+    
+    /**
+     * Registers an object as modified.
+     * @param object The object.
+     * @throws TransactionException if an error occurs.
+     */
+    void registerDirty(Transactionable object) throws TransactionException;
+    
+    /**
+     * Registers an object as removed.
+     * @param object The object.
+     * @throws TransactionException if an error occurs.
+     */
+    void registerRemoved(Transactionable object) throws TransactionException;
+    
+    /**
+     * Commits the transaction.
+     * @throws TransactionException if an error occurs.
+     */
+    void commit() throws TransactionException;
+    
+    /**
+     * Rolls the transaction back.
+     * @throws TransactionException if an error occurs.
+     */
+    void rollback() throws TransactionException;
+    
+    /**
+     * @param transactionable A transactionable.
+     * @return If the transactionable is registered as dirty.
+     */
+    boolean isDirty(Transactionable transactionable);
+    
+    /**
+     * Creates a lock.
+     * @param lockable The lockable.
+     * @param version The version.
+     * @return A lock.
+     * @throws TransactionException if a lock is already placed on this transactionable.
+     */
+    Lock createLock(Lockable lockable, int version) throws TransactionException;
+    
+    /**
+     * Removes a lock.
+     * @param lockable The lockable.
+     * @throws TransactionException if no lock is placed on this transactionable.
+     */
+    void removeLock(Lockable lockable) throws TransactionException;
+
+}
diff --git a/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/UnitOfWorkImpl.java b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/UnitOfWorkImpl.java
new file mode 100644
index 0000000..b48a138
--- /dev/null
+++ b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/UnitOfWorkImpl.java
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.transaction;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.Validate;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Default implementation of a unit of work.
+ * 
+ * @version $Id$
+ */
+public class UnitOfWorkImpl implements UnitOfWork {
+    
+    private static final Log logger = LogFactory.getLog(UnitOfWorkImpl.class);
+
+    /**
+     * Ctor.
+     * @param map The identity map to use.
+     * @param identity The identity.
+     */
+    public UnitOfWorkImpl(IdentityMap map, Identity identity)
+    {
+    	Validate.notNull(map);
+        this.identityMap = map;
+        this.identityMap.setUnitOfWork(this);
+
+        this.identity = identity;
+    }
+
+    private IdentityMap identityMap;
+
+    /**
+     * @return The identity map.
+     */
+    public IdentityMap getIdentityMap() {
+        return this.identityMap;
+    }
+
+    private Set newObjects = new HashSet();
+    private Set modifiedObjects = new HashSet();
+    private Set removedObjects = new HashSet();
+
+    /**
+     * @see org.apache.lenya.transaction.UnitOfWork#registerNew(org.apache.lenya.transaction.Transactionable)
+     */
+    public void registerNew(Transactionable object) throws TransactionException {
+        this.newObjects.add(object);
+    }
+
+    /**
+     * @throws TransactionException
+     * @throws LockException
+     * @see org.apache.lenya.transaction.UnitOfWork#registerDirty(org.apache.lenya.transaction.Transactionable)
+     */
+    public void registerDirty(Transactionable object) throws TransactionException {
+        this.modifiedObjects.add(object);
+    }
+
+    /**
+     * @see org.apache.lenya.transaction.UnitOfWork#registerRemoved(org.apache.lenya.transaction.Transactionable)
+     */
+    public void registerRemoved(Transactionable object) throws TransactionException {
+        this.removedObjects.add(object);
+    }
+
+    /**
+     * Commit the transaction. We lock this method for the whole class to avoid synchronization
+     * problems.
+     * @see org.apache.lenya.transaction.UnitOfWork#commit()
+     */
+    public void commit() throws TransactionException {
+        if (logger.isDebugEnabled()) {
+            logger.debug("UnitOfWorkImpl::commit() called");
+        }
+
+        Set lockedObjects = this.locks.keySet();
+
+        for (Iterator i = lockedObjects.iterator(); i.hasNext();) {
+            Transactionable t = (Transactionable) i.next();
+            if (t.hasChanged()) {
+                throw new ConcurrentModificationException(t);
+            }
+        }
+
+        Set involvedObjects = new HashSet();
+        involvedObjects.addAll(this.newObjects);
+        involvedObjects.addAll(this.modifiedObjects);
+        involvedObjects.addAll(this.removedObjects);
+
+        try {
+            for (Iterator i = involvedObjects.iterator(); i.hasNext();) {
+                Transactionable t = (Transactionable) i.next();
+                t.checkout();
+            }
+
+            for (Iterator i = this.newObjects.iterator(); i.hasNext();) {
+                Transactionable t = (Transactionable) i.next();
+                t.createTransactionable();
+                t.saveTransactionable();
+            }
+            for (Iterator i = this.modifiedObjects.iterator(); i.hasNext();) {
+                Transactionable t = (Transactionable) i.next();
+                if (logger.isDebugEnabled()) {
+                    logger.debug("UnitOfWorkImpl::commit() calling save on [" + t + "]");
+                }
+                t.saveTransactionable();
+            }
+            for (Iterator i = this.removedObjects.iterator(); i.hasNext();) {
+                Transactionable t = (Transactionable) i.next();
+                t.deleteTransactionable();
+            }
+
+        } finally {
+            if (getIdentityMap() != null) {
+                Object[] objects = getIdentityMap().getObjects();
+                for (int i = 0; i < objects.length; i++) {
+                    if (objects[i] instanceof Transactionable) {
+                        Transactionable t = (Transactionable) objects[i];
+                        if (t.isCheckedOutBySession() && !this.removedObjects.contains(t)) {
+                            t.checkin();
+                        }
+                        if (t.isLocked()) {
+                            t.unlock();
+                        }
+                    }
+                }
+            }
+        }
+
+        resetTransaction();
+
+    }
+
+    protected void resetTransaction() {
+        this.modifiedObjects.clear();
+        this.newObjects.clear();
+        this.removedObjects.clear();
+    }
+
+    private Identity identity;
+
+    protected Identity getIdentity() {
+        return this.identity;
+    }
+
+    /**
+     * @see org.apache.lenya.transaction.UnitOfWork#isDirty(org.apache.lenya.transaction.Transactionable)
+     */
+    public boolean isDirty(Transactionable transactionable) {
+        return this.modifiedObjects.contains(transactionable)
+                || this.newObjects.contains(transactionable)
+                || this.removedObjects.contains(transactionable);
+    }
+
+    /**
+     * Rollback the transaction. We lock this method for the whole class to avoid synchronization
+     * problems.
+     * @see org.apache.lenya.transaction.UnitOfWork#rollback()
+     */
+    public synchronized void rollback() throws TransactionException {
+        if (logger.isDebugEnabled()) {
+            logger.debug("UnitOfWorkImpl::rollback() called");
+        }
+        if (getIdentityMap() != null) {
+            Object[] objects = getIdentityMap().getObjects();
+            for (int i = 0; i < objects.length; i++) {
+                if (objects[i] instanceof Transactionable) {
+                    Transactionable t = (Transactionable) objects[i];
+                    if (t.isCheckedOutBySession()) {
+                        t.checkin();
+                    }
+                    if (t.isLocked()) {
+                        t.unlock();
+                    }
+                }
+            }
+            resetTransaction();
+        }
+    }
+
+    private Map locks = new HashMap();
+
+    public Lock createLock(Lockable lockable, int version) throws TransactionException {
+        if (this.locks.containsKey(lockable)) {
+            throw new LockException("A lock is already placed on [" + lockable
+                    + "]. A new lock could lead to inconsistent data.");
+        }
+        Lock lock = new Lock(version);
+        this.locks.put(lockable, lock);
+        return lock;
+    }
+
+    public void removeLock(Lockable lockable) throws TransactionException {
+        if (!this.locks.containsKey(lockable)) {
+            throw new LockException("No lock is already placed on [" + lockable + "]!");
+        }
+        this.locks.remove(lockable);
+    }
+
+}
diff --git a/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/Versionable.java b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/Versionable.java
new file mode 100644
index 0000000..e8990b4
--- /dev/null
+++ b/org.apache.lenya.core.transaction/src/main/java/org/apache/lenya/transaction/Versionable.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.lenya.transaction;
+
+/**
+ * A versionable object.
+ *
+ * @version $Id$
+ */
+public interface Versionable extends Lockable {
+
+    /**
+     * Checks the object in.
+     * @throws TransactionException if an error occurs.
+     */
+    void checkin() throws TransactionException;
+
+    /**
+     * Checks the object out.
+     * @throws TransactionException if an error occurs.
+     */
+    void checkout() throws TransactionException;
+
+    /**
+     * @return if the object is checked out.
+     * @throws TransactionException if an error occurs.
+     */
+    boolean isCheckedOut() throws TransactionException;
+    
+    /**
+     * @return if the object is checked out by its session.
+     * @throws TransactionException if an error occurs.
+     */
+    boolean isCheckedOutBySession() throws TransactionException;
+
+    /**
+     * Checks if the object has been changed since it has been locked.
+     * @return A boolean value.
+     * @throws TransactionException if an error occurs.
+     */
+    boolean hasChanged() throws TransactionException;
+
+}
\ No newline at end of file