Search code examples
javajsonjacksoncdi

Injection of stateless session bean into custom JsonDeserializer fails


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?


Solution

  • 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
      }
    
    }