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