View Javadoc
1   /*
2    * Copyright Openmind http://www.openmindonline.it
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 it.openutils.spring.rmibernate.server.aspects;
18  
19  import it.openutils.spring.rmibernate.server.aspects.util.EntitySerializer;
20  import it.openutils.spring.rmibernate.shared.LazyReference;
21  import it.openutils.spring.rmibernate.shared.managers.HibernateLazyService;
22  
23  import java.lang.reflect.Field;
24  import java.lang.reflect.Method;
25  import java.lang.reflect.Modifier;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.Collections;
29  import java.util.HashMap;
30  import java.util.HashSet;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Set;
34  
35  import net.sf.cglib.proxy.Callback;
36  import net.sf.cglib.proxy.CallbackFilter;
37  import net.sf.cglib.proxy.Enhancer;
38  import net.sf.cglib.proxy.Factory;
39  import net.sf.cglib.proxy.MethodInterceptor;
40  import net.sf.cglib.proxy.MethodProxy;
41  import net.sf.cglib.proxy.NoOp;
42  
43  import org.apache.commons.collections.CollectionUtils;
44  import org.apache.commons.collections.ListUtils;
45  import org.apache.commons.collections.MapUtils;
46  import org.apache.commons.collections.SetUtils;
47  import org.apache.commons.collections.Transformer;
48  import org.apache.commons.lang.builder.HashCodeBuilder;
49  import org.hibernate.EntityMode;
50  import org.hibernate.SessionFactory;
51  import org.hibernate.collection.PersistentCollection;
52  import org.hibernate.proxy.HibernateProxy;
53  import org.slf4j.Logger;
54  import org.slf4j.LoggerFactory;
55  import org.springframework.util.ReflectionUtils;
56  
57  
58  /**
59   * Proxy object with {@link EntitySerializer} and Intercept writeReplace calls
60   * @author mmolaschi
61   * @version $Id: SerializationInterceptor.java 716 2008-03-03 14:35:57Z fcarone $
62   */
63  public class SerializationInterceptor implements MethodInterceptor
64  {
65  
66      private static ThreadLocal<List<Object>> processed = new ThreadLocal<List<Object>>();
67  
68      private static ThreadLocal<HibernateLazyService> hibernateLazyService = new ThreadLocal<HibernateLazyService>();
69  
70      @SuppressWarnings("unchecked")
71      private static Map<Class, Object> proxies = Collections.synchronizedMap(new HashMap<Class, Object>());
72  
73      /**
74       * log
75       */
76      private static Logger log = LoggerFactory.getLogger(SerializationInterceptor.class);
77  
78      /**
79       * non proxied object
80       */
81      private Object original;
82  
83      /**
84       * hibernate sessionfactory
85       */
86      private SessionFactory sessionFactory;
87  
88      /**
89       * Constructor
90       * @param o object to be proxied
91       * @param sessionFactory hibernate sessionfactory
92       */
93      private SerializationInterceptor(Object o, SessionFactory sessionFactory)
94      {
95          this.original = o;
96          this.sessionFactory = sessionFactory;
97      }
98  
99      /**
100      * Cleans the processed beans cache
101      */
102     public static void clean()
103     {
104         processed.set(null);
105     }
106 
107     /**
108      * Get proxy object intercepting writeReplace calls
109      * @param o object to be proxied
110      * @param sessionFactory hibernate sessionfactory
111      * @return proxied object
112      */
113     public static Object getEnhancedObject(Object o, SessionFactory sessionFactory)
114     {
115         return getEnhancedObject(o, sessionFactory, null);
116     }
117 
118     /**
119      * Get proxy object intercepting writeReplace calls
120      * @param o object to be proxied
121      * @param sessionFactory hibernate sessionfactory
122      * @param lazyRef reference to a lazy field
123      * @return proxied object
124      */
125     @SuppressWarnings("unchecked")
126     public static Object getEnhancedObject(Object o, SessionFactory sessionFactory, LazyReference lazyRef)
127     {
128         if (o == null)
129         {
130             return null;
131         }
132 
133         if (!(o instanceof HibernateProxy)
134             && !(o instanceof PersistentCollection)
135             && processed.get() != null
136             && processed.get().contains(o))
137         {
138             return o;
139         }
140 
141         if (o.getClass().getName().startsWith("java.") && !((o instanceof Collection) || (o instanceof Map)))
142         {
143             return o;
144         }
145 
146         // check if there is an empty constructor
147         try
148         {
149             o.getClass().getConstructor(new Class[]{});
150         }
151         catch (NoSuchMethodException ex)
152         {
153             return o;
154         }
155 
156         // check if object can be subclassed
157         if (Modifier.isFinal(o.getClass().getModifiers()))
158         {
159             return o;
160         }
161 
162         try
163         {
164             // get class
165             Class clazz = o.getClass();
166 
167             // if it is an hibernateproxy get superclass
168             if (o instanceof HibernateProxy)
169             {
170                 clazz = o.getClass().getSuperclass();
171                 if (clazz == null)
172                 {
173                     clazz = o.getClass().getInterfaces()[0];
174                 }
175             }
176 
177             Callback callback = null;
178 
179             // if this is a lazy field user lazyreferenceaspect
180             if (lazyRef != null)
181             {
182                 LazyReferenceAspect lra = new LazyReferenceAspect();
183                 lra.setLazyReference(lazyRef);
184                 callback = lra;
185             }
186             else
187             {
188                 // user default interceptor
189                 callback = new SerializationInterceptor(o, sessionFactory);
190             }
191 
192             synchronized (proxies)
193             {
194                 if (proxies.containsKey(clazz))
195                 {
196                     Factory proxy = (Factory) proxies.get(clazz);
197                     return proxy.newInstance(new Callback[]{callback, NoOp.INSTANCE });
198                 }
199             }
200             // create proxy to listen on writeReplace method calls
201             Object proxy = Enhancer.create(clazz, new Class[]{EntitySerializer.class }, new CallbackFilter()
202             {
203 
204                 public int accept(Method method)
205                 {
206                     if (method.getName().equals("writeReplace"))
207                     {
208                         return 0;
209                     }
210                     if (method.getName().equals("hashCode"))
211                     {
212                         return 0;
213                     }
214                     else
215                     {
216                         return 1;
217                     }
218                 }
219 
220             }, new Callback[]{callback, NoOp.INSTANCE });
221 
222             // store proxy
223             proxies.put(clazz, ((Factory) proxy).newInstance(new Callback[]{
224                 EmptyMethodInterceptor.INSTANCE,
225                 NoOp.INSTANCE }));
226             return proxy;
227         }
228         catch (Throwable t)
229         {
230             log.warn(t.getMessage(), t);
231             return o;
232         }
233     }
234 
235     /**
236      * {@inheritDoc}
237      */
238     @SuppressWarnings("unchecked")
239     public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable
240     {
241         if (method.getName().equals("hashCode"))
242         {
243             return hashCode();
244         }
245         try
246         {
247             if (original == null)
248             {
249                 return null;
250             }
251 
252             if (original.getClass().getName().startsWith("java.")
253                 && !((original instanceof Collection) || (original instanceof Map)))
254             {
255                 return original;
256             }
257 
258             if (processed.get() == null)
259             {
260                 processed.set(new ArrayList<Object>());
261             }
262 
263             // add to processed objects
264             processed.get().add(original);
265 
266             if (original.getClass().isPrimitive())
267             {
268                 log.debug("Non enhancing primitive type: {}", original.getClass().getName());
269                 return original;
270             }
271             else if (original.getClass().isArray())
272             {
273                 // replace array entries with proxied ones
274                 Object[] array = (Object[]) original;
275                 for (int i = 0; i < array.length; i++)
276                 {
277                     array[i] = SerializationInterceptor.getEnhancedObject(array[i], sessionFactory);
278                 }
279                 return array;
280             }
281             else if (original instanceof Set)
282             {
283                 // replace set entries with proxied ones
284                 Set set = (Set) original;
285                 Set transformed = SetUtils.transformedSet(new HashSet(), transformer(sessionFactory));
286                 transformed.addAll(set);
287                 Set result = new HashSet();
288                 result.addAll(transformed);
289                 return result;
290             }
291             else if (original instanceof List)
292             {
293                 List list = (List) original;
294                 List transformed = ListUtils.transformedList(new ArrayList(), transformer(sessionFactory));
295                 transformed.addAll(list);
296                 List result = new ArrayList();
297                 result.addAll(transformed);
298                 return result;
299             }
300             else if (original instanceof Collection)
301             {
302                 // replace collection entries with proxied ones
303                 Collection collection = (Collection) original;
304                 CollectionUtils.transform(collection, transformer(sessionFactory));
305                 return collection;
306             }
307             else if (original instanceof Map)
308             {
309                 // replace map entries with proxied ones
310                 Map map = (Map) original;
311                 Map transformed = MapUtils.transformedMap(
312                     new HashMap(),
313                     transformer(sessionFactory),
314                     transformer(sessionFactory));
315 
316                 // this is needed since AbstractHashedMap used by commons collections
317                 // needs at least 1 element before transforming
318                 if (map.size() > 0)
319                 {
320                     transformed.putAll(map);
321                 }
322                 Map result = new HashMap();
323                 result.putAll(transformed);
324                 return result;
325             }
326             else
327             {
328                 // cycle on bean fields
329                 ReflectionUtils.doWithFields(original.getClass(), new ReflectionUtils.FieldCallback()
330                 {
331 
332                     /**
333                      * {@inheritDoc}
334                      */
335                     public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException
336                     {
337                         // set field accessible
338                         field.setAccessible(true);
339 
340                         // get original value
341                         Object oldValue = field.get(original);
342 
343                         // get new value
344                         Object newValue = null;
345 
346                         if (oldValue instanceof HibernateProxy || oldValue instanceof PersistentCollection)
347                         {
348                             // if this field is a hibernate proxy or a persistent collection store reference
349                             LazyReference lazyRef = new LazyReference();
350                             lazyRef.setFieldClassName(field.getType().getName());
351                             lazyRef.setClassName(original.getClass().getName());
352                             lazyRef.setFieldName(field.getName());
353                             // load id
354                             lazyRef.setId(sessionFactory.getClassMetadata(original.getClass()).getIdentifier(
355                                 original,
356                                 EntityMode.POJO));
357 
358                             // get proxy for lazy
359                             newValue = SerializationInterceptor.getEnhancedObject(oldValue, sessionFactory, lazyRef);
360                         }
361                         else
362                         {
363                             // get default proxy
364                             newValue = SerializationInterceptor.getEnhancedObject(oldValue, sessionFactory);
365                         }
366 
367                         // if there is a variation store it
368                         if (newValue != oldValue)
369                         {
370                             field.set(original, newValue);
371                         }
372                     }
373 
374                 });
375             }
376 
377             return original;
378 
379         }
380         catch (Throwable t)
381         {
382             log.error(t.getMessage(), t);
383             return original;
384         }
385     }
386 
387     /**
388      * Get transformer for collection, map or set injection of proxied objects
389      * @param factory hibernate session factory
390      * @return transformer
391      */
392     private Transformer transformer(SessionFactory factory)
393     {
394         return new Transformer()
395         {
396 
397             public Object transform(Object input)
398             {
399                 return SerializationInterceptor.getEnhancedObject(input, sessionFactory);
400             }
401 
402         };
403     }
404 
405     /**
406      * Sets the hibernateLazyService.
407      * @param hibernateLazyService the hibernateLazyService to set
408      */
409     public static void setHibernateLazyService(HibernateLazyService hibernateLazyService)
410     {
411         SerializationInterceptor.hibernateLazyService.set(hibernateLazyService);
412     }
413 
414     /**
415      * Returns the hibernateLazyService.
416      * @return the hibernateLazyService
417      */
418     public static HibernateLazyService getHibernateLazyService()
419     {
420         return hibernateLazyService.get();
421     }
422 
423     /**
424      * {@inheritDoc}
425      */
426     @Override
427     public int hashCode()
428     {
429         return new HashCodeBuilder(-469032761, 855711273)
430             .appendSuper(super.hashCode())
431             .append(this.original)
432             .toHashCode();
433     }
434 
435 }