Revision: 1228
Author: dhanji
Date: Sun Sep 12 10:04:50 2010
Log: Dynamic finders fixed and test added. Probably want a few more tests
and early-error checking to be ideal, but otherwise working as advertised.
=)
http://code.google.com/p/google-guice/source/detail?r=1228
Added:
/trunk/extensions/persist/test/com/google/inject/persist/jpa/DynamicFinderTest.java
Modified:
/trunk/extensions/persist/src/com/google/inject/persist/finder/DynamicFinder.java
/trunk/extensions/persist/src/com/google/inject/persist/finder/Finder.java
/trunk/extensions/persist/src/com/google/inject/persist/jpa/JpaFinderProxy.java
/trunk/extensions/persist/src/com/google/inject/persist/jpa/JpaPersistModule.java
/trunk/extensions/persist/test/com/google/inject/persist/jpa/JpaTestEntity.java
=======================================
--- /dev/null
+++
/trunk/extensions/persist/test/com/google/inject/persist/jpa/DynamicFinderTest.java
Sun Sep 12 10:04:50 2010
@@ -0,0 +1,105 @@
+/**
+ * Copyright (C) 2010 Google, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.inject.persist.jpa;
+
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Provider;
+import com.google.inject.persist.PersistService;
+import com.google.inject.persist.Transactional;
+import com.google.inject.persist.finder.Finder;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+import javax.persistence.EntityManager;
+import junit.framework.TestCase;
+
+/**
+ * A test around providing sessions (starting, closing etc.)
+ *
+ * @author Dhanji R. Prasanna ([email protected])
+ */
+public class DynamicFinderTest extends TestCase {
+ private Injector injector;
+
+ public void setUp() {
+ injector = Guice.createInjector(new
JpaPersistModule("testUnit").addFinder(JpaFinder.class));
+
+ //startup persistence
+ injector.getInstance(PersistService.class).start();
+ }
+
+ public final void tearDown() {
+ injector.getInstance(PersistService.class).stop();
+ }
+
+ public void testDynamicFinderListAll() {
+ //obtain em
+ JpaDao dao = injector.getInstance(JpaDao.class);
+
+ //obtain same em again (bound to txn)
+ JpaTestEntity te = new JpaTestEntity();
+ te.setText("HIAjsOKAOSD" + new Date() + UUID.randomUUID().toString());
+
+ dao.persist(te);
+
+ //im not sure this hack works...
+ assertFalse("Duplicate entity managers crossing-scope",
+ dao.lastEm.equals(injector.getInstance(EntityManager.class)));
+
+ List<JpaTestEntity> list =
injector.getInstance(JpaFinder.class).listAll();
+ assertNotNull(list);
+ assertFalse(list.isEmpty());
+ assertEquals(1, list.size());
+ assertEquals(te, list.get(0));
+ }
+
+ public static interface JpaFinder {
+ @Finder(query = "from JpaTestEntity", returnAs = ArrayList.class)
+ public List<JpaTestEntity> listAll();
+ }
+
+ public static class JpaDao {
+ private final Provider<EntityManager> em;
+ EntityManager lastEm;
+
+ @Inject
+ public JpaDao(Provider<EntityManager> em) {
+ this.em = em;
+ }
+
+ @Transactional
+ public <T> void persist(T t) {
+ lastEm = em.get();
+ assertTrue("em is not open!", lastEm.isOpen());
+ assertTrue("no active txn!", lastEm.getTransaction().isActive());
+ lastEm.persist(t);
+
+ assertTrue("Persisting object failed", lastEm.contains(t));
+ }
+
+ @Transactional
+ public <T> boolean contains(T t) {
+ if (null == lastEm) {
+ lastEm = em.get();
+ }
+ return lastEm.contains(t);
+ }
+ }
+}
=======================================
---
/trunk/extensions/persist/src/com/google/inject/persist/finder/DynamicFinder.java
Sun Sep 12 09:05:06 2010
+++
/trunk/extensions/persist/src/com/google/inject/persist/finder/DynamicFinder.java
Sun Sep 12 10:04:50 2010
@@ -19,19 +19,29 @@
import java.lang.reflect.Method;
/**
- * Utility that helps you discover metadata about dynamic finder methods.
+ * Utility that helps you introspect dynamic finder methods.
*
* @author [email protected] (Dhanji R. Prasanna)
*/
public final class DynamicFinder {
+ private final Method method;
+ private final Finder finder;
+
+ public DynamicFinder(Method method) {
+ this.method = method;
+ this.finder = method.getAnnotation(Finder.class);
+ }
/**
- * Tests if {...@code method} is a dynamic finder method.
+ * Returns some metadata if the method is annotated {...@code @Finder} or
null.
*
* @param method a method you want to test as a dynamic finder
- * @return {...@code true} if the method is annotated {...@code @Finder}
*/
- public static boolean isFinder(Method method) {
- return method.isAnnotationPresent(Finder.class);
+ public static DynamicFinder from(Method method) {
+ return method.isAnnotationPresent(Finder.class) ? new
DynamicFinder(method) : null;
+ }
+
+ public Finder metadata() {
+ return finder;
}
}
=======================================
---
/trunk/extensions/persist/src/com/google/inject/persist/finder/Finder.java
Sun Sep 12 09:05:06 2010
+++
/trunk/extensions/persist/src/com/google/inject/persist/finder/Finder.java
Sun Sep 12 10:04:50 2010
@@ -20,7 +20,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import java.util.ArrayList;
import java.util.Collection;
/**
@@ -29,7 +28,8 @@
*
* @author Dhanji R. Prasanna ([email protected])
*/
-...@target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME)
+...@target(ElementType.METHOD)
+...@retention(RetentionPolicy.RUNTIME)
public @interface Finder {
/**
* Returns the configured named query's name. Specify a named query's
name
@@ -47,5 +47,5 @@
* Use this clause to specify a collection impl to autobox result lists
into. The impl must
* have a default no-arg constructor and be a subclass of {...@code
java.util.Collection}.
*/
- Class<? extends Collection> returnAs() default ArrayList.class;
-}
+ Class<? extends Collection> returnAs() default Collection.class;
+}
=======================================
---
/trunk/extensions/persist/src/com/google/inject/persist/jpa/JpaFinderProxy.java
Sun Sep 12 09:05:06 2010
+++
/trunk/extensions/persist/src/com/google/inject/persist/jpa/JpaFinderProxy.java
Sun Sep 12 10:04:50 2010
@@ -16,7 +16,10 @@
package com.google.inject.persist.jpa;
+import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.google.inject.internal.util.MapMaker;
import com.google.inject.name.Named;
import com.google.inject.persist.finder.Finder;
import com.google.inject.persist.finder.FirstResult;
@@ -28,7 +31,6 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import org.aopalliance.intercept.MethodInterceptor;
@@ -39,11 +41,12 @@
*
* @author Dhanji R. Prasanna ([email protected])
*/
+...@singleton
class JpaFinderProxy implements MethodInterceptor {
- private final Map<Method, FinderDescriptor> finderCache
- = new ConcurrentHashMap<Method, FinderDescriptor>();
+ private final Map<Method, FinderDescriptor> finderCache = new
MapMaker().weakKeys().makeMap();
private final Provider<EntityManager> emProvider;
+ @Inject
public JpaFinderProxy(Provider<EntityManager> emProvider) {
this.emProvider = emProvider;
}
@@ -99,7 +102,7 @@
return collection;
}
- private void bindQueryNamedParameters(Query hibernateQuery,
+ private void bindQueryNamedParameters(Query jpaQuery,
JpaFinderProxy.FinderDescriptor descriptor, Object[] arguments) {
for (int i = 0; i < arguments.length; i++) {
Object argument = arguments[i];
@@ -111,11 +114,11 @@
continue; //skip param as it's not bindable
} else if (annotation instanceof Named) {
Named named = (Named) annotation;
- hibernateQuery.setParameter(named.value(), argument);
+ jpaQuery.setParameter(named.value(), argument);
} else if (annotation instanceof FirstResult) {
- hibernateQuery.setFirstResult((Integer) argument);
+ jpaQuery.setFirstResult((Integer) argument);
} else if (annotation instanceof MaxResults) {
- hibernateQuery.setMaxResults((Integer) argument);
+ jpaQuery.setMaxResults((Integer) argument);
}
}
}
@@ -188,7 +191,9 @@
finderDescriptor.parameterAnnotations = discoveredAnnotations;
//discover the returned collection implementation if this finder
returns a collection
- if
(JpaFinderProxy.ReturnType.COLLECTION.equals(finderDescriptor.returnType)) {
+ if
(JpaFinderProxy.ReturnType.COLLECTION.equals(finderDescriptor.returnType)
+ && finderDescriptor.returnClass != Collection.class) {
+ System.out.println("-----" + finderDescriptor.returnClass);
finderDescriptor.returnCollectionType = finder.returnAs();
try {
finderDescriptor.returnCollectionTypeConstructor =
finderDescriptor.returnCollectionType
=======================================
---
/trunk/extensions/persist/src/com/google/inject/persist/jpa/JpaPersistModule.java
Sun Sep 12 09:05:06 2010
+++
/trunk/extensions/persist/src/com/google/inject/persist/jpa/JpaPersistModule.java
Sun Sep 12 10:04:50 2010
@@ -16,16 +16,26 @@
package com.google.inject.persist.jpa;
+import com.google.inject.Inject;
import com.google.inject.Singleton;
+import com.google.inject.internal.util.Lists;
import com.google.inject.internal.util.Preconditions;
import com.google.inject.persist.PersistModule;
import com.google.inject.persist.PersistService;
import com.google.inject.persist.UnitOfWork;
+import com.google.inject.persist.finder.DynamicFinder;
+import com.google.inject.persist.finder.Finder;
import com.google.inject.util.Providers;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.List;
import java.util.Properties;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
/**
* JPA provider for guice persist.
@@ -64,6 +74,11 @@
transactionInterceptor = new JpaLocalTxnInterceptor();
requestInjection(transactionInterceptor);
+
+ // Bind dynamic finders.
+ for (Class<?> finder : dynamicFinders) {
+ bindFinder(finder);
+ }
}
@Override protected MethodInterceptor getTransactionInterceptor() {
@@ -80,4 +95,85 @@
this.properties = properties;
return this;
}
-}
+
+ private final List<Class<?>> dynamicFinders = Lists.newArrayList();
+
+ /**
+ * Adds an interface to this module to use as a dynamic finder.
+ *
+ * @param iface Any interface type whose methods are all dynamic finders.
+ */
+ public <T> JpaPersistModule addFinder(Class<T> iface) {
+ dynamicFinders.add(iface);
+ return this;
+ }
+
+ private <T> void bindFinder(Class<T> iface) {
+ if (!isDynamicFinderValid(iface)) {
+ return;
+ }
+
+ InvocationHandler finderInvoker = new InvocationHandler() {
+ @Inject JpaFinderProxy finderProxy;
+
+ public Object invoke(final Object thisObject, final Method method,
final Object[] args)
+ throws Throwable {
+
+ // Don't intercept non-finder methods like equals and hashcode.
+ if (!method.isAnnotationPresent(Finder.class)) {
+ // NOTE(dhanji): This is not ideal, we are using the invocation
handler's equals
+ // and hashcode as a proxy (!) for the proxy's equals and
hashcode.
+ return method.invoke(this, args);
+ }
+
+ return finderProxy.invoke(new MethodInvocation() {
+ public Method getMethod() {
+ return method;
+ }
+
+ public Object[] getArguments() {
+ return null == args ? new Object[0] : args;
+ }
+
+ public Object proceed() throws Throwable {
+ return method.invoke(thisObject, args);
+ }
+
+ public Object getThis() {
+ throw new UnsupportedOperationException("Bottomless proxies
don't expose a this.");
+ }
+
+ public AccessibleObject getStaticPart() {
+ throw new UnsupportedOperationException();
+ }
+ });
+ }
+ };
+ requestInjection(finderInvoker);
+
+ @SuppressWarnings("unchecked") // Proxy must produce instance of type
given.
+ T proxy = (T) Proxy
+ .newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[] { iface },
+ finderInvoker);
+
+ bind(iface).toInstance(proxy);
+ }
+
+ private boolean isDynamicFinderValid(Class<?> iface) {
+ boolean valid = true;
+ if (!iface.isInterface()) {
+ addError(iface + " is not an interface. Dynamic Finders must be
interfaces.");
+ valid = false;
+ }
+
+ for (Method method : iface.getMethods()) {
+ DynamicFinder finder = DynamicFinder.from(method);
+ if (null == finder) {
+ addError("Dynamic Finder methods must be annotated with @Finder,
but " + iface
+ + "." + method.getName() + " was not");
+ valid = false;
+ }
+ }
+ return valid;
+ }
+}
=======================================
---
/trunk/extensions/persist/test/com/google/inject/persist/jpa/JpaTestEntity.java
Tue May 25 15:48:47 2010
+++
/trunk/extensions/persist/test/com/google/inject/persist/jpa/JpaTestEntity.java
Sun Sep 12 10:04:50 2010
@@ -16,45 +16,58 @@
package com.google.inject.persist.jpa;
-import com.google.inject.persist.finder.Finder;
-import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
-import javax.persistence.NamedQuery;
-
-/**
- * Created with IntelliJ IDEA.
- * On: 2/06/2007
- *
- * @author Dhanji R. Prasanna ([email protected])
- * @since 1.0
- */
+
+/** @author Dhanji R. Prasanna ([email protected]) */
@Entity
-...@namedquery(name = JpaTestEntity.LIST_ALL_QUERY, query = "from
JpaTestEntity")
public class JpaTestEntity {
- private Long id;
- private String text;
- public static final String LIST_ALL_QUERY = "JpaTestEntity.listAll";
-
- @Id
- @GeneratedValue
- public Long getId() {
- return id;
- }
-
- public void setId(Long id) {
- this.id = id;
- }
-
- public String getText() {
- return text;
+ private Long id;
+ private String text;
+
+ @Id @GeneratedValue
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
}
- public void setText(String text) {
- this.text = text;
+ JpaTestEntity that = (JpaTestEntity) o;
+
+ if (id != null ? !id.equals(that.id) : that.id != null) {
+ return false;
+ }
+ if (text != null ? !text.equals(that.text) : that.text != null) {
+ return false;
}
- @Finder(query = "from JpaTestEntity")
- public List<JpaTestEntity> listAll() { return null; }
-}
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = id != null ? id.hashCode() : 0;
+ result = 31 * result + (text != null ? text.hashCode() : 0);
+ return result;
+ }
+}
--
You received this message because you are subscribed to the Google Groups
"google-guice-dev" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/google-guice-dev?hl=en.