Tuesday, October 22, 2013

Re: RequestFactory, ValueProxy, EntityProxy and JSR303 - is it a bug?

OK Thomas, I will go for Objectify.

The only problem is that I cannot find a really good tutorial for using it with Requestfactory.

http://code.google.com/p/objectify-appengine/wiki/ObjectifyWithGWT
For example, their official GWT-Tutorial is totally confusing (see link above), they state: "Make sure your entity classes are part of your GWT module (typically in a .client package)." ...... Now I get totally confused! Is this really true?????


I have played today wit it a bit with Objectify, and I have managed it to create an simple working example.
Can you please tell me if this is the right way of working with the Objectify 4 and the RequestFactory? The code works but I am somehow not really sure if I am on the right track. Here is my code:
Btw: I have all my entities on server side.

@Entity
public class EntityOne
{
    @Id
    private Long id;
    private Integer version = 0;
    private String name;   
    private Key<EntityTwo> entityTwoKey;    //This is the Key for the EntityTwo (Using this you will know how to lad it)
    @Ignore
    private EntityTwo entityTwo; //Transient field (not saved in the Datastore) it is a real representation of the entityt
   
    @OnSave
    void onPersist() {
        version++; // Increase the Version
    }

    // ========= HANDLER METHODS ==========//
    public static EntityOne findEntityOne(Long id) {
        return ofy().load().type(EntityOne.class).id(id).now();
    }

    public void persist() {
        ofy().save().entity(this).now();
    }
   
....
    public Key<EntityTwo> getEntityTwoKey() {
        return entityTwoKey;
    }
    public void setEntityTwoKey(Key<EntityTwo> entityTwoKey) {
        this.entityTwoKey = entityTwoKey;
    }

    public EntityTwo getEntityTwo() {
        //Load entity using its Key
        this.entityTwo = ofy().load().key(getEntityTwoKey()).now();
        return this.entityTwo;
    }
    public void setEntityTwo(EntityTwo entityTwo) {
        //Step1: Set the key (This will make sure it be saved into the DB)
        this.entityTwoKey = Key.create(EntityTwo.class, entityTwo.getId());
        //Step2: Set entity
        this.entityTwo = entityTwo;
    }
}


@Entity
public class EntityTwo
{
    @Id
    private Long id;
    private Integer version = 0;

    private String name;
   
   @OnSave
    void onPersist() {
        version++; // Increase the Version
    }
   
    // ========= HANDLER METHODS ==========//
    public static EntityTwo findEntityTwo(Long id) {
        return ofy().load().type(EntityTwo.class).id(id).now();
    }
   
    public EntityTwo persist() {
        ofy().save().entity(this).now();
        return this;
    }
... Get/set
}

@ProxyFor(EntityOne.class)
public interface EntityOneProxy extends EntityProxy {

    Long getId();
    Integer getVersion();
    void setVersion(Integer version);
    String getName();
    void setName(String name);

    EntityTwoProxy getEntityTwo();
    void setEntityTwo(EntityTwoProxy entityTwo);
}

@ProxyFor(EntityTwo.class)
public interface EntityTwoProxy extends EntityProxy {

    Long getId();
    Integer getVersion();
    void setVersion(Integer version);
    String getName();
    void setName(String name);
}

... And registering to my RequestFactory interface ... etc.

Is this code OK??


If I understood correctly, I do not need to care about OSIV etc.
All I need to do is to register ObjectifyFilter. Right?


Thank you in advance for your help:

Nermin



On Tuesday, October 22, 2013 3:12:59 PM UTC+2, Thomas Broyer wrote:


On Tuesday, October 22, 2013 2:03:19 PM UTC+2, Nermin wrote:


On Tuesday, October 22, 2013 11:40:48 AM UTC+2, Thomas Broyer wrote:


On Monday, October 21, 2013 11:11:07 PM UTC+2, Nermin wrote:
Dear Thomas,

thank you for your Post. I have checked my OSIV konfiguration and I have it the right way (transaction per service method).

Apparently not (see below)


OK, let me show you my OSIV code:

=========== THREAD LOCAL EM
public class ThreadLocalEntityManager {
    private static ThreadLocal<EntityManager> holder = new ThreadLocal<EntityManager>();
   
    private ThreadLocalEntityManager() {
    }

    public static EntityManager get() {
        return holder.get();
    }

    public static void set(EntityManager em) {
        holder.set(em);
    }
}


=========== FILTER
public class AppHandlerFilter  implements Filter {
....
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
      
