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