I am beginner in Spring framework.
In my case Session can be expire by following way
--> Success Log-out (Explicit Log-out )
--> Session Timeout (Implicit Log-out )
I have do DML(record insertion) in database whenever some user log in and I want to perform DML(record deletion) in database whenever user session timeout (Implicit Log-out).
My Question is that is there any way in Spring that tell us before the expiry of session.
So I can do perform my custom event before session expiry.
Thanks in advance
Yes, you can do that with SessionDestroyedEvent.
#Component
public class SessionEndedListener implements ApplicationListener<SessionDestroyedEvent> {
#Override
public void onApplicationEvent(SessionDestroyedEvent event)
{
for (SecurityContext securityContext : event.getSecurityContexts())
{
Authentication authentication = securityContext.getAuthentication();
YourPrincipalClass user = (YourPrincipalClass) authentication.getPrincipal();
// do something
}
}
}
And in web.xml:
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
This event will be fired for both the regular logout as well as the session timeout.
I have solved my problem by following way similar #Codo answer
#Component
public class SessionCreatedListenerService implements ApplicationListener<ApplicationEvent> {
private static final Logger logger = LoggerFactory
.getLogger(SessionCreatedListenerService.class);
#Autowired
HttpSession httpSession;
#Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
if(applicationEvent instanceof HttpSessionCreatedEvent){ //If event is a session created event
}else if(applicationEvent instanceof HttpSessionDestroyedEvent){ //If event is a session destroy event
// handler.expireCart();
logger.debug(""+(Long)httpSession.getAttribute("userId"));
logger.debug(" Session is destory :" ); //log data
}else if(applicationEvent instanceof AuthenticationSuccessEvent){ //If event is a session destroy event
logger.debug(" athentication is success :" ); //log data
}else{
/*logger.debug(" unknown event occur : " Source: " + ); //log data
}
}
}
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.LogoutSuccessEvent;
import org.springframework.stereotype.Component;
#Component
public class LogoutSuccessListener implements ApplicationListener<LogoutSuccessEvent>{
#Override
public void onApplicationEvent(LogoutSuccessEvent evt) {
String login = evt.getAuthentication().getName();
System.out.println(login + " has just logged out");
}
}
Related
I have the following setup:
#Component
public class Scheduler {
Logger logger = LoggerFactory.getLogger(this.getClass());
#Autowired
BatchService batchService;
#Scheduled(cron = "0 */1 * ? * *")
void tick() {
logger.info("Beginning of a batch tick");
batchService.refundNotAssignedVisits();
logger.info("End of the batch tick");
}
}
With BatchService containing the following:
#Service
public class BatchServiceImpl implements BatchService {
Logger logger = LoggerFactory.getLogger(this.getClass());
#Autowired
VisitService visitService;
#Override
#Transactional
public void refundNotAssignedVisits() {
logger.info("Start automatic refund of past visits being assigned");
Set<Visit> visits = visitService.findRefundableVisits();
if(visits != null && visits.size() != 0) {
logger.info("Found " + visits.size() + " visits to refund with IDs: " + visits.stream().map(x -> x.getId().toString()).collect(Collectors.joining(", ")));
visits.forEach(x -> {
logger.info("Refunding visit with ID: " + x.getId());
try {
visitService.cancel(x);
logger.info("Visit successfully refunded!");
}
catch(Exception e) {
logger.error("Error while refunding visit...", e);
}
});
}
else {
logger.info("Found no visit to refund.");
}
logger.info("End of automatic refund");
}
}
And the cancel method defined like this:
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public Visit cancel(Visit visit) throws Exception {
// Some business logic
}
I need the cancel method to have one transaction per call, for business purposes, and at the moment, the refundNotAssignedVisits is #Transactional in order to enable a Hibernate session so I am able to use lazy loading with related entities in the cancel method.
This causes problems such as duplicate commits and I'm wondering what's a good pattern to achieve what I want: have a #Scheduled method that enables a Hibernate session in order to make multiple calls to another method with one transaction per call.
#Transactional 's REQUIRES_NEW will create another new Hibernate session , so the session inside cancel() will be different from the session that is used to load the entities which seems like awkward to me. Normally , we use the same session for loading and managing the same entity within a transaction.
I will refactor the codes into the followings :
VisitService:
//Cannel by visitorId and load the Visitor by Id in a new transaction
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public Visit cancel(Integer visitorId) throws Exception {
Visit visit= session.get(Visit.class , visitorId);
cancel(visit);
}
#Override
public Visit cancel(Visit visit) throws Exception {
// Some business logic
}
//Add method to return the IDs only
#Transactional(readOnly=true)
public Set<Integer> findRefundableVisitId(){
}
BatchService:
//#Transactional (Do not require anymore)
public void refundNotAssignedVisits() {
logger.info("Start automatic refund of past visits being assigned");
Set<Integer> refundVisitIds = visitService.findRefundableVisitId();
refundVisitIds.forEach( id-> {
try {
visitService.refund(id);
logger.info("Visit successfully refunded!");
}
catch(Exception e) {
logger.error("Error while refunding visit...", e);
}
});
}
In this way , each refund is executed in their own transaction and the transaction that is used to load the refund visitors do not need to wait for all refund complete in order to commit and no more "duplicate commits".
My original HttpSessionListener code:
public class SessionListener implements HttpSessionListener {
#Override
public void sessionDestroyed(HttpSessionEvent event) {
final Object user = event.getSession().getAttribute("user");
if (user != null && user insteaceof User) {
UserUtils.deleteUser((User) user);
}
}
}
and my web.xml
<filter>
<filter-name>ObjectifyFilter</filter-name>
<filter-class>com.googlecode.objectify.ObjectifyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ObjectifyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
When a session timeout event happens it throws:
WARNING: Problem scavenging sessions
java.lang.IllegalStateException: You have not started an Objectify context. You are probably missing the ObjectifyFilter. If you are not running in the context of an http request, see the ObjectifyService.run() method.
at com.googlecode.objectify.ObjectifyService.ofy(ObjectifyService.java:44)
at com.learnkeeper.server.OfyService.ofy(OfyService.java:61)
at com.learnkeeper.server.UserUtils.deleteUser(UserUtils.java:28)
at com.learnkeeper.server.SessionListener.sessionDestroyed(SessionListener.java:36)
at org.mortbay.jetty.servlet.AbstractSessionManager.removeSession(AbstractSessionManager.java:669)
at org.mortbay.jetty.servlet.AbstractSessionManager$Session.timeout(AbstractSessionManager.java:926)
at org.mortbay.jetty.servlet.HashSessionManager.scavenge(HashSessionManager.java:285)
at org.mortbay.jetty.servlet.HashSessionManager.access$000(HashSessionManager.java:44)
at org.mortbay.jetty.servlet.HashSessionManager$2.run(HashSessionManager.java:219)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
I tried that (from this post How to resolve "You have not started an Objectify context" in JUnit?):
public class SessionListener implements HttpSessionListener {
private Closeable closeable;
#Override
public void sessionDestroyed(HttpSessionEvent event) {
final Object user = event.getSession().getAttribute("user");
if (user != null && user instanceof User) {
closeable = OfyService.begin();
UserUtils.deleteUser((User) user);
closeable.close();
}
}
}
And here is my OfyService class:
class OfyService {
static {
// Register all my Entities classes
ObjectifyService.register(User.class);
...
}
public static Closeable begin() {
return ObjectifyService.begin();
}
public static ObjectifyFactory factory() {
return ObjectifyService.factory();
}
public static Objectify ofy() {
return ObjectifyService.ofy();
}
}
but same stacktrace :(
So what did I miss?
thx
EDIT
to follow-up with #stickfigure
So I cleaned my project and re-run my use case and I get this stacktrace now:
WARNING: Problem scavenging sessions
java.lang.NullPointerException: No API environment is registered for this thread.
at com.google.appengine.api.datastore.DatastoreApiHelper.getCurrentAppId(DatastoreApiHelper.java:132)
at com.google.appengine.api.datastore.DatastoreApiHelper.getCurrentAppIdNamespace(DatastoreApiHelper.java:148)
at com.google.appengine.api.datastore.Key.(Key.java:96)
at com.google.appengine.api.datastore.Key.(Key.java:78)
at com.google.appengine.api.datastore.KeyFactory.createKey(KeyFactory.java:54)
at com.google.appengine.api.datastore.KeyFactory.createKey(KeyFactory.java:47)
at com.googlecode.objectify.util.DatastoreUtils.createKey(DatastoreUtils.java:86)
at com.googlecode.objectify.impl.KeyMetadata.getRawKey(KeyMetadata.java:187)
at com.googlecode.objectify.impl.Keys.rawKeyOf(Keys.java:36)
at com.googlecode.objectify.impl.Keys.keyOf(Keys.java:29)
at com.googlecode.objectify.impl.LoaderImpl.entity(LoaderImpl.java:121)
at com.learnkeeper.server.UserUtils.deleteUser(UserUtils.java:28)
at com.learnkeeper.server.SessionListener.sessionDestroyed(SessionListener.java:40)
at org.mortbay.jetty.servlet.AbstractSessionManager.removeSession(AbstractSessionManager.java:669)
at org.mortbay.jetty.servlet.AbstractSessionManager$Session.timeout(AbstractSessionManager.java:926)
at org.mortbay.jetty.servlet.HashSessionManager.scavenge(HashSessionManager.java:285)
at org.mortbay.jetty.servlet.HashSessionManager.access$000(HashSessionManager.java:44)
at org.mortbay.jetty.servlet.HashSessionManager$2.run(HashSessionManager.java:219)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
I don't see any reason why that code should fail, although it can be written more elegantly:
public class SessionListener implements HttpSessionListener {
#Override
public void sessionDestroyed(HttpSessionEvent event) {
final Object user = event.getSession().getAttribute("user");
if (user != null && user instanceof User) {
try (Closable closable = OfyService.begin()) {
UserUtils.deleteUser((User) user);
}
}
}
I have many variations of this code in my applications and there are examples in the test cases - actually, all of Objectify's test cases rely on this pattern.
I would like to see the exact stacktrace generated when you run this code. It should be quite impossible to get that stacktrace if you have called begin() properly. You can look at the code in ObjectifyService.ofy() - it is quite simple. Doublecheck that the code you have deployed is the code you think you have deployed.
UPDATE: The new stacktrace is quite different, and indicates that GAE is not set up to perform API calls from that listener callback. It has nothing to do with Objectify; this is now a question for Google. I suggest writing a new question that focuses on that aspect and tagging it with GAE-related tags.
That said, my general advice is to avoid relying on this callback. Aside from technical issues like this, I would not trust it to be executed consistently in a distributed environment like GAE. If you want to expire an object, put a datestamp on it and cull anything older than a week (or whatever is reasonable).
Google App Engine Doesn't support session listeners. Session listeners may get invoke in local, but No API environment is registered for this thread. In production listeners will not even invoke.
Source #Ramesh V
https://stackoverflow.com/a/11152125/421563
Anyway thx #stickfigure
I am trying to server a particular error page when session timeouts to my users.
For this i configured the error page on my Application's init method.
But this thing is not working.
I set up the session tiemout in 1 minute, after that nothing happen, I went through the logs, but wicket didn't throw any PageExpiredException.
When session timeouts wicket simply logs it as:
Session unbound: C061F4F21C41EDF13C66795DAC9EDD02
Removing data for pages in session with id 'C061F4F21C41EDF13C66795DAC9EDD02'
this is my init method in my customApplication
protected void init() {
super.init();
this.getApplicationSettings().setPageExpiredErrorPage(SessionExpiredPage.class);
...
...
}
my SessionExpiredPage.class
public class SessionExpiredPage extends TecnoAccionPage {
public SessionExpiredPage() {
this.setOutputMarkupId(true);
this.add(new Label("title", "SesiĆ³n Expirada"));
CSSLoader.get().appendCssUntil(this, SessionExpiredPage.class);
}
}
And i have a custom implementation of AbstractRequestCycleListener i override the OnException method But, when my session expire, I never pass in the "onException".
Thank You, best regards.
For some reason there is no PageExpiredException thrown by wicket, while it can reconstruct requested page, even if the session was expired.
So, there is another way to deal with this problem.
You have to override onRequestHandlerResolved method in your AbstractRequestCycleListener, to catch all incoming requests, and check there if incoming session id is outdated.
To check this, you must have list of the expired sessions in your app and catch unbound event to manage them.
This is going to be something like that:
public class YourApp extends WebApplication {
//synchronized list with ids
private List<String> unboundSessions = new CopyOnWriteArrayList<String>();
#Override
protected void init() {
super.init();
this.getApplicationSettings().setPageExpiredErrorPage(SessionExpiredPage.class);
//add request listener
getRequestCycleListeners().add(new AbstractRequestCycleListener() {
public void onRequestHandlerResolved(RequestCycle cycle, IRequestHandler handler) {
if (handler instanceof IPageRequestHandler) {
HttpServletRequest request = (HttpServletRequest) cycle.getRequest().getContainerRequest();
String sessionId = request.getRequestedSessionId();
//check whether the requested session has expired
boolean expired = sessionId != null && !request.isRequestedSessionIdValid();
//if session is not valid and it was really expired
if (expired && unboundSessions.contains(sessionId)) {
//then remove it from unbound list
unboundSessions.remove(sessionId);
//and throw exception
throw new PageExpiredException("Expired");
}
}
super.onRequestHandlerResolved(cycle, handler);
}
});
...
}
//this method called when any session is invalidated, so check your manual invalidating calls (if you ever do them)
#Override
public void sessionUnbound(String sessionId) {
super.sessionUnbound(sessionId);
if (!unboundSessions.contains(sessionId)) {
unboundSessions.add(sessionId);
}
}
}
Unbound sessions list needs for us to know, that user's session is really expired, since the expired variable in our listener could be also true when user just openes our site after redeploy, for example. His session is taken from his cookies and it could be already expired, but that would be weird to redirect him to SessionExpiredPage immediately.
It looks like a workaround, but it should work.
I want to create a session-scoped bean to monitor activations and passivations of HTTP sessions. The bean is very simple:
package my.log;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionEvent;
import org.apache.log4j.Logger;
public class SessionLoggingListenerBean implements HttpSessionActivationListener {
private final Logger LOG = Logger.getLogger(this.getClass());
public SessionLoggingListenerBean() {
LOG.info("SessionLoggingListenerBean starting");
}
public void init() {
LOG.info("SessionLoggingListenerBean init");
}
public void sessionDidActivate(HttpSessionEvent event) {
LOG.info("Session " + event.getSession().getId() + " activated");
}
public void sessionWillPassivate(HttpSessionEvent event) {
LOG.info("Session " + event.getSession().getId() + " will passivate");
}
}
Bean definition in application context:
<bean id="sessionLoggingListenerBean" class="my.log.SessionLoggingListenerBean" scope="session" init-method="init" lazy-init="false"/>
With this configuration there is no logs from this class, even from the constructor or init() method. Apparently, Spring does not create this bean.
By trial and error I checked that Spring instantiates such a bean when it is needed by another bean, e.g. used by UI. Is there any other (better) way? Is it a bug in Spring?
Spring version used: 2.0.8.
HttpSessionActivationListener is part of the javax.servlet.http package. That should give you a hint that it should be managed by the Servlet container. In your case, you aren't registering the Listener with your ServletContext, neither through the web.xml or a SerlvetContainerInitializer.
Through web.xml you wouldn't be able to make it both a Spring and Servlet container managed object so instead these workarounds exist, first, second.
If you are using a WebApplicationInitializer, you can instantiate your AnnotationConfigWebApplicationContext, have the SessionLoggingListenerBean bean created, retrieve it and use it with
SessionLoggingListenerBean yourBean = context.getBean(SessionLoggingListenerBean.class);
servletContext.addListener(yourBean);
After some experimenting I think it is better not to use Spring for this purpose. I've modified the class to implement also HttpSessionListener and Serializable:
public class SessionLoggingListener implements HttpSessionListener,
HttpSessionActivationListener, Serializable {
private static final long serialVersionUID = -763785365219658010L;
private static final Logger LOG = Logger.getLogger(SessionLoggingListener.class);
public SessionLoggingListener() {
LOG.info("SessionLoggingListener created");
}
public void sessionDidActivate(HttpSessionEvent event) {
LOG.info("Session " + event.getSession().getId() + " activated");
}
public void sessionWillPassivate(HttpSessionEvent event) {
LOG.info("Session " + event.getSession().getId() + " will passivate");
}
public void sessionCreated(HttpSessionEvent event) {
final HttpSession session = event.getSession();
LOG.info("Session " + session.getId() + " created. MaxInactiveInterval: " + session.getMaxInactiveInterval() + " s");
session.setAttribute(this.getClass().getName(), this);
}
public void sessionDestroyed(HttpSessionEvent event) {
LOG.info("Session " + event.getSession().getId() + " destroyed");
event.getSession().removeAttribute(this.getClass().getName());
}
}
and added it to web.xml:
<listener>
<listener-class>
evo.log.SessionLoggingListener
</listener-class>
</listener>
From now on, whenever a new session is created, the listener binds to it (session.setAttribute(...)). This is necessary to make the container notice the listener about session activation or passivation.
In case of Spring and sessions - according to this forum thread Spring does not load session beans until they are requested:
Session bean is treated as a special form of "prototype". That means it will follow prototype symantics in creating an instance.
For me this is an unintuitive and not well documented behavior.
In cases where the user has closed the browser and walked away, I want to log when the session times out. For example as a system induced log out as opposed to user request (I already have working and tested code to log a user requested logout).
Since the user isn't actively submitting requests (especially if it is just a matter of the now unused session timing out on the server) I don't think a filter is possible. Can this be done with a phase listener? If so can you provide some insight or a skeleton, or at least point me in the right direction on how this might be done.
My understanding is that the session on the server is still active until it times out or some other mechanism invalidates it. I am assuming therefore that a phase listener will also be able to tell if as part of your login method, you kill any existing session prior to the user logging in again with a fresh view, other machine, etc.
I am OK with research, but would like to at least start while pointed in the right direction. :)
On a second note: Is it possible to differentiate between a session time out and a view expired?
Thought I'd post the solution I ended up with based on the suggestions:
public class ElsSoulardSessionListener implements HttpSessionListener {
#EJB
private SessionLogger sessionLogger = new SessionLogger();
private SessionLogDTO sessionData = new SessionLogDTO();
private ElsDateTimeFunctions ts = new ElsDateTimeFunctions();
private static final Logger logger = Logger.getLogger(ElsSoulardSessionListener.class.getSimpleName());
#Override
public void sessionCreated(HttpSessionEvent se) {
// Nothing to do yet
}
#Override
public void sessionDestroyed(HttpSessionEvent se) {
logger.log(Level.FINE, "SESSION DESTROYED LISTENER");
HttpSession session = se.getSession();
finalizeUserSessionLog(session);
}
/**
* Utility method to finalize user's session log entry. Returns
* early if the session log isn't found or more than one is returned.
* #param session
*/
private void finalizeUserSessionLog(HttpSession session) {
String sessionId = session.getId();
LogoutReasonType logoutReason = (LogoutReasonType) session.getAttribute("logoutreason");
if (logoutReason == null) {
logoutReason = LogoutReasonType.SESSION_TIMEOUT;
}
try {
sessionData = sessionLogger.findBySessionId(sessionId);
} catch (NonexistentEntityException | UnexpectedResultSetSizeException ex) {
logger.log(Level.WARNING, " sessionDestroyed ", ex);
return;
}
Calendar now = ts.getUtcDateTimeAsCalendar();
sessionData.setLogoutTimestamp(now);
sessionData.setLogoutReason(logoutReason);
sessionLogger.update(sessionData);
}
}
If this helps you...
In our application we have extended HttpSessionListener and used sessionDestroyed method to log the event of session timeout.
and registered the same in web.xml as
<listener>
<listener-class>
com.ourpackage.OurHttpSessionListener
</listener-class>
</listener>
I believe which you only catch events of the servlet container through ServletFilters. PhaseListener only exist inside JSF "sessions", after servlet requests. Check the JSF life cycle to make sure. After, you can create another request for invalidate the "session" in JSF