cancel
Showing results for 
Search instead for 
Did you mean: 

Desirialization of JPA process variables via REST

kaihuener
Champ in-the-making
Champ in-the-making
Hey – I am trying to deserialize a JPA process variable via the new REST API (amazing work, by the way). I am using Hibernate for JPA and Jersey for deserialization.


import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;

Client client = Client.create();
final HTTPBasicAuthFilter authFilter = new HTTPBasicAuthFilter(username, password);
client.addFilter(authFilter);
WebResource r = client.resource(url);
ClientResponse response =  r.get(ClientResponse.class);
BusinessPartner bp = response.getEntity(BusinessPartner.class);


But when I run the code, Jersey tells me that a "message body" or something is missing for deserialization.


SEVERE: A message body reader for Java class cdq.cdl.bpr.datamodel.BusinessPartner, and Java type class cdq.cdl.bpr.datamodel.BusinessPartner, and MIME media type application/x-java-serialized-object was not found
Jul 01, 2013 1:22:53 PM com.sun.jersey.api.client.ClientResponse getEntity
SEVERE: The registered message body readers compatible with the MIME media type are:
*/* ->
  com.sun.jersey.core.impl.provider.entity.FormProvider
  com.sun.jersey.core.impl.provider.entity.StringProvider
  com.sun.jersey.core.impl.provider.entity.ByteArrayProvider
  com.sun.jersey.core.impl.provider.entity.FileProvider
  com.sun.jersey.core.impl.provider.entity.InputStreamProvider
  com.sun.jersey.core.impl.provider.entity.DataSourceProvider
  com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$General
  com.sun.jersey.core.impl.provider.entity.ReaderProvider
  com.sun.jersey.core.impl.provider.entity.DocumentProvider
  com.sun.jersey.core.impl.provider.entity.SourceProvider$StreamSourceReader
  com.sun.jersey.core.impl.provider.entity.SourceProvider$SAXSourceReader
  com.sun.jersey.core.impl.provider.entity.SourceProvider$DOMSourceReader
  com.sun.jersey.json.impl.provider.entity.JSONJAXBElementProvider$General
  com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$General
  com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$General
  com.sun.jersey.core.impl.provider.entity.XMLRootObjectProvider$General
  com.sun.jersey.core.impl.provider.entity.EntityHolderReader
  com.sun.jersey.json.impl.provider.entity.JSONRootElementProvider$General
  com.sun.jersey.json.impl.provider.entity.JSONListElementProvider$General


Do I have to add something special to my serialized bean? I would expect Jersey (bundle version 1.17) to handle all the deserialization without additional information?

Maybe – and this is why I ask it here – I missed to tell Jersey the way the bean is serialized by Activity, I mean something like

ClientResponse response =  r.type("application/json").get(ClientResponse.class);

I mean the "application/json" part. Do you use some special type hear?

Just to be complete, this is the serialized class that is not properly deserialized:

package cdq.cdl.bpr.datamodel;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;

import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.envers.Audited;

import cdq.cdl.bpr.shared.Constants;

@Audited
@Entity
@Table(name="BusinessPartner")
public class BusinessPartner extends ValidatableEntity implements Serializable {

   private static final long serialVersionUID = -4791687256196212218L;

   private String id;
   private String name;
   private BusinessPartnerType type;
   private BusinessPartner parentBusinessPartner;
   private LegalData legalData;
   private Set<Address> addresses;   
   
   // Metadata attributes
   private String dataSteward;
   private Set<String> subscribers;

   public BusinessPartner() {
      super();
      addresses = new HashSet<Address>();
      subscribers = new HashSet<String>();
      legalData = new LegalData();
   }

   @Id
   public String getId() {
      return this.id;
   }

   public void setId(String id) {
      this.id = id;
   }

   public String getName() {
      return this.name;
   }

   public void setName(String name) {
      this.name = name;
   }

   public BusinessPartner getParentBusinessPartner() {
      return parentBusinessPartner;
   }

   public void setParentBusinessPartner(BusinessPartner parentBusinessPartner) {
      this.parentBusinessPartner = parentBusinessPartner;
   }

   public void setParentCompany(BusinessPartner parentBusinessPartner) {
      this.parentBusinessPartner = parentBusinessPartner;
   }
   
   @ManyToOne
   @Cascade(CascadeType.ALL)
    @JoinColumn(name="businessPartnerType")
   public BusinessPartnerType getType() {
      return type;
   }

   public void setType(BusinessPartnerType type) {
      this.type = type;
   }

   @OneToOne
   @Cascade(CascadeType.ALL)
   public LegalData getLegalData() {
      return legalData;
   }

   public void setLegalData(LegalData legalData) {
      this.legalData = legalData;
   }
   
   @ManyToMany
   @Cascade(CascadeType.ALL)
   @Fetch(FetchMode.JOIN) // Slows down, but required (to be checked …) to load all addresses also after adding a new address
   @JoinTable(name = "BusinessPartnerAddresses", joinColumns = { @JoinColumn(name = "businessPartnerId") }, inverseJoinColumns = { @JoinColumn(name = "addressId") })
   public Set<Address> getAddresses() {
      return addresses;
   }

   public void setAddresses(Set<Address> addresses) {
      this.addresses = addresses;
   }

   public String getDataSteward() {
      return dataSteward;
   }

   public void setDataSteward(String dataSteward) {
      this.dataSteward = dataSteward;
   }

   @Cascade(CascadeType.ALL)
   @ElementCollection
   public Set<String> getSubscribers() {
      return subscribers;
   }

   public void setSubscribers(Set<String> subscribers) {
      this.subscribers = subscribers;
   }
   
   public String toString() {
      String retval = "";
      
      retval += name;
      if (type != null && type.getName().equals(Constants.BusinessPartnerType.LEGAL_ENTITY)) {
         retval += " " + legalData.getLegalForm();
      }
      if (retval.equals("")) {
         return "new business partner";
      }
      return retval;
   }
}


Hope you can help before I have to test all available types …
Thank you in advance and best regards,
Kai
6 REPLIES 6

augustus
Champ in-the-making
Champ in-the-making
There are few things that I needed to do to make it work.
First use this artifact:
<code> 
               <dependency>
   <groupId>com.sun.jersey</groupId>
   <artifactId>jersey-bundle</artifactId>
   <version>1.8</version><!– 1.17.1 –>
  </dependency>
</code>

Second, I'm using jackson lib for this (it worked better for me)
<code>
  <dependency>
   <groupId>org.codehaus.jackson</groupId>
   <artifactId>jackson-core-asl</artifactId>
   <version>1.9.4</version> //or newer
  </dependency>
</code>

Your BusinessPartner must have @XmlRootElement annotation but I'm not sure if you can send this object to activiti as a variable (unless you did something I dont know about Smiley Happy  ).

kaihuener
Champ in-the-making
Champ in-the-making
Hey mojsilo,
First of all: Thank you for your comment – it is great to get some community support.
In the meantime, I tried various things to better understand what actually is provided by the REST API (including several media types, self-written message body writer), without success.
Finally I realized – and of course it is also written in the User Guide – that the variables are just serialized, not in JSON or XML or something, just "raw" serialization.
So, finally, both the following solutions work in my case (by the way, both without any annotation in the serialized bean, maybe I am wrong, but "@XmlRootElement" or something is not required, except for dedicated XML serialization):

<java>
import java.io.InputStream;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.lang.SerializationUtils;

HttpClient client = new HttpClient();
GetMethod get = new GetMethod(uri);
client.executeMethod(get);
InputStream stream = get.getResponseBodyAsStream();
BusinessPartner bp = (BusinessPartner)SerializationUtils.deserialize(stream);
</java>

<java>
import java.io.InputStream;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;

Client client = Client.create();
WebResource r = client .resource(uri); 
InputStream response= r.get(InputStream.class);
BusinessPartner bp = (BusinessPartner)SerializationUtils.deserialize(response);
</java>

But I am not sure, if this is the intended way for process variable deserialization, because I would expect this solution to be heavily implementation dependent (i.e. how java serializes objects). So maybe the Activiti team can clarify the intended way? I also wonder why variables (except binary ones) are not provided as json, because coming from the REST API implementation, I would have expected a json response.

Thank you guys,
best,
Kai

augustus
Champ in-the-making
Champ in-the-making
What do you mean by "not provided as json" ?

frederikherema1
Star Contributor
Star Contributor
See the user guide, to use custom representation of certain objects:

It's possible to support additional variable-types with a custom JSON representation (either simple value or complex/nested JSON object). By extending the initializeVariableConverters() method on org.activiti.rest.api.RestResponseFactory, you can add additional org.activiti.rest.api.engine.variable.RestVariableConverter classes to support converting your POJO's to a format suitable for transerring through REST and converting the REST-value back to your POJO. The actual transformation to JSON is done by Jackson.

So if you add a org.activiti.rest.api.engine.variable.RestVariableConverter for the JPA-type, that returns a Jackson-serializable POJO (which by default, are all of them). Alternatively, you can return a map<String, Object> of key-values where key is the resulting JSON-key and value the -no shit- value Smiley Wink If you want to have sub-json-objects, use another Map as value.

Or stick with the default "serializable" that is being used now, off course…

I was planning on writing a blog on this subject, so any feedback on my "theory" is appreciated Smiley Wink

gregdavisfromnj
Champ on-the-rise
Champ on-the-rise
Using the custom RestVariableConverter approach works pretty well for simple types.  And, being able to send a serializable object directly to Jackson when serializing (a one-liner in the convertVariableValue implementation) is fantastic.

However, I have some pretty deeply nested datatypes I would like deserialize from JSON sent to the REST service.  Deserializing by iterating over all the Maps of Maps of Maps in getVariableValue just feels wrong and is resulting in a lot of converter code.  Jackson is probably already able to deserialize without the helper converter code, if it only know about the object type corresponding to the JSON stream.

Is there a way to instruct the Jackson mapper in the REST service to DE-serialize a JsonNode into a specific type (which could be provided with all sorts of Jackson helper annotations)?  Is there a place in the REST webapp code where I can look to see how to shim that in?

There seems to be something called a CustomVariableType, but it sounds like that is for storing variables in a custom manner into the database, and after the REST service deserialization I just want to use the default database process variable serialization.

Thanks,
gd

jbarrez
Star Contributor
Star Contributor
VariableType is for the engine only, not for REST.

Your remark makes a lot of sense. I was wondering (haven't tried it), but couldn't you simply write the jackson code in a custom implementation of the RestVariableConverter already now? Eg by calling the readValue() method from Jackson?