Hot questions for Using GlassFish in moxy

Question:

I’m migrating our web application from Glassfish 3 to Glassfish 5, and during the migration I ran across this error for a request.

[2019-09-17T15:57:30.732-0600] [glassfish 5.0] [WARNING] [] [javax.enterprise.web] [tid: _ThreadID=241 _ThreadName=http-listener-2(27)] [timeMillis: 1568757450732] [levelValue: 900] [[
 StandardWrapperValve[ClientControllers]: Servlet.service() for servlet ClientControllers threw exception
java.lang.ClassCastException: [Z cannot be cast to [Ljava.lang.Object;
   at org.eclipse.yasson.internal.serializer.ObjectArraySerializer.serializeInternal(ObjectArraySerializer.java:27)
   at org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serialize(AbstractContainerSerializer.java:60)
   at org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serializerCaptor(AbstractContainerSerializer.java:91)
   at org.eclipse.yasson.internal.serializer.ObjectSerializer.marshallProperty(ObjectSerializer.java:92)
   at org.eclipse.yasson.internal.serializer.ObjectSerializer.serializeInternal(ObjectSerializer.java:59)
   at org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serialize(AbstractContainerSerializer.java:60)
   at org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serializerCaptor(AbstractContainerSerializer.java:91)
   at org.eclipse.yasson.internal.serializer.ObjectArraySerializer.serializeInternal(ObjectArraySerializer.java:46)
   at org.eclipse.yasson.internal.serializer.ObjectArraySerializer.serializeInternal(ObjectArraySerializer.java:27)
   at org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serialize(AbstractContainerSerializer.java:60)
   at org.eclipse.yasson.internal.Marshaller.serializeRoot(Marshaller.java:118)
   at org.eclipse.yasson.internal.Marshaller.marshall(Marshaller.java:76)
   at org.eclipse.yasson.internal.JsonBinding.toJson(JsonBinding.java:98)
   at org.glassfish.jersey.jsonb.internal.JsonBindingProvider.writeTo(JsonBindingProvider.java:118)
   at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.invokeWriteTo(WriterInterceptorExecutor.java:266)
   at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:251)
   at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163)
   at org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor.aroundWriteTo(JsonWithPaddingInterceptor.java:109)
   at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163)
   at org.glassfish.jersey.spi.ContentEncoder.aroundWriteTo(ContentEncoder.java:137)
   at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163)
   at org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:85)
   at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163)
   at org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1135)
   at org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:662)
   at org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:395)
   at org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:385)
   at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:280)
   at org.glassfish.jersey.internal.Errors$1.call(Errors.java:272)
   at org.glassfish.jersey.internal.Errors$1.call(Errors.java:268)
   at org.glassfish.jersey.internal.Errors.process(Errors.java:316)
   at org.glassfish.jersey.internal.Errors.process(Errors.java:298)
   at org.glassfish.jersey.internal.Errors.process(Errors.java:268)
   at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:289)
   at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:256)
   at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:703)
   at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:416)
   at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:370)
   at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:389)
   at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:342)
   at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:229)
   at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1580)
   at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:258)
   at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:160)
   at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:652)
   at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:591)
   at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:99)
   at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:155)
   at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:652)
   at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:591)
   at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:368)
   at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:238)
   at com.sun.enterprise.v3.services.impl.ContainerMapper$HttpHandlerCallable.call(ContainerMapper.java:463)
   at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:168)
   at org.glassfish.grizzly.http.server.HttpHandler$1.run(HttpHandler.java:224)
   at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:593)
   at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:573)
   at java.lang.Thread.run(Thread.java:745)
]]

It appears Glassfish 5 is using JSON-B serialization. Glassfish 5 ignores JAXB @XmlJavaTypeAdapter annotation?

In the past we used moxy and jaxb for json binding, and as a result I’ve been trying to register Moxy as the default provider in Glassfish 5. I’ve followed the instructions here with no luck.

https://howtodoinjava.com/jersey/jax-rs-jersey-moxy-json-example/

I also read the Glassfish 5 documentation, but did not find any similar examples for registering a default provider. If someone could shed some light on what I’m doing wrong in the configuration that would be greatly appreciated. I’ve provided samples of my configuration below.

How do I configure Glassfish 5 to use Moxy as the default Provider?

