0
votes

I'm searching all over the web the past 2 days and I still don't find a solution to my strange problem.

I have a @SessionScoped bean, A Restful Jersey Service with @Stateless annotation and @Inject the bean and @EJB my ejb.

When I try to call the rest web service from the URL of my browser the service injects my @Inject userBean. But when I try to call the web service from code inside @SessionScoped UserBean it doesn't injected.

The Bean:

@SessionScoped
@Named("userBean")
public class UserBean implements Serializable {

@EJB
private UserEJB userEJB;

private User user = new User();

public User getUser() {
    return user;
}

public void setUser(User user) {
    this.user = user;
}

private String newStatusContent;

public String getNewStatusContent() {
    return newStatusContent;
}

public void setNewStatusContent(String content) {
    this.newStatusContent = content;
}

public void publishStatus() {
    if (newStatusContent != null && newStatusContent.trim().isEmpty() == false) {
        //Maybe a way to set manualy the user property of this bean here?
        Client client = ClientBuilder.newClient();
        Response response = client.target("http://localhost:8080/services/posts/status")
                .queryParam("content", newStatusContent)
                .request(MediaType.APPLICATION_JSON).get();
        newStatusContent = "";
        FacesContext context = FacesContext.getCurrentInstance();
        HttpServletRequest request
                = (HttpServletRequest) context.getExternalContext().getRequest();
        if (request.getRequestURI().equals("/index/profile/profile.xhtml") &&     request.getParameter("prettyname") != null && request.getParameter("prettyname").equals(user.getPrettyname())) {
        }
        if (response.getStatus() == 200) {
            FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Δημοσίευση", "Η νέα κατάσταση δημοσιεύτηκε επιτυχώς."));

        } else {
            FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Δημοσίευση", "Συγγνώμη, κάτι πήγε στραβά με την νέα κατάσταση :("));

        }

    }

}

 public String login() throws ServletException {
    User authenticatedUser;
    if (userEJB.mailExists(user)) {
        if ((authenticatedUser = userEJB.authenticate(user)) != null) {
            user = authenticatedUser;
            //and store the api key for rest api
            // userEJB.storeApiKey(user.getUserId());
        } else {
            FacesContext.getCurrentInstance().addMessage("mail", new FacesMessage("Wrong password for this e-mail"));
            return "";
        }

    } else {
        user = userEJB.create(user);
    }

    if (getPrevURI().isEmpty() == false) {
        return getPrevURI();
    } else {
        return "/index/index.xhtml?faces-redirect=true";
    }

}
} 

The RestApplication class (no web.xml) :

@ApplicationPath("/services/*")
public class RestApplication extends javax.ws.rs.core.Application{

@Override
public Set<Class<?>> getClasses() {
return new HashSet<Class<?>>(Arrays.asList(
        UserService.class, 
        MessageService.class, PostService.class));
}

}

The Restful Webservice class:

@Path("/posts")
@Stateless
public class PostService {

@EJB
private PostEJB postEJB;

@Inject
UserBean userBean;

@Path("/status")
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response publishStatus(@QueryParam("content") String content) {
    if (content.isEmpty() || userBean.getUser().getUserId() == null) {
        System.out.println("null user id or content but?" + content);
        return Response.status(Response.Status.BAD_REQUEST).build();
    }
    Post postStatus = new Post();
    postStatus.setContent(content.trim());
    postStatus.setPublisher(userBean.getUser());
    postStatus.setPublisherUserId(userBean.getUser().getUserId());
    postEJB.create(postStatus);
    System.out.println("post id : "+postStatus.getPostId());
    PostStatus status = new PostStatus(postStatus.getPostId());
    status.setReceiver(userBean.getUser());
    status.setReceiverId(userBean.getUser().getUserId());
    postEJB.postStatus(status);
    return Response.status(Response.Status.OK).build();
}

//*this is not the problem I tried to remove and still doesn't worked*
@PreDestroy
public void destruct() {
    postEJB.destruct();

  }
}

Beans.xml (bean-discovery-mode to 'all')

 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee  http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
    bean-discovery-mode="all"
    >

 </beans>

My steps:

