Reputation: 514
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
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>
Upvotes: 3
Views: 953
Reputation: 514
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:
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:
Created an annotation called “ModelParam”:
@Target({ PARAMETER, METHOD, FIELD })
@Retention(RUNTIME)
@Documented
public @interface ModelParam
{
}
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);
}
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
Upvotes: 1