• 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.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@gmail.com (Dhanji R. Prasanna)
49  */
50 public final class JpaPersistModule extends PersistModule {
51   private final String jpaUnit;
52 
JpaPersistModule(String jpaUnit)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 
configurePersistence()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 
getTransactionInterceptor()82   @Override protected MethodInterceptor getTransactionInterceptor() {
83     return transactionInterceptor;
84   }
85 
provideProperties()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    */
properties(Map<?,?> properties)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    */
addFinder(Class<T> iface)109   public <T> JpaPersistModule addFinder(Class<T> iface) {
110     dynamicFinders.add(iface);
111     return this;
112   }
113 
bindFinder(Class<T> iface)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 
isDynamicFinderValid(Class<?> iface)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