i'll push soon a first version but if someone could review the code. I created the proxy in EjbHomeProxyhandler...
@David: was not really what you expected i think, is it a problem? - Romain 2011/8/3 Romain Manni-Bucau <[email protected]> > Thanks for this feedback. > > What you describe was my first thought but then i wanted to avoid standard > annotation while this feature is not in the specs but if you think it is ok > it is ok for me too on this point. > > Where i'm not agree (at least for a first step) is to specify a proxy: the > goal is exactly what you said at the end "sometimes the interface is enough" > and it means too "sometimes implementation - as simple it can be - is too > complex" so i don't want to add something which can be a bit complicated > (proxy doesn't seem so easy to expose them IMHO). We could replce it by a > convention: instead of a proxy the user give a class and the matching is > done from methods name (_ could replace a star for regex). But in the spirit > of give user simple thing i prefer to avoid any handler logic here which > could conflict with interceptors for classical cases. > > I'll have a try to implement a first version and if it works i'll remove > @Repository. > > > - Romain > > 2011/8/2 David Blevins <[email protected]> > >> A home interface impl is essentially a stateless session bean. We could >> do the same. >> >> We just need to tag the interface as a session bean in the >> AnnotationDeployer and add it to the app. Then we can support transactions, >> security, interceptors, decorators and more. It could even be a web service >> or a jax-rs service. We could allow people to tag their @Repository bean as >> any kind of session bean so @Stateful with EXTENDED persistence context >> would even work. The interface becomes a business local interface and people >> can reference it via @EJB. The persistence context reference could be added >> to the bean meta-data as a plain <persistence-context> reference and >> injected with a setter (just need to add another interface to supply the >> setter method). >> >> We could do something tricky like use the Repository.class itself as the >> bean class -- or some other well known class. When we see it we create our >> AnnotationFinder from the interface instead of the bean class. We normally >> check that people aren't using annotations in interfaces, but in this case >> we would just skip checking @Repository beans and allow it. >> >> Then all the data is passed to the assembler as usual. Very early in the >> createApplication() method we would generate the proxy class for any >> @Repository bean and set that class as the bean class. >> >> That just leaves "how to construct the proxy" as the last unsolved issue. >> For that we could do something simple. This is cool... we could establish >> the convention that if you use an implementation of >> java.lang.reflect.InvocationHandler as your bean class, then we construct it >> with full dependency injection, create the proxy for you and pass it back >> from BeanContext.newInstance(). So I guess we wouldn't need a second >> interface for the EntityManager setter, we just put it right in the >> QueryProxy class. >> >> And really at this point, we actually don't need "Repository" specific >> logic. We could enable this processing for any interface that wishes to >> supply a handler and be an bean. >> >> @Stateless >> @PersistenceContext(unitName = "fooUnit", name = "foo") >> @Proxy(handler = org.apache.openejb.util.QueryProxy.class) >> public interface Foo { >> MyEntity findByXXX(String xxx); >> >> MyEntity findById(long i); >> >> List<MyEntity> findByBar(int bar); >> // ... >> } >> >> We could still offer a @Repository implementation, but as a >> meta-annotation. So when we scan for all @Proxy beans we would pick it up >> automatically with the right implementation. >> >> @Proxy(handler = org.apache.openejb.util.QueryProxy.class) >> @Metatype >> @Target({ElementType.TYPE}) >> @Retention(RetentionPolicy.RUNTIME) >> public static @interface Repository { >> } >> >> Now we or anyone else can do a million more things just like this. The >> basic idea is -- sometimes the interface is enough! >> >> >> Thoughts? >> >> >> -David >> >> >> On Aug 2, 2011, at 11:15 AM, Romain Manni-Bucau wrote: >> >> > Today it is a simple implementation managing only finders but if someone >> > give me some clues to manage correctly transaction i would like to add >> > persistence methods. >> > >> > - Romain >> > >> > 2011/8/2 David Blevins <[email protected]> >> > >> >> 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 >> >>>>>> >> >>>>>> >> >>>>>> >> >>>>>> >> >>>>>> >> >>>> >> >>>> >> >> >> >> >> >> >
