I am new to JSF 2.0 / PrimeFaces and I've created one webapp using JSF 2.0 + Spring 4. For session timeout, I've done below mapping in web.xml :
<session-config>
<session-timeout>1</session-timeout>
</session-config>
<error-page>
<exception-type>javax.faces.application.ViewExpiredException</exception-type>
<location>/resources/login/timeout.xhtml</location>
</error-page>
After login, user redirects to admin.xhtml in which there element as
<h:link value="showAnotherPage" outcome="other.xhtml"/>
But after 2 or 5 minutes, when I click on the link, it redirects me to other.xhtml page not on timeout page.
Is anything that I'm missing to configure? Please help.
I had this problem. You need to make sure there is no client call polling (ajax) server side resource, if this happens the session will not expire. I had a <p:poll /> tag that didn't let the session expire.
The ViewExpiredException will be thrown whenever the javax.faces.STATE_SAVING_METHOD is set to server (default) and the enduser sends a HTTP POST request on a view via <h:form> with <h:commandLink>, <h:commandButton> or <f:ajax>, while the associated view state isn't available in the session anymore.
The <h:link> will fire a full GET request so no POST request.
A good post with a more in-depth explanation can be found here
I'd personally implement it in another way.
Define an Exception handler factory in your faces-config.xml as follows:
<factory>
<exception-handler-factory>
com.package.faces.FullAjaxExceptionHandlerFactory
</exception-handler-factory>
</factory>
Create the exception handler factory that extends javax.faces.context.ExceptionHandlerFactory. It should return your own implementation of the ExceptionHandler. This could be an example:
import javax.faces.context.ExceptionHandler;
import javax.faces.context.ExceptionHandlerFactory;
public class FullAjaxExceptionHandlerFactory extends ExceptionHandlerFactory {
private ExceptionHandlerFactory wrapped;
/**
* Construct a new full ajax exception handler factory around the given wrapped factory.
* #param wrapped The wrapped factory.
*/
public FullAjaxExceptionHandlerFactory(ExceptionHandlerFactory wrapped) {
this.wrapped = wrapped;
}
/**
* Returns a new instance of {#link FullAjaxExceptionHandler} which wraps the original exception handler.
*/
#Override
public ExceptionHandler getExceptionHandler() {
return new FullAjaxExceptionHandler(wrapped.getExceptionHandler());
}
/**
* Returns the wrapped factory.
*/
#Override
public ExceptionHandlerFactory getWrapped() {
return wrapped;
}
}
In the end, extend the javax.faces.context.ExceptionHandlerWrapper to handle all exceptions. An example is the following:
public class FullAjaxExceptionHandler extends ExceptionHandlerWrapper {
private ExceptionHandler wrapped;
public FullAjaxExceptionHandler(ExceptionHandler wrapped) {
this.wrapped = wrapped;
}
private static Throwable extractCustomException(Throwable ex) {
Throwable t = ex;
while (t != null) {
if (t instanceof YourOwnExceptionInterface) {
return t;
}
t = t.getCause();
}
return ex;
}
private static String extractMessage(Throwable t) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
return matchJmillErrorTag(sw.toString());
}
public static boolean handleException(Throwable original) {
Throwable ex = extractCustomException(original);
if (ex instanceof ViewExpiredException) {
// redirect to login page
return false;
} else if (ex instanceof YourOwnExceptionInterface) {
((YourOwnExceptionInterface) ex).handle();
return true;
} else if (ex instanceof NonexistentConversationException) {
FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
// redirect to login page
return false;
} else {
String message = extractMessage(ex);
final FacesContext fc = FacesContext.getCurrentInstance();
original.printStackTrace();
// redirect to error page
fc.responseComplete();
return true;
}
}
#Override
public void handle() throws FacesException {
final Iterator<ExceptionQueuedEvent> i = getUnhandledExceptionQueuedEvents().iterator();
FacesContext facesContext = FacesContext.getCurrentInstance();
if (Redirector.isRedirectingToLogin(facesContext)) {
return;
}
while (i.hasNext()) {
ExceptionQueuedEvent event = i.next();
ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();
i.remove();
if (!handleException(context.getException())) {
return;
}
}
getWrapped().handle();
}
#Override
public ExceptionHandler getWrapped() {
return wrapped;
}
}
Check the public static boolean handleException(Throwable original) in the previous class. You could use that to manage all your exceptions.
At one point I put a condition there about YourOwnExceptionInterface which is an interface with a handle() method that I'd implement for example by a NotAuthorizedException kind of exception. In this case in the handle() method of the NotAuthorizedException I'd notify the user that he can't complete a certain operation through a p:growl component for example. I'd use this in my beans as throw new NotAuthorizedException("message");
The custom exception class should of course extend the RuntimeException.
Related
I 'm getting this error from my service
jvm org.hibernate.internal.ExceptionMapperStandardImpl {"#trace_info":"[availability-psql,eba16d49e23479cc,675789f41e0dda5b,eba16d49e23479cc,false]", "#message": "HHH000346: Error during managed flush [Error creating bean with name 'scopedTarget.infoUser': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.]
This is because of I have a bean of scope #ScopeRequest. This problem show up when a new message from kafka is received and I try to update my data base with spring data. If I remove my #Transactional I don't have any problem to save the data.
#KafkaListener(topics = "#{kafkaMastersConfig.topics}", containerFactory = "mastersContainerFactory")
#Transactional
#Authorized
public void consumeWrapperMasterChangeEvent(#Payload String payload,
#Header(KafkaHeaders.RECEIVED_TOPIC) String topic, #Nullable #Header(AUTHORIZATION) String authorization) throws IOException {
try {
log.info("Received change event in masters: '{}'", payload);
RequestAttributes context = RequestContextHolder.currentRequestAttributes();
RequestContextHolder.setRequestAttributes(context);
changeProcessorFactory.getEntityChangeProcessor(getEntityFromTopic(topic)).processChange(payload);
} catch ( Exception e ) {
log.error("Error proccesing message {} ", e.getMessage());
} finally {
RequestContextHolder.resetRequestAttributes();
}
}
And here is the bean:
#RequestScope
#Component
#NoArgsConstructor
#Getter
#Setter
public class InfoUser {
private DecodedJWT jwt;
public String getCurrentUser() {
if (jwt == null) {
return null;
}
return jwt.getSubject();
}
public String getAuthorizationBearer() {
if (jwt == null) {
return null;
}
return jwt.getToken();
}
}
And this class:
public class CustomRequestScopeAttr implements RequestAttributes {
private Map<String, Object> requestAttributeMap = new HashMap<>();
#Override
public Object getAttribute(String name, int scope) {
if (scope == RequestAttributes.SCOPE_REQUEST) {
return this.requestAttributeMap.get(name);
}
return null;
}
#Override
public void setAttribute(String name, Object value, int scope) {
if (scope == RequestAttributes.SCOPE_REQUEST) {
this.requestAttributeMap.put(name, value);
}
}
#Override
public void removeAttribute(String name, int scope) {
if (scope == RequestAttributes.SCOPE_REQUEST) {
this.requestAttributeMap.remove(name);
}
}
#Override
public String[] getAttributeNames(int scope) {
if (scope == RequestAttributes.SCOPE_REQUEST) {
return this.requestAttributeMap.keySet().toArray(new String[0]);
}
return new String[0];
}
#Override
public void registerDestructionCallback(String name, Runnable callback, int scope) {
// Not Supported
}
#Override
public Object resolveReference(String key) {
// Not supported
return null;
}
#Override
public String getSessionId() {
return null;
}
#Override
public Object getSessionMutex() {
return null;
}
}
And futhermore I have an aspect class to save the authorization token:
#Aspect
#Component
#RequiredArgsConstructor
public class AuthorizationAspect {
private final AuthorizationDecoder authorizationDecoder;
private final ApplicationContext applicationContext;
#Around("#annotation(Authorized)")
public Object setInfoUser(ProceedingJoinPoint joinPoint) throws Throwable {
try {
String[] parameterNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
Object[] args = joinPoint.getArgs();
Map<String, Object> arguments = new HashMap<>();
for (int i = 0; i < args.length; i++) {
if (null != args[i]) {
arguments.put(parameterNames[i], args[i]);
}
}
Object authorization = arguments.get("authorization");
RequestContextHolder.setRequestAttributes(new CustomRequestScopeAttr());
InfoUser infoUser = applicationContext.getBean(InfoUser.class);
infoUser.setJwt(authorizationDecoder.decodeToken((String) authorization));
return joinPoint.proceed();
} finally {
RequestContextHolder.resetRequestAttributes();
}
}
And the last class is trying to save de info:
#RequiredArgsConstructor
public class RoomChangeMaster implements ChangeMaster<Room> {
private final TimetableRepository timetableRepository;
private final AvailabilityRepository availabilityRepository;
#Override
public void processChange(Room entity, ActionEnum action) {
if (action == ActionEnum.updated) {
List<Timetable> timetables = (List<Timetable>) timetableRepository.findByRoomId(entity.getId());
Room room = timetables.get(0).getRoom();
room.setDescription(entity.getDescription());
room.setCode(entity.getCode());
timetables.forEach(timetable -> {
timetable.setRoom(room);
timetableRepository.save(timetable);
});
availabilityRepository
.updateAvailabilityRoomByRoomId(room, entity.getId());
} else {
throw new IllegalStateException("Unexpected value: " + action);
}
}
}
I have spent a lot of time finding out the problem, but so far, I was not able to know the problem. Any idea will be appreciate.
Thank you
RequestContextHolder is for Spring-MVC - it is for a Web request only and is populated with information from an HTTP request.
/**
* Holder class to expose the web request in the form of a thread-bound
* {#link RequestAttributes} object. The request will be inherited
* by any child threads spawned by the current thread if the
* {#code inheritable} flag is set to {#code true}.
*
...
There is no equivalent for listener containers (of any type) because there is no "incoming request".
Looks like your hibernate code is tightly tied to the web.
If you are trying to reuse existing code you need to decouple it and use some other technique to pass information between the layers (e.g. a custom equivalent of RequestContextHolder).
Finally, I have solved it changing the hiberante method save by saveAndFlush
In my Spring Boot1.2.7/JSF2.2.12/PrimeFaces5.2/Tomcat 8 application I'm trying to implement redirect to login page after AJAX call on a website where /logout has been performed.
In order to do this I have added JsfRedirectStrategy:
/**
* Inspired by <a href=
* "http://stackoverflow.com/questions/10143539/jsf-2-spring-security-3-x-and-richfaces-4-redirect-to-login-page-on-session-tim">StackOverflow.com</a>
* and by <a href=http://www.icesoft.org/wiki/display/ICE/Spring+Security#SpringSecurity-Step4%3AConfigureYourSpringSecurityredirectStrategy">
* Spring Security 3 and ICEfaces 3 Tutorial</a>.
*
* #author banterCZ
*/
public class JsfRedirectStrategy implements InvalidSessionStrategy {
final static Logger logger = LoggerFactory.getLogger(JsfRedirectStrategy.class);
private static final String FACES_REQUEST_HEADER = "faces-request";
private String invalidSessionUrl;
/**
* {#inheritDoc}
*/
#Override
public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
boolean ajaxRedirect = "partial/ajax".equals(request.getHeader(FACES_REQUEST_HEADER));
if (ajaxRedirect) {
String contextPath = request.getContextPath();
String redirectUrl = contextPath + invalidSessionUrl;
logger.debug("Session expired due to ajax request, redirecting to '{}'", redirectUrl);
String ajaxRedirectXml = createAjaxRedirectXml(redirectUrl);
logger.debug("Ajax partial response to redirect: {}", ajaxRedirectXml);
response.setContentType("text/xml");
response.getWriter().write(ajaxRedirectXml);
} else {
String requestURI = getRequestUrl(request);
logger.debug(
"Session expired due to non-ajax request, starting a new session and redirect to requested url '{}'",
requestURI);
request.getSession(true);
response.sendRedirect(requestURI);
}
}
private String getRequestUrl(HttpServletRequest request) {
StringBuffer requestURL = request.getRequestURL();
String queryString = request.getQueryString();
if (StringUtils.hasText(queryString)) {
requestURL.append("?").append(queryString);
}
return requestURL.toString();
}
private String createAjaxRedirectXml(String redirectUrl) {
return new StringBuilder().append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
.append("<partial-response><redirect url=\"").append(redirectUrl)
.append("\"></redirect></partial-response>").toString();
}
public void setInvalidSessionUrl(String invalidSessionUrl) {
this.invalidSessionUrl = invalidSessionUrl;
}
}
This is my WebSecurityConfig
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.addFilterBefore(sessionManagementFilter(), AnonymousAuthenticationFilter.class)
.csrf().disable()
.authorizeRequests()
.antMatchers("/invite.xhtml").permitAll()
.antMatchers("/forgotpassword.xhtml").permitAll()
.antMatchers("/resetpwd.xhtml").permitAll()
.antMatchers("/admin/**").hasRole(Roles.ROLE_ADMIN.getSpringSecName())
.antMatchers("/**").authenticated()
.antMatchers("/actuator/**").permitAll()
.and()
.formLogin()
.loginPage("/login.xhtml").permitAll()
//.failureUrl("/login?error").permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutRequestMatcher( new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login.xhtml")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.permitAll();
http.headers().frameOptions().disable();
// #formatter:on
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/javax.faces.resource/**");
}
#Bean
public SessionManagementFilter sessionManagementFilter() {
SessionManagementFilter sessionManagementFilter = new SessionManagementFilter(httpSessionSecurityContextRepository());
sessionManagementFilter.setInvalidSessionStrategy(jsfRedirectStrategy());
return sessionManagementFilter;
}
public HttpSessionSecurityContextRepository httpSessionSecurityContextRepository() {
return new HttpSessionSecurityContextRepository();
}
#Bean
public JsfRedirectStrategy jsfRedirectStrategy() {
JsfRedirectStrategy jsfRedirectStrategy = new JsfRedirectStrategy();
jsfRedirectStrategy.setInvalidSessionUrl("/login.xhtml");
return jsfRedirectStrategy;
}
}
This is logout link:
<div id="LogoutContainer" class="PFTopLinks floatRight boldFont">
<h:form rendered="#{not empty request.remoteUser}">
<h:graphicImage name="main/images/pfPush.svg" />
<h:outputLink value="${pageContext.request.contextPath}/logout">
<span class="PFDarkText">Logout</span>
</h:outputLink>
</h:form>
</div>
The problem: Right now JsfRedirectStrategy.onInvalidSessionDetected is never invoked on AJAX JSF call because request.isRequestedSessionIdValid() in SessionManagementFilter.doFilter() always returns true.
There after logout I have an instance of org.apache.catalina.session.StandardSessionFacade
Whats wrong with my code ?
I have reimplemented this approach with a following code(based on this topic http://forum.primefaces.org/viewtopic.php?f=3&t=33380):
I have added AjaxTimeoutPhaseListener phase listener:
public class AjaxTimeoutPhaseListener implements PhaseListener {
private static final long serialVersionUID = 2639152532235352192L;
public static Logger logger = LoggerFactory.getLogger(AjaxTimeoutPhaseListener.class);
#Override
public void afterPhase(PhaseEvent ev) {
}
#Override
public void beforePhase(PhaseEvent ev) {
FacesContext fc = FacesUtils.getContext();
RequestContext rc = RequestContext.getCurrentInstance();
HttpServletResponse response = FacesUtils.getResponse();
HttpServletRequest request = FacesUtils.getRequest();
if (FacesUtils.getExternalContext().getUserPrincipal() == null) {
if (FacesUtils.getExternalContext().isResponseCommitted()) {
// redirect is not possible
return;
}
try {
if (((rc != null && rc.isAjaxRequest())
|| (fc != null && fc.getPartialViewContext().isPartialRequest()))
&& fc.getResponseWriter() == null && fc.getRenderKit() == null) {
response.setCharacterEncoding(request.getCharacterEncoding());
RenderKitFactory factory = (RenderKitFactory) FactoryFinder
.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
RenderKit renderKit = factory.getRenderKit(fc,
fc.getApplication().getViewHandler().calculateRenderKitId(fc));
ResponseWriter responseWriter = renderKit.createResponseWriter(response.getWriter(), null,
request.getCharacterEncoding());
responseWriter = new PartialResponseWriter(responseWriter);
fc.setResponseWriter(responseWriter);
FacesUtils.redirect("/login.xhtml");
}
} catch (IOException ex) {
StringBuilder error = new StringBuilder("Redirect to the specified page '");
error.append("/login.xhtml");
error.append("' failed");
logger.error(error.toString(), ex);
throw new FacesException(ex);
}
} else {
return; // This is not a timeout case . Do nothing !
}
}
public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}
}
Also added FacesUtils class(extracted from OmniFaces lib):
public class FacesUtils {
public static Logger logger = LoggerFactory.getLogger(FacesUtils.class);
/**
* Returns the current faces context.
* <p>
* <i>Note that whenever you absolutely need this method to perform a general task, you might want to consider to
* submit a feature request to OmniFaces in order to add a new utility method which performs exactly this general
* task.</i>
* #return The current faces context.
* #see FacesContext#getCurrentInstance()
*/
public static FacesContext getContext() {
return FacesContext.getCurrentInstance();
}
/**
* Returns the HTTP servlet response.
* <p>
* <i>Note that whenever you absolutely need this method to perform a general task, you might want to consider to
* submit a feature request to OmniFaces in order to add a new utility method which performs exactly this general
* task.</i>
* #return The HTTP servlet response.
* #see ExternalContext#getResponse()
*/
public static HttpServletResponse getResponse() {
return getResponse(getContext());
}
/**
* {#inheritDoc}
* #see Faces#getResponse()
*/
public static HttpServletResponse getResponse(FacesContext context) {
return (HttpServletResponse) context.getExternalContext().getResponse();
}
/**
* Returns the HTTP servlet request.
* <p>
* <i>Note that whenever you absolutely need this method to perform a general task, you might want to consider to
* submit a feature request to OmniFaces in order to add a new utility method which performs exactly this general
* task.</i>
* #return The HTTP servlet request.
* #see ExternalContext#getRequest()
*/
public static HttpServletRequest getRequest() {
return getRequest(getContext());
}
/**
* {#inheritDoc}
* #see Faces#getRequest()
*/
public static HttpServletRequest getRequest(FacesContext context) {
return (HttpServletRequest) context.getExternalContext().getRequest();
}
/**
* Returns the current external context.
* <p>
* <i>Note that whenever you absolutely need this method to perform a general task, you might want to consider to
* submit a feature request to OmniFaces in order to add a new utility method which performs exactly this general
* task.</i>
* #return The current external context.
* #see FacesContext#getExternalContext()
*/
public static ExternalContext getExternalContext() {
return getContext().getExternalContext();
}
/**
* Returns the HTTP request context path. It's the webapp context name, with a leading slash. If the webapp runs
* on context root, then it returns an empty string.
* #return The HTTP request context path.
* #see ExternalContext#getRequestContextPath()
*/
public static String getRequestContextPath() {
return getRequestContextPath(getContext());
}
/**
* {#inheritDoc}
* #see Faces#getRequestContextPath()
*/
public static String getRequestContextPath(FacesContext context) {
return context.getExternalContext().getRequestContextPath();
}
/**
* Does a regular or ajax redirect.
*/
public static void redirect(String redirectPage) throws FacesException {
checkViewRoot(FacesUtils.getContext(), FacesUtils.getRequestContextPath());
FacesContext fc = FacesUtils.getContext();
ExternalContext ec = fc.getExternalContext();
try {
if (ec.isResponseCommitted()) {
// redirect is not possible
return;
}
// fix for renderer kit (Mojarra's and PrimeFaces's ajax redirect)
if ((RequestContext.getCurrentInstance().isAjaxRequest() || fc.getPartialViewContext().isPartialRequest())
&& fc.getResponseWriter() == null && fc.getRenderKit() == null) {
ServletResponse response = (ServletResponse) ec.getResponse();
ServletRequest request = (ServletRequest) ec.getRequest();
response.setCharacterEncoding(request.getCharacterEncoding());
RenderKitFactory factory = (RenderKitFactory) FactoryFinder
.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
RenderKit renderKit = factory.getRenderKit(fc,
fc.getApplication().getViewHandler().calculateRenderKitId(fc));
ResponseWriter responseWriter = renderKit.createResponseWriter(response.getWriter(), null,
request.getCharacterEncoding());
fc.setResponseWriter(responseWriter);
}
ec.redirect(ec.getRequestContextPath() + (redirectPage != null ? redirectPage : ""));
} catch (IOException e) {
logger.error("Redirect to the specified page '" + redirectPage + "' failed");
throw new FacesException(e);
}
}
public static void checkViewRoot(FacesContext ctx, String viewId) {
if (ctx.getViewRoot() == null) {
UIViewRoot viewRoot = ctx.getApplication().getViewHandler().createView(ctx, viewId);
if (viewRoot != null) {
ctx.setViewRoot(viewRoot);
}
}
}
}
also added following lines to faces-config.xml:
<lifecycle>
<phase-listener>com.domain.AjaxTimeoutPhaseListener</phase-listener>
</lifecycle>
Now everything works fine
I have implemented Global Exception handling in jsf 2.0 following the code example here:
http://www.openlogic.com/wazi/bid/259014/How-to-add-exception-handling-to-JSF-applications
I do not want the user to be navigated to the error page. I want the user to be on the same page and display a message.
So the code now looks like this:
public class CustomExceptionHandler extends ExceptionHandlerWrapper
{
private ExceptionHandler wrapped;
public CustomExceptionHandler(ExceptionHandler wrapped){
this.wrapped = wrapped;
}
#Override
public ExceptionHandler getWrapped(){
return wrapped;
}
#Override
public void handle() throws FacesException{
Iterator iterator = getUnhandledExceptionQueuedEvents().iterator();
while (iterator.hasNext()){
ExceptionQueuedEvent event = (ExceptionQueuedEvent)iterator.next();
ExceptionQueuedEventContext context = (ExceptionQueuedEventContext)event.getSource();
Throwable throwable = context.getException();
FacesContext fc = FacesContext.getCurrentInstance();
final ExternalContext externalContext = fc.getExternalContext();
final Map<String, Object> requestMap = fc.getExternalContext().getRequestMap();
final ConfigurableNavigationHandler nav = (ConfigurableNavigationHandler) fc.getApplication().getNavigationHandler();
//here you do what ever you want with exception
try {
fc.getExternalContext().setResponseStatus(500);
fc.addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR, "There is an error.", ""));
}
finally {
//remove it from queue
iterator.remove();
}
}
getWrapped().handle();
}
}
But with this code, neither the page is not navigated nor the message is displayed.
If I remove this line of code:
fc.getExternalContext().setResponseStatus(500);
Then the user is taken to the next page and the message is displayed on that page.
Any idea how I can show the error message on the same page and not navigate the user away ?
Thanks
I am trying to build a simple didactic websocket application using spring 4.0, jsf and glassfish 4.0.
I have created a maven web project (because this app has another web component(jsf)), and from this app i`m trying to setup some websockets.
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(echoHandler(), "/echo");
}
#Bean
public WebSocketHandler echoHandler() {
return new EchoHandler();
}
}
and
public class EchoHandler extends TextWebSocketHandler {
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
session.sendMessage(message);
}
}
and on the client side a very simple connect:
<script>
/* <![CDATA[ */
var endPointURL = "ws://localhost:8080/liveasterisk/echo";
var chatClient = null;
function connect () {
chatClient = new WebSocket(endPointURL);
chatClient.onmessage = function (event) {
alert(event);
};
}
function disconnect () {
chatClient.close();
}
function sendMessage() {
chatClient.send("xxx");
}
connect();
/* ]]> */
</script>
The problem is that when the connect() method fires i get a 404 response.
I guess that i have to somehow train jsf to respond to handshake request.
All my *.xhtml are mapped to jsf servlet.
So what I`m I missing here ?
I have solved the problem like this:
#ServerEndpoint(value = "/keepalive", configurator = SpringConfigurator.class)
public class KeepAliveEndpoint {
private static Logger logger = Logger.getLogger(KeepAliveEndpoint.class);
#Autowired
private KeepAliveService keepAliveService;
private List<Session> sessions = new ArrayList<Session>();
#OnOpen
public void onOpen(Session session) {
sessions.add(session);
System.out.println("onOpen: " + session.getId()+" list size: " + sessions.size());
}
#OnClose
public void onClose(Session session) {
System.out.println("onClose: " + session.getId());
sessions.remove(session);
}
#OnMessage
public void handleMessage(Session session, String message) {
try{
Long userId = Long.parseLong(message);
keepAliveService.keepAlive(userId);
}catch(NumberFormatException nfe){
try {
session.getBasicRemote().sendText("Cannot perform live update for your status");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
so now I have a sockets exposed via jsf and I can inject "services" with #Autowired in this endpoint.
And with this js code:
<script type="text/javascript">
var host = "ws://localhost:8080/myApp/keepalive";
var wSocket = new WebSocket(host);
var browserSupport = ("WebSocket" in window) ? true : false;
// called body onLoad()
function initializeReception() {
if (browserSupport) {
wSocket.onopen = function(){
setInterval(function(){wSocket.send('<h:outputText value="#{managedBean.userDTO.id}" />')}, 300000);
};
// called when a message is received
wSocket.onmessage = function(event) {
alert(event.data);
};
// on error handler
wSocket.onError = function(event) {
alert('An error has occured '+event.data+'.');
};
// called when socket closes
wSocket.onclose = function(){
// websocket is closed.
//alert("Connection is closed...");
};
}
else {
// The browser doesn't support WebSocket
alert("WebSocket is NOT supported by your Browser!");
}
}
initializeReception();
</script>
The above configuration is for use with Spring MVC's DispatcherServlet. Do you have one configured in the web application? Depending on the servlet mapping (not shown above) you'll most likely need one more part added to the URL to match the servlet mapping.
The longer explanation is that #EnableWebSocket creates a HandlerMapping that maps "/echo" to the WebSocketHandler. That HandlerMapping needs to reside in the configuration of the DispatcherServlet in order for the HTTP handshake to be processed.
Is there any scope like JSF #ViewScoped in Spring 3.0? I have an application using JSF+Spring where backing beans are managed by Spring. I didn't find any scope like JSF wiew scope in Spring. I saw the blog Porting JSF 2.0’s ViewScope to Spring 3.0, but it didn't work for me.
Here's my attempt on the custom Spring scope:
import java.util.Map;
import javax.faces.context.FacesContext;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
/**
* Implements the JSF View Scope for use by Spring. This class is registered as a Spring bean with the CustomScopeConfigurer.
*/
public class ViewScope implements Scope {
public Object get(String name, ObjectFactory<?> objectFactory) {
System.out.println("**************************************************");
System.out.println("-------------------- Getting objects For View Scope ----------");
System.out.println("**************************************************");
if (FacesContext.getCurrentInstance().getViewRoot() != null) {
Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
if (viewMap.containsKey(name)) {
return viewMap.get(name);
} else {
Object object = objectFactory.getObject();
viewMap.put(name, object);
return object;
}
} else {
return null;
}
}
public Object remove(String name) {
System.out.println("**************************************************");
System.out.println("-------------------- View Scope object Removed ----------");
System.out.println("**************************************************");
if (FacesContext.getCurrentInstance().getViewRoot() != null) {
return FacesContext.getCurrentInstance().getViewRoot().getViewMap().remove(name);
} else {
return null;
}
}
public void registerDestructionCallback(String name, Runnable callback) {
// Do nothing
}
public Object resolveContextualObject(String key) { return null;
}
public String getConversationId() {
return null;
}
}
application-context.xml:
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="view">
<bean class="com.delta.beans.ViewScope"/>
</entry>
</map>
</property>
</bean>
Recently I've created maven artifact which will solve this problem.
See my github javaplugs/spring-jsf repository.
I did something like this without Porting bean to Spring. It's working for me.
#ManagedBean(name="bean")
#ViewScoped // actual jsf viewscoped only with javax.faces.viewscoped import
public class Bean implements
Serializable {
#ManagedProperty(value="#{appService}") // Spring Manged Bean and singleton
private transient AppService appService;
// Getting AppService Object which is singleton in the application during deserialization
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
stream.defaultReadObject();
FacesContext context = FacesContext.getCurrentInstance();
appService = (AppService)context.getApplication()
.evaluateExpressionGet(context, "#{appService}", AppService.class);
}
}
public class ViewScopeCallbackRegistrer implements ViewMapListener {
#SuppressWarnings("unchecked")
#Override
public void processEvent(SystemEvent event) throws AbortProcessingException {
if (event instanceof PostConstructViewMapEvent) {
PostConstructViewMapEvent viewMapEvent = (PostConstructViewMapEvent) event;
UIViewRoot viewRoot = (UIViewRoot) viewMapEvent.getComponent();
viewRoot.getViewMap().put(
ViewScope.VIEW_SCOPE_CALLBACKS,
new HashMap<String, Runnable>()
);
} else if (event instanceof PreDestroyViewMapEvent) {
PreDestroyViewMapEvent viewMapEvent = (PreDestroyViewMapEvent) event;
UIViewRoot viewRoot = (UIViewRoot) viewMapEvent.getComponent();
Map<String, Runnable> callbacks = (Map<String, Runnable>) viewRoot
.getViewMap().get(ViewScope.VIEW_SCOPE_CALLBACKS);
if (callbacks != null) {
for (Runnable c : callbacks.values()) {
c.run();
}
callbacks.clear();
}
}
}
#Override
public boolean isListenerForSource(Object source) {
return source instanceof UIViewRoot;
}
}
public class ViewScope implements Scope {
public static final String VIEW_SCOPE_CALLBACKS = "viewScope.callbacks";
#Override
public synchronized Object get(String name, ObjectFactory<?> objectFactory) {
Object instance = this.getViewMap().get(name);
if(instance == null){
instance = objectFactory.getObject();
this.getViewMap().put(name, instance);
}
return instance;
}
#SuppressWarnings("unchecked")
#Override
public Object remove(String name) {
Object instance = this.getViewMap().remove(name);
if(instance == null){
Map<String, Runnable> callbacks = (Map<String, Runnable>) this.getViewMap().get(VIEW_SCOPE_CALLBACKS);
if(callbacks != null)
callbacks.remove(name);
}
return instance;
}
/**
* Responsável por registrar uma chamada de destruição ao bean
* que será armazenadano [b]viewMap[/b] da [b]ViewRoot[/b](nossa página que será mostrada)
* #see #getViewMap()
* #param name - nome do bean
* #param runnable
*/
#SuppressWarnings("unchecked")
#Override
public void registerDestructionCallback(String name, Runnable runnable) {
Map<String, Runnable> callbacks = (Map<String, Runnable>) this.getViewMap().get(VIEW_SCOPE_CALLBACKS);
if(callbacks != null)
callbacks.put(name, runnable);
}
#Override
public Object resolveContextualObject(String key) {
FacesContext facesContext = FacesContext.getCurrentInstance();
FacesRequestAttributes facesResquestAttributes = new FacesRequestAttributes(facesContext);
return facesResquestAttributes.resolveReference(key);
}
#Override
public String getConversationId() {
FacesContext facesContext = FacesContext.getCurrentInstance();
FacesRequestAttributes facesResquestAttributes = new FacesRequestAttributes(facesContext);
return facesResquestAttributes.getSessionId() + "-" + facesContext.getViewRoot().getViewId();
}
private Map<String, Object> getViewMap(){
return FacesContext.getCurrentInstance().getViewRoot().getViewMap();
}
}
I have tried a work around for the Jsf view bean memory leak issue for both Jsf 2.1 & Jsf 2.2. Try the code in following link Memory leak with ViewScoped bean?. It will clear the view bean in session while navigating to next page.