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  
17  package net.sf.ehcache.exceptionhandler;
18  
19  import net.sf.ehcache.Ehcache;
20  
21  import java.lang.reflect.InvocationHandler;
22  import java.lang.reflect.Method;
23  import java.lang.reflect.Proxy;
24  
25  import org.slf4j.Logger;
26  import org.slf4j.LoggerFactory;
27  
28  
29  /***
30   * A dynamic proxy which provides CacheException handling.
31   * <p/>
32   * The ehcache configuration will create and register in the <code>CacheManager</code> {@link Ehcache}s decorated
33   * with this dynamic proxy. See following for programmatic use.
34   * <p/>
35   * The createProxy factory method may be used to simply create a proxy. Otherwise the calling client
36   * will need code similar to:
37   * <pre>
38   * (Ehcache) Proxy.newProxyInstance(ehcache.getClass().getClassLoader(), new Class[]{ Ehcache.class },
39   * new ExceptionHandlingDynamicCacheProxy(ehcache));</pre>
40   * <p/>
41   * A common usage is to create a proxy and then register the proxy in <code>CacheManager</code> in place of the
42   * underlying cache. To do that create a proxy and then call
43   * <pre>
44   * cacheManager.replaceCacheWithDecoratedCache(Ehcache cache, Ehcache decoratedCache);
45   * </pre>
46   * All clients accessing the cache through<code>cacheManager.getEhcache()</code> will then receive proxy references.
47   * <p/>
48   * See CacheTest for a perf test.
49   *
50   * @author <a href="mailto:gluck@gregluck.com">Greg Luck</a>
51   * @version $Id: ExceptionHandlingDynamicCacheProxy.html 13146 2011-08-01 17:12:39Z oletizi $
52   */
53  public final class ExceptionHandlingDynamicCacheProxy implements InvocationHandler {
54  
55      private static final Logger LOG = LoggerFactory.getLogger(ExceptionHandlingDynamicCacheProxy.class.getName());
56  
57      private Ehcache ehcache;
58  
59      /***
60       * Constructor: Use with something like:
61       * <pre>
62       * (Ehcache) Proxy.newProxyInstance(ehcache.getClass().getClassLoader(), new Class[]{ Ehcache.class },
63       * new ExceptionHandlingDynamicCacheProxy(ehcache));</pre>
64       * @param ehcache the backing ehcache
65       */
66      public ExceptionHandlingDynamicCacheProxy(Ehcache ehcache) {
67          this.ehcache = ehcache;
68      }
69  
70      /***
71       * A simple factory method to hide the messiness of creating the proxy from clients.
72       *
73       * @param ehcache the target cache
74       * @return a proxied Ehcache
75       */
76      public static Ehcache createProxy(Ehcache ehcache) {
77          return (Ehcache) Proxy.newProxyInstance(ehcache.getClass().getClassLoader(), new Class[]{Ehcache.class},
78                  new ExceptionHandlingDynamicCacheProxy(ehcache));
79      }
80  
81  
82  
83      /***
84       * Processes a method invocation on a proxy instance and returns
85       * the result.  This method will be invoked on an invocation handler
86       * when a method is invoked on a proxy instance that it is
87       * associated with.
88       *
89       * @param proxy  the proxy instance that the method was invoked on
90       * @param method the <code>Method</code> instance corresponding to
91       *               the interface method invoked on the proxy instance.  The declaring
92       *               class of the <code>Method</code> object will be the interface that
93       *               the method was declared in, which may be a superinterface of the
94       *               proxy interface that the proxy class inherits the method through.
95       * @param args   an array of objects containing the values of the
96       *               arguments passed in the method invocation on the proxy instance,
97       *               or <code>null</code> if interface method takes no arguments.
98       *               Arguments of primitive types are wrapped in instances of the
99       *               appropriate primitive wrapper class, such as
100      *               <code>java.lang.Integer</code> or <code>java.lang.Boolean</code>.
101      * @return the value to return from the method invocation on the
102      *         proxy instance.  If the declared return type of the interface
103      *         method is a primitive type, then the value returned by
104      *         this method must be an instance of the corresponding primitive
105      *         wrapper class; otherwise, it must be a type assignable to the
106      *         declared return type.  If the value returned by this method is
107      *         <code>null</code> and the interface method's return type is
108      *         primitive, then a <code>NullPointerException</code> will be
109      *         thrown by the method invocation on the proxy instance.  If the
110      *         value returned by this method is otherwise not compatible with
111      *         the interface method's declared return type as described above,
112      *         a <code>ClassCastException</code> will be thrown by the method
113      *         invocation on the proxy instance.
114      * @throws Throwable the exception to throw from the method
115      *                   invocation on the proxy instance.  The exception's type must be
116      *                   assignable either to any of the exception types declared in the
117      *                   <code>throws</code> clause of the interface method or to the
118      *                   unchecked exception types <code>java.lang.RuntimeException</code>
119      *                   or <code>java.lang.Error</code>.  If a checked exception is
120      *                   thrown by this method that is not assignable to any of the
121      *                   exception types declared in the <code>throws</code> clause of
122      *                   the interface method, then an
123      *                   {@link java.lang.reflect.UndeclaredThrowableException} containing the
124      *                   exception that was thrown by this method will be thrown by the
125      *                   method invocation on the proxy instance.
126      * @see java.lang.reflect.UndeclaredThrowableException
127      *
128      */
129     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
130         Object invocationResult = null;
131         try {
132             invocationResult = method.invoke(ehcache, args);
133         } catch (Exception e) {
134             CacheExceptionHandler cacheExceptionHandler = ehcache.getCacheExceptionHandler();
135             if (cacheExceptionHandler != null) {
136                 String keyAsString = null;
137                 //should be a CacheException
138                 Throwable cause = e.getCause();
139                 if (cause != null) {
140                     keyAsString = extractKey(cause.getMessage());
141                 }
142                 Exception causeAsException = null;
143                 try {
144                     causeAsException = (Exception) cause;
145                 } catch (ClassCastException cce) {
146                     //we only handle exceptions, not errors.
147                         LOG.debug("Underlying cause was not an Exception: {}", cce);
148                 }
149 
150                 cacheExceptionHandler.onException(ehcache, keyAsString, causeAsException);
151             } else {
152                 throw e.getCause();
153             }
154 
155         }
156         return invocationResult;
157     }
158 
159     /***
160      * Extracts the key from the message, if any
161      */
162     static String extractKey(String message) {
163         if (message == null) {
164             return null;
165         }
166         int beginIndex = message.lastIndexOf("key ");
167         if (beginIndex < 0) {
168             return null;
169         }
170         beginIndex += "key ".length();
171         int endIndex = beginIndex;
172         for (int i = beginIndex; i < message.length(); i++) {
173             char character = message.charAt(i);
174             if (character == ' ') {
175                 break;
176             }
177             endIndex = i;
178         }
179         endIndex++;
180         if (endIndex > message.length()) {
181             endIndex = message.length();
182         }
183         String key = message.substring(beginIndex, endIndex);
184         return key;
185     }
186 }