Is there a useDirtyFlag option for Tomcat 6 cluster configuration? - java

In Tomcat 5.0.x you had the ability to set useDirtyFlag="false" to force replication of the session after every request rather than checking for set/removeAttribute calls.
<Cluster className="org.apache.catalina.cluster.tcp.SimpleTcpCluster"
managerClassName="org.apache.catalina.cluster.session.SimpleTcpReplicationManager"
expireSessionsOnShutdown="false"
**useDirtyFlag="false"**
doClusterLog="true"
clusterLogName="clusterLog"> ...
The comments in the server.xml stated this may be used to make the following work:
<%
HashMap map = (HashMap)session.getAttribute("map");
map.put("key","value");
%>
i.e. change the state of an object that has already been put in the session and you can be sure that this object still be replicated to the other nodes in the cluster.
According to the Tomcat 6 documentation you only have two "Manager" options - DeltaManager & BackupManager ... neither of these seem to allow this option or anything like it. In my testing the default setup:
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
where you get the DeltaManager by default, it's definitely behaving as useDirtyFlag="true" (as I'd expect).
So my question is - is there an equivalent in Tomcat 6?
Looking at the source I can see a manager implementation "org.apache.catalina.ha.session.SimpleTcpReplicationManager" which does have the useDirtyFlag but the javadoc comments in this state it's "Tomcat Session Replication for Tomcat 4.0" ... I don't know if this is ok to use - I'm guessing not as it's not mentioned in the main cluster configuration documentation.

I posted essentially the same question on the tomcat-users mailing list and the responses to this along with some information in the tomcat bugzilla ([43866]) led me to the following conclusions:
There is no equivalent to the useDirtyFlag, if you're putting mutable (ie changing) objects in the session you need a custom coded solution.
A Tomcat ClusterValve seems to be an effecting place for this solution - plug into the cluster mechanism, manipulate attributes to make it appear to the DeltaManager that all attributes in the session have changed. This forces replication of the entire session.
Step 1: Write the ForceReplicationValve (extends ValveBase implements ClusterValve)
I won't include the whole class but the key bit of logic (taking out the logging and instanceof checking):
#Override
public void invoke(Request request, Response response)
throws IOException, ServletException {
getNext().invoke(request, response);
Session session = request.getSessionInternal();
HttpSession deltaSession = (HttpSession) session;
for (Enumeration<String> names = deltaSession.getAttributeNames();
names.hasMoreElements(); ) {
String name = names.nextElement();
deltaSession.setAttribute(name, deltaSession.getAttribute(name));
}
}
Step 2: Alter the cluster config (in conf/server.xml)
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="8">
<Valve className="org.apache.catalina.ha.tcp.ForceReplicationValve"/>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
filter=".*\.gif;.*\.jpg;.*\.png;.*\.js;.*\.htm;.*\.html;.*\.txt;.*\.css;"/>
<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
<ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
Replication of the session to all cluster nodes will now happen after every request.
Aside: Note the channelSendOptions setting. This replaces the replicationMode=asynchronous/synchronous/pooled from Tomcat 5.0.x. See the cluster documentation for the possible int values.
Appendix: Full Valve source as requested
package org.apache.catalina.ha.tcp;
import java.io.IOException;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpSession;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Session;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.ha.CatalinaCluster;
import org.apache.catalina.ha.ClusterValve;
import org.apache.catalina.ha.session.ReplicatedSession;
import org.apache.catalina.ha.session.SimpleTcpReplicationManager;
import org.apache.catalina.util.LifecycleSupport;
//import org.apache.catalina.util.StringManager;
import org.apache.catalina.valves.ValveBase;
/**
* <p>With the {#link SimpleTcpReplicationManager} effectively deprecated, this allows
* mutable objects to be replicated in the cluster by forcing the "dirty" status on
* every request.</p>
*
* #author Jon Brisbin (via post on tomcat-users http://markmail.org/thread/rdo3drcir75dzzrq)
* #author Kevin Jansz
*/
public class ForceReplicationValve extends ValveBase implements Lifecycle, ClusterValve {
private static org.apache.juli.logging.Log log =
org.apache.juli.logging.LogFactory.getLog( ForceReplicationValve.class );
#SuppressWarnings("hiding")
protected static final String info = "org.apache.catalina.ha.tcp.ForceReplicationValve/1.0";
// this could be used if ForceReplicationValve messages were setup
// in org/apache/catalina/ha/tcp/LocalStrings.properties
//
// /**
// * The StringManager for this package.
// */
// #SuppressWarnings("hiding")
// protected static StringManager sm =
// StringManager.getManager(Constants.Package);
/**
* Not actually required but this must implement {#link ClusterValve} to
* be allowed to be added to the Cluster.
*/
private CatalinaCluster cluster = null ;
/**
* Also not really required, implementing {#link Lifecycle} to allow
* initialisation and shutdown to be logged.
*/
protected LifecycleSupport lifecycle = new LifecycleSupport(this);
/**
* Default constructor
*/
public ForceReplicationValve() {
super();
if (log.isInfoEnabled()) {
log.info(getInfo() + ": created");
}
}
#Override
public String getInfo() {
return info;
}
#Override
public void invoke(Request request, Response response) throws IOException,
ServletException {
getNext().invoke(request, response);
Session session = null;
try {
session = request.getSessionInternal();
} catch (Throwable e) {
log.error(getInfo() + ": Unable to perform replication request.", e);
}
String context = request.getContext().getName();
String task = request.getPathInfo();
if(task == null) {
task = request.getRequestURI();
}
if (session != null) {
if (log.isDebugEnabled()) {
log.debug(getInfo() + ": [session=" + session.getId() + ", instanceof=" + session.getClass().getName() + ", context=" + context + ", request=" + task + "]");
}
if (session instanceof ReplicatedSession) {
// it's a SimpleTcpReplicationManager - can just set to dirty
((ReplicatedSession) session).setIsDirty(true);
if (log.isDebugEnabled()) {
log.debug(getInfo() + ": [session=" + session.getId() + ", context=" + context + ", request=" + task + "] maked DIRTY");
}
} else {
// for everything else - cycle all attributes
List cycledNames = new LinkedList();
// in a cluster where the app is <distributable/> this should be
// org.apache.catalina.ha.session.DeltaSession - implements HttpSession
HttpSession deltaSession = (HttpSession) session;
for (Enumeration<String> names = deltaSession.getAttributeNames(); names.hasMoreElements(); ) {
String name = names.nextElement();
deltaSession.setAttribute(name, deltaSession.getAttribute(name));
cycledNames.add(name);
}
if (log.isDebugEnabled()) {
log.debug(getInfo() + ": [session=" + session.getId() + ", context=" + context + ", request=" + task + "] cycled atrributes=" + cycledNames + "");
}
}
} else {
String id = request.getRequestedSessionId();
log.warn(getInfo() + ": [session=" + id + ", context=" + context + ", request=" + task + "] Session not available, unable to send session over cluster.");
}
}
/*
* ClusterValve methods - implemented to ensure this valve is not ignored by Cluster
*/
public CatalinaCluster getCluster() {
return cluster;
}
public void setCluster(CatalinaCluster cluster) {
this.cluster = cluster;
}
/*
* Lifecycle methods - currently implemented just for logging startup
*/
/**
* Add a lifecycle event listener to this component.
*
* #param listener The listener to add
*/
public void addLifecycleListener(LifecycleListener listener) {
lifecycle.addLifecycleListener(listener);
}
/**
* Get the lifecycle listeners associated with this lifecycle. If this
* Lifecycle has no listeners registered, a zero-length array is returned.
*/
public LifecycleListener[] findLifecycleListeners() {
return lifecycle.findLifecycleListeners();
}
/**
* Remove a lifecycle event listener from this component.
*
* #param listener The listener to remove
*/
public void removeLifecycleListener(LifecycleListener listener) {
lifecycle.removeLifecycleListener(listener);
}
public void start() throws LifecycleException {
lifecycle.fireLifecycleEvent(START_EVENT, null);
if (log.isInfoEnabled()) {
log.info(getInfo() + ": started");
}
}
public void stop() throws LifecycleException {
lifecycle.fireLifecycleEvent(STOP_EVENT, null);
if (log.isInfoEnabled()) {
log.info(getInfo() + ": stopped");
}
}
}

