Author: bleny Date: 2011-10-28 18:00:53 +0200 (Fri, 28 Oct 2011) New Revision: 121 Url: http://nuiton.org/repositories/revision/nuiton-web/121 Log: #1789, #1790, #1791, introduce CloseTopiaTransactionFilter and OpenTopiaTransactionInterceptor, deprecate TopiaTransactionInterceptor Added: trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/filter/ trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/filter/CloseTopiaTransactionFilter.java trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/interceptor/OpenTopiaTransactionInterceptor.java Modified: trunk/nuiton-struts2/pom.xml trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/interceptor/TopiaTransactionInterceptor.java Modified: trunk/nuiton-struts2/pom.xml =================================================================== --- trunk/nuiton-struts2/pom.xml 2011-09-22 17:27:59 UTC (rev 120) +++ trunk/nuiton-struts2/pom.xml 2011-10-28 16:00:53 UTC (rev 121) @@ -20,10 +20,10 @@ <dependencies> - <!--dependency> + <dependency> <groupId>org.apache.struts</groupId> <artifactId>struts2-core</artifactId> - </dependency--> + </dependency> <dependency> <groupId>org.apache.struts.xwork</groupId> @@ -51,6 +51,11 @@ </dependency> <dependency> + <groupId>javax.servlet</groupId> + <artifactId>servlet-api</artifactId> + </dependency> + + <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> Added: trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/filter/CloseTopiaTransactionFilter.java =================================================================== --- trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/filter/CloseTopiaTransactionFilter.java (rev 0) +++ trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/filter/CloseTopiaTransactionFilter.java 2011-10-28 16:00:53 UTC (rev 121) @@ -0,0 +1,73 @@ +package org.nuiton.web.struts2.filter; + + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.nuiton.topia.TopiaContext; +import org.nuiton.topia.TopiaException; +import org.nuiton.topia.TopiaRuntimeException; +import org.nuiton.web.struts2.interceptor.OpenTopiaTransactionInterceptor; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import java.io.IOException; + +/** Close a TopiaTransaction instance stored in the servlet context. + * + * The main usage is to close a transaction opened by the + * {@link OpenTopiaTransactionInterceptor}. A convention is used to find it, + * the transaction attached to the request is found at key + * {@link OpenTopiaTransactionInterceptor#TOPIA_TRANSACTION_REQUEST_ATTRIBUTE} + * + * @since 1.5 + * @author bleny + */ +public class CloseTopiaTransactionFilter implements Filter { + + /** Logger. */ + private static final Log log = LogFactory.getLog(CloseTopiaTransactionFilter.class); + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + try { + filterChain.doFilter(servletRequest, servletResponse); + } finally { + TopiaContext transaction = (TopiaContext) + servletRequest.getAttribute( + OpenTopiaTransactionInterceptor.TOPIA_TRANSACTION_REQUEST_ATTRIBUTE); + + if (transaction == null) { + if (log.isTraceEnabled()) { + log.trace("no transaction to close"); + } + } else if (transaction.isClosed()) { + if (log.isTraceEnabled()) { + log.trace("transaction " + transaction + " is already closed"); + } + } else { + if (log.isDebugEnabled()) { + log.debug("close transaction " + transaction); + } + try { + transaction.closeContext(); + } catch (TopiaException e) { + throw new TopiaRuntimeException(e); + } + } + } + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // nothing to do + } + + @Override + public void destroy() { + // nothing to do + } +} Added: trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/interceptor/OpenTopiaTransactionInterceptor.java =================================================================== --- trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/interceptor/OpenTopiaTransactionInterceptor.java (rev 0) +++ trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/interceptor/OpenTopiaTransactionInterceptor.java 2011-10-28 16:00:53 UTC (rev 121) @@ -0,0 +1,235 @@ +package org.nuiton.web.struts2.interceptor; + +import com.opensymphony.xwork2.ActionInvocation; +import com.opensymphony.xwork2.interceptor.AbstractInterceptor; +import com.opensymphony.xwork2.util.TextParseUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.struts2.ServletActionContext; +import org.nuiton.topia.TopiaContext; +import org.nuiton.topia.TopiaException; +import org.nuiton.topia.framework.TopiaContextImplementor; +import org.nuiton.topia.framework.TopiaTransactionAware; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * <!-- START SNIPPET: description --> + * <p/> + * The aim of this Interceptor is to manage a {@code transaction} all along + * a action which implements {@link TopiaTransactionAware} contract. + * <p/> + * Technicaly, the action will receive only a proxy of a transaction and a real + * transaction will only be created as soon as a method will be asked on it. + * <p/> + * The interceptor is abstract and let user to implement the way how to open a + * new transaction via the method {@link #beginTransaction()}. + * <p/> + * Note that the transaction pushed in the action can be limited using a list + * of methods to exclude on it. The list of methods to forbid can be customized + * using the interceptor parameter {@link #excludeMethods}. + * <p/> + * Note also that the transaction is <strong>not</strong> commited nor closed. + * If you want the transaction to be closed, you may use + * {@link org.nuiton.web.struts2.filter.CloseTopiaTransactionFilter} by adding + * it to your web.xml file. + * <p/> + * This interceptor, as it provides connection to database should be in the + * interceptor stack before any other interceptor requiring access to database. + * For example, it is a common behaviour to do such calls in a prepare method, + * so make sure to place this interceptor before the {@code prepare} interceptor. + * <!-- END SNIPPET: description --> + * <p/> + * <p/> <u>Interceptor parameters:</u> + * <p/> + * <!-- START SNIPPET: parameters --> + * <p/> + * <ul> + * <li>excludeMethods (optional) - Customized method names separated by coma to + * forbid on the proxy of the transaction given to action. By default, if this + * parameter is not filled, then we will use this one : + * {@link #DEFAULT_EXCLUDE_METHODS}.</li> + * </ul> + * <p/> + * <!-- END SNIPPET: parameters --> + * + * @author tchemit <chemit@codelutin.com> + * @since 1.5 + */ +public abstract class OpenTopiaTransactionInterceptor extends AbstractInterceptor { + + /** To specify on your action that you never want any commit. */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.TYPE, ElementType.METHOD}) + public @interface NoCommit { + } + + /** Logger. */ + private static final Log log = + LogFactory.getLog(OpenTopiaTransactionInterceptor.class); + + private static final long serialVersionUID = 1L; + + public static final String TOPIA_TRANSACTION_REQUEST_ATTRIBUTE = "topiaTransaction"; + + public static final String[] DEFAULT_EXCLUDE_METHODS = { + "beginTransaction", + "closeContext", + "clear" + }; + + /** names of methods to forbid access while using proxy. */ + protected Set<String> excludeMethods; + + public Set<String> getExcludeMethods() { + return excludeMethods; + } + + public void setExcludeMethods(String excludeMethods) { + this.excludeMethods = + TextParseUtil.commaDelimitedStringToSet(excludeMethods); + } + + /** + * Method to open a new transaction. + * + * @return the new freshly opened transaction + * @throws org.nuiton.topia.TopiaException if any problem while opening a new transaction + */ + protected abstract TopiaContext beginTransaction() throws TopiaException; + + @Override + public void init() { + super.init(); + + if (getExcludeMethods() == null) { + + // use default exclude methods + excludeMethods = new HashSet<String>( + Arrays.asList(DEFAULT_EXCLUDE_METHODS) + ); + } + } + + /** + * @deprecated to be removed in the same time as TopiaTransactionInterceptor + */ + @Deprecated + protected void closeTransaction(TopiaContext transaction, Object action, + ActionInvocation invocation) throws TopiaException { + // do nothing by contract + } + + @Override + public String intercept(ActionInvocation invocation) throws Exception { + + TopiaTransactionAware transactionAware = null; + + Object action = invocation.getProxy().getAction(); + + if (action instanceof TopiaTransactionAware) { + transactionAware = (TopiaTransactionAware) action; + } + + if (transactionAware == null) { + + // not a transaction aware action, direct skip this interceptor + return invocation.invoke(); + } + + // creates a proxy of a lazy transaction + + TopiaTransactionProxyInvocationHandler proxyInvocationHandler = + new TopiaTransactionProxyInvocationHandler(); + + TopiaContext proxy = (TopiaContext) Proxy.newProxyInstance( + getClass().getClassLoader(), + new Class<?>[]{TopiaContext.class, + TopiaContextImplementor.class}, + proxyInvocationHandler + ); + + // set the transaction in the action + transactionAware.setTransaction(proxy); + + try { + + return invocation.invoke(); + + } finally { + + closeTransaction(proxyInvocationHandler.getTransaction(), action, invocation); + + } + } + + /** + * Handler of a proxy on a {@link TopiaContext}. + * + * @see #excludeMethods + */ + public class TopiaTransactionProxyInvocationHandler implements InvocationHandler { + + /** Target to use for the proxy. */ + protected TopiaContext transaction; + + public TopiaContext getTransaction() { + return transaction; + } + + @Override + public Object invoke(Object proxy, + Method method, + Object[] args) throws Throwable { + + String methodName = method.getName(); + + if (getExcludeMethods().contains(methodName)) { + + // not authorized + throw new IllegalAccessException( + "Not allowed to access method " + methodName + " on " + + proxy); + } + + if (transaction == null) { + + if (log.isTraceEnabled()) { + log.trace("transaction started due to a call to " + methodName); + } + + // first time transaction is required, create its + transaction = beginTransaction(); + + // push transaction in request to make it available for closing in + // CloseTopiaTransactionFilter + ServletActionContext.getRequest().setAttribute( + TOPIA_TRANSACTION_REQUEST_ATTRIBUTE, transaction); + + if (log.isDebugEnabled()) { + log.debug("Open transaction " + transaction); + } + } + + // can invoke the method on the transaction + try { + Object result = method.invoke(transaction, args); + return result; + } catch (Exception eee) { + if (log.isErrorEnabled()) { + log.error("Could not execute method " + method.getName(), eee); + } + throw eee; + } + } + } +} Modified: trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/interceptor/TopiaTransactionInterceptor.java =================================================================== --- trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/interceptor/TopiaTransactionInterceptor.java 2011-09-22 17:27:59 UTC (rev 120) +++ trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/interceptor/TopiaTransactionInterceptor.java 2011-10-28 16:00:53 UTC (rev 121) @@ -25,206 +25,56 @@ package org.nuiton.web.struts2.interceptor; import com.opensymphony.xwork2.ActionInvocation; -import com.opensymphony.xwork2.interceptor.AbstractInterceptor; -import com.opensymphony.xwork2.util.TextParseUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuiton.topia.TopiaContext; import org.nuiton.topia.TopiaException; -import org.nuiton.topia.framework.TopiaContextImplementor; -import org.nuiton.topia.framework.TopiaTransactionAware; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -/** - * <!-- START SNIPPET: description --> - * <p/> - * The aim of this Interceptor is to manage a {@code transaction} all along - * a action which implements {@link TopiaTransactionAware} - * contract. - * <p/> - * Technicaly, the action will receive only a proxy of a transaction and a real - * transaction will only be created as soon as a method will be asked on it. - * <p/> - * The interceptor is abstract and let user to implement the way how to open a - * new transaction via the method {@link #beginTransaction()}. - * <p/> - * Note that the transaction pushed in the action can be limited using a list - * of methods to exclude on it. The list of methods to forbid can be customized - * using the interceptor parameter {@link #excludeMethods}. - * <p/> - * Note also that the transaction is commited and closed after all stack of - * interceptor consumed, this means that the transaction will still be opened - * while rendering the result, this is a particular interesting thing to avoid - * pre-loading of entities due to lazy strategy of hibernate for example. - * With this mecanism you can feel free to just obtain the obtain from database - * via a DAO and then really load it in the rendering result. - * <p/> - * If you do not want any commit for a given action, just put on the class a commit - * <p/> - * This interceptor, as it provides connection to database should be in the - * interceptor stack before any other interceptor requiring access to database. - * For example, it is a common behaviour to do such calls in a prepare method, - * so make sure to place this interceptor before the {@code prepare} interceptor. - * <!-- END SNIPPET: description --> - * <p/> - * <p/> <u>Interceptor parameters:</u> - * <p/> - * <!-- START SNIPPET: parameters --> - * <p/> - * <ul> - * <li>excludeMethods (optional) - Customized method names separated by coma to - * forbid on the proxy of the transaction given to action. By default, if this - * parameter is not filled, then we will use this one : - * {@link #DEFAULT_EXCLUDE_METHODS}.</li> - * </ul> - * <p/> - * <!-- END SNIPPET: parameters --> +/** Same interceptor as {@link OpenTopiaTransactionInterceptor} but close + * the transaction after action is called. The transaction will be commited + * (unless asked otherwise) and closed. * * @author tchemit <chemit@codelutin.com> * @since 1.2 */ -public abstract class TopiaTransactionInterceptor extends AbstractInterceptor { +@Deprecated +public abstract class TopiaTransactionInterceptor extends OpenTopiaTransactionInterceptor { - /** To specify on your action that you never want any commit. */ - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.TYPE, ElementType.METHOD}) - public @interface NoCommit { - } - /** Logger. */ - private static final Log log = - LogFactory.getLog(TopiaTransactionInterceptor.class); + private static final Log log = LogFactory.getLog(TopiaTransactionInterceptor.class); - private static final long serialVersionUID = 1L; - - public static final String[] DEFAULT_EXCLUDE_METHODS = { - "beginTransaction", - "closeContext", - "clear" - }; - - /** names of methods to forbid access while using proxy. */ - protected Set<String> excludeMethods; - - public Set<String> getExcludeMethods() { - return excludeMethods; - } - - public void setExcludeMethods(String excludeMethods) { - this.excludeMethods = - TextParseUtil.commaDelimitedStringToSet(excludeMethods); - } - - /** - * Method to open a new transaction. - * - * @return the new freshly opened transaction - * @throws TopiaException if any problem while opening a new transaction - */ - protected abstract TopiaContext beginTransaction() throws TopiaException; - @Override - public void init() { - super.init(); + protected void closeTransaction(TopiaContext transaction, Object action, + ActionInvocation invocation) throws TopiaException { - if (getExcludeMethods() == null) { + boolean commitNeeded = isCommitNeeded(action, invocation); - // use default exclude methods - excludeMethods = new HashSet<String>( - Arrays.asList(DEFAULT_EXCLUDE_METHODS) - ); - } - } + if (transaction != null && ! transaction.isClosed()) { + try { + if (commitNeeded) { - @Override - public String intercept(ActionInvocation invocation) throws Exception { - - TopiaTransactionAware transactionAware = null; - - Object action = invocation.getProxy().getAction(); - - if (action instanceof TopiaTransactionAware) { - transactionAware = (TopiaTransactionAware) action; - } - - if (transactionAware == null) { - - // not a transaction aware action, direct skip this interceptor - return invocation.invoke(); - } - - // creates a proxy of a lazy transaction - - TopiaTransactionProxyInvocationHandler proxyInvocationHandler = - new TopiaTransactionProxyInvocationHandler(); - - TopiaContext proxy = (TopiaContext) Proxy.newProxyInstance( - getClass().getClassLoader(), - new Class<?>[]{TopiaContext.class, - TopiaContextImplementor.class}, - proxyInvocationHandler - ); - - // set the transaction in the action - transactionAware.setTransaction(proxy); - - boolean doCommit = isCommitNeeded(action, invocation); - try { - return invocation.invoke(); - } catch (Exception e) { - - doCommit = false; - TopiaContext transaction = proxyInvocationHandler.getTransaction(); - - if (transaction != null && !transaction.isClosed()) { - if (log.isDebugEnabled()) { - log.debug("rollback transaction " + transaction); - } - transaction.rollbackTransaction(); - } - - throw e; - } finally { - - TopiaContext transaction = proxyInvocationHandler.getTransaction(); - - if (transaction != null && !transaction.isClosed()) { - try { - if (doCommit) { - - // commit the opened transaction - if (log.isDebugEnabled()) { - log.debug("Commit transaction " + transaction); - } - transaction.commitTransaction(); - } - } finally { - - // close the opened transaction + // commit the opened transaction if (log.isDebugEnabled()) { - log.debug("Close transaction " + transaction); + log.debug("Commit transaction " + transaction); } - transaction.closeContext(); + transaction.commitTransaction(); } + } finally { + // close the opened transaction + if (log.isDebugEnabled()) { + log.debug("Close transaction " + transaction); + } + transaction.closeContext(); } - } } protected boolean isCommitNeeded(Object action, - ActionInvocation invocation) throws NoSuchMethodException { + ActionInvocation invocation) { Class<?> actionType = action.getClass(); boolean noCommit = actionType.isAnnotationPresent(NoCommit.class); if (noCommit) { @@ -244,7 +94,12 @@ // no methodName specify, means the execute one methodName = "execute"; } - Method method = actionType.getMethod(methodName); + Method method; + try { + method = actionType.getMethod(methodName); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } noCommit = method.isAnnotationPresent(NoCommit.class); if (noCommit) { if (log.isDebugEnabled()) { @@ -255,55 +110,4 @@ return !noCommit; } - /** - * Handler of a proxy on a {@link TopiaContext}. - * - * @see TopiaTransactionInterceptor#excludeMethods - */ - public class TopiaTransactionProxyInvocationHandler implements InvocationHandler { - - /** Target to use for the proxy. */ - protected TopiaContext transaction; - - public TopiaContext getTransaction() { - return transaction; - } - - @Override - public Object invoke(Object proxy, - Method method, - Object[] args) throws Throwable { - - String methodName = method.getName(); - - if (getExcludeMethods().contains(methodName)) { - - // not authorized - throw new IllegalAccessException( - "Not allowed to access method " + methodName + " on " + - proxy); - } - - if (transaction == null) { - - // first time transaction is required, create its - transaction = beginTransaction(); - - if (log.isDebugEnabled()) { - log.debug("Open transaction " + transaction); - } - } - // can invoke the method on the transaction - try { - Object result = method.invoke(transaction, args); - return result; - } catch (Exception eee) { - if (log.isErrorEnabled()) { - log.error("Could not execute method " + method.getName(), eee); - } - throw eee; - } - } - } - } \ No newline at end of file