Unmarshalling XML or JSON to guice injected object in jersey using @InjectParam annotation

StackOverflow https://stackoverflow.com/questions/21952858

  •  15-10-2022
  •  | 
  •  

Frage

I have got a problem having XML data being unmarshalled correctly. I am using the technologies guice and jersey. The strange thing is that when I attempt to unmarshall manually using JAXB everything works fine:

StringBuilder xml = new StringBuilder();
xml.append("<role>");
xml.append("  <name><values><value>Administrator</value><value l=\"en\">Administrator</value></values></name>");
xml.append("  <permissions>");
xml.append("    <permission>READ_XX</permission>");
xml.append("    <permission>WRITE_XX</permission>");
xml.append("  </permissions>");
xml.append("</role>");

JAXBContext jaxbContext = JAXBContext.newInstance(DefaultRole.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
Object o = jaxbUnmarshaller.unmarshal(new StringReader(xml.toString()));

I end up with the correctly populated object: DefaultRole [id=null, name=[{val=Administrator}, {l=en, val=Administrator}], permissions=[READ_XX, WRITE_XX]]

However when attempting this with jersey and injecting my Role interface it does not work:

@POST
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public JSONObject processLogin(@InjectParam Role role) throws JSONException
{
  System.out.println(role);

  return null;
}

The object is instantiated, but not populated: DefaultRole [id=null, name=null, permissions=[]]

The strange thing is that when I replace the interface parameter (@InjectParam Role role) with the default class:

@POST
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public JSONObject processLogin(DefaultRole role) throws JSONException
{
  System.out.println(role);

  return null;
}

it works and I get my object populated correctly again.

What do I need to do to get my object populated correctly when injecting it with guice using the interface?

My DefaultRole class looks like this:

@XmlRootElement(name = "role")
@XmlAccessorType(XmlAccessType.FIELD)
public class DefaultRole implements Role
{
  private Id id = null;

  @XmlJavaTypeAdapter(ContextObjectAdapter.class)
  private ContextObject<String> name = null;

  @XmlElementWrapper(name = "permissions")
  @XmlElement(name = "permission")
  private List<String> permissions = Lists.newArrayList();

  @Inject
  private DefaultRole()
  {
  }
  [...]

I have tried adding the JAXB annotations to the interface, but that has not helped either. Could someone please shed some light on this. I have spent hours trying to get this to work now without any luck.

Any help on this would be greatly appreciated. Thanks in advance, Michael

Answer to web.xml question:

I am not actually going the standard route via the WEB-INF, but this is what my code looks like that registers the jersey components (of course, if you would still like to see my web.xml, I will be happy to provide it):

    Injector injector = ModuleInjector.get().createChildInjector(new JerseyServletModule()
    {
        @Override
        protected void configureServlets()
        {
            bind(GuiceContainer.class);

            Set<Class<?>> foundClasses1 = Reflect.getReflections().getTypesAnnotatedWith(Path.class, false);
            Set<Class<?>> foundClasses2 = Reflect.getReflections().getTypesAnnotatedWith(Provider.class, false);

            Set<Class<?>> foundClasses = Sets.newHashSet();
            foundClasses.addAll(foundClasses1);
            foundClasses.addAll(foundClasses2);

            if (foundClasses != null && foundClasses.size() > 0)
            {
                for (Class<?> foundClass : foundClasses)
                {
                    if (foundClass == null)
                        continue;

                    if (ResourceConfig.isProviderClass(foundClass)
                        || ResourceConfig.isRootResourceClass(foundClass))
                    {
                        bind(foundClass);
                    }
                }
            }

            serve("/restws/*").with(GuiceContainer.class, ImmutableMap.of(JSONConfiguration.FEATURE_POJO_MAPPING, "true"));
        }
    });

OK, I'll just post it. Here is part of my web.xml:

    <!-- WHERE THE JERSEY COMPONENTS ARE BEING REGISTERED -->
<filter>
     <display-name>Bootstrap Filter</display-name>
     <filter-name>BootstrapFilter</filter-name>
     <filter-class>xxx.BootstrapFilter</filter-class>
 </filter>

<filter>
    <filter-name>Guice Filter</filter-name>
    <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>


    <!-- WHERE THE JERSEY COMPONENTS ARE BEING REGISTERED -->
<filter-mapping>
    <filter-name>BootstrapFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
</filter-mapping>

<filter-mapping>
    <filter-name>Guice Filter</filter-name>
    <url-pattern>/restws/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
</filter-mapping>
War es hilfreich?

Lösung

After I got a reply in the jersey mailing list saying that guice was not supported by jersey, I have digged a bit further to try and come up with a solution. Here is the message:

"Jersey 2.0 doesn't support Guice. See https://java.net/jira/browse/JERSEY-1950". (http://jersey.576304.n2.nabble.com/Guice-integration-td7581958.html)

Just for completeness I'll post the solution here too. The solution I have come up with may not be a generic solution for everyone, but may give some ideas or help if the following conditions are met:

  1. All objects which are to be injected are "marked" with a class-level annotation.
  2. All objects which are to be injected implement some interface (although it may work without by specifying Object?).
  3. You are happy with creating your own custom param annotation.

In my particular case all of my beans are marked with a @Model annotation and implement my "Model" interface. Other classes such as repositories or services can be injected normally into jersey resources by guice anyway. The problem I had was merely to do with the @InjectParam annotation.

The magic part of it all I suppose is mainly the line:

    model = request.getEntity(model.getClass());" 

which magically populates the guice-injected-object with the deserialized content (no matter whether JSON or XML). I am surprised that this doesn't happen with the built in solution when using @InjectParam already because after all, the actual injecting wasn't the problem - it just wasn't populating the object.

So here is how I solved it:

  1. Created an annotation called “ModelParam”:

    @Target({ PARAMETER, METHOD, FIELD })
    @Retention(RUNTIME)
    @Documented
    public @interface ModelParam
    {
    }
    
  2. Replaced the “InjectParam” annotation in my resource “RoleRestWS” with the new “ModelParam” annotation:

    @POST
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    public Role createRole(@ModelParam Role role) throws JSONException
    {
      return userService.createRole(role);
    }
    
  3. Created an InjectableProvider for the new “ModelParam” annotation:

    @Provider
    @Singleton
    public class ModelInjectableProvider extends AbstractHttpContextInjectable<Model> implements InjectableProvider<ModelParam, Type>
    {
        private final Type type;
    
        public ModelInjectableProvider()
        {
            type = null;
        }
    
        public ModelInjectableProvider(Type type)
        {
            this.type = type;
        }
    
        @Override
        public ComponentScope getScope()
        {
            return ComponentScope.Undefined;
        }
    
        @Override
        public Injectable<Model> getInjectable(ComponentContext ic, ModelParam mp, Type type)
        {
            if (type instanceof Class && Model.class.isAssignableFrom((Class<?>) type))
            {
                return new ModelInjectableProvider(type);
            }
    
            return null;
        }
    
        @Override
        public Model getValue(HttpContext ctx)
        {
            if (type instanceof Class && Model.class.isAssignableFrom((Class<?>) type))
            {
                HttpRequestContext request = ctx.getRequest();
    
                Model model = null;
    
                if (HttpMethod.POST.equals(request.getMethod()) || HttpMethod.PUT.equals(request.getMethod()))
                {
                    model = (Model) MyGuiceInjector.inject((Class<?>) type);
    
                    if (model != null)
                        model = request.getEntity(model.getClass());
                }
    
                return model;
            }
    
            return null;
        }
    }
    

Hope this helps someone with a similar problem.

Best regards, Michael

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top