Many thanks to kevinjansz for providing the source for ForceReplicationValve.
I adjusted it for Tomcat7, here it is if anyone needs it:
package org.apache.catalina.ha.tcp;
import java.io.IOException;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpSession;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Session;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.ha.CatalinaCluster;
import org.apache.catalina.ha.ClusterValve;
import org.apache.catalina.util.LifecycleSupport;
import org.apache.catalina.valves.ValveBase;
import org.apache.catalina.LifecycleState;
// import org.apache.tomcat.util.res.StringManager;
/**
* <p>With the {#link SimpleTcpReplicationManager} effectively deprecated, this allows
* mutable objects to be replicated in the cluster by forcing the "dirty" status on
* every request.</p>
*
* #author Jon Brisbin (via post on tomcat-users http://markmail.org/thread/rdo3drcir75dzzrq)
* #author Kevin Jansz
*/
public class ForceReplicationValve extends ValveBase implements Lifecycle, ClusterValve {
private static org.apache.juli.logging.Log log =
org.apache.juli.logging.LogFactory.getLog( ForceReplicationValve.class );
#SuppressWarnings("hiding")
protected static final String info = "org.apache.catalina.ha.tcp.ForceReplicationValve/1.0";
// this could be used if ForceReplicationValve messages were setup
// in org/apache/catalina/ha/tcp/LocalStrings.properties
//
// /**
// * The StringManager for this package.
// */
// #SuppressWarnings("hiding")
// protected static StringManager sm =
// StringManager.getManager(Constants.Package);
/**
* Not actually required but this must implement {#link ClusterValve} to
* be allowed to be added to the Cluster.
*/
private CatalinaCluster cluster = null;
/**
* Also not really required, implementing {#link Lifecycle} to allow
* initialisation and shutdown to be logged.
*/
protected LifecycleSupport lifecycle = new LifecycleSupport(this);
/**
* Default constructor
*/
public ForceReplicationValve() {
super();
if (log.isInfoEnabled()) {
log.info(getInfo() + ": created");
}
}
#Override
public String getInfo() {
return info;
}
#Override
public void invoke(Request request, Response response) throws IOException,
ServletException {
getNext().invoke(request, response);
Session session = null;
try {
session = request.getSessionInternal();
} catch (Throwable e) {
log.error(getInfo() + ": Unable to perform replication request.", e);
}
String context = request.getContext().getName();
String task = request.getPathInfo();
if(task == null) {
task = request.getRequestURI();
}
if (session != null) {
if (log.isDebugEnabled()) {
log.debug(getInfo() + ": [session=" + session.getId() + ", instanceof=" + session.getClass().getName() + ", context=" + context + ", request=" + task + "]");
}
//cycle all attributes
List<String> cycledNames = new LinkedList<String>();
// in a cluster where the app is <distributable/> this should be
// org.apache.catalina.ha.session.DeltaSession - implements HttpSession
HttpSession deltaSession = (HttpSession) session;
for (Enumeration<String> names = deltaSession.getAttributeNames(); names.hasMoreElements(); ) {
String name = names.nextElement();
deltaSession.setAttribute(name, deltaSession.getAttribute(name));
cycledNames.add(name);
}
if (log.isDebugEnabled()) {
log.debug(getInfo() + ": [session=" + session.getId() + ", context=" + context + ", request=" + task + "] cycled atrributes=" + cycledNames + "");
}
} else {
String id = request.getRequestedSessionId();
log.warn(getInfo() + ": [session=" + id + ", context=" + context + ", request=" + task + "] Session not available, unable to send session over cluster.");
}
}
/*
* ClusterValve methods - implemented to ensure this valve is not ignored by Cluster
*/
public CatalinaCluster getCluster() {
return cluster;
}
public void setCluster(CatalinaCluster cluster) {
this.cluster = cluster;
}
/*
* Lifecycle methods - currently implemented just for logging startup
*/
/**
* Add a lifecycle event listener to this component.
*
* #param listener The listener to add
*/
public void addLifecycleListener(LifecycleListener listener) {
lifecycle.addLifecycleListener(listener);
}
/**
* Get the lifecycle listeners associated with this lifecycle. If this
* Lifecycle has no listeners registered, a zero-length array is returned.
*/
public LifecycleListener[] findLifecycleListeners() {
return lifecycle.findLifecycleListeners();
}
/**
* Remove a lifecycle event listener from this component.
*
* #param listener The listener to remove
*/
public void removeLifecycleListener(LifecycleListener listener) {
lifecycle.removeLifecycleListener(listener);
}
protected synchronized void startInternal() throws LifecycleException {
setState(LifecycleState.STARTING);
if (log.isInfoEnabled()) {
log.info(getInfo() + ": started");
}
}
protected synchronized void stopInternal() throws LifecycleException {
setState(LifecycleState.STOPPING);
if (log.isInfoEnabled()) {
log.info(getInfo() + ": stopped");
}
}
}

Related

Action Not Found For request 'GET /authenticate/github? in securesocial for playframwork 2.6

