blob: d0eea3f714088c828ae05b3fdc46d17ee51889a3 [file] [log] [blame]
/*
* 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.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 TRANSACTIONAL_ELEMENT = "transactional";
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 = TransactionalMethod.REQUIRES;
private TransactionManager m_transactionManager; // Service Dependency
private List<TransactionalMethod> m_methods = new ArrayList<TransactionalMethod>();
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(TRANSACTIONAL_ELEMENT);
if (sub == null || sub.length == 0) {
throw new ConfigurationException("The handler " + NAMESPACE + ":" + NAME + " must have " + TRANSACTIONAL_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 transactional element must specified the method attribute");
}
MethodMetadata meta = this.getPojoMetadata().getMethod(method);
if (meta == null) {
throw new ConfigurationException("A transactional 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();
}
TransactionalMethod tm = new TransactionalMethod(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 TransactionalMethod.REQUIRES;
} else if (propa.equalsIgnoreCase("mandatory")){
return TransactionalMethod.MANDATORY;
} else if (propa.equalsIgnoreCase("notsupported")) {
return TransactionalMethod.NOT_SUPPORTED;
} else if (propa.equalsIgnoreCase("supported")) {
return TransactionalMethod.SUPPORTED;
} else if (propa.equalsIgnoreCase("never")) {
return TransactionalMethod.NEVER;
} else if (propa.equalsIgnoreCase("requiresnew")) {
return TransactionalMethod.REQUIRES_NEW;
}
throw new ConfigurationException("Unknown propgation policy : " + propa);
}
public void start() {
// Set transaction managers.
for (TransactionalMethod method : m_methods) {
method.setTransactionManager(m_transactionManager);
}
}
public void stop() {
// Nothing to do.
}
public synchronized void bind(TransactionManager tm) {
for (TransactionalMethod method : m_methods) {
method.setTransactionManager(tm);
}
}
public synchronized void unbind(TransactionManager tm) {
for (TransactionalMethod 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;
}
}