View Javadoc

1   /***
2    *  Copyright 2003-2010 Terracotta, Inc.
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package net.sf.ehcache.transaction.manager;
17  
18  import java.lang.reflect.InvocationTargetException;
19  import java.lang.reflect.Method;
20  import java.util.Properties;
21  import java.util.concurrent.locks.Lock;
22  import java.util.concurrent.locks.ReentrantLock;
23  
24  import javax.naming.InitialContext;
25  import javax.naming.NamingException;
26  import javax.transaction.TransactionManager;
27  import javax.transaction.xa.XAResource;
28  
29  import net.sf.ehcache.transaction.xa.EhcacheXAResource;
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  
33  /***
34   * Default {@link TransactionManagerLookup} implementation, that will be used by an {@link net.sf.ehcache.Cache#initialise() initializing}
35   * Cache should the user have not specified otherwise.<p>
36   * This implementation will:
37   * <ol>
38   * <li>try lookup an {@link javax.naming.InitialContext};
39   * <li>if successful, lookup a TransactionManager under java:/TransactionManager, this location can be overridden;
40   * <li>if it failed, or couldn't find {@link javax.transaction.TransactionManager} instance, look for a Bitronix TransactionManager;
41   * <li>and finally an Atomikos one.
42   * </ol>
43   *
44   * To specify under what specific name the TransactionManager is to be found, you can provide a jndiName property
45   * using {@link #setProperties(java.util.Properties)}. That can be set in the CacheManager's configuration file.
46   *
47   * The first TransactionManager instance is then kept and returned on each {@link #getTransactionManager()} call
48   *
49   * @author Alex Snaps
50   */
51  public class DefaultTransactionManagerLookup implements TransactionManagerLookup {
52  
53      private static final Logger LOG = LoggerFactory.getLogger(DefaultTransactionManagerLookup.class.getName());
54  
55      private       transient TransactionManager transactionManager;
56      private       transient String             vendor             = "";
57      private       transient Properties         properties         = new Properties();
58      private final           Lock               lock               = new ReentrantLock();
59  
60      private final JndiSelector defaultJndiSelector = new JndiSelector("genericJNDI", "java:/TransactionManager");
61  
62      private final Selector[] transactionManagerSelectors = new Selector[] {defaultJndiSelector,
63              new JndiSelector("Weblogic", "javax.transaction.TransactionManager"),
64              new FactorySelector("Bitronix", "bitronix.tm.TransactionManagerServices"),
65              new ClassSelector("Atomikos", "com.atomikos.icatch.jta.UserTransactionManager"),
66      };
67  
68      /***
69       * Lookup available txnManagers
70       * @return TransactionManager
71       */
72      public TransactionManager getTransactionManager() {
73          if (transactionManager == null) {
74              lock.lock();
75              try {
76                  if (transactionManager == null) {
77                      lookupTransactionManager();
78                  }
79              } finally {
80                  lock.unlock();
81              }
82          }
83          return transactionManager;
84      }
85  
86      /***
87       * {@inheritDoc}
88       */
89      public void register(EhcacheXAResource resource) {
90          if (vendor.equals("Bitronix")) {
91              registerResourceWithBitronix(resource.getCacheName(), resource);
92          }
93      }
94  
95      /***
96       * {@inheritDoc}
97       */
98      public void unregister(EhcacheXAResource resource) {
99          if (vendor.equals("Bitronix")) {
100             unregisterResourceWithBitronix(resource.getCacheName(), resource);
101         }
102     }
103 
104     /***
105      * {@inheritDoc}
106      */
107     public void setProperties(Properties properties) {
108         this.properties = properties;
109         parseProperties();
110     }
111 
112     private void parseProperties() {
113         if (this.properties != null) {
114             String jndiName = this.properties.getProperty("jndiName");
115             if (jndiName != null) {
116                 defaultJndiSelector.setJndiName(jndiName);
117             }
118         }
119     }
120 
121     private void registerResourceWithBitronix(String uniqueName, EhcacheXAResource resource) {
122         ClassLoader cl = Thread.currentThread().getContextClassLoader();
123         if (cl == null) {
124             cl = ClassLoader.getSystemClassLoader();
125         }
126         try {
127             // This requires BTM 2.0.0 at least
128             Class producerClass = cl.loadClass("bitronix.tm.resource.ehcache.EhCacheXAResourceProducer");
129             
130             Class[] signature = new Class[] {String.class, XAResource.class};
131             Object[] args = new Object[] {uniqueName, resource};
132             Method method = producerClass.getMethod("registerXAResource", signature);
133             method.invoke(null, args);
134         } catch (Exception e) {
135             LOG.error("unable to register resource of cache " + uniqueName + " with BTM", e);
136         }
137     }
138 
139     private void unregisterResourceWithBitronix(String uniqueName, EhcacheXAResource resource) {
140         ClassLoader cl = Thread.currentThread().getContextClassLoader();
141         if (cl == null) {
142             cl = ClassLoader.getSystemClassLoader();
143         }
144         try {
145             // This requires BTM 2.0.0 at least
146             Class producerClass = cl.loadClass("bitronix.tm.resource.ehcache.EhCacheXAResourceProducer");
147 
148             Class[] signature = new Class[] {String.class, XAResource.class};
149             Object[] args = new Object[] {uniqueName, resource};
150             Method method = producerClass.getMethod("unregisterXAResource", signature);
151             method.invoke(null, args);
152         } catch (Exception e) {
153             LOG.error("unable to unregister resource of cache " + uniqueName + " with BTM", e);
154         }
155     }
156 
157     private void lookupTransactionManager() {
158         InitialContext context = null;
159         try {
160             context = new InitialContext();
161         } catch (NamingException e) {
162             LOG.debug("Couldn't create an InitialContext", e);
163         }
164 
165         for (Selector selector : transactionManagerSelectors) {
166             this.transactionManager = selector.lookup(context);
167             if (this.transactionManager != null) {
168                 this.vendor = selector.getVendor();
169                 LOG.debug("Found TransactionManager for {}", vendor);
170                 return;
171             }
172         }
173 
174         LOG.warn("No TransactionManager located!");
175     }
176 
177     /***
178      *
179      */
180     private abstract static class Selector {
181 
182         private final String vendor;
183 
184         protected Selector(final String vendor) {
185             this.vendor = vendor;
186         }
187 
188         public String getVendor() {
189             return vendor;
190         }
191 
192         protected abstract TransactionManager lookup(InitialContext initialContext);
193     }
194 
195     /***
196      * 
197      */
198     private static final class JndiSelector extends Selector {
199 
200         private volatile String jndiName;
201 
202         private JndiSelector(final String vendor, final String jndiName) {
203             super(vendor);
204             this.jndiName = jndiName;
205         }
206 
207         public String getJndiName() {
208             return jndiName;
209         }
210 
211         public void setJndiName(final String jndiName) {
212             this.jndiName = jndiName;
213         }
214 
215         @Override
216         protected TransactionManager lookup(InitialContext initialContext) {
217 
218             if (initialContext == null) {
219                 return null;
220             }
221 
222             Object jndiObject;
223             try {
224                 jndiObject = initialContext.lookup(getJndiName());
225                 if (jndiObject instanceof TransactionManager) {
226                     return (TransactionManager) jndiObject;
227                 }
228             } catch (NamingException e) {
229                 LOG.debug("Couldn't locate TransactionManager for {} under {}", getVendor(), getJndiName());
230             }
231             return null;
232         }
233     }
234 
235     /***
236      *
237      */
238     private static final class FactorySelector extends Selector {
239 
240         private final String factoryClassName;
241 
242         private FactorySelector(final String vendor, final String factoryClassName) {
243             super(vendor);
244             this.factoryClassName = factoryClassName;
245         }
246 
247         @Override
248         protected TransactionManager lookup(final InitialContext initialContext) {
249             TransactionManager transactionManager = null;
250             ClassLoader cl = Thread.currentThread().getContextClassLoader();
251             if (cl == null) {
252                 cl = ClassLoader.getSystemClassLoader();
253             }
254             try {
255                 Class factoryClass = cl.loadClass(factoryClassName);
256                 Class[] signature = null;
257                 Object[] args = null;
258                 Method method = factoryClass.getMethod("getTransactionManager", signature);
259                 transactionManager = (TransactionManager) method.invoke(null, args);
260             } catch (ClassNotFoundException e) {
261                 LOG.debug("FactorySelector failed lookup", e);
262             } catch (NoSuchMethodException e) {
263                 LOG.debug("FactorySelector failed lookup", e);
264             } catch (InvocationTargetException e) {
265                 LOG.debug("FactorySelector failed lookup", e);
266             } catch (IllegalAccessException e) {
267                 LOG.debug("FactorySelector failed lookup", e);
268             }
269             return transactionManager;
270         }
271     }
272 
273     /***
274      *
275      */
276     private static final class ClassSelector extends Selector {
277 
278         private final String classname;
279 
280         private ClassSelector(final String vendor, final String classname) {
281             super(vendor);
282             this.classname = classname;
283         }
284 
285         @Override
286         protected TransactionManager lookup(final InitialContext initialContext) {
287             TransactionManager transactionManager = null;
288             ClassLoader cl = Thread.currentThread().getContextClassLoader();
289             if (cl == null) {
290                 cl = ClassLoader.getSystemClassLoader();
291             }
292             try {
293                 Class txManagerClass = cl.loadClass(classname);
294                 transactionManager = (TransactionManager) txManagerClass.newInstance();
295             } catch (ClassNotFoundException e) {
296                 LOG.debug("FactorySelector failed lookup", e);
297             } catch (InstantiationException e) {
298                 LOG.debug("FactorySelector failed lookup", e);
299             } catch (IllegalAccessException e) {
300                 LOG.debug("FactorySelector failed lookup", e);
301             }
302             return transactionManager;
303         }
304     }
305 
306 }