I have been trying to use securesocial in play 2.6. I am using master snapshot. When I click on GitHub icon, it navigates me to github.com and when I enter my username and password, I get action not found error http://localhost:9000/authenticate/github?error=redirect_uri_mismatch&error_description=The+redirect_uri+MUST+match+the+registered+callback+URL+for+this+application.&error_uri=https%3A%2F%2Fdeveloper.github.com%2Fapps%2Fmanaging-oauth-apps%2Ftroubleshooting-authorization-request-errors%2F%23redirect-uri-mismatch&state=9eb23bec-37b6-4930-9b76-899ff8e1fbe1.
Below is my route file
GET / controllers.Application.index
GET /home #controllers.Application.goHome
GET /userAware #controllers.Application.userAware
GET /link-result #controllers.Application.linkResult
GET /current-user #controllers.Application.currentUser
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
-> /auth securesocial.Routes
Application.java
package controllers;
import play.mvc.*;
import com.google.inject.Inject;
import play.Logger;
import play.libs.F;
import services.MyUserService;
import securesocial.core.BasicProfile;
import securesocial.core.RuntimeEnvironment;
import securesocial.core.java.SecureSocial;
import securesocial.core.java.SecuredAction;
import securesocial.core.java.UserAwareAction;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import views.html.*;
/**
* This controller contains an action to handle HTTP requests
* to the application's home page.
*/
public class Application extends Controller {
/**
* An action that renders an HTML page with a welcome message.
* The configuration in the <code>routes</code> file means that
* this method will be called when the application receives a
* <code>GET</code> request with a path of <code>/</code>.
*/
public static Logger.ALogger logger = Logger.of("application.controllers.Application");
private RuntimeEnvironment env;
/**
* A constructor needed to get a hold of the environment instance.
* This could be injected using a DI framework instead too.
*
* #param env
*/
#Inject()
public Application (RuntimeEnvironment env) {
this.env = env;
}
/**
* This action only gets called if the user is logged in.
*
* #return
*/
#SecuredAction
public Result index() {
if(logger.isDebugEnabled()){
logger.debug("access granted to index");
}
MyUserService user = (MyUserService) ctx().args.get(SecureSocial.USER_KEY);
return ok(index.render(user, SecureSocial.env()));
}
#UserAwareAction
public Result userAware() {
MyUserService myUser = (MyUserService) ctx().args.get(SecureSocial.USER_KEY);
String userName ;
if ( myUser != null ) {
BasicProfile user = myUser.main;
if ( user.firstName().isDefined() ) {
userName = user.firstName().get();
} else if ( user.fullName().isDefined()) {
userName = user.fullName().get();
} else {
userName = "authenticated user";
}
} else {
userName = "guest";
}
return ok("Hello " + userName + ", you are seeing a public page");
}
#SecuredAction
public Result goHome() {
if(logger.isDebugEnabled()){
logger.debug("access granted to Home");
}
MyUserService user = (MyUserService) ctx().args.get(SecureSocial.USER_KEY);
return ok(home.render());
}
#SecuredAction
public Result linkResult() {
MyUserService current = (MyUserService) ctx().args.get(SecureSocial.USER_KEY);
return ok(linkResult.render(current, current.identities));
}
/**
* Sample use of SecureSocial.currentUser. Access the /current-user to test it
*/
public CompletionStage<Result> currentUser() {
return SecureSocial.currentUser(env).thenApply( new Function<Object, Result>() {
public Result apply(Object maybeUser) {
String id;
if ( maybeUser != null ) {
MyUserService user = (MyUserService) maybeUser;
id = user.main.userId();
} else {
id = "not available. Please log in.";
}
return ok("your id is " + id);
}
});
}
}
And in securesocial.conf I have added onLoginGoTo = /home
It looks like you need to use /authenticate rather than /auth:
-> /authenticate securesocial.Routes
Or if you want to use the url use it with auth and not authenticate:
http://localhost:9000/auth/github?error=redirect_uri_mismatch&error_description=The+redirect_uri+MUST+match+the+registered+callback+URL+for+this+application.&error_uri=https%3A%2F%2Fdeveloper.github.com%2Fapps%2Fmanaging-oauth-apps%2Ftroubleshooting-authorization-request-errors%2F%23redirect-uri-mismatch&state=9eb23bec-37b6-4930-9b76-899ff8e1fbe1
The issue was I did not add route to redirect URL in my route file.
After adding -> /authenticate/github securesocial.Routes solved my problem. /authenticate/github was the URL I registered while creating client token on github

Java servlet session not working properly with NGinx, how to fix?

