You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by re...@apache.org on 2007/04/17 01:30:56 UTC

svn commit: r529444 - in /tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager: HTMLManagerServlet.java JspHelper.java util/ util/BaseSessionComparator.java util/ReverseComparator.java util/SessionUtils.java

Author: remm
Date: Mon Apr 16 16:30:55 2007
New Revision: 529444

URL: http://svn.apache.org/viewvc?view=rev&rev=529444
Log:
- Add session browser capabilities in the manager. Let me know if it creates problems (I checked XSS to some extent to,
  but please double check if you can).
- Submitted by Cédrik Lime.

Added:
    tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/JspHelper.java   (with props)
    tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/util/
    tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/util/BaseSessionComparator.java   (with props)
    tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/util/ReverseComparator.java   (with props)
    tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/util/SessionUtils.java   (with props)
Modified:
    tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/HTMLManagerServlet.java

Modified: tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/HTMLManagerServlet.java
URL: http://svn.apache.org/viewvc/tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/HTMLManagerServlet.java?view=diff&rev=529444&r1=529443&r2=529444
==============================================================================
--- tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/HTMLManagerServlet.java (original)
+++ tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/HTMLManagerServlet.java Mon Apr 16 16:30:55 2007
@@ -23,6 +23,10 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -30,9 +34,14 @@
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
 
 import org.apache.catalina.Container;
 import org.apache.catalina.Context;
+import org.apache.catalina.Session;
+import org.apache.catalina.manager.util.BaseSessionComparator;
+import org.apache.catalina.manager.util.ReverseComparator;
+import org.apache.catalina.manager.util.SessionUtils;
 import org.apache.catalina.util.RequestUtil;
 import org.apache.catalina.util.ServerInfo;
 import org.apache.tomcat.util.http.fileupload.DiskFileUpload;
@@ -50,7 +59,7 @@
 * makes it easier to administrate.
 * <p>
 * However if you use a software that parses the output of
-* <code>ManagerServlet</code you won't be able to upgrade
+* <code>ManagerServlet</code> you won't be able to upgrade
 * to this Servlet since the output are not in the
 * same format ar from <code>ManagerServlet</code>
 *
@@ -63,6 +72,11 @@
 
 public final class HTMLManagerServlet extends ManagerServlet {
 
+    protected static final String APPLICATION_MESSAGE = "message";
+    protected static final String APPLICATION_ERROR = "error";
+    protected String sessionsListJspPath  = "/sessionsList.jsp";
+    protected String sessionDetailJspPath = "/sessionDetail.jsp";
+
     // --------------------------------------------------------- Public Methods
 
     /**
@@ -100,7 +114,15 @@
         } else if (command.equals("/undeploy")) {
             message = undeploy(path);
         } else if (command.equals("/sessions")) {
-            message = sessions(path);
+            //message = sessions(path);
+            try {
+                doSessions(path, request, response);
+                return;
+            } catch (Exception e) {
+                log("HTMLManagerServlet.sessions[" + path + "]", e);
+                message = sm.getString("managerServlet.exception",
+                        e.toString());
+            }
         } else if (command.equals("/start")) {
             message = start(path);
         } else if (command.equals("/stop")) {
@@ -562,6 +584,309 @@
         return stringWriter.toString();
     }
 
+    /**
+     * @see javax.servlet.Servlet#getServletInfo()
+     */
+    public String getServletInfo() {
+        return "HTMLManagerServlet, Copyright (c) The Apache Software Foundation";
+    }   
+    
+    /**
+     * @see javax.servlet.GenericServlet#init()
+     */
+    public void init() throws ServletException {
+        super.init();
+    }   
+
+    // ------------------------------------------------ Sessions administration
+
+    /**
+     * 
+     * @param req
+     * @param resp
+     * @throws ServletException
+     * @throws IOException 
+     */
+    protected void doSessions(String path, HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        req.setAttribute("path", path);
+        String action = req.getParameter("action");
+        if (debug >= 1) {
+            log("sessions: Session action '" + action + "' for web application at '" + path + "'");
+        }
+        if ("sessionDetail".equals(action)) {
+	        String sessionId = req.getParameter("sessionId");
+	        displaySessionDetailPage(req, resp, path, sessionId);
+	        return;
+        } else if ("invalidateSessions".equals(action)) {
+            String[] sessionIds = req.getParameterValues("sessionIds");
+            int i = invalidateSessions(path, sessionIds);
+            req.setAttribute(APPLICATION_MESSAGE, "" + i + " sessions invalidated.");
+        } else if ("removeSessionAttribute".equals(action)) {
+            String sessionId = req.getParameter("sessionId");
+            String name = req.getParameter("attributeName");
+            boolean removed = removeSessionAttribute(path, sessionId, name);
+            String outMessage = removed ? "Session attribute '" + name + "' removed." : "Session did not contain any attribute named '" + name + "'";
+            req.setAttribute(APPLICATION_MESSAGE, outMessage);
+            resp.sendRedirect(resp.encodeRedirectURL(req.getRequestURL().append("?path=").append(path).append("&action=sessionDetail&sessionId=").append(sessionId).toString()));
+            return;
+        } // else
+        displaySessionsListPage(path, req, resp);
+    }
+
+    protected Session[] getSessionsForPath(String path) {
+        if ((path == null) || (!path.startsWith("/") && path.equals(""))) {
+            throw new IllegalArgumentException(sm.getString("managerServlet.invalidPath",
+                                        RequestUtil.filter(path)));
+        }
+        String displayPath = path;
+        if( path.equals("/") )
+            path = "";
+        Context context = (Context) host.findChild(path);
+        if (null == context) {
+            throw new IllegalArgumentException(sm.getString("managerServlet.noContext",
+                                        RequestUtil.filter(displayPath)));
+        }
+        Session[] sessions = context.getManager().findSessions();
+        return sessions;
+    }
+    protected Session getSessionForPathAndId(String path, String id) throws IOException {
+        if ((path == null) || (!path.startsWith("/") && path.equals(""))) {
+            throw new IllegalArgumentException(sm.getString("managerServlet.invalidPath",
+                                        RequestUtil.filter(path)));
+        }
+        String displayPath = path;
+        if( path.equals("/") )
+            path = "";
+        Context context = (Context) host.findChild(path);
+        if (null == context) {
+            throw new IllegalArgumentException(sm.getString("managerServlet.noContext",
+                                        RequestUtil.filter(displayPath)));
+        }
+        Session session = context.getManager().findSession(id);
+        return session;
+    }
+
+    /**
+     * 
+     * @param req
+     * @param resp
+     * @throws ServletException
+     * @throws IOException
+     */
+    protected void displaySessionsListPage(String path, HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        List/*<Session>*/ activeSessions = Arrays.asList(getSessionsForPath(path));
+        String sortBy = req.getParameter("sort");
+        String orderBy = null;
+        if (null != sortBy && !"".equals(sortBy.trim())) {
+            Comparator comparator = getComparator(sortBy);
+            if (comparator != null) {
+                orderBy = req.getParameter("order");
+                if ("DESC".equalsIgnoreCase(orderBy)) {
+                    comparator = new ReverseComparator(comparator);
+                    // orderBy = "ASC";
+                } else {
+                    //orderBy = "DESC";
+                }
+                try {
+					Collections.sort(activeSessions, comparator);
+				} catch (IllegalStateException ise) {
+					// at least 1 of the sessions is invalidated
+					req.setAttribute(APPLICATION_ERROR, "Can't sort session list: one session is invalidated");
+				}
+            } else {
+                log("WARNING: unknown sort order: " + sortBy);
+            }
+        }
+        // keep sort order
+        req.setAttribute("sort", sortBy);
+        req.setAttribute("order", orderBy);
+        req.setAttribute("activeSessions", activeSessions);
+        //strong>NOTE</strong> - This header will be overridden
+        // automatically if a <code>RequestDispatcher.forward()</code> call is
+        // ultimately invoked.
+        resp.setHeader("Pragma", "No-cache"); // HTTP 1.0
+        resp.setHeader("Cache-Control", "no-cache,no-store,max-age=0"); // HTTP 1.1
+        resp.setDateHeader("Expires", 0); // 0 means now
+        getServletContext().getRequestDispatcher(sessionsListJspPath).include(req, resp);
+    }
+
+    /**
+     * 
+     * @param req
+     * @param resp
+     * @throws ServletException
+     * @throws IOException
+     */
+    protected void displaySessionDetailPage(HttpServletRequest req, HttpServletResponse resp, String path, String sessionId) throws ServletException, IOException {
+        Session session = getSessionForPathAndId(path, sessionId);
+        //strong>NOTE</strong> - This header will be overridden
+        // automatically if a <code>RequestDispatcher.forward()</code> call is
+        // ultimately invoked.
+        resp.setHeader("Pragma", "No-cache"); // HTTP 1.0
+        resp.setHeader("Cache-Control", "no-cache,no-store,max-age=0"); // HTTP 1.1
+        resp.setDateHeader("Expires", 0); // 0 means now
+        req.setAttribute("currentSession", session);
+        getServletContext().getRequestDispatcher(sessionDetailJspPath).include(req, resp);
+    }
+
+    /**
+     * Invalidate HttpSessions
+     * @param sessionIds
+     * @return number of invalidated sessions
+     * @throws IOException 
+     */
+    public int invalidateSessions(String path, String[] sessionIds) throws IOException {
+        if (null == sessionIds) {
+            return 0;
+        }
+        int nbAffectedSessions = 0;
+        for (int i = 0; i < sessionIds.length; ++i) {
+            String sessionId = sessionIds[i];
+            HttpSession session = getSessionForPathAndId(path, sessionId).getSession();
+            if (null == session) {
+                // Shouldn't happen, but let's play nice...
+            	if (debug >= 1) {
+            		log("WARNING: can't invalidate null session " + sessionId);
+            	}
+                continue;
+            }
+            try {
+				session.invalidate();
+				++nbAffectedSessions;
+	            if (debug >= 1) {
+	                log("Invalidating session id " + sessionId);
+	            }
+			} catch (IllegalStateException ise) {
+				if (debug >= 1) {
+					log("Can't invalidate already invalidated session id " + sessionId);
+				}
+			}
+        }
+        return nbAffectedSessions;
+    }
+
+    /**
+     * Removes an attribute from an HttpSession
+     * @param sessionId
+     * @param attributeName
+     * @return true if there was an attribute removed, false otherwise
+     * @throws IOException 
+     */
+    public boolean removeSessionAttribute(String path, String sessionId, String attributeName) throws IOException {
+        HttpSession session = getSessionForPathAndId(path, sessionId).getSession();
+        if (null == session) {
+            // Shouldn't happen, but let's play nice...
+        	if (debug >= 1) {
+        		log("WARNING: can't remove attribute '" + attributeName + "' for null session " + sessionId);
+        	}
+            return false;
+        }
+        boolean wasPresent = (null != session.getAttribute(attributeName));
+        try {
+            session.removeAttribute(attributeName);
+        } catch (IllegalStateException ise) {
+        	if (debug >= 1) {
+        		log("Can't remote attribute '" + attributeName + "' for invalidated session id " + sessionId);
+        	}
+        }
+        return wasPresent;
+    }
+
+    /**
+     * Sets the maximum inactive interval (session timeout) an HttpSession
+     * @param sessionId
+     * @param maxInactiveInterval in seconds
+     * @return old value for maxInactiveInterval
+     * @throws IOException 
+     */
+    public int setSessionMaxInactiveInterval(String path, String sessionId, int maxInactiveInterval) throws IOException {
+        HttpSession session = getSessionForPathAndId(path, sessionId).getSession();
+        if (null == session) {
+            // Shouldn't happen, but let's play nice...
+        	if (debug >= 1) {
+        		log("WARNING: can't set timout for null session " + sessionId);
+        	}
+            return 0;
+        }
+        try {
+			int oldMaxInactiveInterval = session.getMaxInactiveInterval();
+			session.setMaxInactiveInterval(maxInactiveInterval);
+			return oldMaxInactiveInterval;
+        } catch (IllegalStateException ise) {
+        	if (debug >= 1) {
+        		log("Can't set MaxInactiveInterval '" + maxInactiveInterval + "' for invalidated session id " + sessionId);
+        	}
+        	return 0;
+		}
+    }
+
+    protected Comparator getComparator(String sortBy) {
+        Comparator comparator = null;
+        if ("CreationTime".equalsIgnoreCase(sortBy)) {
+            comparator = new BaseSessionComparator() {
+                public Comparable getComparableObject(Session session) {
+                    return new Date(session.getCreationTime());
+                }
+            };
+        } else if ("id".equalsIgnoreCase(sortBy)) {
+            comparator = new BaseSessionComparator() {
+                public Comparable getComparableObject(Session session) {
+                    return session.getId();
+                }
+            };
+        } else if ("LastAccessedTime".equalsIgnoreCase(sortBy)) {
+            comparator = new BaseSessionComparator() {
+                public Comparable getComparableObject(Session session) {
+                    return new Date(session.getLastAccessedTime());
+                }
+            };
+        } else if ("MaxInactiveInterval".equalsIgnoreCase(sortBy)) {
+            comparator = new BaseSessionComparator() {
+                public Comparable getComparableObject(Session session) {
+                    return new Date(session.getMaxInactiveInterval());
+                }
+            };
+        } else if ("new".equalsIgnoreCase(sortBy)) {
+            comparator = new BaseSessionComparator() {
+                public Comparable getComparableObject(Session session) {
+                    return Boolean.valueOf(session.getSession().isNew());
+                }
+            };
+        } else if ("locale".equalsIgnoreCase(sortBy)) {
+            comparator = new BaseSessionComparator() {
+                public Comparable getComparableObject(Session session) {
+                    return JspHelper.guessDisplayLocaleFromSession(session);
+                }
+            };
+        } else if ("user".equalsIgnoreCase(sortBy)) {
+            comparator = new BaseSessionComparator() {
+                public Comparable getComparableObject(Session session) {
+                    return JspHelper.guessDisplayUserFromSession(session);
+                }
+            };
+        } else if ("UsedTime".equalsIgnoreCase(sortBy)) {
+            comparator = new BaseSessionComparator() {
+                public Comparable getComparableObject(Session session) {
+                    return new Date(SessionUtils.getUsedTimeForSession(session));
+                }
+            };
+        } else if ("InactiveTime".equalsIgnoreCase(sortBy)) {
+            comparator = new BaseSessionComparator() {
+                public Comparable getComparableObject(Session session) {
+                    return new Date(SessionUtils.getInactiveTimeForSession(session));
+                }
+            };
+        } else if ("TTL".equalsIgnoreCase(sortBy)) {
+            comparator = new BaseSessionComparator() {
+                public Comparable getComparableObject(Session session) {
+                    return new Date(SessionUtils.getTTLForSession(session));
+                }
+            };
+        }
+        //TODO: complete this to TTL, etc.
+        return comparator;
+    }
+
     // ------------------------------------------------------ Private Constants
 
     // These HTML sections are broken in relatively small sections, because of
@@ -586,7 +911,7 @@
         " <td class=\"row-left\" bgcolor=\"{5}\"><small><a href=\"{0}\">{0}</a></small></td>\n" +
         " <td class=\"row-left\" bgcolor=\"{5}\"><small>{1}</small></td>\n" +
         " <td class=\"row-center\" bgcolor=\"{5}\"><small>{2}</small></td>\n" +
-        " <td class=\"row-center\" bgcolor=\"{5}\"><small><a href=\"{3}\">{4}</a></small></td>\n";
+        " <td class=\"row-center\" bgcolor=\"{5}\"><small><a href=\"{3}\" target=\"_new\">{4}</a></small></td>\n";
 
     private static final String MANAGER_APP_ROW_BUTTON_SECTION =
         " <td class=\"row-left\" bgcolor=\"{8}\">\n" +

Added: tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/JspHelper.java
URL: http://svn.apache.org/viewvc/tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/JspHelper.java?view=auto&rev=529444
==============================================================================
--- tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/JspHelper.java (added)
+++ tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/JspHelper.java Mon Apr 16 16:30:55 2007
@@ -0,0 +1,239 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.manager;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.text.DateFormat;
+import java.text.NumberFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import org.apache.catalina.Session;
+import org.apache.catalina.manager.util.SessionUtils;
+
+
+/**
+ * Helper JavaBean for JSPs, because JSTL 1.1/EL 2.0 is too dumb to
+ * to what I need (call methods with parameters), or I am too dumb to use it correctly. :)
+ * @author C&eacute;drik LIME
+ */
+public class JspHelper {
+
+    private static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+    private static final String DATE_FORMAT = "yyyy-MM-dd";
+    private static final String TIME_FORMAT = "HH:mm:ss";
+
+    /**
+     * Public constructor, so that this class can be considered a JavaBean
+     */
+    private JspHelper() {
+        super();
+    }
+
+    /**
+     * Try to get user locale from the session, if possible.
+     * IMPLEMENTATION NOTE: this method has explicit support for Tapestry 3 and Struts 1.x
+     * @param in_session
+     * @return String
+     */
+    public static String guessDisplayLocaleFromSession(Session in_session) {
+        return localeToString(SessionUtils.guessLocaleFromSession(in_session));
+    }
+    private static String localeToString(Locale locale) {
+        if (locale != null) {
+            return locale.toString();//locale.getDisplayName();
+        } else {
+            return "";
+        }
+    }
+
+    /**
+     * Try to get user name from the session, if possible.
+     * @param in_session
+     * @return String
+     */
+    public static String guessDisplayUserFromSession(Session in_session) {
+        Object user = SessionUtils.guessUserFromSession(in_session);
+        return escapeXml(user);
+    }
+
+
+    public static String getDisplayCreationTimeForSession(Session in_session) {
+        try {
+			DateFormat formatter = new SimpleDateFormat(DATE_TIME_FORMAT);
+			return formatter.format(new Date(in_session.getCreationTime()));
+        } catch (IllegalStateException ise) {
+        	//ignore: invalidated session
+        	return "";
+		}
+    }
+
+    public static String getDisplayLastAccessedTimeForSession(Session in_session) {
+        try {
+			DateFormat formatter = new SimpleDateFormat(DATE_TIME_FORMAT);
+			return formatter.format(new Date(in_session.getLastAccessedTime()));
+        } catch (IllegalStateException ise) {
+        	//ignore: invalidated session
+        	return "";
+		}
+    }
+
+    public static String getDisplayUsedTimeForSession(Session in_session) {
+        return secondsToTimeString(SessionUtils.getUsedTimeForSession(in_session)/1000);
+    }
+
+    public static String getDisplayTTLForSession(Session in_session) {
+        return secondsToTimeString(SessionUtils.getTTLForSession(in_session)/1000);
+    }
+
+    public static String getDisplayInactiveTimeForSession(Session in_session) {
+        return secondsToTimeString(SessionUtils.getInactiveTimeForSession(in_session)/1000);
+    }
+
+    public static String secondsToTimeString(long in_seconds) {
+        StringBuffer buff = new StringBuffer(9);
+        long rest = in_seconds;
+        long hour = rest / 3600;
+        rest = rest % 3600;
+        long minute = rest / 60;
+        rest = rest % 60;
+        long second = rest;
+        if (hour < 10) {
+            buff.append('0');
+        }
+        buff.append(hour);
+        buff.append(':');
+        if (minute < 10) {
+            buff.append('0');
+        }
+        buff.append(minute);
+        buff.append(':');
+        if (second < 10) {
+            buff.append('0');
+        }
+        buff.append(second);
+        return buff.toString();
+    }
+
+
+    /**
+    * Following copied from org.apache.taglibs.standard.tag.common.core.OutSupport v1.1.2
+    * 
+    *  Optimized to create no extra objects and write directly
+    *  to the JspWriter using blocks of escaped and unescaped characters
+    *
+    */
+   private static void writeEscapedXml(char[] buffer, int length, Writer w) throws IOException {
+       int start = 0;
+
+       for (int i = 0; i < length; i++) {
+           char c = buffer[i];
+           if (c <= HIGHEST_SPECIAL) {
+               char[] escaped = specialCharactersRepresentation[c];
+               if (escaped != null) {
+                   // add unescaped portion
+                   if (start < i) {
+                       w.write(buffer,start,i-start);
+                   }
+                   // add escaped xml
+                   w.write(escaped);
+                   start = i + 1;
+               }
+           }
+       }
+       // add rest of unescaped portion
+       if (start < length) {
+           w.write(buffer,start,length-start);
+       }
+   }
+
+
+    /*
+     * Following copied from org.apache.taglibs.standard.tag.common.core.Util v1.1.2
+     */
+
+    private static final int HIGHEST_SPECIAL = '>';
+    private static char[][] specialCharactersRepresentation = new char[HIGHEST_SPECIAL + 1][];
+    static {
+        specialCharactersRepresentation['&'] = "&amp;".toCharArray();
+        specialCharactersRepresentation['<'] = "&lt;".toCharArray();
+        specialCharactersRepresentation['>'] = "&gt;".toCharArray();
+        specialCharactersRepresentation['"'] = "&#034;".toCharArray();
+        specialCharactersRepresentation['\''] = "&#039;".toCharArray();
+    }
+
+    public static String escapeXml(Object obj) {
+    	return obj == null ? "" : escapeXml(String.valueOf(obj));
+    }
+    /**
+     * Performs the following substring replacements
+     * (to facilitate output to XML/HTML pages):
+     *
+     *    & -> &amp;
+     *    < -> &lt;
+     *    > -> &gt;
+     *    " -> &#034;
+     *    ' -> &#039;
+     *
+     * See also OutSupport.writeEscapedXml().
+     */
+    public static String escapeXml(String buffer) {
+    	if (buffer == null) {
+			return "";
+		}
+        int start = 0;
+        int length = buffer.length();
+        char[] arrayBuffer = buffer.toCharArray();
+        StringBuffer escapedBuffer = null;
+
+        for (int i = 0; i < length; i++) {
+            char c = arrayBuffer[i];
+            if (c <= HIGHEST_SPECIAL) {
+                char[] escaped = specialCharactersRepresentation[c];
+                if (escaped != null) {
+                    // create StringBuffer to hold escaped xml string
+                    if (start == 0) {
+                        escapedBuffer = new StringBuffer(length + 5);
+                    }
+                    // add unescaped portion
+                    if (start < i) {
+                        escapedBuffer.append(arrayBuffer,start,i-start);
+                    }
+                    start = i + 1;
+                    // add escaped xml
+                    escapedBuffer.append(escaped);
+                }
+            }
+        }
+        // no xml escaping was necessary
+        if (start == 0) {
+            return buffer;
+        }
+        // add rest of unescaped portion
+        if (start < length) {
+            escapedBuffer.append(arrayBuffer,start,length-start);
+        }
+        return escapedBuffer.toString();
+    }
+
+    public static String formatNumber(long number) {
+    	return NumberFormat.getNumberInstance().format(number);
+    }
+}

Propchange: tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/JspHelper.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/util/BaseSessionComparator.java
URL: http://svn.apache.org/viewvc/tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/util/BaseSessionComparator.java?view=auto&rev=529444
==============================================================================
--- tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/util/BaseSessionComparator.java (added)
+++ tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/util/BaseSessionComparator.java Mon Apr 16 16:30:55 2007
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.manager.util;
+
+import java.util.Comparator;
+
+import org.apache.catalina.Session;
+
+/**
+ * Comparator which permits to compare on a session's content
+ * @author C&eacute;drik LIME
+ */
+public abstract class BaseSessionComparator implements Comparator {
+
+    /**
+     * 
+     */
+    public BaseSessionComparator() {
+        super();
+    }
+
+    public abstract Comparable getComparableObject(Session session);
+
+    /* (non-Javadoc)
+     * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
+     */
+    public final int compare(Object o1, Object o2) {
+        Comparable c1 = getComparableObject((Session)o1);
+        Comparable c2 = getComparableObject((Session)o2);
+        return c1==null ? (c2==null ? 0 : -1) : (c2==null ? 1 : c1.compareTo(c2));
+    }
+}

Propchange: tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/util/BaseSessionComparator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/util/ReverseComparator.java
URL: http://svn.apache.org/viewvc/tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/util/ReverseComparator.java?view=auto&rev=529444
==============================================================================
--- tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/util/ReverseComparator.java (added)
+++ tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/util/ReverseComparator.java Mon Apr 16 16:30:55 2007
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.manager.util;
+
+import java.util.Comparator;
+
+/**
+ * Comparator which reverse the sort order
+ * @author C&eacute;drik LIME
+ */
+public class ReverseComparator implements Comparator {
+    protected Comparator comparator;
+
+    /**
+     * 
+     */
+    public ReverseComparator(Comparator comparator) {
+        super();
+        this.comparator = comparator;
+    }
+
+    /* (non-Javadoc)
+     * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
+     */
+    public int compare(Object o1, Object o2) {
+        int returnValue = comparator.compare(o1, o2);
+        return (- returnValue);
+    }
+}

Propchange: tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/util/ReverseComparator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/util/SessionUtils.java
URL: http://svn.apache.org/viewvc/tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/util/SessionUtils.java?view=auto&rev=529444
==============================================================================
--- tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/util/SessionUtils.java (added)
+++ tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/util/SessionUtils.java Mon Apr 16 16:30:55 2007
@@ -0,0 +1,267 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.catalina.manager.util;
+
+import java.lang.reflect.Method;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Locale;
+
+import javax.security.auth.Subject;
+import javax.servlet.http.HttpSession;
+
+import org.apache.catalina.Session;
+
+/**
+ * Utility methods on HttpSessions...
+ * @author C&eacute;drik LIME
+ */
+public class SessionUtils {
+
+    /**
+     * 
+     */
+    private SessionUtils() {
+        super();
+    }
+
+    /**
+     * The session attributes key under which the user's selected
+     * <code>java.util.Locale</code> is stored, if any.
+     */
+    // org.apache.struts.Globals.LOCALE_KEY
+    private static final String STRUTS_LOCALE_KEY = "org.apache.struts.action.LOCALE";//$NON-NLS-1$
+    // javax.servlet.jsp.jstl.core.Config.FMT_LOCALE
+    private static final String JSTL_LOCALE_KEY   = "javax.servlet.jsp.jstl.fmt.locale";//$NON-NLS-1$
+    // org.springframework.web.servlet.i18n.SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME
+    private static final String SPRING_LOCALE_KEY = "org.springframework.web.servlet.i18n.SessionLocaleResolver.LOCALE";//$NON-NLS-1$
+    /**
+     * Lower and upper-case strings will be dynamically generated. Put mid-capitalised strings here!
+     */
+    private static final String[] LOCALE_TEST_ATTRIBUTES = new String[] {
+        STRUTS_LOCALE_KEY, SPRING_LOCALE_KEY, JSTL_LOCALE_KEY, "Locale", "java.util.Locale" };
+    /**
+     * Lower and upper-case strings will be dynamically generated. Put mid-capitalised strings here!
+     */
+    private static final String[] USER_TEST_ATTRIBUTES = new String[] {
+        "Login", "User", "userName", "UserName", "Utilisateur" };
+
+    /**
+     * Try to get user locale from the session, if possible.
+     * IMPLEMENTATION NOTE: this method has explicit support for Tapestry 3, Struts 1.x and Spring
+     * JSF check the browser meta tag "accept languages" to choose what langage to display.
+     * @param in_session
+     * @return String
+     */
+    public static Locale guessLocaleFromSession(final Session in_session) {
+    	return guessLocaleFromSession(in_session.getSession());
+    }
+    public static Locale guessLocaleFromSession(final HttpSession in_session) {
+    	if (null == in_session) {
+			return null;
+		}
+        try {
+            Locale locale = null;
+            
+	        // First search "known locations"
+	        for (int i = 0; i < LOCALE_TEST_ATTRIBUTES.length; ++i) {
+	            Object obj = in_session.getAttribute(LOCALE_TEST_ATTRIBUTES[i]);
+	            if (null != obj && obj instanceof Locale) {
+	                locale = (Locale) obj;
+	                break;
+	            }
+	            obj = in_session.getAttribute(LOCALE_TEST_ATTRIBUTES[i].toLowerCase());
+	            if (null != obj && obj instanceof Locale) {
+	                locale = (Locale) obj;
+	                break;
+	            }
+	            obj = in_session.getAttribute(LOCALE_TEST_ATTRIBUTES[i].toUpperCase());
+	            if (null != obj && obj instanceof Locale) {
+	                locale = (Locale) obj;
+	                break;
+	            }
+	        }
+	
+	        if (null != locale) {
+	            return locale;
+	        }
+	
+	        // Tapestry 3.0: Engine stored in session under "org.apache.tapestry.engine:" + config.getServletName()
+	        // TODO: Tapestry 4+
+	        {
+	            final List tapestryArray = new ArrayList();
+	            for (Enumeration enumeration = in_session.getAttributeNames(); enumeration.hasMoreElements();) {
+	                String name = (String) enumeration.nextElement();
+	                if (name.indexOf("tapestry") > -1 && name.indexOf("engine") > -1 && null != in_session.getAttribute(name)) {//$NON-NLS-1$ //$NON-NLS-2$
+	                    tapestryArray.add(in_session.getAttribute(name));
+	                }
+	            }
+	            if (tapestryArray.size() == 1) {
+	                // found a potential Engine! Let's call getLocale() on it.
+	                Object probableEngine = tapestryArray.get(0);
+	                if (null != probableEngine) {
+	                    try {
+	                        Method readMethod = probableEngine.getClass().getMethod("getLocale", null);//$NON-NLS-1$
+	                        if (null != readMethod) {
+	                            // Call the property getter and return the value
+	                            Object possibleLocale = readMethod.invoke(probableEngine, null);
+	                            if (null != possibleLocale && possibleLocale instanceof Locale) {
+	                                locale = (Locale) possibleLocale;
+	                            }
+	                        }
+	                    } catch (Exception e) {
+	                        // stay silent
+	                    }
+	                }
+	            }
+	        }
+	        
+	        if (null != locale) {
+	            return locale;
+	        }
+	
+	        // Last guess: iterate over all attributes, to find a Locale
+	        // If there is only one, consider it to be /the/ locale
+	        {
+	            final List localeArray = new ArrayList();
+	            for (Enumeration enumeration = in_session.getAttributeNames(); enumeration.hasMoreElements();) {
+	                String name = (String) enumeration.nextElement();
+	                Object obj = in_session.getAttribute(name);
+	                if (null != obj && obj instanceof Locale) {
+	                    localeArray.add(obj);
+	                }
+	            }
+	            if (localeArray.size() == 1) {
+	                locale = (Locale) localeArray.get(0);
+	            }
+	        }
+
+	        return locale;
+        } catch (IllegalStateException ise) {
+        	//ignore: invalidated session
+        	return null;
+        }
+    }
+
+    /**
+     * Try to get user from the session, if possible.
+     * @param in_session
+     * @return Object
+     */
+    public static Object guessUserFromSession(final Session in_session) {
+    	if (null == in_session) {
+			return null;
+		}
+    	if (in_session.getPrincipal() != null) {
+			return in_session.getPrincipal().getName();
+		}
+    	HttpSession httpSession = in_session.getSession();
+    	try {
+	        Object user = null;
+	        // First search "known locations"
+	        for (int i = 0; i < USER_TEST_ATTRIBUTES.length; ++i) {
+	            Object obj = httpSession.getAttribute(USER_TEST_ATTRIBUTES[i]);
+	            if (null != obj) {
+	                user = obj;
+	                break;
+	            }
+	            obj = httpSession.getAttribute(USER_TEST_ATTRIBUTES[i].toLowerCase());
+	            if (null != obj) {
+	                user = obj;
+	                break;
+	            }
+	            obj = httpSession.getAttribute(USER_TEST_ATTRIBUTES[i].toUpperCase());
+	            if (null != obj) {
+	                user = obj;
+	                break;
+	            }
+	        }
+	
+	        if (null != user) {
+	            return user;
+	        }
+	
+	        // Last guess: iterate over all attributes, to find a java.security.Principal or javax.security.auth.Subject
+	        // If there is only one, consider it to be /the/ user
+	        {
+	            final List principalArray = new ArrayList();
+	            for (Enumeration enumeration = httpSession.getAttributeNames(); enumeration.hasMoreElements();) {
+	                String name = (String) enumeration.nextElement();
+	                Object obj = httpSession.getAttribute(name);
+	                if (null != obj && (obj instanceof Principal || obj instanceof Subject)) {
+	                    principalArray.add(obj);
+	                }
+	                // This workaround for JDK 1.3 compatibility. For JDK 1.4+, use previous (commented) instanceof.
+//	                try {
+//	                    Class subjectClass = Class.forName("javax.security.auth.Subject", true, Thread.currentThread().getContextClassLoader());
+//	                    if (subjectClass.isInstance(obj)) {
+//	                        principalArray.add(obj);
+//	                    }
+//	                } catch (ClassNotFoundException cnfe) {
+//	                    // This is JDK 1.3: javax.security.auth.Subject does not exist; do nothing
+//	                }
+	            }
+	            if (principalArray.size() == 1) {
+	                user = principalArray.get(0);
+	            }
+	        }
+	
+	        if (null != user) {
+	            return user;
+	        }
+
+	        return user;
+        } catch (IllegalStateException ise) {
+        	//ignore: invalidated session
+        	return null;
+        }
+    }
+
+
+    public static long getUsedTimeForSession(Session in_session) {
+        try {
+			long diffMilliSeconds = in_session.getLastAccessedTime() - in_session.getCreationTime();
+			return diffMilliSeconds;
+        } catch (IllegalStateException ise) {
+        	//ignore: invalidated session
+        	return -1;
+		}
+    }
+
+    public static long getTTLForSession(Session in_session) {
+        try {
+			long diffMilliSeconds = (1000*in_session.getMaxInactiveInterval()) - (System.currentTimeMillis() - in_session.getLastAccessedTime());
+			return diffMilliSeconds;
+        } catch (IllegalStateException ise) {
+        	//ignore: invalidated session
+        	return -1;
+		}
+    }
+
+    public static long getInactiveTimeForSession(Session in_session) {
+        try {
+			long diffMilliSeconds =  System.currentTimeMillis() - in_session.getLastAccessedTime();
+			return diffMilliSeconds;
+        } catch (IllegalStateException ise) {
+        	//ignore: invalidated session
+        	return -1;
+		}
+    }
+}

Propchange: tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager/util/SessionUtils.java
------------------------------------------------------------------------------
    svn:eol-style = native



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: svn commit: r529444 - in /tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager: HTMLManagerServlet.java JspHelper.java util/ util/BaseSessionComparator.java util/ReverseComparator.java util/SessionUtils.java

Posted by Rainer Jung <ra...@kippdata.de>.
I tested and updated my patch. It now uses the URL /expire and leaves 
/sessions to Rémys session administration extensions.

The patch builds against 6.0.x head.

The functionality works in HTML Manager and also in the non HTML manager 
servlet. In HTML-Manager you can test it by Clicking on the "Expire 
Sessions" button in the apps list. The default timeout value is the 
webapp session timeout. The button expires sessions which are longer 
idle than the value in the form and also it produces an answer message 
in the message part of the list with an idle time histogram of all sessions.

http://people.apache.org/~rjung/patches/tomcatManagerSessionExpireCodePatch.txt 


Still an old docs patch against 5.0.28 (just to get an idea) is:

http://people.apache.org/~rjung/patches/tomcatManagerSessionExpireDocsPatch.txt 


Regards,

Rainer

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: svn commit: r529444 - in /tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager: HTMLManagerServlet.java JspHelper.java util/ util/BaseSessionComparator.java util/ReverseComparator.java util/SessionUtils.java

Posted by Rainer Jung <ra...@kippdata.de>.
I'm a little narrow on time at the moment, but I did a quick port of my 
old 5.0 patch to 6.0 head.

I didn't yet test the 6.0 port (nor build it), but you might get an idea 
about the amount of things the patch touches. My original 5.0 patch was 
used quite some time.

I won't insist on URL/action the feature can be called by. Originally it 
was an extension to /sessions for both the Manager and the HTML Manager. 
Since now you added nice detailed sessions functions to /sessions, it 
would either be another URL or a sub action. To make the patch simple, I 
  simply again occupied /sessions and put the detailed session 
management under another URL (mostly because it wasn't there in the 
Manager).

If there is some basic interest in it, I would test and polish it.

The patch is at:

http://people.apache.org/~rjung/patches/tomcatManagerSessionExpireCodePatch.txt

An old docs patch against 5.0.28 (just to get an idea) is:

http://people.apache.org/~rjung/patches/tomcatManagerSessionExpireDocsPatch.txt

I think this feature is not obsoleted by the new detailed session 
administration, because it allows a statistical view and a simple mass 
expiration by idle time. For me it looks like a good fit to the new 
management of individual sessions.

Regards,

Rainer

Remy Maucherat wrote:
> Rainer Jung wrote:
>> Hi,
>>
>> I have a patch for the 5.0/5.5 Manager webapp, which I could port to 
>> 6.0 if there is some interest in it. Since Rémy was recently giving 
>> additional value to the manager webapp in 6.0, this patch could fit 
>> very well.
>>
>> I described it's functionality and use some time ago. The mail is 
>> archived at
>>
>> http://marc.info/?l=tomcat-dev&m=114125602727531&w=2
> 
> What would the patch be ?
> 
> Rémy

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: svn commit: r529444 - in /tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager: HTMLManagerServlet.java JspHelper.java util/ util/BaseSessionComparator.java util/ReverseComparator.java util/SessionUtils.java

Posted by Remy Maucherat <re...@apache.org>.
Rainer Jung wrote:
> Hi,
> 
> I have a patch for the 5.0/5.5 Manager webapp, which I could port to 6.0 
> if there is some interest in it. Since Rémy was recently giving 
> additional value to the manager webapp in 6.0, this patch could fit very 
> well.
> 
> I described it's functionality and use some time ago. The mail is 
> archived at
> 
> http://marc.info/?l=tomcat-dev&m=114125602727531&w=2

What would the patch be ?

Rémy

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: svn commit: r529444 - in /tomcat/tc6.0.x/trunk/java/org/apache/catalina/manager: HTMLManagerServlet.java JspHelper.java util/ util/BaseSessionComparator.java util/ReverseComparator.java util/SessionUtils.java

Posted by Rainer Jung <ra...@kippdata.de>.
Hi,

I have a patch for the 5.0/5.5 Manager webapp, which I could port to 6.0 
if there is some interest in it. Since Rémy was recently giving 
additional value to the manager webapp in 6.0, this patch could fit very 
well.

I described it's functionality and use some time ago. The mail is 
archived at

http://marc.info/?l=tomcat-dev&m=114125602727531&w=2

Feedback welcome.

Regards,

Rainer

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org