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