[prev in list] [next in list] [prev in thread] [next in thread] 

List:       openejb-development
Subject:    Re: openejb & dynamic finders/dao?
From:       Romain Manni-Bucau <rmannibucau () gmail ! com>
Date:       2011-08-03 8:58:07
Message-ID: CACLE=7PsZg8WK-fWGuM=QwxV374SodfGjCWc8Z46oTxNOYY8zg () mail ! gmail ! com
[Download RAW message or body]


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 <rmannibucau@gmail.com>

> 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 <david.blevins@gmail.com>
>
>> 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 <david.blevins@gmail.com>
>> >
>> >> 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 <rmannibucau@gmail.com>
>> >>>
>> >>>> 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" <rmannibucau@gmail.com>
>> 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 <rmannibucau@gmail.com>
>> >>>>>
>> >>>>>> 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
>> >>>>>>
>> >>>>>>
>> >>>>>>
>> >>>>>>
>> >>>>>>
>> >>>>
>> >>>>
>> >>
>> >>
>>
>>
>


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic