You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by tr...@apache.org on 2007/10/23 04:29:48 UTC

svn commit: r587364 [5/7] - in /mina/sandbox/asyncweb: ./ assembly/ assembly/src/ assembly/src/main/ assembly/src/main/descriptor/ core/ core/src/ core/src/main/ core/src/main/java/ core/src/main/java/org/ core/src/main/java/org/safehaus/ core/src/main...

Added: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/DefaultSessionAccessor.java
URL: http://svn.apache.org/viewvc/mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/DefaultSessionAccessor.java?rev=587364&view=auto
==============================================================================
--- mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/DefaultSessionAccessor.java (added)
+++ mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/DefaultSessionAccessor.java Mon Oct 22 19:29:38 2007
@@ -0,0 +1,155 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.safehaus.asyncweb.service.session;
+
+import org.safehaus.asyncweb.common.MutableHttpResponse;
+import org.safehaus.asyncweb.service.HttpServiceContext;
+import org.safehaus.asyncweb.service.HttpSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A simple <code>SessionAccessor</code> implementation which acts as a facade 
+ * to an employed <code>SessionIdentifier</code>, <code>SessionKeyFactory</code>
+ * and <code>SessionStore</code>.<br/>
+ * 
+ * A Default identifier and key factory is employed by this accessor, but the
+ * implementations used can be switched (if required) using the appropriate
+ * setter methods.<br/>
+ * 
+ * @author irvingd
+ *
+ */
+public class DefaultSessionAccessor implements HttpSessionAccessor {
+
+  private static final Logger LOG = LoggerFactory.getLogger(DefaultSessionAccessor.class);
+  
+  private HttpSessionIdentifier identifier = new CookieIdentifier();
+  private HttpSessionKeyFactory keyFactory;
+  private HttpSessionStore      store;
+  
+  /**
+   * Constructs with the default identifier and key factory
+   */
+  public DefaultSessionAccessor() {
+    SecureRandomKeyFactory secureKeyFactory = new SecureRandomKeyFactory();
+    secureKeyFactory.start();
+    keyFactory = secureKeyFactory;
+  }
+  
+  public HttpSession getSession(HttpServiceContext context, boolean create) {
+    String sessionKey = identifier.getSessionKey(context.getRequest());
+    HttpSession session = null;
+    if (sessionKey != null) {
+      LOG.debug("Request contains session key - attempting to lookup");
+      session = store.locateSession(sessionKey);
+      if (session == null) {
+        LOG.debug("No session found with request's session key");
+      }
+    } 
+    if (session == null && create) {
+      LOG.debug("No existing session found for request - creating new session");
+      session = createNewSession();
+    }
+    return session;
+  }
+  
+  public void addSessionIdentifier(HttpServiceContext context, MutableHttpResponse response) {
+    HttpSession session = context.getSession(false);
+    if (session == null) {
+      return;
+    }
+    identifier.addSessionKey(session.getId(), response);
+  }
+
+  /**
+   * Sets the <code>SessionIdentifier</code> used for encoding / decoding
+   * session keys to / from requests.
+   * By default, a <code>CookieIdentifier</code> is employed
+   * 
+   * @param identifier  The identifier to be employed
+   */
+  public void setSessionIdentifier(HttpSessionIdentifier identifier) {
+    this.identifier = identifier;
+  }
+  
+  /**
+   * Sets the <code>SessionKeyFactory</code> employed by this accessor for
+   * creating new session keys.
+   * By default, a <code>SecureRandomKeyFactory</code> is employed
+   * 
+   * @param keyFactory  The key factory to be employed
+   */
+  public void setSessionKeyFactory(HttpSessionKeyFactory keyFactory) {
+    this.keyFactory = keyFactory;
+  }
+  
+  /**
+   * Sets the session store employed by this accessor
+   * 
+   * @param store  The store
+   */
+  public void setSessionStore(HttpSessionStore store) {
+    if (this.store != null) {
+      this.store.close();
+    }
+    this.store = store;
+  }
+  
+  /**
+   * Disposes of this accessor. We simply close our store
+   */
+  public void dispose() {
+    store.close();
+  }
+  
+  /**
+   * Initialises this accessor. If we have not been configured with a session store,
+   * we simply employ a <code>BasicSessionStore</code> with a default time out
+   */
+  public void init() {
+    if (store == null) {
+      LOG.info("No session store configured. Employing default session store");
+      store = new BasicSessionStore();
+    }
+  }
+  
+  /**
+   * Establishes a new session with the specified response.
+   * We employ our <code>SessionKeyFactory</code> to generate a new session
+   * key, and request our <code>SessionStore</code> to create a session based
+   * on this id.<br/>
+   * Our key factory is designed to not provide duplicate keys, but if this occurs
+   * we cycle until an unused key is located.
+   */
+  private HttpSession createNewSession() {
+    HttpSession session;
+    String sessionKey;
+    do {
+      sessionKey = keyFactory.createSessionKey();
+      session    = store.createSession(sessionKey);
+      if (session == null) {
+        LOG.warn("SessionKeyFactory is providing duplicate keys!!");
+      }
+    } while (session == null);
+    LOG.debug("New session created");
+    return session;
+  }
+}

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/DefaultSessionAccessor.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/DefaultSessionAccessor.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionAccessor.java
URL: http://svn.apache.org/viewvc/mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionAccessor.java?rev=587364&view=auto
==============================================================================
--- mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionAccessor.java (added)
+++ mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionAccessor.java Mon Oct 22 19:29:38 2007
@@ -0,0 +1,69 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.safehaus.asyncweb.service.session;
+
+import org.safehaus.asyncweb.common.MutableHttpResponse;
+import org.safehaus.asyncweb.service.HttpServiceContext;
+import org.safehaus.asyncweb.service.HttpSession;
+
+
+/**
+ * A facade through which <code>Session</code>s are created and accessed.
+ * A <code>SessionAccessor</code> provides a simple interface for accessing
+ * the Session associated with a request (and optionally creating it if required)
+ * 
+ * @author irvingd
+ *
+ */
+public interface HttpSessionAccessor {
+
+  /**
+   * Attempts to locate the session associated with the specified context.
+   * If no session can be located based on the request, and <code>create</code>
+   * is <code>true</code>, a new session is created and bound against the request.
+   * Otherwise, if a session is not currently bound against the specified request
+   * and <code>create</code> is false, this method returns <code>null</code>
+   * 
+   * @param context  The context for which a session is required
+   * @param create   If this parameter is <code>true</code> and no session can
+   *                 be found for the given request, a new session is created
+   *                 and bound against the request
+   * @return         The located / created session - <code>null</code> if an
+   *                 existing session can not be located and <code>create</code>
+   *                 is <code>false</code>
+   */
+  public HttpSession getSession(HttpServiceContext context, boolean create);
+  
+  /**
+   * Adds session identifier to the specified response.
+   */
+  public void addSessionIdentifier(HttpServiceContext context, MutableHttpResponse response);
+  
+  /**
+   * Prepares this accessor for use
+   */
+  public void init();
+  
+  /**
+   * Disposes of this accessor - freeing all session resources
+   */
+  public void dispose();
+    
+}

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionAccessor.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionAccessor.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionIdentifier.java
URL: http://svn.apache.org/viewvc/mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionIdentifier.java?rev=587364&view=auto
==============================================================================
--- mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionIdentifier.java (added)
+++ mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionIdentifier.java Mon Oct 22 19:29:38 2007
@@ -0,0 +1,51 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.safehaus.asyncweb.service.session;
+
+import org.safehaus.asyncweb.common.HttpRequest;
+import org.safehaus.asyncweb.common.MutableHttpResponse;
+
+
+/**
+ * A strategy for encoding / decoding session keys information to / from
+ * requests.
+ * 
+ * @author irvingd
+ *
+ */
+public interface HttpSessionIdentifier {
+
+  /**
+   * Attempts to extract a session key from a specified request.
+   *
+   * @param request  The request from which to extract a session key
+   * @return         The extracted key, or <code>null</code> if the request
+   *                 does not contain a session key
+   */
+  public String getSessionKey(HttpRequest request);
+  
+  /**
+   * Adds a session key to the specified response
+   * 
+   * @param key      The session key
+   * @param response  The response
+   */
+  public void addSessionKey(String key, MutableHttpResponse response);
+}

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionIdentifier.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionIdentifier.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionKeyFactory.java
URL: http://svn.apache.org/viewvc/mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionKeyFactory.java?rev=587364&view=auto
==============================================================================
--- mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionKeyFactory.java (added)
+++ mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionKeyFactory.java Mon Oct 22 19:29:38 2007
@@ -0,0 +1,45 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.safehaus.asyncweb.service.session;
+
+/**
+ * A factory for creating Session keys.
+ * <code>SessionKeyFactory</code> implementations should make a best effort to:
+ * <ul>
+ *   <li>Avoid the generation of two keys <i>k(1), k(2)</i> 
+ *       such that <i>k(1) == k(2)</i></li>
+ *   <li>Avoid the generation of a key <i>k</i> such that an adversary can
+ *       make an informed guess of the content of any other key created by this factory
+ *       at any time in the future</li>
+ * </ul>
+ *  
+ * @author irvingd
+ *
+ */
+public interface HttpSessionKeyFactory {
+
+  /**
+   * Returns a new session key String
+   * 
+   * @return  The session key
+   */
+  public String createSessionKey();
+  
+}

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionKeyFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionKeyFactory.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionListener.java
URL: http://svn.apache.org/viewvc/mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionListener.java?rev=587364&view=auto
==============================================================================
--- mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionListener.java (added)
+++ mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionListener.java Mon Oct 22 19:29:38 2007
@@ -0,0 +1,53 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.safehaus.asyncweb.service.session;
+
+import org.safehaus.asyncweb.service.HttpSession;
+
+/**
+ * Receives notifications of session lifecycle events
+ * 
+ * @author irvingd
+ *
+ */
+public interface HttpSessionListener {
+
+  /**
+   * Invoked when a new session is created
+   * 
+   * @param session  The created session
+   */
+  public void sessionCreated(HttpSession session);
+  
+  /**
+   * Invoked when a session is destroyed before it expires
+   * 
+   * @param session  The destroyed session
+   */
+  public void sessionDestroyed(HttpSession session);
+  
+  /**
+   * Invoked when a session expires before being manually destroyed
+   * 
+   * @param session  The expired session
+   */
+  public void sessionExpired(HttpSession session);
+  
+}

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionListener.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionListener.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionStore.java
URL: http://svn.apache.org/viewvc/mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionStore.java?rev=587364&view=auto
==============================================================================
--- mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionStore.java (added)
+++ mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionStore.java Mon Oct 22 19:29:38 2007
@@ -0,0 +1,67 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.safehaus.asyncweb.service.session;
+
+import org.safehaus.asyncweb.service.HttpSession;
+
+
+/**
+ * Creates and maintains <code>Session</code>s.
+ * 
+ * @author irvingd
+ *
+ */
+public interface HttpSessionStore {
+
+  /**
+   * Adds a listener to this <code>SessionStore</code>
+   * 
+   * @param listener  The listener to be added
+   */
+  public void addSessionListener(HttpSessionListener listener);
+  
+  /**
+   * Closes this store, releasing any resources it may be consuming
+   */
+  public void close();
+  
+  /**
+   * Creates a new session with a specified key.
+   * If the specified key is already in use, this method returns <code>null</code>
+   * to indicate that an alternative key should be used
+   * 
+   * @param key  The session key for the new session
+   * @return     The created session, or <code>null</code> if the supplied key
+   *             is already in use
+   */
+  public HttpSession createSession(String key);
+  
+  /**
+   * Locates an existing session with the specified key.
+   * Any store which employs session time-outs should perform the appropriate 
+   * action to mark the session as recently used before returning it.<br/>
+   * 
+   * @param key  The key for which a session is required
+   * @return     The session, or <code>null</code> if no session was found with
+   *             the specified key
+   */
+  public HttpSession locateSession(String key);
+  
+}

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionStore.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/HttpSessionStore.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/LoggingSessionListener.java
URL: http://svn.apache.org/viewvc/mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/LoggingSessionListener.java?rev=587364&view=auto
==============================================================================
--- mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/LoggingSessionListener.java (added)
+++ mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/LoggingSessionListener.java Mon Oct 22 19:29:38 2007
@@ -0,0 +1,54 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.safehaus.asyncweb.service.session;
+
+import org.safehaus.asyncweb.service.HttpSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A simple <code>SessionListener</code> which logs the various lifecycle events
+ * 
+ * @author irvingd
+ *
+ */
+public class LoggingSessionListener implements HttpSessionListener {
+
+  private static final Logger LOG = LoggerFactory.getLogger(LoggingSessionListener.class);
+  
+  public void sessionCreated(HttpSession session) {
+    doLog("New Session Created", session);
+  }
+
+  public void sessionDestroyed(HttpSession session) {
+    doLog("Session Destroyed", session);
+  }
+
+  public void sessionExpired(HttpSession session) {
+    doLog("Session Expired", session);
+  }
+
+  private void doLog(String msg, HttpSession session) {
+    if (LOG.isInfoEnabled()) {
+      LOG.info(msg + " [SessionId = " + session.getId() + "]");
+    }
+  }
+  
+}

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/LoggingSessionListener.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/LoggingSessionListener.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/SecureRandomKeyFactory.java
URL: http://svn.apache.org/viewvc/mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/SecureRandomKeyFactory.java?rev=587364&view=auto
==============================================================================
--- mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/SecureRandomKeyFactory.java (added)
+++ mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/SecureRandomKeyFactory.java Mon Oct 22 19:29:38 2007
@@ -0,0 +1,129 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.safehaus.asyncweb.service.session;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SecureRandomKeyFactory implements HttpSessionKeyFactory {
+
+  /**
+   * The default key length provided by this factory
+   */
+  private static final int DEFAULT_KEY_LENGTH = 16;
+  
+  /**
+   * The minimum key length supported by this factory
+   */
+  private static final int MINIMUM_KEY_LENGTH = 8;
+  
+  /**
+   * The default algorithm employed
+   */
+  private static final String DEFAULT_ALGORITHM = "SHA1PRNG";
+  
+  private static final Logger LOG = LoggerFactory.getLogger(SecureRandomKeyFactory.class);
+  
+  private SecureRandom secureRandom;
+  private int keyLength = DEFAULT_KEY_LENGTH;
+  private volatile boolean isStarted;
+  private String algorithm = DEFAULT_ALGORITHM;
+  
+  /**
+   * Creates a session key based on bytes provided from an underlying
+   * <code>SecureRandom</code>
+   * 
+   * @return The created key
+   */
+  public String createSessionKey() {
+    byte[] keyBytes = new byte[keyLength];
+    synchronized (secureRandom) {
+      secureRandom.nextBytes(keyBytes);
+    }
+    return bytesToSessionKey(keyBytes);
+  }
+
+  /**
+   * Sets the number of <i>bytes</i> used when forming keys created by this factory.
+   * A hex encoding is applied to the generated key bytes such that the number of
+   * <i>characters</i> in keys created by this factory is twice the key length
+   * 
+   * @param keyLength  The number of bytes employed in keys created by this factory
+   */
+  public void setKeyLength(int keyLength) {
+    if (keyLength < MINIMUM_KEY_LENGTH) {
+      throw new IllegalArgumentException("Key length must be >= " + MINIMUM_KEY_LENGTH);
+    }
+    if (isStarted) { // sanity check
+      throw new IllegalStateException("Key factory started");
+    }
+    this.keyLength = keyLength;  
+  }
+  
+  /**
+   * Sets the algorithm employed by the underlying <code>SecureRandom</code>.
+   * The default is <code>SHA1PRNG</code>
+   * 
+   * @param algorithm The algorithm to be employed
+   */
+  public void setAlgorithm(String algorithm) {
+    this.algorithm = algorithm;
+  }
+  
+  /**
+   * Starts this factory.
+   */
+  public void start() {
+    isStarted = true;
+    LOG.info("Attempting to obtain SecureRandom using algorithim: " + algorithm);
+    try {
+      secureRandom = SecureRandom.getInstance(algorithm);
+      LOG.info("Ok - using algorithm: " + algorithm);
+    } catch (NoSuchAlgorithmException e) {
+      LOG.info("Failed to obtain secure random with algorithm: " + algorithm + 
+               ". Resorting to default");
+    }
+    secureRandom = new SecureRandom();
+    secureRandom.nextBytes(new byte[keyLength]); // seed
+  }
+
+  private static String bytesToSessionKey(byte[] bytes) {
+    char[] keyChars = new char[bytes.length * 2];
+    for (int i = 0; i < bytes.length; ++i) {
+      byte b1 = (byte) ((bytes[i] & 0xf0) >> 4);
+      byte b2 = (byte) (bytes[i] & 0x0f);
+      keyChars[2 * i] = toKeyStringChar(b1);
+      keyChars[(2 * i) + 1] = toKeyStringChar(b2);
+    }
+    return new String(keyChars);
+  }
+  
+  private static final char toKeyStringChar(byte b) {
+    if (b < 10) {
+      return (char) ('0' + b);
+    } else {
+      return (char) ('A' + (b - 10));
+    }
+  }
+  
+}

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/SecureRandomKeyFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/session/SecureRandomKeyFactory.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/DefaultHttpIoHandler.java
URL: http://svn.apache.org/viewvc/mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/DefaultHttpIoHandler.java?rev=587364&view=auto
==============================================================================
--- mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/DefaultHttpIoHandler.java (added)
+++ mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/DefaultHttpIoHandler.java Mon Oct 22 19:29:38 2007
@@ -0,0 +1,60 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.safehaus.asyncweb.service.transport.mina;
+
+import org.apache.mina.common.IoSession;
+import org.apache.mina.handler.multiton.SingleSessionIoHandler;
+import org.apache.mina.handler.multiton.SingleSessionIoHandlerDelegate;
+import org.apache.mina.handler.multiton.SingleSessionIoHandlerFactory;
+import org.safehaus.asyncweb.service.ServiceContainer;
+
+/**
+ * @author trustin
+ */
+public class DefaultHttpIoHandler extends SingleSessionIoHandlerDelegate implements HttpIoHandler {
+
+  public DefaultHttpIoHandler() {
+    super(new Factory());
+  }
+
+  public ServiceContainer getContainer() {
+    return ((Factory) getFactory()).getContainer();
+  }
+
+  public void setContainer(ServiceContainer container) {
+    ((Factory) getFactory()).setContainer(container);
+  }
+
+  private static class Factory implements SingleSessionIoHandlerFactory {
+    private ServiceContainer container;
+    
+    public ServiceContainer getContainer() {
+      return container;
+    }
+
+    public void setContainer(ServiceContainer container) {
+      this.container = container;
+    }
+
+    public SingleSessionIoHandler getHandler(IoSession session) {
+      return new SingleHttpSessionIoHandler(container, session);
+    }
+  }
+}

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/DefaultHttpIoHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/DefaultHttpIoHandler.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/HttpIoHandler.java
URL: http://svn.apache.org/viewvc/mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/HttpIoHandler.java?rev=587364&view=auto
==============================================================================
--- mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/HttpIoHandler.java (added)
+++ mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/HttpIoHandler.java Mon Oct 22 19:29:38 2007
@@ -0,0 +1,34 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.safehaus.asyncweb.service.transport.mina;
+
+import org.apache.mina.common.IoHandler;
+import org.safehaus.asyncweb.service.ServiceContainer;
+
+public interface HttpIoHandler extends IoHandler {
+  
+  /**
+   * Associates this handler with the container it should dispatch requests
+   * to
+   * 
+   * @param container  The associated container
+   */
+  void setContainer(ServiceContainer container);
+}

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/HttpIoHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/HttpIoHandler.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/LoggingExceptionMonitor.java
URL: http://svn.apache.org/viewvc/mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/LoggingExceptionMonitor.java?rev=587364&view=auto
==============================================================================
--- mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/LoggingExceptionMonitor.java (added)
+++ mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/LoggingExceptionMonitor.java Mon Oct 22 19:29:38 2007
@@ -0,0 +1,41 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.safehaus.asyncweb.service.transport.mina;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.mina.common.ExceptionMonitor;
+
+/**
+ * An <code>ExceptionMonitor</code> which simply logs exceptions.
+ * 
+ * @author irvingd
+ *
+ */
+class LoggingExceptionMonitor extends ExceptionMonitor {
+
+  private static final Logger LOG = LoggerFactory.getLogger(LoggingExceptionMonitor.class);
+  
+  public void exceptionCaught(Throwable e) {
+    if (LOG.isWarnEnabled()) {
+      LOG.warn("NIOTransport encountered exception.", e);
+    }
+  }
+}

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/LoggingExceptionMonitor.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/LoggingExceptionMonitor.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/MinaTransport.java
URL: http://svn.apache.org/viewvc/mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/MinaTransport.java?rev=587364&view=auto
==============================================================================
--- mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/MinaTransport.java (added)
+++ mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/MinaTransport.java Mon Oct 22 19:29:38 2007
@@ -0,0 +1,215 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.safehaus.asyncweb.service.transport.mina;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.mina.common.IoFilter;
+import org.apache.mina.filter.executor.ExecutorFilter;
+import org.apache.mina.filter.logging.LoggingFilter;
+import org.apache.mina.transport.socket.SocketSessionConfig;
+import org.apache.mina.transport.socket.nio.SocketAcceptor;
+import org.safehaus.asyncweb.service.ServiceContainer;
+import org.safehaus.asyncweb.service.Transport;
+import org.safehaus.asyncweb.service.TransportException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * A <code>Transport</code> implementation which receives requests and sends
+ * responses using non-blocking selector based IO.
+ * 
+ * @author irvingd
+ *
+ */
+public class MinaTransport implements Transport {
+
+  private static final Logger LOG = LoggerFactory.getLogger(MinaTransport.class);
+  
+  private static final int    DEFAULT_PORT = 9012;
+  private static final int    DEFAULT_IO_THREADS = Runtime.getRuntime().availableProcessors();
+  private static final int    DEFAULT_EVENT_THREADS = 16;
+    
+  private SocketAcceptor acceptor;
+  private ExecutorService ioExecutor;
+  private ExecutorService eventExecutor;
+  private int port = DEFAULT_PORT;
+  private String address = null;
+  private int ioThreads = DEFAULT_IO_THREADS;
+  private int eventThreads = DEFAULT_EVENT_THREADS;
+  private HttpIoHandler ioHandler;
+  private boolean isLoggingTraffic = false;
+  private ServiceContainer container;
+  
+  /**
+   * Sets the port this transport will listen on
+   * 
+   * @param port  The port
+   */
+  public void setPort(int port) {
+    this.port = port;
+  }
+
+  /**
+   * Sets the address this transport will listen on
+   * 
+   * @param address  The address to bind to.
+   *                 Specify <tt>null</tt> or <tt>"*"</tt> to listen to all
+   *                 NICs (Network Interface Cards).
+   */
+  public void setAddress(String address) {
+    if ("*".equals(address)) {
+      address = null;
+    }
+    this.address = address;
+  }
+  
+  public int getIoThreads() {
+    return ioThreads;
+  }
+    
+  /**
+   * Sets the number of worker threads employed by this transport.
+   * This should typically be a small number (2 is a good choice) - 
+   * and is not tied to the number of concurrent connections you wish to
+   * support
+   * 
+   * @param ioThreads  The number of worker threads to employ
+   */
+  public void setIoThreads(int ioThreads) {
+    this.ioThreads = ioThreads;
+  }
+  
+  public int getEventThreads() {
+    return eventThreads;
+  }
+
+  public void setEventThreads(int eventThreads) {
+    this.eventThreads = eventThreads;
+  }
+
+  /**
+   * Sets whether traffic received through this transport is
+   * logged (off by default)
+   * 
+   * @param isLoggingTraffic  <code>true</code> iff traffic should be logged
+   */
+  public void setIsLoggingTraffic(boolean isLoggingTraffic) {
+    this.isLoggingTraffic = isLoggingTraffic;
+  }
+  
+  /**
+   * Sets the <code>ServiceContainer</code> to which we issue requests
+   * 
+   * @param container  Our associated <code>ServiceContainer</code>
+   */
+  public void setServiceContainer(ServiceContainer container) {
+    this.container = container;
+  }
+
+  /**
+   * Sets the <code>HttpIOHandler</code> to be employed by this transport
+   * 
+   * @param httpIOHandler  The handler to be employed by this transport
+   */
+  public void setIoHandler(HttpIoHandler httpIOHandler) {
+    this.ioHandler = httpIOHandler;
+  }
+  
+  /**
+   * Starts this transport
+   * 
+   * @throws TransportException  If the transport can not be started
+   */
+  public void start() throws TransportException {
+    initIOHandler();
+    ioExecutor = new ThreadPoolExecutor(
+        ioThreads + 1, ioThreads + 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>() );
+    eventExecutor = new ThreadPoolExecutor(
+        eventThreads + 1, eventThreads + 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>() );
+    acceptor = new SocketAcceptor(ioThreads, ioExecutor);
+
+    try {
+      acceptor.getFilterChain().addLast(
+              "threadPool",
+              new ExecutorFilter(eventExecutor));
+      acceptor.setReuseAddress(true);
+      ((SocketSessionConfig) acceptor.getSessionConfig()).setReuseAddress(true);
+      if (isLoggingTraffic) {
+        LOG.info("Configuring traffic logging filter");
+        IoFilter filter = new LoggingFilter();
+        acceptor.getFilterChain().addFirst("LoggingFilter", filter);
+      }
+      acceptor.setBacklog(100);
+      
+      if (address != null)
+	      acceptor.setLocalAddress(new InetSocketAddress(address,port));
+      else
+	      acceptor.setLocalAddress(new InetSocketAddress(port));
+      acceptor.setHandler(ioHandler);
+      
+      acceptor.bind();
+
+      LOG.info("NIO HTTP Transport bound on port " + port);
+    } catch (IOException e) { 
+      throw new TransportException("NIOTransport Failed to bind to port " + port, e);
+    }
+  }
+  
+  /**
+   * Stops this transport
+   */
+  public void stop() throws TransportException {
+    acceptor.unbind();
+    ioExecutor.shutdown();
+    eventExecutor.shutdown();
+  }
+  
+  /**
+   * @return A string representation of this transport
+   */
+  public String toString() {
+    return "NIOTransport [port=" + port + "]";
+  }
+    
+  /**
+   * Initialises our handler - creating a new (default) handler if none has
+   * been specified
+   * 
+   * @throws IllegalStateException If we have not yet been associated with a
+   *                               container
+   */
+  private void initIOHandler() {
+    if (ioHandler == null) {
+      LOG.info("No http IO Handler associated - using defaults");
+      ioHandler = new DefaultHttpIoHandler();
+    }
+    if (container == null) {
+      throw new IllegalStateException("Transport not associated with a container");
+    }
+    ioHandler.setContainer(container);
+  }
+}

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/MinaTransport.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/MinaTransport.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/SingleHttpSessionIoHandler.java
URL: http://svn.apache.org/viewvc/mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/SingleHttpSessionIoHandler.java?rev=587364&view=auto
==============================================================================
--- mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/SingleHttpSessionIoHandler.java (added)
+++ mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/SingleHttpSessionIoHandler.java Mon Oct 22 19:29:38 2007
@@ -0,0 +1,375 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.safehaus.asyncweb.service.transport.mina;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+import org.apache.mina.common.DefaultWriteRequest;
+import org.apache.mina.common.IdleStatus;
+import org.apache.mina.common.IoFilterAdapter;
+import org.apache.mina.common.IoFutureListener;
+import org.apache.mina.common.IoSession;
+import org.apache.mina.common.WriteFuture;
+import org.apache.mina.common.WriteRequest;
+import org.apache.mina.filter.codec.ProtocolCodecFilter;
+import org.apache.mina.filter.codec.ProtocolDecoderException;
+import org.apache.mina.handler.multiton.SingleSessionIoHandler;
+import org.safehaus.asyncweb.codec.HttpServerCodecFactory;
+import org.safehaus.asyncweb.codec.decoder.HttpDecoderException;
+import org.safehaus.asyncweb.common.DefaultHttpRequest;
+import org.safehaus.asyncweb.common.DefaultHttpResponse;
+import org.safehaus.asyncweb.common.HttpRequest;
+import org.safehaus.asyncweb.common.HttpResponseStatus;
+import org.safehaus.asyncweb.common.HttpVersion;
+import org.safehaus.asyncweb.common.MutableHttpResponse;
+import org.safehaus.asyncweb.service.HttpServiceContext;
+import org.safehaus.asyncweb.service.HttpServiceFilter;
+import org.safehaus.asyncweb.service.ServiceContainer;
+import org.safehaus.asyncweb.service.context.AbstractHttpServiceContext;
+import org.safehaus.asyncweb.service.pipeline.RequestPipeline;
+import org.safehaus.asyncweb.service.pipeline.RequestPipelineListener;
+import org.safehaus.asyncweb.service.pipeline.StandardRequestPipeline;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+class SingleHttpSessionIoHandler implements SingleSessionIoHandler {
+
+  private static final Logger LOG = LoggerFactory.getLogger(SingleHttpSessionIoHandler.class);
+  
+  /**
+   * The number of parsers we pre-allocate
+   */
+//  private static final int DEFAULT_PARSERS = 5;
+  
+  /**
+   * The default idle time
+   */
+  private static final int DEFAULT_IDLE_TIME = 30000;
+  
+  /**
+   * Out default pipeline
+   */
+  private static final int DEFAULT_PIPELINE = 100;
+  
+  private final ServiceContainer container;
+  private final IoSession session;
+  private final RequestPipeline pipeline;
+
+  private HttpServiceContext currentContext;
+  private int readIdleTime  = DEFAULT_IDLE_TIME;
+  
+  SingleHttpSessionIoHandler(ServiceContainer container, IoSession session) {
+    this.container = container;
+    this.session = session;
+    this.pipeline = new StandardRequestPipeline(DEFAULT_PIPELINE);
+    
+    session.getConfig().setIdleTime(IdleStatus.READER_IDLE, readIdleTime);
+    
+    session.getFilterChain().addLast(
+        "codec", new ProtocolCodecFilter(new HttpServerCodecFactory()));
+    
+    session.getFilterChain().addLast(
+        "converter",
+        new ContextConverter());
+    
+    session.getFilterChain().addLast(
+        "pipeline",
+        new RequestPipelineAdapter(pipeline));
+
+    int i = 0;
+    for (HttpServiceFilter serviceFilter: container.getServiceFilters()) {
+      session.getFilterChain().addLast(
+          "serviceFilter." + (i++),
+          new ServiceFilterAdapter(serviceFilter));
+    }
+  }
+  
+  public void sessionCreated() {
+  }
+
+  public void sessionOpened() {
+    LOG.info("Connection opened");
+  }
+
+  public void sessionClosed() {
+    LOG.info("Connection closed");
+  }
+
+  /**
+   * Invoked when this connection idles out.
+   * If we are in the process of parsing a request, the current request
+   * is rejected with a {@link HttpResponseStatus#REQUEST_TIMEOUT} response status.
+   * 
+   */
+  public void sessionIdle(IdleStatus idleType) {
+    if (session.getIdleCount(idleType) == 1) {
+//      // FIXME currentRequest is always null now; we need to cooperate with a decoder.
+//      if (currentContext != null) {
+//        LOG.info("Read idled out while parsing request. Scheduling timeout response");
+//        handleReadFailure(currentContext, HttpResponseStatus.REQUEST_TIMEOUT, "Timeout while reading request");
+//      } else {
+        LOG.info("Idled with no current request. Scheduling closure when pipeline empties");
+        pipeline.runWhenEmpty(new Runnable() {
+          public void run() {
+            LOG.info("Pipeline empty after idle. Closing session");
+            session.close();
+          }
+        });    
+//      }
+    }
+  }
+
+  public void exceptionCaught(Throwable cause) {
+    MutableHttpResponse response = null;
+    if (cause instanceof ProtocolDecoderException) {
+      HttpResponseStatus status;
+      if (cause instanceof HttpDecoderException) {
+        status = ((HttpDecoderException) cause).getResponseStatus();
+      } else {
+        status = HttpResponseStatus.BAD_REQUEST;
+      }
+      
+      LOG.warn("Bad request:", cause);
+      
+      response = new DefaultHttpResponse();
+      response.setProtocolVersion(HttpVersion.HTTP_1_1);
+      response.setStatus(status);
+    } else if (cause instanceof IOException) {
+      LOG.warn("IOException on HTTP connection", cause);
+      session.close();
+    } else {
+      response = new DefaultHttpResponse();
+      response.setProtocolVersion(HttpVersion.HTTP_1_1);
+      response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
+      LOG.warn("Unexpected exception from a service.", cause);
+    }
+    if (response != null) {
+      HttpServiceContext context = this.currentContext;
+      if (context == null) {
+        context = createContext(new DefaultHttpRequest());
+      }
+      context.commitResponse(response);
+    }
+  }
+
+  public void messageReceived(Object message) {
+    // FIXME messageReceived invoked only when whole message is built.
+    
+    // When headers were built
+    //sendContinuationIfRequested(request);
+    
+    // When body has been built
+  }
+
+  /**
+   * Sends a continuation response for the specified request, if
+   * the client has requested that a continuation response should
+   * be provided.</br>
+   * The continuation response is enqueued with our pipe-line.
+   * Note that we do <i>not</i> commit the request - a final response
+   * must still be provided.
+   * 
+   * TODO: We're not currently adding value here: If we cant route
+   *       to a service, we'll still accept the request. We should
+   *       add the ability to inject the request in to the container
+   *       and let it decide whether the request can be handled.
+   */
+  private void sendContinuationIfRequested(HttpServiceContext context) {
+    if (context.getRequest().requiresContinuationResponse()) {
+      MutableHttpResponse continuationResponse = new DefaultHttpResponse();
+      continuationResponse.setStatus(HttpResponseStatus.CONTINUE);
+      context.commitResponse(continuationResponse);
+      LOG.info("Scheduled continuation response");
+    }
+  }
+
+  /**
+   * Invoked when we fail to parse an incoming request.
+   * We configure our parser to discard any further data received from the client,
+   * and schedule a response with the appropriate failure code for the
+   * current request
+   * 
+   * @param status  The status
+   * @param message Failure message
+   */
+  private void handleReadFailure(HttpServiceContext context, HttpResponseStatus status, String message) {
+    if (LOG.isInfoEnabled()) {
+      LOG.info("Failed to handle client request. Reason: " + status);
+    }
+    MutableHttpResponse response = new DefaultHttpResponse();
+    response.setStatusReasonPhrase(message);
+    response.setStatus(status);
+    context.commitResponse(response);
+  }
+
+  /**
+   * Invoked when data wrote has been fully written.
+   * If we have scheduled closure after sending a final response, we will
+   * be provided with the <code>CLOSE_MARKER</code> as our marker object.<br/>
+   * This signals us to schedule closure of the connection
+   * 
+   * @param message   The marker provided when writing data. If this is
+   *                 our closure marker, we schedule closure of the connection
+   */
+  public void messageSent(Object message)  {
+  }
+
+  /**
+   * Sets the read idle time for all connections
+   * 
+   * @param readIdleTime  The read idle time (seconds)
+   */
+  public void setReadIdleTime(int readIdleTime) {
+    this.readIdleTime = readIdleTime;
+  }
+  
+  private HttpServiceContext createContext(HttpRequest request) {
+    return new DefaultHttpServiceContext(request);
+  }
+
+  private class ContextConverter extends IoFilterAdapter {
+
+    @Override
+    public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception {
+      nextFilter.filterWrite(
+          session,
+          new DefaultWriteRequest(
+              ((HttpServiceContext) writeRequest.getMessage()).getCommittedResponse(),
+              writeRequest.getFuture()));
+    }
+
+    @Override
+    public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception {
+      HttpRequest request = (HttpRequest) message;
+      HttpServiceContext context = createContext(request);
+      currentContext = context;
+      nextFilter.messageReceived(session, context);
+    }
+  }
+  
+  private class ServiceFilterAdapter extends IoFilterAdapter {
+    private final HttpServiceFilter filter;
+
+    public ServiceFilterAdapter(HttpServiceFilter filter) {
+      this.filter = filter;
+    }
+    
+    @Override
+    public void messageReceived(final NextFilter nextFilter, final IoSession session, final Object message) throws Exception {
+      org.safehaus.asyncweb.service.HttpServiceFilter.NextFilter nextFilterAdapter =
+        new org.safehaus.asyncweb.service.HttpServiceFilter.NextFilter() {
+          public void invoke() {
+            nextFilter.messageReceived(session, message);
+          }
+      };
+      filter.handleRequest(nextFilterAdapter, (HttpServiceContext) message);
+    }
+    
+    @Override
+    public void filterWrite(final NextFilter nextFilter, final IoSession session, final WriteRequest writeRequest) throws Exception {
+      org.safehaus.asyncweb.service.HttpServiceFilter.NextFilter nextFilterAdapter =
+        new org.safehaus.asyncweb.service.HttpServiceFilter.NextFilter() {
+          public void invoke() {
+            nextFilter.filterWrite(session, writeRequest);
+          }
+      };
+      
+      HttpServiceContext context = (HttpServiceContext) writeRequest.getMessage();
+      
+      filter.handleResponse(nextFilterAdapter, context);
+    }
+  }
+  
+  private class RequestPipelineAdapter extends IoFilterAdapter {
+
+    private final RequestPipeline pipeline;
+    
+    public RequestPipelineAdapter(final RequestPipeline pipeline) {
+      this.pipeline = pipeline;
+    }
+    
+    public void sessionOpened(final NextFilter nextFilter, final IoSession session) {
+      pipeline.setPipelineListener(new RequestPipelineListener() {
+        public void responseReleased(HttpServiceContext context) {
+          nextFilter.filterWrite(
+              session,
+              new DefaultWriteRequest(
+                  context, ((DefaultHttpServiceContext) context).getWriteFuture()));
+        }
+      });
+      
+      nextFilter.sessionOpened(session);
+    }
+
+    @Override
+    public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception {
+      HttpServiceContext context = (HttpServiceContext) message;
+      if (pipeline.addRequest(context)) {
+        LOG.debug("Allocated slot in request pipeline");
+        nextFilter.messageReceived(session, message);
+      } else {
+        // The client has filled their pipeline. Currently, this
+        // triggers closure. Another option would be to drop read interest
+        // until we drain. 
+        LOG.warn("Could not allocate room in the pipeline for request");
+        handleReadFailure(context, HttpResponseStatus.SERVICE_UNAVAILABLE, "Pipeline full");
+      }
+    }
+
+    @Override
+    public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception {
+      DefaultHttpServiceContext context = (DefaultHttpServiceContext) writeRequest.getMessage();
+      context.setWriteFuture(writeRequest.getFuture());
+      pipeline.releaseResponse(context);
+      // nextFilter will be invoked when pipeline listener is notified.
+    }
+  }
+  
+  private class DefaultHttpServiceContext extends AbstractHttpServiceContext {
+    private WriteFuture writeFuture;
+    
+    private DefaultHttpServiceContext(HttpRequest request) {
+      super((InetSocketAddress) session.getRemoteAddress(), request, container);
+    }
+
+    private WriteFuture getWriteFuture() {
+      return writeFuture;
+    }
+
+    private void setWriteFuture(WriteFuture writeFuture) {
+      if (!isResponseCommitted()) {
+        throw new IllegalStateException();
+      }
+      this.writeFuture = writeFuture;
+    }
+
+    @Override
+    protected void doWrite(boolean requiresClosure) {
+      currentContext = null;
+      WriteFuture future = session.write(this);
+      if (requiresClosure) {
+        LOG.debug("Added CLOSE future listener.");
+        future.addListener(IoFutureListener.CLOSE);
+      }
+    }
+  }
+}

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/SingleHttpSessionIoHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/service/transport/mina/SingleHttpSessionIoHandler.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/HttpDateFormat.java
URL: http://svn.apache.org/viewvc/mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/HttpDateFormat.java?rev=587364&view=auto
==============================================================================
--- mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/HttpDateFormat.java (added)
+++ mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/HttpDateFormat.java Mon Oct 22 19:29:38 2007
@@ -0,0 +1,98 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.safehaus.asyncweb.util;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Utility for generating date strings in the format required by HTTP.
+ * 
+ * @author irvingd
+ *
+ */
+public class HttpDateFormat {
+
+  /**
+   * By default, we update the format if it is more than a second old 
+   */
+  private static final int DEFAULT_GRANULARITY = 1000;
+  
+  private static int granularity = DEFAULT_GRANULARITY;
+  
+  /**
+   * Thread local <code>HttpDateFormat</code>
+   */
+  private static final ThreadLocal FORMAT_LOCAL = new ThreadLocal() {
+    protected Object initialValue() {
+      return new HttpDateFormat();
+    }
+  };
+  
+  /**
+   * Format for HTTP dates 
+   */
+  private final SimpleDateFormat dateFormat = 
+    new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
+  
+  /**
+   * The time of the last format operation (0 if none have yet taken place)
+   */
+  private long timeLastGenerated;
+  
+  /**
+   * The current formatted HTTP date
+   */
+  private String currentHTTPDate;
+  
+  private HttpDateFormat() {
+    // HTTP date format specifies GMT
+    dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
+  }
+  
+  /**
+   * Returns the current time formatted as specified in the HTTP 1.1 specification.
+   * 
+   * @return  The formatted date
+   */
+  public static String getCurrentHttpDate() {
+    return ((HttpDateFormat) FORMAT_LOCAL.get()).getCurrentDate();
+  }
+  
+  /**
+   * Provides the current formatted date to be employed.
+   * If we haven't updated our view of the time in the last 'granularity' ms,
+   * we format a fresh value.
+   * 
+   * @return  The current http date
+   */
+  private String getCurrentDate() {
+    long currentTime = System.currentTimeMillis();
+    if (currentTime - timeLastGenerated > granularity) {
+      timeLastGenerated = currentTime;
+      currentHTTPDate = dateFormat.format(new Date(currentTime));
+    }
+    return currentHTTPDate;
+  }
+  
+
+}

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/HttpDateFormat.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/HttpDateFormat.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/HttpHeaderConstants.java
URL: http://svn.apache.org/viewvc/mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/HttpHeaderConstants.java?rev=587364&view=auto
==============================================================================
--- mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/HttpHeaderConstants.java (added)
+++ mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/HttpHeaderConstants.java Mon Oct 22 19:29:38 2007
@@ -0,0 +1,78 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.safehaus.asyncweb.util;
+
+/**
+ * HTTP Header Constants.
+ * 
+ * @author irvingd
+ * @author trustin
+ * @version $Rev$, $Date$
+ */
+public class HttpHeaderConstants {
+
+  /**
+   * The name of the "connection" header
+   */
+  public static final String KEY_CONNECTION = "Connection";
+  
+  /**
+   * The server header
+   */
+  public static final String KEY_SERVER = "Server";
+    
+  /**
+   * The header value to indicate connection closure
+   */
+  public static final String VALUE_CLOSE = "close";
+  
+  /**
+   * The header value to indicate connection keep-alive (http 1.0)
+   */
+  public static final String VALUE_KEEP_ALIVE = "Keep-Alive";
+  
+  /**
+   * The "content-length" header name
+   */
+  public static final String KEY_CONTENT_LENGTH = "Content-Length";
+  
+  /**
+   * The "transfer-coding" header name
+   */
+  public static final String KEY_TRANSFER_CODING = "Transfer-Coding";
+  
+  /**
+   * The "expect" header name
+   */
+  public static final String KEY_EXPECT = "Expect";
+  
+  /**
+   * The continue expectation
+   */
+  public static final String VALUE_CONTINUE_EXPECTATION = "100-continue";
+  
+  /**
+   * The "date" header
+   */
+  public static final String KEY_DATE = "Date";
+
+  private HttpHeaderConstants() {
+  }
+}

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/HttpHeaderConstants.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/HttpHeaderConstants.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/LinkedPermitIssuer.java
URL: http://svn.apache.org/viewvc/mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/LinkedPermitIssuer.java?rev=587364&view=auto
==============================================================================
--- mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/LinkedPermitIssuer.java (added)
+++ mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/LinkedPermitIssuer.java Mon Oct 22 19:29:38 2007
@@ -0,0 +1,380 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.safehaus.asyncweb.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A <code>TimedPermitIssuer</code> which stores all issued permits in a linked
+ * list.
+ * As a permit is renewed, its lifetime is extended and it is simply moved to the
+ * back of the list (As <code>LinkedPermitIssuer</code> uses a fixed lifetime for
+ * all permits and renewals).<br/> 
+ * Each permit issued by this issuer has direct access to its place in the list -
+ * allowing constant time renewals.
+ * 
+ * @author irvingd
+ *
+ */
+public class LinkedPermitIssuer implements TimedPermitIssuer {
+
+  private static final Logger LOG = LoggerFactory.getLogger(LinkedPermitIssuer.class);
+  
+  /**
+   * The head of the permit list. 
+   * This entry is the next due to expire
+   */
+  private PermitEntry head;
+  
+  /**
+   * The tail of the permit list
+   */
+  private PermitEntry tail;
+  
+  /**
+   * The lifetime given to new permits, and permit renewals
+   */
+  private long lifetime;
+  
+  private Object lock = new Object();
+  
+  private List<PermitExpirationListener> listeners = Collections.synchronizedList(new ArrayList<PermitExpirationListener>());
+  
+  private boolean isClosed;
+    
+  /**
+   * Creates a <code>LinkedPermitIssuer</code> with a specified lifetime given to
+   * new permits. When a permit issued by this issuer is renewed, its expiry time
+   * is renewed to the current time plus this lifetime
+   * 
+   * @param lifetime  The lifetime to be used for new permits, and permit renewals
+   */
+  public LinkedPermitIssuer(long lifetime) {
+    if (lifetime <= 0) {
+      throw new IllegalArgumentException("lifetime must be >0");
+    }
+    this.lifetime = lifetime;
+    new Thread(new ExpiryNotifier()).start();
+  }
+  
+  /**
+   * Issues a new <code>TimedPermit</code> for the target object.
+   * Unless <code>renew</code>ed, the permit expires after this
+   * issuers imposed lifetime. Upon renewal, the permit becomes valid
+   * for this issuers configured lifetime from the time of the renewal.
+   * 
+   * @param o The target object
+   */
+  public TimedPermit issuePermit(Object o) {
+    PermitEntry permit;
+    synchronized (lock) {
+      permit = new PermitEntry(o);
+      if (isEmpty()) {
+        head = tail = permit;
+        lock.notify(); // notify when move from empty to non empty
+      } else {
+        tail.entryAfter = permit;
+        permit.entryBefore = tail;
+        tail = permit;
+      }
+    }
+    return permit;
+  }
+  
+  /**
+   * Adds a <code>PermitExpirationListener</code> to this issuer
+   * 
+   * @param listener the listener
+   */
+  public void addPermitExpirationListener(PermitExpirationListener listener) {
+    listeners.add(listener);
+  }
+
+  /**
+   * Closes this issuer
+   */
+  public void close() {
+    synchronized (lock) {
+      isClosed = true;
+      lock.notify();
+      LOG.debug("Marked as closed");
+    }
+  }
+  
+  /**
+   * Determines whether there are any outstanding permits
+   * to be processed
+   * 
+   * @return  <code>true</code> if there are no outstanding
+   *          permits
+   */
+  private boolean isEmpty() {
+    return head == null;
+  }
+  
+  /**
+   * Moves a <code>PermitEntry</code> to the back of the list.
+   * An entry is moved to the back upon renewal. If we move the current head
+   * to the back, we notify to allow the expiry time of the next element
+   * to be observed
+   * 
+   * @param entry  The entry to move
+   */
+  private void moveToBack(PermitEntry entry) {
+    boolean movedHead = (entry == head);  
+    if (entry != tail) { // nothing to move / no need to notify if already at back
+      PermitEntry previous = entry.entryBefore;
+      PermitEntry after    = entry.entryAfter;
+
+      tail.entryAfter   = entry;
+      entry.entryBefore = tail;
+      entry.entryAfter  = null;
+      tail = entry;
+      
+      after.entryBefore = previous;
+      if (!movedHead) {
+        previous.entryAfter = after;
+      } else {
+        head = after;
+        lock.notify();
+      }
+    }
+  }
+  
+  /**
+   * Removes the head entry (without notifications)
+   */
+  private void removeHead() {
+    head = head.entryAfter;
+    if (head != null) {
+      head.entryBefore = null;
+    } else {
+      LOG.debug("Permit list empty following removal");
+    }
+  }
+  
+  /**
+   * Notifies all listeners of the expiry of a specified target object
+   * 
+   * @param target  The expired target object
+   */
+  private void notifyExpiry(Object target) {
+    synchronized (listeners) {
+      for (Iterator iter = listeners.iterator(); iter.hasNext(); ) {
+        PermitExpirationListener listener = (PermitExpirationListener) iter.next();
+        listener.permitExpired(target);
+      }
+    }
+  }
+  
+  /**
+   * An entry in a linked list of permits.
+   * 
+   * @author irvingd
+   *
+   */
+  private class PermitEntry implements TimedPermit {
+
+    private PermitEntry entryBefore;
+    private PermitEntry entryAfter;
+    private long expiryTime;
+    private Object o;
+    private boolean isCancelled;
+    
+    PermitEntry(Object o) {
+      this.o = o;
+      extendLifetime();
+    }
+    
+    /**
+     * Renews this permit. If we are cancelled, no action is taken.
+     * Otherwise, our lifetime is extended, and we move to the back of the
+     * list
+     */
+    public void renew() {
+      synchronized (lock) {
+        if (!isCancelled) {
+          extendLifetime();
+          moveToBack(this);
+          if (LOG.isDebugEnabled()) {
+            LOG.debug("Entry has been renewed. New expiry time: " + expiryTime);
+          }
+        }
+      }
+    }
+
+    /**
+     * @return  This permits target object
+     */
+    Object getTarget() {
+      return o;
+    }
+    
+    /**
+     * @return the time remaining, in ms until this permit
+     * expires
+     */
+    long timeToExpiry() {
+      return expiryTime - System.currentTimeMillis();
+    }
+    
+    /**
+     * @return  <code>true</code> if this permit has been cancelled and should be
+     *          removed
+     */
+    boolean isCancelled() {
+      return isCancelled;
+    }
+        
+    /**
+     * Cancels this permit.
+     * If we are either expired or already cancelled, no action is taken.
+     * Otherwise, we mark ourself as cancelled.
+     * If we are at the head of the permit list, we notify to allow
+     * the notification thread to take any required action. 
+     */
+    public boolean cancel() {
+      synchronized (lock) {
+        if (isCancelled) {
+          LOG.debug("Ignoring cancel request");
+          return false;
+        }
+        isCancelled = true;
+        LOG.debug("Entry has been successfully cancelled");
+        if (this == head) {
+          LOG.debug("Head entry cancelled - notifying");
+          lock.notify();
+        }
+        return true;
+      }
+    }
+    
+    /**
+     * Marks this permit as cancelled
+     */
+    void markCancelled() {
+      isCancelled = true;
+    }
+    
+    /**
+     * Extends our lifetime
+     */
+    private void extendLifetime() {
+      expiryTime = System.currentTimeMillis() + lifetime;
+    }
+    
+  }
+
+  /**
+   * Services the head of the permit list, blocking until work is available.
+   * Cancelled and expired entries are removed (listeners are notified of all
+   * expired entries)
+   * 
+   * @author irvingd
+   */
+  private class ExpiryNotifier implements Runnable {
+
+    public void run() {
+      try {
+        LOG.debug("ExpiryNotifier starting");
+        while (processHeadEntry()) {
+          continue;
+        }
+        LOG.debug("ExpiryNotifier closing");
+      } catch (RuntimeException e) {
+        LOG.error("Unexpected exception on expiry notifier", e);
+      }
+      
+    }
+    
+    /**
+     * Waits for cancellation / expiration of the head entry.
+     * If the head entry is expired, listeners are notified.
+     * 
+     * @return  <code>true</code> if the entry is processed,
+     *          <code>false</code> if we become closed while waiting for a result
+     */
+    private boolean processHeadEntry() {
+      PermitEntry toExpire = null;
+      try {
+        synchronized (lock) {
+          while (!isClosed && isEmpty()) {
+            lock.wait();
+          }
+          if (isClosed) {
+            return false;
+          } else {
+            toExpire = processFirst();
+          }
+        }
+      } catch (InterruptedException e) {
+        throw new RuntimeException(e);
+      }
+      if (toExpire != null) {
+        notifyExpiry(toExpire.getTarget());
+      }
+      return true;
+    }
+        
+    /**
+     * Examines the head of the list for cancellation or expiry.
+     * We block until either the entry is cancelled, expires, or we are
+     * closed
+     * 
+     * @return  The previous head entry - if it has expired. 
+     *          <code>null</code> if the entry was cancelled, or we were closed
+     */
+    private PermitEntry processFirst() {
+      PermitEntry entry = head;
+      boolean expired = false;
+      long timeToExpiry = entry.timeToExpiry();
+      while (!isClosed && !entry.isCancelled() && timeToExpiry > 0) {
+        try {
+          if (LOG.isDebugEnabled()) {
+            LOG.debug("Waiting for head entry to expire: " + timeToExpiry + "ms");
+          }
+          lock.wait(timeToExpiry);
+        } catch (InterruptedException e) {
+          throw new RuntimeException("Unexpected interrupt");
+        }
+        timeToExpiry = entry.timeToExpiry();
+      }
+
+      if (entry.isCancelled()) {
+        LOG.debug("Head entry is cancelled. Removing");
+        removeHead();
+      } else if (!isClosed) {
+        LOG.debug("Head entry has expired");
+        entry.markCancelled();
+        removeHead();
+        expired = true;
+      }
+      return expired ? entry : null;
+    }
+    
+  }
+  
+}

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/LinkedPermitIssuer.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/LinkedPermitIssuer.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/PermitExpirationListener.java
URL: http://svn.apache.org/viewvc/mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/PermitExpirationListener.java?rev=587364&view=auto
==============================================================================
--- mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/PermitExpirationListener.java (added)
+++ mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/PermitExpirationListener.java Mon Oct 22 19:29:38 2007
@@ -0,0 +1,38 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.safehaus.asyncweb.util;
+
+/**
+ * Something interested in receiving notifications when permits issued by a
+ * <code>TimedPermitIssuer</code> exipre
+ * 
+ * @author irvingd
+ *
+ */
+public interface PermitExpirationListener {
+
+  /**
+   * Invoked when the permit associated with the specified object expires
+   * 
+   * @param o The object for which an associated permit has expired
+   */
+  public void permitExpired(Object o);
+  
+}

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/PermitExpirationListener.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/PermitExpirationListener.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/StringBundle.java
URL: http://svn.apache.org/viewvc/mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/StringBundle.java?rev=587364&view=auto
==============================================================================
--- mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/StringBundle.java (added)
+++ mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/StringBundle.java Mon Oct 22 19:29:38 2007
@@ -0,0 +1,108 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.safehaus.asyncweb.util;
+
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Simplifies looking up locale specific format strings.
+ * 
+ * @author irvingd
+ *
+ */
+public class StringBundle {
+
+  private static final String RESOURCE_POSTFIX = ".strings";
+  private static final Logger LOG = LoggerFactory.getLogger(StringBundle.class);
+  private static Map<String, StringBundle> bundles = new HashMap<String, StringBundle>();
+  
+  private ResourceBundle bundle;
+  
+  private StringBundle(String packageName) {
+    String bundleName = packageName + RESOURCE_POSTFIX;
+    bundle = ResourceBundle.getBundle(bundleName);
+    if (bundle == null) {
+      LOG.warn("Cant find resource '" + bundleName + "'");
+    }
+  }
+ 
+  public synchronized static StringBundle getBundle(String packageName) {
+    StringBundle bundle = bundles.get(packageName);
+    if (bundle == null) {
+      bundle = new StringBundle(packageName);
+      bundles.put(packageName, bundle);
+    }
+    return bundle;
+  }
+  
+  public String getString(String key) {
+    return MessageFormat.format(getValue(key), (Object) null);
+  }
+  
+  public String getString(String key, Object param) {
+    Object[] params = new Object[] {param};
+    return getString(key, params);
+  }
+  
+  public String getString(String key, Object param1, Object param2) {
+    Object[] params = new Object[] {param1, param2};
+    return getString(key, params);
+  }
+  
+  public String getString(String key, Object[] params) {
+    String value = getValue(key);
+    String formatted;
+    try {
+      formatted = MessageFormat.format(value, params);
+    } catch (IllegalArgumentException e) {
+      formatted = applyDefaultFormat(value, params);
+    }
+    return formatted;
+  }
+  
+  private String getValue(String key) {
+    if (key == null) {
+      throw new IllegalArgumentException("Null key");
+    }
+    if (bundle == null) {
+      return key;
+    }
+    String value = bundle.getString(key);
+    return value == null ? "Missing resource '" + key + "'" : value;
+  }
+  
+  private String applyDefaultFormat(String value, Object[] params) {
+    StringBuffer buff = new StringBuffer();
+    buff.append(value);
+    if (params != null) {
+      for (int i=0; i<params.length; ++i) {
+        buff.append(" param[").append(i).append(" = ").append(params[i]).append(" ]");
+      }
+    }
+    return buff.toString();
+  }
+  
+}

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/StringBundle.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/StringBundle.java
------------------------------------------------------------------------------
    svn:keywords = Rev Date

