This looks like a cool idea. It's not worth investigating, but this is exactly
how CMP home interfaces look. The developer creates an interface with "create"
and "find" method and the container makes it work.
Home interfaces were actually pretty useful for CMP, just CMP itself was
terrible and home interfaces weren't very useful for session beans. So they
got the axe in EJB 3.0.
I haven't had a chance to check out the implementation code, but the concept
lines up very well.
-David
On Jul 31, 2011, at 1:31 PM, Romain Manni-Bucau wrote:
> I commited,
>
> you can test it with this interface:
>
> @Repository public interface Foo {
> MyEntity findByXXX(String xxx);
> MyEntity findById(long i);
> List<MyEntity> findByBar(int bar);
> // ...
> }
>
> look the logs you'll have the glbal jndi name ;)
>
> - Romain
>
> 2011/7/31 Romain Manni-Bucau <[email protected]>
>
>> Up ;)
>>
>> Nobody thinks it can be useful?
>>
>> /me still waits for some feedbacks before commiting a first version...
>>
>> - Romain
>>
>> Le 29 juil. 2011 22:37, "Romain Manni-Bucau" <[email protected]> a
>> écrit :
>>
>>> Here is a patch:
>>>
>>> http://pastebin.com/4CgcLkmH
>>>
>>> it is a bit dirty at JNDI level but it works if you want to try:
>>>
>>> The repository:
>>>
>>> @Repository(context = @PersistenceContext(unitName = "user"))
>>> public interface UserDAO {
>>> User findById(long id);
>>> Collection<User> findByName(String name);
>>> Collection<User> findAll();
>>> }
>>>
>>> An entity:
>>>
>>> @Entity
>>> public class User {
>>> @Id @GeneratedValue private long id;
>>> private String name;
>>> private int age;
>>>
>>> public long getId() {
>>> return id;
>>> }
>>>
>>> public void setId(long id) {
>>> this.id = id;
>>> }
>>>
>>> public String getName() {
>>> return name;
>>> }
>>>
>>> public void setName(String name) {
>>> this.name = name;
>>> }
>>>
>>> public int getAge() {
>>> return age;
>>> }
>>>
>>> public void setAge(int age) {
>>> this.age = age;
>>> }
>>>
>>> @Override
>>> public String toString() {
>>> return "User{" +
>>> "id=" + id +
>>> ", name='" + name + '\'' +
>>> ", age=" + age +
>>> '}';
>>> }
>>> }
>>>
>>> A stateless to init the database:
>>>
>>> @Stateless
>>> public class InitUserDAO {
>>> @PersistenceContext private EntityManager em;
>>> public void insert(User user) {
>>> em.persist(user);
>>> }
>>> }
>>>
>>> The test:
>>>
>>> package org.superbiz.test;
>>>
>>> import org.junit.AfterClass;
>>> import org.junit.BeforeClass;
>>> import org.junit.Ignore;
>>> import org.junit.Test;
>>> import org.superbiz.dao.InitUserDAO;
>>> import org.superbiz.dao.UserDAO;
>>> import org.superbiz.model.User;
>>>
>>> import javax.ejb.embeddable.EJBContainer;
>>> import javax.naming.Context;
>>> import java.util.Collection;
>>>
>>> /**
>>> * @author rmannibucau
>>> */
>>> public class QueryTest {
>>> private static Context context;
>>> private static UserDAO dao;
>>>
>>> @BeforeClass public static void init() throws Exception {
>>> context = EJBContainer.createEJBContainer().getContext();
>>> InitUserDAO init = (InitUserDAO)
>>> context.lookup("java:global/dynamic-query/InitUserDAO");
>>> dao = (UserDAO) context.lookup("java:global/openejb/Repository/" +
>>> UserDAO.class.getName());
>>> for (int i = 0; i < 10; i++) {
>>> User u = new User();
>>> u.setAge(i * 8);
>>> if (i % 3 == 0) {
>>> u.setName("foo");
>>> } else {
>>> u.setName("bar-" + i);
>>> }
>>> init.insert(u);
>>> }
>>> }
>>>
>>> @AfterClass public static void close() throws Exception {
>>> if (context != null) {
>>> context.close();
>>> }
>>> }
>>>
>>> @Test public void query() {
>>> Collection<User> u1 = dao.findByName("foo");
>>> Collection<User> users = dao.findAll();
>>> User u2 = dao.findById(1);
>>> System.out.println("\n\n" + users + "\n\n" + u1 + "\n\n" + u2);
>>> }
>>> }
>>>
>>> and the persistence.xml:
>>>
>>> <persistence xmlns="http://java.sun.com/xml/ns/persistence"
>>> version="2.0"
>>> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>>> xsi:schemaLocation="
>>> http://java.sun.com/xml/ns/persistence
>>> http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
>>> <persistence-unit name="user" transaction-type="JTA">
>>> <jta-data-source>My Default DataSource</jta-data-source>
>>> <class>org.superbiz.model.User</class>
>>> <properties>
>>> <property name="openjpa.jdbc.SynchronizeMappings"
>>> value="buildSchema(ForeignKeys=true)"/>
>>> </properties>
>>> </persistence-unit>
>>> </persistence>
>>>
>>> Don't hesitate to give me feedback if i should continue or not, what you
>>> think should be done or not.
>>>
>>> - Romain
>>>
>>> 2011/7/29 Romain Manni-Bucau <[email protected]>
>>>
>>>> Hi,
>>>>
>>>> i discover a bit more spring data jpa and saw it was possible to create
>>>> dynamically classes!!
>>>>
>>>> it is exactly the same than the stateless without interface excepted
>> here
>>>> the interface has no implementation.
>>>>
>>>> what do you think if we had it to OpenEJB, it is not standard but it is
>>>> pretty cool.
>>>>
>>>> Here what i think we could do:
>>>>
>>>> 1. create a API to scan interfaces
>>>> 1. we need persistencecontext information so we could use
>>
>>>> @PersistenceContext (i don't like) or add another annotation withthe
>> same
>>>> information (@Repository?)
>>>> 2. we need a name, @Named can probably used or we can add it to
>>>> @Repository
>>>> 2. we scan "@Repository" interface constructing pseudo injection to
>>
>>>> be able to get an entitymanager
>>>> 3. then we deploy it in JNDI
>>>> 1. instead of binding a class we bind a proxy which manage to create
>>
>>>> the query from the name
>>>>
>>>>
>>>> It is probably no clear so here some snippets:
>>>>
>>>> My repository:
>>>>
>>>> @Repository(name = "user")
>>>> public interface UserDAO {
>>>> User findById(long id);
>>>> Collection<User> findByName(String name);
>>>> Collection<User> findAll();
>>>> }
>>>>
>>>>
>>>> One very simple implementation of the invocation handler which manage
>> only
>>>> one condition (this version need an EntityManagerHolder which is here
>> only
>>>> to be able to get the em, it is just an interface with a method
>>>> getEntityManager()...just to do the poc):
>>>>
>>>>
>>>> public class QueryProxy<T> implements InvocationHandler {
>>>> public static final String FIND_PREFIX = "find";
>>>>
>>>> private static final Map<String, List<String>> CONDITIONS = new
>>>> ConcurrentHashMap<String, List<String>>();
>>>>
>>>> private EntityManagerHolder entityManagerHolder;
>>>> private Class<T> type;
>>>>
>>>> public QueryProxy(EntityManagerHolder holder, Class<T> entityClass) {
>>>> entityManagerHolder = holder;
>>>> type = entityClass;
>>>> }
>>>>
>>>> public Object invoke(Object proxy, Method method, Object[] args) throws
>>>> Throwable {
>>>> if (!method.getName().startsWith(FIND_PREFIX)) {
>>>> throw new IllegalArgumentException("finder should start with
>>>> find");
>>>> }
>>>>
>>>> Query query = getQuery(entityManagerHolder.getEntityManager(),
>>>> method.getName(), args);
>>>> if (Collection.class.isAssignableFrom(method.getReturnType())) {
>>>> return query.getResultList();
>>>> }
>>>> return query.getSingleResult();
>>>> }
>>>>
>>>> private Query getQuery(EntityManager entityManager, String methodName,
>>>> Object[] args) {
>>>> final List<String> conditions = parseMethodName(methodName);
>>>> final EntityType<T> et = entityManager.getMetamodel().entity(type);
>>>> final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
>>>>
>>>> CriteriaQuery<Object> query = cb.createQuery();
>>>> Root<T> from = query.from(type);
>>>> query = query.select(from);
>>>>
>>>> int i = 0;
>>>> for (String condition : conditions) {
>>>> SingularAttribute<? super T, ?> attribute =
>>>> et.getSingularAttribute(condition);
>>>> Path<?> path = from.get(attribute);
>>>> Class<?> javaType = attribute.getType().getJavaType();
>>>> if (javaType.equals(String.class)) {
>>>> query = query.where(cb.like((Expression<String>) path,
>>>> (String) args[i++]));
>>>> } else if (Number.class.isAssignableFrom(javaType) ||
>>>> javaType.isPrimitive()) {
>>>> query = query.where(cb.equal(path, args[i++]));
>>>> }
>>>> }
>>>>
>>>> return entityManager.createQuery(query);
>>>> }
>>>>
>>>> private List<String> parseMethodName(final String methodName) {
>>>> List<String> parsed;
>>>> if (CONDITIONS.containsKey(methodName)) {
>>>> parsed = CONDITIONS.get(methodName);
>>>> } else {
>>>> parsed = new ArrayList<String>();
>>>> String toParse = methodName.substring(FIND_PREFIX.length());
>>>> // TODO
>>>> if (toParse.startsWith("By")) {
>>>> toParse = StringUtils.uncapitalize(toParse.substring(2));
>>>> parsed.add(toParse);
>>>> }
>>>> CONDITIONS.put(methodName, parsed);
>>>> }
>>>> return parsed;
>>>> }
>>>> }
>>>>
>>>>
>>>> Finally i can do:
>>>>
>>>> public class QueryTest {
>>>> private static Context context;
>>>> private static UserDAO dao;
>>>>
>>>> @BeforeClass public static void init() throws Exception {
>>>> context = EJBContainer.createEJBContainer().getContext();
>>>> dao = (UserDAO)
>>>> Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
>>>> new Class<?>[] { UserDAO.class },
>>>> new QueryProxy((EntityManagerHolder)
>>>> context.lookup("java:global/dynamic-query/EMH"), User.class));
>>>>
>>>>
>>>> InitUserDAO init = (InitUserDAO)
>>>> context.lookup("java:global/dynamic-query/InitUserDAO");
>>>> for (int i = 0; i < 10; i++) {
>>>> User u = new User();
>>>> u.setAge(i * 8);
>>>> if (i % 3 == 0) {
>>>> u.setName("foo");
>>>> } else {
>>>> u.setName("bar-" + i);
>>>> }
>>>> init.insert(u);
>>>> }
>>>> }
>>>>
>>>> @AfterClass public static void close() throws Exception {
>>>> if (context != null) {
>>>> context.close();
>>>> }
>>>> }
>>>>
>>>> @Test public void query() {
>>>> Collection<User> u1 = dao.findByName("foo");
>>>> Collection<User> users = dao.findAll();
>>>> User u2 = dao.findById(1);
>>>> System.out.println("\n\n" + users + "\n\n" + u1 + "\n\n" + u2);
>>>> }
>>>> }
>>>>
>>>> Any thoughts? should it be added to OpenEJB (after some enhancement of
>>>> course ;))?
>>>>
>>>>
>>>> we could extend it to persist, update etc... methods
>>>>
>>>>
>>>> - Romain
>>>>
>>>>
>>>>
>>>>
>>>>
>>
>>