I have a site running Java servlet, using tomcat8.5, it's working fine : http://69.197.177.154:8080/GATE_Web/index.jsp
But the guy at the ISP is concerned about security, so he suggested we use NGinx to make it safer, therefore the same app is doing a NGinx Proxy pass through, and is running at : http://gatecybertech.net/
It is not running properly, when I clicked on the buttons, the cursor at the bottom is not moving, and I've notice the "User Count : " at the upper right of the page is increasing quickly, even when there is only one user [ me ] testing the app. I use Java session to keep track of new user sessions, it's working fine in the 1st site, but when working with NGinx, it's not behaving correctly.
Since the app is complicated, in order to quickly figure out why it's not working properly with NGinx, I simplified the app and created another servlet called Test_Servlet : http://69.197.177.154:8080/GATE_Web/Test_Servlet
As you can see, when you click on different buttons, the cursor with follow and point to the clicked button.
The same Test_Servlet running with NGinx is at : http://gatecybertech.net/Test_Servlet
With this one, the cursor won't follow the button you click on.
I suspect the config for the NGinx is not properly set, what do you think is wrong with it ?
And here is the simplified servlet.
import java.io.*;
import java.net.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
import Utility.*;
public class Test_Servlet extends HttpServlet
{
private boolean Debug=false;
String App_Id="Test_Servlet",GATE_Title,User_Id=null,Profile="Profile",requestMethod,
Spaces_Text=" ",Line_Breaks="<P><Br>\n<P><Br>\n<P><Br>\n<P><Br>\n<P><Br>\n<P><Br>\n<P><Br>\n";
public Test_Servlet()
{
super();
GATE_Title="<Table Border=0 Cellpadding=0 Cellspacing=8>\n"+
" <Tr><Td Align=Center><Font Color=#3636CC Size=2>Server : [Server_Info] "+Spaces_Text+" User Count : [User_Count]</Font></Td></Tr>\n"+
"</Table>\n";
}
/** Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
* #param request servlet request
* #param response servlet response
*/
private void processRequest(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException
{
HttpSession session=request.getSession(true);
Session_Counter counter=(Session_Counter)session.getAttribute(Session_Counter.COUNTER);
int User_Count=counter.getActiveSessionNumber();
String title="Test_Servlet",Server_Info=getServletContext().getServerInfo(),Action,Current_Id_Button,responseText,
responseString="<!DOCTYPE html>\n"+
"<Html>\n"+
"<Head>\n"+
" <Title>"+title+"</Title>\n"+
Get_jQuery()+
" <Link rel=\"stylesheet\" type=\"text/css\" href=\"GATE_Style.css\">\n"+
"</Head>\n\n"+
"<Body BgColor=#C6DAFA>\n\n"+
"<Center>\n"+
"<P><Br><Br><P><Br><P><Br><P><Br><P><Br><P><Br><P><Br><P><Br><P><Br><P>\n"+
GATE_Title.replace("[Server_Info]",Server_Info).replace("[User_Count]",User_Count+"")+"<P><P>\n";
LinkedHashMap<String,String> params=getQueryParameterMap(request);
int Font_Size=20,Current_Id;
requestMethod=request.getMethod()+"()";
response.setContentType("text/html");
response.setHeader("Content-Type","text/html;charset=utf-8");
PrintWriter out=response.getWriter();
try
{
User_Id=Decode_String(params.get("User_Id"));
Action=Decode_String(params.get("Action"));
Current_Id_Button=Decode_String(params.get("Current_Id_Button"));
Debug=(params.get("Debug")==null?Debug:params.get("Debug").toLowerCase().equals("true"));
if (session.getAttribute("Current_Id")==null)
{
Current_Id=1;
session.setAttribute("Current_Id",Current_Id+"");
}
else Current_Id=Integer.parseInt((String)session.getAttribute("Current_Id"));
if (Action!=null && Action.equals("Get_Pointer_Table")) // Everytime after Current_Id change, adjust pointer table
{
out.print(Get_Pointer_Table(Current_Id));
// Out(Get_Pointer_Table(Current_Id));
}
else if (Current_Id_Button!=null && Current_Id_Button.startsWith("Current_")) // Current_Id Button is clicked, set Current_Id to it
{
Current_Id=Integer.parseInt(Current_Id_Button.substring(8));
session.setAttribute("Current_Id",Current_Id+"");
}
else if (Action!=null && Action.equals("Current_Id")) // Everytime a symbols/token is clicked, increase Current_Id by one
{
responseText=Current_Id+"";
if (Current_Id<6) Current_Id++;
else Current_Id=1;
session.setAttribute("Current_Id",Current_Id+"");
out.println(responseText);
// Out(" Current_Id = "+Current_Id);
}
else
{
responseString+=Get_Parameters(params)+"<P>\n";
// Object does not exist or is not a file: reject with 404 error.
// responseString+="404 (Not Found)\n";
responseString+="<Br><P><Br><P><Br><P>\n";
Current_Id=1;
session.setAttribute("Current_Id",Current_Id+"");
responseString+="<Table Border=0 Cellpadding=0 Cellspacing=0>\n"+
" <Tr>\n"+
" <Td>\n"+
" <Table Border=0 Cellpadding=0 Cellspacing=3>\n"+
" <Tr>\n"+
" <Td>"+Get_Html_Button("Current_1","1",82,82,Font_Size)+"</Td>\n"+
" <Td>"+Get_Html_Button("Current_2","2",82,82,Font_Size)+"</Td>\n"+
" <Td>"+Get_Html_Button("Current_3","3",82,82,Font_Size)+"</Td>\n"+
" <Td>"+Get_Html_Button("Current_4","4",82,82,Font_Size)+"</Td>\n"+
" <Td>"+Get_Html_Button("Current_5","5",82,82,Font_Size)+"</Td>\n"+
" <Td>"+Get_Html_Button("Current_6","6",82,82,Font_Size)+"</Td>\n"+
" </Tr>\n"+
" </Table>\n"+
" </Td>\n"+
" </Tr>\n"+
" <Tr>\n"+
" <Td>\n"+
Get_Pointer_Table(Current_Id)+
" </Td>\n"+
" </Tr>\n"+
"</Table>\n";
responseString+="</div>\n";
out.println(responseString);
// Out(responseString);
}
}
catch (Exception e)
{
if (!e.toString().equals("java.io.IOException: Broken pipe")) // When user closes a window while half way writing, gets a "Broken pipe"
{
responseString+="<P><Pre>"+e.toString()+"\n"+Tool_Lib_Thin.Get_Stack_Trace(e)+"</Pre>\n";
e.printStackTrace();
}
}
finally
{
try
{
// responseBody.close();
// if (User_Id!=null && User_Id.equals("Stop_Server")) server.stop(0);
}
catch (Exception ex)
{
if (!ex.toString().equals("java.io.IOException: Broken pipe")) // When user closes a window while half way writing, gets a "Broken pipe"
{
ex.printStackTrace();
}
}
}
}
public static String Decode_String(String Input) // Fix this problem : // Error : java.lang.IllegalArgumentException: URLDecoder: Illegal hex characters in escape (%) pattern - For input string: "</"
{
if (Input==null) return null; // Replace "+" with "<plus>", and replace "%" with "<percentage>" won't work. Because characters that get encoded have % and + signs in them,
// so although this helps with % and + characters in a string, yet it doesn't decode things like %20 (space) because you are taking out the percent before decoding.
String Output=""; // Stack overflow solution : https://stackoverflow.com/questions/6067673/urldecoder-illegal-hex-characters-in-escape-pattern-for-input-string
try // A solution is to replace %2B (+) and %25 (%) instead.
{
Output=Input.replaceAll("%(?![0-9a-fA-F]{2})","%25"); // The first replaceAll is the regex that replaces any percent sign that isn't another encoded character with the percent encoded value. The question
Output=Output.replaceAll("\\+","%2B"); // mark makes the proceeding optional, the [0-9a-fA-F] looks for those ranges of characters/numbers and the {2} looks for exactly 2 characters/numbers.
Output=Output.replaceAll("&","&"); // The second replaceAll just replaces the + sign with its encoded value.
Output=URLDecoder.decode(Output,"utf-8");
}
catch (Exception e) { e.printStackTrace(); }
return Output;
}
public LinkedHashMap<String,String> getQueryParameterMap(HttpServletRequest request)
{
LinkedHashMap<String,String> queryParameterMap=new LinkedHashMap();
Map params=request.getParameterMap();
Iterator i=params.keySet().iterator();
while (i.hasNext())
{
String key=(String)i.next();
String value=((String[])params.get(key))[0];
queryParameterMap.put(key,value);
}
return queryParameterMap;
}
String Get_jQuery()
{
return "<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js\"></script>\n"+
"<script>\n"+
"$(document).ready(function()\n"+
"{\n"+
" $('button').not('#Save').not('#Login').click($.now(),function(event)\n"+ // 100% Firefox , IE & Chrome [ OK when click on side if var Clicked_Button=event.target ]
" {\n"+
" if($(this).attr('id').indexOf('Current_')==0) // Clicked on Current_Id_Button \n"+
" {\n"+
" $.get('Test_Servlet?Current_Id_Button='+this.id+'&now='+$.now(),function()\n"+ // use now=$.now() to avoid IE cashing
" {\n"+
" $.get('Test_Servlet?Action=Get_Pointer_Table&now='+$.now(),function(data)\n"+ // data holds new Pointer_Table html, use now=$.now() to avoid IE cashing
" {\n"+ // Get_Pointer_Table must be done here after Current_Id_Button to avoid out of sync
" var Current_Pointer_Table=data;\n"+
" $(\"div.Table\").replaceWith(Current_Pointer_Table);\n"+
" });\n"+
" });\n"+
" }\n"+
// " alert(data);\n"+
" });\n"+
"});\n"+
"</script>\n";
}
String Get_Pointer_Table(int Id)
{
String Pointer_Text="<Font size=4 Color=blue>\u2191</Font><Br><Font size=2 Color=blue>Current</Font>";
return " <div class=\"inner Table\">\n"+
" <Table border=0>\n"+
" <Tr>\n"+
" <Td Align=Center Width=82>"+(Id==1?Pointer_Text:"")+"</Td>\n"+
" <Td Align=Center Width=82>"+(Id==2?Pointer_Text:"")+"</Td>\n"+
" <Td Align=Center Width=82>"+(Id==3?Pointer_Text:"")+"</Td>\n"+
" <Td Align=Center Width=82>"+(Id==4?Pointer_Text:"")+"</Td>\n"+
" <Td Align=Center Width=82>"+(Id==5?Pointer_Text:"")+"</Td>\n"+
" <Td Align=Center Width=82>"+(Id==6?Pointer_Text:"")+"</Td>\n"+
" <Td Width=56> </Td>\n"+
" </Tr>\n"+
" </Table>\n"+
" </div>\n";
}
String Get_Html_Button(String Id,String Text,int W,int H,int Font_Size)
{
return "<button id="+Id+" type=button style=\""+(W<50?"padding: .01em;":"")+"width:"+W+"px;height:"+H+"px;font-size: "+Font_Size+"px\">"+Text+"</button>";
}
String Get_Parameters(Map<String,String> params)
{
String title="Reading All Request Parameters",paramName,paramValue="";
String Parameters_Table="<Table Border=1 Align=Center Cellpadding=5 Cellspacing=2>\n <Tr Bgcolor=#0088FF Colspan=2><Th><Font Color=White>"+title+"</Font></Th></Tr>\n</Table>\n<P>\n"+
"<Font Color=blue>This is class [ "+this.getClass().getName()+" ] using the "+requestMethod+" method [ "+new Date()+" ]</Font><P>\n\n"+
"<Table Border=1 Align=Center Cellpadding=2 Cellspacing=2>\n <Tr Bgcolor=#99CCFF><Th>Parameter Name</Th><Th>Parameter Value(s)</Th></Tr>\n";
for (Map.Entry<String,String> entry : params.entrySet())
{
paramName=entry.getKey().replace("+"," ");
Parameters_Table+=" <Tr><Td Align=Right>"+(paramName.trim().length()<1?" ":paramName)+"</Td>";
try { paramValue=URLDecoder.decode(entry.getValue(),"utf-8"); }
catch (Exception e) { e.printStackTrace(); }
// Out(paramName+" : "+paramValue);
Parameters_Table+="<Td>"+(paramValue.trim().length()<1?" ":paramValue)+"</Td></Tr>\n";
}
Parameters_Table+="</Table>\n";
return Debug?Parameters_Table:"";
}
public LinkedHashMap<String,String> queryToMap(String query) // http://localhost:6600/Resume_App?Id=Edit&File_Name=AT&T.txt
{
// Out("query = "+query);
LinkedHashMap<String,String> result=new LinkedHashMap();
for (String param : query.split("&"))
{
String pair[]=param.split("=");
if (pair.length>1) result.put(pair[0],pair[1]);
else result.put(pair[0],"");
}
return result;
}
private static void out(String message)
{
System.out.print(message);
try { System.out.print(message); }
catch (Exception e) { }
// GATE_App.Log_TextArea.append(message);
}
private static void Out(String message)
{
System.out.println(message);
try { System.out.println(message); }
catch (Exception e) { }
// GATE_App.Log_TextArea.append(message+"\n");
}
/** Handles the HTTP <code>GET</code> method. This method is called when a form has its tag value method equals to get.
* #param request servlet request
* #param response servlet response
*/
public void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException { processRequest(request,response); }
/** Handles the HTTP <code>POST</code> method. This method is called when a form has its tag value method equals to post.
* #param request servlet request
* #param response servlet response
*/
public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException { processRequest(request,response); }
/** This method is called when a HTTP put request is received.
*
* #param request the request send by the client to the server
* #param response the response send by the server to the client
*/
public void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException
{
// Put your code here
}
/** This method is called when a HTTP delete request is received.
*
* #param request the request send by the client to the server
* #param response the response send by the server to the client
*/
public void doDelete(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException
{
// Put your code here
}
public void destroy() { super.destroy(); }
/**
* Returns information about the servlet, such as
* author, version, and copyright.
*
* #return String information about this servlet
*/
public String getServletInfo() { return "This is servlet : [ "+this.getClass().getName()+" ]"; }
/**
* Initialization of the servlet. <br>
*
* #throws ServletException if an error occurs
*/
public void init() throws ServletException
{
// Put your code here
}
}
=========================================================
package Utility;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import javax.servlet.http.HttpSession;
import java.util.List;
import java.util.ArrayList;
public class Session_Counter implements HttpSessionListener
{
private List<String> sessions=new ArrayList<>();
public static final String COUNTER="session-counter";
GATE_4_App_Lib GATE_4_app_lib=new GATE_4_App_Lib();
public void sessionCreated(HttpSessionEvent event)
{
System.out.println("Session_Counter.sessionCreated");
HttpSession session=event.getSession();
sessions.add(session.getId());
session.setAttribute(Session_Counter.COUNTER,this);
}
public void sessionDestroyed(HttpSessionEvent event)
{
System.out.println("Session_Counter.sessionDestroyed");
HttpSession session=event.getSession();
sessions.remove(session.getId());
session.setAttribute(Session_Counter.COUNTER,this);
GATE_4_app_lib.Save_To_GATE_Web_Log("[-] Client_IP = "+session.getAttribute("Client_IP")+", Client_Browser = "+session.getAttribute("Client_Browser"));
}
public int getActiveSessionNumber() { return sessions.size(); }
}
Yes, it seems that nginx configuration is not correct
curl -vvv http://gatecybertech.net/Test_Servlet
<skip>
Set-Cookie: JSESSIONID=E3551B683A0FD1A8A17829022A070AE2;path=/GATE_Web;HttpOnly
Your site sets cookie with path /GATE_Web while from the point of a browser the URL is not under this path, so the browser will not send the cookie on subsequent requests, and HTTP session will be started over.
One suggestion is to map to /GATE_Web in nginx as well and use http://gatecybertech.net/GATE_Web/Test_Servlet instead of http://gatecybertech.net/Test_Servlet
Alright, I've solved the problem. By doing the following :
In /opt/tomcat85/conf/context.xml, change "<Context>" to :
<Context sessionCookiePath="/">
...
</Context>

com.ibm.msg.client.wmq.WMQConstants cannot rsolve

I am implementing an IBM MQ client using java class as follows;
import javax.jms.JMSException;
import com.ibm.msg.client.jms.JmsConnectionFactory;
import com.ibm.msg.client.jms.JmsFactoryFactory;
import com.ibm.msg.client.wmq.WMQConstants;
import javax.jms.JMSContext;
import javax.jms.Topic;
import javax.jms.Queue;
import javax.jms.JMSConsumer;
import javax.jms.Message;
import javax.jms.JMSProducer;
/*
* Implements both Subscriber and Publisher
*/
class SharedNonDurableSubscriberAndPublisher implements Runnable {
private Thread t;
private String threadName;
SharedNonDurableSubscriberAndPublisher( String name){
threadName = name;
System.out.println("Creating Thread:" + threadName );
}
/*
* Demonstrates shared non-durable subscription in JMS 2.0
*/
private void sharedNonDurableSubscriptionDemo(){
JmsConnectionFactory cf = null;
JMSContext msgContext = null;
try {
// Create Factory for WMQ JMS provider
JmsFactoryFactory ff = JmsFactoryFactory.getInstance(WMQConstants.WMQ_PROVIDER);
// Create connection factory
cf = ff.createConnectionFactory();
// Set MQ properties
cf.setStringProperty(WMQConstants.WMQ_QUEUE_MANAGER, "QM3");
cf.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_BINDINGS);
// Create message context
msgContext = cf.createContext();
// Create a topic destination
Topic fifaScores = msgContext.createTopic("/FIFA2014/UPDATES");
// Create a consumer. Subscription name specified, required for sharing of subscription.
JMSConsumer msgCons = msgContext.createSharedConsumer(fifaScores, "FIFA2014SUBID");
// Loop around to receive publications
while(true){
String msgBody=null;
// Use JMS 2.0 receiveBody method as we are interested in message body only.
msgBody = msgCons.receiveBody(String.class);
if(msgBody != null){
System.out.println(threadName + " : " + msgBody);
}
}
}catch(JMSException jmsEx){
System.out.println(jmsEx);
}
}
/*
* Publisher publishes match updates like current attendance in the stadium, goal score and ball possession by teams.
*/
private void matchUpdatePublisher(){
JmsConnectionFactory cf = null;
JMSContext msgContext = null;
int nederlandsGoals = 0;
int chileGoals = 0;
int stadiumAttendence = 23231;
int switchIndex = 0;
String msgBody = "";
int nederlandsHolding = 60;
int chileHolding = 40;
try {
// Create Factory for WMQ JMS provider
JmsFactoryFactory ff = JmsFactoryFactory.getInstance(WMQConstants.WMQ_PROVIDER);
// Create connection factory
cf = ff.createConnectionFactory();
// Set MQ properties
cf.setStringProperty(WMQConstants.WMQ_QUEUE_MANAGER, "QM3");
cf.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_BINDINGS);
// Create message context
msgContext = cf.createContext();
// Create a topic destination
Topic fifaScores = msgContext.createTopic("/FIFA2014/UPDATES");
// Create publisher to publish updates from stadium
JMSProducer msgProducer = msgContext.createProducer();
while(true){
// Send match updates
switch(switchIndex){
// Attendance
case 0:
msgBody ="Stadium Attendence " + stadiumAttendence;
stadiumAttendence += 314;
break;
// Goals
case 1:
msgBody ="SCORE: The Netherlands: " + nederlandsGoals + " - Chile:" + chileGoals;
break;
// Ball possession percentage
case 2:
msgBody ="Ball possession: The Netherlands: " + nederlandsHolding + "% - Chile: " + chileHolding + "%";
if((nederlandsHolding > 60) && (nederlandsHolding < 70)){
nederlandsHolding -= 2;
chileHolding += 2;
}else{
nederlandsHolding += 2;
chileHolding -= 2;
}
break;
}
// Publish and wait for two seconds to publish next update
msgProducer.send (fifaScores, msgBody);
try{
Thread.sleep(2000);
}catch(InterruptedException iex){
}
// Increment and reset the index if greater than 2
switchIndex++;
if(switchIndex > 2)
switchIndex = 0;
}
}catch(JMSException jmsEx){
System.out.println(jmsEx);
}
}
/*
* (non-Javadoc)
* #see java.lang.Runnable#run()
*/
public void run() {
// If this is a publisher thread
if(threadName == "PUBLISHER"){
matchUpdatePublisher();
}else{
// Create subscription and start receiving publications
sharedNonDurableSubscriptionDemo();
}
}
// Start thread
public void start (){
System.out.println("Starting " + threadName );
if (t == null)
{
t = new Thread (this, threadName);
t.start ();
}
}
}
I am new to IBM MQ and can't understand how to resolve following imports.
import com.ibm.msg.client.jms.JmsConnectionFactory;
import com.ibm.msg.client.jms.JmsFactoryFactory;
import com.ibm.msg.client.wmq.WMQConstants;
Just resolved other dependencies through jars. Please help on this.
You want 'com.ibm.mq.allclient.jar'. You can find this on disk if you have a queue manager installed or MQC8 support pac under INSTALL_DIR/java/lib.
I recently used IBM MQ classes for JMS to place messages onto queues and was successful. Please ensure you have the following jar files in your build path before compilation. You can either google this and download or if you have installed WebSphere MQ on your pc then go to the installation folder(WebSphere MQ)/Java/lib to find the jar files:
com.ibm.mq.jmqi.jar
com.ibm.mqjms.jar
jms.jar

