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.

Reply via email to