Wednesday, June 04, 2014

Wildfly and Resteasy are missing support for FastInfoset collection and map...

so, if you have a REST resource returning a java.util.List or a java.util.Map, like:
@Path("people")
@Produces({"application/fastinfoset"})
@Consumes({"*/*"})
public class SomeResource {
  
  @GET
  public List<SomeType> getAll() {
  ...
    
  // and / or

  @GET
  @Path("/map")
  public Map<String, SomeType> getMap() {
  ...
and you try to access it (e.g. curl -H "Accept: application/fastinfoset" http://localhost:8080/.../resources/map) you will see

Could not find MessageBodyWriter for response object of type: java.util.ArrayList of media type: application/fastinfoset

or

Could not find MessageBodyWriter for response object of type: java.util.LinkedHashMap of media type: application/fastinfoset Unfortunately Resteasy "forgot" to implement two following classes:

@Provider
@Consumes("application/*+fastinfoset")
@Produces("application/*+fastinfoset")
public class FastInfosetCollectionProvider extends org.jboss.resteasy.plugins.providers.jaxb.CollectionProvider {
  @Override
  protected boolean suppressExpandEntityExpansion() {
    return false;
  }
}
@Provider
@Consumes("application/*+fastinfoset")
@Produces("application/*+fastinfoset")
public class FastInfosetMapProvider extends org.jboss.resteasy.plugins.providers.jaxb.MapProvider {
  @Override
  protected boolean suppressExpandEntityExpansion() {
    return false;
  }
}
I already sent my pull request, but in case it is never merged, you just have to provide two classes I showed above to make it work.

Tuesday, May 27, 2014

How to convert broken FastInfoset stream into XML?

Have you ever received a broken FastInfoset (FI) stream? Or maybe, your logging infrastructure is truncating the logs so FI messages are incomplete? If you try this piece of code:
import java.io.IOException;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;
import com.sun.org.apache.xml.internal.serializer.Method;
import com.sun.xml.internal.fastinfoset.sax.SAXDocumentParser;

public class FiDecoder {

  public static void main(String[] args) throws IOException, SAXException {
    XMLReader saxReader = new SAXDocumentParser();
    OutputFormat format = new OutputFormat(Method.XML, null, false);
    format.setOmitXMLDeclaration(true);
    XMLSerializer handler = new XMLSerializer(System.out, format);
    saxReader.setContentHandler(handler);
    saxReader.parse(new InputSource(System.in));
  }
}
on a broken FI stream (e.g. PersonMap-broken.fi) you will see just an exception, no XML in the standard output. The problem is that the internal buffer hold by XMLSerializer has not been flushed yet and in case of error it is just dropped. It's a problem for small FI documents where buffer has no chance to be filled up before EOF or invalid FI character. Fortunately there is a simple solution. You just need to extend XMLSerializer and handle error case correctly:
import java.io.IOException;
import java.io.PrintStream;

import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;

public class XmlAutoFlushHandler extends XMLSerializer implements ErrorHandler {

  public XmlAutoFlushHandler(PrintStream out, OutputFormat format) {
    super(out, format);
  }
  
  @Override
  public void fatalError(SAXParseException exception) throws SAXException {
    try {
      _printer.flush();
    } catch (IOException e) {
      throw new IllegalStateException("unable to flush the stream", e);
    }
  }

  //remainder omitted
}
Finally you have to slightly modify FiDecoder class:
    XmlAutoFlushHandler handler = new XmlAutoFlushHandler(System.out, format);
    saxReader.setContentHandler(handler);
    saxReader.setErrorHandler(handler);
    try {
      saxReader.parse(new InputSource(System.in));
    } catch (IOException | SAXException e) {
      // I don't care
    }
Done! Now when you try PersonMap-broken.fi, you will see incomplete XML (which is correct) and no exception.