Can you explain me why we need in String clientId in the method
void addMessage(String ClientId, FacesMessage msg);?
Is it true that when client sent an initial request to server then server assign to this client a specific String ClientId. As i understand FacesContext created when aplication deployed and runnig the first time and this FacesContext is unique for application. Is it true?
Client id in FacesContext#addMessage(String clientId, FacesMessage message) is needed to be able to add a specific message to a specific component. You can either specify a client id of the component you'd like to assign the message to, or null to add the message to the messages stack not tied to a specific component. The latter will be updated in e.g. <h:message for="clientId>, while the latter in e.g. <h:messages globalonly="true">. You can add a message to global messages in case it is not component-specific, for instance, when database operation failed.
Related
I'm trying to use library azure-servicebus-jms-spring-boot-starter to send messages to topic. Everything works, however messages are being stored in subscriptions as application/xml type and I can't find the way how to setup this correctly to have them stored as application/json.
I've tried to configure message converter to send ContentType as described here but that doesn't work either.
#Bean
public MessageConverter jacksonJmsMessageConverter() {
final MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(){
#Override
protected TextMessage mapToTextMessage(Object object, Session session, ObjectWriter objectWriter)
throws JMSException, IOException {
final TextMessage message = super.mapToTextMessage(object, session, objectWriter);
message.setStringProperty("ContentType", "application/json");
return message;
}
};
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
converter.setObjectMapper(objectMapper);
return converter;
}
There is no exposed means of setting the content-type on the messages sent from the Qpid JMS client. The client itself uses this field as part of the JMS mapping to AMQP to distinguish certain message types that it sends and to determine at receive time what certain messages should be presented as.
It is technically possible to use reflection to reach in and so the value but the APIs you have to use from the JmsMessageFacade class are not public and could change with any release so choosing to do so comes with significant risk.
In short, I don't know how I notify a session scoped bean from a application scoped bean. I found a dirty hack that work, but I'm not sure there is a better way to solve this problem (or my dirty hack is not dirty :-D ).
Every websocket in java ee has a sessionid. But this sessionid is not the same like in jsf, and there is no way for a easy mapping. In my environment I have a jsf webpage, an underlying sessionscoped backing bean and a websocket, that is connected to an external service via jms. When the jsf page is loaded, and the websocket is also connected to the browser, the backing bean sends a request to the external service. When I got a async answer message via jms I dont know which websocket is connected with the jsf page/backing bean that send the request.
To solve this problem with a partly dirty hack, I wrote an application scoped mediator class.
#Named
#ApplicationScoped
public class WebsocketMediator {
#Inject
private Event<UUID> notifyBackingBeans;
private Integer newSequenceId=0;
// I need this map for the dirty hack
private Map<UUID, BackingBean> registrationIdBackingBeanMap;
private Map<Integer, UUID> sequenceIdRegistrationIdMap;
registrationIdWebSocketMap = new ConcurrentHashMap<>();
public UUID register(BackingBean backingBean) {
UUID registrationId = UUID.randomUUID();
registrationIdSequenceMap.put(registrationId, new HashSet<>());
registrationIdBackinBeanMap.put(registrationId, backingBean);
}
public Integer getSequenceId(UUID registrationId) {
sequenceId++;
sequenceIdRegistrationIdMap.put(sequenceId, registrationId);
registrationIdSequenceMap.get(registrationId).add(sequenceId);
return sequenceId;
}
// this is called from the ws server enpoint
public void registerWebsocket(UUID registrationId, Session wsSession) {
registrationIdWebSocketMap.put(registrationId, wsSession);
websocketRegistrationIdMap.put(wsSession.getId(), registrationId);
notifyBackingBeans.fire(registrationId); // This does not work
SwitchDataModel switchDataModel = registrationIdSwitchDataModelMap.get(registrationId);
if (backingBean != null) {
backingBean.dirtyHackTrigger();
}
}
public void unregisterWebsocket(String wsSessionId) {
...
}
}
The backing bean calls a registration method and gets a uniq random registration id (uuid) . The registration id is placed in a jsf table as a hidden data attribute (f:passTrough). When the websocket is connected, the ws.open function is called in the browser and send the registration id via the websocket to the websocket server endpoint class. The Server endpoint class call the public void registerWebsocket(UUID registrationId, Session wsSession) method in the mediator and the registration id is mapped. When the backing bean is timed out, I call a unregister method from an #PreDestroyed annotated method. Every time, when the external system is called via jms, I put a sequence id into the payload. The Sequence Id is registered in the Mediator class. Every time when the external system sends a message I can lookup the correct websocket in the mediator to bypass the message via the websocket to the correct browser.
Now the system is able to receive async events via the external system, but the backing bean doesn't know that. I tried to send the registration id in a cdi event to the session scoped backing bean, but the event never reach an observer in a session scoped backing bean. So I realized this gab with a dirty hack. I put the instance of every backing bean with the registration id as key into a map in the mediator in the registration method. I placed in the public void registerWebsocket(UUID registrationId, Session wsSession) a dirty hack trigger call of the backing bean. Is there a better solution?
I use Wildfly 13 with CDI 1.2.
Thanks in advance!
I found a solution. When the webpage is called, the #SessionScoped bean is created. I calculate a unique registration id and put it as attribute in the HttpSession:
#PostConstruct
public void register() {
registrationId = switchPortMediator.register(this);
HttpSession session = (HttpSession) facesContext.getExternalContext().getSession(true);
session.setAttribute("switchRegistrationId", registrationId);
log.debug("Registered at mediator - ID=" + registrationId + " http session = "+ session.getId());
}
At the #ServerEndpoint annotated web socket endpoint, is the #OnOpen annotated method. This method is called when the webpage is loaded and the websocket is established. The websocket endpoint class is #ApplicationScoped. The attribute map, from the #SessionScoped bean, where the registration id is stored, is accessable in the EndpointConfig of the websocket endpoint. Here is the #OnOpen annotated method:
#OnOpen
public void onOpen(Session wsSession, EndpointConfig config) {
UUID registrationId = (UUID) config.getUserProperties().get("switchRegistrationId");
websocketRegistrationIdMap.put(registrationId, wsSession);
}
When the JSF page is loaded, the #SessionScoped bean calculate the registration id, put it in the attribute map and sent an async message via jms to an external system. The external system send an answer message, with the registration id inside and a payload. When the external message arrives via JMS in the websocket endpoint class, the resulting wsSession can be retrived from the websocketRegistrationIdMap and the payload can be send via the websocket to the browser, who initiate the async message. The dom updates in the website are processed by javascript.
I'm having some difficulty interacting with a Session-scoped managed bean after a user programmatically logs into my web application.
BACKGROUND:
I have a [javax.enterprise.context.]Session-scoped bean named "SessionHelper" where I place a lot of information gathered from the user as he/she uses the application. In my logon page (which is NOT SessionScoped), Here's a sample of what I'm doing:
#Inject SessionHelper theHelper;
....
FacesContext theContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = theContext.getExternalContext();
HttpServletRequest theRequest = (HttpServletRequest) externalContext.getRequest();
....
theRequest.login(username, password);
....
theSession.method(dostuff);
After this section of code is executed, my application redirects into a protected directory and allows the user (based on roles) to perform their job functions.
When I attempt to "#Inject SessionHelper" into any of my protected resources, my understanding is that I should get the specific SessionScoped instance of SessionHelper that has the data set right after the call to login. This should be available to me for as long as the session (for that specific user) is valid. Unfortunately, the instance I'm getting has none of my "theSession.method(dostuff)" in it.
Am I fundamentally misunderstanding the scope here?
The only thing I could potentially see is that the initial #Inject into my login page is not carried over after the session has been created. If this is the case, is there a way to force a re-injection after the session is created?
As always, thank you very much for your help!!
My Problem
I have a #SessionScoped sessionInformationBean, which holds a Person-Entity from a logged in user. So, if a User logs in, I am looking up the corresponding Entity and put in in the #SessionScoped CDI Bean. This Bean is used to retrieve the current user (a Person-Entity) at any position in code, so that you can check, if it is a Admin or things like that.
#Inject
private PersonFacade personFacade;
private Person currentUser;
public Person getCurrentUser() {
if (currentUser == null) {
String loginname = FacesContext.
getCurrentInstance().
getExternalContext().getRemoteUser();
currentUser = personFacade.findByLoginname(loginname);
}
return currentUser;
}
But set the case, an Admin is giving this logged in user ( the Person-Entity) some Admin-Rights and saves him to the database. In this case, the Person at the #SessionScoped Bean is not updated, therefore the already logged in user is not seeing his Admin-Rights after a refresh of his page. Thats the problem. To avoid this problem I am fetching the user new from the database every access (There is no cache activated) to the #SessionScoped bean.
What I want
But I want to cache him and avoid a database access every time. So, I thought, if anyone saves a user, I will simply notice all sessionInformationBean-Instances and set the currentUser-Attribute to null. So, the next call, they fetch it again from database and cache it till its set to null again from my Person.save()-Operation.
What I tried
But that seems to be a little bit tricky. I thought I can handle it with CDI-Events, but they only will be pushed to the sessionInformationBean of the user, that is editing the other user.
Maybe something to do with my problem: CDI Events observed across sessions
Then I thought.. okay.. lets do it with Primefaces-Push. But the same thing.. the Events are just coming to my own sessionInformationBean.
EventBus eventBus = EventBusFactory.getDefault().eventBus();
eventBus.publish("/session", "test");
I thought the purpose of push and WebSockets is to notify all users or sessions.
What should I do?
So, the question is: How to access all instances of a specific #SessionScopedBean? I just want to access the sessionInformationBean from every logged in user and set the currentUserto null.
There's no built in way I can think of to do this. What I would recommend is to add an ApplicationScoped bean. Whenever your SessionScoped bean is created, register it with this app scoped bean. When you want to process this event iterate through all of these objects.
I'm curious though, what happens when you have multiple servers?
So for my webapp, if I remove a user that is currently logged in, and I want to invalidate his/her session. So that as soon as he/she refresh the page or navigate, they are no longer log in. The way I have now is that if a User logged in successfully, I will store the user object in my SessionScoped bean, and store the HttpSession to the Application Map. Below is my code
This is my SessionScoped bean
#PostConstruct
public void init() {
User user = UserDAO.findById(userId, password);
Map<String, Object> appMap = FacesContext.getCurrentInstance().
getExternalContext().getApplicationMap();
HttpSession session = (HttpSession) FacesContext.getCurrentInstance().
getExternalContext().getSession(false);
appMap.put(userId, session);
}
Is this a correct approach? If so, how do I clean up my application map?
Is this a correct approach?
There are basically 2 ways.
Store the HttpSession handle in the application scope by the user ID as key so that you can get a handle of it and invalidate it. This may work for a small web application running on a single server, but may not work on a web application running on a cluster of servers, depending on its configuration.
I would only store it in another map in the application scope, not directly in the application scope like as you did, so that you can easier get an overview of all users and that you can guarantee that an arbitrary user ID won't clash with an existing application scoped managed bean name, for example.
Add a new boolean/bit column to some DB table associated with the user which is checked on every HTTP request. If the admin sets it to true, then the session associated with the request will be invalidated and the value in the DB will be set back to false.
how do I clean up my application map?
You could use HttpSessionListener#sessionDestroyed() for this. E.g.
public void sessionDestroyed(HttpSessionEvent event) {
User user = (User) event.getSession().getAttribute("user");
if (user != null) {
Map<User, HttpSession> logins = (Map<User, HttpSession>) event.getSession().getServletContext().getAttribute("logins");
logins.remove(user);
}
}
I think you can use your approach (with some modifications proposed by #BalusC) plus some notification mechanism (to make it work in distributed environment). You can do one of the following:
Use a topic queue subscribed by all your servers. When you remove user from your admin panel the JMS message will be created and sent to the topic. Every server will be responsible for invalidating the user session if it exists on the particular server (if the session is referenced in servletContext map).
Implement some action to invalidate the user session and run this action on every server in the cluster (The admin panel should send HTTP request to every server).
Use JGroups and TCP reliable multicast.
All of these solutions are not simple but much faster than polling the DB server on every request.