        //1. Create new EntityManager for this call
        EntityManager em = factory.createEntityManager();
        ThreadLocalEntityManager.set(em);    //== Create EM

        try {
            chain.doFilter(request, response);
          
        } catch (Exception e) { //Any other Exception
            if(em.getTransaction().isActive()) {
                em.getTransaction().rollback();
            }
            logger.severe(e.getMessage());
            e.printStackTrace();
        } finally {
            if(em.isOpen()) {
                em.close();                    //== Close EM
            }
        }
    }
}


======== Entity:

@Entity
public class TestEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @org.datanucleus.api.jpa.annotations.Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
    private String id;
    @Version
    private Long version;

    @NotNull
    private String myName;
    @Pattern(regexp = "[^@\\s]+@[^@\\s]+\\.[A-Za-z]{2,4}", message="Invalid e-mail address")
    @NotNull
    private String myEmailAddress;
   
    public static TestEntity findTestEntity(String id) {
        if (id == null) {
            return null;
        }
        TestEntity te = null;
        EntityManager em = ThreadLocalEntityManager.get();
        em.getTransaction().begin();
        te = em.find(TestEntity.class, id);
        em.getTransaction().commit();
        return te;
    }

    public ProcessResponse persist() {
        EntityManager em = ThreadLocalEntityManager.get();
        em.getTransaction().begin();
        em.persist(this);
        em.refresh(this);
        em.getTransaction().commit();
        return new ProcessResponse(this.getId(), true);
    }
   
    public ProcessResponse update(){
        EntityManager em = ThreadLocalEntityManager.get();
        em.getTransaction().begin();
        em.merge(this);
        em.getTransaction().commit();
        return new ProcessResponse(true);
    }
   
...
}


==== persistence.xml

    <persistence-unit name="emajstor_persistence" transaction-type="RESOURCE_LOCAL">
        <provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider>
       
        <class>com.emajstor.server.persistence.TestEntity</class>

        <exclude-unlisted-classes>true</exclude-unlisted-classes>
        <properties>
            <property name="datanucleus.NontransactionalRead" value="true"/>
            <property name="datanucleus.NontransactionalWrite" value="true"/>
            <property name="datanucleus.ConnectionURL" value="appengine"/>
            <property name="datanucleus.appengine.datastoreEnableXGTransactions" value="true"/>
                       
            <property name="javax.persistence.validation.group.pre-persist" value="javax.validation.groups.Default, com.emajstor.server.persistence.validator.PrePersitValidationGroup" />
            <property name="javax.persistence.validation.group.pre-update" value="javax.validation.groups.Default" />
            <property name="javax.persistence.validation.group.pre-remove" value="" />
           
        </properties>
    </persistence-unit>

 
The debuggung in ReflectiveServiceLayer#validate showed sofar an appropriate behaviour.

So it correctly returns ConstraintViolations in all cases?

YES! Validation works fine!
 
 
The only problem is that the javax.validation.ConstraintViolationException is turned into a java.lang.RuntimeException in case of an entity update. (see error below.)

There shouldn't be any ConstraintViolation*Exception*.

Why shouldn't be there a ConstraintViolation*Exception*?? The entity contains constraint violation because the provided emailAddress is purposely made wrong!

But ReflectiveServiceLayer#validate returns a Set<ConstraintViolation> without the need to throw a ConstratintViolation*Exception*.
 
I cannot figure out why is this happening.
I personally find GWT beeing an easy and very elegant tool for web development. The Appengine-Datastore-handling part is in my point of view a real pain in the ass.

JPA and JDO are a PITA, whether or not they're used on AppEngine.
There are alternatives though (e.g. Objectify).

I agree. Maybe Objectify would be a "better" alternative. Unfortunately I have 12 Entities which I would need to migrate and to test, and there are almost no examples how to use Objectify 4 with the Requestfactory.

AFAICT, there's absolutely nothing special; Objectify takes care of the request-scoped caching (required by RequestFactory, the only reason you need OSIV with JPA/JDO).
 
 

