Here is the code of my servlet.
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// TODO Auto-generated method stub
Map m=request.getParameterMap();
Set s = m.entrySet();
Iterator it = s.iterator();
int index=0;
while (it.hasNext()) {
Map.Entry<String,String[]> entry = (Map.Entry<String,String[]>) it.next();
String key = entry.getKey();
String[] value = entry.getValue();
System.out.println("Value is " + value[0].toString());
switch(key) {
case "RegId":
RegId = value[0].toString();
break;
case "isTrackingRequested":
isTrackingRequested = Boolean.valueOf(value[0]);
break;
}
}
// Create a session object if it is already not created.
HttpSession session = request.getSession(true);
if (session.isNew()) {
session.setAttribute("id",isTrackingRequested);
}
ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
ses.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
boolean isTrackingRequestednew = (boolean) session.getAttribute("id");
// code to run
if (isTrackingRequestednew) {
try {
System.out.println("===========================================================================");
System.out.println("new track status is " + isTrackingRequestednew);
System.out.println("===========================================================================");
} catch (Exception e) {
}
} else {
ses.shutdown();
}
}
}, 0, 1, TimeUnit.SECONDS);
}
I am trying to track a vehicle by using a ScheduledExcecutorService. I am using a flag isTrackingRequested to check if user has requested the tracking. So I am saving the values in the session, but whenever I request for the tracking to stop, the previously set session attribute shows null value.
In short, I am trying to access a previously set session variable, but I end up getting null. No solution I have tried seems to work.
whenever I request for the tracking to stop, the previously set session attribute shows null value.
A reasonably likely explanation is that the requests are not in the same session. Maintaining a session requires cooperation from the client, which is not guaranteed to be given. The most common mechanisms for associating requests with sessions are cookies and URL rewriting. If the client refuses cookies and is making its requests to a static URL then every request will likely be in its own session.
That's one of the lesser of your problems, however. You also have these:
On every POST request, you create a new ScheduledExecutorService and a new task for it to manage. Surely that's not what you intended.
Added: You do not update existing sessions with the tracking state carried by requests belonging to those sessions. Only if the session is newly created for the request being serviced do you set the session attribute.
Moreover, when last I studied the JavaEE specs (a version ago) JavaEE components such as servlets were not permitted to start their own threads, but yours does -- many of them -- within all the ScheduledExecutorServices. That doesn't mean starting a new thread (or creating a ScheduledExecutorService) will necessarily fail, but your violation of the specs does mean that you cannot rely on the JavaEE APIs to behave as documented.
Moreover, your code is not properly synchronized. You access shared state (the Session) without proper synchronization.
Furthermore, you appear to have no mechanism to shut down tracking when a session expires or is manually terminated.
To do this properly, the tracking should be performed in a separate service running outside the servlet container. Alternatively, you could hack it together with only the scheduler itself running outside the container, and all the tracked state living inside. The scheduler would then need only to serve as a clock by sending a periodic request to a different servlet in the same container.
You would do well to decouple your task from the session. Instead of having it get the tracking state from the session, give it a member variable for that, and store a reference to the task itself in the session. Modify the task's state directly in response to requests, instead of passing that information indirectly via the session. And make sure all accesses to that object's shared state are properly synchronized!
Added: Furthermore, I suggest that you make the task implement HttpSessionBindingListener, so that when it is unbound from the session -- either manually or as a result of the session reaching the end of its life -- it can cancel itself.
Added: Additionally, note that modern JavaEE requires the container to make a ScheduledExecutorService available to enterprise components. You should be able to obtain a reference to it via the JNDI name java:comp/DefaultManagedScheduledExecutorService (see section EE.5.21 in the Java EE 7 platform specification). It would be wise to use this container-provided service instead of attempting to set up your own.
There are a few bugs in the code you provided. Value of isTrackingRequested should be checked every time as requester may send a value false to stop tracking. Also null value of isTrackingRequested should be taken into account. If it is null then it probably means user wants to carry on as with the previous decision.
These have been corrected in the code below, this should be working now.
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
String RegId = String.valueOf(request.getParameter("RegId"));
// Create a session object if it is already not created.
final HttpSession session = request.getSession(true);
String trackingRequestParam = request.getParameter("isTrackingRequested");
boolean isTrackingRequested = false;
if(trackingRequestParam != null) {
isTrackingRequested = Boolean.valueOf(trackingRequestParam);
session.setAttribute("id", isTrackingRequested);
}
if(trackingRequestParam != null && isTrackingRequested) {
final ScheduledExecutorService ses = Executors
.newSingleThreadScheduledExecutor();
session.setAttribute("isRunning", true);
ses.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
boolean isTrackingRequestednew = Boolean.valueOf(String.valueOf(session.getAttribute("id") ));
System.out.println("inside repeater : " + session.getAttribute("id") + " : " + isTrackingRequestednew);
// code to run
if (isTrackingRequestednew) {
try {
System.out.println("===========================================================================");
System.out.println("new track status is " + isTrackingRequestednew);
System.out.println("===========================================================================");
} catch (Exception e) {
}
} else {
ses.shutdown();
}
}
}, 0, 1, TimeUnit.SECONDS);
}
}
Edits *****
I'm adding entire TestServlet code that I used here. I have converted POST method to GET method for ease of testing.
TestServlet
package test;
import java.io.IOException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
#WebServlet("/TestServlet")
public class TestServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
Boolean isLogout = Boolean.valueOf(String.valueOf(request.getParameter("logout")));
String RegId = String.valueOf(request.getParameter("RegId"));
// Create a session object if it is already not created.
final HttpSession session = request.getSession(true);
String trackingRequestParam = request.getParameter("isTrackingRequested");
boolean isTrackingRequested = false;
if(trackingRequestParam != null) {
isTrackingRequested = Boolean.valueOf(trackingRequestParam);
session.setAttribute("id", isTrackingRequested);
}
if(isLogout) {
session.invalidate();
}
if(trackingRequestParam != null && isTrackingRequested) {
final ScheduledExecutorService ses = Executors
.newSingleThreadScheduledExecutor();
session.setAttribute("isRunning", true);
ses.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
try {
boolean isTrackingRequestednew = Boolean.valueOf(String.valueOf(session.getAttribute("id") ));
System.out.println("inside repeater : " + session.getAttribute("id") + " : " + isTrackingRequestednew);
// code to run
if (isTrackingRequestednew) {
try {
System.out.println("===========================================================================");
System.out.println("new track status is " + isTrackingRequestednew);
System.out.println("===========================================================================");
} catch (Exception e) {
}
} else {
ses.shutdown();
}
} catch (Exception ex) {
ex.printStackTrace();
ses.shutdown();
}
}
}, 0, 1, TimeUnit.SECONDS);
}
}
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
}
}
Entire TestServlet
Testing
Hit below URLs (TestWeb was context name in my case, replace it with yours)
http://localhost:8080/TestWeb/TestServlet?isTrackingRequested=true<br>
//then after a few seconds<br>
http://localhost:8080/TestWeb/TestServlet?isTrackingRequested=false
Output (Test was done on Tomcat 7.0.59)
inside repeater : true : true
===========================================================================
new track status is true
===========================================================================
inside repeater : true : true
===========================================================================
new track status is true
===========================================================================
inside repeater : true : true
===========================================================================
new track status is true
===========================================================================
inside repeater : true : true
===========================================================================
new track status is true
===========================================================================
inside repeater : true : true
===========================================================================
new track status is true
===========================================================================
inside repeater : true : true
===========================================================================
new track status is true
===========================================================================
inside repeater : false : false
After tracking was set to false, it stops printing as executor was shutdown.
Note: Clear your cookies to start a new session.
Edit2 ***
Added code to support manual logout call URL below to logout.
http://localhost:8080/TestWeb/TestServlet?logout=true
Related
This question already has answers here:
How do servlets work? Instantiation, sessions, shared variables and multithreading
(8 answers)
Closed 6 years ago.
I created a small web application using jsp and servlet. My ajax post method call the java class for every three seconds. I want to know for every 3 secs, java class variables isBootRunning,istest1Running,istest1Running is initialized to "null" or not.
If it will initialized for every request, how to prevent this initialization.
My JSP:
setInterval(function(){
TestReport();
}, 3000);
function TestReport(){
var tbname = $("#tbname").attr('class');
var userName = $("#userName").attr('class');
var name = tbname;
var url ="TestReport";
var params = {
tbname: tbname,
userName:userName
};
$.post('TestReport', {
tbname: tbname,
userName:userName,
}, function(responseText) {
alert(responseText);
});
}
My Servlet:
public class TestReport extends HttpServlet {
private static final long serialVersionUID = 1L;
String isBootRunning = null;
String istest1Running = null;
String istest2Running = null;
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
File f1 = new File("myfirstpath");//this directory is visible for 10 mins only
File f2 = new File("mythirdpath");//this directory is visible for 10 mins only
File f3 = new File("mythirdpath");//this directory is visible for 10 mins only
if (f1.exists() && f1.isDirectory()) {
isBootRunning = "Running";
istest1Running = "Scheduled";
istest2Running = "Scheduled";
} else if(f2.exists() && f2.isDirectory()){
istest1Running = "Running";
istest2Running = "Scheduled";
if(isBootRunning=="Running"){
//here my logic
}
} else if(f2.exists() && f2.isDirectory()){
istest2Running = "Running";
if(isBootRunning=="Running"){
//here my logic
}
if(istest1Running=="Running"){
//here my logic
}
}
}
}
You are facing this issue because every time you make a new ajax request to your servlet the results of previous request is not stored/saved.
This issue can be solved using HttpSession. You have to save and fetch the string objects isBootRunning, istest1Running, istest2Running in the session object as below:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try{
HttpSession session =null;
if(request.getSession().isNew()){
session= request.getSession();//new session
}else{
session= request.getSession(false);//current session
}
if(null != session && null != session.getAttribute("isBootRunning") && null != session.getAttribute("istest1Running") && null != session.getAttribute("istest2Running")){
yourLogic(session);//compute your logic for not null values
}
else{
session.setAttribute("isBootRunning", "");
session.setAttribute("istest1Running", "");
session.setAttribute("istest2Running", "");
yourLogic(session);//compute your logic for null values
}
}catch(Exception e){
e.printStackTrace();
}
}
private void yourLogic(HttpSession session) {
File f1 = new File("myfirstpath");//this directory is visible for 10 mins only
File f2 = new File("mythirdpath");//this directory is visible for 10 mins only
File f3 = new File("mythirdpath");//this directory is visible for 10 mins only
String isBootRunning = (String)session.getAttribute("isBootRunning");
String istest1Running = (String)session.getAttribute("istest1Running");;
String istest2Running = (String)session.getAttribute("istest2Running");;
if (f1.exists() && f1.isDirectory()) {
session.setAttribute("isBootRunning", "Running");
session.setAttribute("istest1Running", "Scheduled");
session.setAttribute("istest2Running", "Scheduled");
} else if(f2.exists() && f2.isDirectory()){
session.setAttribute("istest1Running", "Scheduled");
session.setAttribute("istest2Running", "Scheduled");
if(isBootRunning=="Running"){
//here my logic
}
} else if(f2.exists() && f2.isDirectory()){
session.setAttribute("istest2Running", "Scheduled");
istest2Running = "Running";
if(isBootRunning=="Running"){
//here my logic
}
if(istest1Running=="Running"){
//here my logic
}
}
}
Here, your String objects are stored in session object. And it is quite safe to use session because session management is done your web container and it never breaches the integrity of a user.
This will prevent the initialization of objects for later requests.
You have to write to get variable:
String isBootRunning = (String) getServletContext().getAttribute("isBootRunning");
You have to write to set variable:
getServletContext().setAttribute("isBootRunning", isBootRunning);
Another thing is that current design is quite bad (possible race condition). Application/web containers are multithreaded. As you are not using any sychonronization, you may not see the result when request is served by another thread.
I want to persist Tomcat's HttpSessions to disk so that it can be used in a scalable cloud environment. The point is that there will be a number of Tomcat nodes up (in a cloud PaaS) and clients can be directed to any of them. We want to persist and load the sessions from a shared disk unit.
I have configured the PersistentManager this way:
context.xml
<Manager className="org.apache.catalina.session.PersistentManager">
<Store className="org.apache.catalina.session.FileStore" directory="c:/somedir"/>
</Manager>
The problem is that sessions are, apparently, never flushed to disk.
I changed the <Manager> config adding maxIdleBackup:
<Manager className="org.apache.catalina.session.PersistentManager maxIdleBackup="1">
This way it takes almost a minute until I see the session persisted to disk. Oddly enough, the doc states that it should take around a second:
maxIdleBackup: The time interval (in seconds) since the last access to
a session before it is eligible for being persisted to the session
store, or -1 to disable this feature. By default, this feature is
disabled.
Other config:
Following the documentation I set the system property
org.apache.catalina.session.StandardSession.ACTIVITY_CHECK -> true
Is there a way to immediately flush the session to disk? Is is possible to make that any change in the session is also persisted right away?
UPDATE:
I have tried to force the passivation of the session and flushing to disk with maxIdleBackup="0" minIdleSwap="0" maxIdleSwap="1", but it still takes almost a minute.
You can also use this valve which is part of the Tomcat distribution (at least in version 8) :
<Valve className="org.apache.catalina.valves.PersistentValve"/>
This node has to be inserted before the <Manager className="org.apache.catalina.session.PersistentManager"> node in the context.xml file.
It will then use the store to maintain the session on each http request. Note that the documentation assumes that only one http request will be made by the same client at a time.
This will allow you to use non sticky session load balancer in front of your java ee servers.
I came across this because Tomcat was taking a minute to shutdown once I added the PersistentManager to the configuration, but it relates to your problem too:
The reason you it takes a minute to persist with the PersistentManager is because you haven't adjusted the processExpiresFrequency. This setting regulates how often the PersistentManager will run it's background processes to expire session, persist them, etc. The default value is 6. (See docs: http://tomcat.apache.org/tomcat-8.5-doc/config/manager.html#Standard_Implementation)
Per the code, this value is multiplied by engine.backgroundProcessorDelay, which you set on your <Engine> element. It's default value is 10. So 6*10 is 60 seconds. If you add processExpiresFrequency="1" on your <Manager> element, you'll see it will shutdown much quicker (10 seconds). If that's not fast enough, you can adjust the backgroundProcessorDelay to be lower too. You'll also still want to set maxIdleBackup to 1. You won't get absolutely immediate persistence, but it's very quick and doesn't require the self-described "ugly tweak" in the accepted answer.
(See comments about backgroundProcessorDelay on setMaxIdleBackup method in http://svn.apache.org/repos/asf/tomcat/tc8.5.x/tags/TOMCAT_8_5_6/java/org/apache/catalina/session/PersistentManagerBase.java)
I finally managed to solve this:
I extended org.apache.catalina.session.ManagerBase overriding every method that used the superclass' sessions map, so that it attacked a file (or cache) directly.
Example:
#Override
public HashMap<String, String> getSession(String sessionId) {
Session s = getSessionFromStore(sessionId);
if (s == null) {
if (log.isInfoEnabled()) {
log.info("Session not found " + sessionId);
}
return null;
}
Enumeration<String> ee = s.getSession().getAttributeNames();
if (ee == null || !ee.hasMoreElements()) {
return null;
}
HashMap<String, String> map = new HashMap<>();
while (ee.hasMoreElements()) {
String attrName = ee.nextElement();
map.put(attrName, getSessionAttribute(sessionId, attrName));
}
return map;
}
IMPORTANT:
load and unload methods must be left empty:
#Override
public void load() throws ClassNotFoundException, IOException {
// TODO Auto-generated method stub
}
#Override
public void unload() throws IOException {
// TODO Auto-generated method stub
}
You have to override startInternal and stopInternal to prevent Lifecycle errors:
#Override
protected synchronized void startInternal() throws LifecycleException {
super.startInternal();
// Load unloaded sessions, if any
try {
load();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("standardManager.managerLoad"), t);
}
setState(LifecycleState.STARTING);
}
#Override
protected synchronized void stopInternal() throws LifecycleException {
if (log.isDebugEnabled()) {
log.debug("Stopping");
}
setState(LifecycleState.STOPPING);
// Write out sessions
try {
unload();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("standardManager.managerUnload"), t);
}
// Expire all active sessions
Session sessions[] = findSessions();
for (int i = 0; i < sessions.length; i++) {
Session session = sessions[i];
try {
if (session.isValid()) {
session.expire();
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
} finally {
// Measure against memory leaking if references to the session
// object are kept in a shared field somewhere
session.recycle();
}
}
// Require a new random number generator if we are restarted
super.stopInternal();
}
The above allows to read always from the file (or cache) but what about the write operations?. For this, I extended org.apache.catalina.session.StandardSession overriding public void setAttribute(String name, Object value, boolean notify) and public void removeAttribute(String name, boolean notify).
Example:
#Override
public void setAttribute(String name, Object value, boolean notify) {
super.setAttribute(name, value, notify);
((DataGridManager)this.getManager()).getCacheManager().getCache("sessions").put(this.getIdInternal(), this);
}
#Override
public void removeAttribute(String name, boolean notify) {
super.removeAttribute(name, notify);
((DataGridManager)this.getManager()).getCacheManager().getCache("sessions").put(this.getIdInternal(), this);
}
IMPORTANT:
In our case the real session backup ended up being a cache (not a file) and when we read the extended Tomcat session from it (in our ManagerBase impl class) we had to tweak it in an kind of ugly way so that everything worked:
private Session getSessionFromStore(String sessionId){
DataGridSession s = (DataGridSession)cacheManager.getCache("sessions").get(sessionId);
if(s!=null){
try {
Field notesField;
notesField = StandardSession.class.getDeclaredField("notes");
notesField.setAccessible(true);
notesField.set(s, new HashMap<String, Object>());
s.setManager(this);
} catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
throw new RuntimeException(e);
}
}
return s;
}
I'm writing a Spring web application and I'm mapping the "/do" URL path to the following Controller's method
#Controller
public class MyController
{
#RequestMapping(value="/do", method=RequestMethod.GET)
public String do()
{
File f = new File("otherMethodEnded.tmp");
while (!f.exists())
{
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
}
// ok, let's continue
}
}
The otherMethodEnded.tmp file is written by one another Controller's method, so when the client calls the second URL I expect the first method to exit the while loop.
Everything works, except when the client calls the "/do" URL and then closes the connection before the response was received. The problem is that the server remains in the while (!f.exists()) loop even though the client is down and cannot call the second URL to unlock the while loop.
I would try to retrieve the connection status of the "/do" URL and exit the loop when the connection is closed by the client, but I cannot find any way to do so.
I tried with the HttpServletRequest.getSession(false) method but the returned HttpSession object is always not null, so the HttpServletRequest object is not updated in case of connection close of the client.
How can I check whether the client is still waiting for the risponse or not?
The simplest way to verify something is not right is to define a timeout value and then during your loop test if your time spent waiting has exceeded the timeout.
something like:
#Controller
public class MyController
{
private static final long MAX_LOOP_TIME = 1000 * 60 * 5; // 5 minutes? choose a value
#RequestMapping(value="/do", method=RequestMethod.GET)
public String do()
{
File f = new File("otherMethodEnded.tmp");
long startedAt = System.currentTimeMillis()
boolean forcedExit = false;
while (!forcedExit && !f.exists())
{
try {
Thread.sleep(5000);
if (System.currentTimeMillis() - startedAt > MAX_LOOP_TIME) {
forcedExit = true;
}
} catch (InterruptedException e) {
forcedExit = true;
}
}
// ok, let's continue
// if forcedExit , handle error scenario?
}
}
Additionally: InterruptedException is not something to blindly catch and ignore. see this discussion
In your case I would really exit the while loop if you're interrupted.
You only know if the client is no longer waiting on your connection when you notice the output stream you write to (response.outputstream) is closed. But there isn't a way to detect it.
(see this question for details)
Seeing as you've indicated your client does occasional callbacks, you could on the clientside poll if the other call has been completed. If this other call has completed, do the operation, otherwise return directly and have the client do the call again. (assuming you are sending json, but adapt as you require)
something like
public class MyController
{
#RequestMapping(value="/do", method=RequestMethod.GET)
public String do()
{
File f = new File("otherMethodEnded.tmp");
if (f.exists()) {
// do what you set out to do
// ok, let's continue
// and return with a response that indicates the call did what it did
// view that returns json { "result" : "success" }
return "viewThatSIgnalsToClientThatOperationSucceeded";
} else {
// view that returns json: { "result" : "retry" }
return "viewThatSignalsToClientToRetryIn5Seconds";
}
}
}
Then the clientside would run something like: (pseudojavascript as it's been a while)
val callinterval = setInterval(function() { checkServer() }, 5000);
function checkServer() {
$.ajax({
// ...
success: successFunction
});
}
function successFunction(response) {
// connection succeeded
var json = $.parseJSON(response);
if (json.result === "retry") {
// interval will call this again
} else {
clearInterval(callinterval);
if (json.result === "success") {
// do the good stuff
} else if (json.result === "failure") {
// report that the server reported an error
}
}
}
Ofcourse this is just semi-serious code but it's roughly how i'd try it if I were to have the dependency. If this is regarding afile upload, keep in mind that this file may not contain all of the bytes yet. file exists != file = completely uploaded, unless you use move it. cp / scp / etc. is not atomic.
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.
Sometimes when I use multiple Modeshape actions inside one function I get this error:
javax.jcr.RepositoryException: The session with an ID of '060742fc6' has been closed and can no longer be used.
I couldn't find any explanations of this on the web. Here is what I call:
myFunction( service.doSomething ( service.getStuff ( id, "en_EN" ).getPath() ) );
doSomething, getStuff:
#Interceptors({Foo.class, TraceInterceptor.class})
#Override
public Node doSomething(final String bar) throws RepositoryException {
return modeshape.execute(new JcrHandler<Node>() {
#Override
public Node execute(Session session) throws RepositoryException {
return session.getNode(bar);
}
});
}
#Interceptors(TraceInterceptor.class)
#Override
public ObjectExtended getStuff(final String query, final String language)
throws RepositoryException {
return modeshape.execute(new JcrHandler<ObjectExtended>() {
#Override
public ObjectExtendedexecute(Session session)
throws RepositoryException {
QueryManager queryManager = session.getWorkspace().getQueryManager();
ObjectExtendeditem = null;
String queryWrapped =
"select * from [th:this] as c where name(c)='lang_"
+ language + "' and c.[th:mylabel] "
+ "= '" + queryStr + "'";
LOGGER.debug("Query: " + queryWrapped);
Query query =
queryManager.createQuery(queryWrapped,Query.JCR_SQL2);
QueryResult result = query.execute();
NodeIterator iter = result.getNodes();
while (iter.hasNext()) {
Node node = iter.nextNode().getParent();
if (node.isNodeType("th:term")) {
item = new ObjectExtended();
item.setLabel(getLabel(language, node));
item.setPath(node.getPath());
}
}
return item;
}
});
}
Why is this happening please? What am I doing wrong?
That error message means one of two thing: either the repository is being shutdown, or the Session.logout() method is being called.
None of the above code shows how your sessions are being managed, and you don't say whether you are using a framework. But I suspect that somehow you are holding onto a Session too long (perhaps after your framework is closing the session), or the Session is leaking to multiple threads, and one thread is attempting to use it after the other has closed it.
The latter could be a real problem: while passing a single Session instance from one thread to another is okay (as long as the original thread no longer uses it), but per the JCR 2.0 specification Session instances are not threadsafe and should not be concurrently used by multiple threads.
If you're creating the Session in your code, it's often good to use a try-finally block:
Session session = null;
try {
session = ... // acquire the session
// use the session, including 0 or more calls to 'save()'
} catch ( RepositoryException e ) {
// handle it
} finally {
if ( session != null ) {
try {
session.logout();
} finally {
session = null;
}
}
}
Note that logout() does not throw a RepositoryException, so the above form usually works well. Of course, if you know you're not using session later on in the method, you don't need the inner try-finally to null the session reference:
Session session = null;
try {
session = ... // acquire the session
// use the session, including 0 or more calls to 'save()'
} catch ( RepositoryException e ) {
// handle it
} finally {
if ( session != null ) session.logout();
}
This kind of logic can easily be encapsulated.