/** * Configuration keys and helper functions. * * @author @author <a href="mailto:[email protected]">Martin Grotzke</a> */ public final class Configurations { /** The TTL for {@link NodeAvailabilityCache} entries, in millis. */ public static final String NODE_AVAILABILITY_CACHE_TTL_KEY = "msm.nodeAvailabilityCacheTTL"; /** The max reconnect delay for the MemcachedClient, in seconds. */ public static final String MAX_RECONNECT_DELAY_KEY = "msm.maxReconnectDelay"; private static final Log LOG = LogFactory.getLog(Configurations.class); public static int getSystemProperty(final String propName, final int defaultValue) { final String value = System.getProperty(propName); if (value != null) { try { return Integer.parseInt(value); } catch (final NumberFormatException e) { LOG.warn( "Could not parse configured value for system property '" + propName + "': " + value); } } return defaultValue; } public static long getSystemProperty(final String propName, final long defaultValue) { final String value = System.getProperty(propName); if (value != null) { try { return Long.parseLong(value); } catch (final NumberFormatException e) { LOG.warn( "Could not parse configured value for system property '" + propName + "': " + value); } } return defaultValue; } }
/** @author kawasima */ public class JspHandler { private final transient Log log = LogFactory.getLog(JspHandler.class); private JspRuntimeContext runtimeContext; private Options options; private ServletConfig config; private ServletContext context; public JspHandler() {} public void setConfig(ServletConfig config) { this.config = config; context = config.getServletContext(); options = new EmbeddedServletOptions(config, context); runtimeContext = new JspRuntimeContext(context, options); } public void handle( HttpServletRequest request, HttpServletResponse response, String jspUri, boolean precompile) throws IOException, ServletException { JspServletWrapper wrapper = runtimeContext.getWrapper(jspUri); if (wrapper == null) { synchronized (this) { wrapper = runtimeContext.getWrapper(jspUri); if (wrapper == null) { if (context.getResource(jspUri) == null) { handleMissingResource(request, response, jspUri); return; } wrapper = new JspServletWrapper(config, options, jspUri, runtimeContext); runtimeContext.addWrapper(jspUri, wrapper); } } } try { wrapper.service(request, response, precompile); } catch (FileNotFoundException e) { handleMissingResource(request, response, jspUri); } } private void handleMissingResource( HttpServletRequest request, HttpServletResponse response, String jspUri) throws ServletException, IOException { String includeRequestUri = (String) request.getAttribute("javax.servlet.include.request_uri"); if (includeRequestUri != null) { String msg = Localizer.getMessage("jsp.error.file.not.found", jspUri); throw new ServletException(SecurityUtil.filter(msg)); } else { try { response.sendError(HttpServletResponse.SC_NOT_FOUND, request.getRequestURI()); } catch (IllegalStateException e) { log.error(Localizer.getMessage("jsp.error.file.not.found", jspUri)); } } } }
/** Rule implementation that creates a connector. */ public class ConnectorCreateRule extends Rule { private static final Log log = LogFactory.getLog(ConnectorCreateRule.class); // --------------------------------------------------------- Public Methods /** * Process the beginning of this element. * * @param namespace the namespace URI of the matching element, or an empty string if the parser is * not namespace aware or the element has no namespace * @param name the local name if the parser is namespace aware, or just the element name otherwise * @param attributes The attribute list for this element */ @Override public void begin(String namespace, String name, Attributes attributes) throws Exception { Service svc = (Service) digester.peek(); Executor ex = null; if (attributes.getValue("executor") != null) { ex = svc.getExecutor(attributes.getValue("executor")); } Connector con = new Connector(attributes.getValue("protocol")); if (ex != null) _setExecutor(con, ex); digester.push(con); } public void _setExecutor(Connector con, Executor ex) throws Exception { Method m = IntrospectionUtils.findMethod( con.getProtocolHandler().getClass(), "setExecutor", new Class[] {java.util.concurrent.Executor.class}); if (m != null) { m.invoke(con.getProtocolHandler(), new Object[] {ex}); } else { log.warn( "Connector [" + con + "] does not support external executors. Method setExecutor(java.util.concurrent.Executor) not found."); } } /** * Process the end of this element. * * @param namespace the namespace URI of the matching element, or an empty string if the parser is * not namespace aware or the element has no namespace * @param name the local name if the parser is namespace aware, or just the element name otherwise */ @Override public void end(String namespace, String name) throws Exception { digester.pop(); } }
/** * Static class used to preload java classes when using the Java SecurityManager so that the * defineClassInPackage RuntimePermission does not trigger an AccessControlException. * * @author Jean-Francois Arcand */ public final class SecurityClassLoad { private static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(SecurityClassLoad.class); public static void securityClassLoad(ClassLoader loader) { if (System.getSecurityManager() == null) { return; } String basePackage = "org.apache.jasper."; try { loader.loadClass(basePackage + "runtime.JspFactoryImpl$PrivilegedGetPageContext"); loader.loadClass(basePackage + "runtime.JspFactoryImpl$PrivilegedReleasePageContext"); loader.loadClass(basePackage + "runtime.JspRuntimeLibrary"); loader.loadClass(basePackage + "runtime.JspRuntimeLibrary$PrivilegedIntrospectHelper"); loader.loadClass(basePackage + "runtime.ServletResponseWrapperInclude"); loader.loadClass(basePackage + "runtime.TagHandlerPool"); loader.loadClass(basePackage + "runtime.JspFragmentHelper"); loader.loadClass(basePackage + "runtime.ProtectedFunctionMapper"); loader.loadClass(basePackage + "runtime.ProtectedFunctionMapper$1"); loader.loadClass(basePackage + "runtime.ProtectedFunctionMapper$2"); loader.loadClass(basePackage + "runtime.ProtectedFunctionMapper$3"); loader.loadClass(basePackage + "runtime.ProtectedFunctionMapper$4"); loader.loadClass(basePackage + "runtime.PageContextImpl"); loader.loadClass(basePackage + "runtime.PageContextImpl$1"); loader.loadClass(basePackage + "runtime.PageContextImpl$2"); loader.loadClass(basePackage + "runtime.PageContextImpl$3"); loader.loadClass(basePackage + "runtime.PageContextImpl$4"); loader.loadClass(basePackage + "runtime.PageContextImpl$5"); loader.loadClass(basePackage + "runtime.PageContextImpl$6"); loader.loadClass(basePackage + "runtime.PageContextImpl$7"); loader.loadClass(basePackage + "runtime.PageContextImpl$8"); loader.loadClass(basePackage + "runtime.PageContextImpl$9"); loader.loadClass(basePackage + "runtime.PageContextImpl$10"); loader.loadClass(basePackage + "runtime.PageContextImpl$11"); loader.loadClass(basePackage + "runtime.PageContextImpl$12"); loader.loadClass(basePackage + "runtime.PageContextImpl$13"); loader.loadClass(basePackage + "runtime.JspContextWrapper"); loader.loadClass(basePackage + "servlet.JspServletWrapper"); loader.loadClass(basePackage + "runtime.JspWriterImpl$1"); } catch (ClassNotFoundException ex) { log.error("SecurityClassLoad", ex); } } }
/** * @author Filip Hanik * @version 1.0 */ public class BufferPool { private static final Log log = LogFactory.getLog(BufferPool.class); public static final int DEFAULT_POOL_SIZE = 100 * 1024 * 1024; // 100MB protected static volatile BufferPool instance = null; protected final BufferPoolAPI pool; private BufferPool(BufferPoolAPI pool) { this.pool = pool; } public XByteBuffer getBuffer(int minSize, boolean discard) { if (pool != null) return pool.getBuffer(minSize, discard); else return new XByteBuffer(minSize, discard); } public void returnBuffer(XByteBuffer buffer) { if (pool != null) pool.returnBuffer(buffer); } public void clear() { if (pool != null) pool.clear(); } public static BufferPool getBufferPool() { if (instance == null) { synchronized (BufferPool.class) { if (instance == null) { BufferPoolAPI pool = new BufferPool15Impl(); pool.setMaxSize(DEFAULT_POOL_SIZE); log.info( "Created a buffer pool with max size:" + DEFAULT_POOL_SIZE + " bytes of type: " + pool.getClass().getName()); instance = new BufferPool(pool); } } } return instance; } public static interface BufferPoolAPI { public void setMaxSize(int bytes); public XByteBuffer getBuffer(int minSize, boolean discard); public void returnBuffer(XByteBuffer buffer); public void clear(); } }
/** * Dispatch based on the message type. ( XXX make it more generic, now it's specific to ajp13 ). * * @author Costin Manolache */ public class HandlerDispatch extends JkHandler { private static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(HandlerDispatch.class); public HandlerDispatch() {} public void init() {} JkHandler handlers[] = new JkHandler[MAX_HANDLERS]; String handlerNames[] = new String[MAX_HANDLERS]; static final int MAX_HANDLERS = 32; static final int RESERVED = 16; // reserved names, backward compat int currentId = RESERVED; public int registerMessageType(int id, String name, JkHandler h, String sig[]) { if (log.isDebugEnabled()) log.debug("Register message " + id + " " + h.getName() + " " + h.getClass().getName()); if (id < 0) { // try to find it by name for (int i = 0; i < handlerNames.length; i++) { if (handlerNames[i] == null) continue; if (name.equals(handlerNames[i])) return i; } handlers[currentId] = h; handlerNames[currentId] = name; currentId++; return currentId; } handlers[id] = h; handlerNames[currentId] = name; return id; } // -------------------- Incoming message -------------------- public int invoke(Msg msg, MsgContext ep) throws IOException { int type = msg.peekByte(); ep.setType(type); if (type > handlers.length || handlers[type] == null) { if (log.isDebugEnabled()) log.debug("Invalid handler " + type); return ERROR; } if (log.isDebugEnabled()) log.debug("Received " + type + " " + handlers[type].getName()); JkHandler handler = handlers[type]; return handler.invoke(msg, ep); } }
/** Store server.xml Element Realm */ public class RealmSF extends StoreFactoryBase { private static Log log = LogFactory.getLog(RealmSF.class); @Override public void store(PrintWriter aWriter, int indent, Object aElement) throws Exception { if (aElement instanceof CombinedRealm) { StoreDescription elementDesc = getRegistry().findDescription(aElement.getClass()); if (elementDesc != null) { if (log.isDebugEnabled()) log.debug(sm.getString("factory.storeTag", elementDesc.getTag(), aElement)); getStoreAppender().printIndent(aWriter, indent + 2); getStoreAppender().printOpenTag(aWriter, indent + 2, aElement, elementDesc); storeChildren(aWriter, indent + 2, aElement, elementDesc); getStoreAppender().printIndent(aWriter, indent + 2); getStoreAppender().printCloseTag(aWriter, elementDesc); } else { if (log.isWarnEnabled()) log.warn(sm.getString("factory.storeNoDescriptor", aElement.getClass())); } } else { super.store(aWriter, indent, aElement); } } /** * Store the specified Realm properties and child (Realm) * * @param aWriter PrintWriter to which we are storing * @param indent Number of spaces to indent this element * @param aRealm Realm whose properties are being stored * @exception Exception if an exception occurs while storing */ @Override public void storeChildren( PrintWriter aWriter, int indent, Object aRealm, StoreDescription parentDesc) throws Exception { if (aRealm instanceof CombinedRealm) { CombinedRealm combinedRealm = (CombinedRealm) aRealm; // Store nested <Realm> element Realm[] realms = combinedRealm.getNestedRealms(); storeElementArray(aWriter, indent, realms); } // Store nested <CredentialHandler> element CredentialHandler credentialHandler = ((Realm) aRealm).getCredentialHandler(); if (credentialHandler != null) { storeElement(aWriter, indent, credentialHandler); } } }
/** Store server.xml Manager element */ public class ManagerSF extends StoreFactoryBase { private static Log log = LogFactory.getLog(ManagerSF.class); /** * Store the only the Manager elements * * @see NamingResourcesSF#storeChildren(PrintWriter, int, Object, StoreDescription) */ @Override public void store(PrintWriter aWriter, int indent, Object aElement) throws Exception { StoreDescription elementDesc = getRegistry().findDescription(aElement.getClass()); if (elementDesc != null) { if (aElement instanceof StandardManager) { StandardManager manager = (StandardManager) aElement; if (!isDefaultManager(manager)) { if (log.isDebugEnabled()) log.debug(sm.getString("factory.storeTag", elementDesc.getTag(), aElement)); getStoreAppender().printIndent(aWriter, indent + 2); getStoreAppender().printTag(aWriter, indent + 2, manager, elementDesc); } } else { super.store(aWriter, indent, aElement); } } else { if (log.isWarnEnabled()) log.warn(sm.getString("factory.storeNoDescriptor", aElement.getClass())); } } /** * Is this an instance of the default <code>Manager</code> configuration, with all-default * properties? * * @param smanager Manager to be tested */ protected boolean isDefaultManager(StandardManager smanager) { if (!"SESSIONS.ser".equals(smanager.getPathname()) || (smanager.getMaxActiveSessions() != -1)) { return (false); } return (true); } }
/** * Title: Internal heartbeat thread * * <p>Description: if <code>Channel.getHeartbeat()==true</code> then a thread of this class is * created * * @version 1.0 */ public static class HeartbeatThread extends Thread { private static final Log log = LogFactory.getLog(HeartbeatThread.class); protected static int counter = 1; protected static synchronized int inc() { return counter++; } protected volatile boolean doRun = true; protected final GroupChannel channel; protected final long sleepTime; public HeartbeatThread(GroupChannel channel, long sleepTime) { super(); this.setPriority(MIN_PRIORITY); setName("GroupChannel-Heartbeat-" + inc()); setDaemon(true); this.channel = channel; this.sleepTime = sleepTime; } public void stopHeartbeat() { doRun = false; interrupt(); } @Override public void run() { while (doRun) { try { Thread.sleep(sleepTime); channel.heartbeat(); } catch (InterruptedException x) { // Ignore. Probably triggered by a call to stopHeartbeat(). // In the highly unlikely event it was a different trigger, // simply ignore it and continue. } catch (Exception x) { log.error(sm.getString("groupChannel.unable.sendHeartbeat"), x); } // catch } // while } // run } // HeartbeatThread
/** * Abstract the protocol implementation, including threading, etc. Processor is single threaded and * specific to stream-based protocols, will not fit Jk protocols like JNI. * * @author Remy Maucherat * @author Costin Manolache */ public class AjpProtocol extends AbstractAjpProtocol<Socket> { private static final Log log = LogFactory.getLog(AjpProtocol.class); @Override protected Log getLog() { return log; } @Override protected AbstractEndpointHandler getHandler() { return cHandler; } // ------------------------------------------------------------ Constructor public AjpProtocol() { setEndpoint(new JIoEndpoint()); cHandler = new AjpProtocolAjpConnectionHandler(this); ((JIoEndpoint) getEndpoint()).setHandler(cHandler); setSoLinger(Constants25.getDefaultConnectionLinger()); setSoTimeout(Constants25.getDefaultConnectionTimeout()); setTcpNoDelay(Constants25.isDefaultTcpNoDelay()); } // ----------------------------------------------------- Instance Variables /** Connection handler for AJP. */ private AjpProtocolAjpConnectionHandler cHandler; // ----------------------------------------------------- JMX related methods @Override protected String getNamePrefix() { return ("ajp-bio"); } public static Log getLogVariable() { return log; } }
/** * The JSP engine (a.k.a Jasper). * * <p>The servlet container is responsible for providing a URLClassLoader for the web application * context Jasper is being used in. Jasper will try get the Tomcat ServletContext attribute for its * ServletContext class loader, if that fails, it uses the parent class loader. In either case, it * must be a URLClassLoader. * * @author Anil K. Vijendran * @author Harish Prabandham * @author Remy Maucherat * @author Kin-man Chung * @author Glenn Nielsen * @author Tim Fennell */ @SuppressWarnings("deprecation") // Have to support SingleThreadModel public class JspServletWrapper { private static final Map<String, Long> ALWAYS_OUTDATED_DEPENDENCIES = new HashMap<String, Long>(); static { // If this is missing, ALWAYS_OUTDATED_DEPENDENCIES.put("/WEB-INF/web.xml", Long.valueOf(-1)); } // Logger private final Log log = LogFactory.getLog(JspServletWrapper.class); private Servlet theServlet; private String jspUri; private Class<?> tagHandlerClass; private JspCompilationContext ctxt; private long available = 0L; private ServletConfig config; private Options options; private boolean firstTime = true; /** Whether the servlet needs reloading on next access */ private volatile boolean reload = true; private boolean isTagFile; private int tripCount; private JasperException compileException; /** Timestamp of last time servlet resource was modified */ private volatile long servletClassLastModifiedTime; private long lastModificationTest = 0L; private long lastUsageTime = System.currentTimeMillis(); private FastRemovalDequeue<JspServletWrapper>.Entry unloadHandle; private final boolean unloadAllowed; private final boolean unloadByCount; private final boolean unloadByIdle; /* * JspServletWrapper for JSP pages. */ public JspServletWrapper( ServletConfig config, Options options, String jspUri, JspRuntimeContext rctxt) { this.isTagFile = false; this.config = config; this.options = options; this.jspUri = jspUri; unloadByCount = options.getMaxLoadedJsps() > 0 ? true : false; unloadByIdle = options.getJspIdleTimeout() > 0 ? true : false; unloadAllowed = unloadByCount || unloadByIdle ? true : false; ctxt = new JspCompilationContext(jspUri, options, config.getServletContext(), this, rctxt); } /* * JspServletWrapper for tag files. */ public JspServletWrapper( ServletContext servletContext, Options options, String tagFilePath, TagInfo tagInfo, JspRuntimeContext rctxt, JarResource tagJarResource) { this.isTagFile = true; this.config = null; // not used this.options = options; this.jspUri = tagFilePath; this.tripCount = 0; unloadByCount = options.getMaxLoadedJsps() > 0 ? true : false; unloadByIdle = options.getJspIdleTimeout() > 0 ? true : false; unloadAllowed = unloadByCount || unloadByIdle ? true : false; ctxt = new JspCompilationContext( jspUri, tagInfo, options, servletContext, this, rctxt, tagJarResource); } public JspCompilationContext getJspEngineContext() { return ctxt; } public void setReload(boolean reload) { this.reload = reload; } public Servlet getServlet() throws ServletException { // DCL on 'reload' requires that 'reload' be volatile // (this also forces a read memory barrier, ensuring the // new servlet object is read consistently) if (reload) { synchronized (this) { // Synchronizing on jsw enables simultaneous loading // of different pages, but not the same page. if (reload) { // This is to maintain the original protocol. destroy(); final Servlet servlet; try { InstanceManager instanceManager = InstanceManagerFactory.getInstanceManager(config); servlet = (Servlet) instanceManager.newInstance(ctxt.getFQCN(), ctxt.getJspLoader()); } catch (IllegalAccessException e) { throw new JasperException(e); } catch (InstantiationException e) { throw new JasperException(e); } catch (Exception e) { throw new JasperException(e); } servlet.init(config); if (!firstTime) { ctxt.getRuntimeContext().incrementJspReloadCount(); } theServlet = servlet; reload = false; // Volatile 'reload' forces in order write of 'theServlet' and new servlet object } } } return theServlet; } public ServletContext getServletContext() { return ctxt.getServletContext(); } /** * Sets the compilation exception for this JspServletWrapper. * * @param je The compilation exception */ public void setCompilationException(JasperException je) { this.compileException = je; } /** * Sets the last-modified time of the servlet class file associated with this JspServletWrapper. * * @param lastModified Last-modified time of servlet class */ public void setServletClassLastModifiedTime(long lastModified) { // DCL requires servletClassLastModifiedTime be volatile // to force read and write barriers on access/set // (and to get atomic write of long) if (this.servletClassLastModifiedTime < lastModified) { synchronized (this) { if (this.servletClassLastModifiedTime < lastModified) { this.servletClassLastModifiedTime = lastModified; reload = true; } } } } /** Compile (if needed) and load a tag file */ public Class<?> loadTagFile() throws JasperException { try { if (ctxt.isRemoved()) { throw new FileNotFoundException(jspUri); } if (options.getDevelopment() || firstTime) { synchronized (this) { firstTime = false; ctxt.compile(); } } else { if (compileException != null) { throw compileException; } } if (reload) { tagHandlerClass = ctxt.load(); reload = false; } } catch (FileNotFoundException ex) { throw new JasperException(ex); } return tagHandlerClass; } /** * Compile and load a prototype for the Tag file. This is needed when compiling tag files with * circular dependencies. A prototype (skeleton) with no dependencies on other other tag files is * generated and compiled. */ public Class<?> loadTagFilePrototype() throws JasperException { ctxt.setPrototypeMode(true); try { return loadTagFile(); } finally { ctxt.setPrototypeMode(false); } } /** Get a list of files that the current page has source dependency on. */ public java.util.Map<String, Long> getDependants() { try { Object target; if (isTagFile) { if (reload) { tagHandlerClass = ctxt.load(); reload = false; } target = tagHandlerClass.newInstance(); } else { target = getServlet(); } if (target != null && target instanceof JspSourceDependent) { return ((JspSourceDependent) target).getDependants(); } } catch (AbstractMethodError ame) { // Almost certainly a pre Tomcat 7.0.17 compiled JSP using the old // version of the interface. Force a re-compile. return ALWAYS_OUTDATED_DEPENDENCIES; } catch (Throwable ex) { ExceptionUtils.handleThrowable(ex); } return null; } public boolean isTagFile() { return this.isTagFile; } public int incTripCount() { return tripCount++; } public int decTripCount() { return tripCount--; } public String getJspUri() { return jspUri; } public FastRemovalDequeue<JspServletWrapper>.Entry getUnloadHandle() { return unloadHandle; } public void service(HttpServletRequest request, HttpServletResponse response, boolean precompile) throws ServletException, IOException, FileNotFoundException { Servlet servlet; try { if (ctxt.isRemoved()) { throw new FileNotFoundException(jspUri); } if ((available > 0L) && (available < Long.MAX_VALUE)) { if (available > System.currentTimeMillis()) { response.setDateHeader("Retry-After", available); response.sendError( HttpServletResponse.SC_SERVICE_UNAVAILABLE, Localizer.getMessage("jsp.error.unavailable")); return; } // Wait period has expired. Reset. available = 0; } /* * (1) Compile */ if (options.getDevelopment() || firstTime) { synchronized (this) { firstTime = false; // The following sets reload to true, if necessary ctxt.compile(); } } else { if (compileException != null) { // Throw cached compilation exception throw compileException; } } /* * (2) (Re)load servlet class file */ servlet = getServlet(); // If a page is to be precompiled only, return. if (precompile) { return; } } catch (ServletException ex) { if (options.getDevelopment()) { throw handleJspException(ex); } throw ex; } catch (FileNotFoundException fnfe) { // File has been removed. Let caller handle this. throw fnfe; } catch (IOException ex) { if (options.getDevelopment()) { throw handleJspException(ex); } throw ex; } catch (IllegalStateException ex) { if (options.getDevelopment()) { throw handleJspException(ex); } throw ex; } catch (Exception ex) { if (options.getDevelopment()) { throw handleJspException(ex); } throw new JasperException(ex); } try { /* * (3) Handle limitation of number of loaded Jsps */ if (unloadAllowed) { synchronized (this) { if (unloadByCount) { if (unloadHandle == null) { unloadHandle = ctxt.getRuntimeContext().push(this); } else if (lastUsageTime < ctxt.getRuntimeContext().getLastJspQueueUpdate()) { ctxt.getRuntimeContext().makeYoungest(unloadHandle); lastUsageTime = System.currentTimeMillis(); } } else { if (lastUsageTime < ctxt.getRuntimeContext().getLastJspQueueUpdate()) { lastUsageTime = System.currentTimeMillis(); } } } } /* * (4) Service request */ if (servlet instanceof SingleThreadModel) { // sync on the wrapper so that the freshness // of the page is determined right before servicing synchronized (this) { servlet.service(request, response); } } else { servlet.service(request, response); } } catch (UnavailableException ex) { String includeRequestUri = (String) request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI); if (includeRequestUri != null) { // This file was included. Throw an exception as // a response.sendError() will be ignored by the // servlet engine. throw ex; } int unavailableSeconds = ex.getUnavailableSeconds(); if (unavailableSeconds <= 0) { unavailableSeconds = 60; // Arbitrary default } available = System.currentTimeMillis() + (unavailableSeconds * 1000L); response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, ex.getMessage()); } catch (ServletException ex) { if (options.getDevelopment()) { throw handleJspException(ex); } throw ex; } catch (IOException ex) { if (options.getDevelopment()) { throw handleJspException(ex); } throw ex; } catch (IllegalStateException ex) { if (options.getDevelopment()) { throw handleJspException(ex); } throw ex; } catch (Exception ex) { if (options.getDevelopment()) { throw handleJspException(ex); } throw new JasperException(ex); } } public void destroy() { if (theServlet != null) { theServlet.destroy(); InstanceManager instanceManager = InstanceManagerFactory.getInstanceManager(config); try { instanceManager.destroyInstance(theServlet); } catch (Exception e) { // Log any exception, since it can't be passed along log.error(Localizer.getMessage("jsp.error.file.not.found", e.getMessage()), e); } } } /** @return Returns the lastModificationTest. */ public long getLastModificationTest() { return lastModificationTest; } /** @param lastModificationTest The lastModificationTest to set. */ public void setLastModificationTest(long lastModificationTest) { this.lastModificationTest = lastModificationTest; } /** @return the lastUsageTime. */ public long getLastUsageTime() { return lastUsageTime; } /** * Attempts to construct a JasperException that contains helpful information about what went * wrong. Uses the JSP compiler system to translate the line number in the generated servlet that * originated the exception to a line number in the JSP. Then constructs an exception containing * that information, and a snippet of the JSP to help debugging. Please see * http://issues.apache.org/bugzilla/show_bug.cgi?id=37062 and http://www.tfenne.com/jasper/ for * more details. * * @param ex the exception that was the cause of the problem. * @return a JasperException with more detailed information */ protected JasperException handleJspException(Exception ex) { try { Throwable realException = ex; if (ex instanceof ServletException) { realException = ((ServletException) ex).getRootCause(); } // First identify the stack frame in the trace that represents the JSP StackTraceElement[] frames = realException.getStackTrace(); StackTraceElement jspFrame = null; for (int i = 0; i < frames.length; ++i) { if (frames[i].getClassName().equals(this.getServlet().getClass().getName())) { jspFrame = frames[i]; break; } } if (jspFrame == null || this.ctxt.getCompiler().getPageNodes() == null) { // If we couldn't find a frame in the stack trace corresponding // to the generated servlet class or we don't have a copy of the // parsed JSP to hand, we can't really add anything return new JasperException(ex); } int javaLineNumber = jspFrame.getLineNumber(); JavacErrorDetail detail = ErrorDispatcher.createJavacError( jspFrame.getMethodName(), this.ctxt.getCompiler().getPageNodes(), null, javaLineNumber, ctxt); // If the line number is less than one we couldn't find out // where in the JSP things went wrong int jspLineNumber = detail.getJspBeginLineNumber(); if (jspLineNumber < 1) { throw new JasperException(ex); } if (options.getDisplaySourceFragment()) { return new JasperException( Localizer.getMessage("jsp.exception", detail.getJspFileName(), "" + jspLineNumber) + Constants.NEWLINE + Constants.NEWLINE + detail.getJspExtract() + Constants.NEWLINE + Constants.NEWLINE + "Stacktrace:", ex); } return new JasperException( Localizer.getMessage("jsp.exception", detail.getJspFileName(), "" + jspLineNumber), ex); } catch (Exception je) { // If anything goes wrong, just revert to the original behaviour if (ex instanceof JasperException) { return (JasperException) ex; } return new JasperException(ex); } } }
/** * Gzip output filter. * * @author Remy Maucherat */ public class GzipOutputFilter implements OutputFilter { /** Logger. */ protected static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(GzipOutputFilter.class); // ----------------------------------------------------- Instance Variables /** Next buffer in the pipeline. */ protected OutputBuffer buffer; /** Compression output stream. */ protected GZIPOutputStream compressionStream = null; /** Fake internal output stream. */ protected OutputStream fakeOutputStream = new FakeOutputStream(); // --------------------------------------------------- OutputBuffer Methods /** * Write some bytes. * * @return number of bytes written by the filter */ @Override public int doWrite(ByteChunk chunk, Response res) throws IOException { if (compressionStream == null) { compressionStream = new FlushableGZIPOutputStream(fakeOutputStream); } compressionStream.write(chunk.getBytes(), chunk.getStart(), chunk.getLength()); return chunk.getLength(); } @Override public long getBytesWritten() { return buffer.getBytesWritten(); } // --------------------------------------------------- OutputFilter Methods /** Added to allow flushing to happen for the gzip'ed outputstream */ public void flush() { if (compressionStream != null) { try { if (log.isDebugEnabled()) { log.debug("Flushing the compression stream!"); } compressionStream.flush(); } catch (IOException e) { if (log.isDebugEnabled()) { log.debug("Ignored exception while flushing gzip filter", e); } } } } /** * Some filters need additional parameters from the response. All the necessary reading can occur * in that method, as this method is called after the response header processing is complete. */ @Override public void setResponse(Response response) { // NOOP: No need for parameters from response in this filter } /** Set the next buffer in the filter pipeline. */ @Override public void setBuffer(OutputBuffer buffer) { this.buffer = buffer; } /** * End the current request. It is acceptable to write extra bytes using buffer.doWrite during the * execution of this method. */ @Override public long end() throws IOException { if (compressionStream == null) { compressionStream = new FlushableGZIPOutputStream(fakeOutputStream); } compressionStream.finish(); compressionStream.close(); return ((OutputFilter) buffer).end(); } /** Make the filter ready to process the next request. */ @Override public void recycle() { // Set compression stream to null compressionStream = null; } // ------------------------------------------- FakeOutputStream Inner Class protected class FakeOutputStream extends OutputStream { protected ByteChunk outputChunk = new ByteChunk(); protected byte[] singleByteBuffer = new byte[1]; @Override public void write(int b) throws IOException { // Shouldn't get used for good performance, but is needed for // compatibility with Sun JDK 1.4.0 singleByteBuffer[0] = (byte) (b & 0xff); outputChunk.setBytes(singleByteBuffer, 0, 1); buffer.doWrite(outputChunk, null); } @Override public void write(byte[] b, int off, int len) throws IOException { outputChunk.setBytes(b, off, len); buffer.doWrite(outputChunk, null); } @Override public void flush() throws IOException { /* NOOP */ } @Override public void close() throws IOException { /* NOOP */ } } }
/** * Plugs Jk into Coyote. Must be named "type=JkHandler,name=container" * * <p>jmx:notification-handler name="org.apache.jk.SEND_PACKET jmx:notification-handler * name="org.apache.coyote.ACTION_COMMIT */ public class JkCoyoteHandler extends JkHandler implements ProtocolHandler { protected static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(JkCoyoteHandler.class); // Set debug on this logger to see the container request time // ----------------------------------------------------------- DoPrivileged private boolean paused = false; int epNote; Adapter adapter; protected JkMain jkMain = null; /** Set a property. Name is a "component.property". JMX should be used instead. */ public void setProperty(String name, String value) { if (log.isTraceEnabled()) log.trace("setProperty " + name + " " + value); getJkMain().setProperty(name, value); properties.put(name, value); } public String getProperty(String name) { return properties.getProperty(name); } public Iterator getAttributeNames() { return properties.keySet().iterator(); } /** Pass config info */ public void setAttribute(String name, Object value) { if (log.isDebugEnabled()) log.debug("setAttribute " + name + " " + value); if (value instanceof String) this.setProperty(name, (String) value); } /** Retrieve config info. Primarily for use with the admin webapp. */ public Object getAttribute(String name) { return getJkMain().getProperty(name); } /** The adapter, used to call the connector */ public void setAdapter(Adapter adapter) { this.adapter = adapter; } public Adapter getAdapter() { return adapter; } public JkMain getJkMain() { if (jkMain == null) { jkMain = new JkMain(); jkMain.setWorkerEnv(wEnv); } return jkMain; } boolean started = false; /** Start the protocol */ public void init() { if (started) return; started = true; if (wEnv == null) { // we are probably not registered - not very good. wEnv = getJkMain().getWorkerEnv(); wEnv.addHandler("container", this); } try { // jkMain.setJkHome() XXX; getJkMain().init(); } catch (Exception ex) { log.error("Error during init", ex); } } public void start() { try { if (oname != null && getJkMain().getDomain() == null) { try { ObjectName jkmainOname = new ObjectName(oname.getDomain() + ":type=JkMain"); Registry.getRegistry(null, null).registerComponent(getJkMain(), jkmainOname, "JkMain"); } catch (Exception e) { log.error("Error registering jkmain " + e); } } getJkMain().start(); } catch (Exception ex) { log.error("Error during startup", ex); } } public void pause() throws Exception { if (!paused) { paused = true; getJkMain().pause(); } } public void resume() throws Exception { if (paused) { paused = false; getJkMain().resume(); } } public void destroy() { if (!started) return; started = false; getJkMain().stop(); } // -------------------- Jk handler implementation -------------------- // Jk Handler mehod public int invoke(Msg msg, MsgContext ep) throws IOException { if (ep.isLogTimeEnabled()) ep.setLong(MsgContext.TIMER_PRE_REQUEST, System.currentTimeMillis()); Request req = ep.getRequest(); Response res = req.getResponse(); if (log.isDebugEnabled()) log.debug("Invoke " + req + " " + res + " " + req.requestURI().toString()); res.setNote(epNote, ep); ep.setStatus(MsgContext.JK_STATUS_HEAD); RequestInfo rp = req.getRequestProcessor(); rp.setStage(Constants.STAGE_SERVICE); try { adapter.service(req, res); } catch (Exception ex) { log.info("Error servicing request " + req, ex); } if (ep.getStatus() != MsgContext.JK_STATUS_CLOSED) { res.finish(); } req.updateCounters(); req.recycle(); res.recycle(); ep.recycle(); if (ep.getStatus() == MsgContext.JK_STATUS_ERROR) { return ERROR; } ep.setStatus(MsgContext.JK_STATUS_NEW); rp.setStage(Constants.STAGE_KEEPALIVE); return OK; } public ObjectName preRegister(MBeanServer server, ObjectName oname) throws Exception { // override - we must be registered as "container" this.name = "container"; return super.preRegister(server, oname); } }
/** * Helper class to simplify setting up a Embedded Tomcat in a IDE and with a Maven web project. * * @author Ralph Schaer */ public class EmbeddedTomcat { private static final Log log = LogFactory.getLog(EmbeddedTomcat.class); private String contextPath; private Integer httpPort; private Integer shutdownPort; private int secondsToWaitBeforePortBecomesAvailable; public int maxPostSize = 2097152; private int httpsPort; private String keyStoreFile; private String keyAlias; private String keyStorePass; private String sslProtocol; private String tempDirectory; private String contextDirectory; private String skipJarsDefaultJarScanner; private String skipJarsContextConfig; private String skipJarsTldConfig; private boolean privileged; private boolean silent; private boolean addDefaultListeners = false; private boolean useNio = false; private int compressionMinSize = -1; private String compressableMimeType; private boolean enableNaming = false; private final List<ContextEnvironment> contextEnvironments; private final List<ContextResource> contextResources; private final List<ApplicationParameter> contextInitializationParameters; private URL contextFileURL; private Tomcat tomcat; /** * Starts a embedded Tomcat on port 8080 with context path "" and context directory current * directory + /src/main/webapp * * @param args program arguments */ public static void main(String[] args) { new EmbeddedTomcat().startAndWait(); } /** * Convenient method to create a embedded Tomcat that listens on port 8080 and with a context path * of "" * * @return EmbeddedTomcat the embedded tomcat */ public static EmbeddedTomcat create() { return new EmbeddedTomcat(); } /** * Creates an embedded Tomcat with context path "" and port 8080. Context directory points to * current directory + /src/main/webapp Change context directory with the method <code> * setContextDirectory(String)</code> * * @see EmbeddedTomcat#setContextDirectory(String) */ public EmbeddedTomcat() { this("", 8080); } /** * Creates an embedded Tomcat with context path "" and specified port. Context directory points to * current directory + /src/main/webapp Change context directory with the method <code> * setContextDirectory(String)</code> * * @param port ip port the server is listening * @see #setContextDirectory(String) */ public EmbeddedTomcat(int port) { this("", port); } /** * Creates an embedded Tomcat with the specified context path and port 8080 Context directory * points to current directory + /src/main/webapp Change context directory with the method <code> * setContextDirectory(String)</code> * * @param contextPath The context path has to start with / * @see #setContextDirectory(String) */ public EmbeddedTomcat(String contextPath) { this(contextPath, 8080); } /** * Creates an embedded Tomcat with specified context path and specified port. Context directory * points to current directory + /src/main/webapp Change context directory with the method <code> * setContextDirectory(String)</code> * * @param contextPath has to start with / * @param httpPort ip port the server is listening for http requests. Shutdown port is set to port * + 1000 * @see EmbeddedTomcat#setContextDirectory(String) */ public EmbeddedTomcat(String contextPath, int httpPort) { this(contextPath, httpPort, 0); } /** * Creates an embedded Tomcat with specified context path and specified ports. Context directory * points to current directory + /src/main/webapp Change context directory with the method <code> * setContextDirectory(String)</code> * * @param contextPath has to start with / * @param httpPort ip port the server is listening for http requests. Shutdown port is set to port * + 1000 * @param httpsPort ip port the server is listening for https requests. * @see EmbeddedTomcat#setContextDirectory(String) */ public EmbeddedTomcat(String contextPath, int httpPort, int httpsPort) { this.tomcat = null; setContextPath(contextPath); setHttpPort(httpPort); setShutdownPort(httpPort + 1000); setHttpsPort(httpsPort); setSecondsToWaitBeforePortBecomesAvailable(10); setPrivileged(false); setSilent(false); setContextDirectory(null); this.tempDirectory = null; this.contextEnvironments = new ArrayList<ContextEnvironment>(); this.contextResources = new ArrayList<ContextResource>(); this.contextInitializationParameters = new ArrayList<ApplicationParameter>(); } /** * Sets the port the server is listening for http requests * * @param port The new port * @return The embedded Tomcat * @deprecated Use {@link #setHttpPort(int)} instead */ @Deprecated public EmbeddedTomcat setPort(int port) { return setHttpPort(port); } /** * Sets the port the server is listening for http requests * * @param httpPort The new port * @return The embedded Tomcat */ public EmbeddedTomcat setHttpPort(int httpPort) { this.httpPort = httpPort; return this; } /** * The maximum size in bytes of the POST which will be handled by the container FORM URL parameter * parsing. The limit can be disabled by setting this attribute to a value less than or equal to * 0. If not specified, this attribute is set to 2097152 (2 megabytes). * * @param maxPostSize maximum size in bytes for POST requests * @return The embedded Tomcat */ public EmbeddedTomcat setMaxPostSize(int maxPostSize) { this.maxPostSize = maxPostSize; return this; } /** * Sets the port the server is listening for https requests * * @param httpsPort The new port * @return The embedded Tomcat */ public EmbeddedTomcat setHttpsPort(int httpsPort) { this.httpsPort = httpsPort; return this; } /** * The pathname of the keystore file where you have stored the server certificate to be loaded. By * default, the pathname is the file ".keystore" in the operating system home directory of the * user that is running Tomcat. * * @param keyStoreFile The keystore pathname * @return The embedded Tomcat */ public EmbeddedTomcat setKeyStoreFile(String keyStoreFile) { this.keyStoreFile = keyStoreFile; return this; } /** * The alias used to for the server certificate in the keystore. If not specified the first key * read in the keystore will be used. * * @param keyAlias The key alias * @return The embedded Tomcat */ public EmbeddedTomcat setKeyAlias(String keyAlias) { this.keyAlias = keyAlias; return this; } /** * The password used to access the server certificate from the specified keystore file. The * default value is "changeit". * * @param keyStorePass The keystore password * @return The embedded Tomcat */ public EmbeddedTomcat setKeyStorePass(String keyStorePass) { this.keyStorePass = keyStorePass; return this; } /** * Sets the contextPath for the webapplication * * @param contextPath The new contextPath. Has to start with / or is the empty "" string * @return The embedded Tomcat */ public EmbeddedTomcat setContextPath(String contextPath) { if (contextPath == null || !contextPath.equals("") && !contextPath.startsWith("/")) { throw new IllegalArgumentException( "contextPath must be the empty string \"\" or a path starting with /"); } this.contextPath = contextPath; return this; } /** * Sets the context directory. Normally this point to the directory that hosts the WEB-INF * directory. Default value is: current directory + /src/main/webapp This is the normal location * of the webapplication directory in a Maven web app project. * * @param contextDirectory Path name to the directory that contains the web application. * @return The embedded Tomcat */ public EmbeddedTomcat setContextDirectory(String contextDirectory) { this.contextDirectory = contextDirectory; return this; } /** * List of JAR files that should not be scanned using the JarScanner functionality. This is * typically used to scan JARs for configuration information. JARs that do not contain such * information may be excluded from the scan to speed up the scanning process. JARs on this list * are excluded from all scans. * * <p>Scan specific lists (to exclude JARs from individual scans) see {@link * #skipJarsContextConfig(String)} and {@link #skipJarsTldConfig(String)}. * * <p>The list must be a comma separated list of JAR file names. Example: spring*.jar,cglib*.jar. * * <p>This list is appended to the default list. The default list is located in the file * CATALINA_HOME\conf\catalina.properties under the key * tomcat.util.scan.DefaultJarScanner.jarsToSkip * * @param skipJars list of jars, comma separated * @return The embedded Tomcat */ public EmbeddedTomcat skipJarsDefaultJarScanner(String skipJars) { this.skipJarsDefaultJarScanner = skipJars; return this; } /** * Additional JARs (over and above the default JARs set with {@link * #skipJarsDefaultJarScanner(String)}) to skip when scanning for Servlet 3.0 pluggability * features. These features include web fragments, annotations, SCIs and classes that * match @HandlesTypes. The list must be a comma separated list of JAR file names. * * @param skipJars list of jars, comma separated * @return The embedded Tomcat */ public EmbeddedTomcat skipJarsContextConfig(String skipJars) { this.skipJarsContextConfig = skipJars; return this; } /** * Additional JARs (over and above the default JARs set with {@link * #skipJarsDefaultJarScanner(String)}) to skip when scanning for TLDs. The list must be a comma * separated list of JAR file names. * * @param skipJars list of jars, comma separated * @return The embedded Tomcat */ public EmbeddedTomcat skipJarsTldConfig(String skipJars) { this.skipJarsTldConfig = skipJars; return this; } /** * Sets the location of the temporary directory. Tomcat needs this for storing temporary files * like compiled jsp files. Default value is * * <p><code> * target/tomcat. + port * </code> * * @param tempDirectory File object that represents the location of the temp directory * @return The embedded Tomcat */ public EmbeddedTomcat setTempDirectory(File tempDirectory) { this.tempDirectory = tempDirectory.getAbsolutePath(); return this; } /** * Sets the temporary directory to a directory beneath the target directory <br> * The directory does not have to exists, Tomcat will create it automatically if necessary. * * @param name directory name * @return The embedded Tomcat * @see EmbeddedTomcat#setTempDirectory(File) */ public EmbeddedTomcat setTempDirectoryName(String name) { this.tempDirectory = new File(".", "target/" + name).getAbsolutePath(); return this; } /** * The EmbeddedTomcat listens per default for shutdown commands on port 8005 with the shutdown * command <code>SHUTDOWN</code>. Calling this method disables adding the shutdown hook. * * @return The embedded Tomcat */ public EmbeddedTomcat dontAddShutdownHook() { this.shutdownPort = null; return this; } /** * Before starting the embedded Tomcat the programm tries to stop a previous process by sendig the * shutdown command to the shutdown port. It then waits for the port to become available. It * checks this every second for the specified number of seconds * * @param seconds number of seconds * @return The embedded Tomcat */ public EmbeddedTomcat setSecondsToWaitBeforePortBecomesAvailable(int seconds) { this.secondsToWaitBeforePortBecomesAvailable = seconds; return this; } /** * Specifies the port the server is listen for the shutdown command. Default is port 8005 * * @param shutdownPort the shutdown port * @return The embedded Tomcat * * @see EmbeddedTomcat#dontAddShutdownHook() */ public EmbeddedTomcat setShutdownPort(int shutdownPort) { this.shutdownPort = shutdownPort; return this; } /** * Set the privileged flag for this web application. * * @param privileged The new privileged flag * @return The embedded Tomcat */ public EmbeddedTomcat setPrivileged(boolean privileged) { this.privileged = privileged; return this; } /** * Instructs the embedded tomcat to use the Non Blocking Connector * (org.apache.coyote.http11.Http11NioProtocol) instead of the Blocking Connector * (org.apache.coyote.http11.Http11Protocol) * * @return The embedded Tomcat */ public EmbeddedTomcat useNio() { this.useNio = true; return this; } @SuppressWarnings("hiding") public EmbeddedTomcat enableCompression(int compressionMinSize, String compressableMimeType) { this.compressionMinSize = compressionMinSize; this.compressableMimeType = compressableMimeType; return this; } /** * Enables JNDI naming which is disabled by default. * * @return The embedded Tomcat */ public EmbeddedTomcat enableNaming() { this.enableNaming = true; return this; } /** * Installs the default listeners AprLifecycleListener, JasperListener, * JreMemoryLeakPreventionListener, GlobalResourcesLifecycleListener and * ThreadLocalLeakPreventionListener during startup. * * @return The embedded Tomcat */ public EmbeddedTomcat addDefaultListeners() { this.addDefaultListeners = true; return this; } /** * Set the silent flag of Tomcat. If true Tomcat no longer prints any log messages * * @param silent The new silent flag * @return The embedded Tomcat */ public EmbeddedTomcat setSilent(boolean silent) { this.silent = silent; return this; } /** * Adds a {@link ContextEnvironment} object to embedded Tomcat. * * <p>Example:<br> * Tomcat context xml file * * <pre> * <Environment name="aparam" * value="test" * type="java.lang.String" * override="false"/> * </pre> * * A programmatic way to add this environment to the embedded Tomcat is by calling this method * * <pre> * ContextEnvironment env = new ContextEnvironment(); * env.setType("java.lang.String"); * env.setName("aparam"); * env.setValue("test"); * embeddedTomcat.addContextEnvironment(env); * </pre> * * @param env context environment variable * @return The embedded Tomcat * @see ContextEnvironment * @see NamingResources#addEnvironment(ContextEnvironment) * @see EmbeddedTomcat#addContextEnvironment(String, String, String) * @see EmbeddedTomcat#addContextEnvironmentString(String, String) */ public EmbeddedTomcat addContextEnvironment(ContextEnvironment env) { this.contextEnvironments.add(env); return this; } /** * Adds a {@link ContextResource} object to the list of resources in the embedded Tomcat. * * <p>Example:<br> * Tomcat context xml file * * <pre> * <Resource name="jdbc/ds" auth="Container" * type="javax.sql.DataSource" username="******" password="" * driverClassName="org.h2.Driver" * url="jdbc:h2:~/mydb" * maxActive="20" maxIdle="4" maxWait="10000" * defaultAutoCommit="false"/> * </pre> * * Programmatic way: * * <pre> * ContextResource res = new ContextResource(); * res.setName("jdbc/ds"); * res.setType("javax.sql.DataSource"); * res.setAuth("Container"); * res.setProperty("username", "sa"); * res.setProperty("password", ""); * res.setProperty("driverClassName", "org.h2.Driver"); * res.setProperty("url", "jdbc:h2:˜/mydb"); * res.setProperty("maxActive", "20"); * res.setProperty("maxIdle", "4"); * res.setProperty("maxWait", "10000"); * res.setProperty("defaultAutoCommit", "false"); * * embeddedTomcat.addContextResource(res); * </pre> * * @param res resource object * @return The embedded Tomcat * @see ContextResource * @see NamingResources#addResource(ContextResource) */ public EmbeddedTomcat addContextResource(ContextResource res) { this.contextResources.add(res); return this; } public EmbeddedTomcat addContextInitializationParameter(String name, String value) { ApplicationParameter parameter = new ApplicationParameter(); parameter.setName(name); parameter.setValue(value); this.contextInitializationParameters.add(parameter); return this; } /** * Convenient method for adding a context environment to the embedded Tomcat. Creates a <code> * ContextEnvironment</code> object and adds it to the list of the context environments. * * <p>Example:<br> * Tomcat context xml file * * <pre> * <Environment name="aparam" * value="test" * type="java.lang.String" * override="false"/> * </pre> * * Programmatic way: * * <pre> * embeddedTomcat.addContextEnvironment("aparam", "test", "java.lang.String"); * </pre> * * @param name name of the context environment * @param value value of the context environment * @param type type of the context environment * @return The embedded Tomcat * @see ContextEnvironment * @see NamingResources#addEnvironment(ContextEnvironment) * @see EmbeddedTomcat#addContextEnvironment(ContextEnvironment) * @see EmbeddedTomcat#addContextEnvironmentString(String, String) */ public EmbeddedTomcat addContextEnvironment(String name, String value, String type) { final ContextEnvironment env = new ContextEnvironment(); env.setType(type); env.setName(name); env.setValue(value); addContextEnvironment(env); return this; } /** * Convenient method for adding a context environment with type java.lang.String to the embedded * Tomcat. * * <pre> * embeddedTomcat.addContextEnvironment("aparam", "test"); * </pre> * * @param name name of the context environment * @param value value of the context environment * @return The embedded Tomcat * @see ContextEnvironment * @see NamingResources#addEnvironment(ContextEnvironment) * @see EmbeddedTomcat#addContextEnvironment(ContextEnvironment) * @see EmbeddedTomcat#addContextEnvironment(String, String, String) */ public EmbeddedTomcat addContextEnvironmentString(String name, String value) { addContextEnvironment(name, value, "java.lang.String"); return this; } /** * Read ContextEnvironment and ContextResource definition from a text file. * * @param contextFile Location of the context file * @return The embedded Tomcat * @deprecated use {@link #setContextFile(URL)} */ @Deprecated public EmbeddedTomcat addContextEnvironmentAndResourceFromFile(File contextFile) { try { return setContextFile(contextFile.toURI().toURL()); } catch (MalformedURLException e) { throw new RuntimeException(e); } } /** * Sets the location of the context file that configures this web application * * @param contextFileURL Location of the context file * @return The embedded Tomcat instance */ public EmbeddedTomcat setContextFile(URL contextFileURL) { this.contextFileURL = contextFileURL; return this; } /** * Sets the location of the context file that configures this web application * * @param contextFile Location of the context file * @return The embedded Tomcat instance */ public EmbeddedTomcat setContextFile(String contextFile) { try { this.contextFileURL = new File(contextFile).toURI().toURL(); } catch (MalformedURLException e) { throw new RuntimeException(e); } return this; } /** * Read ContextEnvironment and ContextResource definition from a text file. * * @param contextFile Location to a context file * @return The embedded Tomcat * @deprecated use {@link #setContextFile(String)} */ @Deprecated public EmbeddedTomcat addContextEnvironmentAndResourceFromFile(String contextFile) { try { setContextFile(new File(contextFile).toURI().toURL()); } catch (MalformedURLException e) { throw new RuntimeException(e); } return this; } /** * Starts the embedded Tomcat and do not wait for incoming requests. Returns immediately if the * configured port is in use. * * @see EmbeddedTomcat#startAndWait() */ public void start() { start(false); } /** * Starts the embedded Tomcat and waits for incoming requests. Returns immediately if the * configured port is in use. * * @see EmbeddedTomcat#start() */ public void startAndWait() { start(true); } private void start(boolean await) { // try to shutdown a previous Tomcat sendShutdownCommand(); try { final ServerSocket srv = new ServerSocket(this.httpPort); srv.close(); } catch (IOException e) { log.error("PORT " + this.httpPort + " ALREADY IN USE"); return; } // Read a dummy value. This triggers loading of the catalina.properties // file CatalinaProperties.getProperty("dummy"); appendSkipJars("tomcat.util.scan.DefaultJarScanner.jarsToSkip", this.skipJarsDefaultJarScanner); appendSkipJars( "org.apache.catalina.startup.ContextConfig.jarsToSkip", this.skipJarsContextConfig); appendSkipJars("org.apache.catalina.startup.TldConfig.jarsToSkip", this.skipJarsTldConfig); this.tomcat = new Tomcat(); if (this.tempDirectory == null) { this.tempDirectory = new File(".", "/target/tomcat." + this.httpPort).getAbsolutePath(); } this.tomcat.setBaseDir(this.tempDirectory); if (this.silent) { this.tomcat.setSilent(true); } if (this.addDefaultListeners) { this.tomcat.getServer().addLifecycleListener(new AprLifecycleListener()); } if (this.useNio) { Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); connector.setPort(this.httpPort); connector.setMaxPostSize(this.maxPostSize); connector.setURIEncoding("UTF-8"); this.tomcat.setConnector(connector); this.tomcat.getService().addConnector(connector); } else { this.tomcat.setPort(this.httpPort); this.tomcat.getConnector().setURIEncoding("UTF-8"); this.tomcat.getConnector().setMaxPostSize(this.maxPostSize); } if (this.compressionMinSize >= 0) { this.tomcat .getConnector() .setProperty("compression", String.valueOf(this.compressionMinSize)); this.tomcat.getConnector().setProperty("compressableMimeType", this.compressableMimeType); } if (this.httpsPort != 0) { final Connector httpsConnector; if (this.useNio) { httpsConnector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); } else { httpsConnector = new Connector("HTTP/1.1"); } httpsConnector.setSecure(true); httpsConnector.setPort(this.httpsPort); httpsConnector.setMaxPostSize(this.maxPostSize); httpsConnector.setScheme("https"); httpsConnector.setURIEncoding("UTF-8"); httpsConnector.setProperty("SSLEnabled", "true"); httpsConnector.setProperty("keyAlias", this.keyAlias); httpsConnector.setProperty("keystoreFile", this.keyStoreFile); httpsConnector.setProperty("keystorePass", this.keyStorePass); httpsConnector.setProperty("sslProtocol", this.sslProtocol); if (this.compressionMinSize >= 0) { httpsConnector.setProperty("compression", String.valueOf(this.compressionMinSize)); httpsConnector.setProperty("compressableMimeType", this.compressableMimeType); } this.tomcat.getEngine().setDefaultHost("localhost"); this.tomcat.getService().addConnector(httpsConnector); } if (this.shutdownPort != null) { this.tomcat.getServer().setPort(this.shutdownPort); } String contextDir = this.contextDirectory; if (contextDir == null) { contextDir = new File(".").getAbsolutePath() + "/src/main/webapp"; } final Context ctx; try { if (!this.contextPath.equals("")) { File rootCtxDir = new File("./target/tcroot"); if (!rootCtxDir.exists()) { rootCtxDir.mkdirs(); } Context rootCtx = this.tomcat.addWebapp("", rootCtxDir.getAbsolutePath()); rootCtx.setPrivileged(true); Tomcat.addServlet(rootCtx, "listContexts", new ListContextsServlet(rootCtx)) .addMapping("/"); } ctx = this.tomcat.addWebapp(this.contextPath, contextDir); ctx.setResources(new TargetClassesContext()); } catch (ServletException e) { throw new RuntimeException(e); } if (this.privileged) { ctx.setPrivileged(true); } if (this.enableNaming || !this.contextEnvironments.isEmpty() || !this.contextResources.isEmpty() || this.contextFileURL != null) { this.tomcat.enableNaming(); if (this.addDefaultListeners) { this.tomcat.getServer().addLifecycleListener(new GlobalResourcesLifecycleListener()); } } if (this.addDefaultListeners) { Server server = this.tomcat.getServer(); server.addLifecycleListener(new JasperListener()); server.addLifecycleListener(new JreMemoryLeakPreventionListener()); server.addLifecycleListener(new ThreadLocalLeakPreventionListener()); } for (ContextEnvironment env : this.contextEnvironments) { ctx.getNamingResources().addEnvironment(env); } for (ContextResource res : this.contextResources) { ctx.getNamingResources().addResource(res); } for (ApplicationParameter param : this.contextInitializationParameters) { ctx.addApplicationParameter(param); } if (this.contextFileURL != null) { ctx.setConfigFile(this.contextFileURL); } // Shutdown tomcat if a failure occurs during startup ctx.addLifecycleListener( new LifecycleListener() { @Override public void lifecycleEvent(LifecycleEvent event) { if (event.getLifecycle().getState() == LifecycleState.FAILED) { ((StandardServer) EmbeddedTomcat.this.tomcat.getServer()).stopAwait(); } } }); try { this.tomcat.start(); } catch (LifecycleException e) { throw new RuntimeException(e); } ((StandardManager) ctx.getManager()).setPathname(null); installSlf4jBridge(); if (await) { this.tomcat.getServer().await(); stop(); } } /** Stops the embedded tomcat. Does nothing if it's not started */ public void stop() { if (this.tomcat != null) { try { this.tomcat.stop(); } catch (LifecycleException e) { throw new RuntimeException(e); } } } private static void appendSkipJars(String systemPropertyKey, String skipJars) { if (skipJars != null && !skipJars.trim().isEmpty()) { String oldValue = System.getProperty(systemPropertyKey); String newValue; if (oldValue != null && !oldValue.trim().isEmpty()) { newValue = oldValue + "," + skipJars; } else { newValue = skipJars; } System.setProperty(systemPropertyKey, newValue); } } private void sendShutdownCommand() { if (this.shutdownPort != null) { try { final Socket socket = new Socket("localhost", this.shutdownPort); final OutputStream stream = socket.getOutputStream(); for (int i = 0; i < "SHUTDOWN".length(); i++) { stream.write("SHUTDOWN".charAt(i)); } stream.flush(); stream.close(); socket.close(); } catch (UnknownHostException e) { if (!this.silent) { log.debug(e); } return; } catch (IOException e) { if (!this.silent) { log.debug(e); } return; } // try to wait the specified amount of seconds until port becomes // available int count = 0; while (count < this.secondsToWaitBeforePortBecomesAvailable * 2) { try { final ServerSocket srv = new ServerSocket(this.httpPort); srv.close(); return; } catch (IOException e) { count++; } try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { return; } } } } private static void installSlf4jBridge() { try { // Check if slf4j bridge is available final Class<?> clazz = Class.forName("org.slf4j.bridge.SLF4JBridgeHandler"); // Remove all JUL handlers java.util.logging.LogManager.getLogManager().reset(); // Install slf4j bridge handler final Method method = clazz.getMethod("install", new Class<?>[0]); method.invoke(null); } catch (ClassNotFoundException e) { // do nothing } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (SecurityException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } }
public class ConnectionPool extends NotificationBroadcasterSupport implements ConnectionPoolMBean { /** logger */ private static final Log log = LogFactory.getLog(ConnectionPool.class); /** the connection pool */ protected org.apache.tomcat.jdbc.pool.ConnectionPool pool = null; /** sequence for JMX notifications */ protected AtomicInteger sequence = new AtomicInteger(0); /** Listeners that are local and interested in our notifications, no need for JMX */ protected ConcurrentLinkedQueue<NotificationListener> listeners = new ConcurrentLinkedQueue<NotificationListener>(); public ConnectionPool(org.apache.tomcat.jdbc.pool.ConnectionPool pool) { super(); this.pool = pool; } public org.apache.tomcat.jdbc.pool.ConnectionPool getPool() { return pool; } public PoolConfiguration getPoolProperties() { return pool.getPoolProperties(); } // ================================================================= // NOTIFICATION INFO // ================================================================= public static final String NOTIFY_INIT = "INIT FAILED"; public static final String NOTIFY_CONNECT = "CONNECTION FAILED"; public static final String NOTIFY_ABANDON = "CONNECTION ABANDONED"; public static final String SLOW_QUERY_NOTIFICATION = "SLOW QUERY"; public static final String FAILED_QUERY_NOTIFICATION = "FAILED QUERY"; public static final String SUSPECT_ABANDONED_NOTIFICATION = "SUSPECT CONNETION ABANDONED"; public static final String POOL_EMPTY = "POOL EMPTY"; @Override public MBeanNotificationInfo[] getNotificationInfo() { MBeanNotificationInfo[] pres = super.getNotificationInfo(); MBeanNotificationInfo[] loc = getDefaultNotificationInfo(); MBeanNotificationInfo[] aug = new MBeanNotificationInfo[pres.length + loc.length]; if (pres.length > 0) System.arraycopy(pres, 0, aug, 0, pres.length); if (loc.length > 0) System.arraycopy(loc, 0, aug, pres.length, loc.length); return aug; } public static MBeanNotificationInfo[] getDefaultNotificationInfo() { String[] types = new String[] { NOTIFY_INIT, NOTIFY_CONNECT, NOTIFY_ABANDON, SLOW_QUERY_NOTIFICATION, FAILED_QUERY_NOTIFICATION, SUSPECT_ABANDONED_NOTIFICATION }; String name = Notification.class.getName(); String description = "A connection pool error condition was met."; MBeanNotificationInfo info = new MBeanNotificationInfo(types, name, description); return new MBeanNotificationInfo[] {info}; } /** * Return true if the notification was sent successfully, false otherwise. * * @param type * @param message * @return true if the notification succeeded */ public boolean notify(final String type, String message) { try { Notification n = new Notification( type, this, sequence.incrementAndGet(), System.currentTimeMillis(), "[" + type + "] " + message); sendNotification(n); for (NotificationListener listener : listeners) { listener.handleNotification(n, this); } return true; } catch (Exception x) { if (log.isDebugEnabled()) { log.debug("Notify failed. Type=" + type + "; Message=" + message, x); } return false; } } public void addListener(NotificationListener list) { listeners.add(list); } public boolean removeListener(NotificationListener list) { return listeners.remove(list); } // ================================================================= // POOL STATS // ================================================================= @Override public int getSize() { return pool.getSize(); } @Override public int getIdle() { return pool.getIdle(); } @Override public int getActive() { return pool.getActive(); } @Override public int getNumIdle() { return getIdle(); } @Override public int getNumActive() { return getActive(); } @Override public int getWaitCount() { return pool.getWaitCount(); } // ================================================================= // POOL OPERATIONS // ================================================================= @Override public void checkIdle() { pool.checkIdle(); } @Override public void checkAbandoned() { pool.checkAbandoned(); } @Override public void testIdle() { pool.testAllIdle(); } // ================================================================= // POOL PROPERTIES // ================================================================= // ========================================================= // PROPERTIES / CONFIGURATION // ========================================================= @Override public String getConnectionProperties() { return getPoolProperties().getConnectionProperties(); } @Override public Properties getDbProperties() { return PoolUtilities.cloneWithoutPassword(getPoolProperties().getDbProperties()); } @Override public String getDefaultCatalog() { return getPoolProperties().getDefaultCatalog(); } @Override public int getDefaultTransactionIsolation() { return getPoolProperties().getDefaultTransactionIsolation(); } @Override public String getDriverClassName() { return getPoolProperties().getDriverClassName(); } @Override public int getInitialSize() { return getPoolProperties().getInitialSize(); } @Override public String getInitSQL() { return getPoolProperties().getInitSQL(); } @Override public String getJdbcInterceptors() { return getPoolProperties().getJdbcInterceptors(); } @Override public int getMaxActive() { return getPoolProperties().getMaxActive(); } @Override public int getMaxIdle() { return getPoolProperties().getMaxIdle(); } @Override public int getMaxWait() { return getPoolProperties().getMaxWait(); } @Override public int getMinEvictableIdleTimeMillis() { return getPoolProperties().getMinEvictableIdleTimeMillis(); } @Override public int getMinIdle() { return getPoolProperties().getMinIdle(); } @Override public long getMaxAge() { return getPoolProperties().getMaxAge(); } @Override public String getName() { return this.getPoolName(); } @Override public int getNumTestsPerEvictionRun() { return getPoolProperties().getNumTestsPerEvictionRun(); } /** @return DOES NOT RETURN THE PASSWORD, IT WOULD SHOW UP IN JMX */ @Override public String getPassword() { return "Password not available as DataSource/JMX operation."; } @Override public int getRemoveAbandonedTimeout() { return getPoolProperties().getRemoveAbandonedTimeout(); } @Override public int getTimeBetweenEvictionRunsMillis() { return getPoolProperties().getTimeBetweenEvictionRunsMillis(); } @Override public String getUrl() { return getPoolProperties().getUrl(); } @Override public String getUsername() { return getPoolProperties().getUsername(); } @Override public long getValidationInterval() { return getPoolProperties().getValidationInterval(); } @Override public String getValidationQuery() { return getPoolProperties().getValidationQuery(); } @Override public int getValidationQueryTimeout() { return getPoolProperties().getValidationQueryTimeout(); } /** {@inheritDoc} */ @Override public String getValidatorClassName() { return getPoolProperties().getValidatorClassName(); } /** {@inheritDoc} */ @Override public Validator getValidator() { return getPoolProperties().getValidator(); } @Override public boolean isAccessToUnderlyingConnectionAllowed() { return getPoolProperties().isAccessToUnderlyingConnectionAllowed(); } @Override public Boolean isDefaultAutoCommit() { return getPoolProperties().isDefaultAutoCommit(); } @Override public Boolean isDefaultReadOnly() { return getPoolProperties().isDefaultReadOnly(); } @Override public boolean isLogAbandoned() { return getPoolProperties().isLogAbandoned(); } @Override public boolean isPoolSweeperEnabled() { return getPoolProperties().isPoolSweeperEnabled(); } @Override public boolean isRemoveAbandoned() { return getPoolProperties().isRemoveAbandoned(); } @Override public int getAbandonWhenPercentageFull() { return getPoolProperties().getAbandonWhenPercentageFull(); } @Override public boolean isTestOnBorrow() { return getPoolProperties().isTestOnBorrow(); } @Override public boolean isTestOnConnect() { return getPoolProperties().isTestOnConnect(); } @Override public boolean isTestOnReturn() { return getPoolProperties().isTestOnReturn(); } @Override public boolean isTestWhileIdle() { return getPoolProperties().isTestWhileIdle(); } @Override public Boolean getDefaultAutoCommit() { return getPoolProperties().getDefaultAutoCommit(); } @Override public Boolean getDefaultReadOnly() { return getPoolProperties().getDefaultReadOnly(); } @Override public InterceptorDefinition[] getJdbcInterceptorsAsArray() { return getPoolProperties().getJdbcInterceptorsAsArray(); } @Override public boolean getUseLock() { return getPoolProperties().getUseLock(); } @Override public boolean isFairQueue() { return getPoolProperties().isFairQueue(); } @Override public boolean isJmxEnabled() { return getPoolProperties().isJmxEnabled(); } @Override public boolean isUseEquals() { return getPoolProperties().isUseEquals(); } @Override public void setAbandonWhenPercentageFull(int percentage) { getPoolProperties().setAbandonWhenPercentageFull(percentage); } @Override public void setAccessToUnderlyingConnectionAllowed(boolean accessToUnderlyingConnectionAllowed) { getPoolProperties().setAccessToUnderlyingConnectionAllowed(accessToUnderlyingConnectionAllowed); } @Override public void setDbProperties(Properties dbProperties) { getPoolProperties().setDbProperties(dbProperties); } @Override public void setDefaultReadOnly(Boolean defaultReadOnly) { getPoolProperties().setDefaultReadOnly(defaultReadOnly); } @Override public void setMaxAge(long maxAge) { getPoolProperties().setMaxAge(maxAge); } @Override public void setName(String name) { getPoolProperties().setName(name); } @Override public String getPoolName() { return getPoolProperties().getName(); } @Override public void setConnectionProperties(String connectionProperties) { getPoolProperties().setConnectionProperties(connectionProperties); } @Override public void setDefaultAutoCommit(Boolean defaultAutoCommit) { getPoolProperties().setDefaultAutoCommit(defaultAutoCommit); } @Override public void setDefaultCatalog(String defaultCatalog) { getPoolProperties().setDefaultCatalog(defaultCatalog); } @Override public void setDefaultTransactionIsolation(int defaultTransactionIsolation) { getPoolProperties().setDefaultTransactionIsolation(defaultTransactionIsolation); } @Override public void setDriverClassName(String driverClassName) { getPoolProperties().setDriverClassName(driverClassName); } @Override public void setFairQueue(boolean fairQueue) { // noop - this pool is already running throw new UnsupportedOperationException(); } @Override public void setInitialSize(int initialSize) { // noop - this pool is already running throw new UnsupportedOperationException(); } @Override public void setInitSQL(String initSQL) { getPoolProperties().setInitSQL(initSQL); } @Override public void setJdbcInterceptors(String jdbcInterceptors) { // noop - this pool is already running throw new UnsupportedOperationException(); } @Override public void setJmxEnabled(boolean jmxEnabled) { // noop - this pool is already running and obviously jmx enabled throw new UnsupportedOperationException(); } @Override public void setLogAbandoned(boolean logAbandoned) { getPoolProperties().setLogAbandoned(logAbandoned); } @Override public void setMaxActive(int maxActive) { getPoolProperties().setMaxActive(maxActive); } @Override public void setMaxIdle(int maxIdle) { getPoolProperties().setMaxIdle(maxIdle); } @Override public void setMaxWait(int maxWait) { getPoolProperties().setMaxWait(maxWait); } @Override public void setMinEvictableIdleTimeMillis(int minEvictableIdleTimeMillis) { boolean wasEnabled = getPoolProperties().isPoolSweeperEnabled(); getPoolProperties().setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); boolean shouldBeEnabled = getPoolProperties().isPoolSweeperEnabled(); // make sure pool cleaner starts/stops when it should if (!wasEnabled && shouldBeEnabled) pool.initializePoolCleaner(getPoolProperties()); else if (wasEnabled && !shouldBeEnabled) pool.terminatePoolCleaner(); } @Override public void setMinIdle(int minIdle) { getPoolProperties().setMinIdle(minIdle); } @Override public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) { getPoolProperties().setNumTestsPerEvictionRun(numTestsPerEvictionRun); } @Override public void setPassword(String password) { getPoolProperties().setPassword(password); } @Override public void setRemoveAbandoned(boolean removeAbandoned) { boolean wasEnabled = getPoolProperties().isPoolSweeperEnabled(); getPoolProperties().setRemoveAbandoned(removeAbandoned); boolean shouldBeEnabled = getPoolProperties().isPoolSweeperEnabled(); // make sure pool cleaner starts/stops when it should if (!wasEnabled && shouldBeEnabled) pool.initializePoolCleaner(getPoolProperties()); else if (wasEnabled && !shouldBeEnabled) pool.terminatePoolCleaner(); } @Override public void setRemoveAbandonedTimeout(int removeAbandonedTimeout) { boolean wasEnabled = getPoolProperties().isPoolSweeperEnabled(); getPoolProperties().setRemoveAbandonedTimeout(removeAbandonedTimeout); boolean shouldBeEnabled = getPoolProperties().isPoolSweeperEnabled(); // make sure pool cleaner starts/stops when it should if (!wasEnabled && shouldBeEnabled) pool.initializePoolCleaner(getPoolProperties()); else if (wasEnabled && !shouldBeEnabled) pool.terminatePoolCleaner(); } @Override public void setTestOnBorrow(boolean testOnBorrow) { getPoolProperties().setTestOnBorrow(testOnBorrow); } @Override public void setTestOnConnect(boolean testOnConnect) { getPoolProperties().setTestOnConnect(testOnConnect); } @Override public void setTestOnReturn(boolean testOnReturn) { getPoolProperties().setTestOnReturn(testOnReturn); } @Override public void setTestWhileIdle(boolean testWhileIdle) { boolean wasEnabled = getPoolProperties().isPoolSweeperEnabled(); getPoolProperties().setTestWhileIdle(testWhileIdle); boolean shouldBeEnabled = getPoolProperties().isPoolSweeperEnabled(); // make sure pool cleaner starts/stops when it should if (!wasEnabled && shouldBeEnabled) pool.initializePoolCleaner(getPoolProperties()); else if (wasEnabled && !shouldBeEnabled) pool.terminatePoolCleaner(); } @Override public void setTimeBetweenEvictionRunsMillis(int timeBetweenEvictionRunsMillis) { boolean wasEnabled = getPoolProperties().isPoolSweeperEnabled(); getPoolProperties().setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); boolean shouldBeEnabled = getPoolProperties().isPoolSweeperEnabled(); // make sure pool cleaner starts/stops when it should if (!wasEnabled && shouldBeEnabled) { pool.initializePoolCleaner(getPoolProperties()); } else if (wasEnabled) { pool.terminatePoolCleaner(); if (shouldBeEnabled) { pool.initializePoolCleaner(getPoolProperties()); } } } @Override public void setUrl(String url) { getPoolProperties().setUrl(url); } @Override public void setUseEquals(boolean useEquals) { getPoolProperties().setUseEquals(useEquals); } @Override public void setUseLock(boolean useLock) { getPoolProperties().setUseLock(useLock); } @Override public void setUsername(String username) { getPoolProperties().setUsername(username); } @Override public void setValidationInterval(long validationInterval) { getPoolProperties().setValidationInterval(validationInterval); } @Override public void setValidationQuery(String validationQuery) { getPoolProperties().setValidationQuery(validationQuery); } @Override public void setValidationQueryTimeout(int validationQueryTimeout) { getPoolProperties().setValidationQueryTimeout(validationQueryTimeout); } /** {@inheritDoc} */ @Override public void setValidatorClassName(String className) { getPoolProperties().setValidatorClassName(className); } /** {@inheritDoc} */ @Override public int getSuspectTimeout() { return getPoolProperties().getSuspectTimeout(); } /** {@inheritDoc} */ @Override public void setSuspectTimeout(int seconds) { getPoolProperties().setSuspectTimeout(seconds); } /** {@inheritDoc} */ @Override public void setDataSource(Object ds) { getPoolProperties().setDataSource(ds); } /** {@inheritDoc} */ @Override public Object getDataSource() { return getPoolProperties().getDataSource(); } /** {@inheritDoc} */ @Override public void setDataSourceJNDI(String jndiDS) { getPoolProperties().setDataSourceJNDI(jndiDS); } /** {@inheritDoc} */ @Override public String getDataSourceJNDI() { return getPoolProperties().getDataSourceJNDI(); } /** {@inheritDoc} */ @Override public boolean isAlternateUsernameAllowed() { return getPoolProperties().isAlternateUsernameAllowed(); } /** {@inheritDoc} */ @Override public void setAlternateUsernameAllowed(boolean alternateUsernameAllowed) { getPoolProperties().setAlternateUsernameAllowed(alternateUsernameAllowed); } /** {@inheritDoc} */ @Override public void setValidator(Validator validator) { getPoolProperties().setValidator(validator); } /** {@inheritDoc} */ @Override public void setCommitOnReturn(boolean commitOnReturn) { getPoolProperties().setCommitOnReturn(commitOnReturn); } /** {@inheritDoc} */ @Override public boolean getCommitOnReturn() { return getPoolProperties().getCommitOnReturn(); } /** {@inheritDoc} */ @Override public void setRollbackOnReturn(boolean rollbackOnReturn) { getPoolProperties().setRollbackOnReturn(rollbackOnReturn); } /** {@inheritDoc} */ @Override public boolean getRollbackOnReturn() { return getPoolProperties().getRollbackOnReturn(); } /** {@inheritDoc} */ @Override public void setUseDisposableConnectionFacade(boolean useDisposableConnectionFacade) { getPoolProperties().setUseDisposableConnectionFacade(useDisposableConnectionFacade); } /** {@inheritDoc} */ @Override public boolean getUseDisposableConnectionFacade() { return getPoolProperties().getUseDisposableConnectionFacade(); } /** {@inheritDoc} */ @Override public void setLogValidationErrors(boolean logValidationErrors) { getPoolProperties().setLogValidationErrors(logValidationErrors); } /** {@inheritDoc} */ @Override public boolean getLogValidationErrors() { return getPoolProperties().getLogValidationErrors(); } /** {@inheritDoc} */ @Override public boolean getPropagateInterruptState() { return getPoolProperties().getPropagateInterruptState(); } /** {@inheritDoc} */ @Override public void setPropagateInterruptState(boolean propagateInterruptState) { getPoolProperties().setPropagateInterruptState(propagateInterruptState); } /** {@inheritDoc} */ @Override public boolean isIgnoreExceptionOnPreLoad() { return getPoolProperties().isIgnoreExceptionOnPreLoad(); } /** {@inheritDoc} */ @Override public void setIgnoreExceptionOnPreLoad(boolean ignoreExceptionOnPreLoad) { // noop - this pool is already running throw new UnsupportedOperationException(); } /** {@inheritDoc} */ @Override public void purge() { pool.purge(); } /** {@inheritDoc} */ @Override public void purgeOnReturn() { pool.purgeOnReturn(); } }
/* * Listener to provider informations to mod_heartbeat.c * *msg_format = "v=%u&ready=%u&busy=%u"; (message to send). * send the muticast message using the format... * what about the bind(IP. port) only IP makes sense (for the moment). * BTW:v = version :-) */ public class HeartbeatListener implements LifecycleListener, ContainerListener { private static final Log log = LogFactory.getLog(HeartbeatListener.class); /* To allow to select the connector */ private int port = 0; private String host = null; public void setHost(String host) { this.host = host; } public void setPort(int port) { this.port = port; } /* for multicasting stuff */ private String ip = "224.0.1.105"; /* Multicast IP */ private int multiport = 23364; /* Multicast Port */ private int ttl = 16; public void setGroup(String ip) { this.ip = ip; } public String getGroup() { return ip; } public void setMultiport(int multiport) { this.multiport = multiport; } public int getMultiport() { return multiport; } public void setTtl(int ttl) { this.ttl = ttl; } public int getTtl() { return ttl; } /** Proxy list, format "address:port,address:port". */ private String proxyList = null; public String getProxyList() { return proxyList; } public void setProxyList(String proxyList) { this.proxyList = proxyList; } /** URL prefix. */ private String proxyURL = "/HeartbeatListener"; public String getProxyURL() { return proxyURL; } public void setProxyURL(String proxyURL) { this.proxyURL = proxyURL; } private CollectedInfo coll = null; private Sender sender = null; @Override public void containerEvent(ContainerEvent event) {} @Override public void lifecycleEvent(LifecycleEvent event) { if (Lifecycle.PERIODIC_EVENT.equals(event.getType())) { if (sender == null) { if (proxyList == null) sender = new MultiCastSender(); else sender = new TcpSender(); } /* Read busy and ready */ if (coll == null) { try { coll = new CollectedInfo(host, port); this.port = coll.getPort(); this.host = coll.getHost(); } catch (Exception ex) { log.error("Unable to initialize info collection: " + ex); coll = null; return; } } /* Start or restart sender */ try { sender.init(this); } catch (Exception ex) { log.error("Unable to initialize Sender: " + ex); sender = null; return; } /* refresh the connector information and send it */ try { coll.refresh(); } catch (Exception ex) { log.error("Unable to collect load information: " + ex); coll = null; return; } String output = "v=1&ready=" + coll.getReady() + "&busy=" + coll.getBusy() + "&port=" + port; try { sender.send(output); } catch (Exception ex) { log.error("Unable to send colllected load information: " + ex); } } } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public CollectedInfo getColl() { return coll; } public void setColl(CollectedInfo coll) { this.coll = coll; } public Sender getSender() { return sender; } public void setSender(Sender sender) { this.sender = sender; } public static Log getLog() { return log; } public int getPort() { return port; } public String getHost() { return host; } }
/** * An implementation of the W3c Extended Log File Format. See http://www.w3.org/TR/WD-logfile.html * for more information about the format. * * <p>The following fields are supported: * * <ul> * <li><code>c-dns</code>: Client hostname * <li><code>c-ip</code>: Client ip address * <li><code>bytes</code>: bytes served * <li><code>cs-method</code>: request method * <li><code>cs-uri</code>: The full uri requested * <li><code>cs-uri-query</code>: The query string * <li><code>cs-uri-stem</code>: The uri without query string * <li><code>date</code>: The date in yyyy-mm-dd format for GMT * <li><code>s-dns</code>: The server dns entry * <li><code>s-ip</code>: The server ip address * <li><code>cs(XXX)</code>: The value of header XXX from client to server * <li><code>sc(XXX)</code>: The value of header XXX from server to client * <li><code>sc-status</code>: The status code * <li><code>time</code>: Time the request was served * <li><code>time-taken</code>: Time (in seconds) taken to serve the request * <li><code>x-A(XXX)</code>: Pull XXX attribute from the servlet context * <li><code>x-C(XXX)</code>: Pull the first cookie of the name XXX * <li><code>x-O(XXX)</code>: Pull the all response header values XXX * <li><code>x-R(XXX)</code>: Pull XXX attribute from the servlet request * <li><code>x-S(XXX)</code>: Pull XXX attribute from the session * <li><code>x-P(...)</code>: Call request.getParameter(...) and URLencode it. Helpful to capture * certain POST parameters. * <li>For any of the x-H(...) the following method will be called from the * HttpServletRequestObject * <li><code>x-H(authType)</code>: getAuthType * <li><code>x-H(characterEncoding)</code>: getCharacterEncoding * <li><code>x-H(contentLength)</code>: getContentLength * <li><code>x-H(locale)</code>: getLocale * <li><code>x-H(protocol)</code>: getProtocol * <li><code>x-H(remoteUser)</code>: getRemoteUser * <li><code>x-H(requestedSessionId)</code>: getGequestedSessionId * <li><code>x-H(requestedSessionIdFromCookie)</code>: isRequestedSessionIdFromCookie * <li><code>x-H(requestedSessionIdValid)</code>: isRequestedSessionIdValid * <li><code>x-H(scheme)</code>: getScheme * <li><code>x-H(secure)</code>: isSecure * </ul> * * <p>Log rotation can be on or off. This is dictated by the rotatable property. * * <p>For UvNIX users, another field called <code>checkExists</code>is also available. If set to * true, the log file's existence will be checked before each logging. This way an external log * rotator can move the file somewhere and tomcat will start with a new file. * * <p>For JMX junkies, a public method called </code>rotate</code> has been made available to allow * you to tell this instance to move the existing log file to somewhere else start writing a new log * file. * * <p>Conditional logging is also supported. This can be done with the <code>condition</code> * property. If the value returned from ServletRequest.getAttribute(condition) yields a non-null * value. The logging will be skipped. * * <p>For extended attributes coming from a getAttribute() call, it is you responsibility to ensure * there are no newline or control characters. * * @author Tim Funk * @author Peter Rossbach * @version $Id: ExtendedAccessLogValve.java 1059388 2011-01-15 18:50:45Z markt $ */ public class ExtendedAccessLogValve extends AccessLogValve { private static final Log log = LogFactory.getLog(ExtendedAccessLogValve.class); // ----------------------------------------------------- Instance Variables /** The descriptive information about this implementation. */ protected static final String extendedAccessLogInfo = "org.apache.catalina.valves.ExtendedAccessLogValve/2.1"; // ------------------------------------------------------------- Properties /** Return descriptive information about this implementation. */ @Override public String getInfo() { return (extendedAccessLogInfo); } // --------------------------------------------------------- Public Methods // -------------------------------------------------------- Private Methods /** * Wrap the incoming value into quotes and escape any inner quotes with double quotes. * * @param value - The value to wrap quotes around * @return '-' if empty of null. Otherwise, toString() will be called on the object and the value * will be wrapped in quotes and any quotes will be escaped with 2 sets of quotes. */ private String wrap(Object value) { String svalue; // Does the value contain a " ? If so must encode it if (value == null || "-".equals(value)) return "-"; try { svalue = value.toString(); if ("".equals(svalue)) return "-"; } catch (Throwable e) { ExceptionUtils.handleThrowable(e); /* Log error */ return "-"; } /* Wrap all quotes in double quotes. */ StringBuilder buffer = new StringBuilder(svalue.length() + 2); buffer.append('\''); int i = 0; while (i < svalue.length()) { int j = svalue.indexOf('\'', i); if (j == -1) { buffer.append(svalue.substring(i)); i = svalue.length(); } else { buffer.append(svalue.substring(i, j + 1)); buffer.append('"'); i = j + 2; } } buffer.append('\''); return buffer.toString(); } /** Open the new log file for the date specified by <code>dateStamp</code>. */ @Override protected synchronized void open() { super.open(); if (currentLogFile.length() == 0) { writer.println("#Fields: " + pattern); writer.println("#Version: 2.0"); writer.println("#Software: " + ServerInfo.getServerInfo()); } } // ------------------------------------------------------ Lifecycle Methods protected static class DateElement implements AccessLogElement { // Milli-seconds in 24 hours private static final long INTERVAL = (1000 * 60 * 60 * 24); private static final ThreadLocal<ElementTimestampStruct> currentDate = new ThreadLocal<ElementTimestampStruct>() { @Override protected ElementTimestampStruct initialValue() { return new ElementTimestampStruct("yyyy-MM-dd"); } }; @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { ElementTimestampStruct eds = currentDate.get(); long millis = eds.currentTimestamp.getTime(); if (date.getTime() > (millis + INTERVAL - 1) || date.getTime() < millis) { eds.currentTimestamp.setTime(date.getTime() - (date.getTime() % INTERVAL)); eds.currentTimestampString = eds.currentTimestampFormat.format(eds.currentTimestamp); } buf.append(eds.currentTimestampString); } } protected static class TimeElement implements AccessLogElement { // Milli-seconds in a second private static final long INTERVAL = 1000; private static final ThreadLocal<ElementTimestampStruct> currentTime = new ThreadLocal<ElementTimestampStruct>() { @Override protected ElementTimestampStruct initialValue() { return new ElementTimestampStruct("HH:mm:ss"); } }; @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { ElementTimestampStruct eds = currentTime.get(); long millis = eds.currentTimestamp.getTime(); if (date.getTime() > (millis + INTERVAL - 1) || date.getTime() < millis) { eds.currentTimestamp.setTime(date.getTime() - (date.getTime() % INTERVAL)); eds.currentTimestampString = eds.currentTimestampFormat.format(eds.currentTimestamp); } buf.append(eds.currentTimestampString); } } protected class RequestHeaderElement implements AccessLogElement { private String header; public RequestHeaderElement(String header) { this.header = header; } @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { buf.append(wrap(request.getHeader(header))); } } protected class ResponseHeaderElement implements AccessLogElement { private String header; public ResponseHeaderElement(String header) { this.header = header; } @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { buf.append(wrap(response.getHeader(header))); } } protected class ServletContextElement implements AccessLogElement { private String attribute; public ServletContextElement(String attribute) { this.attribute = attribute; } @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { buf.append(wrap(request.getContext().getServletContext().getAttribute(attribute))); } } protected class CookieElement implements AccessLogElement { private String name; public CookieElement(String name) { this.name = name; } @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { Cookie[] c = request.getCookies(); for (int i = 0; c != null && i < c.length; i++) { if (name.equals(c[i].getName())) { buf.append(wrap(c[i].getValue())); } } } } /** write a specific response header - x-O(xxx) */ protected class ResponseAllHeaderElement implements AccessLogElement { private String header; public ResponseAllHeaderElement(String header) { this.header = header; } @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { if (null != response) { Iterator<String> iter = response.getHeaders(header).iterator(); if (iter.hasNext()) { StringBuilder buffer = new StringBuilder(); boolean first = true; while (iter.hasNext()) { if (!first) { buffer.append(","); } buffer.append(iter.next()); } buf.append(wrap(buffer.toString())); } return; } buf.append("-"); } } protected class RequestAttributeElement implements AccessLogElement { private String attribute; public RequestAttributeElement(String attribute) { this.attribute = attribute; } @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { buf.append(wrap(request.getAttribute(attribute))); } } protected class SessionAttributeElement implements AccessLogElement { private String attribute; public SessionAttributeElement(String attribute) { this.attribute = attribute; } @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { HttpSession session = null; if (request != null) { session = request.getSession(false); if (session != null) buf.append(wrap(session.getAttribute(attribute))); } } } protected class RequestParameterElement implements AccessLogElement { private String parameter; public RequestParameterElement(String parameter) { this.parameter = parameter; } /** urlEncode the given string. If null or empty, return null. */ private String urlEncode(String value) { if (null == value || value.length() == 0) { return null; } return URLEncoder.encode(value); } @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { buf.append(wrap(urlEncode(request.getParameter(parameter)))); } } protected static class PatternTokenizer { private StringReader sr = null; private StringBuilder buf = new StringBuilder(); private boolean ended = false; private boolean subToken; private boolean parameter; public PatternTokenizer(String str) { sr = new StringReader(str); } public boolean hasSubToken() { return subToken; } public boolean hasParameter() { return parameter; } public String getToken() throws IOException { if (ended) return null; String result = null; subToken = false; parameter = false; int c = sr.read(); while (c != -1) { switch (c) { case ' ': result = buf.toString(); buf = new StringBuilder(); buf.append((char) c); return result; case '-': result = buf.toString(); buf = new StringBuilder(); subToken = true; return result; case '(': result = buf.toString(); buf = new StringBuilder(); parameter = true; return result; case ')': result = buf.toString(); buf = new StringBuilder(); break; default: buf.append((char) c); } c = sr.read(); } ended = true; if (buf.length() != 0) { return buf.toString(); } else { return null; } } public String getParameter() throws IOException { String result; if (!parameter) { return null; } parameter = false; int c = sr.read(); while (c != -1) { if (c == ')') { result = buf.toString(); buf = new StringBuilder(); return result; } buf.append((char) c); c = sr.read(); } return null; } public String getWhiteSpaces() throws IOException { if (isEnded()) return ""; StringBuilder whiteSpaces = new StringBuilder(); if (buf.length() > 0) { whiteSpaces.append(buf); buf = new StringBuilder(); } int c = sr.read(); while (Character.isWhitespace((char) c)) { whiteSpaces.append((char) c); c = sr.read(); } if (c == -1) { ended = true; } else { buf.append((char) c); } return whiteSpaces.toString(); } public boolean isEnded() { return ended; } public String getRemains() throws IOException { StringBuilder remains = new StringBuilder(); for (int c = sr.read(); c != -1; c = sr.read()) { remains.append((char) c); } return remains.toString(); } } @Override protected AccessLogElement[] createLogElements() { if (log.isDebugEnabled()) { log.debug("decodePattern, pattern =" + pattern); } List<AccessLogElement> list = new ArrayList<AccessLogElement>(); PatternTokenizer tokenizer = new PatternTokenizer(pattern); try { // Ignore leading whitespace. tokenizer.getWhiteSpaces(); if (tokenizer.isEnded()) { log.info("pattern was just empty or whitespace"); return null; } String token = tokenizer.getToken(); while (token != null) { if (log.isDebugEnabled()) { log.debug("token = " + token); } AccessLogElement element = getLogElement(token, tokenizer); if (element == null) { break; } list.add(element); String whiteSpaces = tokenizer.getWhiteSpaces(); if (whiteSpaces.length() > 0) { list.add(new StringElement(whiteSpaces)); } if (tokenizer.isEnded()) { break; } token = tokenizer.getToken(); } if (log.isDebugEnabled()) { log.debug("finished decoding with element size of: " + list.size()); } return list.toArray(new AccessLogElement[0]); } catch (IOException e) { log.error("parse error", e); return null; } } protected AccessLogElement getLogElement(String token, PatternTokenizer tokenizer) throws IOException { if ("date".equals(token)) { return new DateElement(); } else if ("time".equals(token)) { if (tokenizer.hasSubToken()) { String nextToken = tokenizer.getToken(); if ("taken".equals(nextToken)) { return new ElapsedTimeElement(false); } } else { return new TimeElement(); } } else if ("bytes".equals(token)) { return new ByteSentElement(true); } else if ("cached".equals(token)) { /* I don't know how to evaluate this! */ return new StringElement("-"); } else if ("c".equals(token)) { String nextToken = tokenizer.getToken(); if ("ip".equals(nextToken)) { return new RemoteAddrElement(); } else if ("dns".equals(nextToken)) { return new HostElement(); } } else if ("s".equals(token)) { String nextToken = tokenizer.getToken(); if ("ip".equals(nextToken)) { return new LocalAddrElement(); } else if ("dns".equals(nextToken)) { return new AccessLogElement() { @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { String value; try { value = InetAddress.getLocalHost().getHostName(); } catch (Throwable e) { ExceptionUtils.handleThrowable(e); value = "localhost"; } buf.append(value); } }; } } else if ("cs".equals(token)) { return getClientToServerElement(tokenizer); } else if ("sc".equals(token)) { return getServerToClientElement(tokenizer); } else if ("sr".equals(token) || "rs".equals(token)) { return getProxyElement(tokenizer); } else if ("x".equals(token)) { return getXParameterElement(tokenizer); } log.error("unable to decode with rest of chars starting: " + token); return null; } protected AccessLogElement getClientToServerElement(PatternTokenizer tokenizer) throws IOException { if (tokenizer.hasSubToken()) { String token = tokenizer.getToken(); if ("method".equals(token)) { return new MethodElement(); } else if ("uri".equals(token)) { if (tokenizer.hasSubToken()) { token = tokenizer.getToken(); if ("stem".equals(token)) { return new RequestURIElement(); } else if ("query".equals(token)) { return new AccessLogElement() { @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { String query = request.getQueryString(); if (query != null) { buf.append(query); } else { buf.append('-'); } } }; } } else { return new AccessLogElement() { @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { String query = request.getQueryString(); if (query == null) { buf.append(request.getRequestURI()); } else { buf.append(request.getRequestURI()); buf.append('?'); buf.append(request.getQueryString()); } } }; } } } else if (tokenizer.hasParameter()) { String parameter = tokenizer.getParameter(); if (parameter == null) { log.error("No closing ) found for in decode"); return null; } return new RequestHeaderElement(parameter); } log.error("The next characters couldn't be decoded: " + tokenizer.getRemains()); return null; } protected AccessLogElement getServerToClientElement(PatternTokenizer tokenizer) throws IOException { if (tokenizer.hasSubToken()) { String token = tokenizer.getToken(); if ("status".equals(token)) { return new HttpStatusCodeElement(); } else if ("comment".equals(token)) { return new StringElement("?"); } } else if (tokenizer.hasParameter()) { String parameter = tokenizer.getParameter(); if (parameter == null) { log.error("No closing ) found for in decode"); return null; } return new ResponseHeaderElement(parameter); } log.error("The next characters couldn't be decoded: " + tokenizer.getRemains()); return null; } protected AccessLogElement getProxyElement(PatternTokenizer tokenizer) throws IOException { String token = null; if (tokenizer.hasSubToken()) { tokenizer.getToken(); return new StringElement("-"); } else if (tokenizer.hasParameter()) { tokenizer.getParameter(); return new StringElement("-"); } log.error("The next characters couldn't be decoded: " + token); return null; } protected AccessLogElement getXParameterElement(PatternTokenizer tokenizer) throws IOException { if (!tokenizer.hasSubToken()) { log.error("x param in wrong format. Needs to be 'x-#(...)' read the docs!"); return null; } String token = tokenizer.getToken(); if (!tokenizer.hasParameter()) { log.error("x param in wrong format. Needs to be 'x-#(...)' read the docs!"); return null; } String parameter = tokenizer.getParameter(); if (parameter == null) { log.error("No closing ) found for in decode"); return null; } if ("A".equals(token)) { return new ServletContextElement(parameter); } else if ("C".equals(token)) { return new CookieElement(parameter); } else if ("R".equals(token)) { return new RequestAttributeElement(parameter); } else if ("S".equals(token)) { return new SessionAttributeElement(parameter); } else if ("H".equals(token)) { return getServletRequestElement(parameter); } else if ("P".equals(token)) { return new RequestParameterElement(parameter); } else if ("O".equals(token)) { return new ResponseAllHeaderElement(parameter); } log.error("x param for servlet request, couldn't decode value: " + token); return null; } protected AccessLogElement getServletRequestElement(String parameter) { if ("authType".equals(parameter)) { return new AccessLogElement() { @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { buf.append(wrap(request.getAuthType())); } }; } else if ("remoteUser".equals(parameter)) { return new AccessLogElement() { @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { buf.append(wrap(request.getRemoteUser())); } }; } else if ("requestedSessionId".equals(parameter)) { return new AccessLogElement() { @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { buf.append(wrap(request.getRequestedSessionId())); } }; } else if ("requestedSessionIdFromCookie".equals(parameter)) { return new AccessLogElement() { @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { buf.append(wrap("" + request.isRequestedSessionIdFromCookie())); } }; } else if ("requestedSessionIdValid".equals(parameter)) { return new AccessLogElement() { @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { buf.append(wrap("" + request.isRequestedSessionIdValid())); } }; } else if ("contentLength".equals(parameter)) { return new AccessLogElement() { @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { buf.append(wrap("" + request.getContentLength())); } }; } else if ("characterEncoding".equals(parameter)) { return new AccessLogElement() { @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { buf.append(wrap(request.getCharacterEncoding())); } }; } else if ("locale".equals(parameter)) { return new AccessLogElement() { @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { buf.append(wrap(request.getLocale())); } }; } else if ("protocol".equals(parameter)) { return new AccessLogElement() { @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { buf.append(wrap(request.getProtocol())); } }; } else if ("scheme".equals(parameter)) { return new AccessLogElement() { @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { buf.append(request.getScheme()); } }; } else if ("secure".equals(parameter)) { return new AccessLogElement() { @Override public void addElement( StringBuilder buf, Date date, Request request, Response response, long time) { buf.append(wrap("" + request.isSecure())); } }; } log.error("x param for servlet request, couldn't decode value: " + parameter); return null; } private static class ElementTimestampStruct { private Date currentTimestamp = new Date(0); private SimpleDateFormat currentTimestampFormat; private String currentTimestampString; ElementTimestampStruct(String format) { currentTimestampFormat = new SimpleDateFormat(format); currentTimestampFormat.setTimeZone(TimeZone.getTimeZone("GMT")); } } }
/** * Generates automatic IIS isapi_redirect configurations based on the Tomcat server.xml settings and * the war contexts initialized during startup. * * <p>This config interceptor is enabled by inserting an IISConfig element in the * <b><ContextManager></b> tag body inside the server.xml file like so: * * <pre> * < ContextManager ... > * ... * <<b>IISConfig</b> <i>options</i> /> * ... * < /ContextManager > * </pre> * * where <i>options</i> can include any of the following attributes: * * <ul> * <li><b>configHome</b> - default parent directory for the following paths. If not set, this * defaults to TOMCAT_HOME. Ignored whenever any of the following paths is absolute. * <li><b>regConfig</b> - path to use for writing IIS isapi_redirect registry file. If not set, * defaults to "conf/auto/iis_redirect.reg". * <li><b>workersConfig</b> - path to workers.properties file used by isapi_redirect. If not set, * defaults to "conf/jk/workers.properties". * <li><b>uriConfig</b> - path to use for writing IIS isapi_redirect uriworkermap file. If not * set, defaults to "conf/auto/uriworkermap.properties". * <li><b>jkLog</b> - path to log file to be used by isapi_redirect. * <li><b>jkDebug</b> - Loglevel setting. May be debug, info, error, or emerg. If not set, * defaults to emerg. * <li><b>jkWorker</b> The desired worker. Must be set to one of the workers defined in the * workers.properties file. "ajp12", "ajp13" or "inprocess" are the workers found in the * default workers.properties file. If not specified, defaults to "ajp13" if an * Ajp13Interceptor is in use, otherwise it defaults to "ajp12". * <li><b>forwardAll</b> - If true, forward all requests to Tomcat. This helps insure that all the * behavior configured in the web.xml file functions correctly. If false, let IIS serve static * resources assuming it has been configured to do so. The default is true. Warning: When * false, some configuration in the web.xml may not be duplicated in IIS. Review the * uriworkermap file to see what configuration is actually being set in IIS. * <li><b>noRoot</b> - If true, the root context is not mapped to Tomcat. If false and forwardAll * is true, all requests to the root context are mapped to Tomcat. If false and forwardAll is * false, only JSP and servlets requests to the root context are mapped to Tomcat. When false, * to correctly serve Tomcat's root context you must also modify the Home Directory setting in * IIS to point to Tomcat's root context directory. Otherwise some content, such as the root * index.html, will be served by IIS before isapi_redirect gets a chance to claim the request * and pass it to Tomcat. The default is true. * </ul> * * <p> * * @author Costin Manolache * @author Larry Isaacs * @author Gal Shachor * @author Bill Barker */ public class IISConfig extends BaseJkConfig { private static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(IISConfig.class); public static final String WORKERS_CONFIG = "/conf/jk/workers.properties"; public static final String URI_WORKERS_MAP_CONFIG = "/conf/auto/uriworkermap.properties"; public static final String ISAPI_LOG_LOCATION = "/logs/iis_redirect.log"; public static final String ISAPI_REG_FILE = "/conf/auto/iis_redirect.reg"; private File regConfig = null; private File uriConfig = null; public IISConfig() {} // -------------------- Properties -------------------- /** * set the path to the output file for the auto-generated isapi_redirect registry file. If this * path is relative then getRegConfig() will resolve it absolutely against the getConfigHome() * path. * * <p> * * @param path String path to a file */ public void setRegConfig(String path) { regConfig = (path == null) ? null : new File(path); } /** * set a path to the uriworkermap.properties file. * * @param path String path to uriworkermap.properties file */ public void setUriConfig(String path) { uriConfig = (path == null ? null : new File(path)); } // -------------------- Initialize/guess defaults -------------------- /** Initialize defaults for properties that are not set explicitely */ protected void initProperties() { super.initProperties(); regConfig = getConfigFile(regConfig, configHome, ISAPI_REG_FILE); workersConfig = getConfigFile(workersConfig, configHome, WORKERS_CONFIG); uriConfig = getConfigFile(uriConfig, configHome, URI_WORKERS_MAP_CONFIG); jkLog = getConfigFile(jkLog, configHome, ISAPI_LOG_LOCATION); } // -------------------- Generate config -------------------- protected PrintWriter getWriter() throws IOException { String abUriConfig = uriConfig.getAbsolutePath(); return new PrintWriter(new FileWriter(abUriConfig, append)); } protected boolean generateJkHead(PrintWriter mod_jk) { try { PrintWriter regfile = new PrintWriter(new FileWriter(regConfig)); log.info("Generating IIS registry file = " + regConfig); generateRegistrySettings(regfile); regfile.close(); } catch (IOException iex) { log.warn("Unable to generate registry file " + regConfig); return false; } log.info("Generating IIS URI worker map file = " + uriConfig); generateUriWorkerHeader(mod_jk); return true; } // -------------------- Config sections -------------------- /** Writes the registry settings required by the IIS connector */ private void generateRegistrySettings(PrintWriter regfile) { regfile.println("REGEDIT4"); regfile.println(); regfile.println( "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Apache Software Foundation\\Jakarta Isapi Redirector\\1.0]"); regfile.println("\"extension_uri\"=\"/jakarta/isapi_redirect.dll\""); regfile.println("\"log_file\"=\"" + dubleSlash(jkLog.toString()) + "\""); regfile.println("\"log_level\"=\"" + jkDebug + "\""); regfile.println("\"worker_file\"=\"" + dubleSlash(workersConfig.toString()) + "\""); regfile.println("\"worker_mount_file\"=\"" + dubleSlash(uriConfig.toString()) + "\""); } /** Writes the header information to the uriworkermap file */ private void generateUriWorkerHeader(PrintWriter uri_worker) { uri_worker.println("###################################################################"); uri_worker.println("# Auto generated configuration. Dated: " + new Date()); uri_worker.println("###################################################################"); uri_worker.println(); uri_worker.println("#"); uri_worker.println("# Default worker to be used through our mappings"); uri_worker.println("#"); uri_worker.println("default.worker=" + jkWorker); uri_worker.println(); } /** Forward all requests for a context to tomcat. The default. */ protected void generateStupidMappings(Context context, PrintWriter uri_worker) { String ctxPath = context.getPath(); String nPath = ("".equals(ctxPath)) ? "/" : ctxPath; if (noRoot && "".equals(ctxPath)) { log.debug("Ignoring root context in forward-all mode "); return; } // map all requests for this context to Tomcat uri_worker.println(nPath + "=$(default.worker)"); if ("".equals(ctxPath)) { uri_worker.println(nPath + "*=$(default.worker)"); uri_worker.println( "# Note: To correctly serve the Tomcat's root context, IIS's Home Directory must"); uri_worker.println("# must be set to: \"" + getAbsoluteDocBase(context) + "\""); } else uri_worker.println(nPath + "/*=$(default.worker)"); } protected void generateContextMappings(Context context, PrintWriter uri_worker) { String ctxPath = context.getPath(); String nPath = ("".equals(ctxPath)) ? "/" : ctxPath; if (noRoot && "".equals(ctxPath)) { log.debug("Ignoring root context in forward-all mode "); return; } // Static files will be served by IIS uri_worker.println(); uri_worker.println("#########################################################"); uri_worker.println("# Auto configuration for the " + nPath + " context."); uri_worker.println("#########################################################"); uri_worker.println(); // Static mappings are not set in uriworkermap, but must be set with IIS admin. // InvokerInterceptor - it doesn't have a container, // but it's implemented using a special module. // XXX we need to better collect all mappings if (context.getLoginConfig() != null) { String loginPage = context.getLoginConfig().getLoginPage(); if (loginPage != null) { int lpos = loginPage.lastIndexOf("/"); String jscurl = loginPage.substring(0, lpos + 1) + "j_security_check"; addMapping(ctxPath, jscurl, uri_worker); } } String[] servletMaps = context.findServletMappings(); for (int ii = 0; ii < servletMaps.length; ii++) { addMapping(ctxPath, servletMaps[ii], uri_worker); } } /** Add an IIS extension mapping. */ protected boolean addMapping(String ctxPath, String ext, PrintWriter uri_worker) { if (log.isDebugEnabled()) log.debug("Adding extension map for " + ctxPath + "/*." + ext); if (!ext.startsWith("/")) ext = "/" + ext; if (ext.length() > 1) uri_worker.println(ctxPath + "/*." + ext + "=$(default.worker)"); return true; } /** Add a fulling specified IIS mapping. */ protected boolean addMapping(String fullPath, PrintWriter uri_worker) { if (log.isDebugEnabled()) log.debug("Adding map for " + fullPath); uri_worker.println(fullPath + "=$(default.worker)"); return true; } // -------------------- Utils -------------------- private String dubleSlash(String in) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < in.length(); i++) { char ch = in.charAt(i); if ('\\' == ch) { sb.append("\\\\"); } else { sb.append(ch); } } return sb.toString(); } }
/** @deprecated Unused: Will be removed in Tomcat 8.0.x */ @Deprecated public class MbeansDescriptorsDOMSource extends ModelerSource { private static final Log log = LogFactory.getLog(MbeansDescriptorsDOMSource.class); Registry registry; String location; String type; Object source; List<ObjectName> mbeans = new ArrayList<ObjectName>(); public void setRegistry(Registry reg) { this.registry = reg; } public void setLocation(String loc) { this.location = loc; } /** * Used if a single component is loaded * * @param type */ public void setType(String type) { this.type = type; } public void setSource(Object source) { this.source = source; } @Override public List<ObjectName> loadDescriptors(Registry registry, String type, Object source) throws Exception { setRegistry(registry); setType(type); setSource(source); execute(); return mbeans; } public void execute() throws Exception { if (registry == null) registry = Registry.getRegistry(null, null); try { InputStream stream = (InputStream) source; long t1 = System.currentTimeMillis(); Document doc = DomUtil.readXml(stream); // Ignore for now the name of the root element Node descriptorsN = doc.getDocumentElement(); // Node descriptorsN=DomUtil.getChild(doc, "mbeans-descriptors"); if (descriptorsN == null) { log.error("No descriptors found"); return; } Node firstMbeanN = null; if ("mbean".equals(descriptorsN.getNodeName())) { firstMbeanN = descriptorsN; } else { firstMbeanN = DomUtil.getChild(descriptorsN, "mbean"); } if (firstMbeanN == null) { log.error(" No mbean tags "); return; } // Process each <mbean> element for (Node mbeanN = firstMbeanN; mbeanN != null; mbeanN = DomUtil.getNext(mbeanN)) { // Create a new managed bean info ManagedBean managed = new ManagedBean(); DomUtil.setAttributes(managed, mbeanN); Node firstN; // Process descriptor subnode /*Node mbeanDescriptorN = DomUtil.getChild(mbeanN, "descriptor"); if (mbeanDescriptorN != null) { Node firstFieldN = DomUtil.getChild(mbeanDescriptorN, "field"); for (Node fieldN = firstFieldN; fieldN != null; fieldN = DomUtil.getNext(fieldN)) { FieldInfo fi = new FieldInfo(); DomUtil.setAttributes(fi, fieldN); managed.addField(fi); } }*/ // process attribute nodes firstN = DomUtil.getChild(mbeanN, "attribute"); for (Node descN = firstN; descN != null; descN = DomUtil.getNext(descN)) { // Create new attribute info AttributeInfo ai = new AttributeInfo(); DomUtil.setAttributes(ai, descN); // Process descriptor subnode /*Node descriptorN = DomUtil.getChild(descN, "descriptor"); if (descriptorN != null) { Node firstFieldN = DomUtil.getChild(descriptorN, "field"); for (Node fieldN = firstFieldN; fieldN != null; fieldN = DomUtil.getNext(fieldN)) { FieldInfo fi = new FieldInfo(); DomUtil.setAttributes(fi, fieldN); ai.addField(fi); } } */ // Add this info to our managed bean info managed.addAttribute(ai); if (log.isTraceEnabled()) { log.trace("Create attribute " + ai); } } // process constructor nodes /* firstN=DomUtil.getChild( mbeanN, "constructor"); for (Node descN = firstN; descN != null; descN = DomUtil.getNext( descN )) { // Create new constructor info ConstructorInfo ci=new ConstructorInfo(); DomUtil.setAttributes(ci, descN); // Process descriptor subnode Node firstDescriptorN = DomUtil.getChild(descN, "descriptor"); if (firstDescriptorN != null) { Node firstFieldN = DomUtil.getChild(firstDescriptorN, "field"); for (Node fieldN = firstFieldN; fieldN != null; fieldN = DomUtil.getNext(fieldN)) { FieldInfo fi = new FieldInfo(); DomUtil.setAttributes(fi, fieldN); ci.addField(fi); } } // Process parameter subnodes Node firstParamN=DomUtil.getChild( descN, "parameter"); for (Node paramN = firstParamN; paramN != null; paramN = DomUtil.getNext(paramN)) { ParameterInfo pi=new ParameterInfo(); DomUtil.setAttributes(pi, paramN); ci.addParameter( pi ); } // Add this info to our managed bean info managed.addConstructor( ci ); if (log.isTraceEnabled()) { log.trace("Create constructor " + ci); } }*/ // process notification nodes firstN = DomUtil.getChild(mbeanN, "notification"); for (Node descN = firstN; descN != null; descN = DomUtil.getNext(descN)) { // Create new notification info NotificationInfo ni = new NotificationInfo(); DomUtil.setAttributes(ni, descN); // Process descriptor subnode /*Node firstDescriptorN = DomUtil.getChild(descN, "descriptor"); if (firstDescriptorN != null) { Node firstFieldN = DomUtil.getChild(firstDescriptorN, "field"); for (Node fieldN = firstFieldN; fieldN != null; fieldN = DomUtil.getNext(fieldN)) { FieldInfo fi = new FieldInfo(); DomUtil.setAttributes(fi, fieldN); ni.addField(fi); } }*/ // Process notification-type subnodes Node firstParamN = DomUtil.getChild(descN, "notification-type"); for (Node paramN = firstParamN; paramN != null; paramN = DomUtil.getNext(paramN)) { ni.addNotifType(DomUtil.getContent(paramN)); } // Add this info to our managed bean info managed.addNotification(ni); if (log.isTraceEnabled()) { log.trace("Created notification " + ni); } } // process operation nodes firstN = DomUtil.getChild(mbeanN, "operation"); for (Node descN = firstN; descN != null; descN = DomUtil.getNext(descN)) { // Create new operation info OperationInfo oi = new OperationInfo(); DomUtil.setAttributes(oi, descN); // Process descriptor subnode /*Node firstDescriptorN = DomUtil.getChild(descN, "descriptor"); if (firstDescriptorN != null) { Node firstFieldN = DomUtil.getChild(firstDescriptorN, "field"); for (Node fieldN = firstFieldN; fieldN != null; fieldN = DomUtil.getNext(fieldN)) { FieldInfo fi = new FieldInfo(); DomUtil.setAttributes(fi, fieldN); oi.addField(fi); } }*/ // Process parameter subnodes Node firstParamN = DomUtil.getChild(descN, "parameter"); for (Node paramN = firstParamN; paramN != null; paramN = DomUtil.getNext(paramN)) { ParameterInfo pi = new ParameterInfo(); DomUtil.setAttributes(pi, paramN); if (log.isTraceEnabled()) log.trace("Add param " + pi.getName()); oi.addParameter(pi); } // Add this info to our managed bean info managed.addOperation(oi); if (log.isTraceEnabled()) { log.trace("Create operation " + oi); } } // Add the completed managed bean info to the registry registry.addManagedBean(managed); } long t2 = System.currentTimeMillis(); log.debug("Reading descriptors ( dom ) " + (t2 - t1)); } catch (Exception ex) { log.error("Error reading descriptors ", ex); } } }
/** * Startup/Shutdown shell program for Catalina. The following command line options are recognized: * * <ul> * <li><b>-config {pathname}</b> - Set the pathname of the configuration file to be processed. If * a relative path is specified, it will be interpreted as relative to the directory pathname * specified by the "catalina.base" system property. [conf/server.xml] * <li><b>-help</b> - Display usage information. * <li><b>-nonaming</b> - Disable naming support. * <li><b>configtest</b> - Try to test the config * <li><b>start</b> - Start an instance of Catalina. * <li><b>stop</b> - Stop the currently running instance of Catalina. </u> * <p>Should do the same thing as Embedded, but using a server.xml file. * * @author Craig R. McClanahan * @author Remy Maucherat */ public class Catalina { /** The string manager for this package. */ protected static final StringManager sm = StringManager.getManager(Constants.Package); // ----------------------------------------------------- Instance Variables /** Use await. */ protected boolean await = false; /** Pathname to the server configuration file. */ protected String configFile = "conf/server.xml"; // XXX Should be moved to embedded /** The shared extensions class loader for this server. */ protected ClassLoader parentClassLoader = Catalina.class.getClassLoader(); /** The server component we are starting or stopping. */ protected Server server = null; /** * Are we starting a new server? * * @deprecated Unused - will be removed in Tomcat 8.0.x */ @Deprecated protected boolean starting = false; /** * Are we stopping an existing server? * * @deprecated Unused - will be removed in Tomcat 8.0.x */ @Deprecated protected boolean stopping = false; /** Use shutdown hook flag. */ protected boolean useShutdownHook = true; /** Shutdown hook. */ protected Thread shutdownHook = null; /** Is naming enabled ? */ protected boolean useNaming = true; // ----------------------------------------------------------- Constructors public Catalina() { setSecurityProtection(); } // ------------------------------------------------------------- Properties /** @deprecated Use {@link #setConfigFile(String)} */ @Deprecated public void setConfig(String file) { configFile = file; } public void setConfigFile(String file) { configFile = file; } public String getConfigFile() { return configFile; } public void setUseShutdownHook(boolean useShutdownHook) { this.useShutdownHook = useShutdownHook; } public boolean getUseShutdownHook() { return useShutdownHook; } /** * Set the shared extensions class loader. * * @param parentClassLoader The shared extensions class loader. */ public void setParentClassLoader(ClassLoader parentClassLoader) { this.parentClassLoader = parentClassLoader; } public ClassLoader getParentClassLoader() { if (parentClassLoader != null) { return (parentClassLoader); } return ClassLoader.getSystemClassLoader(); } public void setServer(Server server) { this.server = server; } public Server getServer() { return server; } /** Return true if naming is enabled. */ public boolean isUseNaming() { return (this.useNaming); } /** * Enables or disables naming support. * * @param useNaming The new use naming value */ public void setUseNaming(boolean useNaming) { this.useNaming = useNaming; } public void setAwait(boolean b) { await = b; } public boolean isAwait() { return await; } // ------------------------------------------------------ Protected Methods /** * Process the specified command line arguments, and return <code>true</code> if we should * continue processing; otherwise return <code>false</code>. * * @param args Command line arguments to process */ protected boolean arguments(String args[]) { boolean isConfig = false; if (args.length < 1) { usage(); return (false); } for (int i = 0; i < args.length; i++) { if (isConfig) { configFile = args[i]; isConfig = false; } else if (args[i].equals("-config")) { isConfig = true; } else if (args[i].equals("-nonaming")) { setUseNaming(false); } else if (args[i].equals("-help")) { usage(); return (false); } else if (args[i].equals("start")) { starting = true; stopping = false; } else if (args[i].equals("configtest")) { starting = true; stopping = false; } else if (args[i].equals("stop")) { starting = false; stopping = true; } else { usage(); return (false); } } return (true); } /** Return a File object representing our configuration file. */ protected File configFile() { File file = new File(configFile); if (!file.isAbsolute()) { file = new File(System.getProperty(Globals.CATALINA_BASE_PROP), configFile); } return (file); } /** Create and configure the Digester we will be using for startup. */ protected Digester createStartDigester() { long t1 = System.currentTimeMillis(); // Initialize the digester Digester digester = new Digester(); digester.setValidating(false); digester.setRulesValidation(true); HashMap<Class<?>, List<String>> fakeAttributes = new HashMap<Class<?>, List<String>>(); ArrayList<String> attrs = new ArrayList<String>(); attrs.add("className"); fakeAttributes.put(Object.class, attrs); digester.setFakeAttributes(fakeAttributes); digester.setUseContextClassLoader(true); // Configure the actions we will be using digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className"); digester.addSetProperties("Server"); digester.addSetNext("Server", "setServer", "org.apache.catalina.Server"); digester.addObjectCreate( "Server/GlobalNamingResources", "org.apache.catalina.deploy.NamingResources"); digester.addSetProperties("Server/GlobalNamingResources"); digester.addSetNext( "Server/GlobalNamingResources", "setGlobalNamingResources", "org.apache.catalina.deploy.NamingResources"); digester.addObjectCreate( "Server/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Listener"); digester.addSetNext( "Server/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); digester.addObjectCreate( "Server/Service", "org.apache.catalina.core.StandardService", "className"); digester.addSetProperties("Server/Service"); digester.addSetNext("Server/Service", "addService", "org.apache.catalina.Service"); digester.addObjectCreate( "Server/Service/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Service/Listener"); digester.addSetNext( "Server/Service/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); // Executor digester.addObjectCreate( "Server/Service/Executor", "org.apache.catalina.core.StandardThreadExecutor", "className"); digester.addSetProperties("Server/Service/Executor"); digester.addSetNext("Server/Service/Executor", "addExecutor", "org.apache.catalina.Executor"); digester.addRule("Server/Service/Connector", new ConnectorCreateRule()); digester.addRule( "Server/Service/Connector", new SetAllPropertiesRule(new String[] {"executor"})); digester.addSetNext( "Server/Service/Connector", "addConnector", "org.apache.catalina.connector.Connector"); digester.addObjectCreate( "Server/Service/Connector/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Service/Connector/Listener"); digester.addSetNext( "Server/Service/Connector/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); // Add RuleSets for nested elements digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/")); digester.addRuleSet(new EngineRuleSet("Server/Service/")); digester.addRuleSet(new HostRuleSet("Server/Service/Engine/")); digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/")); addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/"); digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/")); // When the 'engine' is found, set the parentClassLoader. digester.addRule("Server/Service/Engine", new SetParentClassLoaderRule(parentClassLoader)); addClusterRuleSet(digester, "Server/Service/Engine/Cluster/"); long t2 = System.currentTimeMillis(); if (log.isDebugEnabled()) { log.debug("Digester for server.xml created " + (t2 - t1)); } return (digester); } /** Cluster support is optional. The JARs may have been removed. */ private void addClusterRuleSet(Digester digester, String prefix) { Class<?> clazz = null; Constructor<?> constructor = null; try { clazz = Class.forName("org.apache.catalina.ha.ClusterRuleSet"); constructor = clazz.getConstructor(String.class); RuleSet ruleSet = (RuleSet) constructor.newInstance(prefix); digester.addRuleSet(ruleSet); } catch (Exception e) { if (log.isDebugEnabled()) { log.debug( sm.getString("catalina.noCluster", e.getClass().getName() + ": " + e.getMessage()), e); } else if (log.isInfoEnabled()) { log.info( sm.getString("catalina.noCluster", e.getClass().getName() + ": " + e.getMessage())); } } } /** Create and configure the Digester we will be using for shutdown. */ protected Digester createStopDigester() { // Initialize the digester Digester digester = new Digester(); digester.setUseContextClassLoader(true); // Configure the rules we need for shutting down digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className"); digester.addSetProperties("Server"); digester.addSetNext("Server", "setServer", "org.apache.catalina.Server"); return (digester); } public void stopServer() { stopServer(null); } public void stopServer(String[] arguments) { if (arguments != null) { arguments(arguments); } Server s = getServer(); if (s == null) { // Create and execute our Digester Digester digester = createStopDigester(); File file = configFile(); FileInputStream fis = null; try { InputSource is = new InputSource(file.toURI().toURL().toString()); fis = new FileInputStream(file); is.setByteStream(fis); digester.push(this); digester.parse(is); } catch (Exception e) { log.error("Catalina.stop: ", e); System.exit(1); } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { // Ignore } } } } else { // Server object already present. Must be running as a service try { s.stop(); } catch (LifecycleException e) { log.error("Catalina.stop: ", e); } return; } // Stop the existing server s = getServer(); if (s.getPort() > 0) { Socket socket = null; OutputStream stream = null; try { socket = new Socket(s.getAddress(), s.getPort()); stream = socket.getOutputStream(); String shutdown = s.getShutdown(); for (int i = 0; i < shutdown.length(); i++) { stream.write(shutdown.charAt(i)); } stream.flush(); } catch (ConnectException ce) { log.error( sm.getString( "catalina.stopServer.connectException", s.getAddress(), String.valueOf(s.getPort()))); log.error("Catalina.stop: ", ce); System.exit(1); } catch (IOException e) { log.error("Catalina.stop: ", e); System.exit(1); } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { // Ignore } } if (socket != null) { try { socket.close(); } catch (IOException e) { // Ignore } } } } else { log.error(sm.getString("catalina.stopServer")); System.exit(1); } } /** Start a new server instance. */ public void load() { long t1 = System.nanoTime(); initDirs(); // Before digester - it may be needed initNaming(); // Create and execute our Digester Digester digester = createStartDigester(); InputSource inputSource = null; InputStream inputStream = null; File file = null; try { file = configFile(); inputStream = new FileInputStream(file); inputSource = new InputSource(file.toURI().toURL().toString()); } catch (Exception e) { if (log.isDebugEnabled()) { log.debug(sm.getString("catalina.configFail", file), e); } } if (inputStream == null) { try { inputStream = getClass().getClassLoader().getResourceAsStream(getConfigFile()); inputSource = new InputSource(getClass().getClassLoader().getResource(getConfigFile()).toString()); } catch (Exception e) { if (log.isDebugEnabled()) { log.debug(sm.getString("catalina.configFail", getConfigFile()), e); } } } // This should be included in catalina.jar // Alternative: don't bother with xml, just create it manually. if (inputStream == null) { try { inputStream = getClass().getClassLoader().getResourceAsStream("server-embed.xml"); inputSource = new InputSource(getClass().getClassLoader().getResource("server-embed.xml").toString()); } catch (Exception e) { if (log.isDebugEnabled()) { log.debug(sm.getString("catalina.configFail", "server-embed.xml"), e); } } } if (inputStream == null || inputSource == null) { if (file == null) { log.warn(sm.getString("catalina.configFail", getConfigFile() + "] or [server-embed.xml]")); } else { log.warn(sm.getString("catalina.configFail", file.getAbsolutePath())); if (file.exists() && !file.canRead()) { log.warn("Permissions incorrect, read permission is not allowed on the file."); } } return; } try { inputSource.setByteStream(inputStream); digester.push(this); digester.parse(inputSource); } catch (SAXParseException spe) { log.warn("Catalina.start using " + getConfigFile() + ": " + spe.getMessage()); return; } catch (Exception e) { log.warn("Catalina.start using " + getConfigFile() + ": ", e); return; } finally { try { inputStream.close(); } catch (IOException e) { // Ignore } } getServer().setCatalina(this); // Stream redirection initStreams(); // Start the new server try { getServer().init(); } catch (LifecycleException e) { if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) { throw new java.lang.Error(e); } else { log.error("Catalina.start", e); } } long t2 = System.nanoTime(); if (log.isInfoEnabled()) { log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms"); } } /* * Load using arguments */ public void load(String args[]) { try { if (arguments(args)) { load(); } } catch (Exception e) { e.printStackTrace(System.out); } } /** Start a new server instance. */ public void start() { if (getServer() == null) { load(); } if (getServer() == null) { log.fatal("Cannot start server. Server instance is not configured."); return; } long t1 = System.nanoTime(); // Start the new server try { getServer().start(); } catch (LifecycleException e) { log.fatal(sm.getString("catalina.serverStartFail"), e); try { getServer().destroy(); } catch (LifecycleException e1) { log.debug("destroy() failed for failed Server ", e1); } return; } long t2 = System.nanoTime(); if (log.isInfoEnabled()) { log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms"); } // Register shutdown hook if (useShutdownHook) { if (shutdownHook == null) { shutdownHook = new CatalinaShutdownHook(); } Runtime.getRuntime().addShutdownHook(shutdownHook); // If JULI is being used, disable JULI's shutdown hook since // shutdown hooks run in parallel and log messages may be lost // if JULI's hook completes before the CatalinaShutdownHook() LogManager logManager = LogManager.getLogManager(); if (logManager instanceof ClassLoaderLogManager) { ((ClassLoaderLogManager) logManager).setUseShutdownHook(false); } } if (await) { await(); stop(); } } /** Stop an existing server instance. */ public void stop() { try { // Remove the ShutdownHook first so that server.stop() // doesn't get invoked twice if (useShutdownHook) { Runtime.getRuntime().removeShutdownHook(shutdownHook); // If JULI is being used, re-enable JULI's shutdown to ensure // log messages are not lost LogManager logManager = LogManager.getLogManager(); if (logManager instanceof ClassLoaderLogManager) { ((ClassLoaderLogManager) logManager).setUseShutdownHook(true); } } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); // This will fail on JDK 1.2. Ignoring, as Tomcat can run // fine without the shutdown hook. } // Shut down the server try { Server s = getServer(); LifecycleState state = s.getState(); if (LifecycleState.STOPPING_PREP.compareTo(state) <= 0 && LifecycleState.DESTROYED.compareTo(state) >= 0) { // Nothing to do. stop() was already called } else { s.stop(); s.destroy(); } } catch (LifecycleException e) { log.error("Catalina.stop", e); } } /** Await and shutdown. */ public void await() { getServer().await(); } /** Print usage information for this application. */ protected void usage() { System.out.println( "usage: java org.apache.catalina.startup.Catalina" + " [ -config {pathname} ]" + " [ -nonaming ] " + " { -help | start | stop }"); } protected void initDirs() { String catalinaHome = System.getProperty(Globals.CATALINA_HOME_PROP); if (catalinaHome == null) { // Backwards compatibility patch for J2EE RI 1.3 String j2eeHome = System.getProperty("com.sun.enterprise.home"); if (j2eeHome != null) { catalinaHome = System.getProperty("com.sun.enterprise.home"); } else if (System.getProperty(Globals.CATALINA_BASE_PROP) != null) { catalinaHome = System.getProperty(Globals.CATALINA_BASE_PROP); } } // last resort - for minimal/embedded cases. if (catalinaHome == null) { catalinaHome = System.getProperty("user.dir"); } if (catalinaHome != null) { File home = new File(catalinaHome); if (!home.isAbsolute()) { try { catalinaHome = home.getCanonicalPath(); } catch (IOException e) { catalinaHome = home.getAbsolutePath(); } } System.setProperty(Globals.CATALINA_HOME_PROP, catalinaHome); } if (System.getProperty(Globals.CATALINA_BASE_PROP) == null) { System.setProperty(Globals.CATALINA_BASE_PROP, catalinaHome); } else { String catalinaBase = System.getProperty(Globals.CATALINA_BASE_PROP); File base = new File(catalinaBase); if (!base.isAbsolute()) { try { catalinaBase = base.getCanonicalPath(); } catch (IOException e) { catalinaBase = base.getAbsolutePath(); } } System.setProperty(Globals.CATALINA_BASE_PROP, catalinaBase); } String temp = System.getProperty("java.io.tmpdir"); if (temp == null || (!(new File(temp)).exists()) || (!(new File(temp)).isDirectory())) { log.error(sm.getString("embedded.notmp", temp)); } } protected void initStreams() { // Replace System.out and System.err with a custom PrintStream System.setOut(new SystemLogHandler(System.out)); System.setErr(new SystemLogHandler(System.err)); } protected void initNaming() { // Setting additional variables if (!useNaming) { log.info("Catalina naming disabled"); System.setProperty("catalina.useNaming", "false"); } else { System.setProperty("catalina.useNaming", "true"); String value = "org.apache.naming"; String oldValue = System.getProperty(javax.naming.Context.URL_PKG_PREFIXES); if (oldValue != null) { value = value + ":" + oldValue; } System.setProperty(javax.naming.Context.URL_PKG_PREFIXES, value); if (log.isDebugEnabled()) { log.debug("Setting naming prefix=" + value); } value = System.getProperty(javax.naming.Context.INITIAL_CONTEXT_FACTORY); if (value == null) { System.setProperty( javax.naming.Context.INITIAL_CONTEXT_FACTORY, "org.apache.naming.java.javaURLContextFactory"); } else { log.debug("INITIAL_CONTEXT_FACTORY already set " + value); } } } /** Set the security package access/protection. */ protected void setSecurityProtection() { SecurityConfig securityConfig = SecurityConfig.newInstance(); securityConfig.setPackageDefinition(); securityConfig.setPackageAccess(); } // --------------------------------------- CatalinaShutdownHook Inner Class // XXX Should be moved to embedded ! /** Shutdown hook which will perform a clean shutdown of Catalina if needed. */ protected class CatalinaShutdownHook extends Thread { @Override public void run() { try { if (getServer() != null) { Catalina.this.stop(); } } catch (Throwable ex) { ExceptionUtils.handleThrowable(ex); log.error(sm.getString("catalina.shutdownHookFail"), ex); } finally { // If JULI is used, shut JULI down *after* the server shuts down // so log messages aren't lost LogManager logManager = LogManager.getLogManager(); if (logManager instanceof ClassLoaderLogManager) { ((ClassLoaderLogManager) logManager).shutdown(); } } } } private static final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(Catalina.class); }
/** * Filesystem Directory Context implementation helper class. * * @author Remy Maucherat * @version $Id: FileDirContext.java 1239053 2012-02-01 10:52:00Z markt $ */ public class FileDirContext extends BaseDirContext { private static final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(FileDirContext.class); // -------------------------------------------------------------- Constants /** The descriptive information string for this implementation. */ protected static final int BUFFER_SIZE = 2048; // ----------------------------------------------------------- Constructors /** Builds a file directory context using the given environment. */ public FileDirContext() { super(); } /** Builds a file directory context using the given environment. */ public FileDirContext(Hashtable<String, Object> env) { super(env); } // ----------------------------------------------------- Instance Variables /** The document base directory. */ protected File base = null; /** Absolute normalized filename of the base. */ protected String absoluteBase = null; /** Allow linking. */ protected boolean allowLinking = false; // ------------------------------------------------------------- Properties /** * Set the document root. * * @param docBase The new document root * @exception IllegalArgumentException if the specified value is not supported by this * implementation * @exception IllegalArgumentException if this would create a malformed URL */ @Override public void setDocBase(String docBase) { // Validate the format of the proposed document root if (docBase == null) throw new IllegalArgumentException(sm.getString("resources.null")); // Calculate a File object referencing this document base directory base = new File(docBase); try { base = base.getCanonicalFile(); } catch (IOException e) { // Ignore } // Validate that the document base is an existing directory if (!base.exists() || !base.isDirectory() || !base.canRead()) throw new IllegalArgumentException(sm.getString("fileResources.base", docBase)); this.absoluteBase = base.getAbsolutePath(); super.setDocBase(docBase); } /** Set allow linking. */ public void setAllowLinking(boolean allowLinking) { this.allowLinking = allowLinking; } /** Is linking allowed. */ public boolean getAllowLinking() { return allowLinking; } // --------------------------------------------------------- Public Methods /** Release any resources allocated for this directory context. */ @Override public void release() { super.release(); } /** * Return the real path for a given virtual path, if possible; otherwise return <code>null</code>. * * @param path The path to the desired resource */ @Override protected String doGetRealPath(String path) { File file = new File(getDocBase(), path); return file.getAbsolutePath(); } // -------------------------------------------------------- Context Methods /** * Retrieves the named object. * * @param name the name of the object to look up * @return the object bound to name */ @Override protected Object doLookup(String name) { Object result = null; File file = file(name); if (file == null) return null; if (file.isDirectory()) { FileDirContext tempContext = new FileDirContext(env); tempContext.setDocBase(file.getPath()); tempContext.setAllowLinking(getAllowLinking()); result = tempContext; } else { result = new FileResource(file); } return result; } /** * Unbinds the named object. Removes the terminal atomic name in name from the target * context--that named by all but the terminal atomic part of name. * * <p>This method is idempotent. It succeeds even if the terminal atomic name is not bound in the * target context, but throws NameNotFoundException if any of the intermediate contexts do not * exist. * * @param name the name to bind; may not be empty * @exception NameNotFoundException if an intermediate context does not exist * @exception NamingException if a naming exception is encountered */ @Override public void unbind(String name) throws NamingException { File file = file(name); if (file == null) throw new NameNotFoundException(sm.getString("resources.notFound", name)); if (!file.delete()) throw new NamingException(sm.getString("resources.unbindFailed", name)); } /** * Binds a new name to the object bound to an old name, and unbinds the old name. Both names are * relative to this context. Any attributes associated with the old name become associated with * the new name. Intermediate contexts of the old name are not changed. * * @param oldName the name of the existing binding; may not be empty * @param newName the name of the new binding; may not be empty * @exception NameAlreadyBoundException if newName is already bound * @exception NamingException if a naming exception is encountered */ @Override public void rename(String oldName, String newName) throws NamingException { File file = file(oldName); if (file == null) throw new NameNotFoundException(sm.getString("resources.notFound", oldName)); File newFile = new File(base, newName); if (!file.renameTo(newFile)) { throw new NamingException(sm.getString("resources.renameFail", oldName, newName)); } } /** * Enumerates the names bound in the named context, along with the class names of objects bound to * them. The contents of any subcontexts are not included. * * <p>If a binding is added to or removed from this context, its effect on an enumeration * previously returned is undefined. * * @param name the name of the context to list * @return an enumeration of the names and class names of the bindings in this context. Each * element of the enumeration is of type NameClassPair. * @exception NamingException if a naming exception is encountered */ @Override public NamingEnumeration<NameClassPair> list(String name) throws NamingException { File file = file(name); if (file == null) throw new NameNotFoundException(sm.getString("resources.notFound", name)); return new NamingContextEnumeration(list(file).iterator()); } /** * Enumerates the names bound in the named context, along with the objects bound to them. The * contents of any subcontexts are not included. * * <p>If a binding is added to or removed from this context, its effect on an enumeration * previously returned is undefined. * * @param name the name of the context to list * @return an enumeration of the bindings in this context. Each element of the enumeration is of * type Binding. * @exception NamingException if a naming exception is encountered */ @Override protected List<NamingEntry> doListBindings(String name) throws NamingException { File file = file(name); if (file == null) return null; return list(file); } /** * Destroys the named context and removes it from the namespace. Any attributes associated with * the name are also removed. Intermediate contexts are not destroyed. * * <p>This method is idempotent. It succeeds even if the terminal atomic name is not bound in the * target context, but throws NameNotFoundException if any of the intermediate contexts do not * exist. * * <p>In a federated naming system, a context from one naming system may be bound to a name in * another. One can subsequently look up and perform operations on the foreign context using a * composite name. However, an attempt destroy the context using this composite name will fail * with NotContextException, because the foreign context is not a "subcontext" of the context in * which it is bound. Instead, use unbind() to remove the binding of the foreign context. * Destroying the foreign context requires that the destroySubcontext() be performed on a context * from the foreign context's "native" naming system. * * @param name the name of the context to be destroyed; may not be empty * @exception NameNotFoundException if an intermediate context does not exist * @exception javax.naming.NotContextException if the name is bound but does not name a context, * or does not name a context of the appropriate type */ @Override public void destroySubcontext(String name) throws NamingException { unbind(name); } /** * Retrieves the named object, following links except for the terminal atomic component of the * name. If the object bound to name is not a link, returns the object itself. * * @param name the name of the object to look up * @return the object bound to name, not following the terminal link (if any). * @exception NamingException if a naming exception is encountered */ @Override public Object lookupLink(String name) throws NamingException { // Note : Links are not supported return lookup(name); } /** * Retrieves the full name of this context within its own namespace. * * <p>Many naming services have a notion of a "full name" for objects in their respective * namespaces. For example, an LDAP entry has a distinguished name, and a DNS record has a fully * qualified name. This method allows the client application to retrieve this name. The string * returned by this method is not a JNDI composite name and should not be passed directly to * context methods. In naming systems for which the notion of full name does not make sense, * OperationNotSupportedException is thrown. * * @return this context's name in its own namespace; never null * @exception OperationNotSupportedException if the naming system does not have the notion of a * full name * @exception NamingException if a naming exception is encountered */ @Override public String getNameInNamespace() throws NamingException { return docBase; } // ----------------------------------------------------- DirContext Methods /** * Retrieves selected attributes associated with a named object. See the class description * regarding attribute models, attribute type names, and operational attributes. * * @return the requested attributes; never null * @param name the name of the object from which to retrieve attributes * @param attrIds the identifiers of the attributes to retrieve. null indicates that all * attributes should be retrieved; an empty array indicates that none should be retrieved * @exception NamingException if a naming exception is encountered */ @Override protected Attributes doGetAttributes(String name, String[] attrIds) throws NamingException { // Building attribute list File file = file(name); if (file == null) return null; return new FileResourceAttributes(file); } /** * Modifies the attributes associated with a named object. The order of the modifications is not * specified. Where possible, the modifications are performed atomically. * * @param name the name of the object whose attributes will be updated * @param mod_op the modification operation, one of: ADD_ATTRIBUTE, REPLACE_ATTRIBUTE, * REMOVE_ATTRIBUTE * @param attrs the attributes to be used for the modification; may not be null * @exception javax.naming.directory.AttributeModificationException if the modification cannot be * completed successfully * @exception NamingException if a naming exception is encountered */ @Override public void modifyAttributes(String name, int mod_op, Attributes attrs) throws NamingException { throw new OperationNotSupportedException(); } /** * Modifies the attributes associated with a named object using an an ordered list of * modifications. The modifications are performed in the order specified. Each modification * specifies a modification operation code and an attribute on which to operate. Where possible, * the modifications are performed atomically. * * @param name the name of the object whose attributes will be updated * @param mods an ordered sequence of modifications to be performed; may not be null * @exception javax.naming.directory.AttributeModificationException if the modification cannot be * completed successfully * @exception NamingException if a naming exception is encountered */ @Override public void modifyAttributes(String name, ModificationItem[] mods) throws NamingException { throw new OperationNotSupportedException(); } /** * Binds a name to an object, along with associated attributes. If attrs is null, the resulting * binding will have the attributes associated with obj if obj is a DirContext, and no attributes * otherwise. If attrs is non-null, the resulting binding will have attrs as its attributes; any * attributes associated with obj are ignored. * * @param name the name to bind; may not be empty * @param obj the object to bind; possibly null * @param attrs the attributes to associate with the binding * @exception NameAlreadyBoundException if name is already bound * @exception javax.naming.directory.InvalidAttributesException if some "mandatory" attributes of * the binding are not supplied * @exception NamingException if a naming exception is encountered */ @Override public void bind(String name, Object obj, Attributes attrs) throws NamingException { // Note: No custom attributes allowed File file = new File(base, name); if (file.exists()) throw new NameAlreadyBoundException(sm.getString("resources.alreadyBound", name)); rebind(name, obj, attrs); } /** * Binds a name to an object, along with associated attributes, overwriting any existing binding. * If attrs is null and obj is a DirContext, the attributes from obj are used. If attrs is null * and obj is not a DirContext, any existing attributes associated with the object already bound * in the directory remain unchanged. If attrs is non-null, any existing attributes associated * with the object already bound in the directory are removed and attrs is associated with the * named object. If obj is a DirContext and attrs is non-null, the attributes of obj are ignored. * * @param name the name to bind; may not be empty * @param obj the object to bind; possibly null * @param attrs the attributes to associate with the binding * @exception javax.naming.directory.InvalidAttributesException if some "mandatory" attributes of * the binding are not supplied * @exception NamingException if a naming exception is encountered */ @Override public void rebind(String name, Object obj, Attributes attrs) throws NamingException { // Note: No custom attributes allowed // Check obj type File file = new File(base, name); InputStream is = null; if (obj instanceof Resource) { try { is = ((Resource) obj).streamContent(); } catch (IOException e) { // Ignore } } else if (obj instanceof InputStream) { is = (InputStream) obj; } else if (obj instanceof DirContext) { if (file.exists()) { if (!file.delete()) throw new NamingException(sm.getString("resources.bindFailed", name)); } if (!file.mkdir()) throw new NamingException(sm.getString("resources.bindFailed", name)); } if (is == null) throw new NamingException(sm.getString("resources.bindFailed", name)); // Open os try { FileOutputStream os = null; byte buffer[] = new byte[BUFFER_SIZE]; int len = -1; try { os = new FileOutputStream(file); while (true) { len = is.read(buffer); if (len == -1) break; os.write(buffer, 0, len); } } finally { if (os != null) os.close(); is.close(); } } catch (IOException e) { NamingException ne = new NamingException(sm.getString("resources.bindFailed", e)); ne.initCause(e); throw ne; } } /** * Creates and binds a new context, along with associated attributes. This method creates a new * subcontext with the given name, binds it in the target context (that named by all but terminal * atomic component of the name), and associates the supplied attributes with the newly created * object. All intermediate and target contexts must already exist. If attrs is null, this method * is equivalent to Context.createSubcontext(). * * @param name the name of the context to create; may not be empty * @param attrs the attributes to associate with the newly created context * @return the newly created context * @exception NameAlreadyBoundException if the name is already bound * @exception javax.naming.directory.InvalidAttributesException if attrs does not contain all the * mandatory attributes required for creation * @exception NamingException if a naming exception is encountered */ @Override public DirContext createSubcontext(String name, Attributes attrs) throws NamingException { File file = new File(base, name); if (file.exists()) throw new NameAlreadyBoundException(sm.getString("resources.alreadyBound", name)); if (!file.mkdir()) throw new NamingException(sm.getString("resources.bindFailed", name)); return (DirContext) lookup(name); } /** * Retrieves the schema associated with the named object. The schema describes rules regarding the * structure of the namespace and the attributes stored within it. The schema specifies what types * of objects can be added to the directory and where they can be added; what mandatory and * optional attributes an object can have. The range of support for schemas is directory-specific. * * @param name the name of the object whose schema is to be retrieved * @return the schema associated with the context; never null * @exception OperationNotSupportedException if schema not supported * @exception NamingException if a naming exception is encountered */ @Override public DirContext getSchema(String name) throws NamingException { throw new OperationNotSupportedException(); } /** * Retrieves a context containing the schema objects of the named object's class definitions. * * @param name the name of the object whose object class definition is to be retrieved * @return the DirContext containing the named object's class definitions; never null * @exception OperationNotSupportedException if schema not supported * @exception NamingException if a naming exception is encountered */ @Override public DirContext getSchemaClassDefinition(String name) throws NamingException { throw new OperationNotSupportedException(); } /** * Searches in a single context for objects that contain a specified set of attributes, and * retrieves selected attributes. The search is performed using the default SearchControls * settings. * * @param name the name of the context to search * @param matchingAttributes the attributes to search for. If empty or null, all objects in the * target context are returned. * @param attributesToReturn the attributes to return. null indicates that all attributes are to * be returned; an empty array indicates that none are to be returned. * @return a non-null enumeration of SearchResult objects. Each SearchResult contains the * attributes identified by attributesToReturn and the name of the corresponding object, named * relative to the context named by name. * @exception NamingException if a naming exception is encountered */ @Override public NamingEnumeration<SearchResult> search( String name, Attributes matchingAttributes, String[] attributesToReturn) throws NamingException { return null; } /** * Searches in a single context for objects that contain a specified set of attributes. This * method returns all the attributes of such objects. It is equivalent to supplying null as the * atributesToReturn parameter to the method search(Name, Attributes, String[]). * * @param name the name of the context to search * @param matchingAttributes the attributes to search for. If empty or null, all objects in the * target context are returned. * @return a non-null enumeration of SearchResult objects. Each SearchResult contains the * attributes identified by attributesToReturn and the name of the corresponding object, named * relative to the context named by name. * @exception NamingException if a naming exception is encountered */ @Override public NamingEnumeration<SearchResult> search(String name, Attributes matchingAttributes) throws NamingException { return null; } /** * Searches in the named context or object for entries that satisfy the given search filter. * Performs the search as specified by the search controls. * * @param name the name of the context or object to search * @param filter the filter expression to use for the search; may not be null * @param cons the search controls that control the search. If null, the default search controls * are used (equivalent to (new SearchControls())). * @return an enumeration of SearchResults of the objects that satisfy the filter; never null * @exception javax.naming.directory.InvalidSearchFilterException if the search filter specified * is not supported or understood by the underlying directory * @exception javax.naming.directory.InvalidSearchControlsException if the search controls contain * invalid settings * @exception NamingException if a naming exception is encountered */ @Override public NamingEnumeration<SearchResult> search(String name, String filter, SearchControls cons) throws NamingException { return null; } /** * Searches in the named context or object for entries that satisfy the given search filter. * Performs the search as specified by the search controls. * * @param name the name of the context or object to search * @param filterExpr the filter expression to use for the search. The expression may contain * variables of the form "{i}" where i is a nonnegative integer. May not be null. * @param filterArgs the array of arguments to substitute for the variables in filterExpr. The * value of filterArgs[i] will replace each occurrence of "{i}". If null, equivalent to an * empty array. * @param cons the search controls that control the search. If null, the default search controls * are used (equivalent to (new SearchControls())). * @return an enumeration of SearchResults of the objects that satisfy the filter; never null * @exception ArrayIndexOutOfBoundsException if filterExpr contains {i} expressions where i is * outside the bounds of the array filterArgs * @exception javax.naming.directory.InvalidSearchControlsException if cons contains invalid * settings * @exception javax.naming.directory.InvalidSearchFilterException if filterExpr with filterArgs * represents an invalid search filter * @exception NamingException if a naming exception is encountered */ @Override public NamingEnumeration<SearchResult> search( String name, String filterExpr, Object[] filterArgs, SearchControls cons) throws NamingException { return null; } // ------------------------------------------------------ Protected Methods /** * Return a context-relative path, beginning with a "/", that represents the canonical version of * the specified path after ".." and "." elements are resolved out. If the specified path attempts * to go outside the boundaries of the current context (i.e. too many ".." path elements are * present), return <code>null</code> instead. * * @param path Path to be normalized */ protected String normalize(String path) { return RequestUtil.normalize(path, File.separatorChar == '\\'); } /** * Return a File object representing the specified normalized context-relative path if it exists * and is readable. Otherwise, return <code>null</code>. * * @param name Normalized context-relative path (with leading '/') */ protected File file(String name) { File file = new File(base, name); if (file.exists() && file.canRead()) { if (allowLinking) return file; // Check that this file belongs to our root path String canPath = null; try { canPath = file.getCanonicalPath(); } catch (IOException e) { // Ignore } if (canPath == null) return null; // Check to see if going outside of the web application root if (!canPath.startsWith(absoluteBase)) { return null; } // Case sensitivity check - this is now always done String fileAbsPath = file.getAbsolutePath(); if (fileAbsPath.endsWith(".")) fileAbsPath = fileAbsPath + "/"; String absPath = normalize(fileAbsPath); canPath = normalize(canPath); if ((absoluteBase.length() < absPath.length()) && (absoluteBase.length() < canPath.length())) { absPath = absPath.substring(absoluteBase.length() + 1); if (absPath == null) return null; if (absPath.equals("")) absPath = "/"; canPath = canPath.substring(absoluteBase.length() + 1); if (canPath.equals("")) canPath = "/"; if (!canPath.equals(absPath)) return null; } } else { return null; } return file; } /** * List the resources which are members of a collection. * * @param file Collection * @return Vector containing NamingEntry objects */ protected List<NamingEntry> list(File file) { List<NamingEntry> entries = new ArrayList<NamingEntry>(); if (!file.isDirectory()) return entries; String[] names = file.list(); if (names == null) { /* Some IO error occurred such as bad file permissions. Prevent a NPE with Arrays.sort(names) */ log.warn(sm.getString("fileResources.listingNull", file.getAbsolutePath())); return entries; } Arrays.sort(names); // Sort alphabetically NamingEntry entry = null; for (int i = 0; i < names.length; i++) { File currentFile = new File(file, names[i]); Object object = null; if (currentFile.isDirectory()) { FileDirContext tempContext = new FileDirContext(env); tempContext.setDocBase(file.getPath()); tempContext.setAllowLinking(getAllowLinking()); object = tempContext; } else { object = new FileResource(currentFile); } entry = new NamingEntry(names[i], object, NamingEntry.ENTRY); entries.add(entry); } return entries; } // ----------------------------------------------- FileResource Inner Class /** * This specialized resource implementation avoids opening the InputStream to the file right away * (which would put a lock on the file). */ protected static class FileResource extends Resource { // -------------------------------------------------------- Constructor public FileResource(File file) { this.file = file; } // --------------------------------------------------- Member Variables /** Associated file object. */ protected File file; // --------------------------------------------------- Resource Methods /** * Content accessor. * * @return InputStream */ @Override public InputStream streamContent() throws IOException { if (binaryContent == null) { FileInputStream fis = new FileInputStream(file); inputStream = fis; return fis; } return super.streamContent(); } } // ------------------------------------- FileResourceAttributes Inner Class /** * This specialized resource attribute implementation does some lazy reading (to speed up simple * checks, like checking the last modified date). */ protected static class FileResourceAttributes extends ResourceAttributes { private static final long serialVersionUID = 1L; // -------------------------------------------------------- Constructor public FileResourceAttributes(File file) { this.file = file; getCreation(); getLastModified(); } // --------------------------------------------------- Member Variables protected File file; protected boolean accessed = false; protected String canonicalPath = null; // ----------------------------------------- ResourceAttributes Methods /** Is collection. */ @Override public boolean isCollection() { if (!accessed) { collection = file.isDirectory(); accessed = true; } return super.isCollection(); } /** * Get content length. * * @return content length value */ @Override public long getContentLength() { if (contentLength != -1L) return contentLength; contentLength = file.length(); return contentLength; } /** * Get creation time. * * @return creation time value */ @Override public long getCreation() { if (creation != -1L) return creation; creation = getLastModified(); return creation; } /** * Get creation date. * * @return Creation date value */ @Override public Date getCreationDate() { if (creation == -1L) { creation = getCreation(); } return super.getCreationDate(); } /** * Get last modified time. * * @return lastModified time value */ @Override public long getLastModified() { if (lastModified != -1L) return lastModified; lastModified = file.lastModified(); return lastModified; } /** * Get lastModified date. * * @return LastModified date value */ @Override public Date getLastModifiedDate() { if (lastModified == -1L) { lastModified = getLastModified(); } return super.getLastModifiedDate(); } /** * Get name. * * @return Name value */ @Override public String getName() { if (name == null) name = file.getName(); return name; } /** * Get resource type. * * @return String resource type */ @Override public String getResourceType() { if (!accessed) { collection = file.isDirectory(); accessed = true; } return super.getResourceType(); } /** * Get canonical path. * * @return String the file's canonical path */ @Override public String getCanonicalPath() { if (canonicalPath == null) { try { canonicalPath = file.getCanonicalPath(); } catch (IOException e) { // Ignore } } return canonicalPath; } } }
/** * Processes AJP requests. * * @author Remy Maucherat * @author Henri Gomez * @author Dan Milstein * @author Keith Wannamaker * @author Kevin Seguin * @author Costin Manolache * @author Bill Barker */ public class AjpAprProcessor extends AbstractAjpProcessor<Long> { /** Logger. */ private static final Log log = LogFactory.getLog(AjpAprProcessor.class); @Override protected Log getLog() { return log; } // ----------------------------------------------------------- Constructors public AjpAprProcessor(int packetSize, AprEndpoint endpoint) { super(packetSize, endpoint); getResponse().setOutputBuffer(new AbstractAjpProcessorSocketOutputBuffer<Long>(this)); // Allocate input and output buffers inputBuffer = ByteBuffer.allocateDirect(packetSize * 2); inputBuffer.limit(0); outputBuffer = ByteBuffer.allocateDirect(packetSize * 2); } // ----------------------------------------------------- Instance Variables /** Direct buffer used for input. */ private ByteBuffer inputBuffer = null; /** Direct buffer used for output. */ private ByteBuffer outputBuffer = null; // --------------------------------------------------------- Public Methods /** * Process pipelined HTTP requests using the specified input and output streams. * * @throws IOException error during an I/O operation */ @Override public SocketState process(SocketWrapper<Long> socket) throws IOException { RequestInfo rp = getRequest().getRequestProcessor(); rp.setStage(Constants24.getStageParse()); // Setting up the socket this.setSocketWrapper(socket); long socketRef = socket.getSocket().longValue(); Socket.setrbb(socketRef, inputBuffer); Socket.setsbb(socketRef, outputBuffer); boolean cping = false; boolean keptAlive = false; while (!getErrorState().isError() && !getEndpoint().isPaused()) { // Parsing the request header try { // Get first message of the request if (!readMessage(getRequestHeaderMessage(), true, keptAlive)) { // This means that no data is available right now // (long keepalive), so that the processor should be recycled // and the method should return true break; } // Check message type, process right away and break if // not regular request processing int type = getRequestHeaderMessage().getByte(); if (type == Constants25.getJkAjp13CpingRequest()) { if (getEndpoint().isPaused()) { recycle(true); break; } cping = true; if (Socket.send(socketRef, getPongmessagearray(), 0, getPongmessagearray().length) < 0) { setErrorState(ErrorState.CLOSE_NOW, null); } continue; } else if (type != Constants25.getJkAjp13ForwardRequest()) { // Unexpected packet type. Unread body packets should have // been swallowed in finish(). if (log.isDebugEnabled()) { log.debug("Unexpected message: " + type); } setErrorState(ErrorState.CLOSE_NOW, null); break; } keptAlive = true; getRequest().setStartTime(System.currentTimeMillis()); } catch (IOException e) { setErrorState(ErrorState.CLOSE_NOW, e); break; } catch (Throwable t) { ExceptionUtils2.handleThrowable(t); log.debug(getSm().getString("ajpprocessor.header.error"), t); // 400 - Bad Request getResponse().setStatus(400); setErrorState(ErrorState.CLOSE_CLEAN, t); getAdapter().log(getRequest(), getResponse(), 0); } if (!getErrorState().isError()) { // Setting up filters, and parse some request headers rp.setStage(Constants24.getStagePrepare()); try { prepareRequest(); } catch (Throwable t) { ExceptionUtils2.handleThrowable(t); log.debug(getSm().getString("ajpprocessor.request.prepare"), t); // 500 - Internal Server Error getResponse().setStatus(500); setErrorState(ErrorState.CLOSE_CLEAN, t); getAdapter().log(getRequest(), getResponse(), 0); } } if (!getErrorState().isError() && !cping && getEndpoint().isPaused()) { // 503 - Service unavailable getResponse().setStatus(503); setErrorState(ErrorState.CLOSE_CLEAN, null); getAdapter().log(getRequest(), getResponse(), 0); } cping = false; // Process the request in the adapter if (!getErrorState().isError()) { try { rp.setStage(Constants24.getStageService()); getAdapter().service(getRequest(), getResponse()); } catch (InterruptedIOException e) { setErrorState(ErrorState.CLOSE_NOW, e); } catch (Throwable t) { ExceptionUtils2.handleThrowable(t); log.error(getSm().getString("ajpprocessor.request.process"), t); // 500 - Internal Server Error getResponse().setStatus(500); setErrorState(ErrorState.CLOSE_CLEAN, t); getAdapter().log(getRequest(), getResponse(), 0); } } if (isAsync() && !getErrorState().isError()) { break; } // Finish the response if not done yet if (!isFinished() && getErrorState().isIoAllowed()) { try { finish(); } catch (Throwable t) { ExceptionUtils2.handleThrowable(t); setErrorState(ErrorState.CLOSE_NOW, t); } } // If there was an error, make sure the request is counted as // and error, and update the statistics counter if (getErrorState().isError()) { getResponse().setStatus(500); } getRequest().updateCounters(); rp.setStage(Constants24.getStageKeepalive()); recycle(false); } rp.setStage(Constants24.getStageEnded()); if (!getErrorState().isError() && !getEndpoint().isPaused()) { if (isAsync()) { return SocketState.LONG; } else { return SocketState.OPEN; } } else { return SocketState.CLOSED; } } // ----------------------------------------------------- ActionHook Methods /** * Send an action to the connector. * * @param actionCode Type of the action * @param param Action parameter */ @Override @SuppressWarnings("incomplete-switch") // Other cases are handled by action() protected void actionInternal(ActionCode actionCode, Object param) { switch (actionCode) { case ASYNC_COMPLETE: { if (getAsyncStateMachine().asyncComplete()) { ((AprEndpoint) getEndpoint()) .processSocketAsync(this.getSocketWrapper(), SocketStatus.OPEN_READ); } break; } case ASYNC_SETTIMEOUT: { if (param == null) return; long timeout = ((Long) param).longValue(); getSocketWrapper().setTimeout(timeout); break; } case ASYNC_DISPATCH: { if (getAsyncStateMachine().asyncDispatch()) { ((AprEndpoint) getEndpoint()) .processSocketAsync(this.getSocketWrapper(), SocketStatus.OPEN_READ); } break; } } } @Override protected void resetTimeouts() { // NO-OP. The AJP APR/native connector only uses the timeout value on // time SocketWrapper for async timeouts. } @Override protected void output(byte[] src, int offset, int length) throws IOException { outputBuffer.put(src, offset, length); long socketRef = getSocketWrapper().getSocket().longValue(); if (outputBuffer.position() > 0) { if ((socketRef != 0) && Socket.sendbb(socketRef, 0, outputBuffer.position()) < 0) { // There are no re-tries so clear the buffer to prevent a // possible overflow if the buffer is used again. BZ53119. outputBuffer.clear(); throw new IOException(getSm().getString("ajpprocessor.failedsend")); } outputBuffer.clear(); } } /** Read at least the specified amount of bytes, and place them in the input buffer. */ protected boolean read(int n) throws IOException { if (inputBuffer.capacity() - inputBuffer.limit() <= n - inputBuffer.remaining()) { inputBuffer.compact(); inputBuffer.limit(inputBuffer.position()); inputBuffer.position(0); } int nRead; while (inputBuffer.remaining() < n) { nRead = Socket.recvbb( getSocketWrapper().getSocket().longValue(), inputBuffer.limit(), inputBuffer.capacity() - inputBuffer.limit()); if (nRead > 0) { inputBuffer.limit(inputBuffer.limit() + nRead); } else { throw new IOException(getSm().getString("ajpprocessor.failedread")); } } return true; } /** Read at least the specified amount of bytes, and place them in the input buffer. */ protected boolean readt(int n, boolean useAvailableData) throws IOException { if (useAvailableData && inputBuffer.remaining() == 0) { return false; } if (inputBuffer.capacity() - inputBuffer.limit() <= n - inputBuffer.remaining()) { inputBuffer.compact(); inputBuffer.limit(inputBuffer.position()); inputBuffer.position(0); } int nRead; while (inputBuffer.remaining() < n) { nRead = Socket.recvbb( getSocketWrapper().getSocket().longValue(), inputBuffer.limit(), inputBuffer.capacity() - inputBuffer.limit()); if (nRead > 0) { inputBuffer.limit(inputBuffer.limit() + nRead); } else { if ((-nRead) == Status.getEtimedout() || (-nRead) == Status.getTimeup()) { return false; } else { throw new IOException(getSm().getString("ajpprocessor.failedread")); } } } return true; } /** * Receive a chunk of data. Called to implement the 'special' packet in ajp13 and to receive the * data after we send a GET_BODY packet */ @Override public boolean receive() throws IOException { setFirst(false); getBodyMessage().reset(); if (!readMessage(getBodyMessage(), false, false)) { // Invalid message return false; } // No data received. if (getBodyMessage().getLen() == 0) { // just the header // Don't mark 'end of stream' for the first chunk. return false; } int blen = getBodyMessage().peekInt(); if (blen == 0) { return false; } getBodyMessage().getBodyBytes(getBodyBytes()); setEmpty(false); return true; } /** * Read an AJP message. * * @param first is true if the message is the first in the request, which will cause a short * duration blocking read * @return true if the message has been read, false if the short read didn't return anything * @throws IOException any other failure, including incomplete reads */ protected boolean readMessage(AjpMessage message, boolean first, boolean useAvailableData) throws IOException { int headerLength = message.getHeaderLength(); if (first) { if (!readt(headerLength, useAvailableData)) { return false; } } else { read(headerLength); } inputBuffer.get(message.getBuffer(), 0, headerLength); int messageLength = message.processHeader(true); if (messageLength < 0) { // Invalid AJP header signature // TODO: Throw some exception and close the connection to frontend. return false; } else if (messageLength == 0) { // Zero length message. return true; } else { if (messageLength > message.getBuffer().length) { // Message too long for the buffer // Need to trigger a 400 response throw new IllegalArgumentException( getSm() .getString( "ajpprocessor.header.tooLong", Integer.valueOf(messageLength), Integer.valueOf(message.getBuffer().length))); } read(messageLength); inputBuffer.get(message.getBuffer(), headerLength, messageLength); return true; } } /** Recycle the processor. */ @Override public void recycle(boolean socketClosing) { super.recycle(socketClosing); inputBuffer.clear(); inputBuffer.limit(0); outputBuffer.clear(); } }
/** * Provides a per class loader (i.e. per web application) instance of a ServerContainer. Web * application wide defaults may be configured by setting the following servlet context * initialisation parameters to the desired values. * * <ul> * <li>{@link Constants#BINARY_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM} * <li>{@link Constants#TEXT_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM} * </ul> */ public class WsServerContainer extends WsWebSocketContainer implements ServerContainer { private static final StringManager sm = StringManager.getManager(Constants.PACKAGE_NAME); private static final Log log = LogFactory.getLog(WsServerContainer.class); private static final CloseReason AUTHENTICATED_HTTP_SESSION_CLOSED = new CloseReason( CloseCodes.VIOLATED_POLICY, "This connection was established under an authenticated " + "HTTP session that has ended."); private final WsWriteTimeout wsWriteTimeout = new WsWriteTimeout(); private final ServletContext servletContext; private final Map<String, ServerEndpointConfig> configExactMatchMap = new ConcurrentHashMap<>(); private final ConcurrentHashMap<Integer, SortedSet<TemplatePathMatch>> configTemplateMatchMap = new ConcurrentHashMap<>(); private volatile boolean enforceNoAddAfterHandshake = org.apache.tomcat.websocket.Constants.STRICT_SPEC_COMPLIANCE; private volatile boolean addAllowed = true; private final ConcurrentHashMap<String, Set<WsSession>> authenticatedSessions = new ConcurrentHashMap<>(); private final ExecutorService executorService; private final ThreadGroup threadGroup; private volatile boolean endpointsRegistered = false; WsServerContainer(ServletContext servletContext) { this.servletContext = servletContext; // Configure servlet context wide defaults String value = servletContext.getInitParameter(Constants.BINARY_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM); if (value != null) { setDefaultMaxBinaryMessageBufferSize(Integer.parseInt(value)); } value = servletContext.getInitParameter(Constants.TEXT_BUFFER_SIZE_SERVLET_CONTEXT_INIT_PARAM); if (value != null) { setDefaultMaxTextMessageBufferSize(Integer.parseInt(value)); } value = servletContext.getInitParameter( Constants.ENFORCE_NO_ADD_AFTER_HANDSHAKE_CONTEXT_INIT_PARAM); if (value != null) { setEnforceNoAddAfterHandshake(Boolean.parseBoolean(value)); } // Executor config int executorCoreSize = 0; long executorKeepAliveTimeSeconds = 60; value = servletContext.getInitParameter(Constants.EXECUTOR_CORE_SIZE_INIT_PARAM); if (value != null) { executorCoreSize = Integer.parseInt(value); } value = servletContext.getInitParameter(Constants.EXECUTOR_KEEPALIVETIME_SECONDS_INIT_PARAM); if (value != null) { executorKeepAliveTimeSeconds = Long.parseLong(value); } FilterRegistration.Dynamic fr = servletContext.addFilter("Tomcat WebSocket (JSR356) Filter", new WsFilter()); fr.setAsyncSupported(true); EnumSet<DispatcherType> types = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD); fr.addMappingForUrlPatterns(types, true, "/*"); // Use a per web application executor for any threads that the WebSocket // server code needs to create. Group all of the threads under a single // ThreadGroup. StringBuffer threadGroupName = new StringBuffer("WebSocketServer-"); threadGroupName.append(servletContext.getVirtualServerName()); threadGroupName.append('-'); if ("".equals(servletContext.getContextPath())) { threadGroupName.append("ROOT"); } else { threadGroupName.append(servletContext.getContextPath()); } threadGroup = new ThreadGroup(threadGroupName.toString()); WsThreadFactory wsThreadFactory = new WsThreadFactory(threadGroup); executorService = new ThreadPoolExecutor( executorCoreSize, Integer.MAX_VALUE, executorKeepAliveTimeSeconds, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), wsThreadFactory); } /** * Published the provided endpoint implementation at the specified path with the specified * configuration. {@link #WsServerContainer(ServletContext)} must be called before calling this * method. * * @param sec The configuration to use when creating endpoint instances * @throws DeploymentException if the endpoint can not be published as requested */ @Override public void addEndpoint(ServerEndpointConfig sec) throws DeploymentException { if (enforceNoAddAfterHandshake && !addAllowed) { throw new DeploymentException(sm.getString("serverContainer.addNotAllowed")); } if (servletContext == null) { throw new DeploymentException(sm.getString("serverContainer.servletContextMissing")); } String path = sec.getPath(); UriTemplate uriTemplate = new UriTemplate(path); if (uriTemplate.hasParameters()) { Integer key = Integer.valueOf(uriTemplate.getSegmentCount()); SortedSet<TemplatePathMatch> templateMatches = configTemplateMatchMap.get(key); if (templateMatches == null) { // Ensure that if concurrent threads execute this block they // both end up using the same TreeSet instance templateMatches = new TreeSet<>(TemplatePathMatchComparator.getInstance()); configTemplateMatchMap.putIfAbsent(key, templateMatches); templateMatches = configTemplateMatchMap.get(key); } if (!templateMatches.add(new TemplatePathMatch(sec, uriTemplate))) { // Duplicate uriTemplate; throw new DeploymentException( sm.getString( "serverContainer.duplicatePaths", path, sec.getEndpointClass(), sec.getEndpointClass())); } } else { // Exact match ServerEndpointConfig old = configExactMatchMap.put(path, sec); if (old != null) { // Duplicate path mappings throw new DeploymentException( sm.getString( "serverContainer.duplicatePaths", path, old.getEndpointClass(), sec.getEndpointClass())); } } endpointsRegistered = true; } /** * Provides the equivalent of {@link #addEndpoint(ServerEndpointConfig)} for publishing plain old * java objects (POJOs) that have been annotated as WebSocket endpoints. * * @param pojo The annotated POJO */ @Override public void addEndpoint(Class<?> pojo) throws DeploymentException { ServerEndpoint annotation = pojo.getAnnotation(ServerEndpoint.class); if (annotation == null) { throw new DeploymentException( sm.getString("serverContainer.missingAnnotation", pojo.getName())); } String path = annotation.value(); // Validate encoders validateEncoders(annotation.encoders()); // Method mapping PojoMethodMapping methodMapping = new PojoMethodMapping(pojo, annotation.decoders(), path); // ServerEndpointConfig ServerEndpointConfig sec; Class<? extends Configurator> configuratorClazz = annotation.configurator(); Configurator configurator = null; if (!configuratorClazz.equals(Configurator.class)) { try { configurator = annotation.configurator().newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new DeploymentException( sm.getString( "serverContainer.configuratorFail", annotation.configurator().getName(), pojo.getClass().getName()), e); } } sec = ServerEndpointConfig.Builder.create(pojo, path) .decoders(Arrays.asList(annotation.decoders())) .encoders(Arrays.asList(annotation.encoders())) .subprotocols(Arrays.asList(annotation.subprotocols())) .configurator(configurator) .build(); sec.getUserProperties().put(PojoEndpointServer.POJO_METHOD_MAPPING_KEY, methodMapping); addEndpoint(sec); } @Override public void destroy() { shutdownExecutor(); super.destroy(); // If the executor hasn't fully shutdown it won't be possible to // destroy this thread group as there will still be threads running. // Mark the thread group as daemon one, so that it destroys itself // when thread count reaches zero. // Synchronization on threadGroup is needed, as there is a race between // destroy() call from termination of the last thread in thread group // marked as daemon versus the explicit destroy() call. int threadCount = threadGroup.activeCount(); boolean success = false; try { while (true) { int oldThreadCount = threadCount; synchronized (threadGroup) { if (threadCount > 0) { Thread.yield(); threadCount = threadGroup.activeCount(); } if (threadCount > 0 && threadCount != oldThreadCount) { // Value not stabilized. Retry. continue; } if (threadCount > 0) { threadGroup.setDaemon(true); } else { threadGroup.destroy(); success = true; } break; } } } catch (IllegalThreadStateException exception) { // Fall-through } if (!success) { log.warn( sm.getString( "serverContainer.threadGroupNotDestroyed", threadGroup.getName(), Integer.valueOf(threadCount))); } } boolean areEndpointsRegistered() { return endpointsRegistered; } public void doUpgrade( HttpServletRequest request, HttpServletResponse response, ServerEndpointConfig sec, Map<String, String> pathParams) throws ServletException, IOException { UpgradeUtil.doUpgrade(this, request, response, sec, pathParams); } public WsMappingResult findMapping(String path) { // Prevent registering additional endpoints once the first attempt has // been made to use one if (addAllowed) { addAllowed = false; } // Check an exact match. Simple case as there are no templates. ServerEndpointConfig sec = configExactMatchMap.get(path); if (sec != null) { return new WsMappingResult(sec, Collections.<String, String>emptyMap()); } // No exact match. Need to look for template matches. UriTemplate pathUriTemplate = null; try { pathUriTemplate = new UriTemplate(path); } catch (DeploymentException e) { // Path is not valid so can't be matched to a WebSocketEndpoint return null; } // Number of segments has to match Integer key = Integer.valueOf(pathUriTemplate.getSegmentCount()); SortedSet<TemplatePathMatch> templateMatches = configTemplateMatchMap.get(key); if (templateMatches == null) { // No templates with an equal number of segments so there will be // no matches return null; } // List is in alphabetical order of normalised templates. // Correct match is the first one that matches. Map<String, String> pathParams = null; for (TemplatePathMatch templateMatch : templateMatches) { pathParams = templateMatch.getUriTemplate().match(pathUriTemplate); if (pathParams != null) { sec = templateMatch.getConfig(); break; } } if (sec == null) { // No match return null; } return new WsMappingResult(sec, pathParams); } public boolean isEnforceNoAddAfterHandshake() { return enforceNoAddAfterHandshake; } public void setEnforceNoAddAfterHandshake(boolean enforceNoAddAfterHandshake) { this.enforceNoAddAfterHandshake = enforceNoAddAfterHandshake; } protected WsWriteTimeout getTimeout() { return wsWriteTimeout; } /** * {@inheritDoc} * * <p>Overridden to make it visible to other classes in this package. */ @Override protected void registerSession(Endpoint endpoint, WsSession wsSession) { super.registerSession(endpoint, wsSession); if (wsSession.isOpen() && wsSession.getUserPrincipal() != null && wsSession.getHttpSessionId() != null) { registerAuthenticatedSession(wsSession, wsSession.getHttpSessionId()); } } /** * {@inheritDoc} * * <p>Overridden to make it visible to other classes in this package. */ @Override protected void unregisterSession(Endpoint endpoint, WsSession wsSession) { if (wsSession.getUserPrincipal() != null && wsSession.getHttpSessionId() != null) { unregisterAuthenticatedSession(wsSession, wsSession.getHttpSessionId()); } super.unregisterSession(endpoint, wsSession); } private void registerAuthenticatedSession(WsSession wsSession, String httpSessionId) { Set<WsSession> wsSessions = authenticatedSessions.get(httpSessionId); if (wsSessions == null) { wsSessions = Collections.newSetFromMap(new ConcurrentHashMap<WsSession, Boolean>()); authenticatedSessions.putIfAbsent(httpSessionId, wsSessions); wsSessions = authenticatedSessions.get(httpSessionId); } wsSessions.add(wsSession); } private void unregisterAuthenticatedSession(WsSession wsSession, String httpSessionId) { Set<WsSession> wsSessions = authenticatedSessions.get(httpSessionId); // wsSessions will be null if the HTTP session has ended if (wsSessions != null) { wsSessions.remove(wsSession); } } public void closeAuthenticatedSession(String httpSessionId) { Set<WsSession> wsSessions = authenticatedSessions.remove(httpSessionId); if (wsSessions != null && !wsSessions.isEmpty()) { for (WsSession wsSession : wsSessions) { try { wsSession.close(AUTHENTICATED_HTTP_SESSION_CLOSED); } catch (IOException e) { // Any IOExceptions during close will have been caught and the // onError method called. } } } } ExecutorService getExecutorService() { return executorService; } private void shutdownExecutor() { if (executorService == null) { return; } executorService.shutdown(); try { executorService.awaitTermination(10, TimeUnit.SECONDS); } catch (InterruptedException e) { // Ignore the interruption and carry on } } private static void validateEncoders(Class<? extends Encoder>[] encoders) throws DeploymentException { for (Class<? extends Encoder> encoder : encoders) { // Need to instantiate decoder to ensure it is valid and that // deployment can be failed if it is not @SuppressWarnings("unused") Encoder instance; try { encoder.newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new DeploymentException( sm.getString("serverContainer.encoderFail", encoder.getName()), e); } } } private static class TemplatePathMatch { private final ServerEndpointConfig config; private final UriTemplate uriTemplate; public TemplatePathMatch(ServerEndpointConfig config, UriTemplate uriTemplate) { this.config = config; this.uriTemplate = uriTemplate; } public ServerEndpointConfig getConfig() { return config; } public UriTemplate getUriTemplate() { return uriTemplate; } } /** This Comparator implementation is thread-safe so only create a single instance. */ private static class TemplatePathMatchComparator implements Comparator<TemplatePathMatch> { private static final TemplatePathMatchComparator INSTANCE = new TemplatePathMatchComparator(); public static TemplatePathMatchComparator getInstance() { return INSTANCE; } private TemplatePathMatchComparator() { // Hide default constructor } @Override public int compare(TemplatePathMatch tpm1, TemplatePathMatch tpm2) { return tpm1.getUriTemplate() .getNormalizedPath() .compareTo(tpm2.getUriTemplate().getNormalizedPath()); } } private static class WsThreadFactory implements ThreadFactory { private final ThreadGroup tg; private final AtomicLong count = new AtomicLong(0); private WsThreadFactory(ThreadGroup tg) { this.tg = tg; } @Override public Thread newThread(Runnable r) { Thread t = new Thread(tg, r); t.setName(tg.getName() + "-" + count.incrementAndGet()); return t; } } }
/** * Startup/Shutdown shell program for Catalina. The following command line options are recognized: * * <ul> * <li><b>-config {pathname}</b> - Set the pathname of the configuration file to be processed. If * a relative path is specified, it will be interpreted as relative to the directory pathname * specified by the "catalina.base" system property. [conf/server.xml] * <li><b>-help</b> - Display usage information. * <li><b>-nonaming</b> - Disable naming support. * <li><b>start</b> - Start an instance of Catalina. * <li><b>stop</b> - Stop the currently running instance of Catalina. </u> * <p>Should do the same thing as Embedded, but using a server.xml file. * * @author Craig R. McClanahan * @author Remy Maucherat * @version $Id: Catalina.java 1066492 2011-02-02 15:02:49Z kkolinko $ */ public class Catalina extends Embedded { // ----------------------------------------------------- Instance Variables /** Pathname to the server configuration file. */ protected String configFile = "conf/server.xml"; // XXX Should be moved to embedded /** The shared extensions class loader for this server. */ protected ClassLoader parentClassLoader = Catalina.class.getClassLoader(); /** Are we starting a new server? */ protected boolean starting = false; /** Are we stopping an existing server? */ protected boolean stopping = false; /** Use shutdown hook flag. */ protected boolean useShutdownHook = true; /** Shutdown hook. */ protected Thread shutdownHook = null; // ------------------------------------------------------------- Properties public void setConfig(String file) { configFile = file; } public void setConfigFile(String file) { configFile = file; } public String getConfigFile() { return configFile; } public void setUseShutdownHook(boolean useShutdownHook) { this.useShutdownHook = useShutdownHook; } public boolean getUseShutdownHook() { return useShutdownHook; } /** * Set the shared extensions class loader. * * @param parentClassLoader The shared extensions class loader. */ public void setParentClassLoader(ClassLoader parentClassLoader) { this.parentClassLoader = parentClassLoader; } // ----------------------------------------------------------- Main Program /** * The application main program. * * @param args Command line arguments */ public static void main(String args[]) { (new Catalina()).process(args); } /** * The instance main program. * * @param args Command line arguments */ public void process(String args[]) { setAwait(true); setCatalinaHome(); setCatalinaBase(); try { if (arguments(args)) { if (starting) { load(args); start(); } else if (stopping) { stopServer(); } } } catch (Exception e) { e.printStackTrace(System.out); } } // ------------------------------------------------------ Protected Methods /** * Process the specified command line arguments, and return <code>true</code> if we should * continue processing; otherwise return <code>false</code>. * * @param args Command line arguments to process */ protected boolean arguments(String args[]) { boolean isConfig = false; if (args.length < 1) { usage(); return (false); } for (int i = 0; i < args.length; i++) { if (isConfig) { configFile = args[i]; isConfig = false; } else if (args[i].equals("-config")) { isConfig = true; } else if (args[i].equals("-nonaming")) { setUseNaming(false); } else if (args[i].equals("-help")) { usage(); return (false); } else if (args[i].equals("start")) { starting = true; stopping = false; } else if (args[i].equals("stop")) { starting = false; stopping = true; } else { usage(); return (false); } } return (true); } /** Return a File object representing our configuration file. */ protected File configFile() { File file = new File(configFile); if (!file.isAbsolute()) file = new File(System.getProperty("catalina.base"), configFile); return (file); } /** Create and configure the Digester we will be using for startup. */ protected Digester createStartDigester() { long t1 = System.currentTimeMillis(); // Initialize the digester Digester digester = new Digester(); digester.setValidating(false); digester.setRulesValidation(true); HashMap<Class, List<String>> fakeAttributes = new HashMap<Class, List<String>>(); ArrayList<String> attrs = new ArrayList<String>(); attrs.add("className"); fakeAttributes.put(Object.class, attrs); digester.setFakeAttributes(fakeAttributes); digester.setClassLoader(StandardServer.class.getClassLoader()); // Configure the actions we will be using digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className"); digester.addSetProperties("Server"); digester.addSetNext("Server", "setServer", "org.apache.catalina.Server"); digester.addObjectCreate( "Server/GlobalNamingResources", "org.apache.catalina.deploy.NamingResources"); digester.addSetProperties("Server/GlobalNamingResources"); digester.addSetNext( "Server/GlobalNamingResources", "setGlobalNamingResources", "org.apache.catalina.deploy.NamingResources"); digester.addObjectCreate( "Server/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Listener"); digester.addSetNext( "Server/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); digester.addObjectCreate( "Server/Service", "org.apache.catalina.core.StandardService", "className"); digester.addSetProperties("Server/Service"); digester.addSetNext("Server/Service", "addService", "org.apache.catalina.Service"); digester.addObjectCreate( "Server/Service/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Service/Listener"); digester.addSetNext( "Server/Service/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); // Executor digester.addObjectCreate( "Server/Service/Executor", "org.apache.catalina.core.StandardThreadExecutor", "className"); digester.addSetProperties("Server/Service/Executor"); digester.addSetNext("Server/Service/Executor", "addExecutor", "org.apache.catalina.Executor"); digester.addRule("Server/Service/Connector", new ConnectorCreateRule()); digester.addRule( "Server/Service/Connector", new SetAllPropertiesRule(new String[] {"executor"})); digester.addSetNext( "Server/Service/Connector", "addConnector", "org.apache.catalina.connector.Connector"); digester.addObjectCreate( "Server/Service/Connector/Listener", null, // MUST be specified in the element "className"); digester.addSetProperties("Server/Service/Connector/Listener"); digester.addSetNext( "Server/Service/Connector/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener"); // Add RuleSets for nested elements digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/")); digester.addRuleSet(new EngineRuleSet("Server/Service/")); digester.addRuleSet(new HostRuleSet("Server/Service/Engine/")); digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/")); digester.addRuleSet( ClusterRuleSetFactory.getClusterRuleSet("Server/Service/Engine/Host/Cluster/")); digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/")); // When the 'engine' is found, set the parentClassLoader. digester.addRule("Server/Service/Engine", new SetParentClassLoaderRule(parentClassLoader)); digester.addRuleSet(ClusterRuleSetFactory.getClusterRuleSet("Server/Service/Engine/Cluster/")); long t2 = System.currentTimeMillis(); if (log.isDebugEnabled()) log.debug("Digester for server.xml created " + (t2 - t1)); return (digester); } /** Create and configure the Digester we will be using for shutdown. */ protected Digester createStopDigester() { // Initialize the digester Digester digester = new Digester(); // Configure the rules we need for shutting down digester.addObjectCreate("Server", "org.apache.catalina.core.StandardServer", "className"); digester.addSetProperties("Server"); digester.addSetNext("Server", "setServer", "org.apache.catalina.Server"); return (digester); } public void stopServer() { stopServer(null); } public void stopServer(String[] arguments) { if (arguments != null) { arguments(arguments); } Server s = getServer(); if (s == null) { // Create and execute our Digester Digester digester = createStopDigester(); digester.setClassLoader(Thread.currentThread().getContextClassLoader()); File file = configFile(); try { InputSource is = new InputSource("file://" + file.getAbsolutePath()); FileInputStream fis = new FileInputStream(file); is.setByteStream(fis); digester.push(this); digester.parse(is); fis.close(); } catch (Exception e) { log.error("Catalina.stop: ", e); System.exit(1); } } else { // Server object already present. Must be running as a service if (s instanceof Lifecycle) { try { ((Lifecycle) s).stop(); } catch (LifecycleException e) { log.error("Catalina.stop: ", e); } return; } // else fall down } // Stop the existing server s = getServer(); try { if (s.getPort() > 0) { String hostAddress = InetAddress.getByName("localhost").getHostAddress(); Socket socket = new Socket(hostAddress, getServer().getPort()); OutputStream stream = socket.getOutputStream(); String shutdown = s.getShutdown(); for (int i = 0; i < shutdown.length(); i++) stream.write(shutdown.charAt(i)); stream.flush(); stream.close(); socket.close(); } else { log.error(sm.getString("catalina.stopServer")); System.exit(1); } } catch (IOException e) { log.error("Catalina.stop: ", e); System.exit(1); } } /** * Set the <code>catalina.base</code> System property to the current working directory if it has * not been set. * * @deprecated Use initDirs() */ public void setCatalinaBase() { initDirs(); } /** * Set the <code>catalina.home</code> System property to the current working directory if it has * not been set. * * @deprecated Use initDirs() */ public void setCatalinaHome() { initDirs(); } /** Start a new server instance. */ public void load() { long t1 = System.nanoTime(); initDirs(); // Before digester - it may be needed initNaming(); // Create and execute our Digester Digester digester = createStartDigester(); InputSource inputSource = null; InputStream inputStream = null; File file = null; try { file = configFile(); inputStream = new FileInputStream(file); inputSource = new InputSource("file://" + file.getAbsolutePath()); } catch (Exception e) {; } if (inputStream == null) { try { inputStream = getClass().getClassLoader().getResourceAsStream(getConfigFile()); inputSource = new InputSource(getClass().getClassLoader().getResource(getConfigFile()).toString()); } catch (Exception e) {; } } // This should be included in catalina.jar // Alternative: don't bother with xml, just create it manually. if (inputStream == null) { try { inputStream = getClass().getClassLoader().getResourceAsStream("server-embed.xml"); inputSource = new InputSource(getClass().getClassLoader().getResource("server-embed.xml").toString()); } catch (Exception e) {; } } if ((inputStream == null) && (file != null)) { log.warn("Can't load server.xml from " + file.getAbsolutePath()); if (file.exists() && !file.canRead()) { log.warn("Permissions incorrect, read permission is not allowed on the file."); } return; } try { inputSource.setByteStream(inputStream); digester.push(this); digester.parse(inputSource); inputStream.close(); } catch (Exception e) { log.warn("Catalina.start using " + getConfigFile() + ": ", e); return; } // Stream redirection initStreams(); // Start the new server if (getServer() instanceof Lifecycle) { try { getServer().initialize(); } catch (LifecycleException e) { if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) throw new java.lang.Error(e); else log.error("Catalina.start", e); } } long t2 = System.nanoTime(); if (log.isInfoEnabled()) log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms"); } /* * Load using arguments */ public void load(String args[]) { try { if (arguments(args)) load(); } catch (Exception e) { e.printStackTrace(System.out); } } public void create() {} public void destroy() {} /** Start a new server instance. */ public void start() { if (getServer() == null) { load(); } if (getServer() == null) { log.fatal("Cannot start server. Server instance is not configured."); return; } long t1 = System.nanoTime(); // Start the new server if (getServer() instanceof Lifecycle) { try { ((Lifecycle) getServer()).start(); } catch (LifecycleException e) { log.error("Catalina.start: ", e); } } long t2 = System.nanoTime(); if (log.isInfoEnabled()) log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms"); try { // Register shutdown hook if (useShutdownHook) { if (shutdownHook == null) { shutdownHook = new CatalinaShutdownHook(); } Runtime.getRuntime().addShutdownHook(shutdownHook); // If JULI is being used, disable JULI's shutdown hook since // shutdown hooks run in parallel and log messages may be lost // if JULI's hook completes before the CatalinaShutdownHook() LogManager logManager = LogManager.getLogManager(); if (logManager instanceof ClassLoaderLogManager) { ((ClassLoaderLogManager) logManager).setUseShutdownHook(false); } } } catch (Throwable t) { // This will fail on JDK 1.2. Ignoring, as Tomcat can run // fine without the shutdown hook. } if (await) { await(); stop(); } } /** Stop an existing server instance. */ public void stop() { try { // Remove the ShutdownHook first so that server.stop() // doesn't get invoked twice if (useShutdownHook) { Runtime.getRuntime().removeShutdownHook(shutdownHook); // If JULI is being used, re-enable JULI's shutdown to ensure // log messages are not lost LogManager logManager = LogManager.getLogManager(); if (logManager instanceof ClassLoaderLogManager) { ((ClassLoaderLogManager) logManager).setUseShutdownHook(true); } } } catch (Throwable t) { // This will fail on JDK 1.2. Ignoring, as Tomcat can run // fine without the shutdown hook. } // Shut down the server if (getServer() instanceof Lifecycle) { try { ((Lifecycle) getServer()).stop(); } catch (LifecycleException e) { log.error("Catalina.stop", e); } } } /** Await and shutdown. */ public void await() { getServer().await(); } /** Print usage information for this application. */ protected void usage() { System.out.println( "usage: java org.apache.catalina.startup.Catalina" + " [ -config {pathname} ]" + " [ -nonaming ] " + " { -help | start | stop }"); } // --------------------------------------- CatalinaShutdownHook Inner Class // XXX Should be moved to embedded ! /** Shutdown hook which will perform a clean shutdown of Catalina if needed. */ protected class CatalinaShutdownHook extends Thread { public void run() { try { if (getServer() != null) { Catalina.this.stop(); } } catch (Throwable ex) { log.error(sm.getString("catalina.shutdownHookFail"), ex); } finally { // If JULI is used, shut JULI down *after* the server shuts down // so log messages aren't lost LogManager logManager = LogManager.getLogManager(); if (logManager instanceof ClassLoaderLogManager) { ((ClassLoaderLogManager) logManager).shutdown(); } } } } private static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(Catalina.class); }
/** * A {@link PooledObjectFactory} that creates {@link PoolableConnection}s. * * @author Rodney Waldhoff * @author Glenn L. Nielsen * @author James House * @author Dirk Verbeeck * @since 2.0 */ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableConnection> { private static final Log log = LogFactory.getLog(PoolableConnectionFactory.class); /** * Create a new {@code PoolableConnectionFactory}. * * @param connFactory the {@link ConnectionFactory} from which to obtain base {@link Connection}s */ public PoolableConnectionFactory(ConnectionFactory connFactory, ObjectName dataSourceJmxName) { _connFactory = connFactory; this.dataSourceJmxName = dataSourceJmxName; } /** * Sets the query I use to {@link #validateObject validate} {@link Connection}s. Should return at * least one row. If not specified, {@link Connection#isValid(int)} will be used to validate * connections. * * @param validationQuery a query to use to {@link #validateObject validate} {@link Connection}s. */ public void setValidationQuery(String validationQuery) { _validationQuery = validationQuery; } /** * Sets the validation query timeout, the amount of time, in seconds, that connection validation * will wait for a response from the database when executing a validation query. Use a value less * than or equal to 0 for no timeout. * * @param timeout new validation query timeout value in seconds */ public void setValidationQueryTimeout(int timeout) { _validationQueryTimeout = timeout; } /** * Sets the SQL statements I use to initialize newly created {@link Connection}s. Using {@code * null} turns off connection initialization. * * @param connectionInitSqls SQL statement to initialize {@link Connection}s. */ public void setConnectionInitSql(Collection<String> connectionInitSqls) { _connectionInitSqls = connectionInitSqls; } /** * Sets the {@link ObjectPool} in which to pool {@link Connection}s. * * @param pool the {@link ObjectPool} in which to pool those {@link Connection}s */ public synchronized void setPool(ObjectPool<PoolableConnection> pool) { if (null != _pool && pool != _pool) { try { _pool.close(); } catch (Exception e) { // ignored !?! } } _pool = pool; } /** * Returns the {@link ObjectPool} in which {@link Connection}s are pooled. * * @return the connection pool */ public synchronized ObjectPool<PoolableConnection> getPool() { return _pool; } /** * Sets the default "read only" setting for borrowed {@link Connection}s * * @param defaultReadOnly the default "read only" setting for borrowed {@link Connection}s */ public void setDefaultReadOnly(Boolean defaultReadOnly) { _defaultReadOnly = defaultReadOnly; } /** * Sets the default "auto commit" setting for borrowed {@link Connection}s * * @param defaultAutoCommit the default "auto commit" setting for borrowed {@link Connection}s */ public void setDefaultAutoCommit(Boolean defaultAutoCommit) { _defaultAutoCommit = defaultAutoCommit; } /** * Sets the default "Transaction Isolation" setting for borrowed {@link Connection}s * * @param defaultTransactionIsolation the default "Transaction Isolation" setting for returned * {@link Connection}s */ public void setDefaultTransactionIsolation(int defaultTransactionIsolation) { _defaultTransactionIsolation = defaultTransactionIsolation; } /** * Sets the default "catalog" setting for borrowed {@link Connection}s * * @param defaultCatalog the default "catalog" setting for borrowed {@link Connection}s */ public void setDefaultCatalog(String defaultCatalog) { _defaultCatalog = defaultCatalog; } public void setCacheState(boolean cacheState) { this._cacheState = cacheState; } public void setPoolStatements(boolean poolStatements) { this.poolStatements = poolStatements; } public void setMaxOpenPrepatedStatements(int maxOpenPreparedStatements) { this.maxOpenPreparedStatements = maxOpenPreparedStatements; } /** * Sets the maximum lifetime in milliseconds of a connection after which the connection will * always fail activation, passivation and validation. A value of zero or less indicates an * infinite lifetime. The default value is -1. */ public void setMaxConnLifetimeMillis(long maxConnLifetimeMillis) { this.maxConnLifetimeMillis = maxConnLifetimeMillis; } public boolean isEnableAutoCommitOnReturn() { return enableAutoCommitOnReturn; } public void setEnableAutoCommitOnReturn(boolean enableAutoCommitOnReturn) { this.enableAutoCommitOnReturn = enableAutoCommitOnReturn; } public boolean isRollbackOnReturn() { return rollbackOnReturn; } public void setRollbackOnReturn(boolean rollbackOnReturn) { this.rollbackOnReturn = rollbackOnReturn; } public Integer getDefaultQueryTimeout() { return defaultQueryTimeout; } public void setDefaultQueryTimeout(Integer defaultQueryTimeout) { this.defaultQueryTimeout = defaultQueryTimeout; } /** * SQL_STATE codes considered to signal fatal conditions. * * <p>Overrides the defaults in {@link Utils#DISCONNECTION_SQL_CODES} (plus anything starting with * {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}). If this property is non-null and {@link * #isFastFailValidation()} is {@code true}, whenever connections created by this factory generate * exceptions with SQL_STATE codes in this list, they will be marked as "fatally disconnected" and * subsequent validations will fail fast (no attempt at isValid or validation query). * * <p>If {@link #isFastFailValidation()} is {@code false} setting this property has no effect. * * @return SQL_STATE codes overriding defaults * @since 2.1 */ public Collection<String> getDisconnectionSqlCodes() { return _disconnectionSqlCodes; } /** * @see #getDisconnectionSqlCodes() * @param disconnectionSqlCodes * @since 2.1 */ public void setDisconnectionSqlCodes(Collection<String> disconnectionSqlCodes) { _disconnectionSqlCodes = disconnectionSqlCodes; } /** * True means that validation will fail immediately for connections that have previously thrown * SQLExceptions with SQL_STATE indicating fatal disconnection errors. * * @return true if connections created by this factory will fast fail validation. * @see #setDisconnectionSqlCodes(Collection) * @since 2.1 */ public boolean isFastFailValidation() { return _fastFailValidation; } /** * @see #isFastFailValidation() * @param fastFailValidation true means connections created by this factory will fast fail * validation * @since 2.1 */ public void setFastFailValidation(boolean fastFailValidation) { _fastFailValidation = fastFailValidation; } @Override public PooledObject<PoolableConnection> makeObject() throws Exception { Connection conn = _connFactory.createConnection(); if (conn == null) { throw new IllegalStateException("Connection factory returned null from createConnection"); } try { initializeConnection(conn); } catch (SQLException sqle) { // Make sure the connection is closed try { conn.close(); } catch (SQLException ignore) { // ignore } // Rethrow original exception so it is visible to caller throw sqle; } long connIndex = connectionIndex.getAndIncrement(); if (poolStatements) { conn = new PoolingConnection(conn); GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig(); config.setMaxTotalPerKey(-1); config.setBlockWhenExhausted(false); config.setMaxWaitMillis(0); config.setMaxIdlePerKey(1); config.setMaxTotal(maxOpenPreparedStatements); if (dataSourceJmxName != null) { StringBuilder base = new StringBuilder(dataSourceJmxName.toString()); base.append(Constants.JMX_CONNECTION_BASE_EXT); base.append(Long.toString(connIndex)); config.setJmxNameBase(base.toString()); config.setJmxNamePrefix(Constants.JMX_STATEMENT_POOL_PREFIX); } else { config.setJmxEnabled(false); } KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> stmtPool = new GenericKeyedObjectPool<>((PoolingConnection) conn, config); ((PoolingConnection) conn).setStatementPool(stmtPool); ((PoolingConnection) conn).setCacheState(_cacheState); } // Register this connection with JMX ObjectName connJmxName; if (dataSourceJmxName == null) { connJmxName = null; } else { connJmxName = new ObjectName( dataSourceJmxName.toString() + Constants.JMX_CONNECTION_BASE_EXT + connIndex); } PoolableConnection pc = new PoolableConnection( conn, _pool, connJmxName, _disconnectionSqlCodes, _fastFailValidation); return new DefaultPooledObject<>(pc); } protected void initializeConnection(Connection conn) throws SQLException { Collection<String> sqls = _connectionInitSqls; if (conn.isClosed()) { throw new SQLException("initializeConnection: connection closed"); } if (null != sqls) { try (Statement stmt = conn.createStatement(); ) { for (String sql : sqls) { if (sql == null) { throw new NullPointerException("null connectionInitSqls element"); } stmt.execute(sql); } } } } @Override public void destroyObject(PooledObject<PoolableConnection> p) throws Exception { p.getObject().reallyClose(); } @Override public boolean validateObject(PooledObject<PoolableConnection> p) { try { validateLifetime(p); validateConnection(p.getObject()); return true; } catch (Exception e) { if (log.isDebugEnabled()) { log.debug(Utils.getMessage("poolableConnectionFactory.validateObject.fail"), e); } return false; } } public void validateConnection(PoolableConnection conn) throws SQLException { if (conn.isClosed()) { throw new SQLException("validateConnection: connection closed"); } conn.validate(_validationQuery, _validationQueryTimeout); } @Override public void passivateObject(PooledObject<PoolableConnection> p) throws Exception { validateLifetime(p); PoolableConnection conn = p.getObject(); Boolean connAutoCommit = null; if (rollbackOnReturn) { connAutoCommit = Boolean.valueOf(conn.getAutoCommit()); if (!connAutoCommit.booleanValue() && !conn.isReadOnly()) { conn.rollback(); } } conn.clearWarnings(); // DBCP-97 / DBCP-399 / DBCP-351 Idle connections in the pool should // have autoCommit enabled if (enableAutoCommitOnReturn) { if (connAutoCommit == null) { connAutoCommit = Boolean.valueOf(conn.getAutoCommit()); } if (!connAutoCommit.booleanValue()) { conn.setAutoCommit(true); } } conn.passivate(); } @Override public void activateObject(PooledObject<PoolableConnection> p) throws Exception { validateLifetime(p); PoolableConnection conn = p.getObject(); conn.activate(); if (_defaultAutoCommit != null && conn.getAutoCommit() != _defaultAutoCommit.booleanValue()) { conn.setAutoCommit(_defaultAutoCommit.booleanValue()); } if (_defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION && conn.getTransactionIsolation() != _defaultTransactionIsolation) { conn.setTransactionIsolation(_defaultTransactionIsolation); } if (_defaultReadOnly != null && conn.isReadOnly() != _defaultReadOnly.booleanValue()) { conn.setReadOnly(_defaultReadOnly.booleanValue()); } if (_defaultCatalog != null && !_defaultCatalog.equals(conn.getCatalog())) { conn.setCatalog(_defaultCatalog); } conn.setDefaultQueryTimeout(defaultQueryTimeout); } private void validateLifetime(PooledObject<PoolableConnection> p) throws Exception { if (maxConnLifetimeMillis > 0) { long lifetime = System.currentTimeMillis() - p.getCreateTime(); if (lifetime > maxConnLifetimeMillis) { throw new LifetimeExceededException( Utils.getMessage( "connectionFactory.lifetimeExceeded", Long.valueOf(lifetime), Long.valueOf(maxConnLifetimeMillis))); } } } protected ConnectionFactory getConnectionFactory() { return _connFactory; } protected boolean getPoolStatements() { return poolStatements; } protected int getMaxOpenPreparedStatements() { return maxOpenPreparedStatements; } protected boolean getCacheState() { return _cacheState; } protected ObjectName getDataSourceJmxName() { return dataSourceJmxName; } protected AtomicLong getConnectionIndex() { return connectionIndex; } private final ConnectionFactory _connFactory; private final ObjectName dataSourceJmxName; private volatile String _validationQuery = null; private volatile int _validationQueryTimeout = -1; private Collection<String> _connectionInitSqls = null; private Collection<String> _disconnectionSqlCodes = null; private boolean _fastFailValidation = false; private volatile ObjectPool<PoolableConnection> _pool = null; private Boolean _defaultReadOnly = null; private Boolean _defaultAutoCommit = null; private boolean enableAutoCommitOnReturn = true; private boolean rollbackOnReturn = true; private int _defaultTransactionIsolation = UNKNOWN_TRANSACTIONISOLATION; private String _defaultCatalog; private boolean _cacheState; private boolean poolStatements = false; private int maxOpenPreparedStatements = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL_PER_KEY; private long maxConnLifetimeMillis = -1; private final AtomicLong connectionIndex = new AtomicLong(0); private Integer defaultQueryTimeout = null; /** Internal constant to indicate the level is not set. */ static final int UNKNOWN_TRANSACTIONISOLATION = -1; }
/** * Represents the session locking hooks that must be implemented by the various locking strategies. * * @author <a href="mailto:[email protected]">Martin Grotzke</a> */ public abstract class LockingStrategy { public static enum LockingMode { /** Sessions are never locked. */ NONE, /** Sessions are locked for each request. */ ALL, /** * Readonly requests are tracked and for requests that modify the session the session is locked. */ AUTO, /** The application explicitely manages locks */ APP, /** The session is locked for configured request patterns * */ URI_PATTERN } protected static final String LOCK_VALUE = "locked"; protected static final int LOCK_RETRY_INTERVAL = 10; protected static final int LOCK_MAX_RETRY_INTERVAL = 500; protected final Log _log = LogFactory.getLog(getClass()); protected MemcachedSessionService _manager; protected final MemcachedClient _memcached; protected LRUCache<String, Boolean> _missingSessionsCache; protected final SessionIdFormat _sessionIdFormat; private final ExecutorService _executor; private final boolean _storeSecondaryBackup; protected final Statistics _stats; protected final CurrentRequest _currentRequest; protected final StorageKeyFormat _storageKeyFormat; protected LockingStrategy( @Nonnull final MemcachedSessionService manager, @Nonnull final MemcachedNodesManager memcachedNodesManager, @Nonnull final MemcachedClient memcached, @Nonnull final LRUCache<String, Boolean> missingSessionsCache, final boolean storeSecondaryBackup, @Nonnull final Statistics stats, @Nonnull final CurrentRequest currentRequest) { _manager = manager; _memcached = memcached; _missingSessionsCache = missingSessionsCache; _sessionIdFormat = memcachedNodesManager.getSessionIdFormat(); _storeSecondaryBackup = storeSecondaryBackup; _stats = stats; _currentRequest = currentRequest; _storageKeyFormat = memcachedNodesManager.getStorageKeyFormat(); _executor = Executors.newFixedThreadPool( Runtime.getRuntime().availableProcessors(), new NamedThreadFactory("msm-2ndary-backup")); } /** Creates the appropriate {@link LockingStrategy} for the given {@link LockingMode}. */ @CheckForNull public static LockingStrategy create( @Nullable final LockingMode lockingMode, @Nullable final Pattern uriPattern, @Nonnull final MemcachedClient memcached, @Nonnull final MemcachedSessionService manager, @Nonnull final MemcachedNodesManager memcachedNodesManager, @Nonnull final LRUCache<String, Boolean> missingSessionsCache, final boolean storeSecondaryBackup, @Nonnull final Statistics stats, @Nonnull final CurrentRequest currentRequest) { if (lockingMode == null) { return null; } switch (lockingMode) { case ALL: return new LockingStrategyAll( manager, memcachedNodesManager, memcached, missingSessionsCache, storeSecondaryBackup, stats, currentRequest); case AUTO: return new LockingStrategyAuto( manager, memcachedNodesManager, memcached, missingSessionsCache, storeSecondaryBackup, stats, currentRequest); case URI_PATTERN: return new LockingStrategyUriPattern( manager, memcachedNodesManager, uriPattern, memcached, missingSessionsCache, storeSecondaryBackup, stats, currentRequest); case NONE: return new LockingStrategyNone( manager, memcachedNodesManager, memcached, missingSessionsCache, storeSecondaryBackup, stats, currentRequest); default: throw new IllegalArgumentException("LockingMode not yet supported: " + lockingMode); } } /** Shutdown this lockingStrategy, which frees all resources / releases threads. */ public void shutdown() { _executor.shutdown(); } protected LockStatus lock(final String sessionId) { return lock(sessionId, _manager.getOperationTimeout(), TimeUnit.MILLISECONDS); } protected LockStatus lock(final String sessionId, final long timeout, final TimeUnit timeUnit) { if (_log.isDebugEnabled()) { _log.debug("Locking session " + sessionId); } final long start = System.currentTimeMillis(); try { acquireLock( sessionId, LOCK_RETRY_INTERVAL, LOCK_MAX_RETRY_INTERVAL, timeUnit.toMillis(timeout), System.currentTimeMillis()); _stats.registerSince(ACQUIRE_LOCK, start); if (_log.isDebugEnabled()) { _log.debug("Locked session " + sessionId); } return LockStatus.LOCKED; } catch (final TimeoutException e) { _log.warn( "Reached timeout when trying to aquire lock for session " + sessionId + ". Will use this session without this lock."); _stats.registerSince(ACQUIRE_LOCK_FAILURE, start); return LockStatus.COULD_NOT_AQUIRE_LOCK; } catch (final InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Got interrupted while trying to lock session.", e); } catch (final ExecutionException e) { _log.warn("An exception occurred when trying to aquire lock for session " + sessionId); _stats.registerSince(ACQUIRE_LOCK_FAILURE, start); return LockStatus.COULD_NOT_AQUIRE_LOCK; } } protected void acquireLock( @Nonnull final String sessionId, final long retryInterval, final long maxRetryInterval, final long timeout, final long start) throws InterruptedException, ExecutionException, TimeoutException { final Future<Boolean> result = _memcached.add(_sessionIdFormat.createLockName(sessionId), 5, LOCK_VALUE); if (result.get().booleanValue()) { if (_log.isDebugEnabled()) { _log.debug("Locked session " + sessionId); } return; } else { checkTimeoutAndWait(sessionId, retryInterval, timeout, start); acquireLock( sessionId, min(retryInterval * 2, maxRetryInterval), maxRetryInterval, timeout, start); } } protected void checkTimeoutAndWait( @Nonnull final String sessionId, final long timeToWait, final long timeout, final long start) throws TimeoutException, InterruptedException { if (System.currentTimeMillis() >= start + timeout) { throw new TimeoutException( "Reached timeout when trying to aquire lock for session " + sessionId); } if (_log.isDebugEnabled()) { _log.debug( "Could not aquire lock for session " + sessionId + ", waiting " + timeToWait + " millis now..."); } sleep(timeToWait); } protected void releaseLock(@Nonnull final String sessionId) { try { if (_log.isDebugEnabled()) { _log.debug("Releasing lock for session " + sessionId); } final long start = System.currentTimeMillis(); _memcached.delete(_sessionIdFormat.createLockName(sessionId)).get(); _stats.registerSince(RELEASE_LOCK, start); } catch (final Exception e) { _log.warn("Caught exception when trying to release lock for session " + sessionId, e); } } /** * Used to register the given requestId as readonly request (for mode auto), so that further * requests like this won't acquire the lock. * * @param requestId the uri/id of the request for that the session backup shall be performed, used * for readonly tracking. */ public void registerReadonlyRequest(final String requestId) { // default empty } /** * Is invoked for the backup of a non-sticky session that was not accessed for the current * request. */ protected void onBackupWithoutLoadedSession( @Nonnull final String sessionId, @Nonnull final String requestId, @Nonnull final BackupSessionService backupSessionService) { if (!_sessionIdFormat.isValid(sessionId)) { return; } try { final long start = System.currentTimeMillis(); final String validityKey = _sessionIdFormat.createValidityInfoKeyName(sessionId); final SessionValidityInfo validityInfo = loadSessionValidityInfoForValidityKey(validityKey); if (validityInfo == null) { _log.warn("Found no validity info for session id " + sessionId); return; } final int maxInactiveInterval = validityInfo.getMaxInactiveInterval(); final byte[] validityData = encode(maxInactiveInterval, System.currentTimeMillis(), System.currentTimeMillis()); // fix for #88, along with the change in session.getMemcachedExpirationTimeToSet final int expiration = maxInactiveInterval <= 0 ? 0 : maxInactiveInterval; final Future<Boolean> validityResult = _memcached.set(validityKey, toMemcachedExpiration(expiration), validityData); if (!_manager.isSessionBackupAsync()) { validityResult.get(_manager.getSessionBackupTimeout(), TimeUnit.MILLISECONDS); } /* * - ping session * - ping session backup * - save validity backup */ final Callable<?> backupSessionTask = new OnBackupWithoutLoadedSessionTask( sessionId, _storeSecondaryBackup, validityKey, validityData, maxInactiveInterval); _executor.submit(backupSessionTask); if (_log.isDebugEnabled()) { _log.debug("Stored session validity info for session " + sessionId); } _stats.registerSince(NON_STICKY_ON_BACKUP_WITHOUT_LOADED_SESSION, start); } catch (final Throwable e) { _log.warn("An error when trying to load/update validity info.", e); } } /** * Is invoked after the backup of the session is initiated, it's represented by the provided * backupResult. The requestId is identifying the request. */ protected void onAfterBackupSession( @Nonnull final MemcachedBackupSession session, final boolean backupWasForced, @Nonnull final Future<BackupResult> result, @Nonnull final String requestId, @Nonnull final BackupSessionService backupSessionService) { if (!_sessionIdFormat.isValid(session.getIdInternal())) { return; } try { final long start = System.currentTimeMillis(); final int maxInactiveInterval = session.getMaxInactiveInterval(); final byte[] validityData = encode( maxInactiveInterval, session.getLastAccessedTimeInternal(), session.getThisAccessedTimeInternal()); final String validityKey = _sessionIdFormat.createValidityInfoKeyName(session.getIdInternal()); // fix for #88, along with the change in session.getMemcachedExpirationTimeToSet final int expiration = maxInactiveInterval <= 0 ? 0 : maxInactiveInterval; final Future<Boolean> validityResult = _memcached.set(validityKey, toMemcachedExpiration(expiration), validityData); if (!_manager.isSessionBackupAsync()) { // TODO: together with session backup wait not longer than sessionBackupTimeout. // Details: Now/here we're waiting the whole session backup timeout, even if (perhaps) some // time // was spent before when waiting for session backup result. // For sync session backup it would be better to set both the session data and // validity info and afterwards wait for both results (but in sum no longer than // sessionBackupTimeout) validityResult.get(_manager.getSessionBackupTimeout(), TimeUnit.MILLISECONDS); } if (_log.isDebugEnabled()) { _log.debug("Stored session validity info for session " + session.getIdInternal()); } /* The following task are performed outside of the request thread (includes waiting for the backup result): * - ping session if the backup was skipped (depends on the backup result) * - save secondary session backup if session was modified (backup not skipped) * - ping secondary session backup if the backup was skipped * - save secondary validity backup */ final boolean pingSessionIfBackupWasSkipped = !backupWasForced; final boolean performAsyncTasks = pingSessionIfBackupWasSkipped || _storeSecondaryBackup; if (performAsyncTasks) { final Callable<?> backupSessionTask = new OnAfterBackupSessionTask( session, result, pingSessionIfBackupWasSkipped, backupSessionService, _storeSecondaryBackup, validityKey, validityData); _executor.submit(backupSessionTask); } _stats.registerSince(NON_STICKY_AFTER_BACKUP, start); } catch (final Throwable e) { _log.warn("An error occurred during onAfterBackupSession.", e); } } @CheckForNull protected SessionValidityInfo loadSessionValidityInfo(@Nonnull final String sessionId) { return loadSessionValidityInfoForValidityKey( _sessionIdFormat.createValidityInfoKeyName(sessionId)); } @CheckForNull protected SessionValidityInfo loadSessionValidityInfoForValidityKey( @Nonnull final String validityInfoKey) { final byte[] validityInfo = (byte[]) _memcached.get(_storageKeyFormat.format(validityInfoKey)); return validityInfo != null ? decode(validityInfo) : null; } @CheckForNull protected SessionValidityInfo loadBackupSessionValidityInfo(@Nonnull final String sessionId) { final String key = _sessionIdFormat.createValidityInfoKeyName(sessionId); final String backupKey = _sessionIdFormat.createBackupKey(key); return loadSessionValidityInfoForValidityKey(backupKey); } /** Invoked before the session for this sessionId is loaded from memcached. */ @CheckForNull protected abstract LockStatus onBeforeLoadFromMemcached(@Nonnull String sessionId) throws InterruptedException, ExecutionException; /** * Invoked after a non-sticky session is loaded from memcached, can be used to update some session * fields based on separately stored information (e.g. session validity info). * * @param lockStatus the {@link LockStatus} that was returned from {@link * #onBeforeLoadFromMemcached(String)}. */ protected void onAfterLoadFromMemcached( @Nonnull final MemcachedBackupSession session, @Nullable final LockStatus lockStatus) { session.setLockStatus(lockStatus); final long start = System.currentTimeMillis(); final SessionValidityInfo info = loadSessionValidityInfo(session.getIdInternal()); if (info != null) { _stats.registerSince(NON_STICKY_AFTER_LOAD_FROM_MEMCACHED, start); session.setLastAccessedTimeInternal(info.getLastAccessedTime()); session.setThisAccessedTimeInternal(info.getThisAccessedTime()); } else { _log.warn("No validity info available for session " + session.getIdInternal()); } } /** Invoked after a non-sticky session is removed from memcached. */ protected void onAfterDeleteFromMemcached(@Nonnull final String sessionId) { final long start = System.currentTimeMillis(); final String validityInfoKey = _sessionIdFormat.createValidityInfoKeyName(sessionId); _memcached.delete(validityInfoKey); if (_storeSecondaryBackup) { _memcached.delete(_sessionIdFormat.createBackupKey(sessionId)); _memcached.delete(_sessionIdFormat.createBackupKey(validityInfoKey)); } _stats.registerSince(NON_STICKY_AFTER_DELETE_FROM_MEMCACHED, start); } private boolean pingSession(@Nonnull final String sessionId) throws InterruptedException { final Future<Boolean> touchResult = _memcached.add(_storageKeyFormat.format(sessionId), 1, 1); try { if (touchResult.get()) { _stats.nonStickySessionsPingFailed(); _log.warn( "The session " + sessionId + " should be touched in memcached, but it does not exist therein."); return false; } _log.debug("The session was ping'ed successfully."); return true; } catch (final ExecutionException e) { _log.warn("An exception occurred when trying to ping session " + sessionId, e); return false; } } private void pingSession( @Nonnull final MemcachedBackupSession session, @Nonnull final BackupSessionService backupSessionService) throws InterruptedException { final Future<Boolean> touchResult = _memcached.add(_storageKeyFormat.format(session.getIdInternal()), 5, 1); try { if (touchResult.get()) { _stats.nonStickySessionsPingFailed(); _log.warn( "The session " + session.getIdInternal() + " should be touched in memcached, but it does not exist" + " therein. Will store in memcached again."); updateSession(session, backupSessionService); } else _log.debug("The session was ping'ed successfully."); } catch (final ExecutionException e) { _log.warn("An exception occurred when trying to ping session " + session.getIdInternal(), e); } } private void updateSession( @Nonnull final MemcachedBackupSession session, @Nonnull final BackupSessionService backupSessionService) throws InterruptedException { final Future<BackupResult> result = backupSessionService.backupSession(session, true); try { if (result.get().getStatus() != BackupResultStatus.SUCCESS) { _log.warn( "Update for session (after unsuccessful ping) did not return SUCCESS, but " + result.get()); } } catch (final ExecutionException e) { _log.warn( "An exception occurred when trying to update session " + session.getIdInternal(), e); } } private final class OnAfterBackupSessionTask implements Callable<Void> { private final MemcachedBackupSession _session; private final Future<BackupResult> _result; private final boolean _pingSessionIfBackupWasSkipped; private final boolean _storeSecondaryBackup; private final BackupSessionService _backupSessionService; private final String _validityKey; private final byte[] _validityData; private OnAfterBackupSessionTask( @Nonnull final MemcachedBackupSession session, @Nonnull final Future<BackupResult> result, final boolean pingSessionIfBackupWasSkipped, @Nonnull final BackupSessionService backupSessionService, final boolean storeSecondaryBackup, @Nonnull final String validityKey, @Nonnull final byte[] validityData) { _session = session; _result = result; _pingSessionIfBackupWasSkipped = pingSessionIfBackupWasSkipped; _storeSecondaryBackup = storeSecondaryBackup; _validityKey = validityKey; _validityData = validityData; _backupSessionService = backupSessionService; } @Override public Void call() throws Exception { final BackupResult backupResult = _result.get(); if (_pingSessionIfBackupWasSkipped) { if (backupResult.getStatus() == BackupResultStatus.SKIPPED) { pingSession(_session, _backupSessionService); } } /* * For non-sticky sessions we store a backup of the session in a secondary memcached node (under a special key * that's resolved by the SuffixBasedNodeLocator), but only when we have more than 1 memcached node configured... */ if (_storeSecondaryBackup) { try { if (_log.isDebugEnabled()) { _log.debug( "Storing backup in secondary memcached for non-sticky session " + _session.getId()); } if (backupResult.getStatus() == BackupResultStatus.SKIPPED) { pingSessionBackup(_session); } else { saveSessionBackupFromResult(backupResult); } saveValidityBackup(); } catch (final RuntimeException e) { _log.info("Could not store secondary backup of session " + _session.getIdInternal(), e); } } return null; } public void saveSessionBackupFromResult(final BackupResult backupResult) { final byte[] data = backupResult.getData(); if (data != null) { final String key = _sessionIdFormat.createBackupKey(_session.getId()); _memcached.set( key, toMemcachedExpiration(_session.getMemcachedExpirationTimeToSet()), data); } else { _log.warn( "No data set for backupResultStatus " + backupResult.getStatus() + " for sessionId " + _session.getIdInternal() + ", skipping backup" + " of non-sticky session in secondary memcached."); } } public void saveValidityBackup() { final String backupValidityKey = _sessionIdFormat.createBackupKey(_validityKey); final int maxInactiveInterval = _session.getMaxInactiveInterval(); // fix for #88, along with the change in session.getMemcachedExpirationTimeToSet final int expiration = maxInactiveInterval <= 0 ? 0 : maxInactiveInterval; _memcached.set(backupValidityKey, toMemcachedExpiration(expiration), _validityData); } private void pingSessionBackup(@Nonnull final MemcachedBackupSession session) throws InterruptedException { final String key = _sessionIdFormat.createBackupKey(session.getId()); final Future<Boolean> touchResultFuture = _memcached.add(key, 5, 1); try { final boolean touchResult = touchResultFuture.get(_manager.getOperationTimeout(), TimeUnit.MILLISECONDS); if (touchResult) { _log.warn( "The secondary backup for session " + session.getIdInternal() + " should be touched in memcached, but it seemed to be" + " not existing. Will store in memcached again."); saveSessionBackup(session, key); } else _log.debug("The secondary session backup was ping'ed successfully."); } catch (final TimeoutException e) { _log.warn( "The secondary backup for session " + session.getIdInternal() + " could not be completed within " + _manager.getOperationTimeout() + " millis, was cancelled now."); } catch (final ExecutionException e) { _log.warn( "An exception occurred when trying to ping session " + session.getIdInternal(), e); } } public void saveSessionBackup( @Nonnull final MemcachedBackupSession session, @Nonnull final String key) throws InterruptedException { try { final byte[] data = _manager.serialize(session); final Future<Boolean> backupResult = _memcached.set( key, toMemcachedExpiration(session.getMemcachedExpirationTimeToSet()), data); if (!backupResult.get().booleanValue()) { _log.warn( "Update for secondary backup of session " + session.getIdInternal() + " (after unsuccessful ping) did not return sucess."); } } catch (final ExecutionException e) { _log.warn( "An exception occurred when trying to update secondary session backup for " + session.getIdInternal(), e); } } } private final class OnBackupWithoutLoadedSessionTask implements Callable<Void> { private final String _sessionId; private final boolean _storeSecondaryBackup; private final String _validityKey; private final byte[] _validityData; private final int _maxInactiveInterval; private OnBackupWithoutLoadedSessionTask( @Nonnull final String sessionId, final boolean storeSecondaryBackup, @Nonnull final String validityKey, @Nonnull final byte[] validityData, final int maxInactiveInterval) { _sessionId = sessionId; _storeSecondaryBackup = storeSecondaryBackup; _validityKey = validityKey; _validityData = validityData; _maxInactiveInterval = maxInactiveInterval; } @Override public Void call() throws Exception { pingSession(_sessionId); /* * For non-sticky sessions we store/ping a backup of the session in a secondary memcached node (under a special key * that's resolved by the SuffixBasedNodeLocator), but only when we have more than 1 memcached node configured... */ if (_storeSecondaryBackup) { try { pingSessionBackup(_sessionId); final String backupValidityKey = _sessionIdFormat.createBackupKey(_validityKey); // fix for #88, along with the change in session.getMemcachedExpirationTimeToSet final int expiration = _maxInactiveInterval <= 0 ? 0 : _maxInactiveInterval; _memcached.set(backupValidityKey, toMemcachedExpiration(expiration), _validityData); } catch (final RuntimeException e) { _log.info("Could not store secondary backup of session " + _sessionId, e); } } return null; } private boolean pingSessionBackup(@Nonnull final String sessionId) throws InterruptedException { final String key = _sessionIdFormat.createBackupKey(sessionId); final Future<Boolean> touchResultFuture = _memcached.add(key, 1, 1); try { final boolean touchResult = touchResultFuture.get(200, TimeUnit.MILLISECONDS); if (touchResult) { _log.warn( "The secondary backup for session " + sessionId + " should be touched in memcached, but it seemed to be" + " not existing."); return false; } _log.debug("The secondary session backup was ping'ed successfully."); return true; } catch (final TimeoutException e) { _log.warn( "The secondary backup for session " + sessionId + " could not be completed within 200 millis, was cancelled now."); return false; } catch (final ExecutionException e) { _log.warn("An exception occurred when trying to ping session " + sessionId, e); return false; } } } // ---------------- for testing @Nonnull ExecutorService getExecutorService() { return _executor; } }
/** * Abstract the protocol implementation, including threading, etc. Processor is single threaded and * specific to stream-based protocols, will not fit Jk protocols like JNI. * * @author Remy Maucherat * @author Costin Manolache */ public class Http11Protocol extends AbstractProtocol implements MBeanRegistration { protected static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(Http11Protocol.class); /** The string manager for this package. */ protected static StringManager sm = StringManager.getManager(Constants.Package); // ------------------------------------------------------------ Constructor public Http11Protocol() { setSoLinger(Constants.DEFAULT_CONNECTION_LINGER); setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT); // setServerSoTimeout(Constants.DEFAULT_SERVER_SOCKET_TIMEOUT); setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY); } // ----------------------------------------------------------------- Fields protected Http11ConnectionHandler cHandler = new Http11ConnectionHandler(this); protected JIoEndpoint endpoint = new JIoEndpoint(); protected final AbstractEndpoint getEndpoint() { return endpoint; } // * protected ObjectName tpOname = null; // * protected ObjectName rgOname = null; protected ServerSocketFactory socketFactory = null; protected SSLImplementation sslImplementation = null; // ----------------------------------------- ProtocolHandler Implementation // * protected HashMap<String, Object> attributes = new HashMap<String, Object>(); /** Pass config info */ public void setAttribute(String name, Object value) { if (log.isTraceEnabled()) { log.trace(sm.getString("http11protocol.setattribute", name, value)); } attributes.put(name, value); } public Object getAttribute(String key) { return attributes.get(key); } public Iterator getAttributeNames() { return attributes.keySet().iterator(); } /** Set a property. */ public void setProperty(String name, String value) { setAttribute(name, value); } /** Get a property */ public String getProperty(String name) { return (String) getAttribute(name); } /** The adapter, used to call the connector. */ protected Adapter adapter; public void setAdapter(Adapter adapter) { this.adapter = adapter; } public Adapter getAdapter() { return adapter; } public void init() throws Exception { endpoint.setName(getName()); endpoint.setHandler(cHandler); // Verify the validity of the configured socket factory try { if (isSSLEnabled()) { sslImplementation = SSLImplementation.getInstance(sslImplementationName); socketFactory = sslImplementation.getServerSocketFactory(); endpoint.setServerSocketFactory(socketFactory); } else if (socketFactoryName != null) { socketFactory = (ServerSocketFactory) Class.forName(socketFactoryName).newInstance(); endpoint.setServerSocketFactory(socketFactory); } } catch (Exception ex) { log.error(sm.getString("http11protocol.socketfactory.initerror"), ex); throw ex; } if (socketFactory != null) { Iterator<String> attE = attributes.keySet().iterator(); while (attE.hasNext()) { String key = attE.next(); Object v = attributes.get(key); socketFactory.setAttribute(key, v); } } try { endpoint.init(); } catch (Exception ex) { log.error(sm.getString("http11protocol.endpoint.initerror"), ex); throw ex; } if (log.isInfoEnabled()) log.info(sm.getString("http11protocol.init", getName())); } public void start() throws Exception { if (this.domain != null) { try { tpOname = new ObjectName(domain + ":" + "type=ThreadPool,name=" + getName()); Registry.getRegistry(null, null).registerComponent(endpoint, tpOname, null); } catch (Exception e) { log.error("Can't register endpoint"); } rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName()); Registry.getRegistry(null, null).registerComponent(cHandler.global, rgOname, null); } try { endpoint.start(); } catch (Exception ex) { log.error(sm.getString("http11protocol.endpoint.starterror"), ex); throw ex; } if (log.isInfoEnabled()) log.info(sm.getString("http11protocol.start", getName())); } public void pause() throws Exception { try { endpoint.pause(); } catch (Exception ex) { log.error(sm.getString("http11protocol.endpoint.pauseerror"), ex); throw ex; } if (log.isInfoEnabled()) log.info(sm.getString("http11protocol.pause", getName())); } public void resume() throws Exception { try { endpoint.resume(); } catch (Exception ex) { log.error(sm.getString("http11protocol.endpoint.resumeerror"), ex); throw ex; } if (log.isInfoEnabled()) log.info(sm.getString("http11protocol.resume", getName())); } public void destroy() throws Exception { if (log.isInfoEnabled()) log.info(sm.getString("http11protocol.stop", getName())); endpoint.destroy(); if (tpOname != null) Registry.getRegistry(null, null).unregisterComponent(tpOname); if (rgOname != null) Registry.getRegistry(null, null).unregisterComponent(rgOname); } public String getName() { String encodedAddr = ""; if (getAddress() != null) { encodedAddr = "" + getAddress(); if (encodedAddr.startsWith("/")) encodedAddr = encodedAddr.substring(1); encodedAddr = URLEncoder.encode(encodedAddr) + "-"; } return ("http-" + encodedAddr + endpoint.getPort()); } // ------------------------------------------------------------- Properties /** Processor cache. */ protected int processorCache = -1; public int getProcessorCache() { return this.processorCache; } public void setProcessorCache(int processorCache) { this.processorCache = processorCache; } protected int socketBuffer = 9000; public int getSocketBuffer() { return socketBuffer; } public void setSocketBuffer(int socketBuffer) { this.socketBuffer = socketBuffer; } /** * This field indicates if the protocol is secure from the perspective of the client (= https is * used). */ protected boolean secure; public boolean getSecure() { return secure; } public void setSecure(boolean b) { secure = b; } protected boolean SSLEnabled = false; public boolean isSSLEnabled() { return SSLEnabled; } public void setSSLEnabled(boolean SSLEnabled) { this.SSLEnabled = SSLEnabled; } /** Name of the socket factory. */ protected String socketFactoryName = null; public String getSocketFactory() { return socketFactoryName; } public void setSocketFactory(String valueS) { socketFactoryName = valueS; } /** Name of the SSL implementation. */ protected String sslImplementationName = null; public String getSSLImplementation() { return sslImplementationName; } public void setSSLImplementation(String valueS) { sslImplementationName = valueS; setSecure(true); } // HTTP /** * Maximum number of requests which can be performed over a keepalive connection. The default is * the same as for Apache HTTP Server. */ protected int maxKeepAliveRequests = 100; public int getMaxKeepAliveRequests() { return maxKeepAliveRequests; } public void setMaxKeepAliveRequests(int mkar) { maxKeepAliveRequests = mkar; } // HTTP /** * The number of seconds Tomcat will wait for a subsequent request before closing the connection. * The default is the same as for Apache HTTP Server (15 000 milliseconds). */ protected int keepAliveTimeout = -1; public int getKeepAliveTimeout() { return keepAliveTimeout; } public void setKeepAliveTimeout(int timeout) { keepAliveTimeout = timeout; } // HTTP /** * This timeout represents the socket timeout which will be used while the adapter execution is in * progress, unless disableUploadTimeout is set to true. The default is the same as for Apache * HTTP Server (300 000 milliseconds). */ protected int timeout = 300000; public int getTimeout() { return timeout; } public void setTimeout(int timeout) { this.timeout = timeout; } // * /** * Maximum size of the post which will be saved when processing certain requests, such as a POST. */ protected int maxSavePostSize = 4 * 1024; public int getMaxSavePostSize() { return maxSavePostSize; } public void setMaxSavePostSize(int valueI) { maxSavePostSize = valueI; } // HTTP /** Maximum size of the HTTP message header. */ protected int maxHttpHeaderSize = 8 * 1024; public int getMaxHttpHeaderSize() { return maxHttpHeaderSize; } public void setMaxHttpHeaderSize(int valueI) { maxHttpHeaderSize = valueI; } // HTTP /** If true, the regular socket timeout will be used for the full duration of the connection. */ protected boolean disableUploadTimeout = true; public boolean getDisableUploadTimeout() { return disableUploadTimeout; } public void setDisableUploadTimeout(boolean isDisabled) { disableUploadTimeout = isDisabled; } // HTTP /** Integrated compression support. */ protected String compression = "off"; public String getCompression() { return compression; } public void setCompression(String valueS) { compression = valueS; } // HTTP protected String noCompressionUserAgents = null; public String getNoCompressionUserAgents() { return noCompressionUserAgents; } public void setNoCompressionUserAgents(String valueS) { noCompressionUserAgents = valueS; } // HTTP protected String compressableMimeTypes = "text/html,text/xml,text/plain"; public String getCompressableMimeType() { return compressableMimeTypes; } public void setCompressableMimeType(String valueS) { compressableMimeTypes = valueS; } // HTTP protected int compressionMinSize = 2048; public int getCompressionMinSize() { return compressionMinSize; } public void setCompressionMinSize(int valueI) { compressionMinSize = valueI; } // HTTP /** User agents regular expressions which should be restricted to HTTP/1.0 support. */ protected String restrictedUserAgents = null; public String getRestrictedUserAgents() { return restrictedUserAgents; } public void setRestrictedUserAgents(String valueS) { restrictedUserAgents = valueS; } // HTTP /** Server header. */ protected String server; public void setServer(String server) { this.server = server; } public String getServer() { return server; } public Executor getExecutor() { return endpoint.getExecutor(); } public void setExecutor(Executor executor) { endpoint.setExecutor(executor); } public int getMaxThreads() { return endpoint.getMaxThreads(); } public void setMaxThreads(int maxThreads) { endpoint.setMaxThreads(maxThreads); } public int getThreadPriority() { return endpoint.getThreadPriority(); } public void setThreadPriority(int threadPriority) { endpoint.setThreadPriority(threadPriority); } public int getBacklog() { return endpoint.getBacklog(); } public void setBacklog(int backlog) { endpoint.setBacklog(backlog); } public int getPort() { return endpoint.getPort(); } public void setPort(int port) { endpoint.setPort(port); } public InetAddress getAddress() { return endpoint.getAddress(); } public void setAddress(InetAddress ia) { endpoint.setAddress(ia); } public boolean getTcpNoDelay() { return endpoint.getTcpNoDelay(); } public void setTcpNoDelay(boolean tcpNoDelay) { endpoint.setTcpNoDelay(tcpNoDelay); } public int getSoLinger() { return endpoint.getSoLinger(); } public void setSoLinger(int soLinger) { endpoint.setSoLinger(soLinger); } public int getSoTimeout() { return endpoint.getSoTimeout(); } public void setSoTimeout(int soTimeout) { endpoint.setSoTimeout(soTimeout); } public int getUnlockTimeout() { return endpoint.getUnlockTimeout(); } public void setUnlockTimeout(int unlockTimeout) { endpoint.setUnlockTimeout(unlockTimeout); } // HTTP /** Return the Keep-Alive policy for the connection. */ public boolean getKeepAlive() { return ((maxKeepAliveRequests != 0) && (maxKeepAliveRequests != 1)); } // HTTP /** Set the keep-alive policy for this connection. */ public void setKeepAlive(boolean keepAlive) { if (!keepAlive) { setMaxKeepAliveRequests(1); } } /* * Note: All the following are JSSE/java.io specific attributes. */ public String getKeystore() { return (String) getAttribute("keystore"); } public void setKeystore(String k) { setAttribute("keystore", k); } public String getKeypass() { return (String) getAttribute("keypass"); } public void setKeypass(String k) { attributes.put("keypass", k); // setAttribute("keypass", k); } public String getKeytype() { return (String) getAttribute("keystoreType"); } public void setKeytype(String k) { setAttribute("keystoreType", k); } public String getClientauth() { return (String) getAttribute("clientauth"); } public void setClientauth(String k) { setAttribute("clientauth", k); } public String getProtocols() { return (String) getAttribute("protocols"); } public void setProtocols(String k) { setAttribute("protocols", k); } public String getAlgorithm() { return (String) getAttribute("algorithm"); } public void setAlgorithm(String k) { setAttribute("algorithm", k); } public String getCiphers() { return (String) getAttribute("ciphers"); } public void setCiphers(String ciphers) { setAttribute("ciphers", ciphers); } public String getKeyAlias() { return (String) getAttribute("keyAlias"); } public void setKeyAlias(String keyAlias) { setAttribute("keyAlias", keyAlias); } /** * When client certificate information is presented in a form other than instances of {@link * java.security.cert.X509Certificate} it needs to be converted before it can be used and this * property controls which JSSE provider is used to perform the conversion. For example it is used * with the AJP connectors, the HTTP APR connector and with the {@link * org.apache.catalina.valves.SSLValve}. If not specified, the default provider will be used. */ protected String clientCertProvider = null; public String getClientCertProvider() { return clientCertProvider; } public void setClientCertProvider(String s) { this.clientCertProvider = s; } // ----------------------------------- Http11ConnectionHandler Inner Class protected static class Http11ConnectionHandler implements Handler { protected Http11Protocol proto; protected AtomicLong registerCount = new AtomicLong(0); protected RequestGroupInfo global = new RequestGroupInfo(); protected ConcurrentLinkedQueue<Http11Processor> recycledProcessors = new ConcurrentLinkedQueue<Http11Processor>() { protected AtomicInteger size = new AtomicInteger(0); public boolean offer(Http11Processor processor) { boolean offer = (proto.processorCache == -1) ? true : (size.get() < proto.processorCache); // avoid over growing our cache or add after we have stopped boolean result = false; if (offer) { result = super.offer(processor); if (result) { size.incrementAndGet(); } } if (!result) unregister(processor); return result; } public Http11Processor poll() { Http11Processor result = super.poll(); if (result != null) { size.decrementAndGet(); } return result; } public void clear() { Http11Processor next = poll(); while (next != null) { unregister(next); next = poll(); } super.clear(); size.set(0); } }; Http11ConnectionHandler(Http11Protocol proto) { this.proto = proto; } public boolean process(Socket socket) { Http11Processor processor = recycledProcessors.poll(); try { if (processor == null) { processor = createProcessor(); } if (processor instanceof ActionHook) { ((ActionHook) processor).action(ActionCode.ACTION_START, null); } if (proto.isSSLEnabled() && (proto.sslImplementation != null)) { processor.setSSLSupport(proto.sslImplementation.getSSLSupport(socket)); } else { processor.setSSLSupport(null); } processor.process(socket); return false; } catch (java.net.SocketException e) { // SocketExceptions are normal Http11Protocol.log.debug(sm.getString("http11protocol.proto.socketexception.debug"), e); } catch (java.io.IOException e) { // IOExceptions are normal Http11Protocol.log.debug(sm.getString("http11protocol.proto.ioexception.debug"), e); } // Future developers: if you discover any other // rare-but-nonfatal exceptions, catch them here, and log as // above. catch (Throwable e) { // any other exception or error is odd. Here we log it // with "ERROR" level, so it will show up even on // less-than-verbose logs. Http11Protocol.log.error(sm.getString("http11protocol.proto.error"), e); } finally { // if(proto.adapter != null) proto.adapter.recycle(); // processor.recycle(); if (processor instanceof ActionHook) { ((ActionHook) processor).action(ActionCode.ACTION_STOP, null); } recycledProcessors.offer(processor); } return false; } protected Http11Processor createProcessor() { Http11Processor processor = new Http11Processor(proto.maxHttpHeaderSize, proto.endpoint); processor.setAdapter(proto.adapter); processor.setMaxKeepAliveRequests(proto.maxKeepAliveRequests); processor.setKeepAliveTimeout(proto.keepAliveTimeout); processor.setTimeout(proto.timeout); processor.setDisableUploadTimeout(proto.disableUploadTimeout); processor.setCompressionMinSize(proto.compressionMinSize); processor.setCompression(proto.compression); processor.setNoCompressionUserAgents(proto.noCompressionUserAgents); processor.setCompressableMimeTypes(proto.compressableMimeTypes); processor.setRestrictedUserAgents(proto.restrictedUserAgents); processor.setSocketBuffer(proto.socketBuffer); processor.setMaxSavePostSize(proto.maxSavePostSize); processor.setServer(proto.server); register(processor); return processor; } protected void register(Http11Processor processor) { if (proto.getDomain() != null) { synchronized (this) { try { long count = registerCount.incrementAndGet(); RequestInfo rp = processor.getRequest().getRequestProcessor(); rp.setGlobalProcessor(global); ObjectName rpName = new ObjectName( proto.getDomain() + ":type=RequestProcessor,worker=" + proto.getName() + ",name=HttpRequest" + count); if (log.isDebugEnabled()) { log.debug("Register " + rpName); } Registry.getRegistry(null, null).registerComponent(rp, rpName, null); rp.setRpName(rpName); } catch (Exception e) { log.warn("Error registering request"); } } } } protected void unregister(Http11Processor processor) { if (proto.getDomain() != null) { synchronized (this) { try { RequestInfo rp = processor.getRequest().getRequestProcessor(); rp.setGlobalProcessor(null); ObjectName rpName = rp.getRpName(); if (log.isDebugEnabled()) { log.debug("Unregister " + rpName); } Registry.getRegistry(null, null).unregisterComponent(rpName); rp.setRpName(null); } catch (Exception e) { log.warn("Error unregistering request", e); } } } } } // -------------------- JMX related methods -------------------- // * protected String domain; protected ObjectName oname; protected MBeanServer mserver; public ObjectName getObjectName() { return oname; } public String getDomain() { return domain; } public ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception { oname = name; mserver = server; domain = name.getDomain(); return name; } public void postRegister(Boolean registrationDone) {} public void preDeregister() throws Exception {} public void postDeregister() {} }
/** * When using mod_proxy_http, the client SSL information is not included in the protocol (unlike * mod_jk and mod_proxy_ajp). To make the client SSL information available to Tomcat, some * additional configuration is required. In httpd, mod_headers is used to add the SSL information as * HTTP headers. In Tomcat, this valve is used to read the information from the HTTP headers and * insert it into the request. * * <p><b>Note: Ensure that the headers are always set by httpd for all requests to prevent a client * spoofing SSL information by sending fake headers. </b> * * <p>In httpd.conf add the following: * * <pre> * <IfModule ssl_module> * RequestHeader set SSL_CLIENT_CERT "%{SSL_CLIENT_CERT}s" * RequestHeader set SSL_CIPHER "%{SSL_CIPHER}s" * RequestHeader set SSL_SESSION_ID "%{SSL_SESSION_ID}s" * RequestHeader set SSL_CIPHER_USEKEYSIZE "%{SSL_CIPHER_USEKEYSIZE}s" * </IfModule> * </pre> * * In server.xml, configure this valve under the Engine element in server.xml: * * <pre> * <Engine ...> * <Valve className="org.apache.catalina.valves.SSLValve" /> * <Host ... /> * </Engine> * </pre> */ public class SSLValve extends ValveBase { private static final Log log = LogFactory.getLog(SSLValve.class); // ------------------------------------------------------ Constructor public SSLValve() { super(true); } public String mygetHeader(Request request, String header) { String strcert0 = request.getHeader(header); if (strcert0 == null) { return null; } /* mod_header writes "(null)" when the ssl variable is no filled */ if ("(null)".equals(strcert0)) { return null; } return strcert0; } @Override public void invoke(Request request, Response response) throws IOException, ServletException { /* * mod_header converts the '\n' into ' ' so we have to rebuild the client * certificate */ String strcert0 = mygetHeader(request, "ssl_client_cert"); if (strcert0 != null && strcert0.length() > 28) { String strcert1 = strcert0.replace(' ', '\n'); String strcert2 = strcert1.substring(28, strcert1.length() - 26); String strcert3 = "-----BEGIN CERTIFICATE-----\n"; String strcert4 = strcert3.concat(strcert2); String strcerts = strcert4.concat("\n-----END CERTIFICATE-----\n"); // ByteArrayInputStream bais = new // ByteArrayInputStream(strcerts.getBytes("UTF-8")); ByteArrayInputStream bais = new ByteArrayInputStream(strcerts.getBytes(Charset.defaultCharset())); X509Certificate jsseCerts[] = null; String providerName = (String) request.getConnector().getProperty("clientCertProvider"); try { CertificateFactory cf; if (providerName == null) { cf = CertificateFactory.getInstance("X.509"); } else { cf = CertificateFactory.getInstance("X.509", providerName); } X509Certificate cert = (X509Certificate) cf.generateCertificate(bais); jsseCerts = new X509Certificate[1]; jsseCerts[0] = cert; } catch (java.security.cert.CertificateException e) { log.warn(sm.getString("sslValve.certError", strcerts), e); } catch (NoSuchProviderException e) { log.error(sm.getString("sslValve.invalidProvider", providerName), e); } request.setAttribute(Globals.CERTIFICATES_ATTR, jsseCerts); } strcert0 = mygetHeader(request, "ssl_cipher"); if (strcert0 != null) { request.setAttribute(Globals.CIPHER_SUITE_ATTR, strcert0); } strcert0 = mygetHeader(request, "ssl_session_id"); if (strcert0 != null) { request.setAttribute(Globals.SSL_SESSION_ID_ATTR, strcert0); request.setAttribute(Globals.SSL_SESSION_ID_TOMCAT_ATTR, strcert0); } strcert0 = mygetHeader(request, "ssl_cipher_usekeysize"); if (strcert0 != null) { request.setAttribute(Globals.KEY_SIZE_ATTR, Integer.valueOf(strcert0)); } getNext().invoke(request, response); } }
/** * The default {@link JarScanner} implementation scans the WEB-INF/lib directory followed by the * provided classloader and then works up the classloader hierarchy. This implementation is * sufficient to meet the requirements of the Servlet 3.0 specification as well as to provide a * number of Tomcat specific extensions. The extensions are: * * <ul> * <li>Scanning the classloader hierarchy (enabled by default) * <li>Testing all files to see if they are JARs (disabled by default) * <li>Testing all directories to see if they are exploded JARs (disabled by default) * </ul> * * All of the extensions may be controlled via configuration. */ public class StandardJarScanner implements JarScanner { private static final Log log = LogFactory.getLog(StandardJarScanner.class); private static final Set<String> defaultJarsToSkip = new HashSet<String>(); /** The string resources for this package. */ private static final StringManager sm = StringManager.getManager(Constants.Package); static { String jarList = System.getProperty(Constants.SKIP_JARS_PROPERTY); if (jarList != null) { StringTokenizer tokenizer = new StringTokenizer(jarList, ","); while (tokenizer.hasMoreElements()) { String token = tokenizer.nextToken().trim(); if (token.length() > 0) { defaultJarsToSkip.add(token); } } } } /** Controls the classpath scanning extension. */ private boolean scanClassPath = true; public boolean isScanClassPath() { return scanClassPath; } public void setScanClassPath(boolean scanClassPath) { this.scanClassPath = scanClassPath; } /** Controls the testing all files to see of they are JAR files extension. */ private boolean scanAllFiles = false; public boolean isScanAllFiles() { return scanAllFiles; } public void setScanAllFiles(boolean scanAllFiles) { this.scanAllFiles = scanAllFiles; } /** Controls the testing all directories to see of they are exploded JAR files extension. */ private boolean scanAllDirectories = false; public boolean isScanAllDirectories() { return scanAllDirectories; } public void setScanAllDirectories(boolean scanAllDirectories) { this.scanAllDirectories = scanAllDirectories; } /** * Controls the testing of the bootstrap classpath which consists of the runtime classes provided * by the JVM and any installed system extensions. */ private boolean scanBootstrapClassPath = false; public boolean isScanBootstrapClassPath() { return scanBootstrapClassPath; } public void setScanBootstrapClassPath(boolean scanBootstrapClassPath) { this.scanBootstrapClassPath = scanBootstrapClassPath; } /** * Scan the provided ServletContext and classloader for JAR files. Each JAR file found will be * passed to the callback handler to be processed. * * @param context The ServletContext - used to locate and access WEB-INF/lib * @param classloader The classloader - used to access JARs not in WEB-INF/lib * @param callback The handler to process any JARs found * @param jarsToSkip List of JARs to ignore. If this list is null, a default list will be read * from the system property defined by {@link Constants#SKIP_JARS_PROPERTY} */ @Override public void scan( ServletContext context, ClassLoader classloader, JarScannerCallback callback, Set<String> jarsToSkip) { if (log.isTraceEnabled()) { log.trace(sm.getString("jarScan.webinflibStart")); } final Set<String> ignoredJars; if (jarsToSkip == null) { ignoredJars = defaultJarsToSkip; } else { ignoredJars = jarsToSkip; } // Scan WEB-INF/lib Set<String> dirList = context.getResourcePaths(Constants.WEB_INF_LIB); if (dirList != null) { Iterator<String> it = dirList.iterator(); while (it.hasNext()) { String path = it.next(); if (path.endsWith(Constants.JAR_EXT) && !Matcher.matchName(ignoredJars, path.substring(path.lastIndexOf('/') + 1))) { // Need to scan this JAR if (log.isDebugEnabled()) { log.debug(sm.getString("jarScan.webinflibJarScan", path)); } URL url = null; try { // File URLs are always faster to work with so use them // if available. String realPath = context.getRealPath(path); if (realPath == null) { url = context.getResource(path); } else { url = (new File(realPath)).toURI().toURL(); } process(callback, url); } catch (IOException e) { log.warn(sm.getString("jarScan.webinflibFail", url), e); } } else { if (log.isTraceEnabled()) { log.trace(sm.getString("jarScan.webinflibJarNoScan", path)); } } } } // Scan the classpath if (scanClassPath && classloader != null) { if (log.isTraceEnabled()) { log.trace(sm.getString("jarScan.classloaderStart")); } ClassLoader loader = classloader; ClassLoader stopLoader = null; if (!scanBootstrapClassPath) { // Stop when we reach the bootstrap class loader stopLoader = ClassLoader.getSystemClassLoader().getParent(); } while (loader != null && loader != stopLoader) { if (loader instanceof URLClassLoader) { URL[] urls = ((URLClassLoader) loader).getURLs(); for (int i = 0; i < urls.length; i++) { // Extract the jarName if there is one to be found String jarName = getJarName(urls[i]); // Skip JARs known not to be interesting and JARs // in WEB-INF/lib we have already scanned if (jarName != null && !(Matcher.matchName(ignoredJars, jarName) || urls[i].toString().contains(Constants.WEB_INF_LIB + jarName))) { if (log.isDebugEnabled()) { log.debug(sm.getString("jarScan.classloaderJarScan", urls[i])); } try { process(callback, urls[i]); } catch (IOException ioe) { log.warn(sm.getString("jarScan.classloaderFail", urls[i]), ioe); } } else { if (log.isTraceEnabled()) { log.trace(sm.getString("jarScan.classloaderJarNoScan", urls[i])); } } } } loader = loader.getParent(); } } } /* * Scan a URL for JARs with the optional extensions to look at all files * and all directories. */ private void process(JarScannerCallback callback, URL url) throws IOException { if (log.isTraceEnabled()) { log.trace(sm.getString("jarScan.jarUrlStart", url)); } URLConnection conn = url.openConnection(); if (conn instanceof JarURLConnection) { callback.scan((JarURLConnection) conn); } else { String urlStr = url.toString(); if (urlStr.startsWith("file:") || urlStr.startsWith("jndi:") || urlStr.startsWith("http:") || urlStr.startsWith("https:")) { if (urlStr.endsWith(Constants.JAR_EXT)) { URL jarURL = new URL("jar:" + urlStr + "!/"); callback.scan((JarURLConnection) jarURL.openConnection()); } else { File f; try { f = new File(url.toURI()); if (f.isFile() && scanAllFiles) { // Treat this file as a JAR URL jarURL = new URL("jar:" + urlStr + "!/"); callback.scan((JarURLConnection) jarURL.openConnection()); } else if (f.isDirectory() && scanAllDirectories) { File metainf = new File(f.getAbsoluteFile() + File.separator + "META-INF"); if (metainf.isDirectory()) { callback.scan(f); } } } catch (URISyntaxException e) { // Wrap the exception and re-throw IOException ioe = new IOException(); ioe.initCause(e); throw ioe; } } } } } /* * Extract the JAR name, if present, from a URL */ private String getJarName(URL url) { String name = null; String path = url.getPath(); int end = path.indexOf(Constants.JAR_EXT); if (end != -1) { int start = path.lastIndexOf('/', end); name = path.substring(start + 1, end + 4); } else if (isScanAllDirectories()) { int start = path.lastIndexOf('/'); name = path.substring(start + 1); } return name; } }
/** * Implementation of a request processor which delegates the processing to a Coyote processor. * * @author Craig R. McClanahan * @author Remy Maucherat */ public class CoyoteAdapter implements Adapter { private static final Log log = LogFactory.getLog(CoyoteAdapter.class); // -------------------------------------------------------------- Constants private static final String POWERED_BY = "Servlet/3.0 JSP/2.2 " + "(" + ServerInfo.getServerInfo() + " Java/" + System.getProperty("java.vm.vendor") + "/" + System.getProperty("java.runtime.version") + ")"; private static final EnumSet<SessionTrackingMode> SSL_ONLY = EnumSet.of(SessionTrackingMode.SSL); public static final int ADAPTER_NOTES = 1; protected static final boolean ALLOW_BACKSLASH = Boolean.valueOf( System.getProperty( "org.apache.catalina.connector.CoyoteAdapter.ALLOW_BACKSLASH", "false")) .booleanValue(); // ----------------------------------------------------------- Constructors /** * Construct a new CoyoteProcessor associated with the specified connector. * * @param connector CoyoteConnector that owns this processor */ public CoyoteAdapter(Connector connector) { super(); this.connector = connector; } // ----------------------------------------------------- Instance Variables /** The CoyoteConnector with which this processor is associated. */ private Connector connector = null; /** The string manager for this package. */ protected static final StringManager sm = StringManager.getManager(Constants.Package); /** Encoder for the Location URL in HTTP redirects. */ protected static URLEncoder urlEncoder; // ----------------------------------------------------- Static Initializer /** The safe character set. */ static { urlEncoder = new URLEncoder(); urlEncoder.addSafeCharacter('-'); urlEncoder.addSafeCharacter('_'); urlEncoder.addSafeCharacter('.'); urlEncoder.addSafeCharacter('*'); urlEncoder.addSafeCharacter('/'); } // -------------------------------------------------------- Adapter Methods /** * Event method. * * @return false to indicate an error, expected or not */ @Override public boolean event( org.apache.coyote.Request req, org.apache.coyote.Response res, SocketStatus status) { Request request = (Request) req.getNote(ADAPTER_NOTES); Response response = (Response) res.getNote(ADAPTER_NOTES); if (request.getWrapper() == null) { return false; } boolean error = false; boolean read = false; try { if (status == SocketStatus.OPEN_READ) { if (response.isClosed()) { // The event has been closed asynchronously, so call end instead of // read to cleanup the pipeline request.getEvent().setEventType(CometEvent.EventType.END); request.getEvent().setEventSubType(null); } else { try { // Fill the read buffer of the servlet layer if (request.read()) { read = true; } } catch (IOException e) { error = true; } if (read) { request.getEvent().setEventType(CometEvent.EventType.READ); request.getEvent().setEventSubType(null); } else if (error) { request.getEvent().setEventType(CometEvent.EventType.ERROR); request.getEvent().setEventSubType(CometEvent.EventSubType.CLIENT_DISCONNECT); } else { request.getEvent().setEventType(CometEvent.EventType.END); request.getEvent().setEventSubType(null); } } } else if (status == SocketStatus.DISCONNECT) { request.getEvent().setEventType(CometEvent.EventType.ERROR); request.getEvent().setEventSubType(CometEvent.EventSubType.CLIENT_DISCONNECT); error = true; } else if (status == SocketStatus.ERROR) { request.getEvent().setEventType(CometEvent.EventType.ERROR); request.getEvent().setEventSubType(CometEvent.EventSubType.IOEXCEPTION); error = true; } else if (status == SocketStatus.STOP) { request.getEvent().setEventType(CometEvent.EventType.END); request.getEvent().setEventSubType(CometEvent.EventSubType.SERVER_SHUTDOWN); } else if (status == SocketStatus.TIMEOUT) { if (response.isClosed()) { // The event has been closed asynchronously, so call end instead of // read to cleanup the pipeline request.getEvent().setEventType(CometEvent.EventType.END); request.getEvent().setEventSubType(null); } else { request.getEvent().setEventType(CometEvent.EventType.ERROR); request.getEvent().setEventSubType(CometEvent.EventSubType.TIMEOUT); } } req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName()); // Calling the container connector .getService() .getContainer() .getPipeline() .getFirst() .event(request, response, request.getEvent()); if (!error && !response.isClosed() && (request.getAttribute(RequestDispatcher.ERROR_EXCEPTION) != null)) { // An unexpected exception occurred while processing the event, so // error should be called request.getEvent().setEventType(CometEvent.EventType.ERROR); request.getEvent().setEventSubType(null); error = true; connector .getService() .getContainer() .getPipeline() .getFirst() .event(request, response, request.getEvent()); } if (response.isClosed() || !request.isComet()) { if (status == SocketStatus.OPEN_READ && request.getEvent().getEventType() != EventType.END) { // CometEvent.close was called during an event other than END request.getEvent().setEventType(CometEvent.EventType.END); request.getEvent().setEventSubType(null); error = true; connector .getService() .getContainer() .getPipeline() .getFirst() .event(request, response, request.getEvent()); } res.action(ActionCode.COMET_END, null); } else if (!error && read && request.getAvailable()) { // If this was a read and not all bytes have been read, or if no data // was read from the connector, then it is an error request.getEvent().setEventType(CometEvent.EventType.ERROR); request.getEvent().setEventSubType(CometEvent.EventSubType.IOEXCEPTION); error = true; connector .getService() .getContainer() .getPipeline() .getFirst() .event(request, response, request.getEvent()); } return (!error); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); if (!(t instanceof IOException)) { log.error(sm.getString("coyoteAdapter.service"), t); } error = true; return false; } finally { req.getRequestProcessor().setWorkerThreadName(null); // Recycle the wrapper request and response if (error || response.isClosed() || !request.isComet()) { ((Context) request.getMappingData().context) .logAccess(request, response, System.currentTimeMillis() - req.getStartTime(), false); request.recycle(); request.setFilterChain(null); response.recycle(); } } } @Override public boolean asyncDispatch( org.apache.coyote.Request req, org.apache.coyote.Response res, SocketStatus status) throws Exception { Request request = (Request) req.getNote(ADAPTER_NOTES); Response response = (Response) res.getNote(ADAPTER_NOTES); if (request == null) { throw new IllegalStateException("Dispatch may only happen on an existing request."); } boolean comet = false; boolean success = true; AsyncContextImpl asyncConImpl = (AsyncContextImpl) request.getAsyncContext(); req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName()); try { if (!request.isAsync() && !comet) { // Error or timeout - need to tell listeners the request is over // Have to test this first since state may change while in this // method and this is only required if entering this method in // this state Context ctxt = (Context) request.getMappingData().context; if (ctxt != null) { ctxt.fireRequestDestroyEvent(request); } // Lift any suspension (e.g. if sendError() was used by an async // request) to allow the response to be written to the client response.setSuspended(false); } if (status == SocketStatus.TIMEOUT) { if (!asyncConImpl.timeout()) { asyncConImpl.setErrorState(null, false); } } // Has an error occurred during async processing that needs to be // processed by the application's error page mechanism (or Tomcat's // if the application doesn't define one)? if (!request.isAsyncDispatching() && request.isAsync() && response.isErrorReportRequired()) { connector.getService().getContainer().getPipeline().getFirst().invoke(request, response); } if (request.isAsyncDispatching()) { connector.getService().getContainer().getPipeline().getFirst().invoke(request, response); Throwable t = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION); if (t != null) { asyncConImpl.setErrorState(t, true); } } if (request.isComet()) { if (!response.isClosed() && !response.isError()) { if (request.getAvailable() || (request.getContentLength() > 0 && (!request.isParametersParsed()))) { // Invoke a read event right away if there are available bytes if (event(req, res, SocketStatus.OPEN_READ)) { comet = true; res.action(ActionCode.COMET_BEGIN, null); } } else { comet = true; res.action(ActionCode.COMET_BEGIN, null); } } else { // Clear the filter chain, as otherwise it will not be reset elsewhere // since this is a Comet request request.setFilterChain(null); } } if (!request.isAsync() && !comet) { request.finishRequest(); response.finishResponse(); req.action(ActionCode.POST_REQUEST, null); ((Context) request.getMappingData().context) .logAccess(request, response, System.currentTimeMillis() - req.getStartTime(), false); } // Check to see if the processor is in an error state. If it is, // bail out now. AtomicBoolean error = new AtomicBoolean(false); res.action(ActionCode.IS_ERROR, error); if (error.get()) { success = false; } } catch (IOException e) { success = false; // Ignore } catch (Throwable t) { ExceptionUtils.handleThrowable(t); success = false; log.error(sm.getString("coyoteAdapter.service"), t); } finally { req.getRequestProcessor().setWorkerThreadName(null); // Recycle the wrapper request and response if (!success || (!comet && !request.isAsync())) { request.recycle(); response.recycle(); } else { // Clear converters so that the minimum amount of memory // is used by this processor request.clearEncoders(); response.clearEncoders(); } } return success; } /** Service method. */ @Override public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception { Request request = (Request) req.getNote(ADAPTER_NOTES); Response response = (Response) res.getNote(ADAPTER_NOTES); if (request == null) { // Create objects request = connector.createRequest(); request.setCoyoteRequest(req); response = connector.createResponse(); response.setCoyoteResponse(res); // Link objects request.setResponse(response); response.setRequest(request); // Set as notes req.setNote(ADAPTER_NOTES, request); res.setNote(ADAPTER_NOTES, response); // Set query string encoding req.getParameters().setQueryStringEncoding(connector.getURIEncoding()); } if (connector.getXpoweredBy()) { response.addHeader("X-Powered-By", POWERED_BY); } boolean comet = false; boolean async = false; try { // Parse and set Catalina and configuration specific // request parameters req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName()); boolean postParseSuccess = postParseRequest(req, request, res, response); if (postParseSuccess) { // check valves if we support async request.setAsyncSupported( connector.getService().getContainer().getPipeline().isAsyncSupported()); // Calling the container connector.getService().getContainer().getPipeline().getFirst().invoke(request, response); if (request.isComet()) { if (!response.isClosed() && !response.isError()) { if (request.getAvailable() || (request.getContentLength() > 0 && (!request.isParametersParsed()))) { // Invoke a read event right away if there are available bytes if (event(req, res, SocketStatus.OPEN_READ)) { comet = true; res.action(ActionCode.COMET_BEGIN, null); } } else { comet = true; res.action(ActionCode.COMET_BEGIN, null); } } else { // Clear the filter chain, as otherwise it will not be reset elsewhere // since this is a Comet request request.setFilterChain(null); } } } AsyncContextImpl asyncConImpl = (AsyncContextImpl) request.getAsyncContext(); if (asyncConImpl != null) { async = true; } else if (!comet) { request.finishRequest(); response.finishResponse(); if (postParseSuccess && request.getMappingData().context != null) { // Log only if processing was invoked. // If postParseRequest() failed, it has already logged it. // If context is null this was the start of a comet request // that failed and has already been logged. ((Context) request.getMappingData().context) .logAccess(request, response, System.currentTimeMillis() - req.getStartTime(), false); } req.action(ActionCode.POST_REQUEST, null); } } catch (IOException e) { // Ignore } finally { req.getRequestProcessor().setWorkerThreadName(null); AtomicBoolean error = new AtomicBoolean(false); res.action(ActionCode.IS_ERROR, error); // Recycle the wrapper request and response if (!comet && !async || error.get()) { request.recycle(); response.recycle(); } else { // Clear converters so that the minimum amount of memory // is used by this processor request.clearEncoders(); response.clearEncoders(); } } } @Override public void errorDispatch(org.apache.coyote.Request req, org.apache.coyote.Response res) { Request request = (Request) req.getNote(ADAPTER_NOTES); Response response = (Response) res.getNote(ADAPTER_NOTES); if (request != null && request.getMappingData().context != null) { ((Context) request.getMappingData().context) .logAccess(request, response, System.currentTimeMillis() - req.getStartTime(), false); } else { log(req, res, System.currentTimeMillis() - req.getStartTime()); } if (request != null) { request.recycle(); } if (response != null) { response.recycle(); } req.recycle(); res.recycle(); } @Override public void log(org.apache.coyote.Request req, org.apache.coyote.Response res, long time) { Request request = (Request) req.getNote(ADAPTER_NOTES); Response response = (Response) res.getNote(ADAPTER_NOTES); if (request == null) { // Create objects request = connector.createRequest(); request.setCoyoteRequest(req); response = connector.createResponse(); response.setCoyoteResponse(res); // Link objects request.setResponse(response); response.setRequest(request); // Set as notes req.setNote(ADAPTER_NOTES, request); res.setNote(ADAPTER_NOTES, response); // Set query string encoding req.getParameters().setQueryStringEncoding(connector.getURIEncoding()); } try { // Log at the lowest level available. logAccess() will be // automatically called on parent containers. boolean logged = false; if (request.mappingData != null) { if (request.mappingData.context != null) { logged = true; ((Context) request.mappingData.context).logAccess(request, response, time, true); } else if (request.mappingData.host != null) { logged = true; ((Host) request.mappingData.host).logAccess(request, response, time, true); } } if (!logged) { connector.getService().getContainer().logAccess(request, response, time, true); } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.warn(sm.getString("coyoteAdapter.accesslogFail"), t); } finally { request.recycle(); response.recycle(); } } private static class RecycleRequiredException extends Exception { private static final long serialVersionUID = 1L; } @Override public void checkRecycled(org.apache.coyote.Request req, org.apache.coyote.Response res) { Request request = (Request) req.getNote(ADAPTER_NOTES); Response response = (Response) res.getNote(ADAPTER_NOTES); String messageKey = null; if (request != null && request.getHost() != null) { messageKey = "coyoteAdapter.checkRecycled.request"; } else if (response != null && response.getContentWritten() != 0) { messageKey = "coyoteAdapter.checkRecycled.response"; } if (messageKey != null) { // Log this request, as it has probably skipped the access log. // The log() method will take care of recycling. log(req, res, 0L); if (connector.getState().isAvailable()) { if (log.isInfoEnabled()) { log.info(sm.getString(messageKey), new RecycleRequiredException()); } } else { // There may be some aborted requests. // When connector shuts down, the request and response will not // be reused, so there is no issue to warn about here. if (log.isDebugEnabled()) { log.debug(sm.getString(messageKey), new RecycleRequiredException()); } } } } @Override public String getDomain() { return connector.getDomain(); } // ------------------------------------------------------ Protected Methods /** Parse additional request parameters. */ protected boolean postParseRequest( org.apache.coyote.Request req, Request request, org.apache.coyote.Response res, Response response) throws Exception { // XXX the processor may have set a correct scheme and port prior to this point, // in ajp13 protocols dont make sense to get the port from the connector... // otherwise, use connector configuration if (!req.scheme().isNull()) { // use processor specified scheme to determine secure state request.setSecure(req.scheme().equals("https")); } else { // use connector scheme and secure configuration, (defaults to // "http" and false respectively) req.scheme().setString(connector.getScheme()); request.setSecure(connector.getSecure()); } // FIXME: the code below doesnt belongs to here, // this is only have sense // in Http11, not in ajp13.. // At this point the Host header has been processed. // Override if the proxyPort/proxyHost are set String proxyName = connector.getProxyName(); int proxyPort = connector.getProxyPort(); if (proxyPort != 0) { req.setServerPort(proxyPort); } if (proxyName != null) { req.serverName().setString(proxyName); } // Copy the raw URI to the decodedURI MessageBytes decodedURI = req.decodedURI(); decodedURI.duplicate(req.requestURI()); // Parse the path parameters. This will: // - strip out the path parameters // - convert the decodedURI to bytes parsePathParameters(req, request); // URI decoding // %xx decoding of the URL try { req.getURLDecoder().convert(decodedURI, false); } catch (IOException ioe) { res.setStatus(400); res.setMessage("Invalid URI: " + ioe.getMessage()); connector.getService().getContainer().logAccess(request, response, 0, true); return false; } // Normalization if (!normalize(req.decodedURI())) { res.setStatus(400); res.setMessage("Invalid URI"); connector.getService().getContainer().logAccess(request, response, 0, true); return false; } // Character decoding convertURI(decodedURI, request); // Check that the URI is still normalized if (!checkNormalize(req.decodedURI())) { res.setStatus(400); res.setMessage("Invalid URI character encoding"); connector.getService().getContainer().logAccess(request, response, 0, true); return false; } // Request mapping. MessageBytes serverName; if (connector.getUseIPVHosts()) { serverName = req.localName(); if (serverName.isNull()) { // well, they did ask for it res.action(ActionCode.REQ_LOCAL_NAME_ATTRIBUTE, null); } } else { serverName = req.serverName(); } if (request.isAsyncStarted()) { // TODO SERVLET3 - async // reset mapping data, should prolly be done elsewhere request.getMappingData().recycle(); } // Version for the second mapping loop and // Context that we expect to get for that version String version = null; Context versionContext = null; boolean mapRequired = true; while (mapRequired) { // This will map the the latest version by default connector.getMapper().map(serverName, decodedURI, version, request.getMappingData()); request.setContext((Context) request.getMappingData().context); request.setWrapper((Wrapper) request.getMappingData().wrapper); // If there is no context at this point, it is likely no ROOT context // has been deployed if (request.getContext() == null) { res.setStatus(404); res.setMessage("Not found"); // No context, so use host Host host = request.getHost(); // Make sure there is a host (might not be during shutdown) if (host != null) { host.logAccess(request, response, 0, true); } return false; } // Now we have the context, we can parse the session ID from the URL // (if any). Need to do this before we redirect in case we need to // include the session id in the redirect String sessionID; if (request .getServletContext() .getEffectiveSessionTrackingModes() .contains(SessionTrackingMode.URL)) { // Get the session ID if there was one sessionID = request.getPathParameter(SessionConfig.getSessionUriParamName(request.getContext())); if (sessionID != null) { request.setRequestedSessionId(sessionID); request.setRequestedSessionURL(true); } } // Look for session ID in cookies and SSL session parseSessionCookiesId(req, request); parseSessionSslId(request); sessionID = request.getRequestedSessionId(); mapRequired = false; if (version != null && request.getContext() == versionContext) { // We got the version that we asked for. That is it. } else { version = null; versionContext = null; Object[] contexts = request.getMappingData().contexts; // Single contextVersion means no need to remap // No session ID means no possibility of remap if (contexts != null && sessionID != null) { // Find the context associated with the session for (int i = (contexts.length); i > 0; i--) { Context ctxt = (Context) contexts[i - 1]; if (ctxt.getManager().findSession(sessionID) != null) { // We found a context. Is it the one that has // already been mapped? if (!ctxt.equals(request.getMappingData().context)) { // Set version so second time through mapping // the correct context is found version = ctxt.getWebappVersion(); versionContext = ctxt; // Reset mapping request.getMappingData().recycle(); mapRequired = true; } break; } } } } if (!mapRequired && request.getContext().getPaused()) { // Found a matching context but it is paused. Mapping data will // be wrong since some Wrappers may not be registered at this // point. try { Thread.sleep(1000); } catch (InterruptedException e) { // Should never happen } // Reset mapping request.getMappingData().recycle(); mapRequired = true; } } // Possible redirect MessageBytes redirectPathMB = request.getMappingData().redirectPath; if (!redirectPathMB.isNull()) { String redirectPath = urlEncoder.encode(redirectPathMB.toString()); String query = request.getQueryString(); if (request.isRequestedSessionIdFromURL()) { // This is not optimal, but as this is not very common, it // shouldn't matter redirectPath = redirectPath + ";" + SessionConfig.getSessionUriParamName(request.getContext()) + "=" + request.getRequestedSessionId(); } if (query != null) { // This is not optimal, but as this is not very common, it // shouldn't matter redirectPath = redirectPath + "?" + query; } response.sendRedirect(redirectPath); request.getContext().logAccess(request, response, 0, true); return false; } // Filter trace method if (!connector.getAllowTrace() && req.method().equalsIgnoreCase("TRACE")) { Wrapper wrapper = request.getWrapper(); String header = null; if (wrapper != null) { String[] methods = wrapper.getServletMethods(); if (methods != null) { for (int i = 0; i < methods.length; i++) { if ("TRACE".equals(methods[i])) { continue; } if (header == null) { header = methods[i]; } else { header += ", " + methods[i]; } } } } res.setStatus(405); res.addHeader("Allow", header); res.setMessage("TRACE method is not allowed"); request.getContext().logAccess(request, response, 0, true); return false; } doConnectorAuthenticationAuthorization(req, request); return true; } private void doConnectorAuthenticationAuthorization( org.apache.coyote.Request req, Request request) { // Set the remote principal String username = req.getRemoteUser().toString(); if (username != null) { if (log.isDebugEnabled()) { log.debug(sm.getString("coyoteAdapter.authenticate", username)); } if (req.getRemoteUserNeedsAuthorization()) { Authenticator authenticator = request.getContext().getAuthenticator(); if (authenticator == null) { // No security constraints configured for the application so // no need to authorize the user. Use the CoyotePrincipal to // provide the authenticated user. request.setUserPrincipal(new CoyotePrincipal(username)); } else if (!(authenticator instanceof AuthenticatorBase)) { if (log.isDebugEnabled()) { log.debug(sm.getString("coyoteAdapter.authorize", username)); } // Custom authenticator that may not trigger authorization. // Do the authorization here to make sure it is done. request.setUserPrincipal(request.getContext().getRealm().authenticate(username)); } // If the Authenticator is an instance of AuthenticatorBase then // it will check req.getRemoteUserNeedsAuthorization() and // trigger authorization as necessary. It will also cache the // result preventing excessive calls to the Realm. } else { // The connector isn't configured for authorization. Create a // user without any roles using the supplied user name. request.setUserPrincipal(new CoyotePrincipal(username)); } } // Set the authorization type String authtype = req.getAuthType().toString(); if (authtype != null) { request.setAuthType(authtype); } } /** * Extract the path parameters from the request. This assumes parameters are of the form * /path;name=value;name2=value2/ etc. Currently only really interested in the session ID that * will be in this form. Other parameters can safely be ignored. * * @param req * @param request */ protected void parsePathParameters(org.apache.coyote.Request req, Request request) { // Process in bytes (this is default format so this is normally a NO-OP req.decodedURI().toBytes(); ByteChunk uriBC = req.decodedURI().getByteChunk(); int semicolon = uriBC.indexOf(';', 0); // What encoding to use? Some platforms, eg z/os, use a default // encoding that doesn't give the expected result so be explicit String enc = connector.getURIEncoding(); if (enc == null) { enc = "ISO-8859-1"; } Charset charset = null; try { charset = B2CConverter.getCharset(enc); } catch (UnsupportedEncodingException e1) { log.warn(sm.getString("coyoteAdapter.parsePathParam", enc)); } if (log.isDebugEnabled()) { log.debug(sm.getString("coyoteAdapter.debug", "uriBC", uriBC.toString())); log.debug(sm.getString("coyoteAdapter.debug", "semicolon", String.valueOf(semicolon))); log.debug(sm.getString("coyoteAdapter.debug", "enc", enc)); } while (semicolon > -1) { // Parse path param, and extract it from the decoded request URI int start = uriBC.getStart(); int end = uriBC.getEnd(); int pathParamStart = semicolon + 1; int pathParamEnd = ByteChunk.findBytes( uriBC.getBuffer(), start + pathParamStart, end, new byte[] {';', '/'}); String pv = null; if (pathParamEnd >= 0) { if (charset != null) { pv = new String( uriBC.getBuffer(), start + pathParamStart, pathParamEnd - pathParamStart, charset); } // Extract path param from decoded request URI byte[] buf = uriBC.getBuffer(); for (int i = 0; i < end - start - pathParamEnd; i++) { buf[start + semicolon + i] = buf[start + i + pathParamEnd]; } uriBC.setBytes(buf, start, end - start - pathParamEnd + semicolon); } else { if (charset != null) { pv = new String( uriBC.getBuffer(), start + pathParamStart, (end - start) - pathParamStart, charset); } uriBC.setEnd(start + semicolon); } if (log.isDebugEnabled()) { log.debug( sm.getString("coyoteAdapter.debug", "pathParamStart", String.valueOf(pathParamStart))); log.debug( sm.getString("coyoteAdapter.debug", "pathParamEnd", String.valueOf(pathParamEnd))); log.debug(sm.getString("coyoteAdapter.debug", "pv", pv)); } if (pv != null) { int equals = pv.indexOf('='); if (equals > -1) { String name = pv.substring(0, equals); String value = pv.substring(equals + 1); request.addPathParameter(name, value); if (log.isDebugEnabled()) { log.debug(sm.getString("coyoteAdapter.debug", "equals", String.valueOf(equals))); log.debug(sm.getString("coyoteAdapter.debug", "name", name)); log.debug(sm.getString("coyoteAdapter.debug", "value", value)); } } } semicolon = uriBC.indexOf(';', semicolon); } } /** * Look for SSL session ID if required. Only look for SSL Session ID if it is the only tracking * method enabled. */ protected void parseSessionSslId(Request request) { if (request.getRequestedSessionId() == null && SSL_ONLY.equals(request.getServletContext().getEffectiveSessionTrackingModes()) && request.connector.secure) { // TODO Is there a better way to map SSL sessions to our sesison ID? // TODO The request.getAttribute() will cause a number of other SSL // attribute to be populated. Is this a performance concern? request.setRequestedSessionId(request.getAttribute(SSLSupport.SESSION_ID_KEY).toString()); request.setRequestedSessionSSL(true); } } /** Parse session id in URL. */ protected void parseSessionCookiesId(org.apache.coyote.Request req, Request request) { // If session tracking via cookies has been disabled for the current // context, don't go looking for a session ID in a cookie as a cookie // from a parent context with a session ID may be present which would // overwrite the valid session ID encoded in the URL Context context = (Context) request.getMappingData().context; if (context != null && !context .getServletContext() .getEffectiveSessionTrackingModes() .contains(SessionTrackingMode.COOKIE)) { return; } // Parse session id from cookies Cookies serverCookies = req.getCookies(); int count = serverCookies.getCookieCount(); if (count <= 0) { return; } String sessionCookieName = SessionConfig.getSessionCookieName(context); for (int i = 0; i < count; i++) { ServerCookie scookie = serverCookies.getCookie(i); if (scookie.getName().equals(sessionCookieName)) { // Override anything requested in the URL if (!request.isRequestedSessionIdFromCookie()) { // Accept only the first session id cookie convertMB(scookie.getValue()); request.setRequestedSessionId(scookie.getValue().toString()); request.setRequestedSessionCookie(true); request.setRequestedSessionURL(false); if (log.isDebugEnabled()) { log.debug(" Requested cookie session id is " + request.getRequestedSessionId()); } } else { if (!request.isRequestedSessionIdValid()) { // Replace the session id until one is valid convertMB(scookie.getValue()); request.setRequestedSessionId(scookie.getValue().toString()); } } } } } /** Character conversion of the URI. */ protected void convertURI(MessageBytes uri, Request request) throws Exception { ByteChunk bc = uri.getByteChunk(); int length = bc.getLength(); CharChunk cc = uri.getCharChunk(); cc.allocate(length, -1); String enc = connector.getURIEncoding(); if (enc != null) { B2CConverter conv = request.getURIConverter(); try { if (conv == null) { conv = new B2CConverter(enc, true); request.setURIConverter(conv); } else { conv.recycle(); } } catch (IOException e) { log.error("Invalid URI encoding; using HTTP default"); connector.setURIEncoding(null); } if (conv != null) { try { conv.convert(bc, cc, true); uri.setChars(cc.getBuffer(), cc.getStart(), cc.getLength()); return; } catch (IOException ioe) { // Should never happen as B2CConverter should replace // problematic characters request.getResponse().sendError(HttpServletResponse.SC_BAD_REQUEST); } } } // Default encoding: fast conversion for ISO-8859-1 byte[] bbuf = bc.getBuffer(); char[] cbuf = cc.getBuffer(); int start = bc.getStart(); for (int i = 0; i < length; i++) { cbuf[i] = (char) (bbuf[i + start] & 0xff); } uri.setChars(cbuf, 0, length); } /** Character conversion of the a US-ASCII MessageBytes. */ protected void convertMB(MessageBytes mb) { // This is of course only meaningful for bytes if (mb.getType() != MessageBytes.T_BYTES) { return; } ByteChunk bc = mb.getByteChunk(); CharChunk cc = mb.getCharChunk(); int length = bc.getLength(); cc.allocate(length, -1); // Default encoding: fast conversion byte[] bbuf = bc.getBuffer(); char[] cbuf = cc.getBuffer(); int start = bc.getStart(); for (int i = 0; i < length; i++) { cbuf[i] = (char) (bbuf[i + start] & 0xff); } mb.setChars(cbuf, 0, length); } /** * Normalize URI. * * <p>This method normalizes "\", "//", "/./" and "/../". This method will return false when * trying to go above the root, or if the URI contains a null byte. * * @param uriMB URI to be normalized */ public static boolean normalize(MessageBytes uriMB) { ByteChunk uriBC = uriMB.getByteChunk(); final byte[] b = uriBC.getBytes(); final int start = uriBC.getStart(); int end = uriBC.getEnd(); // An empty URL is not acceptable if (start == end) { return false; } // URL * is acceptable if ((end - start == 1) && b[start] == (byte) '*') { return true; } int pos = 0; int index = 0; // Replace '\' with '/' // Check for null byte for (pos = start; pos < end; pos++) { if (b[pos] == (byte) '\\') { if (ALLOW_BACKSLASH) { b[pos] = (byte) '/'; } else { return false; } } if (b[pos] == (byte) 0) { return false; } } // The URL must start with '/' if (b[start] != (byte) '/') { return false; } // Replace "//" with "/" for (pos = start; pos < (end - 1); pos++) { if (b[pos] == (byte) '/') { while ((pos + 1 < end) && (b[pos + 1] == (byte) '/')) { copyBytes(b, pos, pos + 1, end - pos - 1); end--; } } } // If the URI ends with "/." or "/..", then we append an extra "/" // Note: It is possible to extend the URI by 1 without any side effect // as the next character is a non-significant WS. if (((end - start) >= 2) && (b[end - 1] == (byte) '.')) { if ((b[end - 2] == (byte) '/') || ((b[end - 2] == (byte) '.') && (b[end - 3] == (byte) '/'))) { b[end] = (byte) '/'; end++; } } uriBC.setEnd(end); index = 0; // Resolve occurrences of "/./" in the normalized path while (true) { index = uriBC.indexOf("/./", 0, 3, index); if (index < 0) { break; } copyBytes(b, start + index, start + index + 2, end - start - index - 2); end = end - 2; uriBC.setEnd(end); } index = 0; // Resolve occurrences of "/../" in the normalized path while (true) { index = uriBC.indexOf("/../", 0, 4, index); if (index < 0) { break; } // Prevent from going outside our context if (index == 0) { return false; } int index2 = -1; for (pos = start + index - 1; (pos >= 0) && (index2 < 0); pos--) { if (b[pos] == (byte) '/') { index2 = pos; } } copyBytes(b, start + index2, start + index + 3, end - start - index - 3); end = end + index2 - index - 3; uriBC.setEnd(end); index = index2; } return true; } /** * Check that the URI is normalized following character decoding. * * <p>This method checks for "\", 0, "//", "/./" and "/../". This method will return false if * sequences that are supposed to be normalized are still present in the URI. * * @param uriMB URI to be checked (should be chars) */ public static boolean checkNormalize(MessageBytes uriMB) { CharChunk uriCC = uriMB.getCharChunk(); char[] c = uriCC.getChars(); int start = uriCC.getStart(); int end = uriCC.getEnd(); int pos = 0; // Check for '\' and 0 for (pos = start; pos < end; pos++) { if (c[pos] == '\\') { return false; } if (c[pos] == 0) { return false; } } // Check for "//" for (pos = start; pos < (end - 1); pos++) { if (c[pos] == '/') { if (c[pos + 1] == '/') { return false; } } } // Check for ending with "/." or "/.." if (((end - start) >= 2) && (c[end - 1] == '.')) { if ((c[end - 2] == '/') || ((c[end - 2] == '.') && (c[end - 3] == '/'))) { return false; } } // Check for "/./" if (uriCC.indexOf("/./", 0, 3, 0) >= 0) { return false; } // Check for "/../" if (uriCC.indexOf("/../", 0, 4, 0) >= 0) { return false; } return true; } // ------------------------------------------------------ Protected Methods /** Copy an array of bytes to a different position. Used during normalization. */ protected static void copyBytes(byte[] b, int dest, int src, int len) { for (int pos = 0; pos < len; pos++) { b[pos + dest] = b[pos + src]; } } }