This is an automated email from the git hooks/post-receive script. New commit to branch pollen-udpate-1.6 in repository pollen_1.x. See https://gitlab.nuiton.org/None/pollen_1.x.git commit e17fcdbcb1f7828cb05d5ad8eb16fe51d879e614 Author: Eric Chatellier <eric.chatellier@gmail.com> Date: Fri Nov 13 23:37:00 2015 +0100 Copy TransactionFilter for compatibility with topia 2 --- .../chorem/pollen/ui/TopiaTransactionFilter.java | 407 +++++++++++++++++++++ 1 file changed, 407 insertions(+) diff --git a/pollen-ui-struts2/src/main/java/org/chorem/pollen/ui/TopiaTransactionFilter.java b/pollen-ui-struts2/src/main/java/org/chorem/pollen/ui/TopiaTransactionFilter.java new file mode 100644 index 0000000..a3ecfd8 --- /dev/null +++ b/pollen-ui-struts2/src/main/java/org/chorem/pollen/ui/TopiaTransactionFilter.java @@ -0,0 +1,407 @@ +/* + * #%L + * Nuiton Web :: Nuiton Web + * + * $Id$ + * $HeadURL$ + * %% + * Copyright (C) 2011 CodeLutin + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/lgpl-3.0.html>. + * #L% + */ +package org.chorem.pollen.ui; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.hibernate.Transaction; +import org.nuiton.topia.TopiaContext; +import org.nuiton.topia.TopiaException; +import org.nuiton.topia.TopiaRuntimeException; +import org.nuiton.topia.framework.TopiaContextImplementor; + +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; +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; + +/** + * <h2>Purpose of this filter</h2> + * This filter purpose is to inject in the request a transaction from + * {@link TopiaContext} and deal with the complete lifecycle of a topia + * transaction while a request. + * <p/> + * The injected transaction will be closed (if was really opened) at the end of + * the request. + * <h2>Configuration of the filter</h2> + * The filter accepts two configuration parameters: + * <ul> + * <li>{@code excludeMethods}: This parameters configure a set of method names + * which should never be called on the proxied transaction. + * When a such method is called on the transaction then the filter will pass in + * the hook {@link #onExcludeMethod(Object, Method, Object[])}. + * <p/> + * Default implementation of this hook is to throw an exception. + * </li> + * <li>{@code unusedMethods}: This parameters configure a set of method names + * which should be by-pass when the proxied transaction was not still open (via a {@link TopiaContext#beginTransaction()}. + * When a such method is called on the transaction then the filter will pass in + * the hook {@link #onUnusedMethod(Object, Method, Object[])}. + * <p/> + * Default implementation of this hook is to not return null values. + * </li> + * </ul> + * <h2>Obtain the transaction</h2> + * The (proxied) transaction is pushed as an attribute in the servlet request. + * <p/> + * The attribute name is defined by field {@link #requestAttributeName} + * (default value is {@link #TOPIA_TRANSACTION_REQUEST_ATTRIBUTE}) and can be + * changed. + * <p/> + * A convience method is created here to obtain the transaction {@link #getTransaction(ServletRequest)} : + * <pre> + * TopiaContext tx = TopiaTransactionFilter.getTransaction(ServletRequest); + * </pre> + * <p/> + * If you prefer to not use this nice method, you can also do this: + * <pre> + * TopiaContext tx = (TopiaContext) request.getAttribute(TopiaTransactionFilter#TOPIA_TRANSACTION_REQUEST_ATTRIBUTE); + * </pre> + * <p/> + * Or + * <pre> + * TopiaContext tx = (TopiaContext) request.getAttribute(modifiedAttributeName); + * </pre> + * + * @author tchemit <chemit@codelutin.com> + * @since 1.6 + */ +public abstract class TopiaTransactionFilter implements Filter { + + public static final String TOPIA_TRANSACTION_REQUEST_ATTRIBUTE = + "topiaTransaction"; + + public static final String[] DEFAULT_EXCLUDE_METHODS = { + "beginTransaction", + "closeContext", + "clear" + }; + + public static final String[] DEFAULT_UNUSED_METHODS = { + "toString", + "isClosed", + "closeContext", + "clear", + "equals", + "hashCode", + "finalize", + "getClass" + }; + + /** Logger. */ + private static final Log log = + LogFactory.getLog(TopiaTransactionFilter.class); + + /** names of methods to forbid access while using proxy. */ + protected Set<String> excludeMethods; + + /** names of methods to by-pass if no transaction opened on proxy. */ + protected Set<String> unusedMethods; + + /** + * Name of the request attribute where to push the transaction. + * <p/> + * By default will use value of + * {@link #TOPIA_TRANSACTION_REQUEST_ATTRIBUTE}. + * + * @since 1.10 + */ + protected String requestAttributeName = TOPIA_TRANSACTION_REQUEST_ATTRIBUTE; + + public Set<String> getExcludeMethods() { + return excludeMethods; + } + + public Set<String> getUnusedMethods() { + return unusedMethods; + } + + /** + * to change the {@link #requestAttributeName}. + * + * @param requestAttributeName new name of the request attribute + * where to push the transaction. + * @since 1.10 + */ + public void setRequestAttributeName(String requestAttributeName) { + this.requestAttributeName = requestAttributeName; + } + + public static TopiaContext getTransaction(ServletRequest request) { + TopiaContext topiaContext = (TopiaContext) + request.getAttribute(TOPIA_TRANSACTION_REQUEST_ATTRIBUTE); + return topiaContext; + } + + /** + * Method to open a new transaction. + * + * @param request incoming request + * @return the new freshly opened transaction + * @throws TopiaRuntimeException if any problem while opening a new transaction + */ + protected abstract TopiaContext beginTransaction(ServletRequest request) throws TopiaRuntimeException; + + @Override + public void destroy() { + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + String methodsFromConfig; + + methodsFromConfig = filterConfig.getInitParameter("excludeMethods"); + String[] methods; + if (StringUtils.isNotEmpty(methodsFromConfig)) { + methods = methodsFromConfig.split(","); + } else { + methods = DEFAULT_EXCLUDE_METHODS; + } + excludeMethods = new HashSet<String>(Arrays.asList(methods)); + + methodsFromConfig = filterConfig.getInitParameter("unusedMethods"); + if (StringUtils.isNotEmpty(methodsFromConfig)) { + methods = methodsFromConfig.split(","); + } else { + methods = DEFAULT_UNUSED_METHODS; + } + unusedMethods = new HashSet<String>(Arrays.asList(methods)); + } + + @Override + public void doFilter(ServletRequest request, + ServletResponse response, + FilterChain chain) throws IOException, ServletException { + + // creates a proxy of a lazy transaction + + TopiaTransactionProxyInvocationHandler proxyInvocationHandler = + new TopiaTransactionProxyInvocationHandler(request); + + TopiaContext proxy = (TopiaContext) Proxy.newProxyInstance( + getClass().getClassLoader(), + new Class<?>[]{TopiaContext.class, + TopiaContextImplementor.class}, + proxyInvocationHandler + ); + + // push it in request as an attribute + request.setAttribute(requestAttributeName, proxy); + try { + + // chain to next filter + chain.doFilter(request, response); + } finally { + + // close the real transaction + onCloseTransaction(proxyInvocationHandler.transaction); + } + } + + /** + * Hook method called when a method with his name in + * {@link #excludeMethods} was invoked on the proxied transaction. + * + * @param proxy proxied transaction + * @param method method invoked + * @param args arguments of the invoked method + * @return the return code of the method + * @throws Throwable if any error to do. + */ + protected Object onExcludeMethod(Object proxy, + Method method, + Object[] args) throws Throwable { + + // not authorized + throw new IllegalAccessException( + "Not allowed to access method " + method.getName() + " on " + + proxy); + } + + /** + * Hook method to close the topia transaction of the request at the end of + * the request when all filter has been consumed. + * + * @param transaction the transaction to close (can be null if transaction + * was not required while the current request) + * @since 1.9.1 + */ + protected void onCloseTransaction(TopiaContext transaction) { + 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("closing transaction " + transaction); + } + try { + // let's rollback transaction if the transaction was not rollbacked nor committed + // as the topia context close context does not affect hibernate transaction + // so if something bad happen then we will always have a + Transaction tx = ((TopiaContextImplementor) transaction).getHibernate().getTransaction(); + if (!tx.wasCommitted() && !tx.wasRolledBack()) { + if (log.isDebugEnabled()) { + log.debug("rollback transaction!"); + } + tx.rollback(); + } + transaction.closeContext(); + } catch (TopiaException e) { + throw new TopiaRuntimeException(e); + } + } + } + + /** + * Hook method called when a method with his name in + * {@link #unusedMethods} was invoked on the proxied transaction + * while the underlying transaction is still not opened. + * + * @param proxy the proxy itself + * @param method method invoked + * @param args arguments of the invoked method + * @return the return code of the method + * @throws Throwable if any error to do. + */ + protected Object onUnusedMethod(Object proxy, + Method method, + Object[] args) throws Throwable { + + // by-pass method since no transaction found + + String methodName = method.getName(); + if (log.isDebugEnabled()) { + log.debug("Skip execution of method " + methodName + + " since no transaction is instanciated."); + } + + Set<String> methods = getUnusedMethods(); + if (methods.contains("toString")) { + + return "No transaction opened yet for this proxy"; + } + + if (methods.contains("isClosed")) { + + return false; + } + + if (methods.contains("equals")) { + + return false; + } + + if (methods.contains("hashCode")) { + + return 0; + } + + if (methods.contains("getClass")) { + + return TopiaContext.class; + } + + return null; + } + + /** + * Handler of a proxy on a {@link TopiaContext}. + * + * @see #excludeMethods + */ + public class TopiaTransactionProxyInvocationHandler implements InvocationHandler { + + /** Incoming request that creates this handler. */ + protected final ServletRequest request; + + /** Target to use for the proxy. */ + protected TopiaContext transaction; + + protected TopiaTransactionProxyInvocationHandler(ServletRequest request) { + this.request = request; + } + + @Override + public Object invoke(Object proxy, + Method method, + Object[] args) throws Throwable { + + String methodName = method.getName(); + + if (getExcludeMethods().contains(methodName)) { + + Object result = onExcludeMethod(proxy, method, args); + return result; + } + + if (transaction == null) { + + if (log.isTraceEnabled()) { + log.trace("transaction started due to a call to " + methodName); + } + + if (getUnusedMethods().contains(methodName)) { + + Object result = onUnusedMethod(proxy, method, args); + return result; + } + + // first time transaction is required, create its + transaction = beginTransaction(request); + + 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; + } + } + } + +} -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.