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