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!