001    
002    /*
003     * Copyright (C) 2011 Archie L. Cobbs. All rights reserved.
004     *
005     * $Id: OpenTransactionInViewFilter.java 2 2011-02-05 21:51:43Z archie.cobbs $
006     */
007    
008    package org.dellroad.stuff.servlet;
009    
010    import java.io.IOException;
011    
012    import javax.servlet.FilterChain;
013    import javax.servlet.ServletException;
014    import javax.servlet.http.HttpServletRequest;
015    import javax.servlet.http.HttpServletResponse;
016    
017    import org.slf4j.Logger;
018    import org.slf4j.LoggerFactory;
019    import org.springframework.transaction.PlatformTransactionManager;
020    import org.springframework.transaction.TransactionStatus;
021    import org.springframework.transaction.support.DefaultTransactionDefinition;
022    import org.springframework.transaction.support.TransactionCallback;
023    import org.springframework.transaction.support.TransactionTemplate;
024    import org.springframework.web.context.WebApplicationContext;
025    import org.springframework.web.context.support.WebApplicationContextUtils;
026    import org.springframework.web.filter.OncePerRequestFilter;
027    
028    /**
029     * Servlet filter that wraps execution in a transaction. A {@link TransactionTemplate} must exist in the
030     * associated {@link WebApplicationContext} and is identified by name via {@link #setTransactionManagerBeanName
031     * setTransactionManagerBeanName()} (by default, {@link #DEFAULT_TRANSACTION_MANAGER_BEAN_NAME}).
032     *
033     * <p>
034     * Transaction properties are configurable via filter <code>&lt;init-param&gt;</code>'s {@code isolation},
035     * {@code propagation}, and {@code readOnly}.
036     * </p>
037     */
038    public class OpenTransactionInViewFilter extends OncePerRequestFilter {
039    
040        public static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager";
041    
042        protected final Logger log = LoggerFactory.getLogger(getClass());
043    
044        private final DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
045    
046        private WebApplicationContext webApplicationContext;
047        private String transactionManagerBeanName = DEFAULT_TRANSACTION_MANAGER_BEAN_NAME;
048        private PlatformTransactionManager transactionManager;
049    
050        @Override
051        protected void initFilterBean() throws ServletException {
052            super.initFilterBean();
053            log.debug("finding containing WebApplicationContext");
054            try {
055                this.webApplicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
056            } catch (IllegalStateException e) {
057                throw new ServletException("could not locate containing WebApplicationContext");
058            }
059        }
060    
061        public void setIsolation(String isolation) {
062            this.transactionDefinition.setIsolationLevelName(isolation);
063        }
064    
065        public void setPropagation(String propagation) {
066            this.transactionDefinition.setPropagationBehaviorName(propagation);
067        }
068    
069        public void setReadOnly(boolean readOnly) {
070            this.transactionDefinition.setReadOnly(readOnly);
071        }
072    
073        public void setTransactionManagerBeanName(String transactionManagerBeanName) {
074            this.transactionManagerBeanName = transactionManagerBeanName;
075        }
076    
077        protected synchronized PlatformTransactionManager getTransactionManager() {
078            if (this.transactionManager == null) {
079                this.transactionManager = this.webApplicationContext.getBean(
080                  this.transactionManagerBeanName, PlatformTransactionManager.class);
081            }
082            return this.transactionManager;
083        }
084    
085        @Override
086        protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response,
087          final FilterChain filterChain) throws ServletException, IOException {
088            TransactionTemplate transactionTemplate = new TransactionTemplate(getTransactionManager(), this.transactionDefinition);
089            try {
090                transactionTemplate.execute(new TransactionCallback<Void>() {
091    
092                    @Override
093                    public Void doInTransaction(TransactionStatus status) {
094                        try {
095                            filterChain.doFilter(request, response);
096                        } catch (IOException e) {
097                            throw new RuntimeException(e);
098                        } catch (ServletException e) {
099                            throw new RuntimeException(e);
100                        }
101                        return null;
102                    }
103                });
104            } catch (RuntimeException e) {
105                Throwable nested = e.getCause();
106                if (nested instanceof IOException)
107                    throw (IOException)nested;
108                if (nested instanceof ServletException)
109                    throw (ServletException)nested;
110                throw e;
111            }
112        }
113    }
114