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