Samples with JmDNS

I've been able to get the samples that come with JmDNS to compile and run, however I can't get any of the classes to discover my services.
I'm running a Windows environment with multiple PC's running VNC, SSH & Apache and I've been trying to get JmDNS to discover at least one of these...
What I ideally want is to be able to detect all running VNC servers on my network. Is there some sort of client and server pairing where I can only discover a service if I've registered it using JmDNS?
Any help getting some results out of the samples will be appreciated, the documentation isn't much help.
import java.io.IOException;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.jmdns.JmDNS;
import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceListener;
/**
* Sample Code for Service Discovery using JmDNS and a ServiceListener.
* <p>
* Run the main method of this class. It listens for HTTP services and lists all changes on System.out.
*
* #author Werner Randelshofer
*/
public class DiscoverServices {
static class SampleListener implements ServiceListener {
#Override
public void serviceAdded(ServiceEvent event) {
System.out.println("Service added : " + event.getName() + "." + event.getType());
}
#Override
public void serviceRemoved(ServiceEvent event) {
System.out.println("Service removed : " + event.getName() + "." + event.getType());
}
#Override
public void serviceResolved(ServiceEvent event) {
System.out.println("Service resolved: " + event.getInfo());
}
}
/**
* #param args
* the command line arguments
*/
public static void main(String[] args) {
try {
// Activate these lines to see log messages of JmDNS
boolean log = false;
if (log) {
Logger logger = Logger.getLogger(JmDNS.class.getName());
ConsoleHandler handler = new ConsoleHandler();
logger.addHandler(handler);
logger.setLevel(Level.FINER);
handler.setLevel(Level.FINER);
}
final JmDNS jmdns = JmDNS.create();
String type = "_http._tcp.local.";
if(args.length > 0) {
type = args[0];
}
jmdns.addServiceListener(type, new SampleListener());
System.out.println("Press q and Enter, to quit");
int b;
while ((b = System.in.read()) != -1 && (char) b != 'q') {
/* Stub */
}
jmdns.close();
System.out.println("Done");
} catch (IOException e) {
e.printStackTrace();
}
}
}
To discover a specific type of service, you need to know the correct service type name, check out DNS SRV (RFC 2782) Service Types:
String bonjourServiceType = "_http._tcp.local.";
bonjourService = JmDNS.create();
bonjourService.addServiceListener(bonjourServiceType, bonjourServiceListener);
ServiceInfo[] serviceInfos = bonjourService.list(bonjourServiceType);
for (ServiceInfo info : serviceInfos) {
System.out.println("## resolve service " + info.getName() + " : " + info.getURL());
}
bonjourService.close();
For VNC, use _rfb._tcp.local.
For SSH, use _ssh._tcp.local.
For Apache, use _http._tcp.local.

