2

I am building an application providing a JAX-RS REST service using JPA (EclipseLink). When exposing User entities over JSON, I am using the @XmlTransient annotation on some fields (e.g. the password field) to hide them from the JSON representation. When sending a create or update (POST/PUT) operation, I would like to populate the missing fields again so JPA will correctly perform the operation.

My current approach is that I have a custom JsonDeserializer that is used to deserialize the User and to add the missing fields. For this I would like to inject (using @Inject) a UserFacadeREST bean which handles the JPA-stuff. However, this injection fails and the bean instance is null (which then of course causes a NullPointerException).

My UserFacadeREST bean is annoted as follows:

@Stateless
@LocalBean
@Path(UserFacadeREST.PATH)
public class UserFacadeREST extends AbstractFacade<User> {
    //...
}

My UserDeserilizer (custom JsonDeserializer):

public class UserDeserializer extends JsonDeserializer<User> {

  @Inject
  private UserFacadeREST userFacade;

  @Override
  public User deserialize(JsonParser parser, DeserializationContext context) throws IOException,
      JsonProcessingException {
    JsonNode node = parser.getCodec().readTree(parser);
    int userId = (Integer) ((IntNode) node.get("userID")).numberValue();
    System.out.println(userId);
    User user = userFacade.find(userId); // This line produces the NullPointerException
    return user;
  }

}

which I then use on my User entity with @JsonDeserialize:

@Entity
@Table(name = "User")
@XmlRootElement
@JsonDeserialize(using = UserDeserializer.class)
public class User implements Serializable {
    // ...
}

I have included a bean.xml file in my WEB-INF folder with bean-discovery-mode set to all. What am I missing?

Neil Stockton
  • 11,383
  • 3
  • 34
  • 29
Severin
  • 668
  • 2
  • 7
  • 19
  • You can only @Inject into CDI container managed objects. Since `UserDeserializer` is not declared to be a managed bean, no dependency injection should occur since the container wouldn't be providing this service for you. – scottb Aug 23 '16 at 22:06
  • Thanks. So how do I declare my UserDeserializer to be a managed bean? I tried with `@ApplicationScoped` and `@Singleton`, but neither one worked... – Severin Aug 23 '16 at 23:33

2 Answers2

5

Jon Peterson pointed me to the right direction. I finally chose to implement the 'hackish' solution, in a way. Please note that there are basically 2 options here (if you know another one, please let me know!). Short version:

  1. Hackish solution (the solution I chose): inject a bean programmatically using javax.enterprise.inject.spi.CDI.current().select(UserFacadeRest.class).get() as described in the accepted answer of the question mentioned by Jon or
  2. Better (clean) solution (but also more elaborate): Redesign the logic to fill the missing fields after deserialization as suggested by Jon.

So for my question, the solution looks as follows:

1.

import javax.enterprise.inject.spi.CDI;

public class UserDeserializer extends JsonDeserializer<User> {

  private final UserFacadeREST userFacade =
      CDI.current().select(UserFacadeREST.class).get();

  // Rest as before
}

2. In this case, in the deserialize method of my JsonDeserializer I would construct a User that just holds the userID. In every request method I would then have to examine all the users and replace them by the actual user by calling EntityManager.find(User.class, user.getUserID()). This means more effort in the business logic as you have to keep in mind that everytime you need to work on a User in a request method, you first have to do a query to get the 'full' User object. In the first solution, this query is hidden from the business logic and already happens in the JsonDeserializer.

public class UserDeserializer extends JsonDeserializer<User> {

  @Override
  public User deserialize(JsonParser parser, DeserializationContext context) throws IOException,
      JsonProcessingException {
    JsonNode node = parser.getCodec().readTree(parser);
    int userId = (Integer) ((IntNode) node.get("userID")).numberValue();
    return new User(userId); // Placeholder User object containing only the user ID, needs to be replaced in business logic
  }

}
Community
  • 1
  • 1
Severin
  • 668
  • 2
  • 7
  • 19
1

I'm not super familiar with CDI, but some quick Google'ing leads me to believe that bean-discovery-mode should either be all, annotated, or none (true not being a valid value). Reference

If that doesn't fix it, it might be the same issue that Spring would have: you have to declare your UserDeserializer as a bean for the dependency injection to be applied.

EDIT: Just found this other question that is basically the same issue you are having.

Ultimately, you probably need to just redesign the logic to call userFacade after deserialization.

Community
  • 1
  • 1
Jon Peterson
  • 2,966
  • 24
  • 32
  • Sorry Jon, you are absolutely right. I actually used `all`, just confused it when writing the question. I just checked and the project is not even compiling when using `true`. So how do I declare my `UserDeserializer` as a bean? Of course with some kind of annotation, but which one? – Severin Aug 23 '16 at 23:35
  • After giving this a second though, I actually don't think this can be done at all. Instances of `UserDeserializer` are going to be created by Jackson and not by CDI. This means that CDI annotations (ex `@Inject`) aren't going be handled. I just found another question that I am going to put in my edited answer: – Jon Peterson Aug 24 '16 at 02:33
  • Thanks Jon, you pointed me to the right direction. Injecting an instance of UserFacadeREST programmatically works for me, so I chose this approach as it requires less effort and we have the 'refill' of the missing fields in one place and abstracted away from the business logic. But I understand, that this is not the best way. I tried to highlight this in my answer and to mention both approaches, so I will mark mine as the accepted one, just in case anybody else prefers to stick with injection. – Severin Aug 25 '16 at 00:54