Added: mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/TimedPermit.java
URL: http://svn.apache.org/viewvc/mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/TimedPermit.java?rev=587364&view=auto
==============================================================================
--- mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/TimedPermit.java (added)
+++ mina/sandbox/asyncweb/core/src/main/java/org/safehaus/asyncweb/util/TimedPermit.java Mon Oct 22 19:29:38 2007
@@ -0,0 +1,56 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ *
+ */
+package org.safehaus.asyncweb.util;
+
+/**
+ * A time limited permit granting access to a target object.
+ * A <code>TimedPermit</code> is issued for a target object by a <code>TimedPermitIssue</code>.
+ * When the time limit (determined by the issuer) is reached, any listeners attached to
+ * the issuer are notified.
+ * 
+ * @author irvingd
+ *
+ */
+public interface TimedPermit {
+
+  /**
+   * Extends the lifetime of this permit.
+   * This is typically used as a "keep-alive" mechanism. For example, if a permit is
+   * issued to manage the idle expiry time of an http session, the permit might be
+   * extended each time the client issues a request associated with the session.<br/>
+   * The amount of time added to the lifetime of this permit by invoking this method
+   * is determined by the <code>TimedPermitIssuer</code> which issued this permit.
+   * 
+   * Invoking this method has no effect if this permit is already expired
+   */
+  public void renew();
+
+  /**
+   * Cancels this permit. After invoking this method, it is guaranteed that 
+   * no expiry notification will be made for the target object by this permits
+   * associated <code>TimedPermitIssuer</code>.
+   * 
+   * @return <code>true</code> if invoking this method prevented expiry notification
+   *         from taking place, <code>false</code> if expiry notification has already
+   *         taken place
+   */
+  public boolean cancel();
+  
+}