• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (C) 2010 Google, 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 com.google.inject.persist.jpa;
18 
19 import com.google.common.collect.MapMaker;
20 import com.google.inject.Inject;
21 import com.google.inject.Provider;
22 import com.google.inject.Singleton;
23 import com.google.inject.name.Named;
24 import com.google.inject.persist.finder.Finder;
25 import com.google.inject.persist.finder.FirstResult;
26 import com.google.inject.persist.finder.MaxResults;
27 
28 import org.aopalliance.intercept.MethodInterceptor;
29 import org.aopalliance.intercept.MethodInvocation;
30 
31 import java.lang.annotation.Annotation;
32 import java.lang.reflect.Constructor;
33 import java.lang.reflect.InvocationTargetException;
34 import java.lang.reflect.Method;
35 import java.util.Collection;
36 import java.util.List;
37 import java.util.Map;
38 
39 import javax.persistence.EntityManager;
40 import javax.persistence.Query;
41 
42 /**
43  * TODO(dhanji): Make this work!!
44  *
45  * @author Dhanji R. Prasanna (dhanji@gmail.com)
46  */
47 @Singleton
48 class JpaFinderProxy implements MethodInterceptor {
49   private final Map<Method, FinderDescriptor> finderCache = new MapMaker().weakKeys().makeMap();
50   private final Provider<EntityManager> emProvider;
51 
52   @Inject
JpaFinderProxy(Provider<EntityManager> emProvider)53   public JpaFinderProxy(Provider<EntityManager> emProvider) {
54     this.emProvider = emProvider;
55   }
56 
invoke(MethodInvocation methodInvocation)57   public Object invoke(MethodInvocation methodInvocation) throws Throwable {
58     EntityManager em = emProvider.get();
59 
60     //obtain a cached finder descriptor (or create a new one)
61     JpaFinderProxy.FinderDescriptor finderDescriptor = getFinderDescriptor(methodInvocation);
62 
63     Object result = null;
64 
65     //execute as query (named params or otherwise)
66     Query jpaQuery = finderDescriptor.createQuery(em);
67     if (finderDescriptor.isBindAsRawParameters) {
68       bindQueryRawParameters(jpaQuery, finderDescriptor, methodInvocation.getArguments());
69     } else {
70       bindQueryNamedParameters(jpaQuery, finderDescriptor, methodInvocation.getArguments());
71     }
72 
73     //depending upon return type, decorate or return the result as is
74     if (JpaFinderProxy.ReturnType.PLAIN.equals(finderDescriptor.returnType)) {
75       result = jpaQuery.getSingleResult();
76     } else if (JpaFinderProxy.ReturnType.COLLECTION.equals(finderDescriptor.returnType)) {
77       result = getAsCollection(finderDescriptor, jpaQuery.getResultList());
78     } else if (JpaFinderProxy.ReturnType.ARRAY.equals(finderDescriptor.returnType)) {
79       result = jpaQuery.getResultList().toArray();
80     }
81 
82     return result;
83   }
84 
getAsCollection(JpaFinderProxy.FinderDescriptor finderDescriptor, List results)85   private Object getAsCollection(JpaFinderProxy.FinderDescriptor finderDescriptor,
86       List results) {
87     Collection<?> collection;
88     try {
89       collection = (Collection) finderDescriptor.returnCollectionTypeConstructor.newInstance();
90     } catch (InstantiationException e) {
91       throw new RuntimeException(
92           "Specified collection class of Finder's returnAs could not be instantated: "
93               + finderDescriptor.returnCollectionType, e);
94     } catch (IllegalAccessException e) {
95       throw new RuntimeException(
96           "Specified collection class of Finder's returnAs could not be instantated (do not have access privileges): "
97               + finderDescriptor.returnCollectionType, e);
98     } catch (InvocationTargetException e) {
99       throw new RuntimeException(
100           "Specified collection class of Finder's returnAs could not be instantated (it threw an exception): "
101               + finderDescriptor.returnCollectionType, e);
102     }
103 
104     collection.addAll(results);
105     return collection;
106   }
107 
bindQueryNamedParameters(Query jpaQuery, JpaFinderProxy.FinderDescriptor descriptor, Object[] arguments)108   private void bindQueryNamedParameters(Query jpaQuery,
109       JpaFinderProxy.FinderDescriptor descriptor, Object[] arguments) {
110     for (int i = 0; i < arguments.length; i++) {
111       Object argument = arguments[i];
112       Object annotation = descriptor.parameterAnnotations[i];
113 
114       if (null == annotation)
115       //noinspection UnnecessaryContinue
116       {
117         continue;   //skip param as it's not bindable
118       } else if (annotation instanceof Named) {
119         Named named = (Named) annotation;
120         jpaQuery.setParameter(named.value(), argument);
121       } else if (annotation instanceof javax.inject.Named) {
122         javax.inject.Named named = (javax.inject.Named) annotation;
123         jpaQuery.setParameter(named.value(), argument);
124       } else if (annotation instanceof FirstResult) {
125         jpaQuery.setFirstResult((Integer) argument);
126       } else if (annotation instanceof MaxResults) {
127         jpaQuery.setMaxResults((Integer) argument);
128       }
129     }
130   }
131 
bindQueryRawParameters(Query jpaQuery, JpaFinderProxy.FinderDescriptor descriptor, Object[] arguments)132   private void bindQueryRawParameters(Query jpaQuery,
133       JpaFinderProxy.FinderDescriptor descriptor, Object[] arguments) {
134     for (int i = 0, index = 1; i < arguments.length; i++) {
135       Object argument = arguments[i];
136       Object annotation = descriptor.parameterAnnotations[i];
137 
138       if (null == annotation) {
139         //bind it as a raw param (1-based index, yes I know its different from Hibernate, blargh)
140         jpaQuery.setParameter(index, argument);
141         index++;
142       } else if (annotation instanceof FirstResult) {
143         jpaQuery.setFirstResult((Integer) argument);
144       } else if (annotation instanceof MaxResults) {
145         jpaQuery.setMaxResults((Integer) argument);
146       }
147     }
148   }
149 
getFinderDescriptor(MethodInvocation invocation)150   private JpaFinderProxy.FinderDescriptor getFinderDescriptor(MethodInvocation invocation) {
151     Method method = invocation.getMethod();
152     JpaFinderProxy.FinderDescriptor finderDescriptor = finderCache.get(method);
153     if (null != finderDescriptor) {
154       return finderDescriptor;
155     }
156 
157     //otherwise reflect and cache finder info...
158     finderDescriptor = new JpaFinderProxy.FinderDescriptor();
159 
160     //determine return type
161     finderDescriptor.returnClass = invocation.getMethod().getReturnType();
162     finderDescriptor.returnType = determineReturnType(finderDescriptor.returnClass);
163 
164     //determine finder query characteristics
165     Finder finder = invocation.getMethod().getAnnotation(Finder.class);
166     String query = finder.query();
167     if (!"".equals(query.trim())) {
168       finderDescriptor.setQuery(query);
169     } else {
170       finderDescriptor.setNamedQuery(finder.namedQuery());
171     }
172 
173     //determine parameter annotations
174     Annotation[][] parameterAnnotations = method.getParameterAnnotations();
175     Object[] discoveredAnnotations = new Object[parameterAnnotations.length];
176     for (int i = 0; i < parameterAnnotations.length; i++) {
177       Annotation[] annotations = parameterAnnotations[i];
178       //each annotation per param
179       for (Annotation annotation : annotations) {
180         //discover the named, first or max annotations then break out
181         Class<? extends Annotation> annotationType = annotation.annotationType();
182         if (Named.class.equals(annotationType) || javax.inject.Named.class.equals(annotationType)) {
183           discoveredAnnotations[i] = annotation;
184           finderDescriptor.isBindAsRawParameters = false;
185           break;
186         } else if (FirstResult.class.equals(annotationType)) {
187           discoveredAnnotations[i] = annotation;
188           break;
189         } else if (MaxResults.class.equals(annotationType)) {
190           discoveredAnnotations[i] = annotation;
191           break;
192         }   //leave as null for no binding
193       }
194     }
195 
196     //set the discovered set to our finder cache object
197     finderDescriptor.parameterAnnotations = discoveredAnnotations;
198 
199     //discover the returned collection implementation if this finder returns a collection
200     if (JpaFinderProxy.ReturnType.COLLECTION.equals(finderDescriptor.returnType)
201         && finderDescriptor.returnClass != Collection.class) {
202       finderDescriptor.returnCollectionType = finder.returnAs();
203       try {
204         finderDescriptor.returnCollectionTypeConstructor = finderDescriptor.returnCollectionType
205             .getConstructor();
206         finderDescriptor.returnCollectionTypeConstructor.setAccessible(true);   //UGH!
207       } catch (NoSuchMethodException e) {
208         throw new RuntimeException(
209             "Finder's collection return type specified has no default constructor! returnAs: "
210                 + finderDescriptor.returnCollectionType, e);
211       }
212     }
213 
214     //cache it
215     cacheFinderDescriptor(method, finderDescriptor);
216 
217     return finderDescriptor;
218   }
219 
220   /**
221    * writes to a chm (used to provide copy-on-write but this is bettah!)
222    *
223    * @param method The key
224    * @param finderDescriptor The descriptor to cache
225    */
cacheFinderDescriptor(Method method, FinderDescriptor finderDescriptor)226   private void cacheFinderDescriptor(Method method, FinderDescriptor finderDescriptor) {
227     //write to concurrent map
228     finderCache.put(method, finderDescriptor);
229   }
230 
determineReturnType(Class<?> returnClass)231   private JpaFinderProxy.ReturnType determineReturnType(Class<?> returnClass) {
232     if (Collection.class.isAssignableFrom(returnClass)) {
233       return JpaFinderProxy.ReturnType.COLLECTION;
234     } else if (returnClass.isArray()) {
235       return JpaFinderProxy.ReturnType.ARRAY;
236     }
237 
238     return JpaFinderProxy.ReturnType.PLAIN;
239   }
240 
241   /**
242    * A wrapper data class that caches information about a finder method.
243    */
244   private static class FinderDescriptor {
245     private volatile boolean isKeyedQuery = false;
246     volatile boolean isBindAsRawParameters = true;
247         //should we treat the query as having ? instead of :named params
248     volatile JpaFinderProxy.ReturnType returnType;
249     volatile Class<?> returnClass;
250     volatile Class<? extends Collection> returnCollectionType;
251     volatile Constructor returnCollectionTypeConstructor;
252     volatile Object[] parameterAnnotations;
253         //contract is: null = no bind, @Named = param, @FirstResult/@MaxResults for paging
254 
255     private String query;
256     private String name;
257 
setQuery(String query)258     void setQuery(String query) {
259       this.query = query;
260     }
261 
setNamedQuery(String name)262     void setNamedQuery(String name) {
263       this.name = name;
264       isKeyedQuery = true;
265     }
266 
isKeyedQuery()267     public boolean isKeyedQuery() {
268       return isKeyedQuery;
269     }
270 
createQuery(EntityManager em)271     Query createQuery(EntityManager em) {
272       return isKeyedQuery ? em.createNamedQuery(name) : em.createQuery(query);
273     }
274   }
275 
276   private static enum ReturnType {
277     PLAIN, COLLECTION, ARRAY
278   }
279 }
280