Copy the transaction handler to the trunk
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@786966 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/ipojo/handler/transaction/pom.xml b/ipojo/handler/transaction/pom.xml
new file mode 100644
index 0000000..06ffd82
--- /dev/null
+++ b/ipojo/handler/transaction/pom.xml
@@ -0,0 +1,64 @@
+<project>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>bundle</packaging>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.ipojo.handler.transaction</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ <name>Apache Felix iPOJO Transaction Handler</name>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>1.4.3</version>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
+ <Private-Package>org.apache.felix.ipojo.transaction</Private-Package>
+ <Import-Package>*</Import-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-ipojo-plugin</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>ipojo-bundle</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.5</source>
+ <target>1.5</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.ipojo</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.ipojo.metadata</artifactId>
+ <version>1.3.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.transaction</artifactId>
+ <version>0.9.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/ipojo/handler/transaction/src/main/java/org/apache/felix/ipojo/transaction/TransactionHandler.java b/ipojo/handler/transaction/src/main/java/org/apache/felix/ipojo/transaction/TransactionHandler.java
new file mode 100644
index 0000000..31415d1
--- /dev/null
+++ b/ipojo/handler/transaction/src/main/java/org/apache/felix/ipojo/transaction/TransactionHandler.java
@@ -0,0 +1,291 @@
+package org.apache.felix.ipojo.transaction;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.List;
+
+import javax.transaction.Status;
+import javax.transaction.Synchronization;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+
+import org.apache.felix.ipojo.ComponentInstance;
+import org.apache.felix.ipojo.ConfigurationException;
+import org.apache.felix.ipojo.PrimitiveHandler;
+import org.apache.felix.ipojo.metadata.Element;
+import org.apache.felix.ipojo.parser.FieldMetadata;
+import org.apache.felix.ipojo.parser.MethodMetadata;
+import org.apache.felix.ipojo.parser.ParseUtils;
+import org.apache.felix.ipojo.util.Callback;
+
+public class TransactionHandler extends PrimitiveHandler implements Synchronization {
+
+ public static final String NAMESPACE= "org.apache.felix.ipojo.transaction";
+
+ public static final String NAME= "transaction";
+
+ private static final String FIELD_ATTRIBUTE= "field";
+
+ private static final String ONCOMMIT_ATTRIBUTE= "oncommit";
+
+ private static final String ONROLLBACK_ATTRIBUTE= "onrollback";
+
+ private static final String TRANSACTIONNAL_ELEMENT = "transactionnal";
+
+ private static final String METHOD_ATTRIBUTE = "method";
+
+ private static final String TIMEOUT_ATTRIBUTE = "timeout";
+
+ private static final String PROPAGATION_ATTRIBUTE = "propagation";
+
+ private static final String EXCEPTIONONROLLBACK_ATTRIBUTE = "exceptiononrollback";
+
+ private static final String NOROLLBACKFOR_ATTRIBUTE = "norollbackfor";
+
+ public static final int DEFAULT_PROPAGATION = TransactionnalMethod.REQUIRES;
+
+
+ private TransactionManager m_transactionManager; // Service Dependency
+
+ private List<TransactionnalMethod> m_methods = new ArrayList<TransactionnalMethod>();
+
+ private Callback m_onRollback;
+
+ private Callback m_onCommit;
+
+ private List<Transaction> m_transactions = new ArrayList<Transaction>();
+
+
+ public void configure(Element arg0, Dictionary arg1)
+ throws ConfigurationException {
+ Element[] elements = arg0.getElements(NAME, NAMESPACE);
+ if (elements.length > 1) {
+ throw new ConfigurationException("The handler " + NAMESPACE + ":" + NAME + " cannot be declared several times");
+ }
+
+ String field = elements[0].getAttribute(FIELD_ATTRIBUTE);
+ if (field != null) {
+ FieldMetadata meta = getPojoMetadata().getField(field);
+ if (meta == null) {
+ throw new ConfigurationException("The transaction field does not exist in the pojo class : " + field);
+ }
+ if (! meta.getFieldType().equals(Transaction.class.getName())) {
+ throw new ConfigurationException("The transaction field type must be " + Transaction.class.getName());
+ }
+ // Register the interceptor
+ getInstanceManager().register(meta, this);
+
+ }
+
+ String oncommit = elements[0].getAttribute(ONCOMMIT_ATTRIBUTE);
+ if (oncommit != null) {
+ m_onCommit = new Callback(oncommit, new String[] { Transaction.class.getName() }, false, getInstanceManager());
+ }
+
+ String onrollback = elements[0].getAttribute(ONROLLBACK_ATTRIBUTE);
+ if (onrollback != null) {
+ m_onRollback = new Callback(onrollback, new String[] { Transaction.class.getName() }, false, getInstanceManager());
+ }
+
+
+ Element[] sub = elements[0].getElements(TRANSACTIONNAL_ELEMENT);
+ if (sub == null || sub.length == 0) {
+ throw new ConfigurationException("The handler " + NAMESPACE + ":" + NAME + " must have " + TRANSACTIONNAL_ELEMENT + " subelement");
+ }
+
+ for (int i = 0; i < sub.length; i++) {
+ String method = sub[i].getAttribute(METHOD_ATTRIBUTE);
+ String to = sub[i].getAttribute(TIMEOUT_ATTRIBUTE);
+ String propa = sub[i].getAttribute(PROPAGATION_ATTRIBUTE);
+ String nrbf = sub[i].getAttribute(NOROLLBACKFOR_ATTRIBUTE);
+ String eorb = sub[i].getAttribute(EXCEPTIONONROLLBACK_ATTRIBUTE);
+
+ if (method == null) {
+ throw new ConfigurationException("A transactionnal element must specified the method attribute");
+ }
+ MethodMetadata meta = this.getPojoMetadata().getMethod(method);
+ if (meta == null) {
+ throw new ConfigurationException("A transactionnal method is not in the pojo class : " + method);
+ }
+
+ int timeout = 0;
+ if (to != null) {
+ timeout = new Integer(to).intValue();
+ }
+
+ int propagation = DEFAULT_PROPAGATION;
+ if (propa != null) {
+ propagation = parsePropagation(propa);
+ }
+
+ List<String> exceptions = new ArrayList<String>();
+ if (nrbf != null) {
+ exceptions = (List<String>) ParseUtils.parseArraysAsList(nrbf);
+ }
+
+ boolean exceptionOnRollback = false;
+ if (eorb != null) {
+ exceptionOnRollback = new Boolean(eorb).booleanValue();
+ }
+
+ TransactionnalMethod tm = new TransactionnalMethod(method, propagation, timeout, exceptions, exceptionOnRollback, this);
+ m_methods.add(tm);
+ this.getInstanceManager().register(meta, tm);
+ }
+
+ }
+
+ private int parsePropagation(String propa) throws ConfigurationException {
+ if (propa.equalsIgnoreCase("requires")) {
+ return TransactionnalMethod.REQUIRES;
+
+ } else if (propa.equalsIgnoreCase("mandatory")){
+ return TransactionnalMethod.MANDATORY;
+
+ } else if (propa.equalsIgnoreCase("notsupported")) {
+ return TransactionnalMethod.NOT_SUPPORTED;
+
+ } else if (propa.equalsIgnoreCase("supported")) {
+ return TransactionnalMethod.SUPPORTED;
+
+ } else if (propa.equalsIgnoreCase("never")) {
+ return TransactionnalMethod.NEVER;
+
+ } else if (propa.equalsIgnoreCase("requiresnew")) {
+ return TransactionnalMethod.REQUIRES_NEW;
+ }
+
+ throw new ConfigurationException("Unknown propgation policy : " + propa);
+ }
+
+ public void start() {
+ // Set transaction managers.
+ for (TransactionnalMethod method : m_methods) {
+ method.setTransactionManager(m_transactionManager);
+ }
+ }
+
+ public void stop() {
+ // Nothing to do.
+ }
+
+ public synchronized void bind(TransactionManager tm) {
+ for (TransactionnalMethod method : m_methods) {
+ method.setTransactionManager(tm);
+ }
+ }
+
+ public synchronized void unbind(TransactionManager tm) {
+ for (TransactionnalMethod method : m_methods) {
+ method.setTransactionManager(null);
+ }
+ }
+
+ public void transactionRolledback(Transaction t) {
+ if (m_onRollback != null) {
+ try {
+ m_onRollback.call(new Object[] { t });
+ } catch (NoSuchMethodException e1) {
+ error("Cannot invoke the onRollback method, method not found",
+ e1);
+ } catch (IllegalAccessException e1) {
+ error(
+ "Cannot invoke the onRollback method,cannot access the method",
+ e1);
+ } catch (InvocationTargetException e1) {
+ error(
+ "Cannot invoke the onRollback method,the method thrown an exception",
+ e1.getTargetException());
+ }
+ }
+ }
+
+ public void transactionCommitted(Transaction t) {
+ if (m_onRollback != null) {
+ try {
+ m_onCommit.call(new Object[] { t });
+ } catch (NoSuchMethodException e1) {
+ error("Cannot invoke the onCommit callback, method not found",
+ e1);
+ } catch (IllegalAccessException e1) {
+ error(
+ "Cannot invoke the onCommit callback,cannot access the method",
+ e1);
+ } catch (InvocationTargetException e1) {
+ error(
+ "Cannot invoke the onCommit callback,the method thrown an exception",
+ e1.getTargetException());
+ }
+ }
+
+ }
+
+ public void stateChanged(int newState) {
+ if (newState == ComponentInstance.INVALID) {
+ // rollback all owned transactions.
+ for (int i = 0; i < m_methods.size(); i++) {
+ m_methods.get(i).rollbackOwnedTransactions();
+ }
+
+ for (int i =0; i < m_transactions.size(); i++) {
+ try {
+ m_transactions.get(i).setRollbackOnly();
+ } catch (Exception e) {
+ error("Cannot set rollback only on a transaction : " + e.getMessage());
+ }
+ }
+ }
+ }
+
+ public synchronized Object onGet(Object pojo, String fieldName, Object value) {
+ try {
+ if (m_transactionManager != null) {
+ return m_transactionManager.getTransaction();
+ } else {
+ return null;
+ }
+ } catch (SystemException e) {
+ error("Cannot get the current transaction, internal error", e);
+ return null;
+ }
+ }
+
+ public void afterCompletion(int arg0) {
+ try {
+ if (m_transactionManager.getTransaction() != null) {
+ m_transactions .remove(m_transactionManager.getTransaction());
+ if (arg0 == Status.STATUS_ROLLEDBACK) {
+ transactionRolledback(m_transactionManager.getTransaction());
+ } else if (arg0 == Status.STATUS_COMMITTED) {
+ transactionCommitted(m_transactionManager.getTransaction());
+ }
+ }
+ } catch (SystemException e) {
+ error("Cannot remove the transaction from the transaction list : " + e.getMessage());
+ }
+ }
+
+ public void beforeCompletion() {
+
+ }
+
+ public void addTransaction(Transaction transaction) {
+ if (m_transactions.contains(transaction)) {
+ return;
+ }
+ try {
+ transaction.registerSynchronization(this);
+ m_transactions.add(transaction);
+ } catch (Exception e) {
+ error("Cannot add the transaction to the transaction list : " + e.getMessage());
+ }
+ }
+
+ public List<Transaction> getTransactions() {
+ return m_transactions;
+ }
+
+
+}
diff --git a/ipojo/handler/transaction/src/main/java/org/apache/felix/ipojo/transaction/TransactionnalMethod.java b/ipojo/handler/transaction/src/main/java/org/apache/felix/ipojo/transaction/TransactionnalMethod.java
new file mode 100644
index 0000000..27a45d0
--- /dev/null
+++ b/ipojo/handler/transaction/src/main/java/org/apache/felix/ipojo/transaction/TransactionnalMethod.java
@@ -0,0 +1,295 @@
+package org.apache.felix.ipojo.transaction;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.InvalidTransactionException;
+import javax.transaction.NotSupportedException;
+import javax.transaction.RollbackException;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+
+import org.apache.felix.ipojo.MethodInterceptor;
+
+public class TransactionnalMethod implements MethodInterceptor {
+
+ public static final int REQUIRES = 0;
+
+ public static final int REQUIRES_NEW = 1;
+
+ public static final int MANDATORY = 2;
+
+ public static final int SUPPORTED = 3;
+
+ public static final int NOT_SUPPORTED = 4;
+
+ public static final int NEVER = 5;
+
+ private String method;
+
+ private int propagation;
+
+ private int timeout;
+
+ private List<String> exceptions;
+
+ private TransactionManager manager;
+
+ private Map <Thread, Transaction> m_owned = new HashMap<Thread, Transaction>();
+
+ private boolean exceptionOnRollback;
+
+
+ private TransactionHandler handler;
+
+ private Transaction suspended;
+
+ public TransactionnalMethod(String method, int propagation, int timeout, List<String> exception, boolean exceptionOnRollback, TransactionHandler handler) {
+ this.method = method;
+ this.propagation = propagation;
+ this.timeout = timeout;
+ this.exceptions = exception;
+ this.exceptionOnRollback = exceptionOnRollback;
+ this.handler = handler;
+ }
+
+ public synchronized void setTransactionManager(TransactionManager tm) {
+ System.out.println("Set transaction manager to " + tm);
+ manager = tm;
+ if (manager == null) {
+ // Clear stored transactions.
+ m_owned.clear();
+
+ }
+ }
+
+
+ public void onEntry() throws SystemException, NotSupportedException {
+ TransactionManager manager = null;
+ synchronized (this) {
+ if (this.manager != null) {
+ manager = this.manager; // Stack confinement
+ } else {
+ return; // Nothing can be done...
+ }
+ }
+
+ Transaction transaction = manager.getTransaction();
+ switch (propagation) {
+ case REQUIRES:
+ // Are we already in a transaction?
+ if (transaction == null) {
+ // No, create one
+ System.out.println("Begin a new transaction !");
+
+ if (timeout > 0) {
+ manager.setTransactionTimeout(timeout);
+ }
+ manager.begin();
+ m_owned.put(Thread.currentThread(), manager.getTransaction());
+ } else {
+ // Add the transaction to the transaction list
+ handler.addTransaction(transaction);
+ }
+ break;
+ case MANDATORY:
+ if (transaction == null) {
+ // Error
+ throw new IllegalStateException("The method " + method + " must be called inside a transaction");
+ } else {
+ // Add the transaction to the transaction list
+ handler.addTransaction(transaction);
+ }
+ break;
+ case SUPPORTED:
+ // if transaction != null, register the callback, else do nothing
+ if (transaction != null) {
+ handler.addTransaction(transaction);
+ } // Else do nothing.
+ break;
+ case NOT_SUPPORTED:
+ // Do nothing.
+ break;
+ case NEVER:
+ if (transaction != null) {
+ throw new IllegalStateException("The method " + method + " must never be called inside a transaction");
+ }
+ break;
+ case REQUIRES_NEW:
+ if (transaction == null) {
+ // No current transaction, Just creates a new one
+ if (timeout > 0) {
+ manager.setTransactionTimeout(timeout);
+ }
+ manager.begin();
+ System.out.println("== Associate " + Thread.currentThread() + " to " + manager.getTransaction() + " ==");
+ m_owned.put(Thread.currentThread(), manager.getTransaction());
+ } else {
+ if (suspended == null) {
+ suspended = manager.suspend();
+ if (timeout > 0) {
+ manager.setTransactionTimeout(timeout);
+ }
+ manager.begin();
+ m_owned.put(Thread.currentThread(), manager.getTransaction());
+ } else {
+ throw new IllegalStateException("The method " + method + " requires to suspend a second times a transaction");
+ }
+ }
+ break;
+ default:
+ throw new UnsupportedOperationException("Unknown or unsupported propagation policy for " + method + " :" + propagation);
+
+ }
+ }
+
+ public void onExit() throws SecurityException, HeuristicMixedException, HeuristicRollbackException, SystemException, InvalidTransactionException, IllegalStateException {
+ switch (propagation) {
+ case REQUIRES:
+ // Are we the owner of the transaction?
+ Transaction transaction = m_owned.get(Thread.currentThread());
+ if (transaction != null) { // Owner.
+ try {
+ transaction.commit(); // Commit the transaction
+ m_owned.remove(Thread.currentThread());
+ handler.transactionCommitted(transaction); // Manage potential notification.
+ } catch ( RollbackException e) {
+ m_owned.remove(Thread.currentThread());
+ // The transaction was rolledback
+ if (exceptionOnRollback) {
+ throw new IllegalStateException("The transaction was rolledback : " + e.getMessage());
+ }
+ handler.transactionRolledback(transaction); // Manage potential notification.
+ }
+ } // Else wait for commit.
+ break;
+ case MANDATORY:
+ // We are never the owner, so just exits the transaction.
+ break;
+ case SUPPORTED:
+ // Do nothing.
+ break;
+ case NOT_SUPPORTED:
+ // Do nothing.
+ break;
+ case NEVER:
+ // Do nothing.
+ break;
+ case REQUIRES_NEW:
+ // We're necessary the owner.
+ transaction = m_owned.get(Thread.currentThread());
+ System.out.println("== Owned " + Thread.currentThread() + " to " + transaction + "==");
+ if (transaction == null) {
+ throw new RuntimeException("Cannot apply the REQUIRES NEW propagation, we're not the transaction owner!");
+ }
+ try {
+ transaction.commit(); // Commit the transaction
+ m_owned.remove(Thread.currentThread());
+ handler.transactionCommitted(transaction); // Manage potential notification.
+ if (suspended != null) {
+ manager.suspend(); // suspend the completed transaction.
+ manager.resume(suspended);
+ suspended = null;
+ }
+ } catch ( RollbackException e) { // The transaction was rolledback rather than committed
+ m_owned.remove(Thread.currentThread());
+ if (suspended != null) {
+ manager.suspend(); // suspend the completed transaction.
+ manager.resume(suspended); // The resume transaction is not rolledback, they are independent.
+ suspended = null;
+ }
+ // The transaction was rolledback
+ if (exceptionOnRollback) {
+ throw new IllegalStateException("The transaction was rolledback : " + e.getMessage());
+ }
+ handler.transactionRolledback(transaction); // Manage potential notification.
+ }
+ break;
+ default:
+ throw new UnsupportedOperationException("Unknown or unsupported propagation policy for " + method + " :" + propagation);
+
+ }
+ }
+
+ public void onError(String exception) throws SystemException {
+ TransactionManager manager = null;
+ synchronized (this) {
+ if (this.manager != null) {
+ manager = this.manager; // Stack confinement
+ } else {
+ return; // Nothing can be done...
+ }
+ }
+
+ // is the error something to exclude, and are we inside the transaction (owner or participant)?
+ if (! exceptions.contains(exception)) {
+ Transaction tr = manager.getTransaction();
+ if (m_owned.containsValue(tr) || handler.getTransactions().contains(tr)) {
+ // Set the transaction to rollback only
+ manager.getTransaction().setRollbackOnly();
+ }
+ }
+ }
+
+ public void onEntry(Object arg0, Method arg1, Object[] arg2) {
+ try {
+ onEntry();
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException("An issue occurs during transaction management of " + method + " : " + e.getMessage());
+ }
+
+ }
+
+ public void onError(Object arg0, Method arg1, Throwable arg2) {
+ try {
+ System.out.println("Error in a TM " + arg1.getName() + " " + arg2.getMessage());
+ arg2.printStackTrace();
+
+ onError(arg2.getClass().getName());
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException("An issue occurs during transaction management of " + method + " : " + e.getMessage());
+ }
+ }
+
+ public void onExit(Object arg0, Method arg1, Object arg2) {
+ // Wait for on finally.
+ }
+
+ public void onFinally(Object arg0, Method arg1) {
+ try {
+ System.out.println("Exits a TM " + arg1.getName());
+
+ onExit();
+ } catch (IllegalStateException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RuntimeException("An issue occurs during transaction management of " + method + " : " + e.getMessage());
+ }
+ }
+
+ public void rollbackOwnedTransactions() {
+ Iterator<Entry<Thread, Transaction>> entries = m_owned.entrySet().iterator();
+ while(entries.hasNext()) {
+ Entry<Thread, Transaction> entry = entries.next();
+ try {
+ entry.getValue().rollback();
+ } catch (IllegalStateException e) {
+ throw new RuntimeException("An issue occurs during transaction management of " + method + " : " + e.getMessage());
+ } catch (SystemException e) {
+ throw new RuntimeException("An issue occurs during transaction management of " + method + " : " + e.getMessage());
+ }
+ }
+ m_owned.clear();
+ }
+
+
+}
diff --git a/ipojo/handler/transaction/src/main/resources/metadata.xml b/ipojo/handler/transaction/src/main/resources/metadata.xml
new file mode 100644
index 0000000..55162a9
--- /dev/null
+++ b/ipojo/handler/transaction/src/main/resources/metadata.xml
@@ -0,0 +1,13 @@
+<ipojo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="org.apache.felix.ipojo http://felix.apache.org/ipojo/schemas/CURRENT/core.xsd"
+ xmlns="org.apache.felix.ipojo">
+
+ <handler name="transaction" namespace="org.apache.felix.ipojo.transaction"
+ classname="org.apache.felix.ipojo.transaction.TransactionHandler">
+ <requires field="m_transactionManager">
+ <callback type="bind" method="bind"/>
+ <callback type="unbind" method="unbind"/>
+ </requires>
+ </handler>
+
+</ipojo>
\ No newline at end of file