  • 1: Login from my login.xhtml page,
  • 2: When I try:
    http://localhost:8080/services/posts/status?content=Test status
  • 3: Check database and status exists
  • 4: When I try to call the same service from my UserBean (publishStatus method),
  • 5: The output is: 'null user id or content but?Test status2' so the content was sent successfully but the getUser().getUserId() from UserBean is null
  • 6: Check database and the new status doesn't exists ofcourse

I am using Glassfish 4.1, JDK 8.20, Jersey 2.10 (module glassfish).
What am I doing wrong? why is working on browser's url and not from the client call from code?

-----WORKING ALTERNATIVE WAY-----------
by using 'API KEY' SAVED on Encrypted Cookie ( for browser's /js calls ) and On Encrypted Session

The other way I found to do secure authentication but it is not that I want:

ApiAuthenticator service filter:

public class ApiAuthenticator implements ClientRequestFilter {

private static final StandardPBEStringEncryptor encryptor;

static {
    encryptor = new StandardPBEStringEncryptor();
    encryptor.setPassword("jasypt");
}

public static String encrypt(String theString) {
    return encryptor.encrypt(theString);
}

public static String decrypt(String encryptedString) {
    return encryptor.decrypt(encryptedString);
}
private final String apikey;

public ApiAuthenticator(String apikey) {
    this.apikey = apikey;
}

@Override
public void filter(ClientRequestContext requestContext) throws IOException {
    //requestContext.getCookies().putIfAbsent("apikey", new Cookie("apikey", apikey));
    requestContext.getHeaders().add("apikey", encrypt(apikey));

}

}

Edited publishStatus method inside PostService:

@Path("/status")
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response publishStatus(@QueryParam("content") String content, @Context HttpHeaders headers)   {

    String apikey = headers.getHeaderString("apikey");
    if (apikey == null) {
        apikey = headers.getCookies().get("apikey").getValue();
    }

    if (content.isEmpty() || apikey == null) {
        return Response.status(Response.Status.BAD_REQUEST).build();
    }
    User user = userEJB.findByApiKey(ApiAuthenticator.decrypt(apikey));
    Post postStatus = new Post();
    postStatus.setContent(content.trim());
    postStatus.setPublisher(user);
    postStatus.setPublisherUserId(user.getUserId());
    postEJB.create(postStatus);
    PostStatus status = new PostStatus(postStatus.getPostId());
    status.setReceiver(user);
    status.setReceiverId(user.getUserId());
    postEJB.postStatus(status);
    return Response.status(Response.Status.OK).build();
}

Edited client call from bean to the service:

 Client client = ClientBuilder.newClient();

        Response response = client.target("http://localhost:8080/services/posts/status")
                .queryParam("content", newStatusContent)
                .register(new ApiAuthenticator(apiEJB.getApiKey(user.getUserId()).getApiKey()))
                .request(MediaType.APPLICATION_JSON).get();

Edited login method on UserBean after user has succesfully logged in:

   ((HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse())
                    .addCookie(new Cookie("apikey",   ApiAuthenticator.encrypt(apiEJB.storeApiKey(user.getUserId()).getApiKey())));

RestApplication.java :

@ApplicationPath("/services/*")
public class RestApplication extends javax.ws.rs.core.Application{

@Override
public Set<Class<?>> getClasses() {
return new HashSet<Class<?>>(Arrays.asList(
        ApiAuthenticator.class ,
        UserService.class, 
        MessageService.class, PostService.class));

}


}

Solution is to @Inject the PostService inside UserBean

on UserBean:

@Inject PostService postService;

on UserBean.login method, remove all rest client code and replace with:

postService.publishStatus(newStatusContent);

But,

Caused by: org.jboss.weld.context.ContextNotActiveException: WELD-001303: No active contexts for scope type javax.enterprise.context.SessionScoped When I try to Inject the MessageService inside WebSocket ServerEndPoint

@ServerEndpoint(value = "/websocket", encoders = {MessageEncoder.class},
    decoders = {MessageDecoder.class}, configurator = GetHttpSessionConfigurator.class)

public class ChatServerEndPoint {

@PersistenceContext(unitName = "CosmosDBPeristenceUnit")
private EntityManager em;

private static Set<Session> peers = Collections.synchronizedSet(new HashSet<Session>());

@Inject
private MessageService messageService;
//private Session session;
private int thisUserId;
private User thisUser;

@OnOpen
public void onOpen(Session thisSession, EndpointConfig config) throws IOException, EncodeException {
    thisUserId = (int) config.getUserProperties().get("userId");
    thisUser = em.find(User.class, thisUserId);
    thisSession.getUserProperties().put("userId", thisUserId);

    Iterator<Session> it = peers.iterator();
    while (it.hasNext()) {
        Session session = it.next();
        int userId = (int) session.getUserProperties().get("userId");

        thisSession.getBasicRemote().sendObject(new UserStatusMessage(em.find(User.class, userId), true));
        session.getBasicRemote().sendObject(new UserStatusMessage(thisUser, true));
    }

    peers.add(thisSession);
}

/* we don't want @stateless problems with chat info except message, I will do it with rest public   */
@OnMessage
public void onMessage(WebsocketMessage message) {
    try {
        Iterator<Session> it = peers.iterator();
        if (message instanceof ChatMessage) {
            ChatMessage msg = (ChatMessage) message;
            msg.setSender(thisUser);
            boolean otherUserIsOnline = false;
            while (it.hasNext()) {
                Session receiver = it.next();
                if ((int) receiver.getUserProperties().get("userId") == msg.getReceiver_userId()) {
                    receiver.getBasicRemote().sendObject(msg);
                    otherUserIsOnline = true;
                    break;
                }
            }
            if (otherUserIsOnline == false) {
                //save the message to database via rest
                messageService.addMessage(msg.getReceiver_userId(), msg.getMessage());
            }
        } else {
            while (it.hasNext()) {
                Session receiver = it.next();
                receiver.getBasicRemote().sendObject(message);
            }
        }

    } catch (IOException | EncodeException ex) {
        System.out.println("Exception on endpoint: " + ex.getMessage());
    }
}

@OnClose
public void onClose(Session session) throws IOException, EncodeException {
    peers.remove(session);
    Iterator<Session> it = peers.iterator();
    while (it.hasNext()) {
        Session otherSession = it.next();    
        otherSession.getBasicRemote().sendObject(new UserStatusMessage(thisUser, false));

    }

}

@OnError
public void onError(Throwable t) {
    t.printStackTrace();
}

}

GetHttpSessionConfigurator.class :

public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator {

@Override
public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) {
    HttpSession ses = (HttpSession) request.getHttpSession();
    config.getUserProperties().put("userId", ses.getAttribute("userId"));
}
}

MessageService.java:

@Stateless
@Path("/messages")
public class MessageService {

@EJB
UserEJB userEJB;

@EJB
ConversationEJB convEJB;

@Inject
UserBean userBean;


@Path("/{otherUserId}")
@GET
@Produces("application/json; charset=UTF-8")
public List<Message> findConversation(@PathParam("otherUserId") int otherUserId) {
    User otherUser = userEJB.findById(otherUserId);

    return convEJB.findConversation(userBean.getUser(), otherUser);

}


@Path("/{receiverUserId}")
@POST
@Produces("application/json; charset=UTF-8")
public Message addMessage(@PathParam("receiverUserId") int receiverUserId, @QueryParam("body") String body) {
    return convEJB.addMessage(userBean.getUser(), userEJB.findById(receiverUserId), body);
}

@Path("/{messageId}")
@DELETE
public void removeMessage(@PathParam("messageId") int messageId) {
    convEJB.removeMessage(messageId);
}

@Path("/{userId}/{otherUserId}")
@DELETE
public void clearConversation(@PathParam("userId") int userId, @PathParam("otherUserId") int otherUserId) {
    convEJB.clearConversation(userEJB.findById(userId), userEJB.findById(otherUserId));
}

}

Thanks in advance!

1
In JAX-RS, there is no active session scope. - John Ament
I @Inject the PostService inside UserBean class and it's working(@Gas answer). What did you mean excactly 'there is no active session scope' ? - kataras
@MakisMaropoulos You've tested that there is session scope, since from browser it worked for you. So I cannot agree with John on that. - Gas
@Gas we have a new problem,I think John was right because of this error: Caused by: org.jboss.weld.context.ContextNotActiveException: WELD-001303: No active contexts for scope type javax.enterprise.context.SessionScoped When I try to Inject the service inside WebSocket ServerEndPoint. I Updated my question on top of this page with all necessary information, thanks - kataras
@MakisMaropoulos WebSocket is different story, so don't mix that with JAX-RS. I'd suggest you to create new question. I'm sorry, but I'm not in web sockets yet. - Gas

1 Answers

0
votes

As i don't know exact flow of your app, it may be wrong, but here are my assumption:
Your userBean is session scoped and it holds user data. Somehow along the way in your app it is probably initialized.

So once you call your service from the browser, it sends session cookie to correctly locate the session data.

But when you call the service from the code, it is not passing that session cookie, so on the server side it creates completely new session hence new userBean with empty user data. That's why you have UserId null. Bean itself IS injected, otherwise you would have null pointer already here:

userBean.getUser().getUserId() // as userBean would be null

so to find correct user bean in that call you will need add your current session cookie to that call.

PS. But why you are making request to the bean via http?? Wouldn't it be more logical to just inject the service bean and call its method, instead doing http call?