com.google.web.bindery.event.shared.UmbrellaException: Exception caught: Server Error 500 <html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
<title>Error 500 Validation failed for com.emajstor.server.persistence.TestEntity@59778fd8 during pre-update for groups [interface javax.validation.groups.Default] - exceptions are attached</title>
</head>
<body><h2>HTTP ERROR 500</h2>
<p>Problem accessing /unAuthRequestFactory. Reason:
<pre>    Validation failed for com.emajstor.server.persistence.TestEntity@59778fd8 during pre-update for groups [interface javax.validation.groups.Default] - exceptions are attached</pre></p><h3>Caused by:</h3><pre>javax.validation.ConstraintViolationException: Validation failed for com.emajstor.server.persistence.TestEntity@59778fd8 during pre-update for groups [interface javax.validation.groups.Default] - exceptions are attached
    at org.datanucleus.validation.BeanValidatorHandler.validate(BeanValidatorHandler.java:71)
    at org.datanucleus.validation.BeanValidatorHandler.preStore(BeanValidatorHandler.java:86)
    at org.datanucleus.api.jpa.JPACallbackHandler.preStore(JPACallbackHandler.java:102)
    at org.datanucleus.state.JDOStateManager.flush(JDOStateManager.java:3827)
    at org.datanucleus.ObjectManagerImpl.flushInternalWithOrdering(ObjectManagerImpl.java:3888)
    at org.datanucleus.ObjectManagerImpl.flushInternal(ObjectManagerImpl.java:3811)
    at org.datanucleus.ObjectManagerImpl.flush(ObjectManagerImpl.java:3751)
    at org.datanucleus.ObjectManagerImpl.preCommit(ObjectManagerImpl.java:4141)
    at org.datanucleus.ObjectManagerImpl.transactionPreCommit(ObjectManagerImpl.java:428)
    at org.datanucleus.TransactionImpl.internalPreCommit(TransactionImpl.java:398)
    at org.datanucleus.TransactionImpl.commit(TransactionImpl.java:287)
    at org.datanucleus.ObjectManagerImpl.close(ObjectManagerImpl.java:1090)
    at org.datanucleus.api.jpa.JPAEntityManager.close(JPAEntityManager.java:193)
    at com.emajstor.server.AppHandlerFilter.doFilter(AppHandlerFilter.java:91)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)

Your OSIV filter is causing a TransactionImpl.commit, which means there's a transaction spanning the entire request (or maybe you have auto-commit enabled, I don't know how JPA/JDO really work).
That commit triggers validation, and the ConstraintViolationException is shadowing the RequestFactory response (which, if ReflectiveServiceLayer#validate behaved expectedly, would be an onConstraintViolations response)

OK .... I see ... YOU ARE RIGHT.
I have tested it using: <property name="datanucleus.NontransactionalWrite" value="false"/> I have expected this to prevent non-transactional write as you mentioned.
I am getting the following error which lets me think that requestfactory is trying to "auto-comit" data into the DB on its own!!

RequestFactory is storage-agnostic (everything's handled by user code in locators or findXxx static methods for loading, and in services themselves for everything else)
 
Am I missing some configurations on GWT level??

No. There's no configuration on GWT's side. You might want to ask the DataNucleus and/or AppEngine experts how to configure DataNucleus (as I said, I don't know JPA or JDO, they only caused me trouble when I tried to use them, so I can't help you further).
 

SEVERE: Server error caused by:org.datanucleus.exceptions.NucleusUserException
Cant write fields outside of transactions. You may want to set 'NontransactionalWrite=true'.
org.datanucleus.exceptions.NucleusUserException: Cant write fields outside of transactions. You may want to set 'NontransactionalWrite=true'.
    at org.datanucleus.api.jpa.state.PersistentNontransactional.transitionWriteField(PersistentNontransactional.java:170)
    at org.datanucleus.state.AbstractStateManager.transitionWriteField(AbstractStateManager.java:871)
    at org.datanucleus.state.JDOStateManager.preWriteField(JDOStateManager.java:3575)
    at org.datanucleus.state.AbstractStateManager.setStringField(AbstractStateManager.java:1998)
    at com.emajstor.server.persistence.TestEntity.jdoSetmyEmailAddress(TestEntity.java)
    at com.emajstor.server.persistence.TestEntity.setMyEmailAddress(TestEntity.java:88)

RequestFactory is only calling your setter; it's expected this value will not be persisted until you explicitly persist() the entity.
(it might be normal with DataNucleus, but I find it strange that there's mention of JDO here whereas you're using the JPA API)

--
You received this message because you are subscribed to the Google Groups "Google Web Toolkit" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit+unsubscribe@googlegroups.com.
To post to this group, send email to google-web-toolkit@googlegroups.com.
Visit this group at http://groups.google.com/group/google-web-toolkit.
For more options, visit https://groups.google.com/groups/opt_out.

No comments:

Post a Comment