Hi Dan


Hi,

I'm writing a JAX-RS app and using CXF as the implementation.  I was having
trouble wiring up some of my methods -- specifically, one that was to return
a list of people:

@GET
@Path("/list")
List<Person> getPersons();

Trying to run that, I got NPEs, as List can't be written out as a root
element by default.

There've been quite a few requests recently about supporting explicit 
collections and I'm committed to fixing it,
I might get a chance to do something around it before CXF 2.2.3 gets released in the end of July or so...If not then it will definitely be in 2.3


A lot of the advice that I saw involved adding extra collections, and
changing the method signature to return the JAXBtized collection wrapper.
Since this isn't strictly a REST call, this either means that everybody
using the service call will have to unwrap the collection, or that I will
have to code two methods every time I want to return a collection.

Instead, I'd like to actually fix this in the providers at the core of CXF.

+1

I created a collection object of my own:

   <complexType name="Collection">
       <sequence>
           <any minOccurs="0" maxOccurs="unbounded"></any>
       </sequence>
   </complexType>

... And then overrode the JAXBElementProvider.writeTo method:

package us.XXXXXXXXX.iotool.jaxrs;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;

import org.apache.cxf.jaxrs.provider.JAXBElementProvider;

import us.XXXXXXXXX.iotool.model.Collection;

public class CollectionJAXBElementProvider extends JAXBElementProvider {

@Override
public void writeTo(Object obj, Class<?> cls, Type genericType,
    Annotation[] anns, MediaType m,
     MultivaluedMap<String, Object> headers, OutputStream os)
       throws IOException {
try {
 Object actualObject = checkAdapter(obj, anns, true);
 // if it's a java.util.Collection, wrap it in our collection object
 if (actualObject instanceof java.util.Collection) {
   us.XXXXXXXXX.iotool.model.Collection collection = new Collection();
   java.util.List list  =
     new java.util.ArrayList<Object>((java.util.Collection)actualObject);
   collection.setAnies(list);
   actualObject = collection;
 }

 // now pass to the superclass
 super.writeTo(actualObject, cls, genericType, anns, m, headers, os);
 }  catch (WebApplicationException e) {
  throw e;
 } catch (Exception e) {
  throw new WebApplicationException(e);
 }
}

}


This works fine -- any java.util.Collection will get wrapped in a
<Collection> tag.

interesting solution...


There are a couple of downsides here:

(1) Doing it in this provider means that it doesn't apply to my JSON
provider.
(2) I don't think that I have a great way to consume this on the client side
as anything other than a wrapped collection (if that even works).

Two sets of questions:
(1) Can I get to my desired end -- transparently handling
java.util.Collection objects -- without all this mucking about?  Can I do
this with a XmlJavaTypeAdapter?

I've been thinking about introducing a @CollectionElement annotation and a 
Collections message body writer :

@CollectionElement(name="persons", ns=http://persons)
List<Person> getPersons() {}

MessageBodyWriters get the method annotations passed to it, so :

Produces("application/xml, text/xml, application/*+xml")
public class CollectionsProvider implements MessageBodyWriter<Object> {

  public boolean isWriteable(...) {return true if Collection }
  public void writeTo(Object obj, Class<?> cls, Type genericType,  Annotation[] 
anns, MediaType m,
         MultivaluedMap<String, Object> headers, OutputStream os)  throws 
IOException {

         CollectionElement root = 
AnnotationUtils.getAnnotation(CollectionElement.class, anns);
         os.write(getStartTag(root));

         // get class of the first element or use genericType
         Collection<Object> collection = (Collection<Object>)obj;
         Class<?> elementClass = ...
         MessageBodyWriter bodyWriter = 
Providers.getMessageBodyWriter(elementClass, ....);
         for (Object o : collection) {
              bodyWriter.writeTo(o, ..., os);
         }

         os.write(getEndTag(root));
  }

}

I think this should work seamlessly for all XML-based collections, even those not relying on JAXB. I was prototyping it when working on 0.8 support but for some reasons I decided not to go ahead then - may be there's some flaw above but it appears it should work.

We can also make the use of @CollectionElement optional - but then we'd need to use some default wrapper element like <result> or <collection> which may make the actual xml instance fail the validation on the other end. Deducing the namespace from the collection elements may end up be too brittle/ineffective or unreliable if it's non-JAXB.

What do you think ? Let me know please...


(2) Is there a different spot in the chain where I can put this that would
change the objects prior to their being consumed by any providers?  The
documentation on the JAX-RS filters wasn't totally clear to me on this
point.

Yes, you can do with CXF out interceptors or JAX-RS response filters or even with custom invokers, they all have access to the response object. With custom invokers you just wrap the response object as you need, for ex see :
http://svn.apache.org/repos/asf/cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/CustomJAXRSInvoker.java

so you do

return new MyWrapper(super.invoke(...));

with filters you can do :

public interface MyResponseHandler {

   Response handleResponse(Message outputMessage,
                          OperationResourceInfo invokedOperation,
                          Response response) {
          // this method is not minimally effective as you can get 
invokedOperation/response from
          // outputMessage - but you might need to deal directly with the 
message
          // 1 - using response
          ResponseBuilder builder = Response.status(response.getStatus());
          // next, copy response headers into the builder
          // finally
          return builder.entity(new MyWrapper(response.getEntity())).build();

          // 2. Using message - this can be done in CXF out interceptor too
          MessageContentsList objs = 
MessageContentsList.getContentsList(message);
if (objs !== null && objs.size() == 1) {
   Object responseObj = objs.remove(0);
   obj.add(new MyWrapper(responseObj));
}
   }

}


cheers, Sergey



Thanks in advance,
Dan


-----
CONFIDENTIALITY NOTICE: The information contained in this message
may be privileged and confidential and protected from disclosure.
If the reader of this message is not the intended recipient, or
responsible for delivering it to the intended recipient, please
be advised that any distribution, dissemination, copying or other
transmission or use of the information contained in this message
or its enclosed attachments is strictly prohibited.  Please
notify us immediately if you have received this message in error
by replying to the sender of the message and deleting it from
your computer.  Thank you.




Reply via email to