/**
 * 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);
  }
}
Exemple #7
0
/** 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);
    }
  }
}
Exemple #8
0
/** 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;
  }
}
Exemple #11
0
/**
 * 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>
   *    &lt;Environment name="aparam"
   *                 value="test"
   *                 type="java.lang.String"
   *                 override="false"/&gt;
   * </pre>
   *
   * A programmatic way to add this environment to the embedded Tomcat is by calling this method
   *
   * <pre>
   * ContextEnvironment env = new ContextEnvironment();
   * env.setType(&quot;java.lang.String&quot;);
   * env.setName(&quot;aparam&quot;);
   * env.setValue(&quot;test&quot;);
   * 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>
   *  &lt;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"/&gt;
   * </pre>
   *
   * Programmatic way:
   *
   * <pre>
   * ContextResource res = new ContextResource();
   * res.setName(&quot;jdbc/ds&quot;);
   * res.setType(&quot;javax.sql.DataSource&quot;);
   * res.setAuth(&quot;Container&quot;);
   * res.setProperty(&quot;username&quot;, &quot;sa&quot;);
   * res.setProperty(&quot;password&quot;, &quot;&quot;);
   * res.setProperty(&quot;driverClassName&quot;, &quot;org.h2.Driver&quot;);
   * res.setProperty(&quot;url&quot;, &quot;jdbc:h2:&tilde;/mydb&quot;);
   * res.setProperty(&quot;maxActive&quot;, &quot;20&quot;);
   * res.setProperty(&quot;maxIdle&quot;, &quot;4&quot;);
   * res.setProperty(&quot;maxWait&quot;, &quot;10000&quot;);
   * res.setProperty(&quot;defaultAutoCommit&quot;, &quot;false&quot;);
   *
   * 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>
   *    &lt;Environment name="aparam"
   *                 value="test"
   *                 type="java.lang.String"
   *                 override="false"/&gt;
   * </pre>
   *
   * Programmatic way:
   *
   * <pre>
   * embeddedTomcat.addContextEnvironment(&quot;aparam&quot;, &quot;test&quot;, &quot;java.lang.String&quot;);
   * </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(&quot;aparam&quot;, &quot;test&quot;);
   * </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);
    }
  }
}
Exemple #15
0
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>&lt;ContextManager&gt;</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);
    }
  }
}
Exemple #20
0
/**
 * 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>
 * &lt;IfModule ssl_module&gt;
 *   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"
 * &lt;/IfModule&gt;
 * </pre>
 *
 * In server.xml, configure this valve under the Engine element in server.xml:
 *
 * <pre>
 * &lt;Engine ...&gt;
 *   &lt;Valve className="org.apache.catalina.valves.SSLValve" /&gt;
 *   &lt;Host ... /&gt;
 * &lt;/Engine&gt;
 * </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;
  }
}
Exemple #30
0
/**
 * 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];
    }
  }
}