Notes:

  • When I start glassfish, I've debugged the JsonServicesContextResolver.java class and verified the constructor and getContext method are hit during initialization.
  • I've tried removing the GridApplication.java class and removing its references in the web.xml, thinking the jersey.config.server.provider.packages may already register any providers in those packages. I still had the same errors.

JsonServicesContextResolver.java

package com.lnka.eng.grid.jersey;

import org.eclipse.persistence.jaxb.JAXBContextProperties;
import org.glassfish.jersey.moxy.json.MoxyJsonConfig;

import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

@Provider
public class JsonServicesContextResolver
    implements ContextResolver<MoxyJsonConfig>
{
  private MoxyJsonConfig config;

  public JsonServicesContextResolver()
  {
    config = new MoxyJsonConfig()
        .setNamespaceSeparator(':')
        .setAttributePrefix("")
        .setValueWrapper("value")
        .property(JAXBContextProperties.JSON_WRAPPER_AS_ARRAY_NAME, true)
        .setFormattedOutput(true)
        .setIncludeRoot(true)
        .setMarshalEmptyCollections(true);
  }

  @Override
  public MoxyJsonConfig getContext(Class<?> objectType)
  {
    return config;
  }
}

ProgramsController.java

package com.lnka.eng.view.controller;

@Path("/mgmt/programs/")
@Stateless
@Transactional(Transactional.TxType.NOT_SUPPORTED)
public class ProgramsController extends Controller
{
 @GET
  @Path("/list/")
  @Produces(MediaType.APPLICATION_JSON)
  public Response getProgramsList()
  {
    List<Program> programs = getPrograms();

    return Response.ok(programs).build();    
  }
}

GridApplication.java

package com.lnka.eng.grid.jersey;

import com.lnka.eng.view.controller.ProgramsController;

import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;

public class GridApplication extends Application
{
  //Add Service APIs
  @Override
  public Set<Class<?>> getClasses()
  {
    Set<Class<?>> resources = new HashSet<Class<?>>();

    //register REST modules
    resources.add(ProgramsController.class);

    //Manually adding MOXyJSONFeature
    resources.add(org.glassfish.jersey.moxy.json.MoxyJsonFeature.class);

    //Configure Moxy behavior
    resources.add(JsonServicesContextResolver.class);

    return resources;
  }
}

web.xml

I've removed some of the config in this file that I thought irrelevant (security, ejb config, etc)

