View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software 
12   * distributed under the License is distributed on an "AS IS" BASIS, 
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
14   * See the License for the specific language governing permissions and 
15   * limitations under the License.
16   */
17  package org.apache.jdo.util.web;
18  
19  import java.io.InputStream;
20  import java.io.IOException;
21  
22  import javax.jdo.JDOHelper;
23  import javax.jdo.PersistenceManagerFactory;
24  import javax.jdo.PersistenceManager;
25  
26  import javax.servlet.Filter;
27  import javax.servlet.FilterChain;
28  import javax.servlet.FilterConfig;
29  import javax.servlet.ServletContext;
30  import javax.servlet.ServletException;
31  import javax.servlet.ServletRequest;
32  import javax.servlet.ServletResponse;
33  
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  
37  /** 
38   * This implementation of the servlet Filter interface creates a JDO 
39   * PersistenceManager, stores it as a request attribute and as a ThreadLocal. 
40   * It closes the PersistenceManager after the filter chain has returned.
41   * The idea for this class is taken from the JavaOne 2003 presentation 
42   * "Using Struts with Java Data Objects" by Craig Russell, Craig McClanahan
43   * and Amy Roh.
44   * <p>
45   * To setup the filter add the following to your deployment descriptor:
46   * <pre>
47   * &lt;filter&gt;
48   *     &lt;filter-name&gt;JDOFilter&lt;/filter-name&gt;
49   *     &lt;filter-class&gt;org.apache.jdo.util.web.JDOFilter&lt;/filter-class&gt;
50   * &lt;/filter&gt;
51   * &lt;filter-mapping&gt;
52   *     &lt;filter-name&gt;JDOFilter&lt;/filter-name&gt;
53   *     &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
54   * &lt;/filter-mapping&gt;
55   * </pre>
56   * The JDOFilter supports two filter initialization paramters:
57   * <ul>
58   * <li><code>pmfPropsResource</code>: the name of the PersistenceManagerFactory
59   * properties resource. Default is <code>/WEB-INF/pmf.properties</code>.</li>
60   * <li><code>pmRequestAttrName</code>: the name of the request attribute used to
61   * store the PersistenceManager instance. Default is <code>jdoPM</code>.
62   * </ul>
63   * This is an sample filter definition using initialization parameter:
64   * <pre>
65   * &lt;filter&gt;
66   *     &lt;filter-name&gt;JDOFilter&lt;/filter-name&gt;
67   *     &lt;filter-class&gt;org.apache.jdo.util.web.JDOFilter&lt;/filter-class&gt;
68   *     &lt;init-param&gt;
69   *         &lt;param-name&gt;pmfPropsResource&lt;/param-name&gt;
70   *         &lt;param-value&gt;/WEB-INF/pmf.properties&lt;/param-value&gt;
71   *     &lt;/init-param&gt;
72   *     &lt;init-param&gt;
73   *         &lt;param-name&gt;pmRequestAttrName&lt;/param-name&gt;
74   *         &lt;param-value&gt;jdoPM&lt;/param-value&gt;
75   *     &lt;/init-param&gt;
76   * &lt;/filter&gt;
77   * </pre>
78   * It is possible to define multiple filters in the deployment descriptor, all 
79   * using the JDOFilter class. In this case it is important to specify the name 
80   * of the PersistenceManager request attribute in the filter configuration by 
81   * setting the pmRequestAttrName initialization paramter. Otherwise, the 
82   * different filter instances would try to use the same request attribute. 
83   * Please note, in case of multiple JDOFilter instances, only the first filter 
84   * stores its PersistenceManager as a ThreadLocal. 
85   * <p>
86   * The static method {@link #getThreadLocalPM()} allows retrieving the 
87   * PersistenceManager instance bound to the current thread.
88   */
89  public class JDOFilter
90      implements Filter  {
91  
92      /** The name of the JDOFilter initialization parameter allowing to specify 
93       * the name of the pmf properties resource. */
94      public static final String PMF_PROPS_RESOURCE_PARAM = "pmfPropsResource";
95  
96      /** The default PMF properties resource. */
97      public static final String PMF_PROPS_RESOURCE_DEFAULT = 
98          "/WEB-INF/pmf.properties";
99      
100     /** The name of the JDOFilter initialization parameter allowing to specify 
101      * the name of the pm request attribute.  */
102     public static final String PM_REQUEST_ATTR_NAME_PARAM = "pmRequestAttrName";
103     
104     /** The name of the request attribute storing the PersistenceManager. */
105     public static final String PM_REQUEST_ATTR_NAME_DEFAULT = "jdoPM";
106 
107     /** The ThreadLocal storing the PersistenceManager. */
108     private static ThreadLocal pmThreadLocal = new ThreadLocal();
109     
110     /** Logging support. */
111     private static final Log logger = LogFactory.getLog(JDOFilter.class);
112         
113     /** The PMF instance created by the init method. */
114     private PersistenceManagerFactory pmf;
115     
116     /** The name of the request attribute holding the PersistenceManager. */
117     private String pmRequestAttrName;
118         
119     // ===== javax.servlet.Filter methods =====
120 
121     /** Called by the web container to indicate to a filter that it is being 
122      * placed into service. 
123      * <p>
124      * This implementation creates a JDO PersistenceManagerFactory instance 
125      * using a properties resource specified by an initialization parameter 
126      * called {@link #PMF_PROPS_RESOURCE_PARAM} or defaulted to 
127      * {@link #PMF_PROPS_RESOURCE_DEFAULT}. The method checks for another 
128      * initialization parameter {@link #PM_REQUEST_ATTR_NAME_PARAM} that may be
129      * used to specify the name of the request attribute holding the 
130      * PersistenceManager instance. The name defaults to 
131      * {@link #PM_REQUEST_ATTR_NAME_DEFAULT} if there is no such initialization
132      * parameter.
133      * @param filterConfig the filter configuration object. 
134      */
135     public void init(FilterConfig filterConfig) throws ServletException {
136         String pmfPropsResource = 
137             filterConfig.getInitParameter(PMF_PROPS_RESOURCE_PARAM);
138         boolean usingPMFPropsResourceDefault = false;
139         if ((pmfPropsResource == null) || pmfPropsResource.length() == 0) {
140             pmfPropsResource = PMF_PROPS_RESOURCE_DEFAULT;
141             usingPMFPropsResourceDefault = true;
142         }
143         ServletContext context = filterConfig.getServletContext();
144         InputStream stream = context.getResourceAsStream(pmfPropsResource);
145         if (stream == null) {
146             ServletException ex = new ServletException(
147                 "Error during initialization of JDOFilter: " + 
148                 "Cannot find JDO PMF properties resource " +
149                 (usingPMFPropsResourceDefault ? "defaulted to " : "") +
150                 pmfPropsResource);
151             throw ex;
152         }
153         pmf = JDOHelper.getPersistenceManagerFactory(stream);
154         if (logger.isDebugEnabled()) {
155             logger.debug(
156                 "Created PMF " + pmf +
157                 "\nPMF properties: " +
158                 "\n\t driver         = " + pmf.getConnectionDriverName() + 
159                 "\n\t URL            = " + pmf.getConnectionURL() + 
160                 "\n\t userName       = " + pmf.getConnectionUserName() + 
161                 "\n\t mapping        = " + pmf.getMapping() +
162                 "\n\t optimistic     = " + pmf.getOptimistic() +
163                 "\n\t retainValues   = " + pmf.getRetainValues() +
164                 "\n\t restoreValues  = " + pmf.getRestoreValues() +
165                 "\n\t nonTxRead      = " + pmf.getNontransactionalRead() +
166                 "\n\t nonTxWrite     = " + pmf.getNontransactionalWrite() +
167                 "\n\t ignoreCache    = " + pmf.getIgnoreCache() +
168                 "\n\t detachOnCommit = " + pmf.getDetachAllOnCommit() +
169                 "\n\t properties     = " + pmf.getProperties());
170         }
171 
172         // get the name of the request attribute holding the PersistenceManager
173         pmRequestAttrName = 
174             filterConfig.getInitParameter(PM_REQUEST_ATTR_NAME_PARAM);
175         if ((pmRequestAttrName == null) || pmRequestAttrName.length() == 0) {
176             pmRequestAttrName = PM_REQUEST_ATTR_NAME_DEFAULT;
177         }
178     }
179 
180     /** The doFilter method of the Filter is called by the container each time a
181      * request/response pair is passed through the chain due to a client request
182      * for a resource at the end of the chain. This implementation creates a 
183      * PersistenceManager, stores it as a request attribute and in a ThreadLocal
184      * and then calls the filter chain. It closes the PersistenceManager after 
185      * the chain returns.
186      * @param request the resquest
187      * @param response the response 
188      * @param chain the filter chain   
189      */
190     public void doFilter(ServletRequest request, ServletResponse response, 
191                          FilterChain chain) 
192         throws IOException, ServletException {
193         PersistenceManager pm = null;
194         try {
195             pm = initializePM(request);
196             if (logger.isDebugEnabled()) {
197                 logger.debug("Created PM " + pm);
198             }
199             chain.doFilter(request, response);
200         } finally {
201             if (logger.isDebugEnabled()) {
202                 logger.debug("Close PM " + pm);
203             }
204             finalizePM(request, pm);
205         }
206     }
207 
208     /** Called by the web container to indicate to a filter that it is being 
209      * taken out of service. This implementation closes the 
210      * PersistenceManagerFactory. 
211      */
212     public void destroy() {
213         if (pmf != null) {
214             if (logger.isDebugEnabled()) {
215                 logger.debug("Close PMF " + pmf);
216             }
217             pmf.close();
218             pmf = null;
219         }
220     }
221     
222     // ===== Public methods not defined in Filter =====
223     
224     /** Returns the PersistenceManager instance bound to the current thread 
225      * using a ThreadLocal. 
226      * @return the PersistenceManager bound to the current thread.
227      */
228     public static PersistenceManager getThreadLocalPM() {
229          return (PersistenceManager)pmThreadLocal.get();
230     }
231     
232     // ===== Internal helper methods =====
233 
234     /** Helper method to create and store a new PersistenceManager. The method 
235      * stores the PersistenceManager as value of the request attribute denoted 
236      * by {@link #pmRequestAttrName} and in a ThreadLocal. Use static method
237      * {@link #getThreadLocalPM()} to retrieve the PersistenceManager instance 
238      * bound to the current thread.
239      * @param request the request to store in the PersistenceManager as 
240      * attribute.
241      * @return a new PersistenceManager
242      */
243     private PersistenceManager initializePM(ServletRequest request) 
244         throws ServletException {
245         PersistenceManager pm = pmf.getPersistenceManager();
246         if (request.getAttribute(pmRequestAttrName) != null) {
247             ServletException ex = new ServletException(
248                 "JDOFilter: error during PM intialization, request attribute " +
249                 pmRequestAttrName + " already defined as " + 
250                  request.getAttribute(pmRequestAttrName));
251             throw ex;
252         }
253         request.setAttribute(pmRequestAttrName, pm);
254         if (pmThreadLocal.get() == null) {
255             pmThreadLocal.set(pm);
256         }
257         return pm;
258     }    
259 
260     /** Helper method to finalize a PersistenceManager. The method closes the 
261      * specified PersistenceManager. If there is an active transaction it is 
262      * rolled back before the PersistenceManager is closed. The method also 
263      * removes the request attribute holding a PersistenceManager and nullifies
264      * the ThreadLocal value, but only if it is the specified 
265      * PersistenceManager.
266      * @param request the request holding the PersistenceManager as attribute.
267      * @param the PersistenceManager
268      */
269     private void finalizePM(ServletRequest request, PersistenceManager pm) {
270         if (pm == null) {
271             return;
272         }
273         if (!pm.isClosed()) {
274             if (pm.currentTransaction().isActive()) {
275                 pm.currentTransaction().rollback();
276             }
277             pm.close();
278         }
279         // Remove request attribute if it is the specified pm
280         if (request.getAttribute(pmRequestAttrName) == pm) {
281             request.removeAttribute(pmRequestAttrName);
282         }
283         // Nullify the thread local if it is the specified pm
284         if (pmThreadLocal.get() == pm) {
285             pmThreadLocal.set(null);
286         }
287     }
288 
289 }