Multiple sessions for one servlet in Java

I have one servlet taking care of multiple sites and therefore I want to have different sessions for different sites, even if its the same user.
Is there any support for this in Java or do I need to prefix the attribute names instead? I guess prefixing is not a good idea.
/Br Johannes
This CANNOT be done in the servlet container based on URL parameters alone; you'll have to do it yourself. Instead of dealing with attribute prefixes in your servlet, however, the easiest way to manage "separate" sessions is via filter:
Write a simple wrapper class for HttpSession. Have it hold a Map of attributes and back all attribute / value methods by said map; delegate all the other methods to the actual session you're wrapping. Override invalidate() method to remove your session wrapper instead of killing the entire "real" session.
Write a servlet filter; map it to intercept all applicable URLs.
Maintain a collection of your session wrappers as an attribute within the real session.
In your filter's doFilter() method extract the appropriate session wrapper from the collection and inject it into the request you're passing down the chain by wrapping the original request into HttpServletRequestWrapper whose getSession() method is overwritten.
Your servlets / JSPs / etc... will enjoy "separate" sessions.
Note that Sessions's "lastAccessedTime" is shared with this approach. If you need to keep those separate you'll have to write your own code for maintaining this setting and for expiring your session wrappers.
I recently came across this problem too, and I went with ChssPly76's suggestion to solve it. I thought I'd post my results here to provide a reference implementation. It hasn't been extensively tested, so kindly let me know if you spot any weaknesses.
I assume that every request to a servlet contains a parameter named uiid, which represents a user ID. The requester has to keep track of sending a new ID everytime a link is clicked that opens a new window. In my case this is sufficient, but feel free to use any other (maybe more secure) method here. Furthermore, I work with Tomcat 7 or 8. You might need to extend other classes when working with different servlet containers, but the APIs shouldn't change too much.
In the following, the created sessions are referred to as subsessions, the original container managed session is the parent session. The implementation consists of the following five classes:
The SingleSessionManager keeps track of creation, distribution and cleanup of all subsessions. It does this by acting as a servlet filter which replaces the ServletRequest with a wrapper that returns the appropriate subsession. A scheduler periodically checks for expired subsessions ...and yes, it's a singleton. Sorry, but I still like them.
package session;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* A singleton class that manages multiple sessions on top of a regular container managed session.
* See web.xml for information on how to enable this.
*
*/
public class SingleSessionManager implements Filter {
/**
* The default session timeout in seconds to be used if no explicit timeout is provided.
*/
public static final int DEFAULT_TIMEOUT = 900;
/**
* The default interval for session validation checks in seconds to be used if no explicit
* timeout is provided.
*/
public static final int DEFAULT_SESSION_INVALIDATION_CHECK = 15;
private static SingleSessionManager instance;
private ScheduledExecutorService scheduler;
protected int timeout;
protected long sessionInvalidationCheck;
private Map<SubSessionKey, HttpSessionWrapper> sessions = new ConcurrentHashMap<SubSessionKey, HttpSessionWrapper>();
public SingleSessionManager() {
sessionInvalidationCheck = DEFAULT_SESSION_INVALIDATION_CHECK;
timeout = DEFAULT_TIMEOUT;
}
public static SingleSessionManager getInstance() {
if (instance == null) {
instance = new SingleSessionManager();
}
return instance;
}
#Override
public void destroy() {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper((HttpServletRequest) request);
chain.doFilter(wrapper, response);
}
#Override
public void init(FilterConfig cfg) throws ServletException {
String timeout = cfg.getInitParameter("sessionTimeout");
if (timeout != null && !timeout.trim().equals("")) {
getInstance().timeout = Integer.parseInt(timeout) * 60;
}
String sessionInvalidationCheck = cfg.getInitParameter("sessionInvalidationCheck");
if (sessionInvalidationCheck != null && !sessionInvalidationCheck.trim().equals("")) {
getInstance().sessionInvalidationCheck = Long.parseLong(sessionInvalidationCheck);
}
getInstance().startSessionExpirationScheduler();
}
/**
* Create a new session ID.
*
* #return A new unique session ID.
*/
public String generateSessionId() {
return UUID.randomUUID().toString();
}
protected void startSessionExpirationScheduler() {
if (scheduler == null) {
scheduler = Executors.newScheduledThreadPool(1);
final Runnable sessionInvalidator = new Runnable() {
public void run() {
SingleSessionManager.getInstance().destroyExpiredSessions();
}
};
final ScheduledFuture<?> sessionInvalidatorHandle =
scheduler.scheduleAtFixedRate(sessionInvalidator
, this.sessionInvalidationCheck
, this.sessionInvalidationCheck
, TimeUnit.SECONDS);
}
}
/**
* Get the timeout after which a session will be invalidated.
*
* #return The timeout of a session in seconds.
*/
public int getSessionTimeout() {
return timeout;
}
/**
* Retrieve a session.
*
* #param uiid
* The user id this session is to be associated with.
* #param create
* If <code>true</code> and no session exists for the given user id, a new session is
* created and associated with the given user id. If <code>false</code> and no
* session exists for the given user id, no new session will be created and this
* method will return <code>null</code>.
* #param originalSession
* The original backing session created and managed by the servlet container.
* #return The session associated with the given user id if this session exists and/or create is
* set to <code>true</code>, <code>null</code> otherwise.
*/
public HttpSession getSession(String uiid, boolean create, HttpSession originalSession) {
if (uiid != null) {
SubSessionKey key = new SubSessionKey(originalSession.getId(), uiid);
if (!sessions.containsKey(key) && create) {
HttpSessionWrapper sw = new HttpSessionWrapper(uiid, originalSession);
sessions.put(key, sw);
}
HttpSessionWrapper session = sessions.get(key);
session.setLastAccessedTime(System.currentTimeMillis());
return session;
}
return null;
}
public HttpSessionWrapper removeSession(SubSessionKey key) {
return sessions.remove(key);
}
/**
* Destroy a session, freeing all it's resources.
*
* #param session
* The session to be destroyed.
*/
public void destroySession(HttpSessionWrapper session) {
String uiid = ((HttpSessionWrapper)session).getUiid();
SubSessionKey key = new SubSessionKey(session.getOriginalSession().getId(), uiid);
HttpSessionWrapper w = getInstance().removeSession(key);
if (w != null) {
System.out.println("Session " + w.getId() + " with uiid " + uiid + " was destroyed.");
} else {
System.out.println("uiid " + uiid + " does not have a session.");
}
}
/**
* Destroy all session that are expired at the time of this method call.
*/
public void destroyExpiredSessions() {
List<HttpSessionWrapper> markedForDelete = new ArrayList<HttpSessionWrapper>();
long time = System.currentTimeMillis() / 1000;
for (HttpSessionWrapper session : sessions.values()) {
if (time - (session.getLastAccessedTime() / 1000) >= session.getMaxInactiveInterval()) {
markedForDelete.add(session);
}
}
for (HttpSessionWrapper session : markedForDelete) {
destroySession(session);
}
}
/**
* Remove all subsessions that were created from a given parent session.
*
* #param originalSession
* All subsessions created with this session as their parent session will be
* invalidated.
*/
public void clearAllSessions(HttpSession originalSession) {
Iterator<HttpSessionWrapper> it = sessions.values().iterator();
while (it.hasNext()) {
HttpSessionWrapper w = it.next();
if (w.getOriginalSession().getId().equals(originalSession.getId())) {
destroySession(w);
}
}
}
public void setSessionTimeout(int timeout) {
this.timeout = timeout;
}
}
A subsession is identified by a SubSessionKey. These key objects depend on the uiid and the ID of the parent session.
package session;
/**
* Key object for identifying a subsession.
*
*/
public class SubSessionKey {
private String sessionId;
private String uiid;
/**
* Create a new instance of {#link SubSessionKey}.
*
* #param sessionId
* The session id of the parent session.
* #param uiid
* The users's id this session is associated with.
*/
public SubSessionKey(String sessionId, String uiid) {
super();
this.sessionId = sessionId;
this.uiid = uiid;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((sessionId == null) ? 0 : sessionId.hashCode());
result = prime * result + ((uiid == null) ? 0 : uiid.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SubSessionKey other = (SubSessionKey) obj;
if (sessionId == null) {
if (other.sessionId != null)
return false;
} else if (!sessionId.equals(other.sessionId))
return false;
if (uiid == null) {
if (other.uiid != null)
return false;
} else if (!uiid.equals(other.uiid))
return false;
return true;
}
#Override
public String toString() {
return "SubSessionKey [sessionId=" + sessionId + ", uiid=" + uiid + "]";
}
}
The HttpServletRequestWrapper wraps a HttpServletRequest object. All methods are redirected to the wrapped request except for the getSession methods which will return an HttpSessionWrapper depending on the user ID in this request's parameters.
package session;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* Wrapper class that wraps a {#link HttpServletRequest} object. All methods are redirected to the
* wrapped request except for the <code>getSession</code> which will return an
* {#link HttpSessionWrapper} depending on the user id in this request's parameters.
*
*/
public class HttpServletRequestWrapper extends javax.servlet.http.HttpServletRequestWrapper {
private HttpServletRequest req;
public HttpServletRequestWrapper(HttpServletRequest req) {
super(req);
this.req = req;
}
#Override
public HttpSession getSession() {
return getSession(true);
}
#Override
public HttpSession getSession(boolean create) {
String[] uiid = getParameterMap().get("uiid");
if (uiid != null && uiid.length >= 1) {
return SingleSessionManager.getInstance().getSession(uiid[0], create, req.getSession(create));
}
return req.getSession(create);
}
}
The HttpSessionWrapper represents a subsession.
package session;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionContext;
/**
* Implementation of a HttpSession. Each instance of this class is created around a container
* managed parent session with it's lifetime linked to it's parent's.
*
*/
#SuppressWarnings("deprecation")
public class HttpSessionWrapper implements HttpSession {
private Map<String, Object> attributes;
private Map<String, Object> values;
private long creationTime;
private String id;
private String uiid;
private boolean isNew;
private long lastAccessedTime;
private HttpSession originalSession;
public HttpSessionWrapper(String uiid, HttpSession originalSession) {
creationTime = System.currentTimeMillis();
lastAccessedTime = creationTime;
id = SingleSessionManager.getInstance().generateSessionId();
isNew = true;
attributes = new HashMap<String, Object>();
Enumeration<String> names = originalSession.getAttributeNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
attributes.put(name, originalSession.getAttribute(name));
}
values = new HashMap<String, Object>();
for (String name : originalSession.getValueNames()) {
values.put(name, originalSession.getValue(name));
}
this.uiid = uiid;
this.originalSession = originalSession;
}
public String getUiid() {
return uiid;
}
public void setNew(boolean b) {
isNew = b;
}
public void setLastAccessedTime(long time) {
lastAccessedTime = time;
}
#Override
public Object getAttribute(String arg0) {
return attributes.get(arg0);
}
#Override
public Enumeration<String> getAttributeNames() {
return Collections.enumeration(attributes.keySet());
}
#Override
public long getCreationTime() {
return creationTime;
}
#Override
public String getId() {
return id;
}
#Override
public long getLastAccessedTime() {
return lastAccessedTime;
}
#Override
public int getMaxInactiveInterval() {
return SingleSessionManager.getInstance().getSessionTimeout();
}
#Override
public ServletContext getServletContext() {
return originalSession.getServletContext();
}
#Override
public HttpSessionContext getSessionContext() {
return new HttpSessionContext() {
#Override
public Enumeration<String> getIds() {
return Collections.enumeration(new HashSet<String>());
}
#Override
public HttpSession getSession(String arg0) {
return null;
}
};
}
#Override
public Object getValue(String arg0) {
return values.get(arg0);
}
#Override
public String[] getValueNames() {
return values.keySet().toArray(new String[values.size()]);
}
#Override
public void invalidate() {
SingleSessionManager.getInstance().destroySession(this);
}
#Override
public boolean isNew() {
return isNew;
}
#Override
public void putValue(String arg0, Object arg1) {
values.put(arg0, arg1);
}
#Override
public void removeAttribute(String arg0) {
attributes.remove(arg0);
}
#Override
public void removeValue(String arg0) {
values.remove(arg0);
}
#Override
public void setAttribute(String arg0, Object arg1) {
attributes.put(arg0, arg1);
}
#Override
public void setMaxInactiveInterval(int arg0) {
SingleSessionManager.getInstance().setSessionTimeout(arg0);
}
public HttpSession getOriginalSession() {
return originalSession;
}
}
The SessionInvalidator is an HttpSessionListener that takes care of cleaning all subsessions in case of the invalidation of their parent session.
package session;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* Session listener that listens for the destruction of a container managed session and takes care
* of destroying all it's subsessions.
* <p>
* Normally this listener won't have much to do since subsessions usually have a shorter lifetime
* than their parent session and therefore will timeout long before this method is called. This
* listener will only be important in case of an explicit invalidation of a parent session.
* </p>
*
*/
public class SessionInvalidator implements HttpSessionListener {
#Override
public void sessionCreated(HttpSessionEvent arg0) {
}
#Override
public void sessionDestroyed(HttpSessionEvent arg0) {
SingleSessionManager.getInstance().clearAllSessions(arg0.getSession());
}
}
Enable everything by putting the following in your web.xml
<filter>
<filter-name>SingleSessionFilter</filter-name>
<filter-class>de.supportgis.sgjWeb.session.SingleSessionManager</filter-class>
<!-- The timeout in minutes after which a subsession will be invalidated. It is recommended to set a session timeout for the servled container using the parameter "session-timeout", which is higher than this value. -->
<init-param>
<param-name>sessionTimeout</param-name>
<param-value>1</param-value>
</init-param>
<init-param>
<!-- The intervall in seconds in which a check for expired sessions will be performed. -->
<param-name>sessionInvalidationCheck</param-name>
<param-value>15</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>SingleSessionFilter</filter-name>
<!-- Insert the name of your servlet here to which the session management should apply, or use url-pattern instead. -->
<servlet-name>YourServlet</servlet-name>
</filter-mapping>
<listener>
<listener-class>session.SessionInvalidator</listener-class>
</listener>
<!-- Timeout of the parent session -->
<session-config>
<session-timeout>40</session-timeout>
<!-- Session timeout interval in minutes -->
</session-config>
I think you're looking for something like Apache Tomcat. It will manage individual sessions for individual servlet applications.
The session is unique for a combination of user and web application. You can of course deploy your servlet in several web applications on the same Tomcat instance, but you will not be able to route the HTTP request to different web applications simply based on URL parameters unless you evaluate the URL parameters in a second servlet and redirect the browser to a new URL for the specific web app.
Different servlet containers or J2EE app servers may have different options for routing requests to specific web applications, but AFAIK out of the box, Tomcat can only delegate the request based on either host name or base directory, e.g.:
http://app1/... or http://server/app1/... is delegated to app1
http://app2/... or http://server/app2/... is delegated to app2, and so on
here is a bug fix for user3792852's reply
public HttpSession getSession(String uiid, boolean create, HttpSession originalSession)
{
if (uiid != null && originalSession != null)
{
SubSessionKey key = new SubSessionKey(originalSession.getId(), uiid);
synchronized (sessions)
{
HttpSessionWrapper session = sessions.get(key);
if (session == null && create)
{
session = new HttpSessionWrapper(uiid, originalSession);
sessions.put(key, session);
}
if (session != null)
{
session.setLastAccessedTime(System.currentTimeMillis());
}
return session;
}
}
return null;
}

Categories