<web-app version="2.5"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
         http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

  <display-name>Grid</display-name>  
  <servlet>
    <servlet-name>JerseyServiceApplications</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    <init-param>
      <param-name>javax.ws.rs.core.Application</param-name>
      <param-value>com.lnka.eng.grid.jersey.GridApplication</param-value>
    </init-param>
    <init-param>
      <param-name>jersey.config.server.provider.packages</param-name>
      <param-value>com.lnka.eng.grid.service;com.lnka.eng.grid.jersey</param-value>
    </init-param>
    <init-param>
      <param-name>jersey.config.server.provider.classnames</param-name>
      <param-value>org.glassfish.jersey.message.GZipEncoder</param-value>
    </init-param>
    <init-param>
      <param-name>debug</param-name>
      <param-value>false</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet>
    <servlet-name>ClientControllers</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    <init-param>
      <param-name>javax.ws.rs.core.Application</param-name>
      <param-value>com.lnka.eng.grid.jersey.GridApplication</param-value>
    </init-param>
    <init-param>
      <param-name>jersey.config.server.provider.packages</param-name>
      <param-value>
        com.lnka.eng.view.controller,
        com.lnka.eng.grid.jersey
      </param-value>
    </init-param>
    <init-param>
      <param-name>jersey.config.server.provider.classnames</param-name>
      <param-value>
        org.glassfish.jersey.message.GZipEncoder
      </param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>ClientControllers</servlet-name>
    <url-pattern>/client/*</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>JerseyServiceApplications</servlet-name>
    <url-pattern>/resources/*</url-pattern>
  </servlet-mapping>

  <session-config>
    <session-timeout>480</session-timeout>
  </session-config>
</web-app>

Answer:

The issue was we needed to update the web.xml to include the MoxyConfig in the session-config scope. I was also missing some configuration for one of our listeners, but the relevant code is below.

The following was placed inside each of the <servlet>...</servlet> tags.

<init-param>
      <param-name>jersey.config.server.jsonFeature</param-name>
      <param-value>MoxyJsonFeature</param-value>
</init-param>

Question:

I have a web application that uploads files through a MULTIPART_FORM_DATA POST which has both binary data and JSON Strings in it. (The JSON strings are created with the browser's JSON.stringify(obj) function).

According to the documentation Glassfish since 4.0.1 uses MOXy for unmarshalling JSON and XML objects.

My method looks like this:

@POST
@Path("put")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_JSON)
public Response put(@FormDataParam("file") List<FormDataBodyPart> bodyParts,
                    @FormDataParam("metadata") List<String> metaParts) throws JAXBException {

    JAXBContext jbc = JAXBContext.newInstance(MetaData.class);

    for (int index = 0; index < metaParts.size(); index += 1) {

        MetaData meta = null;
        String metaString = metaParts.get(index);
        if (metaString != null && !metaString.isEmpty()) {
            Unmarshaller um = jbc.createUnmarshaller();
            // um.setProperty(???, "application/json");
            meta = (MetaData) um.unmarshal(new StreamSource(new StringReader(metaString)));
        }

The code like this will attempt to parse the data in metaString as an XML document so it throws an exception.

Searching through the available documentation I find that the solution for this for the EclipseLink MOXy implementation appears to be to do a

um.setProperty("eclipselink.media-type", "application/json");

That doesn't work because the Glassfish 5 implementation of MOXy is from com.sun.xml.* not Eclipse. Tracing the code it seems that this implementation will throw an Exception on any setProperty call since it doesn't support any implementation specific properties.

Yet I know Sun's MOXy can do it because it is processing my HTTP requests/responses just fine. Yet I can find no examples or documentation anywhere -- all roads lead to the EclipseLink implementation.

Does anyone know how to do this?


Answer:

You don't need to manually parse the data. What you can do is get the body part as a FormDataBodyPart as you have already done for the "file" part. From the FormDataBodyPart, you would then need to set the media type to application/json1, then just get the POJO using bodyPart.getValueAs(POJO.class).

public Response put(@FormDataBodyPart("metadata") FormDataBodyPart metaDataPart) {
    metaDataPart.setMediaType(MediaType.APPLICATION_JSON_TYPE);
    MetaData metaData = metaDataPart.getValueAs(MetaData.class);
}

See more about this in File upload along with other object in Jersey restful web service


1 - In a multipart request, each body part has it's own Content-Type header. If you don't set it, it will automatically be considered text/plain as the default. With Javascript, you have no way to set the content-type of individual parts, so it will default to text/plain. But we need it to be application/json so that the JAX-RS JSON provider is used for the deserialization.

Question:

I have a Jersey client that makes a call to a 3rd party rest api and retrieves some JSON.

{"A":1,"W":2,"List":[{"name":"John","amount":10.0}]}

After that I need to append this JSON to my response class and give it back in the response.

@XmlRootElement
public class MyResponse {

    private JsonObject body;
    private String status;

I manage to assign the value that comes from the 3rd party api to body but the response that's sent is like this:

{
"status": "success",
"body": {
"entry": [
  {
  "key": "A",
  "value": 1
  }  ,
  {
  "key": "W",
  "value": 2
  },
  {
  "key": "List",
  "value": "[{\"name\":\"John\",\"amount\":10.0}]"
  }
]
}
}

So there are two main issues, moxy is generating key and value elements while I would like it to be key: value and also it is not generating properly the 2nd level objects in the JSON structure provided by the API.


Answer:

MOXy is a JAXB implementation, while JsonObject is part of JSON-P. MOXy happens to be able to deal with JSON too, but that is a proprietary extension over the JAXB standard. As far as I know, there is no default mapping available between JSON-P and JAXB. The reason you're seeing those key/value entries must be because JsonObject extends java.util.Map, so you get the default MOXy mapping for that type.

I think you have the following possibilities:

  1. Go with either JSON-P or JAXB/MOXy (MOXy required for its additional JSON binding) only.
  2. Use one of the JAXB/MOXy mechanisms for mapping custom types from/to JAXB. The standard way is to use an XmlAdapter, examples for dealing with Maps in particular are here and here. But I think this will be difficult if you don't know the structure of the 3rd party JSON content and want to keep nested levels intact.

Yet another possibility might be to use a proprietary API like Jackson, but I can't help with that.