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.base.Preconditions; 20 import com.google.common.collect.Lists; 21 import com.google.inject.Inject; 22 import com.google.inject.Provides; 23 import com.google.inject.Singleton; 24 import com.google.inject.persist.PersistModule; 25 import com.google.inject.persist.PersistService; 26 import com.google.inject.persist.UnitOfWork; 27 import com.google.inject.persist.finder.DynamicFinder; 28 import com.google.inject.persist.finder.Finder; 29 import java.lang.reflect.AccessibleObject; 30 import java.lang.reflect.InvocationHandler; 31 import java.lang.reflect.Method; 32 import java.lang.reflect.Proxy; 33 import java.util.List; 34 import java.util.Map; 35 import javax.persistence.EntityManager; 36 import javax.persistence.EntityManagerFactory; 37 import org.aopalliance.intercept.MethodInterceptor; 38 import org.aopalliance.intercept.MethodInvocation; 39 40 /** 41 * JPA provider for guice persist. 42 * 43 * @author dhanji@gmail.com (Dhanji R. Prasanna) 44 */ 45 public final class JpaPersistModule extends PersistModule { 46 private final String jpaUnit; 47 JpaPersistModule(String jpaUnit)48 public JpaPersistModule(String jpaUnit) { 49 Preconditions.checkArgument( 50 null != jpaUnit && jpaUnit.length() > 0, "JPA unit name must be a non-empty string."); 51 this.jpaUnit = jpaUnit; 52 } 53 54 private Map<?, ?> properties; 55 private MethodInterceptor transactionInterceptor; 56 57 @Override configurePersistence()58 protected void configurePersistence() { 59 bindConstant().annotatedWith(Jpa.class).to(jpaUnit); 60 61 bind(JpaPersistService.class).in(Singleton.class); 62 63 bind(PersistService.class).to(JpaPersistService.class); 64 bind(UnitOfWork.class).to(JpaPersistService.class); 65 bind(EntityManager.class).toProvider(JpaPersistService.class); 66 bind(EntityManagerFactory.class) 67 .toProvider(JpaPersistService.EntityManagerFactoryProvider.class); 68 69 transactionInterceptor = new JpaLocalTxnInterceptor(); 70 requestInjection(transactionInterceptor); 71 72 // Bind dynamic finders. 73 for (Class<?> finder : dynamicFinders) { 74 bindFinder(finder); 75 } 76 } 77 78 @Override getTransactionInterceptor()79 protected MethodInterceptor getTransactionInterceptor() { 80 return transactionInterceptor; 81 } 82 83 @Provides 84 @Jpa provideProperties()85 Map<?, ?> provideProperties() { 86 return properties; 87 } 88 89 /** 90 * Configures the JPA persistence provider with a set of properties. 91 * 92 * @param properties A set of name value pairs that configure a JPA persistence provider as per 93 * the specification. 94 * @since 4.0 (since 3.0 with a parameter type of {@code java.util.Properties}) 95 */ properties(Map<?, ?> properties)96 public JpaPersistModule properties(Map<?, ?> properties) { 97 this.properties = properties; 98 return this; 99 } 100 101 private final List<Class<?>> dynamicFinders = Lists.newArrayList(); 102 103 /** 104 * Adds an interface to this module to use as a dynamic finder. 105 * 106 * @param iface Any interface type whose methods are all dynamic finders. 107 */ addFinder(Class<T> iface)108 public <T> JpaPersistModule addFinder(Class<T> iface) { 109 dynamicFinders.add(iface); 110 return this; 111 } 112 bindFinder(Class<T> iface)113 private <T> void bindFinder(Class<T> iface) { 114 if (!isDynamicFinderValid(iface)) { 115 return; 116 } 117 118 InvocationHandler finderInvoker = 119 new InvocationHandler() { 120 @Inject JpaFinderProxy finderProxy; 121 122 @Override 123 public Object invoke(final Object thisObject, final Method method, final Object[] args) 124 throws Throwable { 125 126 // Don't intercept non-finder methods like equals and hashcode. 127 if (!method.isAnnotationPresent(Finder.class)) { 128 // NOTE(dhanji): This is not ideal, we are using the invocation handler's equals 129 // and hashcode as a proxy (!) for the proxy's equals and hashcode. 130 return method.invoke(this, args); 131 } 132 133 return finderProxy.invoke( 134 new MethodInvocation() { 135 @Override 136 public Method getMethod() { 137 return method; 138 } 139 140 @Override 141 public Object[] getArguments() { 142 return null == args ? new Object[0] : args; 143 } 144 145 @Override 146 public Object proceed() throws Throwable { 147 return method.invoke(thisObject, args); 148 } 149 150 @Override 151 public Object getThis() { 152 throw new UnsupportedOperationException( 153 "Bottomless proxies don't expose a this."); 154 } 155 156 @Override 157 public AccessibleObject getStaticPart() { 158 throw new UnsupportedOperationException(); 159 } 160 }); 161 } 162 }; 163 requestInjection(finderInvoker); 164 165 @SuppressWarnings("unchecked") // Proxy must produce instance of type given. 166 T proxy = 167 (T) 168 Proxy.newProxyInstance( 169 Thread.currentThread().getContextClassLoader(), 170 new Class<?>[] {iface}, 171 finderInvoker); 172 173 bind(iface).toInstance(proxy); 174 } 175 isDynamicFinderValid(Class<?> iface)176 private boolean isDynamicFinderValid(Class<?> iface) { 177 boolean valid = true; 178 if (!iface.isInterface()) { 179 addError(iface + " is not an interface. Dynamic Finders must be interfaces."); 180 valid = false; 181 } 182 183 for (Method method : iface.getMethods()) { 184 DynamicFinder finder = DynamicFinder.from(method); 185 if (null == finder) { 186 addError( 187 "Dynamic Finder methods must be annotated with @Finder, but " 188 + iface 189 + "." 190 + method.getName() 191 + " was not"); 192 valid = false; 193 } 194 } 195 return valid; 196 } 197 } 198