JBossWeb SVN: r336 - branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/ajp.
by jbossweb-commits@lists.jboss.org
Author: jfrederic.clere(a)jboss.com
Date: 2007-11-05 05:37:12 -0500 (Mon, 05 Nov 2007)
New Revision: 336
Modified:
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/ajp/AjpProcessor.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/ajp/AjpProtocol.java
Log:
Fix CLOSE_WAIT socket. case 18354 JBPAPP-366
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/ajp/AjpProcessor.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/ajp/AjpProcessor.java 2007-11-05 10:23:56 UTC (rev 335)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/ajp/AjpProcessor.java 2007-11-05 10:37:12 UTC (rev 336)
@@ -355,7 +355,7 @@
*
* @throws IOException error during an I/O operation
*/
- public boolean process(Socket socket)
+ public void process(Socket socket)
throws IOException {
RequestInfo rp = request.getRequestProcessor();
rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
@@ -471,8 +471,6 @@
input = null;
output = null;
- return true;
-
}
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/ajp/AjpProtocol.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/ajp/AjpProtocol.java 2007-11-05 10:23:56 UTC (rev 335)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/coyote/ajp/AjpProtocol.java 2007-11-05 10:37:12 UTC (rev 336)
@@ -378,7 +378,8 @@
((ActionHook) processor).action(ActionCode.ACTION_START, null);
}
- return processor.process(socket);
+ processor.process(socket);
+ return false;
} catch(java.net.SocketException e) {
// SocketExceptions are normal
16 years, 6 months
JBossWeb SVN: r335 - in branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache: catalina/authenticator and 5 other directories.
by jbossweb-commits@lists.jboss.org
Author: jfrederic.clere(a)jboss.com
Date: 2007-11-05 05:23:56 -0500 (Mon, 05 Nov 2007)
New Revision: 335
Modified:
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/Globals.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/authenticator/Constants.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/connector/CoyoteAdapter.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/connector/Response.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/realm/RealmBase.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/Constants.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/tagplugins/jstl/Util.java
branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/tomcat/util/net/JIoEndpoint.java
Log:
Configurable JSESSIONID Cookie Name for case 16667.
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/Globals.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/Globals.java 2007-11-01 17:03:01 UTC (rev 334)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/Globals.java 2007-11-05 10:23:56 UTC (rev 335)
@@ -269,14 +269,16 @@
* The name of the cookie used to pass the session identifier back
* and forth with the client.
*/
- public static final String SESSION_COOKIE_NAME = "JSESSIONID";
+ public static final String SESSION_COOKIE_NAME =
+ System.getProperty("org.apache.catalina.JSESSIONID", "JSESSIONID");
/**
* The name of the path parameter used to pass the session identifier
* back and forth with the client.
*/
- public static final String SESSION_PARAMETER_NAME = "jsessionid";
+ public static final String SESSION_PARAMETER_NAME =
+ System.getProperty("org.apache.catalina.jsessionid", "jsessionid");
/**
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/authenticator/Constants.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/authenticator/Constants.java 2007-11-01 17:03:01 UTC (rev 334)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/authenticator/Constants.java 2007-11-05 10:23:56 UTC (rev 335)
@@ -40,7 +40,8 @@
public static final String FORM_USERNAME = "j_username";
// Cookie name for single sign on support
- public static final String SINGLE_SIGN_ON_COOKIE = "JSESSIONIDSSO";
+ public static final String SINGLE_SIGN_ON_COOKIE =
+ System.getProperty("org.apache.catalina.authenticator.Constants.JSESSIONIDSSO", "JSESSIONIDSSO");
// --------------------------------------------------------- Request Notes
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/connector/CoyoteAdapter.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/connector/CoyoteAdapter.java 2007-11-01 17:03:01 UTC (rev 334)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/connector/CoyoteAdapter.java 2007-11-05 10:23:56 UTC (rev 335)
@@ -427,7 +427,7 @@
if (request.isRequestedSessionIdFromURL()) {
// This is not optimal, but as this is not very common, it
// shouldn't matter
- redirectPath = redirectPath + ";jsessionid="
+ redirectPath = redirectPath + ";" + Globals.SESSION_PARAMETER_NAME + "="
+ request.getRequestedSessionId();
}
if (query != null) {
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/connector/Response.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/connector/Response.java 2007-11-01 17:03:01 UTC (rev 334)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/connector/Response.java 2007-11-05 10:23:56 UTC (rev 335)
@@ -1435,7 +1435,8 @@
String file = url.getFile();
if ((file == null) || !file.startsWith(contextPath))
return (false);
- if( file.indexOf(";jsessionid=" + session.getIdInternal()) >= 0 )
+ String tok = ";" + Globals.SESSION_PARAMETER_NAME + "=" + session.getIdInternal();
+ if (file.indexOf(tok, contextPath.length()) >= 0)
return (false);
}
@@ -1569,7 +1570,9 @@
}
StringBuffer sb = new StringBuffer(path);
if( sb.length() > 0 ) { // jsessionid can't be first.
- sb.append(";jsessionid=");
+ sb.append(";");
+ sb.append(Globals.SESSION_PARAMETER_NAME);
+ sb.append("=");
sb.append(sessionId);
}
sb.append(anchor);
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/realm/RealmBase.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/realm/RealmBase.java 2007-11-01 17:03:01 UTC (rev 334)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/catalina/realm/RealmBase.java 2007-11-05 10:23:56 UTC (rev 335)
@@ -37,6 +37,7 @@
import org.apache.catalina.Container;
import org.apache.catalina.Context;
+import org.apache.catalina.Globals;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
@@ -944,7 +945,9 @@
String requestedSessionId = request.getRequestedSessionId();
if ((requestedSessionId != null) &&
request.isRequestedSessionIdFromURL()) {
- file.append(";jsessionid=");
+ file.append(";");
+ file.append(Globals.SESSION_PARAMETER_NAME);
+ file.append("=");
file.append(requestedSessionId);
}
String queryString = request.getQueryString();
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/Constants.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/Constants.java 2007-11-01 17:03:01 UTC (rev 334)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/Constants.java 2007-11-05 10:23:56 UTC (rev 335)
@@ -193,4 +193,10 @@
public static final boolean IS_SECURITY_ENABLED =
(System.getSecurityManager() != null);
+ /**
+ * The name of the path parameter used to pass the session identifier
+ * back and forth with the client.
+ */
+ public static final String SESSION_PARAMETER_NAME = "jsessionid";
+
}
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/tagplugins/jstl/Util.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/tagplugins/jstl/Util.java 2007-11-01 17:03:01 UTC (rev 334)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/jasper/tagplugins/jstl/Util.java 2007-11-05 10:23:56 UTC (rev 335)
@@ -33,6 +33,8 @@
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.PageContext;
+import org.apache.jasper.Constants;
+
/**
* Util contains some often used consts, static methods and embedded class
* to support the JSTL tag plugin.
@@ -150,7 +152,7 @@
public static String stripSession(String url) {
StringBuffer u = new StringBuffer(url);
int sessionStart;
- while ((sessionStart = u.toString().indexOf(";jsessionid=")) != -1) {
+ while ((sessionStart = u.toString().indexOf(";" + Constants.SESSION_PARAMETER_NAME + "=")) != -1) {
int sessionEnd = u.toString().indexOf(";", sessionStart + 1);
if (sessionEnd == -1)
sessionEnd = u.toString().indexOf("?", sessionStart + 1);
Modified: branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/tomcat/util/net/JIoEndpoint.java
===================================================================
--- branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/tomcat/util/net/JIoEndpoint.java 2007-11-01 17:03:01 UTC (rev 334)
+++ branches/JBOSSWEB_2_0_0_GA_CP/src/share/classes/org/apache/tomcat/util/net/JIoEndpoint.java 2007-11-05 10:23:56 UTC (rev 335)
@@ -440,8 +440,12 @@
// Wait for the next socket to be assigned
Socket socket = await();
+ // JFC
+ log.error("JIoEndpoint.run: " + socket);
if (socket == null)
continue;
+ log.error("JIoEndpoint.run: " + socket.isClosed() + " " + socket.isInputShutdown()
+ + " " + socket.isOutputShutdown());
// Process the request from this socket
if (!setSocketOptions(socket) || !handler.process(socket)) {
16 years, 6 months
JBossWeb SVN: r334 - sandbox/tomcat/tomcat6.
by jbossweb-commits@lists.jboss.org
Author: jfrederic.clere(a)jboss.com
Date: 2007-11-01 13:03:01 -0400 (Thu, 01 Nov 2007)
New Revision: 334
Added:
sandbox/tomcat/tomcat6/oldjk.patch
Modified:
sandbox/tomcat/tomcat6/README
Log:
Add the remove of the jk code.
Modified: sandbox/tomcat/tomcat6/README
===================================================================
--- sandbox/tomcat/tomcat6/README 2007-11-01 16:27:49 UTC (rev 333)
+++ sandbox/tomcat/tomcat6/README 2007-11-01 17:03:01 UTC (rev 334)
@@ -1,3 +1,4 @@
patch.build.patch: Arrange build.
cluster.patch: remove cluster and documentation.
nio.patch: remove nio and documentation.
+oldjk.patch: remove the old jk code.
Added: sandbox/tomcat/tomcat6/oldjk.patch
===================================================================
--- sandbox/tomcat/tomcat6/oldjk.patch (rev 0)
+++ sandbox/tomcat/tomcat6/oldjk.patch 2007-11-01 17:03:01 UTC (rev 334)
@@ -0,0 +1,11271 @@
+Index: java/org/apache/jk/apr/AprImpl.java
+===================================================================
+--- java/org/apache/jk/apr/AprImpl.java (revision 590752)
++++ java/org/apache/jk/apr/AprImpl.java (working copy)
+@@ -1,317 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.apr;
+-
+-import java.io.FileOutputStream;
+-import java.io.IOException;
+-import java.io.PrintStream;
+-import java.util.Hashtable;
+-import org.apache.jk.core.JkHandler;
+-import org.apache.jk.core.MsgContext;
+-import org.apache.jk.core.JkChannel;
+-
+-/** Implements the interface with the APR library. This is for internal-use
+- * only. The goal is to use 'natural' mappings for user code - for example
+- * java.net.Socket for unix-domain sockets, etc.
+- *
+- */
+-public class AprImpl extends JkHandler { // This will be o.a.t.util.handler.TcHandler - lifecycle and config
+- static AprImpl aprSingleton=null;
+-
+- String baseDir;
+- String aprHome;
+- String soExt="so";
+-
+- static boolean ok=true;
+- boolean initialized=false;
+- // Handlers for native callbacks
+- Hashtable jkHandlers=new Hashtable();
+-
+- // Name of the so used in inprocess mode
+- String jniModeSo="inprocess";
+- // name of the so used by java. If not set we'll loadLibrary("jkjni" ),
+- // if set we load( nativeSo )
+- String nativeSo;
+-
+- public AprImpl() {
+- aprSingleton=this;
+- }
+-
+- // -------------------- Properties --------------------
+-
+- /** Native libraries are located based on base dir.
+- * XXX Add platform, version, etc
+- */
+- public void setBaseDir(String s) {
+- baseDir=s;
+- }
+-
+- public void setSoExt(String s ) {
+- soExt=s;
+- }
+-
+- // XXX maybe install the jni lib in apr-home ?
+- public void setAprHome( String s ) {
+- aprHome=s;
+- }
+-
+- /** Add a Handler for jni callbacks.
+- */
+- public void addJkHandler(String type, JkHandler cb) {
+- jkHandlers.put( type, cb );
+- }
+-
+- /** Name of the so used in inprocess mode
+- */
+- public void setJniModeSo(String jniModeSo ) {
+- this.jniModeSo=jniModeSo;
+- }
+-
+- /** name of the so used by java. If not set we'll loadLibrary("jkjni" ),
+- if set we load( nativeSo )
+- */
+- public void setNativeSo( String nativeSo ) {
+- this.nativeSo=nativeSo;
+- }
+-
+- /** Sets the System.out stream */
+-
+- public static void setOut( String filename ) {
+- try{
+- if( filename !=null ){
+- System.setOut( new PrintStream(new FileOutputStream(filename )));
+- }
+- }catch (Throwable th){
+- }
+- }
+- /** Sets the System.err stream */
+-
+- public static void setErr( String filename ) {
+- try{
+- if( filename !=null ){
+- System.setErr( new PrintStream(new FileOutputStream(filename )));
+- }
+- }catch (Throwable th){
+- }
+- }
+-
+- // -------------------- Apr generic utils --------------------
+- /** Initialize APR
+- */
+- public native int initialize();
+-
+- public native int terminate();
+-
+- /* -------------------- Access to the jk_env_t -------------------- */
+-
+- /* The jk_env_t provide temporary storage ( pool ), logging, common services
+- */
+-
+- /* Return a jk_env_t, used to keep the execution context ( temp pool, etc )
+- */
+- public native long getJkEnv();
+-
+- /** Clean the temp pool, put back the env in the pool
+- */
+- public native void releaseJkEnv(long xEnv);
+-
+- /* -------------------- Interface to the jk_bean object -------------------- */
+- /* Each jk component is 'wrapped' as a bean, with a specified lifecycle
+- *
+- */
+-
+- /** Get a native component
+- * @return 0 if the component is not found.
+- */
+- public native long getJkHandler(long xEnv, String compName );
+-
+- public native long createJkHandler(long xEnv, String compName );
+-
+- public native int jkSetAttribute( long xEnv, long componentP, String name, String val );
+-
+- public native String jkGetAttribute( long xEnv, long componentP, String name );
+-
+- public native int jkInit( long xEnv, long componentP );
+-
+- public native int jkDestroy( long xEnv, long componentP );
+-
+- /** Send the packet to the C side. On return it contains the response
+- * or indication there is no response. Asymetrical because we can't
+- * do things like continuations.
+- */
+- public static native int jkInvoke(long xEnv, long componentP, long endpointP,
+- int code, byte data[], int off, int len, int raw);
+-
+- /** Recycle an endpoint after use.
+- */
+- public native void jkRecycle(long xEnv, long endpointP);
+-
+- // -------------------- Called from C --------------------
+- // XXX Check security, add guard or other protection
+- // It's better to do it the other way - on init 'push' AprImpl into
+- // the native library, and have native code call instance methods.
+-
+- public static Object createJavaContext(String type, long cContext) {
+- // XXX will be an instance method, fields accessible directly
+- AprImpl apr=aprSingleton;
+- JkChannel jkH=(JkChannel)apr.jkHandlers.get( type );
+- if( jkH==null ) return null;
+-
+- MsgContext ep=jkH.createMsgContext();
+-
+- ep.setSource( jkH );
+-
+- ep.setJniContext( cContext );
+- return ep;
+- }
+-
+- /** Return a buffer associated with the ctx.
+- */
+- public static byte[] getBuffer( Object ctx, int id ) {
+- return ((MsgContext)ctx).getBuffer( id );
+- }
+-
+- public static int jniInvoke( long jContext, Object ctx ) {
+- try {
+- MsgContext ep=(MsgContext)ctx;
+- ep.setJniEnv( jContext );
+- ep.setType( 0 );
+- return ((MsgContext)ctx).execute();
+- } catch( Throwable ex ) {
+- ex.printStackTrace();
+- return -1;
+- }
+- }
+-
+- // -------------------- Initialization --------------------
+-
+- public void init() throws IOException {
+- try {
+- initialized=true;
+- loadNative();
+-
+- initialize();
+- jkSetAttribute(0, 0, "channel:jni", "starting");
+-
+- log.info("JK: Initialized apr" );
+-
+- } catch( Throwable t ) {
+- throw new IOException( t.toString() );
+- }
+- ok=true;
+- }
+-
+- public boolean isLoaded() {
+- if( ! initialized ) {
+- try {
+- init();
+- } catch( Throwable t ) {
+- log.info("Apr not loaded: " + t);
+- }
+- }
+- return ok;
+- }
+-
+- static boolean jniMode=false;
+-
+-
+- public static void jniMode() {
+- jniMode=true;
+- }
+-
+- /** This method of loading the libs doesn't require setting
+- * LD_LIBRARY_PATH. Assuming a 'right' binary distribution,
+- * or a correct build all files will be in their right place.
+- *
+- * The burden is on our code to deal with platform specific
+- * extensions and to keep the paths consistent - not easy, but
+- * worth it if it avoids one extra step for the user.
+- *
+- * Of course, this can change to System.load() and putting the
+- * libs in LD_LIBRARY_PATH.
+- */
+- public void loadNative() throws Throwable {
+- if( aprHome==null )
+- aprHome=baseDir;
+-
+- // XXX Update for windows
+- if( jniMode ) {
+- /* In JNI mode we use mod_jk for the native functions.
+- This seems the cleanest solution that works with multiple
+- VMs.
+- */
+- if (jniModeSo.equals("inprocess")) {
+- ok=true;
+- return;
+- }
+- try {
+- log.info("Loading " + jniModeSo);
+- if( jniModeSo!= null ) System.load( jniModeSo );
+- } catch( Throwable ex ) {
+- // ignore
+- //ex.printStackTrace();
+- return;
+- }
+- ok=true;
+- return;
+- }
+-
+- /*
+- jkjni _must_ be linked with apr and crypt -
+- this seem the only ( decent ) way to support JDK1.4 and
+- JDK1.3 at the same time
+- try {
+- System.loadLibrary( "crypt" );
+- } catch( Throwable ex ) {
+- // ignore
+- ex.printStackTrace();
+- }
+- try {
+- System.loadLibrary( "apr" );
+- } catch( Throwable ex ) {
+- System.out.println("can't load apr, that's fine");
+- ex.printStackTrace();
+- }
+- */
+- try {
+- if( nativeSo == null ) {
+- // This will load libjkjni.so or jkjni.dll in LD_LIBRARY_PATH
+- log.debug("Loading jkjni from " + System.getProperty("java.library.path"));
+- System.loadLibrary( "jkjni" );
+- } else {
+- System.load( nativeSo );
+- }
+- } catch( Throwable ex ) {
+- ok=false;
+- //ex.printStackTrace();
+- throw ex;
+- }
+- }
+-
+- public void loadNative(String libPath) {
+- try {
+- System.load( libPath );
+- } catch( Throwable ex ) {
+- ok=false;
+- if( log.isDebugEnabled() )
+- log.debug( "Error loading native library ", ex);
+- }
+- }
+- private static org.apache.juli.logging.Log log=
+- org.apache.juli.logging.LogFactory.getLog( AprImpl.class );
+-}
+Index: java/org/apache/jk/apr/TomcatStarter.java
+===================================================================
+--- java/org/apache/jk/apr/TomcatStarter.java (revision 590752)
++++ java/org/apache/jk/apr/TomcatStarter.java (working copy)
+@@ -1,94 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.apr;
+-
+-import java.lang.reflect.Method;
+-
+-// Hack for Catalina 4.1 who hungs the calling thread.
+-// Also avoids delays in apache initialization ( tomcat can take a while )
+-
+-/**
+- * Start some tomcat.
+- *
+- */
+-public class TomcatStarter implements Runnable {
+- Class c;
+- String args[];
+- AprImpl apr = new AprImpl();
+-
+- public static String mainClasses[]={ "org.apache.tomcat.startup.Main",
+- "org.apache.catalina.startup.BootstrapService",
+- "org.apache.catalina.startup.Bootstrap"};
+-
+- // If someone has time - we can also guess the classpath and do other
+- // fancy guessings.
+-
+- public static void main( String args[] ) {
+- System.err.println("TomcatStarter: main()");
+- int nClasses = 0;
+-
+- try {
+- AprImpl.jniMode();
+- // Find the class
+- Class c=null;
+- for( int i=0; i<mainClasses.length; i++ ) {
+- try {
+- System.err.println("Try " + mainClasses[i]);
+- c=Class.forName( mainClasses[i] );
+- } catch( ClassNotFoundException ex ) {
+- continue;
+- }
+- if( c!= null ) {
+- ++nClasses;
+- Thread startThread=new Thread( new TomcatStarter(c, args));
+- c=null;
+- startThread.start();
+- break;
+- }
+- }
+- if (nClasses==0)
+- System.err.println("No class found ");
+-
+- } catch (Throwable t ) {
+- t.printStackTrace(System.err);
+- }
+- }
+-
+- public TomcatStarter( Class c, String args[] ) {
+- this.c=c;
+- this.args=args;
+- }
+-
+- public void run() {
+- System.err.println("Starting " + c.getName());
+- try {
+- Class argClass=args.getClass();
+- Method m=c.getMethod( "main", new Class[] {argClass} );
+- m.invoke( c, new Object[] { args } );
+- System.out.println("TomcatStarter: Done");
+- if (apr.isLoaded())
+- apr.jkSetAttribute(0, 0, "channel:jni", "done");
+- if (args[0].equals("stop")) {
+- Thread.sleep(5000);
+- Runtime.getRuntime().exit(0);
+- }
+- } catch( Throwable t ) {
+- t.printStackTrace(System.err);
+- }
+- }
+-}
+Index: java/org/apache/jk/mbeans-descriptors.xml
+===================================================================
+--- java/org/apache/jk/mbeans-descriptors.xml (revision 590752)
++++ java/org/apache/jk/mbeans-descriptors.xml (working copy)
+@@ -1,557 +0,0 @@
+-<?xml version="1.0"?>
+-<!--
+- 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.
+--->
+-<!DOCTYPE mbeans-descriptors PUBLIC
+- "-//Apache Software Foundation//DTD Model MBeans Configuration File"
+- "http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd">
+-
+-<!--
+- Descriptions of JMX MBeans for jk
+- -->
+-
+-<mbeans-descriptors>
+-
+- <mbean name="ChannelSocket"
+- description="Socket channel"
+- domain="Catalina"
+- group="Jk"
+- type="org.apache.jk.common.ChannelSocket">
+-
+- <attribute name="port"
+- description="The port number on which we listen for ajp13 requests"
+- type="int"/>
+- <attribute name="maxPort"
+- description="The max port number on which we listen for ajp13 requests"
+- type="int"/>
+- <attribute name="address"
+- description="The IP address on which to bind"
+- type="java.lang.String"/>
+- <attribute name="maxSpareThreads"
+- description="The maximum number of unused request processing threads"
+- type="int"/>
+- <attribute name="maxThreads"
+- description="The maximum number of request processing threads to be created"
+- type="int"/>
+- <attribute name="minSpareThreads"
+- description="The number of request processing threads that will be created"
+- type="int"/>
+- <attribute name="tcpNoDelay"
+- description="Should we use TCP no delay?"
+- type="boolean"/>
+- <attribute name="soLinger"
+- description="Linger value on the incoming connection"
+- type="int"/>
+- <attribute name="soTimeout"
+- description="Socket timeout"
+- type="int"/>
+- <attribute name="requestCount"
+- description="current request count"
+- type="int"
+- writeable="false"/>
+- <attribute name="daemon"
+- description="are worker threads on daemon mode"
+- type="boolean"
+- writeable="false"/>
+- <attribute name="packetSize"
+- description="The maximum AJP packet size"
+- type="int" />
+-
+- <operation name="start"
+- description="Start, if server socket no create call init"
+- impact="ACTION"
+- returnType="void" />
+- <operation name="stop"
+- description="Stop"
+- impact="ACTION"
+- returnType="void" />
+- <operation name="pause"
+- description="Pause ajp socket, no new connection accepted"
+- impact="ACTION"
+- returnType="void"/>
+- <operation name="resume"
+- description="Resume socket for new connections"
+- impact="ACTION"
+- returnType="void"/>
+- <operation name="reinit"
+- description="Init and Destroy"
+- impact="ACTION"
+- returnType="void" />
+- <operation name="init"
+- description="Init"
+- impact="ACTION"
+- returnType="void" />
+- <operation name="destroy"
+- description="Destroy"
+- impact="ACTION"
+- returnType="void" />
+- <operation name="resetCounters"
+- description="reset request counter"
+- impact="ACTION"
+- returnType="void"/>
+-
+-
+- </mbean>
+-
+- <mbean name="JkWorkerEnv"
+- description="Worker env for jk"
+- domain="Catalina"
+- group="Jk"
+- type="org.apache.jk.core.WorkerEnv">
+-
+- <attribute name="localId"
+- description="If automatic port allocation is enabled, ChannelSocket will allocate ports sequentially. This is the sequence number"
+- type="java.lang.Integer"/>
+-
+- <attribute name="jkHome"
+- description="Base directory for jk"
+- type="java.lang.String"/>
+-
+- <attribute name="managedResource"
+- description="Access to the object"
+- type="java.lang.Object" writeable="false" />
+-
+- <attribute name="handlersObjectName"
+- description="List of all jk handlers"
+- type="[Ljavax.management.ObjectName;"/>
+-
+- <operation name="addHandler"
+- description="add a jk component"
+- returnType="void">
+- <parameter name="name"
+- description="local name"
+- type="java.lang.String"/>
+- <parameter name="handler"
+- description="handler"
+- type="org.apache.jk.core.JkHandler"/>
+- </operation>
+-
+- </mbean>
+-
+- <!-- Native connectors -->
+- <mbean name="JkAjp13"
+- description="native Ajp13 connector"
+- domain="Catalina"
+- group="Jk"
+- type="org.apache.jk.modjk.ajp13">
+-
+- <attribute name="Id"
+- description="Internal id"
+- type="java.lang.String"/>
+-
+- <attribute name="disabled"
+- description="State"
+- type="java.lang.Integer"/>
+-
+- <attribute name="ver"
+- description="Generation"
+- type="java.lang.Integer"/>
+-
+- <attribute name="debug"
+- description="Debug level"
+- type="java.lang.Integer"/>
+-
+- <attribute name="lb_factor"
+- description=""
+- type="java.lang.Integer"/>
+- <attribute name="lb_value"
+- description=""
+- type="java.lang.Integer"/>
+- <attribute name="epCount"
+- description=""
+- type="java.lang.Integer"/>
+- <attribute name="graceful"
+- description=""
+- type="java.lang.Integer"/>
+-
+- <attribute name="route"
+- description=""
+- type="java.lang.String"/>
+-
+- </mbean>
+-
+- <mbean name="JkChannelSocket"
+- description="native Ajp13 connector"
+- domain="Catalina"
+- group="Jk"
+- type="org.apache.jk.modjk.channel.socket">
+-
+- <attribute name="Id"
+- description="Internal id"
+- type="java.lang.String"/>
+-
+- <attribute name="disabled"
+- description="State"
+- type="java.lang.Integer"/>
+-
+- <attribute name="ver"
+- description="Generation"
+- type="java.lang.Integer"/>
+-
+- <attribute name="debug"
+- description="Debug level"
+- type="java.lang.Integer"/>
+-
+- </mbean>
+-
+- <mbean name="JkWorkerEnv"
+- description=""
+- domain="Catalina"
+- group="Jk"
+- type="org.apache.jk.modjk.workerEnv">
+-
+- <attribute name="Id"
+- description="Internal id"
+- type="java.lang.String"/>
+-
+- <attribute name="disabled"
+- description="State"
+- type="java.lang.Integer"/>
+-
+- <attribute name="ver"
+- description="Generation"
+- type="java.lang.Integer"/>
+-
+- <attribute name="debug"
+- description="Debug level"
+- type="java.lang.Integer"/>
+-
+- </mbean>
+-
+- <mbean name="JkLoggerApache2"
+- description=""
+- domain="Catalina"
+- group="Jk"
+- type="org.apache.jk.modjk.logger.apache2">
+- <attribute name="Id"
+- description="Internal id"
+- type="java.lang.String"/>
+-
+- <attribute name="disabled"
+- description="State"
+- type="java.lang.Integer"/>
+-
+- <attribute name="ver"
+- description="Generation"
+- type="java.lang.Integer"/>
+-
+- <attribute name="debug"
+- description="Debug level"
+- type="java.lang.Integer"/>
+-
+-
+- </mbean>
+-
+- <mbean name="JkUriMap"
+- description=""
+- domain="Catalina"
+- group="Jk"
+- type="org.apache.jk.modjk.uriMap">
+- <attribute name="Id"
+- description="Internal id"
+- type="java.lang.String"/>
+-
+- <attribute name="disabled"
+- description="State"
+- type="java.lang.Integer"/>
+-
+- <attribute name="ver"
+- description="Generation"
+- type="java.lang.Integer"/>
+-
+- <attribute name="debug"
+- description="Debug level"
+- type="java.lang.Integer"/>
+-
+-
+- </mbean>
+-
+- <mbean name="JkConfig"
+- description=""
+- domain="Catalina"
+- group="Jk"
+- type="org.apache.jk.modjk.config">
+- <attribute name="Id"
+- description="Internal id"
+- type="java.lang.String"/>
+-
+- <attribute name="disabled"
+- description="State"
+- type="java.lang.Integer"/>
+-
+- <attribute name="ver"
+- description="Generation"
+- type="java.lang.Integer"/>
+-
+- <attribute name="debug"
+- description="Debug level"
+- type="java.lang.Integer"/>
+-
+- <attribute name="file"
+- description="Config file"
+- type="java.lang.String"/>
+-
+- </mbean>
+- <mbean name="JkShm"
+- description=""
+- domain="Catalina"
+- group="Jk"
+- type="org.apache.jk.modjk.shm">
+- <attribute name="Id"
+- description="Internal id"
+- type="java.lang.String"/>
+-
+- <attribute name="disabled"
+- description="State"
+- type="java.lang.Integer"/>
+-
+- <attribute name="ver"
+- description="Generation"
+- type="java.lang.Integer"/>
+-
+- <attribute name="debug"
+- description="Debug level"
+- type="java.lang.Integer"/>
+-
+-
+- </mbean>
+- <mbean name="JkUri"
+- description=""
+- domain="Catalina"
+- group="Jk"
+- type="org.apache.jk.modjk.uri">
+-
+- <attribute name="Id"
+- description="Internal id"
+- type="java.lang.String"/>
+-
+- <attribute name="disabled"
+- description="State"
+- type="java.lang.Integer"/>
+-
+- <attribute name="ver"
+- description="Generation"
+- type="java.lang.Integer"/>
+-
+- <attribute name="debug"
+- description="Debug level"
+- type="java.lang.Integer"/>
+-
+- <attribute name="host"
+- description="Uri components"
+- type="java.lang.String"/>
+- <attribute name="uri"
+- description="Uri"
+- type="java.lang.String"/>
+- <attribute name="path"
+- description="Uri"
+- type="java.lang.String"/>
+-
+- </mbean>
+-
+- <mbean name="JkVm"
+- description=""
+- domain="Catalina"
+- group="Jk"
+- type="org.apache.jk.modjk.vm">
+- <attribute name="Id"
+- description="Internal id"
+- type="java.lang.String"/>
+-
+- <attribute name="disabled"
+- description="State"
+- type="java.lang.Integer"/>
+-
+- <attribute name="ver"
+- description="Generation"
+- type="java.lang.Integer"/>
+-
+- <attribute name="debug"
+- description="Debug level"
+- type="java.lang.Integer"/>
+-
+-
+- </mbean>
+-
+- <mbean name="JkChannelUn"
+- description=""
+- domain="Catalina"
+- group="Jk"
+- type="org.apache.jk.modjk.channel.un">
+- <attribute name="Id"
+- description="Internal id"
+- type="java.lang.String"/>
+-
+- <attribute name="disabled"
+- description="State"
+- type="java.lang.Integer"/>
+-
+- <attribute name="ver"
+- description="Generation"
+- type="java.lang.Integer"/>
+-
+- <attribute name="debug"
+- description="Debug level"
+- type="java.lang.Integer"/>
+-
+-
+- </mbean>
+-
+- <mbean name="JkChannelJni"
+- description=""
+- domain="Catalina"
+- group="Jk"
+- type="org.apache.jk.modjk.channel.jni">
+- <attribute name="Id"
+- description="Internal id"
+- type="java.lang.String"/>
+-
+- <attribute name="disabled"
+- description="State"
+- type="java.lang.Integer"/>
+-
+- <attribute name="ver"
+- description="Generation"
+- type="java.lang.Integer"/>
+-
+- <attribute name="debug"
+- description="Debug level"
+- type="java.lang.Integer"/>
+-
+-
+- </mbean>
+-
+- <mbean name="JkWorkerJni"
+- description=""
+- domain="Catalina"
+- group="Jk"
+- type="org.apache.jk.modjk.worker.jni">
+- <attribute name="Id"
+- description="Internal id"
+- type="java.lang.String"/>
+-
+- <attribute name="disabled"
+- description="State"
+- type="java.lang.Integer"/>
+-
+- <attribute name="ver"
+- description="Generation"
+- type="java.lang.Integer"/>
+-
+- <attribute name="debug"
+- description="Debug level"
+- type="java.lang.Integer"/>
+-
+-
+- </mbean>
+-
+- <mbean name="JkStatus"
+- description=""
+- domain="Catalina"
+- group="Jk"
+- type="org.apache.jk.modjk.status">
+- <attribute name="Id"
+- description="Internal id"
+- type="java.lang.String"/>
+-
+- <attribute name="disabled"
+- description="State"
+- type="java.lang.Integer"/>
+-
+- <attribute name="ver"
+- description="Generation"
+- type="java.lang.Integer"/>
+-
+- <attribute name="debug"
+- description="Debug level"
+- type="java.lang.Integer"/>
+-
+-
+- </mbean>
+- <mbean name="JkHandlerResponse"
+- description=""
+- domain="Catalina"
+- group="Jk"
+- type="org.apache.jk.modjk.handler.response">
+- <attribute name="Id"
+- description="Internal id"
+- type="java.lang.String"/>
+-
+- <attribute name="disabled"
+- description="State"
+- type="java.lang.Integer"/>
+-
+- <attribute name="ver"
+- description="Generation"
+- type="java.lang.Integer"/>
+-
+- <attribute name="debug"
+- description="Debug level"
+- type="java.lang.Integer"/>
+-
+-
+- </mbean>
+- <mbean name="JkHandlerLogon"
+- description=""
+- domain="Catalina"
+- group="Jk"
+- type="org.apache.jk.modjk.handler.logon">
+- <attribute name="Id"
+- description="Internal id"
+- type="java.lang.String"/>
+-
+- <attribute name="disabled"
+- description="State"
+- type="java.lang.Integer"/>
+-
+- <attribute name="ver"
+- description="Generation"
+- type="java.lang.Integer"/>
+-
+- <attribute name="debug"
+- description="Debug level"
+- type="java.lang.Integer"/>
+-
+-
+- </mbean>
+-
+- <mbean name="JkLb"
+- description=""
+- domain="Catalina"
+- group="Jk"
+- type="org.apache.jk.modjk.lb">
+-
+- <attribute name="Id"
+- description="Internal id"
+- type="java.lang.String"/>
+-
+- <attribute name="disabled"
+- description="State"
+- type="java.lang.Integer"/>
+-
+- <attribute name="ver"
+- description="Generation"
+- type="java.lang.Integer"/>
+-
+- <attribute name="debug"
+- description="Debug level"
+- type="java.lang.Integer"/>
+-
+- </mbean>
+-
+-
+-
+-</mbeans-descriptors>
+Index: java/org/apache/jk/config/BaseJkConfig.java
+===================================================================
+--- java/org/apache/jk/config/BaseJkConfig.java (revision 590752)
++++ java/org/apache/jk/config/BaseJkConfig.java (working copy)
+@@ -1,516 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.config;
+-
+-import java.io.File;
+-import java.io.IOException;
+-import java.io.PrintWriter;
+-
+-import org.apache.catalina.Container;
+-import org.apache.catalina.Context;
+-import org.apache.catalina.Engine;
+-import org.apache.catalina.Host;
+-import org.apache.catalina.Lifecycle;
+-import org.apache.catalina.LifecycleEvent;
+-import org.apache.catalina.LifecycleListener;
+-import org.apache.catalina.Server;
+-
+-
+-/**
+- Base class for automatic jk based configurations based on
+- the Tomcat server.xml settings and the war contexts
+- initialized during startup.
+- <p>
+- This config interceptor is enabled by inserting a Config
+- element in the <b><ContextManager></b> tag body inside
+- the server.xml file like so:
+- <pre>
+- * < ContextManager ... >
+- * ...
+- * <<b>???Config</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>
+- <li><b>workersConfig</b> - path to workers.properties file used by
+- jk connector. If not set, defaults to
+- "conf/jk/workers.properties".</li>
+- <li><b>jkLog</b> - path to log file to be used by jk connector.</li>
+- <li><b>jkDebug</b> - Loglevel setting. May be debug, info, error, or emerg.
+- If not set, defaults to emerg.</li>
+- <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>
+- <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 Apache serve
+- static resources. The default is true.
+- Warning: When false, some configuration in
+- the web.xml may not be duplicated in Apache.
+- Review the mod_jk conf file to see what
+- configuration is actually being set in Apache.</li>
+- <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 may also
+- need to modify the web server to point it's home
+- directory to Tomcat's root context directory.
+- Otherwise some content, such as the root index.html,
+- may be served by the web server before the connector
+- gets a chance to claim the request and pass it to Tomcat.
+- The default is true.</li>
+- </ul>
+- <p>
+- @author Costin Manolache
+- @author Larry Isaacs
+- @author Bill Barker
+- @version $Revision$
+- */
+-public class BaseJkConfig implements LifecycleListener {
+- private static org.apache.juli.logging.Log log =
+- org.apache.juli.logging.LogFactory.getLog(BaseJkConfig.class);
+-
+- protected File configHome = null;
+- protected File workersConfig = null;
+-
+- protected File jkLog = null;
+- protected String jkDebug="emerg";
+- protected String jkWorker = "ajp13";
+-
+- protected boolean noRoot=true;
+- protected boolean forwardAll=true;
+-
+- protected String tomcatHome;
+- protected boolean regenerate=false;
+- protected boolean append=false;
+- protected boolean legacy=true;
+-
+- // -------------------- Tomcat callbacks --------------------
+-
+-
+- // Auto-config should be able to react to dynamic config changes,
+- // and regenerate the config.
+-
+- /**
+- * Generate the configuration - only when the server is
+- * completely initialized ( before starting )
+- */
+- public void lifecycleEvent(LifecycleEvent evt) {
+- if(Lifecycle.START_EVENT.equals(evt.getType())) {
+- execute( evt );
+- }
+- }
+-
+- /**
+- * Generate configuration files. Override with method to generate
+- * web server specific configuration.
+- */
+- public void execute(LifecycleEvent evt) {
+- initProperties();
+- PrintWriter mod_jk = null;
+- try {
+- mod_jk = getWriter();
+- } catch(IOException iex) {
+- log.warn("Unable to open config file");
+- return;
+- }
+- Lifecycle who = evt.getLifecycle();
+- if( who instanceof Server ) {
+- executeServer((Server)who, mod_jk);
+- } else if(who instanceof Engine) {
+- executeEngine((Engine)who, mod_jk);
+- } else if ( who instanceof Host ) {
+- executeHost((Host)who, mod_jk);
+- } else if( who instanceof Context ) {
+- executeContext((Context)who, mod_jk);
+- }
+- mod_jk.close();
+- }
+- /**
+- * Generate configuration files. Override with method to generate
+- * web server specific configuration.
+- */
+- public void executeServer(Server svr, PrintWriter mod_jk) {
+- if(! append ) {
+- if( ! generateJkHead(mod_jk) )
+- return;
+- generateSSLConfig(mod_jk);
+- generateJkTail(mod_jk);
+- }
+- }
+-
+- /**
+- * Generate SSL options
+- */
+- protected void generateSSLConfig(PrintWriter mod_jk) {
+- }
+-
+- /**
+- * Generate general options
+- */
+- protected boolean generateJkHead(PrintWriter mod_jk) {
+- return true;
+- }
+-
+- /**
+- * Generate general options
+- */
+- protected void generateJkTail(PrintWriter mod_jk) {
+- }
+-
+- /**
+- * Generate Virtual Host start
+- */
+- protected void generateVhostHead(Host host, PrintWriter mod_jk) {
+- }
+-
+- /**
+- * Generate Virtual Host end
+- */
+- protected void generateVhostTail(Host host, PrintWriter mod_jk) {
+- }
+-
+- /**
+- * Generate configuration files. Override with method to generate
+- * web server specific configuration.
+- */
+- protected void executeEngine(Engine egn, PrintWriter mod_jk) {
+- if(egn.getJvmRoute() != null) {
+- jkWorker = egn.getJvmRoute();
+- }
+- executeServer(egn.getService().getServer(), mod_jk);
+- Container [] children = egn.findChildren();
+- for(int ii=0; ii < children.length; ii++) {
+- if( children[ii] instanceof Host ) {
+- executeHost((Host)children[ii], mod_jk);
+- } else if( children[ii] instanceof Context ) {
+- executeContext((Context)children[ii], mod_jk);
+- }
+- }
+- }
+- /**
+- * Generate configuration files. Override with method to generate
+- * web server specific configuration.
+- */
+- protected void executeHost(Host hst, PrintWriter mod_jk) {
+- generateVhostHead(hst, mod_jk);
+- Container [] children = hst.findChildren();
+- for(int ii=0; ii < children.length; ii++) {
+- if(children[ii] instanceof Context) {
+- executeContext((Context)children[ii],mod_jk);
+- }
+- }
+- generateVhostTail(hst, mod_jk);
+- }
+- /**
+- * executes the ApacheConfig interceptor. This method generates apache
+- * configuration files for use with mod_jk.
+- * @param context a Context object.
+- * @param mod_jk Writer for output.
+- */
+- public void executeContext(Context context, PrintWriter mod_jk){
+-
+- if(context.getPath().length() > 0 || ! noRoot ) {
+- String docRoot = context.getServletContext().getRealPath("/");
+- if( forwardAll || docRoot == null)
+- generateStupidMappings( context, mod_jk );
+- else
+- generateContextMappings( context, mod_jk);
+- }
+- }
+-
+- protected void generateStupidMappings(Context context, PrintWriter mod_jk){
+- }
+- protected void generateContextMappings(Context context, PrintWriter mod_jk){
+- }
+-
+- /**
+- * Get the output Writer. Override with method to generate
+- * web server specific configuration.
+- */
+- protected PrintWriter getWriter() throws IOException {
+- return null;
+- }
+-
+- /**
+- * Get the host associated with this Container (if any).
+- */
+- protected Host getHost(Container child) {
+- while(child != null && ! (child instanceof Host) ) {
+- child = child.getParent();
+- }
+- return (Host)child;
+- }
+-
+- //-------------------- Properties --------------------
+-
+- /**
+- * Append to config file.
+- * Set to <code>true</code> if the config information should be
+- * appended.
+- */
+- public void setAppend(boolean apnd) {
+- append = apnd;
+- }
+-
+- /**
+- * If false, we'll try to generate a config that will
+- * let apache serve static files.
+- * The default is true, forward all requests in a context
+- * to tomcat.
+- */
+- public void setForwardAll( boolean b ) {
+- forwardAll=b;
+- }
+-
+- /**
+- * Special option - do not generate mappings for the ROOT
+- * context. The default is true, and will not generate the mappings,
+- * not redirecting all pages to tomcat (since /* matches everything).
+- * This means that the web server's root remains intact but isn't
+- * completely servlet/JSP enabled. If the ROOT webapp can be configured
+- * with the web server serving static files, there's no problem setting
+- * this option to false. If not, then setting it true means the web
+- * server will be out of picture for all requests.
+- */
+- public void setNoRoot( boolean b ) {
+- noRoot=b;
+- }
+-
+- /**
+- * set a path to the parent directory of the
+- * conf folder. That is, the parent directory
+- * within which path setters would be resolved against,
+- * if relative. For example if ConfigHome is set to "/home/tomcat"
+- * and regConfig is set to "conf/mod_jk.conf" then the resulting
+- * path used would be:
+- * "/home/tomcat/conf/mod_jk.conf".</p>
+- * <p>
+- * However, if the path is set to an absolute path,
+- * this attribute is ignored.
+- * <p>
+- * If not set, execute() will set this to TOMCAT_HOME.
+- * @param dir - path to a directory
+- */
+- public void setConfigHome(String dir){
+- if( dir==null ) return;
+- File f=new File(dir);
+- if(!f.isDirectory()){
+- throw new IllegalArgumentException(
+- "BaseConfig.setConfigHome(): "+
+- "Configuration Home must be a directory! : "+dir);
+- }
+- configHome = f;
+- }
+-
+- /**
+- * set a path to the workers.properties file.
+- * @param path String path to workers.properties file
+- */
+- public void setWorkersConfig(String path){
+- workersConfig= (path==null?null:new File(path));
+- }
+-
+- /**
+- * set the path to the log file
+- * @param path String path to a file
+- */
+- public void setJkLog(String path){
+- jkLog = ( path==null ? null : new File(path));
+- }
+-
+- /**
+- * Set the verbosity level
+- * ( use debug, error, etc. ) If not set, no log is written.
+- */
+- public void setJkDebug( String level ) {
+- jkDebug=level;
+- }
+-
+- /**
+- * Sets the JK worker.
+- * @param worker The worker
+- */
+- public void setJkWorker(String worker){
+- jkWorker = worker;
+- }
+-
+- public void setLegacy(boolean legacy) {
+- this.legacy = legacy;
+- }
+-
+- // -------------------- Initialize/guess defaults --------------------
+-
+- /**
+- * Initialize defaults for properties that are not set
+- * explicitely
+- */
+- protected void initProperties() {
+- tomcatHome = System.getProperty("catalina.home");
+- File tomcatDir = new File(tomcatHome);
+- if(configHome==null){
+- configHome=tomcatDir;
+- }
+- }
+-
+- // -------------------- Config Utils --------------------
+-
+-
+- /**
+- * Add an extension mapping. Override with method to generate
+- * web server specific configuration
+- */
+- protected boolean addExtensionMapping( String ctxPath, String ext,
+- PrintWriter pw ) {
+- return true;
+- }
+-
+-
+- /**
+- * Add a fulling specified mapping. Override with method to generate
+- * web server specific configuration
+- */
+- protected boolean addMapping( String fullPath, PrintWriter pw ) {
+- return true;
+- }
+-
+- // -------------------- General Utils --------------------
+-
+- protected String getAbsoluteDocBase(Context context) {
+- // Calculate the absolute path of the document base
+- String docBase = context.getServletContext().getRealPath("/");
+- docBase = docBase.substring(0,docBase.length()-1);
+- if (!isAbsolute(docBase)){
+- docBase = tomcatHome + "/" + docBase;
+- }
+- docBase = patch(docBase);
+- return docBase;
+- }
+-
+- // ------------------ Grabbed from FileUtil -----------------
+- public static File getConfigFile( File base, File configDir, String defaultF )
+- {
+- if( base==null )
+- base=new File( defaultF );
+- if( ! base.isAbsolute() ) {
+- if( configDir != null )
+- base=new File( configDir, base.getPath());
+- else
+- base=new File( base.getAbsolutePath()); //??
+- }
+- File parent=new File(base.getParent());
+- if(!parent.exists()){
+- if(!parent.mkdirs()){
+- throw new RuntimeException(
+- "Unable to create path to config file :"+
+- base.getAbsolutePath());
+- }
+- }
+- return base;
+- }
+-
+- public static String patch(String path) {
+- String patchPath = path;
+-
+- // Move drive spec to the front of the path
+- if (patchPath.length() >= 3 &&
+- patchPath.charAt(0) == '/' &&
+- Character.isLetter(patchPath.charAt(1)) &&
+- patchPath.charAt(2) == ':') {
+- patchPath=patchPath.substring(1,3)+"/"+patchPath.substring(3);
+- }
+-
+- // Eliminate consecutive slashes after the drive spec
+- if (patchPath.length() >= 2 &&
+- Character.isLetter(patchPath.charAt(0)) &&
+- patchPath.charAt(1) == ':') {
+- char[] ca = patchPath.replace('/', '\\').toCharArray();
+- char c;
+- StringBuffer sb = new StringBuffer();
+-
+- for (int i = 0; i < ca.length; i++) {
+- if ((ca[i] != '\\') ||
+- (ca[i] == '\\' &&
+- i > 0 &&
+- ca[i - 1] != '\\')) {
+- if (i == 0 &&
+- Character.isLetter(ca[i]) &&
+- i < ca.length - 1 &&
+- ca[i + 1] == ':') {
+- c = Character.toUpperCase(ca[i]);
+- } else {
+- c = ca[i];
+- }
+-
+- sb.append(c);
+- }
+- }
+-
+- patchPath = sb.toString();
+- }
+-
+- // fix path on NetWare - all '/' become '\\' and remove duplicate '\\'
+- if (System.getProperty("os.name").startsWith("NetWare") &&
+- path.length() >=3 &&
+- path.indexOf(':') > 0) {
+- char[] ca = patchPath.replace('/', '\\').toCharArray();
+- StringBuffer sb = new StringBuffer();
+-
+- for (int i = 0; i < ca.length; i++) {
+- if ((ca[i] != '\\') ||
+- (ca[i] == '\\' && i > 0 && ca[i - 1] != '\\')) {
+- sb.append(ca[i]);
+- }
+- }
+- patchPath = sb.toString();
+- }
+-
+- return patchPath;
+- }
+-
+- public static boolean isAbsolute( String path ) {
+- // normal file
+- if( path.startsWith("/" ) ) return true;
+-
+- if( path.startsWith(File.separator ) ) return true;
+-
+- // win c:
+- if (path.length() >= 3 &&
+- Character.isLetter(path.charAt(0)) &&
+- path.charAt(1) == ':')
+- return true;
+-
+- // NetWare volume:
+- if (System.getProperty("os.name").startsWith("NetWare") &&
+- path.length() >=3 &&
+- path.indexOf(':') > 0)
+- return true;
+-
+- return false;
+- }
+-}
+Index: java/org/apache/jk/config/WebXml2Jk.java
+===================================================================
+--- java/org/apache/jk/config/WebXml2Jk.java (revision 590752)
++++ java/org/apache/jk/config/WebXml2Jk.java (working copy)
+@@ -1,452 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.config;
+-
+-import java.io.File;
+-import java.io.IOException;
+-import java.io.StringReader;
+-import java.util.Hashtable;
+-import java.util.Vector;
+-
+-import javax.xml.parsers.DocumentBuilder;
+-import javax.xml.parsers.DocumentBuilderFactory;
+-import javax.xml.parsers.ParserConfigurationException;
+-
+-import org.apache.tomcat.util.IntrospectionUtils;
+-import org.w3c.dom.Document;
+-import org.w3c.dom.Node;
+-import org.xml.sax.EntityResolver;
+-import org.xml.sax.InputSource;
+-import org.xml.sax.SAXException;
+-
+-
+-/* Naming conventions:
+-
+-JK_CONF_DIR == serverRoot/work ( XXX /jkConfig ? )
+-
+-- Each vhost has a sub-dir named after the canonycal name
+-
+-- For each webapp in a vhost, there is a separate WEBAPP_NAME.jkmap
+-
+-- In httpd.conf ( or equivalent servers ), in each virtual host you
+-should "Include JK_CONF_DIR/VHOST/jk_apache.conf". The config
+-file will contain the Alias declarations and other rules required
+-for apache operation. Same for other servers.
+-
+-- WebXml2Jk will be invoked by a config tool or automatically for each
+-webapp - it'll generate the WEBAPP.jkmap files and config fragments.
+-
+-WebXml2Jk will _not_ generate anything else but mappings.
+-It should _not_ try to guess locations or anything else - that's
+-another components' job.
+-
+-*/
+-
+-/**
+- * Read a web.xml file and generate the mappings for jk2.
+- * It can be used from the command line or ant.
+- *
+- * In order for the web server to serve static pages, all webapps
+- * must be deployed on the computer that runs Apache, IIS, etc.
+- *
+- * Dynamic pages can be executed on that computer or other servers
+- * in a pool, but even if the main server doesn't run tomcat,
+- * it must have all the static files and WEB-INF/web.xml.
+- * ( you could have a script remove everything else, including jsps - if
+- * security paranoia is present ).
+- *
+- * XXX We could have this in WEB-INF/urimap.properties.
+- *
+- * @author Costin Manolache
+- */
+-public class WebXml2Jk {
+- String vhost="";
+- String cpath="";
+- String docBase;
+- String file;
+- String worker="lb";
+-
+- // -------------------- Settings --------------------
+-
+- // XXX We can also generate location-independent mappings.
+-
+- /** Set the canonycal name of the virtual host.
+- */
+- public void setHost( String vhost ) {
+- this.vhost=vhost;
+- }
+-
+- /** Set the canonical name of the virtual host.
+- */
+- public void setContext( String contextPath ) {
+- this.cpath=contextPath;
+- }
+-
+-
+- /** Set the base directory where the application is
+- * deployed ( on the web server ).
+- */
+- public void setDocBase(String docBase ) {
+- this.docBase=docBase;
+- }
+-
+- // Automatically generated.
+-// /** The file where the jk2 mapping will be generated
+-// */
+-// public void setJk2Conf( String outFile ) {
+-// file=outFile;
+-// type=CONFIG_JK2_URIMAP;
+-// }
+-
+-// /** Backward compat: generate JkMounts for mod_jk1
+-// */
+-// public void setJkmountFile( String outFile ) {
+-// file=outFile;
+-// type=CONFIG_JK_MOUNT;
+-// }
+-
+- /* By default we map to the lb - in jk2 this is automatically
+- * created and includes all tomcat instances.
+- *
+- * This is equivalent to the worker in jk1.
+- */
+- public void setGroup(String route ) {
+- worker=route;
+- }
+-
+- // -------------------- Generators --------------------
+- public static interface MappingGenerator {
+- void setWebXmlReader(WebXml2Jk wxml );
+-
+- /** Start section( vhost declarations, etc )
+- */
+- void generateStart() throws IOException ;
+-
+- void generateEnd() throws IOException ;
+-
+- void generateServletMapping( String servlet, String url )throws IOException ;
+- void generateFilterMapping( String servlet, String url ) throws IOException ;
+-
+- void generateLoginConfig( String loginPage,
+- String errPage, String authM ) throws IOException ;
+-
+- void generateErrorPage( int err, String location ) throws IOException ;
+-
+- void generateConstraints( Vector urls, Vector methods, Vector roles, boolean isSSL ) throws IOException ;
+- }
+-
+- // -------------------- Implementation --------------------
+- Node webN;
+- File jkDir;
+-
+- /** Return the top level node
+- */
+- public Node getWebXmlNode() {
+- return webN;
+- }
+-
+- public File getJkDir() {
+- return jkDir;
+- }
+-
+- /** Extract the wellcome files from the web.xml
+- */
+- public Vector getWellcomeFiles() {
+- Node n0=getChild( webN, "welcome-file-list" );
+- Vector wF=new Vector();
+- if( n0!=null ) {
+- for( Node mapN=getChild( webN, "welcome-file" );
+- mapN != null; mapN = getNext( mapN ) ) {
+- wF.addElement( getContent(mapN));
+- }
+- }
+- // XXX Add index.html, index.jsp
+- return wF;
+- }
+-
+-
+- void generate(MappingGenerator gen ) throws IOException {
+- gen.generateStart();
+- log.info("Generating mappings for servlets " );
+- for( Node mapN=getChild( webN, "servlet-mapping" );
+- mapN != null; mapN = getNext( mapN ) ) {
+-
+- String serv=getChildContent( mapN, "servlet-name");
+- String url=getChildContent( mapN, "url-pattern");
+-
+- gen.generateServletMapping( serv, url );
+- }
+-
+- log.info("Generating mappings for filters " );
+- for( Node mapN=getChild( webN, "filter-mapping" );
+- mapN != null; mapN = getNext( mapN ) ) {
+-
+- String filter=getChildContent( mapN, "filter-name");
+- String url=getChildContent( mapN, "url-pattern");
+-
+- gen.generateFilterMapping( filter, url );
+- }
+-
+-
+- for( Node mapN=getChild( webN, "error-page" );
+- mapN != null; mapN = getNext( mapN ) ) {
+- String errorCode= getChildContent( mapN, "error-code" );
+- String location= getChildContent( mapN, "location" );
+-
+- if( errorCode!=null && ! "".equals( errorCode ) ) {
+- try {
+- int err=new Integer( errorCode ).intValue();
+- gen.generateErrorPage( err, location );
+- } catch( Exception ex ) {
+- log.error( "Format error " + location, ex);
+- }
+- }
+- }
+-
+- Node lcN=getChild( webN, "login-config" );
+- if( lcN!=null ) {
+- log.info("Generating mapping for login-config " );
+-
+- String authMeth=getContent( getChild( lcN, "auth-method"));
+- if( authMeth == null ) authMeth="BASIC";
+-
+- Node n1=getChild( lcN, "form-login-config");
+- String loginPage= getChildContent( n1, "form-login-page");
+- String errPage= getChildContent( n1, "form-error-page");
+-
+- if(loginPage != null) {
+- int lpos = loginPage.lastIndexOf("/");
+- String jscurl = loginPage.substring(0,lpos+1) + "j_security_check";
+- gen.generateLoginConfig( jscurl, errPage, authMeth );
+- }
+- }
+-
+- log.info("Generating mappings for security constraints " );
+- for( Node mapN=getChild( webN, "security-constraint" );
+- mapN != null; mapN = getNext( mapN )) {
+-
+- Vector methods=new Vector();
+- Vector urls=new Vector();
+- Vector roles=new Vector();
+- boolean isSSL=false;
+-
+- Node wrcN=getChild( mapN, "web-resource-collection");
+- for( Node uN=getChild(wrcN, "http-method");
+- uN!=null; uN=getNext( uN )) {
+- methods.addElement( getContent( uN ));
+- }
+- for( Node uN=getChild(wrcN, "url-pattern");
+- uN!=null; uN=getNext( uN )) {
+- urls.addElement( getContent( uN ));
+- }
+-
+- // Not used at the moment
+- Node acN=getChild( mapN, "auth-constraint");
+- for( Node rN=getChild(acN, "role-name");
+- rN!=null; rN=getNext( rN )) {
+- roles.addElement(getContent( rN ));
+- }
+-
+- Node ucN=getChild( mapN, "user-data-constraint");
+- String transp=getContent(getChild( ucN, "transport-guarantee"));
+- if( transp!=null ) {
+- if( "INTEGRAL".equalsIgnoreCase( transp ) ||
+- "CONFIDENTIAL".equalsIgnoreCase( transp ) ) {
+- isSSL=true;
+- }
+- }
+-
+- gen.generateConstraints( urls, methods, roles, isSSL );
+- }
+- gen.generateEnd();
+- }
+-
+- // -------------------- Main and ant wrapper --------------------
+-
+- public void execute() {
+- try {
+- if( docBase== null) {
+- log.error("No docbase - please specify the base directory of you web application ( -docBase PATH )");
+- return;
+- }
+- if( cpath== null) {
+- log.error("No context - please specify the mount ( -context PATH )");
+- return;
+- }
+- File docbF=new File(docBase);
+- File wXmlF=new File( docBase, "WEB-INF/web.xml");
+-
+- Document wXmlN=readXml(wXmlF);
+- if( wXmlN == null ) return;
+-
+- webN = wXmlN.getDocumentElement();
+- if( webN==null ) {
+- log.error("Can't find web-app");
+- return;
+- }
+-
+- jkDir=new File( docbF, "WEB-INF/jk2" );
+- jkDir.mkdirs();
+-
+- MappingGenerator generator=new GeneratorJk2();
+- generator.setWebXmlReader( this );
+- generate( generator );
+-
+- generator=new GeneratorJk1();
+- generator.setWebXmlReader( this );
+- generate( generator );
+-
+- generator=new GeneratorApache2();
+- generator.setWebXmlReader( this );
+- generate( generator );
+-
+- } catch( Exception ex ) {
+- ex.printStackTrace();
+- }
+- }
+-
+-
+- public static void main(String args[] ) {
+- try {
+- if( args.length == 1 &&
+- ( "-?".equals(args[0]) || "-h".equals( args[0])) ) {
+- System.out.println("Usage: ");
+- System.out.println(" WebXml2Jk [OPTIONS]");
+- System.out.println();
+- System.out.println(" -docBase DIR The location of the webapp. Required");
+- System.out.println(" -group GROUP Group, if you have multiple tomcats with diffrent content. " );
+- System.out.println(" The default is 'lb', and should be used in most cases");
+- System.out.println(" -host HOSTNAME Canonical hostname - for virtual hosts");
+- System.out.println(" -context /CPATH Context path where the app will be mounted");
+- return;
+- }
+-
+- WebXml2Jk w2jk=new WebXml2Jk();
+-
+- /* do ant-style property setting */
+- IntrospectionUtils.processArgs( w2jk, args, new String[] {},
+- null, new Hashtable());
+- w2jk.execute();
+- } catch( Exception ex ) {
+- ex.printStackTrace();
+- }
+-
+- }
+-
+- private static org.apache.juli.logging.Log log=
+- org.apache.juli.logging.LogFactory.getLog( WebXml2Jk.class );
+-
+-
+- // -------------------- DOM utils --------------------
+-
+- /** Get the content of a node
+- */
+- public static String getContent(Node n ) {
+- if( n==null ) return null;
+- Node n1=n.getFirstChild();
+- // XXX Check if it's a text node
+-
+- String s1=n1.getNodeValue();
+- return s1.trim();
+- }
+-
+- /** Get the first child
+- */
+- public static Node getChild( Node parent, String name ) {
+- if( parent==null ) return null;
+- Node first=parent.getFirstChild();
+- if( first==null ) return null;
+- for (Node node = first; node != null;
+- node = node.getNextSibling()) {
+- //System.out.println("getNode: " + name + " " + node.getNodeName());
+- if( name.equals( node.getNodeName() ) ) {
+- return node;
+- }
+- }
+- return null;
+- }
+-
+- /** Get the first child's content ( i.e. it's included TEXT node )
+- */
+- public static String getChildContent( Node parent, String name ) {
+- Node first=parent.getFirstChild();
+- if( first==null ) return null;
+- for (Node node = first; node != null;
+- node = node.getNextSibling()) {
+- //System.out.println("getNode: " + name + " " + node.getNodeName());
+- if( name.equals( node.getNodeName() ) ) {
+- return getContent( node );
+- }
+- }
+- return null;
+- }
+-
+- /** Get the node in the list of siblings
+- */
+- public static Node getNext( Node current ) {
+- Node first=current.getNextSibling();
+- String name=current.getNodeName();
+- if( first==null ) return null;
+- for (Node node = first; node != null;
+- node = node.getNextSibling()) {
+- //System.out.println("getNode: " + name + " " + node.getNodeName());
+- if( name.equals( node.getNodeName() ) ) {
+- return node;
+- }
+- }
+- return null;
+- }
+-
+- public static class NullResolver implements EntityResolver {
+- public InputSource resolveEntity (String publicId,
+- String systemId)
+- throws SAXException, IOException
+- {
+- if (log.isDebugEnabled())
+- log.debug("ResolveEntity: " + publicId + " " + systemId);
+- return new InputSource(new StringReader(""));
+- }
+- }
+-
+- public static Document readXml(File xmlF)
+- throws SAXException, IOException, ParserConfigurationException
+- {
+- if( ! xmlF.exists() ) {
+- log.error("No xml file " + xmlF );
+- return null;
+- }
+- DocumentBuilderFactory dbf =
+- DocumentBuilderFactory.newInstance();
+-
+- dbf.setValidating(false);
+- dbf.setIgnoringComments(false);
+- dbf.setIgnoringElementContentWhitespace(true);
+- //dbf.setCoalescing(true);
+- //dbf.setExpandEntityReferences(true);
+-
+- DocumentBuilder db = null;
+- db = dbf.newDocumentBuilder();
+- db.setEntityResolver( new NullResolver() );
+-
+- // db.setErrorHandler( new MyErrorHandler());
+-
+- Document doc = db.parse(xmlF);
+- return doc;
+- }
+-
+-}
+Index: java/org/apache/jk/config/NSConfig.java
+===================================================================
+--- java/org/apache/jk/config/NSConfig.java (revision 590752)
++++ java/org/apache/jk/config/NSConfig.java (working copy)
+@@ -1,329 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.config;
+-
+-import java.io.File;
+-import java.io.FileWriter;
+-import java.io.IOException;
+-import java.io.PrintWriter;
+-import java.util.Date;
+-
+-import org.apache.catalina.Context;
+-
+-
+-/**
+- Generates automatic Netscape nsapi_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 NSConfig
+- element in the <b><ContextManager></b> tag body inside
+- the server.xml file like so:
+- <pre>
+- * < ContextManager ... >
+- * ...
+- * <<b>NSConfig</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>
+- <li><b>objConfig</b> - path to use for writing Netscape obj.conf
+- file. If not set, defaults to
+- "conf/auto/obj.conf".</li>
+- <li><b>objectName</b> - Name of the Object to execute the requests.
+- Defaults to "servlet".</li>
+- <li><b>workersConfig</b> - path to workers.properties file used by
+- nsapi_redirect. If not set, defaults to
+- "conf/jk/workers.properties".</li>
+- <li><b>nsapiJk</b> - path to Netscape mod_jk plugin file. If not set,
+- defaults to "bin/nsapi_redirect.dll" on windows,
+- "bin/nsapi_rd.nlm" on netware, and
+- "bin/nsapi_redirector.so" everywhere else.</li>
+- <li><b>jkLog</b> - path to log file to be used by nsapi_redirect.</li>
+- <li><b>jkDebug</b> - Loglevel setting. May be debug, info, error, or emerg.
+- If not set, defaults to emerg.</li>
+- <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>
+- <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 Netscape 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 Netscape.
+- Review the uriworkermap file to see what
+- configuration is actually being set in Netscape.</li>
+- <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 Netscape
+- to point to Tomcat's root context directory.
+- Otherwise some content, such as the root index.html,
+- will be served by Netscape before nsapi_redirect gets a chance
+- to claim the request and pass it to Tomcat.
+- The default is true.</li>
+- </ul>
+- <p>
+- @author Costin Manolache
+- @author Larry Isaacs
+- @author Gal Shachor
+- @author Bill Barker
+- */
+-public class NSConfig extends BaseJkConfig {
+- private static org.apache.juli.logging.Log log =
+- org.apache.juli.logging.LogFactory.getLog(NSConfig.class);
+-
+- public static final String WORKERS_CONFIG = "/conf/jk/workers.properties";
+- public static final String NS_CONFIG = "/conf/auto/obj.conf";
+- public static final String NSAPI_LOG_LOCATION = "/logs/nsapi_redirect.log";
+- /** default location of nsapi plug-in. */
+- public static final String NSAPI_REDIRECTOR;
+-
+- //set up some defaults based on OS type
+- static{
+- String os = System.getProperty("os.name").toLowerCase();
+- if(os.indexOf("windows")>=0){
+- NSAPI_REDIRECTOR = "bin/nsapi_redirect.dll";
+- }else if(os.indexOf("netware")>=0){
+- NSAPI_REDIRECTOR = "bin/nsapi_rd.nlm";
+- }else{
+- NSAPI_REDIRECTOR = "bin/nsapi_redirector.so";
+- }
+- }
+-
+- private File objConfig = null;
+- private File nsapiJk = null;
+- private String objectName = "servlet";
+-
+- public NSConfig()
+- {
+- }
+-
+- //-------------------- 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 setObjConfig(String path) {
+- objConfig= (path==null)?null:new File(path);
+- }
+-
+- /**
+- set the path to the nsapi plugin module
+- @param path String path to a file
+- */
+- public void setNsapiJk(String path) {
+- nsapiJk=( path==null?null:new File(path));
+- }
+-
+- /**
+- Set the name for the Object that implements the
+- jk_service call.
+- @param name Name of the obj.conf Object
+- */
+- public void setObjectName(String name) {
+- objectName = name;
+- }
+-
+- // -------------------- Initialize/guess defaults --------------------
+-
+- /** Initialize defaults for properties that are not set
+- explicitely
+- */
+- protected void initProperties() {
+- super.initProperties();
+-
+- objConfig=getConfigFile( objConfig, configHome, NS_CONFIG);
+- workersConfig=getConfigFile( workersConfig, configHome, WORKERS_CONFIG);
+-
+- if( nsapiJk == null )
+- nsapiJk=new File(NSAPI_REDIRECTOR);
+- else
+- nsapiJk =getConfigFile( nsapiJk, configHome, NSAPI_REDIRECTOR );
+- jkLog=getConfigFile( jkLog, configHome, NSAPI_LOG_LOCATION);
+- }
+-
+- // -------------------- Generate config --------------------
+- protected PrintWriter getWriter() throws IOException {
+- String abObjConfig = objConfig.getAbsolutePath();
+- return new PrintWriter(new FileWriter(abObjConfig,append));
+- }
+- protected boolean generateJkHead(PrintWriter mod_jk) {
+- log.info("Generating netscape web server config = "+objConfig );
+-
+- generateNsapiHead( mod_jk );
+-
+- mod_jk.println("<Object name=default>");
+- return true;
+- }
+-
+- private void generateNsapiHead(PrintWriter objfile)
+- {
+- objfile.println("###################################################################");
+- objfile.println("# Auto generated configuration. Dated: " + new Date());
+- objfile.println("###################################################################");
+- objfile.println();
+-
+- objfile.println("#");
+- objfile.println("# You will need to merge the content of this file with your ");
+- objfile.println("# regular obj.conf and then restart (=stop + start) your Netscape server. ");
+- objfile.println("#");
+- objfile.println();
+-
+- objfile.println("#");
+- objfile.println("# Loading the redirector into your server");
+- objfile.println("#");
+- objfile.println();
+- objfile.println("Init fn=\"load-modules\" funcs=\"jk_init,jk_service\" shlib=\"<put full path to the redirector here>\"");
+- objfile.println("Init fn=\"jk_init\" worker_file=\"" +
+- workersConfig.toString().replace('\\', '/') +
+- "\" log_level=\"" + jkDebug + "\" log_file=\"" +
+- jkLog.toString().replace('\\', '/') +
+- "\"");
+- objfile.println();
+- }
+-
+- protected void generateJkTail(PrintWriter objfile)
+- {
+- objfile.println();
+- objfile.println("#######################################################");
+- objfile.println("# Protecting the WEB-INF and META-INF directories.");
+- objfile.println("#######################################################");
+- objfile.println("PathCheck fn=\"deny-existence\" path=\"*/WEB-INF/*\"");
+- objfile.println("PathCheck fn=\"deny-existence\" path=\"*/META-INF/*\"");
+- objfile.println();
+-
+- objfile.println("</Object>");
+- objfile.println();
+-
+- objfile.println("#######################################################");
+- objfile.println("# New object to execute your servlet requests.");
+- objfile.println("#######################################################");
+- objfile.println("<Object name=" + objectName + ">");
+- objfile.println("ObjectType fn=force-type type=text/html");
+- objfile.println("Service fn=\"jk_service\" worker=\""+ jkWorker + "\" path=\"/*\"");
+- objfile.println("</Object>");
+- objfile.println();
+- }
+-
+- // -------------------- Forward all mode --------------------
+-
+- /** Forward all requests for a context to tomcat.
+- The default.
+- */
+- protected void generateStupidMappings(Context context, PrintWriter objfile )
+- {
+- String ctxPath = context.getPath();
+- String nPath=("".equals(ctxPath)) ? "/" : ctxPath;
+-
+- if( noRoot && "".equals(ctxPath) ) {
+- log.debug("Ignoring root context in forward-all mode ");
+- return;
+- }
+- objfile.println("<Object name=" + context.getName() + ">");
+-
+- objfile.println("NameTrans fn=\"assign-name\" from=\"" + ctxPath + "\" name=\"" + objectName + "\"");
+- objfile.println("NameTrans fn=\"assign-name\" from=\"" + ctxPath + "/*\" name=\"" + objectName + "\"");
+- objfile.println("</Object>");
+- }
+-
+-
+- // -------------------- Netscape serves static mode --------------------
+- // This is not going to work for all apps. We fall back to stupid mode.
+-
+- protected void generateContextMappings(Context context, PrintWriter objfile )
+- {
+- String ctxPath = context.getPath();
+- String nPath=("".equals(ctxPath)) ? "/" : ctxPath;
+-
+- if( noRoot && "".equals(ctxPath) ) {
+- log.debug("Ignoring root context in non-forward-all mode ");
+- return;
+- }
+- objfile.println("<Object name=" + context.getName() + ">");
+- // Static files will be served by Netscape
+- objfile.println("#########################################################");
+- objfile.println("# Auto configuration for the " + nPath + " context starts.");
+- objfile.println("#########################################################");
+- objfile.println();
+-
+- // XXX Need to determine what if/how static mappings are done
+-
+- // 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, objfile);
+- }
+- }
+-
+- String [] servletMaps=context.findServletMappings();
+- for(int ii=0; ii < servletMaps.length; ii++) {
+- addMapping( ctxPath , servletMaps[ii] , objfile );
+- }
+- objfile.println("</Object>");
+- }
+-
+- /** Add a Netscape extension mapping.
+- */
+- protected boolean addMapping( String ctxPath, String ext,
+- PrintWriter objfile )
+- {
+- if( log.isDebugEnabled() )
+- log.debug( "Adding extension map for " + ctxPath + "/*." + ext );
+- if(! ext.startsWith("/") )
+- ext = "/" + ext;
+- if(ext.length() > 1)
+- objfile.println("NameTrans fn=\"assign-name\" from=\"" +
+- ctxPath + ext + "\" name=\"" + objectName + "\"");
+- return true;
+- }
+-
+- /** Add a fulling specified Netscape mapping.
+- */
+- protected boolean addMapping( String fullPath, PrintWriter objfile ) {
+- if( log.isDebugEnabled() )
+- log.debug( "Adding map for " + fullPath );
+- objfile.println("NameTrans fn=\"assign-name\" from=\"" +
+- fullPath + "\" name=\"" + objectName + "\"");
+- return true;
+- }
+-
+-}
+Index: java/org/apache/jk/config/ApacheConfig.java
+===================================================================
+--- java/org/apache/jk/config/ApacheConfig.java (revision 590752)
++++ java/org/apache/jk/config/ApacheConfig.java (working copy)
+@@ -1,574 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.config;
+-
+-import java.io.File;
+-import java.io.FileWriter;
+-import java.io.IOException;
+-import java.io.PrintWriter;
+-import java.util.Date;
+-import java.util.Hashtable;
+-
+-import org.apache.catalina.Context;
+-import org.apache.catalina.Host;
+-
+-/* The idea is to keep all configuration in server.xml and
+- the normal apache config files. We don't want people to
+- touch apache ( or IIS, NES ) config files unless they
+- want to and know what they're doing ( better than we do :-).
+-
+- One nice feature ( if someone sends it ) would be to
+- also edit httpd.conf to add the include.
+-
+- We'll generate a number of configuration files - this one
+- is trying to generate a native apache config file.
+-
+- Some web.xml mappings do not "map" to server configuration - in
+- this case we need to fallback to forward all requests to tomcat.
+-
+- Ajp14 will add to that the posibility to have tomcat and
+- apache on different machines, and many other improvements -
+- but this should also work for Ajp12, Ajp13 and Jni.
+-
+-*/
+-
+-/**
+- Generates automatic apache mod_jk configurations based on
+- the Tomcat server.xml settings and the war contexts
+- initialized during startup.
+- <p>
+- This config interceptor is enabled by inserting an ApacheConfig
+- <code>Listener</code> in
+- the server.xml file like so:
+- <pre>
+- * < Server ... >
+- * ...
+- * <Listener className=<b>org.apache.ajp.tomcat4.config.ApacheConfig</b>
+- * <i>options</i> />
+- * ...
+- * < /Server >
+- </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>
+- <li><b>jkConfig</b> - path to use for writing Apache mod_jk conf file. If
+- not set, defaults to
+- "conf/auto/mod_jk.conf".</li>
+- <li><b>workersConfig</b> - path to workers.properties file used by
+- mod_jk. If not set, defaults to
+- "conf/jk/workers.properties".</li>
+- <li><b>modJk</b> - path to Apache mod_jk plugin file. If not set,
+- defaults to "modules/mod_jk.dll" on windows,
+- "modules/mod_jk.nlm" on netware, and
+- "libexec/mod_jk.so" everywhere else.</li>
+- <li><b>jkLog</b> - path to log file to be used by mod_jk.</li>
+- <li><b>jkDebug</b> - JK Loglevel setting. May be debug, info, error, or emerg.
+- If not set, defaults to emerg.</li>
+- <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>
+- <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 Apache serve
+- static resources. The default is true.
+- Warning: When false, some configuration in
+- the web.xml may not be duplicated in Apache.
+- Review the mod_jk conf file to see what
+- configuration is actually being set in Apache.</li>
+- <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 DocumentRoot setting in Apache's httpd.conf
+- file to point to Tomcat's root context directory.
+- Otherwise some content, such as Apache's index.html,
+- will be served by Apache before mod_jk gets a chance
+- to claim the request and pass it to Tomcat.
+- The default is true.</li>
+- </ul>
+- <p>
+- @author Costin Manolache
+- @author Larry Isaacs
+- @author Mel Martinez
+- @author Bill Barker
+- */
+-public class ApacheConfig extends BaseJkConfig {
+-
+- private static org.apache.juli.logging.Log log =
+- org.apache.juli.logging.LogFactory.getLog(ApacheConfig.class);
+-
+- /** default path to mod_jk .conf location */
+- public static final String MOD_JK_CONFIG = "conf/auto/mod_jk.conf";
+- /** default path to workers.properties file
+- This should be also auto-generated from server.xml.
+- */
+- public static final String WORKERS_CONFIG = "conf/jk/workers.properties";
+- /** default mod_jk log file location */
+- public static final String JK_LOG_LOCATION = "logs/mod_jk.log";
+- /** default location of mod_jk Apache plug-in. */
+- public static final String MOD_JK;
+-
+- //set up some defaults based on OS type
+- static{
+- String os = System.getProperty("os.name").toLowerCase();
+- if(os.indexOf("windows")>=0){
+- MOD_JK = "modules/mod_jk.dll";
+- }else if(os.indexOf("netware")>=0){
+- MOD_JK = "modules/mod_jk.nlm";
+- }else{
+- MOD_JK = "libexec/mod_jk.so";
+- }
+- }
+-
+- private File jkConfig = null;
+- private File modJk = null;
+-
+- // ssl settings
+- private boolean sslExtract=true;
+- private String sslHttpsIndicator="HTTPS";
+- private String sslSessionIndicator="SSL_SESSION_ID";
+- private String sslCipherIndicator="SSL_CIPHER";
+- private String sslCertsIndicator="SSL_CLIENT_CERT";
+-
+- Hashtable NamedVirtualHosts=null;
+-
+- public ApacheConfig() {
+- }
+-
+- //-------------------- Properties --------------------
+-
+- /**
+- set the path to the output file for the auto-generated
+- mod_jk configuration file. If this path is relative
+- then it will be resolved absolutely against
+- the getConfigHome() path.
+- <p>
+- @param path String path to a file
+- */
+- public void setJkConfig(String path){
+- jkConfig= (path==null)?null:new File(path);
+- }
+-
+- /**
+- set the path to the mod_jk Apache Module
+- @param path String path to a file
+- */
+- public void setModJk(String path){
+- modJk=( path==null?null:new File(path));
+- }
+-
+- /** By default mod_jk is configured to collect SSL information from
+- the apache environment and send it to the Tomcat workers. The
+- problem is that there are many SSL solutions for Apache and as
+- a result the environment variable names may change.
+-
+- The following JK related SSL configureation
+- can be used to customize mod_jk's SSL behaviour.
+-
+- Should mod_jk send SSL information to Tomact (default is On)
+- */
+- public void setExtractSSL( boolean sslMode ) {
+- this.sslExtract=sslMode;
+- }
+-
+- /** What is the indicator for SSL (default is HTTPS)
+- */
+- public void setHttpsIndicator( String s ) {
+- sslHttpsIndicator=s;
+- }
+-
+- /**What is the indicator for SSL session (default is SSL_SESSION_ID)
+- */
+- public void setSessionIndicator( String s ) {
+- sslSessionIndicator=s;
+- }
+-
+- /**What is the indicator for client SSL cipher suit (default is SSL_CIPHER)
+- */
+- public void setCipherIndicator( String s ) {
+- sslCipherIndicator=s;
+- }
+-
+- /** What is the indicator for the client SSL certificated(default
+- is SSL_CLIENT_CERT
+- */
+- public void setCertsIndicator( String s ) {
+- sslCertsIndicator=s;
+- }
+-
+- // -------------------- Initialize/guess defaults --------------------
+-
+- /** Initialize defaults for properties that are not set
+- explicitely
+- */
+- protected void initProperties() {
+- super.initProperties();
+-
+- jkConfig= getConfigFile( jkConfig, configHome, MOD_JK_CONFIG);
+- workersConfig=getConfigFile( workersConfig, configHome,
+- WORKERS_CONFIG);
+- if( modJk == null )
+- modJk=new File(MOD_JK);
+- else
+- modJk=getConfigFile( modJk, configHome, MOD_JK );
+- jkLog=getConfigFile( jkLog, configHome, JK_LOG_LOCATION);
+- }
+- // -------------------- Generate config --------------------
+-
+- protected PrintWriter getWriter() throws IOException {
+- String abJkConfig = jkConfig.getAbsolutePath();
+- return new PrintWriter(new FileWriter(abJkConfig, append));
+- }
+-
+-
+- // -------------------- Config sections --------------------
+-
+- /** Generate the loadModule and general options
+- */
+- protected boolean generateJkHead(PrintWriter mod_jk)
+- {
+-
+- mod_jk.println("########## Auto generated on " + new Date() +
+- "##########" );
+- mod_jk.println();
+-
+- // Fail if mod_jk not found, let the user know the problem
+- // instead of running into problems later.
+- if( ! modJk.exists() ) {
+- log.info( "mod_jk location: " + modJk );
+- log.info( "Make sure it is installed corectly or " +
+- " set the config location" );
+- log.info( "Using <Listener className=\""+getClass().getName()+"\" modJk=\"PATH_TO_MOD_JK.SO_OR_DLL\" />" );
+- }
+-
+- // Verify the file exists !!
+- mod_jk.println("<IfModule !mod_jk.c>");
+- mod_jk.println(" LoadModule jk_module \""+
+- modJk.toString().replace('\\','/') +
+- "\"");
+- mod_jk.println("</IfModule>");
+- mod_jk.println();
+-
+-
+- // Fail if workers file not found, let the user know the problem
+- // instead of running into problems later.
+- if( ! workersConfig.exists() ) {
+- log.warn( "Can't find workers.properties at " + workersConfig );
+- log.warn( "Please install it in the default location or " +
+- " set the config location" );
+- log.warn( "Using <Listener className=\"" + getClass().getName() + "\" workersConfig=\"FULL_PATH\" />" );
+- return false;
+- }
+-
+- mod_jk.println("JkWorkersFile \""
+- + workersConfig.toString().replace('\\', '/')
+- + "\"");
+-
+- mod_jk.println("JkLogFile \""
+- + jkLog.toString().replace('\\', '/')
+- + "\"");
+- mod_jk.println();
+-
+- if( jkDebug != null ) {
+- mod_jk.println("JkLogLevel " + jkDebug);
+- mod_jk.println();
+- }
+- return true;
+- }
+-
+- protected void generateVhostHead(Host host, PrintWriter mod_jk) {
+-
+- mod_jk.println();
+- String vhostip = host.getName();
+- String vhost = vhostip;
+- int ppos = vhost.indexOf(":");
+- if(ppos >= 0)
+- vhost = vhost.substring(0,ppos);
+-
+- mod_jk.println("<VirtualHost "+ vhostip + ">");
+- mod_jk.println(" ServerName " + vhost );
+- String [] aliases=host.findAliases();
+- if( aliases.length > 0 ) {
+- mod_jk.print(" ServerAlias " );
+- for( int ii=0; ii < aliases.length ; ii++) {
+- mod_jk.print( aliases[ii] + " " );
+- }
+- mod_jk.println();
+- }
+- indent=" ";
+- }
+-
+- protected void generateVhostTail(Host host, PrintWriter mod_jk) {
+- mod_jk.println("</VirtualHost>");
+- indent="";
+- }
+-
+- protected void generateSSLConfig(PrintWriter mod_jk) {
+- if( ! sslExtract ) {
+- mod_jk.println("JkExtractSSL Off");
+- }
+- if( ! "HTTPS".equalsIgnoreCase( sslHttpsIndicator ) ) {
+- mod_jk.println("JkHTTPSIndicator " + sslHttpsIndicator);
+- }
+- if( ! "SSL_SESSION_ID".equalsIgnoreCase( sslSessionIndicator )) {
+- mod_jk.println("JkSESSIONIndicator " + sslSessionIndicator);
+- }
+- if( ! "SSL_CIPHER".equalsIgnoreCase( sslCipherIndicator )) {
+- mod_jk.println("JkCIPHERIndicator " + sslCipherIndicator);
+- }
+- if( ! "SSL_CLIENT_CERT".equalsIgnoreCase( sslCertsIndicator )) {
+- mod_jk.println("JkCERTSIndicator " + sslCertsIndicator);
+- }
+-
+- mod_jk.println();
+- }
+-
+- // -------------------- Forward all mode --------------------
+- String indent="";
+-
+- /** Forward all requests for a context to tomcat.
+- The default.
+- */
+- protected void generateStupidMappings(Context context,
+- PrintWriter mod_jk )
+- {
+- String ctxPath = context.getPath();
+- if(ctxPath == null)
+- return;
+-
+- String nPath=("".equals(ctxPath)) ? "/" : ctxPath;
+-
+- mod_jk.println();
+- mod_jk.println(indent + "JkMount " + nPath + " " + jkWorker );
+- if( "".equals(ctxPath) ) {
+- mod_jk.println(indent + "JkMount " + nPath + "* " + jkWorker );
+- if ( context.getParent() instanceof Host ) {
+- mod_jk.println(indent + "DocumentRoot \"" +
+- getApacheDocBase(context) + "\"");
+- } else {
+- mod_jk.println(indent +
+- "# To avoid Apache serving root welcome files from htdocs, update DocumentRoot");
+- mod_jk.println(indent +
+- "# to point to: \"" + getApacheDocBase(context) + "\"");
+- }
+-
+- } else {
+- mod_jk.println(indent + "JkMount " + nPath + "/* " + jkWorker );
+- }
+- }
+-
+-
+- private void generateNameVirtualHost( PrintWriter mod_jk, String ip ) {
+- if( !NamedVirtualHosts.containsKey(ip) ) {
+- mod_jk.println("NameVirtualHost " + ip + "");
+- NamedVirtualHosts.put(ip,ip);
+- }
+- }
+-
+- // -------------------- Apache serves static mode --------------------
+- // This is not going to work for all apps. We fall back to stupid mode.
+-
+- protected void generateContextMappings(Context context, PrintWriter mod_jk )
+- {
+- String ctxPath = context.getPath();
+- Host vhost = getHost(context);
+-
+- if( noRoot && "".equals(ctxPath) ) {
+- log.debug("Ignoring root context in non-forward-all mode ");
+- return;
+- }
+-
+- mod_jk.println();
+- mod_jk.println(indent + "#################### " +
+- ((vhost!=null ) ? vhost.getName() + ":" : "" ) +
+- (("".equals(ctxPath)) ? "/" : ctxPath ) +
+- " ####################" );
+- mod_jk.println();
+- // Dynamic /servet pages go to Tomcat
+-
+- generateStaticMappings( context, mod_jk );
+-
+- // 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, mod_jk);
+- }
+- }
+- String [] servletMaps = context.findServletMappings();
+- for(int ii=0; ii < servletMaps.length; ii++) {
+- addMapping( ctxPath, servletMaps[ii] , mod_jk );
+- }
+- }
+-
+- /** Add an Apache extension mapping.
+- */
+- protected boolean addExtensionMapping( String ctxPath, String ext,
+- PrintWriter mod_jk )
+- {
+- if( log.isDebugEnabled() )
+- log.debug( "Adding extension map for " + ctxPath + "/*." + ext );
+- mod_jk.println(indent + "JkMount " + ctxPath + "/*." + ext
+- + " " + jkWorker);
+- return true;
+- }
+-
+-
+- /** Add a fulling specified Appache mapping.
+- */
+- protected boolean addMapping( String fullPath, PrintWriter mod_jk ) {
+- if( log.isDebugEnabled() )
+- log.debug( "Adding map for " + fullPath );
+- mod_jk.println(indent + "JkMount " + fullPath + " " + jkWorker );
+- return true;
+- }
+- /** Add a partially specified Appache mapping.
+- */
+- protected boolean addMapping( String ctxP, String ext, PrintWriter mod_jk ) {
+- if( log.isDebugEnabled() )
+- log.debug( "Adding map for " + ext );
+- if(! ext.startsWith("/") )
+- ext = "/" + ext;
+- if(ext.length() > 1)
+- mod_jk.println(indent + "JkMount " + ctxP + ext+ " " + jkWorker );
+- return true;
+- }
+-
+- private void generateWelcomeFiles(Context context, PrintWriter mod_jk ) {
+- String wf[]=context.findWelcomeFiles();
+- if( wf==null || wf.length == 0 )
+- return;
+- mod_jk.print(indent + " DirectoryIndex ");
+- for( int i=0; i<wf.length ; i++ ) {
+- mod_jk.print( wf[i] + " " );
+- }
+- mod_jk.println();
+- }
+-
+- /** Mappings for static content. XXX need to add welcome files,
+- * mime mappings ( all will be handled by Mime and Static modules of
+- * apache ).
+- */
+- private void generateStaticMappings(Context context, PrintWriter mod_jk ) {
+- String ctxPath = context.getPath();
+-
+- // Calculate the absolute path of the document base
+- String docBase = getApacheDocBase(context);
+-
+- if( !"".equals(ctxPath) ) {
+- // Static files will be served by Apache
+- mod_jk.println(indent + "# Static files ");
+- mod_jk.println(indent + "Alias " + ctxPath + " \"" + docBase + "\"");
+- mod_jk.println();
+- } else {
+- if ( getHost(context) != null ) {
+- mod_jk.println(indent + "DocumentRoot \"" +
+- getApacheDocBase(context) + "\"");
+- } else {
+- // For root context, ask user to update DocumentRoot setting.
+- // Using "Alias / " interferes with the Alias for other contexts.
+- mod_jk.println(indent +
+- "# Be sure to update DocumentRoot");
+- mod_jk.println(indent +
+- "# to point to: \"" + docBase + "\"");
+- }
+- }
+- mod_jk.println(indent + "<Directory \"" + docBase + "\">");
+- mod_jk.println(indent + " Options Indexes FollowSymLinks");
+-
+- generateWelcomeFiles(context, mod_jk);
+-
+- // XXX XXX Here goes the Mime types and welcome files !!!!!!!!
+- mod_jk.println(indent + "</Directory>");
+- mod_jk.println();
+-
+-
+- // Deny serving any files from WEB-INF
+- mod_jk.println();
+- mod_jk.println(indent +
+- "# Deny direct access to WEB-INF and META-INF");
+- mod_jk.println(indent + "#");
+- mod_jk.println(indent + "<Location \"" + ctxPath + "/WEB-INF/*\">");
+- mod_jk.println(indent + " AllowOverride None");
+- mod_jk.println(indent + " deny from all");
+- mod_jk.println(indent + "</Location>");
+- // Deny serving any files from META-INF
+- mod_jk.println();
+- mod_jk.println(indent + "<Location \"" + ctxPath + "/META-INF/*\">");
+- mod_jk.println(indent + " AllowOverride None");
+- mod_jk.println(indent + " deny from all");
+- mod_jk.println(indent + "</Location>");
+- if (File.separatorChar == '\\') {
+- mod_jk.println(indent + "#");
+- mod_jk.println(indent +
+- "# Use Directory too. On Windows, Location doesn't"
+- + " work unless case matches");
+- mod_jk.println(indent + "#");
+- mod_jk.println(indent +
+- "<Directory \"" + docBase + "/WEB-INF/\">");
+- mod_jk.println(indent + " AllowOverride None");
+- mod_jk.println(indent + " deny from all");
+- mod_jk.println(indent + "</Directory>");
+- mod_jk.println();
+- mod_jk.println(indent +
+- "<Directory \"" + docBase + "/META-INF/\">");
+- mod_jk.println(indent + " AllowOverride None");
+- mod_jk.println(indent + " deny from all");
+- mod_jk.println(indent + "</Directory>");
+- }
+- mod_jk.println();
+- }
+-
+- // -------------------- Utils --------------------
+-
+- private String getApacheDocBase(Context context)
+- {
+- // Calculate the absolute path of the document base
+- String docBase = getAbsoluteDocBase(context);
+- if (File.separatorChar == '\\') {
+- // use separator preferred by Apache
+- docBase = docBase.replace('\\','/');
+- }
+- return docBase;
+- }
+-
+- private String getVirtualHostAddress(String vhost, String vhostip) {
+- if( vhostip == null ) {
+- if ( vhost != null && vhost.length() > 0 && Character.isDigit(vhost.charAt(0)) )
+- vhostip=vhost;
+- else
+- vhostip="*";
+- }
+- return vhostip;
+- }
+-
+-}
+Index: java/org/apache/jk/config/GeneratorApache2.java
+===================================================================
+--- java/org/apache/jk/config/GeneratorApache2.java (revision 590752)
++++ java/org/apache/jk/config/GeneratorApache2.java (working copy)
+@@ -1,194 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.config;
+-
+-import java.io.File;
+-import java.io.FileWriter;
+-import java.io.IOException;
+-import java.io.PrintWriter;
+-import java.util.Vector;
+-
+-import org.w3c.dom.Node;
+-
+-
+-/* Naming conventions:
+-
+-JK_CONF_DIR == serverRoot/work ( XXX /jkConfig ? )
+-
+-- Each vhost has a sub-dir named after the canonycal name
+-
+-- For each webapp in a vhost, there is a separate WEBAPP_NAME.jkmap
+-
+-- In httpd.conf ( or equivalent servers ), in each virtual host you
+-should "Include JK_CONF_DIR/VHOST/jk_apache.conf". The config
+-file will contain the Alias declarations and other rules required
+-for apache operation. Same for other servers.
+-
+-- WebXml2Jk will be invoked by a config tool or automatically for each
+-webapp - it'll generate the WEBAPP.jkmap files and config fragments.
+-
+-WebXml2Jk will _not_ generate anything else but mappings.
+-It should _not_ try to guess locations or anything else - that's
+-another components' job.
+-
+-*/
+-
+-/**
+- *
+- * @author Costin Manolache
+- */
+-public class GeneratorApache2 implements WebXml2Jk.MappingGenerator {
+- WebXml2Jk wxml;
+- String vhost;
+- String cpath;
+- String worker;
+- PrintWriter out;
+-
+- public void setWebXmlReader(WebXml2Jk wxml ) {
+- this.wxml=wxml;
+- vhost=wxml.vhost;
+- cpath=wxml.cpath;
+- worker=wxml.worker;
+- }
+-
+- public void generateStart() throws IOException {
+- File base=wxml.getJkDir();
+- File outF=new File(base, "jk2.conf");
+- out=new PrintWriter( new FileWriter( outF ));
+-
+- out.println("# Must be included in a virtual host context for " + vhost );
+-
+- out.println("Alias " + cpath + " \"" + wxml.docBase + "\"");
+- out.println("<Directory \"" + wxml.docBase + "\" >");
+- out.println(" Options Indexes FollowSymLinks");
+- generateMimeMapping( out );
+- generateWelcomeFiles( out);
+-
+- // If we use this instead of extension mapping for jsp we can avoid most
+- // jsp security problems.
+- out.println(" AddHandler jakarta-servlet2 .jsp");
+- out.println("</Directory>");
+- out.println();
+-
+- out.println("<Location \"" + cpath + "/WEB-INF\" >");
+- out.println(" AllowOverride None");
+- out.println(" Deny from all");
+- out.println("</Location>");
+- out.println();
+- out.println("<Location \"" + cpath + "/META-INF\" >");
+- out.println(" AllowOverride None");
+- out.println(" Deny from all");
+- out.println("</Location>");
+- out.println();
+- }
+-
+- private void generateWelcomeFiles( PrintWriter out ) {
+- Vector wf= wxml.getWellcomeFiles();
+- out.print(" DirectoryIndex ");
+- for( int i=0; i<wf.size(); i++ ) {
+- out.print( " " + (String)wf.elementAt(i));
+- }
+- out.println();
+- }
+-
+- private void generateMimeMapping( PrintWriter out ) {
+- Node webN=wxml.getWebXmlNode();
+- for( Node mapN=WebXml2Jk.getChild( webN, "mime-mapping" );
+- mapN != null; mapN = WebXml2Jk.getNext( mapN ) ) {
+- String ext=WebXml2Jk.getChildContent( mapN, "extension" );
+- String type=WebXml2Jk.getChildContent( mapN, "mime-type" );
+-
+- out.println(" AddType " + type + " " + ext );
+- }
+-
+-
+- }
+-
+- public void generateEnd() {
+- out.close();
+- }
+-
+- public void generateServletMapping( String servlet, String url ) {
+- out.println( "<Location \"" + cpath + url + "\" >");
+- out.println( " SetHandler jakarta-servlet2" );
+- out.println( " JkUriSet group " + worker );
+- out.println( " JkUriSet servlet " + servlet);
+- out.println( " JkUriSet host " + vhost );
+- out.println( " JkUriSet context " + cpath );
+- out.println( "</Location>");
+- out.println();
+- }
+-
+- public void generateFilterMapping( String servlet, String url ) {
+- out.println( "<Location \"" + cpath + url + "\" >");
+- out.println( " SetHandler jakarta-servlet2" );
+- out.println( " JkUriSet group " + worker );
+- out.println( " JkUriSet servlet " + servlet);
+- out.println( " JkUriSet host " + vhost );
+- out.println( " JkUriSet context " + cpath );
+- out.println( "</Location>");
+- out.println();
+- }
+-
+- public void generateLoginConfig( String loginPage,
+- String errPage, String authM ) {
+- out.println( "<Location \"" + cpath + loginPage + "\" >");
+- out.println( " SetHandler jakarta-servlet2" );
+- out.println( " JkUriSet group " + worker );
+- out.println( " JkUriSet host " + vhost );
+- out.println( " JkUriSet context " + cpath );
+- out.println( "</Location>");
+- out.println();
+- }
+-
+- public void generateErrorPage( int err, String location ) {
+-
+- }
+-
+- // XXX Only if BASIC/DIGEST and 'integrated auth'
+- public void generateConstraints( Vector urls, Vector methods, Vector roles, boolean isSSL ) {
+- for( int i=0; i<urls.size(); i++ ) {
+- String url=(String)urls.elementAt(i);
+-
+- out.println( "<Location \"" + cpath + url + "\" >");
+-
+- if( methods.size() > 0 ) {
+- out.print(" <Limit ");
+- for( int j=0; j<methods.size(); j++ ) {
+- String m=(String)methods.elementAt(j);
+- out.print( " " + m);
+- }
+- out.println( " >" );
+- }
+-
+- out.println( " AuthType basic" );
+- out.print( " Require group " );
+- for( int j=0; j<roles.size(); j++ ) {
+- String role=(String)roles.elementAt(j);
+- out.print( " " + role);
+- }
+- out.println();
+-
+- if( methods.size() > 0 ) {
+- out.println(" </Limit>");
+- }
+-
+- out.println( "</Location>");
+- }
+- }
+-}
+Index: java/org/apache/jk/config/IISConfig.java
+===================================================================
+--- java/org/apache/jk/config/IISConfig.java (revision 590752)
++++ java/org/apache/jk/config/IISConfig.java (working copy)
+@@ -1,306 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.config;
+-
+-import java.io.File;
+-import java.io.FileWriter;
+-import java.io.IOException;
+-import java.io.PrintWriter;
+-import java.util.Date;
+-
+-import org.apache.catalina.Context;
+-
+-
+-/**
+- Generates automatic IIS isapi_redirect configurations based on
+- the Tomcat server.xml settings and the war contexts
+- initialized during startup.
+- <p>
+- This config interceptor is enabled by inserting an IISConfig
+- element in the <b><ContextManager></b> tag body inside
+- the server.xml file like so:
+- <pre>
+- * < ContextManager ... >
+- * ...
+- * <<b>IISConfig</b> <i>options</i> />
+- * ...
+- * < /ContextManager >
+- </pre>
+- where <i>options</i> can include any of the following attributes:
+- <ul>
+- <li><b>configHome</b> - default parent directory for the following paths.
+- If not set, this defaults to TOMCAT_HOME. Ignored
+- whenever any of the following paths is absolute.
+- </li>
+- <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>
+- <li><b>workersConfig</b> - path to workers.properties file used by
+- isapi_redirect. If not set, defaults to
+- "conf/jk/workers.properties".</li>
+- <li><b>uriConfig</b> - path to use for writing IIS isapi_redirect uriworkermap
+- file. If not set, defaults to
+- "conf/auto/uriworkermap.properties".</li>
+- <li><b>jkLog</b> - path to log file to be used by isapi_redirect.</li>
+- <li><b>jkDebug</b> - Loglevel setting. May be debug, info, error, or emerg.
+- If not set, defaults to emerg.</li>
+- <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>
+- <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>
+- <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.</li>
+- </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();
+- }
+-
+-}
+Index: java/org/apache/jk/config/GeneratorJk1.java
+===================================================================
+--- java/org/apache/jk/config/GeneratorJk1.java (revision 590752)
++++ java/org/apache/jk/config/GeneratorJk1.java (working copy)
+@@ -1,113 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.config;
+-
+-import java.io.File;
+-import java.io.FileWriter;
+-import java.io.IOException;
+-import java.io.PrintWriter;
+-import java.util.Vector;
+-
+-
+-/* Naming conventions:
+-
+-JK_CONF_DIR == serverRoot/work ( XXX /jkConfig ? )
+-
+-- Each vhost has a sub-dir named after the canonycal name
+-
+-- For each webapp in a vhost, there is a separate WEBAPP_NAME.jkmap
+-
+-- In httpd.conf ( or equivalent servers ), in each virtual host you
+-should "Include JK_CONF_DIR/VHOST/jk_apache.conf". The config
+-file will contain the Alias declarations and other rules required
+-for apache operation. Same for other servers.
+-
+-- WebXml2Jk will be invoked by a config tool or automatically for each
+-webapp - it'll generate the WEBAPP.jkmap files and config fragments.
+-
+-WebXml2Jk will _not_ generate anything else but mappings.
+-It should _not_ try to guess locations or anything else - that's
+-another components' job.
+-
+-*/
+-
+-/**
+- *
+- * @author Costin Manolache
+- */
+-public class GeneratorJk1 implements WebXml2Jk.MappingGenerator {
+- WebXml2Jk wxml;
+- String vhost;
+- String cpath;
+- String worker;
+- PrintWriter out;
+-
+- public void setWebXmlReader(WebXml2Jk wxml ) {
+- this.wxml=wxml;
+- vhost=wxml.vhost;
+- cpath=wxml.cpath;
+- worker=wxml.worker;
+- }
+-
+- public void generateStart( ) throws IOException {
+- File base=wxml.getJkDir();
+- File outF=new File(base, "jk.conf");
+- out=new PrintWriter( new FileWriter( outF ));
+-
+- out.println("# This must be included in the virtual host section for " + vhost );
+- }
+-
+- public void generateEnd() {
+- out.close();
+- }
+-
+-
+- public void generateServletMapping( String servlet, String url ) {
+- out.println( "JkMount " + cpath + url + " " + worker);
+- }
+-
+- public void generateFilterMapping( String servlet, String url ) {
+- out.println( "JkMount " + cpath + url + " " + worker);
+- }
+-
+- public void generateLoginConfig( String loginPage,
+- String errPage, String authM ) {
+- out.println( "JkMount " + cpath + loginPage + " " + worker);
+- }
+-
+- public void generateErrorPage( int err, String location ) {
+-
+- }
+-
+- public void generateMimeMapping( String ext, String type ) {
+-
+- }
+-
+- public void generateWelcomeFiles( Vector wf ) {
+-
+- }
+-
+-
+- public void generateConstraints( Vector urls, Vector methods, Vector roles, boolean isSSL ) {
+- for( int i=0; i<urls.size(); i++ ) {
+- String url=(String)urls.elementAt(i);
+-
+- out.println( "JkMount " + cpath + url + " " + worker);
+- }
+- }
+-}
+Index: java/org/apache/jk/config/GeneratorJk2.java
+===================================================================
+--- java/org/apache/jk/config/GeneratorJk2.java (revision 590752)
++++ java/org/apache/jk/config/GeneratorJk2.java (working copy)
+@@ -1,144 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.config;
+-
+-import java.io.File;
+-import java.io.FileWriter;
+-import java.io.IOException;
+-import java.io.PrintWriter;
+-import java.util.Vector;
+-
+-
+-/* Naming conventions:
+-
+-JK_CONF_DIR == serverRoot/work ( XXX /jkConfig ? )
+-
+-- Each vhost has a sub-dir named after the canonycal name
+-
+-- For each webapp in a vhost, there is a separate WEBAPP_NAME.jkmap
+-
+-- In httpd.conf ( or equivalent servers ), in each virtual host you
+-should "Include JK_CONF_DIR/VHOST/jk_apache.conf". The config
+-file will contain the Alias declarations and other rules required
+-for apache operation. Same for other servers.
+-
+-- WebXml2Jk will be invoked by a config tool or automatically for each
+-webapp - it'll generate the WEBAPP.jkmap files and config fragments.
+-
+-WebXml2Jk will _not_ generate anything else but mappings.
+-It should _not_ try to guess locations or anything else - that's
+-another components' job.
+-
+-*/
+-
+-/**
+- *
+- * @author Costin Manolache
+- */
+-public class GeneratorJk2 implements WebXml2Jk.MappingGenerator {
+- WebXml2Jk wxml;
+- String vhost;
+- String cpath;
+- String worker;
+- PrintWriter out;
+-
+- public void setWebXmlReader(WebXml2Jk wxml ) {
+- this.wxml=wxml;
+- vhost=wxml.vhost;
+- cpath=wxml.cpath;
+- worker=wxml.worker;
+- }
+-
+- public void generateStart( ) throws IOException {
+- File base=wxml.getJkDir();
+- File outF=new File(base, "jk2map.properties");
+- out=new PrintWriter( new FileWriter( outF ));
+-
+- out.println("# Autogenerated from web.xml" );
+- }
+-
+- public void generateEnd() {
+- out.close();
+- }
+-
+- public void generateServletMapping( String servlet, String url ) {
+- out.println( "[uri:" + vhost + cpath + url + "]");
+- out.println( "group=" + worker );
+- out.println( "servlet=" + servlet);
+- out.println( "host=" + vhost);
+- out.println( "context=" + cpath);
+- out.println();
+- }
+-
+- public void generateFilterMapping( String servlet, String url ) {
+- out.println( "[url:" + vhost + cpath + url + "]");
+- out.println( "group=" + worker );
+- out.println( "filter=" + servlet);
+- out.println( "host=" + vhost);
+- out.println( "context=" + cpath);
+- out.println();
+- }
+-
+- public void generateLoginConfig( String loginPage,
+- String errPage, String authM ) {
+- out.println("[url:" + vhost + cpath + loginPage + "]" );
+- out.println( "group=" + worker );
+- out.println( "host=" + vhost);
+- out.println( "context=" + cpath);
+- out.println();
+- out.println("[url:" + vhost + cpath + errPage + "]" );
+- out.println( "group=" + worker );
+- out.println( "host=" + vhost);
+- out.println( "context=" + cpath);
+- out.println();
+- }
+-
+- public void generateErrorPage( int err, String location ) {
+-
+- }
+-
+- public void generateMimeMapping( String ext, String type ) {
+-
+- }
+-
+- public void generateWelcomeFiles( Vector wf ) {
+-
+- }
+-
+-
+- public void generateConstraints( Vector urls, Vector methods, Vector roles, boolean isSSL ) {
+- for( int i=0; i<urls.size(); i++ ) {
+- String url=(String)urls.elementAt(i);
+-
+- out.println("[url:" + vhost + cpath + url + "]");
+- out.println( "group=" + worker );
+- out.println( "host=" + vhost);
+- out.println( "context=" + cpath);
+- for( int j=0; j<roles.size(); j++ ) {
+- String role=(String)roles.elementAt(j);
+- out.println( "role=" + role);
+- }
+- for( int j=0; j<methods.size(); j++ ) {
+- String m=(String)methods.elementAt(j);
+- out.println( "method=" + m);
+- }
+- if( isSSL )
+- out.println("ssl=true");
+- }
+- }
+-}
+Index: java/org/apache/jk/server/JkCoyoteHandler.java
+===================================================================
+--- java/org/apache/jk/server/JkCoyoteHandler.java (revision 590752)
++++ java/org/apache/jk/server/JkCoyoteHandler.java (working copy)
+@@ -1,218 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.server;
+-
+-import java.io.IOException;
+-import java.util.Iterator;
+-
+-import javax.management.MBeanServer;
+-import javax.management.ObjectName;
+-
+-import org.apache.coyote.Adapter;
+-import org.apache.coyote.ProtocolHandler;
+-import org.apache.coyote.Request;
+-import org.apache.coyote.Response;
+-import org.apache.coyote.RequestInfo;
+-import org.apache.coyote.Constants;
+-import org.apache.jk.core.JkHandler;
+-import org.apache.jk.core.Msg;
+-import org.apache.jk.core.MsgContext;
+-import org.apache.tomcat.util.modeler.Registry;
+-
+-/** Plugs Jk into Coyote. Must be named "type=JkHandler,name=container"
+- *
+- * 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.recycle();
+- req.updateCounters();
+- 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);
+- }
+-}
+Index: java/org/apache/jk/server/JkMain.java
+===================================================================
+--- java/org/apache/jk/server/JkMain.java (revision 590752)
++++ java/org/apache/jk/server/JkMain.java (working copy)
+@@ -1,695 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.server;
+-
+-import java.io.File;
+-import java.io.FileInputStream;
+-import java.io.FileOutputStream;
+-import java.io.IOException;
+-import java.io.PrintStream;
+-import java.util.Enumeration;
+-import java.util.Hashtable;
+-import java.util.Properties;
+-import java.util.StringTokenizer;
+-import java.util.Vector;
+-
+-import javax.management.MBeanRegistration;
+-import javax.management.MBeanServer;
+-import javax.management.ObjectName;
+-
+-import org.apache.jk.core.JkHandler;
+-import org.apache.jk.core.WorkerEnv;
+-import org.apache.tomcat.util.IntrospectionUtils;
+-import org.apache.tomcat.util.modeler.Registry;
+-
+-/** Main class used to startup and configure jk. It manages the conf/jk2.properties file
+- * and is the target of JMX proxy.
+- *
+- * It implements a policy of save-on-change - whenever a property is changed at
+- * runtime the jk2.properties file will be overriden.
+- *
+- * You can edit the config file when tomcat is stoped ( or if you don't use JMX or
+- * other admin tools ).
+- *
+- * The format of jk2.properties:
+- * <dl>
+- * <dt>TYPE[.LOCALNAME].PROPERTY_NAME=VALUE
+- * <dd>Set a property on the associated component. TYPE will be used to
+- * find the class name and instantiate the component. LOCALNAME allows
+- * multiple instances. In JMX mode, TYPE and LOCALNAME will form the
+- * JMX name ( eventually combined with a 'jk2' component )
+- *
+- * <dt>NAME=VALUE
+- * <dd>Define global properties to be used in ${} substitutions
+- *
+- * <dt>class.COMPONENT_TYPE=JAVA_CLASS_NAME
+- * <dd>Adds a new 'type' of component. We predefine all known types.
+- * </dl>
+- *
+- * Instances are created the first time a component name is found. In addition,
+- * 'handler.list' property will override the list of 'default' components that are
+- * loaded automatically.
+- *
+- * Note that the properties file is just one (simplistic) way to configure jk. We hope
+- * to see configs based on registry, LDAP, db, etc. ( XML is not necesarily better )
+- *
+- * @author Costin Manolache
+- */
+-public class JkMain implements MBeanRegistration
+-{
+- WorkerEnv wEnv;
+- String propFile;
+- Properties props=new Properties();
+-
+- Properties modules=new Properties();
+- boolean modified=false;
+- boolean started=false;
+- boolean saveProperties=false;
+-
+- public JkMain()
+- {
+- JkMain.jkMain=this;
+- modules.put("channelSocket", "org.apache.jk.common.ChannelSocket");
+- modules.put("channelNioSocket", "org.apache.jk.common.ChannelNioSocket");
+- modules.put("channelUnix", "org.apache.jk.common.ChannelUn");
+- modules.put("channelJni", "org.apache.jk.common.ChannelJni");
+- modules.put("apr", "org.apache.jk.apr.AprImpl");
+- modules.put("mx", "org.apache.jk.common.JkMX");
+- modules.put("modeler", "org.apache.jk.common.JkModeler");
+- modules.put("shm", "org.apache.jk.common.Shm");
+- modules.put("request","org.apache.jk.common.HandlerRequest");
+- modules.put("container","org.apache.jk.common.HandlerRequest");
+- modules.put("modjk","org.apache.jk.common.ModJkMX");
+-
+- }
+-
+- public static JkMain getJkMain() {
+- return jkMain;
+- }
+-
+- private static String DEFAULT_HTTPS="com.sun.net.ssl.internal.www.protocol";
+- private void initHTTPSUrls() {
+- try {
+- // 11657: if only ajp is used, https: redirects need to work ( at least for 1.3+)
+- String value = System.getProperty("java.protocol.handler.pkgs");
+- if (value == null) {
+- value = DEFAULT_HTTPS;
+- } else if (value.indexOf(DEFAULT_HTTPS) >= 0 ) {
+- return; // already set
+- } else {
+- value += "|" + DEFAULT_HTTPS;
+- }
+- System.setProperty("java.protocol.handler.pkgs", value);
+- } catch(Exception ex ) {
+- log.info("Error adding SSL Protocol Handler",ex);
+- }
+- }
+-
+- // -------------------- Setting --------------------
+-
+- /** Load a .properties file into and set the values
+- * into jk2 configuration.
+- */
+- public void setPropertiesFile( String p ) {
+- propFile=p;
+- if( started ) {
+- loadPropertiesFile();
+- }
+- }
+-
+- public String getPropertiesFile() {
+- return propFile;
+- }
+-
+- public void setSaveProperties( boolean b ) {
+- saveProperties=b;
+- }
+-
+- /** Set a name/value as a jk2 property
+- */
+- public void setProperty( String n, String v ) {
+- if( "jkHome".equals( n ) ) {
+- setJkHome( v );
+- }
+- if( "propertiesFile".equals( n ) ) {
+- setPropertiesFile( v );
+- }
+- props.put( n, v );
+- if( started ) {
+- processProperty( n, v );
+- saveProperties();
+- }
+- }
+- /**
+- * Retrieve a property.
+- */
+- public Object getProperty(String name) {
+- String alias = (String)replacements.get(name);
+- Object result = null;
+- if(alias != null) {
+- result = props.get(alias);
+- }
+- if(result == null) {
+- result = props.get(name);
+- }
+- return result;
+- }
+- /**
+- * Set the <code>channelClassName</code> that will used to connect to
+- * httpd.
+- */
+- public void setChannelClassName(String name) {
+- props.put( "handler.channel.className",name);
+- }
+-
+- public String getChannelClassName() {
+- return (String)props.get( "handler.channel.className");
+- }
+-
+- /**
+- * Set the <code>workerClassName</code> that will handle the request.
+- * ( sort of 'pivot' in axis :-)
+- */
+- public void setWorkerClassName(String name) {
+- props.put( "handler.container.className",name);
+- }
+-
+- public String getWorkerClassName() {
+- return (String)props.get( "handler.container.className");
+- }
+-
+- /** Set the base dir of jk2. ( including WEB-INF if in a webapp ).
+- * We'll try to guess it from classpath if none is set ( for
+- * example on command line ), but if in a servlet environment
+- * you need to use Context.getRealPath or a system property or
+- * set it expliciltey.
+- */
+- public void setJkHome( String s ) {
+- getWorkerEnv().setJkHome(s);
+- }
+-
+- public String getJkHome() {
+- return getWorkerEnv().getJkHome();
+- }
+-
+- String out;
+- String err;
+- File propsF;
+-
+- public void setOut( String s ) {
+- this.out=s;
+- }
+-
+- public String getOut() {
+- return this.out;
+- }
+-
+- public void setErr( String s ) {
+- this.err=s;
+- }
+-
+- public String getErr() {
+- return this.err;
+- }
+-
+- // -------------------- Initialization --------------------
+-
+- public void init() throws IOException
+- {
+- long t1=System.currentTimeMillis();
+- if(null != out) {
+- PrintStream outS=new PrintStream(new FileOutputStream(out));
+- System.setOut(outS);
+- }
+- if(null != err) {
+- PrintStream errS=new PrintStream(new FileOutputStream(err));
+- System.setErr(errS);
+- }
+-
+- String home=getWorkerEnv().getJkHome();
+- if( home==null ) {
+- // XXX use IntrospectionUtil to find myself
+- this.guessHome();
+- }
+- home=getWorkerEnv().getJkHome();
+- if( home==null ) {
+- log.info( "Can't find home, jk2.properties not loaded");
+- }
+- if(log.isDebugEnabled())
+- log.debug("Starting Jk2, base dir= " + home );
+- loadPropertiesFile();
+-
+- String initHTTPS = (String)props.get("class.initHTTPS");
+- if("true".equalsIgnoreCase(initHTTPS)) {
+- initHTTPSUrls();
+- }
+-
+- long t2=System.currentTimeMillis();
+- initTime=t2-t1;
+- }
+-
+- static String defaultHandlers[]= { "request",
+- "container",
+- "channelSocket"};
+- /*
+- static String defaultHandlers[]= { "apr",
+- "shm",
+- "request",
+- "container",
+- "channelSocket",
+- "channelJni",
+- "channelUnix"};
+- */
+-
+- public void stop()
+- {
+- for( int i=0; i<wEnv.getHandlerCount(); i++ ) {
+- if( wEnv.getHandler(i) != null ) {
+- try {
+- wEnv.getHandler(i).destroy();
+- } catch( IOException ex) {
+- log.error("Error stopping " + wEnv.getHandler(i).getName(), ex);
+- }
+- }
+- }
+-
+- started=false;
+- }
+-
+- public void start() throws IOException
+- {
+- long t1=System.currentTimeMillis();
+- // We must have at least 3 handlers:
+- // channel is the 'transport'
+- // request is the request processor or 'global' chain
+- // container is the 'provider'
+- // Additional handlers may exist and be used internally
+- // or be chained to create one of the standard handlers
+-
+- String handlers[]=defaultHandlers;
+- // backward compat
+- String workers=props.getProperty( "handler.list", null );
+- if( workers!=null ) {
+- handlers= split( workers, ",");
+- }
+-
+- // Load additional component declarations
+- processModules();
+-
+- for( int i=0; i<handlers.length; i++ ) {
+- String name= handlers[i];
+- JkHandler w=getWorkerEnv().getHandler( name );
+- if( w==null ) {
+- newHandler( name, "", name );
+- }
+- }
+-
+- // Process properties - and add aditional handlers.
+- processProperties();
+-
+- for( int i=0; i<wEnv.getHandlerCount(); i++ ) {
+- if( wEnv.getHandler(i) != null ) {
+- try {
+- wEnv.getHandler(i).init();
+- } catch( IOException ex) {
+- if( "apr".equals(wEnv.getHandler(i).getName() )) {
+- log.info( "APR not loaded, disabling jni components: " + ex.toString());
+- } else {
+- log.error( "error initializing " + wEnv.getHandler(i).getName(), ex );
+- }
+- }
+- }
+- }
+-
+- started=true;
+- long t2=System.currentTimeMillis();
+- startTime=t2-t1;
+-
+- this.saveProperties();
+- log.info("Jk running ID=" + wEnv.getLocalId() + " time=" + initTime + "/" + startTime +
+- " config=" + propFile);
+- }
+-
+- // -------------------- Usefull methods --------------------
+-
+- public WorkerEnv getWorkerEnv() {
+- if( wEnv==null ) {
+- wEnv=new WorkerEnv();
+- }
+- return wEnv;
+- }
+-
+- public void setWorkerEnv(WorkerEnv wEnv) {
+- this.wEnv = wEnv;
+- }
+-
+- /* A bit of magic to support workers.properties without giving
+- up the clean get/set
+- */
+- public void setBeanProperty( Object target, String name, String val ) {
+- if( val!=null )
+- val=IntrospectionUtils.replaceProperties( val, props, null );
+- if( log.isDebugEnabled())
+- log.debug( "setProperty " + target + " " + name + "=" + val );
+-
+- IntrospectionUtils.setProperty( target, name, val );
+- }
+-
+- /*
+- * Set a handler property
+- */
+- public void setPropertyString( String handlerN, String name, String val ) {
+- if( log.isDebugEnabled() )
+- log.debug( "setProperty " + handlerN + " " + name + "=" + val );
+- Object target=getWorkerEnv().getHandler( handlerN );
+-
+- setBeanProperty( target, name, val );
+- if( started ) {
+- saveProperties();
+- }
+-
+- }
+-
+- /** The time it took to initialize jk ( ms)
+- */
+- public long getInitTime() {
+- return initTime;
+- }
+-
+- /** The time it took to start jk ( ms )
+- */
+- public long getStartTime() {
+- return startTime;
+- }
+-
+- // -------------------- Main --------------------
+-
+- long initTime;
+- long startTime;
+- static JkMain jkMain=null;
+-
+- public static void main(String args[]) {
+- try {
+- if( args.length == 1 &&
+- ( "-?".equals(args[0]) || "-h".equals( args[0])) ) {
+- System.out.println("Usage: ");
+- System.out.println(" JkMain [args]");
+- System.out.println();
+- System.out.println(" Each bean setter corresponds to an arg ( like -debug 10 )");
+- System.out.println(" System properties:");
+- System.out.println(" jk2.home Base dir of jk2");
+- return;
+- }
+-
+- jkMain=new JkMain();
+-
+- IntrospectionUtils.processArgs( jkMain, args, new String[] {},
+- null, new Hashtable());
+-
+- jkMain.init();
+- jkMain.start();
+- } catch( Exception ex ) {
+- log.warn("Error running",ex);
+- }
+- }
+-
+- // -------------------- Private methods --------------------
+-
+-
+- private boolean checkPropertiesFile() {
+- if(propFile == null) {
+- return false;
+- }
+- propsF = new File(propFile);
+- if(!propsF.isAbsolute()) {
+- String home = getWorkerEnv().getJkHome();
+- if( home == null ) {
+- return false;
+- }
+- propsF = new File(home, propFile);
+- }
+- return propsF.exists();
+- }
+-
+- private void loadPropertiesFile() {
+- if(!checkPropertiesFile()) {
+- return;
+- }
+-
+- try {
+- props.load( new FileInputStream(propsF) );
+- } catch(IOException ex ){
+- log.warn("Unable to load properties from "+propsF,ex);
+- }
+- }
+-
+- public void saveProperties() {
+- if( !saveProperties) return;
+-
+- if(propsF == null) {
+- log.warn("No properties file specified. Unable to save");
+- return;
+- }
+- // Temp - to check if it works
+- File outFile= new File(propsF.getParentFile(), propsF.getName()+".save");
+- log.debug("Saving properties " + outFile );
+- try {
+- props.store( new FileOutputStream(outFile), "AUTOMATICALLY GENERATED" );
+- } catch(IOException ex ){
+- log.warn("Unable to save to "+outFile,ex);
+- }
+- }
+-
+- // translate top-level keys ( from coyote or generic ) into component keys
+- static Hashtable replacements=new Hashtable();
+- static {
+- replacements.put("port","channelSocket.port");
+- replacements.put("maxThreads", "channelSocket.maxThreads");
+- replacements.put("minSpareThreads", "channelSocket.minSpareThreads");
+- replacements.put("maxSpareThreads", "channelSocket.maxSpareThreads");
+- replacements.put("backlog", "channelSocket.backlog");
+- replacements.put("tcpNoDelay", "channelSocket.tcpNoDelay");
+- replacements.put("soTimeout", "channelSocket.soTimeout");
+- replacements.put("timeout", "channelSocket.timeout");
+- replacements.put("address", "channelSocket.address");
+- replacements.put("bufferSize", "channelSocket.bufferSize");
+- replacements.put("tomcatAuthentication", "request.tomcatAuthentication");
+- replacements.put("packetSize", "channelSocket.packetSize");
+- }
+-
+- private void preProcessProperties() {
+- Enumeration keys=props.keys();
+- Vector v=new Vector();
+-
+- while( keys.hasMoreElements() ) {
+- String key=(String)keys.nextElement();
+- Object newName=replacements.get(key);
+- if( newName !=null ) {
+- v.addElement(key);
+- }
+- }
+- keys=v.elements();
+- while( keys.hasMoreElements() ) {
+- String key=(String)keys.nextElement();
+- Object propValue=props.getProperty( key );
+- String replacement=(String)replacements.get(key);
+- props.put(replacement, propValue);
+- if( log.isDebugEnabled())
+- log.debug("Substituting " + key + " " + replacement + " " +
+- propValue);
+- }
+- }
+-
+- private void processProperties() {
+- preProcessProperties();
+- Enumeration keys=props.keys();
+-
+- while( keys.hasMoreElements() ) {
+- String name=(String)keys.nextElement();
+- String propValue=props.getProperty( name );
+-
+- processProperty( name, propValue );
+- }
+- }
+-
+- private void processProperty(String name, String propValue) {
+- String type=name;
+- String fullName=name;
+- String localName="";
+- String propName="";
+- // ignore
+- if( name.startsWith("key.")) return;
+-
+- int dot=name.indexOf(".");
+- int lastDot=name.lastIndexOf(".");
+- if( dot > 0 ) {
+- type=name.substring(0, dot );
+- if( dot != lastDot ) {
+- localName=name.substring( dot + 1, lastDot );
+- fullName=type + "." + localName;
+- } else {
+- fullName=type;
+- }
+- propName=name.substring( lastDot+1);
+- } else {
+- return;
+- }
+-
+- if( log.isDebugEnabled() )
+- log.debug( "Processing " + type + ":" + localName + ":" + fullName + " " + propName );
+- if( "class".equals( type ) || "handler".equals( type ) ) {
+- return;
+- }
+-
+- JkHandler comp=getWorkerEnv().getHandler( fullName );
+- if( comp==null ) {
+- comp=newHandler( type, localName, fullName );
+- }
+- if( comp==null )
+- return;
+-
+- if( log.isDebugEnabled() )
+- log.debug("Setting " + propName + " on " + fullName + " " + comp);
+- this.setBeanProperty( comp, propName, propValue );
+- }
+-
+- private JkHandler newHandler( String type, String localName, String fullName )
+- {
+- JkHandler handler;
+- String classN=modules.getProperty(type);
+- if( classN == null ) {
+- log.error("No class name for " + fullName + " " + type );
+- return null;
+- }
+- try {
+- Class channelclass = Class.forName(classN);
+- handler=(JkHandler)channelclass.newInstance();
+- } catch (Throwable ex) {
+- handler=null;
+- log.error( "Can't create " + fullName, ex );
+- return null;
+- }
+- if( this.domain != null ) {
+- try {
+- ObjectName handlerOname = new ObjectName
+- (this.domain + ":" + "type=JkHandler,name=" + fullName);
+- Registry.getRegistry(null, null).registerComponent(handler, handlerOname, classN);
+- } catch (Exception e) {
+- log.error( "Error registering " + fullName, e );
+- }
+-
+- }
+- wEnv.addHandler( fullName, handler );
+- return handler;
+- }
+-
+- private void processModules() {
+- Enumeration keys=props.keys();
+- int plen=6;
+-
+- while( keys.hasMoreElements() ) {
+- String k=(String)keys.nextElement();
+- if( ! k.startsWith( "class." ) )
+- continue;
+-
+- String name= k.substring( plen );
+- String propValue=props.getProperty( k );
+-
+- if( log.isDebugEnabled()) log.debug("Register " + name + " " + propValue );
+- modules.put( name, propValue );
+- }
+- }
+-
+- private String[] split(String s, String delim ) {
+- Vector v=new Vector();
+- StringTokenizer st=new StringTokenizer(s, delim );
+- while( st.hasMoreTokens() ) {
+- v.addElement( st.nextToken());
+- }
+- String res[]=new String[ v.size() ];
+- for( int i=0; i<res.length; i++ ) {
+- res[i]=(String)v.elementAt(i);
+- }
+- return res;
+- }
+-
+- // guessing home
+- private static String CNAME="org/apache/jk/server/JkMain.class";
+-
+- private void guessHome() {
+- String home= wEnv.getJkHome();
+- if( home != null )
+- return;
+- home=IntrospectionUtils.guessInstall( "jk2.home","jk2.home",
+- "tomcat-jk2.jar", CNAME );
+- if( home != null ) {
+- log.info("Guessed home " + home );
+- wEnv.setJkHome( home );
+- }
+- }
+-
+- static org.apache.juli.logging.Log log=
+- org.apache.juli.logging.LogFactory.getLog( JkMain.class );
+-
+- 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() {
+- }
+-
+- public void pause() throws Exception {
+- for( int i=0; i<wEnv.getHandlerCount(); i++ ) {
+- if( wEnv.getHandler(i) != null ) {
+- wEnv.getHandler(i).pause();
+- }
+- }
+- }
+-
+- public void resume() throws Exception {
+- for( int i=0; i<wEnv.getHandlerCount(); i++ ) {
+- if( wEnv.getHandler(i) != null ) {
+- wEnv.getHandler(i).resume();
+- }
+- }
+- }
+-
+-
+-}
+Index: java/org/apache/jk/common/JkInputStream.java
+===================================================================
+--- java/org/apache/jk/common/JkInputStream.java (revision 590752)
++++ java/org/apache/jk/common/JkInputStream.java (working copy)
+@@ -1,327 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.common;
+-
+-import java.io.IOException;
+-
+-import org.apache.coyote.OutputBuffer;
+-import org.apache.coyote.InputBuffer;
+-import org.apache.coyote.Request;
+-import org.apache.coyote.Response;
+-
+-import org.apache.jk.core.Msg;
+-import org.apache.jk.core.MsgContext;
+-
+-import org.apache.tomcat.util.buf.ByteChunk;
+-import org.apache.tomcat.util.buf.MessageBytes;
+-import org.apache.tomcat.util.buf.C2BConverter;
+-import org.apache.tomcat.util.http.HttpMessages;
+-import org.apache.tomcat.util.http.MimeHeaders;
+-
+-/** Generic input stream impl on top of ajp
+- */
+-public class JkInputStream implements InputBuffer, OutputBuffer {
+- private static org.apache.juli.logging.Log log=
+- org.apache.juli.logging.LogFactory.getLog( JkInputStream.class );
+-
+- private Msg bodyMsg;
+- private Msg outputMsg;
+- private MsgContext mc;
+-
+-
+- // Holds incoming chunks of request body data
+- private MessageBytes bodyBuff = MessageBytes.newInstance();
+- private MessageBytes tempMB = MessageBytes.newInstance();
+- private boolean end_of_stream=false;
+- private boolean isEmpty = true;
+- private boolean isFirst = true;
+- private boolean isReplay = false;
+- private boolean isReadRequired = false;
+-
+- static {
+- // Make certain HttpMessages is loaded for SecurityManager
+- try {
+- Class.forName("org.apache.tomcat.util.http.HttpMessages");
+- } catch(Exception ex) {
+- // ignore
+- }
+- }
+-
+- public JkInputStream(MsgContext context, int bsize) {
+- mc = context;
+- bodyMsg = new MsgAjp(bsize);
+- outputMsg = new MsgAjp(bsize);
+- }
+- /**
+- * @deprecated
+- */
+- public JkInputStream(MsgContext context) {
+- this(context, 8*1024);
+- }
+-
+- // -------------------- Jk specific methods --------------------
+-
+-
+- /**
+- * Set the flag saying that the server is sending a body
+- */
+- public void setIsReadRequired(boolean irr) {
+- isReadRequired = irr;
+- }
+-
+- /**
+- * Return the flag saying that the server is sending a body
+- */
+- public boolean isReadRequired() {
+- return isReadRequired;
+- }
+-
+-
+- /** Must be called before or after each request
+- */
+- public void recycle() {
+- if(isReadRequired && isFirst) {
+- // The Servlet never read the request body, so we need to junk it
+- try {
+- receive();
+- } catch(IOException iex) {
+- log.debug("Error consuming request body",iex);
+- }
+- }
+-
+- end_of_stream = false;
+- isEmpty = true;
+- isFirst = true;
+- isReplay = false;
+- isReadRequired = false;
+- bodyBuff.recycle();
+- tempMB.recycle();
+- }
+-
+-
+- public void endMessage() throws IOException {
+- outputMsg.reset();
+- outputMsg.appendByte(AjpConstants.JK_AJP13_END_RESPONSE);
+- outputMsg.appendByte(1);
+- mc.getSource().send(outputMsg, mc);
+- mc.getSource().flush(outputMsg, mc);
+- }
+-
+-
+- // -------------------- OutputBuffer implementation --------------------
+-
+-
+- public int doWrite(ByteChunk chunk, Response res)
+- throws IOException {
+- if (!res.isCommitted()) {
+- // Send the connector a request for commit. The connector should
+- // then validate the headers, send them (using sendHeader) and
+- // set the filters accordingly.
+- res.sendHeaders();
+- }
+-
+- int len=chunk.getLength();
+- byte buf[]=outputMsg.getBuffer();
+- // 4 - hardcoded, byte[] marshalling overhead
+- int chunkSize=buf.length - outputMsg.getHeaderLength() - 4;
+- int off=0;
+- while( len > 0 ) {
+- int thisTime=len;
+- if( thisTime > chunkSize ) {
+- thisTime=chunkSize;
+- }
+- len-=thisTime;
+-
+- outputMsg.reset();
+- outputMsg.appendByte( AjpConstants.JK_AJP13_SEND_BODY_CHUNK);
+- if( log.isTraceEnabled() )
+- log.trace("doWrite " + off + " " + thisTime + " " + len );
+- outputMsg.appendBytes( chunk.getBytes(), chunk.getOffset() + off, thisTime );
+- off+=thisTime;
+- mc.getSource().send( outputMsg, mc );
+- }
+- return 0;
+- }
+-
+- public int doRead(ByteChunk responseChunk, Request req)
+- throws IOException {
+-
+- if( log.isDebugEnabled())
+- log.debug( "doRead " + end_of_stream+
+- " " + responseChunk.getOffset()+ " " + responseChunk.getLength());
+- if( end_of_stream ) {
+- return -1;
+- }
+-
+- if( isFirst && isReadRequired ) {
+- // Handle special first-body-chunk, but only if httpd expects it.
+- if( !receive() ) {
+- return 0;
+- }
+- } else if(isEmpty) {
+- if ( !refillReadBuffer() ){
+- return -1;
+- }
+- }
+- ByteChunk bc = bodyBuff.getByteChunk();
+- responseChunk.setBytes( bc.getBuffer(), bc.getStart(), bc.getLength() );
+- isEmpty = true;
+- return responseChunk.getLength();
+- }
+-
+- /** 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
+- */
+- public boolean receive() throws IOException {
+- isFirst = false;
+- bodyMsg.reset();
+- int err = mc.getSource().receive(bodyMsg, mc);
+- if( log.isDebugEnabled() )
+- log.info( "Receiving: getting request body chunk " + err + " " + bodyMsg.getLen() );
+-
+- if(err < 0) {
+- throw new IOException();
+- }
+-
+- // No data received.
+- if( bodyMsg.getLen() == 0 ) { // just the header
+- // Don't mark 'end of stream' for the first chunk.
+- // end_of_stream = true;
+- return false;
+- }
+- int blen = bodyMsg.peekInt();
+-
+- if( blen == 0 ) {
+- return false;
+- }
+-
+- if( log.isTraceEnabled() ) {
+- bodyMsg.dump("Body buffer");
+- }
+-
+- bodyMsg.getBytes(bodyBuff);
+- if( log.isTraceEnabled() )
+- log.trace( "Data:\n" + bodyBuff);
+- isEmpty = false;
+- return true;
+- }
+-
+- /**
+- * Get more request body data from the web server and store it in the
+- * internal buffer.
+- *
+- * @return true if there is more data, false if not.
+- */
+- private boolean refillReadBuffer() throws IOException
+- {
+- // If the server returns an empty packet, assume that that end of
+- // the stream has been reached (yuck -- fix protocol??).
+- if(isReplay) {
+- end_of_stream = true; // we've read everything there is
+- }
+- if (end_of_stream) {
+- if( log.isDebugEnabled() )
+- log.debug("refillReadBuffer: end of stream " );
+- return false;
+- }
+-
+- // Why not use outBuf??
+- bodyMsg.reset();
+- bodyMsg.appendByte(AjpConstants.JK_AJP13_GET_BODY_CHUNK);
+- bodyMsg.appendInt(AjpConstants.MAX_READ_SIZE);
+-
+- if( log.isDebugEnabled() )
+- log.debug("refillReadBuffer " + Thread.currentThread());
+-
+- mc.getSource().send(bodyMsg, mc);
+- mc.getSource().flush(bodyMsg, mc); // Server needs to get it
+-
+- // In JNI mode, response will be in bodyMsg. In TCP mode, response need to be
+- // read
+-
+- boolean moreData=receive();
+- if( !moreData ) {
+- end_of_stream=true;
+- }
+- return moreData;
+- }
+-
+- public void appendHead(Response res) throws IOException {
+- if( log.isDebugEnabled() )
+- log.debug("COMMIT sending headers " + res + " " + res.getMimeHeaders() );
+-
+- C2BConverter c2b=mc.getConverter();
+-
+- outputMsg.reset();
+- outputMsg.appendByte(AjpConstants.JK_AJP13_SEND_HEADERS);
+- outputMsg.appendInt( res.getStatus() );
+-
+- String message=res.getMessage();
+- if( message==null ){
+- message= HttpMessages.getMessage(res.getStatus());
+- } else {
+- message = message.replace('\n', ' ').replace('\r', ' ');
+- }
+- tempMB.setString( message );
+- c2b.convert( tempMB );
+- outputMsg.appendBytes(tempMB);
+-
+- // XXX add headers
+-
+- MimeHeaders headers=res.getMimeHeaders();
+- String contentType = res.getContentType();
+- if( contentType != null ) {
+- headers.setValue("Content-Type").setString(contentType);
+- }
+- String contentLanguage = res.getContentLanguage();
+- if( contentLanguage != null ) {
+- headers.setValue("Content-Language").setString(contentLanguage);
+- }
+- long contentLength = res.getContentLengthLong();
+- if( contentLength >= 0 ) {
+- headers.setValue("Content-Length").setLong(contentLength);
+- }
+- int numHeaders = headers.size();
+- outputMsg.appendInt(numHeaders);
+- for( int i=0; i<numHeaders; i++ ) {
+- MessageBytes hN=headers.getName(i);
+- // no header to sc conversion - there's little benefit
+- // on this direction
+- c2b.convert ( hN );
+- outputMsg.appendBytes( hN );
+-
+- MessageBytes hV=headers.getValue(i);
+- c2b.convert( hV );
+- outputMsg.appendBytes( hV );
+- }
+- mc.getSource().send( outputMsg, mc );
+- }
+-
+- /**
+- * Set the replay buffer for Form auth
+- */
+- public void setReplay(ByteChunk replay) {
+- isFirst = false;
+- isEmpty = false;
+- isReplay = true;
+- bodyBuff.setBytes(replay.getBytes(), replay.getStart(), replay.getLength());
+- }
+-
+-
+-}
+Index: java/org/apache/jk/common/AjpConstants.java
+===================================================================
+--- java/org/apache/jk/common/AjpConstants.java (revision 590752)
++++ java/org/apache/jk/common/AjpConstants.java (working copy)
+@@ -1,193 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.common;
+-
+-
+-/**
+- * Common class for the AJP Protocol values
+- */
+-
+-public class AjpConstants {
+- // Prefix codes for message types from server to container
+- /**
+- * Message code for initial Request packet
+- */
+- public static final byte JK_AJP13_FORWARD_REQUEST = 2;
+- /**
+- * Message code for a request to shutdown Tomcat
+- */
+- public static final byte JK_AJP13_SHUTDOWN = 7;
+- /**
+- * Message code for a Ping request (obsolete)
+- */
+- public static final byte JK_AJP13_PING_REQUEST = 8;
+- /**
+- * Message code for a CPing request
+- */
+- public static final byte JK_AJP13_CPING_REQUEST = 10;
+-
+- // Prefix codes for message types from container to server
+- /**
+- * Response code that the package is part of the Response body
+- */
+- public static final byte JK_AJP13_SEND_BODY_CHUNK = 3;
+- /**
+- * Response code that the package is the HTTP headers
+- */
+- public static final byte JK_AJP13_SEND_HEADERS = 4;
+- /**
+- * Response code for EOT
+- */
+- public static final byte JK_AJP13_END_RESPONSE = 5;
+- /**
+- * Response code to request the next Request body chunk
+- */
+- public static final byte JK_AJP13_GET_BODY_CHUNK = 6;
+- /**
+- * Response code to reply to a CPing
+- */
+- public static final byte JK_AJP13_CPONG_REPLY = 9;
+-
+- // Integer codes for common response header strings
+- public static final int SC_RESP_CONTENT_TYPE = 0xA001;
+- public static final int SC_RESP_CONTENT_LANGUAGE = 0xA002;
+- public static final int SC_RESP_CONTENT_LENGTH = 0xA003;
+- public static final int SC_RESP_DATE = 0xA004;
+- public static final int SC_RESP_LAST_MODIFIED = 0xA005;
+- public static final int SC_RESP_LOCATION = 0xA006;
+- public static final int SC_RESP_SET_COOKIE = 0xA007;
+- public static final int SC_RESP_SET_COOKIE2 = 0xA008;
+- public static final int SC_RESP_SERVLET_ENGINE = 0xA009;
+- public static final int SC_RESP_STATUS = 0xA00A;
+- public static final int SC_RESP_WWW_AUTHENTICATE = 0xA00B;
+-
+- // Integer codes for common (optional) request attribute names
+- public static final byte SC_A_CONTEXT = 1; // XXX Unused
+- public static final byte SC_A_SERVLET_PATH = 2; // XXX Unused
+- public static final byte SC_A_REMOTE_USER = 3;
+- public static final byte SC_A_AUTH_TYPE = 4;
+- public static final byte SC_A_QUERY_STRING = 5;
+- public static final byte SC_A_JVM_ROUTE = 6;
+- public static final byte SC_A_SSL_CERT = 7;
+- public static final byte SC_A_SSL_CIPHER = 8;
+- public static final byte SC_A_SSL_SESSION = 9;
+- public static final byte SC_A_SSL_KEYSIZE = 11;
+- public static final byte SC_A_SECRET = 12;
+- public static final byte SC_A_STORED_METHOD = 13;
+-
+- // Used for attributes which are not in the list above
+- /**
+- * Request Attribute is passed as a String
+- */
+- public static final byte SC_A_REQ_ATTRIBUTE = 10;
+-
+- /**
+- * Terminates list of attributes
+- */
+- public static final byte SC_A_ARE_DONE = (byte)0xFF;
+-
+- /**
+- * Translates integer codes to names of HTTP methods
+- */
+- public static final String []methodTransArray = {
+- "OPTIONS",
+- "GET",
+- "HEAD",
+- "POST",
+- "PUT",
+- "DELETE",
+- "TRACE",
+- "PROPFIND",
+- "PROPPATCH",
+- "MKCOL",
+- "COPY",
+- "MOVE",
+- "LOCK",
+- "UNLOCK",
+- "ACL",
+- "REPORT",
+- "VERSION-CONTROL",
+- "CHECKIN",
+- "CHECKOUT",
+- "UNCHECKOUT",
+- "SEARCH",
+- "MKWORKSPACE",
+- "UPDATE",
+- "LABEL",
+- "MERGE",
+- "BASELINE-CONTROL",
+- "MKACTIVITY"
+- };
+-
+- /**
+- * Request Method is passed as a String
+- */
+- public static final int SC_M_JK_STORED = (byte) 0xFF;
+-
+- // id's for common request headers
+- public static final int SC_REQ_ACCEPT = 1;
+- public static final int SC_REQ_ACCEPT_CHARSET = 2;
+- public static final int SC_REQ_ACCEPT_ENCODING = 3;
+- public static final int SC_REQ_ACCEPT_LANGUAGE = 4;
+- public static final int SC_REQ_AUTHORIZATION = 5;
+- public static final int SC_REQ_CONNECTION = 6;
+- public static final int SC_REQ_CONTENT_TYPE = 7;
+- public static final int SC_REQ_CONTENT_LENGTH = 8;
+- public static final int SC_REQ_COOKIE = 9;
+- public static final int SC_REQ_COOKIE2 = 10;
+- public static final int SC_REQ_HOST = 11;
+- public static final int SC_REQ_PRAGMA = 12;
+- public static final int SC_REQ_REFERER = 13;
+- public static final int SC_REQ_USER_AGENT = 14;
+- // AJP14 new header
+- public static final byte SC_A_SSL_KEY_SIZE = 11; // XXX ???
+-
+- /**
+- * Translates integer codes to request header names
+- */
+- public static final String []headerTransArray = {
+- "accept",
+- "accept-charset",
+- "accept-encoding",
+- "accept-language",
+- "authorization",
+- "connection",
+- "content-type",
+- "content-length",
+- "cookie",
+- "cookie2",
+- "host",
+- "pragma",
+- "referer",
+- "user-agent"
+- };
+- // Ajp13 specific - needs refactoring for the new model
+- /**
+- * Maximum Total byte size for a AJP packet
+- */
+- public static final int MAX_PACKET_SIZE=8192;
+- /**
+- * Size of basic packet header
+- */
+- public static final int H_SIZE=4;
+- /**
+- * Maximum size of data that can be sent in one packet
+- */
+- public static final int MAX_READ_SIZE = MAX_PACKET_SIZE - H_SIZE - 2;
+-
+-}
+Index: java/org/apache/jk/common/ChannelJni.java
+===================================================================
+--- java/org/apache/jk/common/ChannelJni.java (revision 590752)
++++ java/org/apache/jk/common/ChannelJni.java (working copy)
+@@ -1,192 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.common;
+-
+-import java.io.IOException;
+-
+-import org.apache.jk.core.JkHandler;
+-import org.apache.jk.core.Msg;
+-import org.apache.jk.core.MsgContext;
+-import org.apache.jk.core.JkChannel;
+-
+-import org.apache.coyote.Request;
+-/** Pass messages using jni
+- *
+- * @author Costin Manolache
+- */
+-public class ChannelJni extends JniHandler implements JkChannel {
+- int receivedNote=1;
+-
+- public ChannelJni() {
+- // we use static for now, it's easier on the C side.
+- // Easy to change after we get everything working
+- }
+-
+- public void init() throws IOException {
+- super.initNative("channel.jni:jni");
+-
+- if( apr==null ) return;
+-
+- // We'll be called from C. This deals with that.
+- apr.addJkHandler( "channelJni", this );
+- log.info("JK: listening on channel.jni:jni" );
+-
+- if( next==null ) {
+- if( nextName!=null )
+- setNext( wEnv.getHandler( nextName ) );
+- if( next==null )
+- next=wEnv.getHandler( "dispatch" );
+- if( next==null )
+- next=wEnv.getHandler( "request" );
+- if( log.isDebugEnabled() )
+- log.debug("Setting default next " + next.getClass().getName());
+- }
+- }
+-
+- /** Receives does nothing - send will put the response
+- * in the same buffer
+- */
+- public int receive( Msg msg, MsgContext ep )
+- throws IOException
+- {
+- Msg sentResponse=(Msg)ep.getNote( receivedNote );
+- ep.setNote( receivedNote, null );
+-
+- if( sentResponse == null ) {
+- if( log.isDebugEnabled() )
+- log.debug("No send() prior to receive(), no data buffer");
+- // No sent() was done prior to receive.
+- msg.reset();
+- msg.end();
+- sentResponse = msg;
+- }
+-
+- sentResponse.processHeader();
+-
+- if( log.isTraceEnabled() )
+- sentResponse.dump("received response ");
+-
+- if( msg != sentResponse ) {
+- log.error( "Error, in JNI mode the msg used for receive() must be identical with the one used for send()");
+- }
+-
+- return 0;
+- }
+-
+- /** Send the packet. XXX This will modify msg !!!
+- * We could use 2 packets, or sendAndReceive().
+- *
+- */
+- public int send( Msg msg, MsgContext ep )
+- throws IOException
+- {
+- ep.setNote( receivedNote, null );
+- if( log.isDebugEnabled() ) log.debug("ChannelJni.send: " + msg );
+-
+- int rc=super.nativeDispatch( msg, ep, JK_HANDLE_JNI_DISPATCH, 0);
+-
+- // nativeDispatch will put the response in the same buffer.
+- // Next receive() will just get it from there. Very tricky to do
+- // things in one thread instead of 2.
+- ep.setNote( receivedNote, msg );
+-
+- return rc;
+- }
+-
+- public int flush(Msg msg, MsgContext ep) throws IOException {
+- ep.setNote( receivedNote, null );
+- return OK;
+- }
+-
+- public boolean isSameAddress(MsgContext ep) {
+- return true;
+- }
+-
+- public void registerRequest(Request req, MsgContext ep, int count) {
+- // Not supported.
+- }
+-
+- public String getChannelName() {
+- return getName();
+- }
+- /** Receive a packet from the C side. This is called from the C
+- * code using invocation, but only for the first packet - to avoid
+- * recursivity and thread problems.
+- *
+- * This may look strange, but seems the best solution for the
+- * problem ( the problem is that we don't have 'continuation' ).
+- *
+- * sendPacket will move the thread execution on the C side, and
+- * return when another packet is available. For packets that
+- * are one way it'll return after it is processed too ( having
+- * 2 threads is far more expensive ).
+- *
+- * Again, the goal is to be efficient and behave like all other
+- * Channels ( so the rest of the code can be shared ). Playing with
+- * java objects on C is extremely difficult to optimize and do
+- * right ( IMHO ), so we'll try to keep it simple - byte[] passing,
+- * the conversion done in java ( after we know the encoding and
+- * if anyone asks for it - same lazy behavior as in 3.3 ).
+- */
+- public int invoke(Msg msg, MsgContext ep ) throws IOException {
+- if( apr==null ) return -1;
+-
+- long xEnv=ep.getJniEnv();
+- long cEndpointP=ep.getJniContext();
+-
+- int type=ep.getType();
+- if( log.isDebugEnabled() ) log.debug("ChannelJni.invoke: " + ep + " " + type);
+-
+- switch( type ) {
+- case JkHandler.HANDLE_RECEIVE_PACKET:
+- return receive( msg, ep );
+- case JkHandler.HANDLE_SEND_PACKET:
+- return send( msg, ep );
+- case JkHandler.HANDLE_FLUSH:
+- return flush(msg, ep);
+- }
+-
+- // Reset receivedNote. It'll be visible only after a SEND and before a receive.
+- ep.setNote( receivedNote, null );
+-
+- // Default is FORWARD - called from C
+- try {
+- // first, we need to get an endpoint. It should be
+- // per/thread - and probably stored by the C side.
+- if( log.isDebugEnabled() ) log.debug("Received request " + xEnv);
+-
+- // The endpoint will store the message pt.
+- msg.processHeader();
+-
+- if( log.isTraceEnabled() ) msg.dump("Incoming msg ");
+-
+- int status= next.invoke( msg, ep );
+-
+- if( log.isDebugEnabled() ) log.debug("after processCallbacks " + status);
+-
+- return status;
+- } catch( Exception ex ) {
+- ex.printStackTrace();
+- }
+- return 0;
+- }
+-
+- private static org.apache.juli.logging.Log log=
+- org.apache.juli.logging.LogFactory.getLog( ChannelJni.class );
+-
+-}
+Index: java/org/apache/jk/common/JkMX.java
+===================================================================
+--- java/org/apache/jk/common/JkMX.java (revision 590752)
++++ java/org/apache/jk/common/JkMX.java (working copy)
+@@ -1,395 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.common;
+-
+-
+-import org.apache.jk.core.JkHandler;
+-
+-import javax.management.MBeanServer;
+-import javax.management.ObjectName;
+-import javax.management.Attribute;
+-import javax.management.MBeanServerFactory;
+-import java.io.IOException;
+-
+-/**
+- * Load the HTTP or RMI adapters for MX4J and JMXRI.
+- *
+- * Add "mx.enabled=true" in jk2.properties to enable it.
+- * You could also select http and/or jrmp protocol,
+- * with mx.httpPort, mx.httpHost, mxjrmpPort and mx.jrmpPort.
+- * <p />
+- * If you run into an error message like
+- * "SystemId Unknown; Line #12; Column #81; Cannot add attribute name after
+- * child nodes or before an element is produced. Attribute will be ignored."
+- * after setting mx.enabled to true, you probably need a newer version
+- * of Xalan. See the RELEASE-NOTES document section on XML Parsers for
+- * more information.
+- *
+- */
+-public class JkMX extends JkHandler
+-{
+- MBeanServer mserver;
+- private boolean enabled=false;
+- private boolean log4jEnabled=true;
+- private int httpport=-1;
+- private String httphost="localhost";
+- private String authmode="none";
+- private String authuser=null;
+- private String authpassword=null;
+- private int jrmpport=-1;
+- private String jrmphost="localhost";
+- private boolean useXSLTProcessor = true;
+-
+- public JkMX() {
+- }
+-
+- /* -------------------- Public methods -------------------- */
+-
+- /** Enable the MX4J adapters (new way)
+- */
+- public void setEnabled(boolean b) {
+- enabled=b;
+- }
+-
+- public boolean getEnabled() {
+- return enabled;
+- }
+-
+- /** Enable the Log4j MBean)
+- */
+- public void setLog4jEnabled(boolean b) {
+- log4jEnabled=b;
+- }
+-
+- public boolean getLog4jEnabled() {
+- return log4jEnabled;
+- }
+-
+- /** Enable the MX4J adapters (old way, compatible)
+- */
+- public void setPort(int i) {
+- enabled=(i != -1);
+- }
+-
+- public int getPort() {
+- return ((httpport != -1) ? httpport : jrmpport);
+- }
+-
+- /** Enable the MX4J HTTP internal adapter
+- */
+- public void setHttpPort( int i ) {
+- httpport=i;
+- }
+-
+- public int getHttpPort() {
+- return httpport;
+- }
+-
+- public void setHttpHost(String host ) {
+- this.httphost=host;
+- }
+-
+- public String getHttpHost() {
+- return httphost;
+- }
+-
+- public void setAuthMode(String mode) {
+- authmode=mode;
+- }
+-
+- public String getAuthMode() {
+- return authmode;
+- }
+-
+- public void setAuthUser(String user) {
+- authuser=user;
+- }
+-
+- public String getAuthUser() {
+- return authuser;
+- }
+-
+- public void setAuthPassword(String password) {
+- authpassword=password;
+- }
+-
+- public String getAuthPassword() {
+- return authpassword;
+- }
+-
+- /** Enable the MX4J JRMP internal adapter
+- */
+- public void setJrmpPort( int i ) {
+- jrmpport=i;
+- }
+-
+- public int getJrmpPort() {
+- return jrmpport;
+- }
+-
+- public void setJrmpHost(String host ) {
+- this.jrmphost=host;
+- }
+-
+- public String getJrmpHost() {
+- return jrmphost;
+- }
+-
+- public boolean getUseXSLTProcessor() {
+- return useXSLTProcessor;
+- }
+-
+- public void setUseXSLTProcessor(boolean uxsltp) {
+- useXSLTProcessor = uxsltp;
+- }
+-
+- /* ==================== Start/stop ==================== */
+- ObjectName httpServerName=null;
+- ObjectName jrmpServerName=null;
+-
+- /** Initialize the worker. After this call the worker will be
+- * ready to accept new requests.
+- */
+- public void loadAdapter() throws IOException {
+- boolean httpAdapterLoaded = false;
+- boolean jrmpAdapterLoaded = false;
+-
+- if ((httpport != -1) && classExists("mx4j.adaptor.http.HttpAdaptor")) {
+- try {
+- httpServerName = registerObject("mx4j.adaptor.http.HttpAdaptor",
+- "Http:name=HttpAdaptor");
+-
+-
+- if( httphost!=null )
+- mserver.setAttribute(httpServerName, new Attribute("Host", httphost));
+- mserver.setAttribute(httpServerName, new Attribute("Port", new Integer(httpport)));
+-
+- if( "none".equals(authmode) || "basic".equals(authmode) || "digest".equals(authmode) )
+- mserver.setAttribute(httpServerName, new Attribute("AuthenticationMethod", authmode));
+-
+- if( authuser!=null && authpassword!=null )
+- mserver.invoke(httpServerName, "addAuthorization",
+- new Object[] {
+- authuser,
+- authpassword},
+- new String[] { "java.lang.String", "java.lang.String" });
+-
+- if(useXSLTProcessor) {
+- ObjectName processorName = registerObject("mx4j.adaptor.http.XSLTProcessor",
+- "Http:name=XSLTProcessor");
+- mserver.setAttribute(httpServerName, new Attribute("ProcessorName", processorName));
+- }
+-
+- // starts the server
+- mserver.invoke(httpServerName, "start", null, null);
+-
+- log.info( "Started MX4J console on host " + httphost + " at port " + httpport);
+-
+- httpAdapterLoaded = true;
+-
+- } catch( Throwable t ) {
+- httpServerName=null;
+- log.error( "Can't load the MX4J http adapter ", t );
+- }
+- }
+-
+- if ((httpport != -1) && (!httpAdapterLoaded) && classExists("mx4j.tools.adaptor.http.HttpAdaptor")) {
+- try {
+- httpServerName = registerObject("mx4j.tools.adaptor.http.HttpAdaptor",
+- "Http:name=HttpAdaptor");
+-
+-
+- if( httphost!=null )
+- mserver.setAttribute(httpServerName, new Attribute("Host", httphost));
+- mserver.setAttribute(httpServerName, new Attribute("Port", new Integer(httpport)));
+-
+- if( "none".equals(authmode) || "basic".equals(authmode) || "digest".equals(authmode) )
+- mserver.setAttribute(httpServerName, new Attribute("AuthenticationMethod", authmode));
+-
+- if( authuser!=null && authpassword!=null )
+- mserver.invoke(httpServerName, "addAuthorization",
+- new Object[] {
+- authuser,
+- authpassword},
+- new String[] { "java.lang.String", "java.lang.String" });
+-
+- if(useXSLTProcessor) {
+- ObjectName processorName = registerObject("mx4j.tools.adaptor.http.XSLTProcessor",
+- "Http:name=XSLTProcessor");
+- mserver.setAttribute(httpServerName, new Attribute("ProcessorName", processorName));
+- }
+- // starts the server
+- mserver.invoke(httpServerName, "start", null, null);
+- if(log.isInfoEnabled())
+- log.info( "Started MX4J console on host " + httphost + " at port " + httpport);
+-
+- httpAdapterLoaded = true;
+-
+- } catch( Throwable t ) {
+- httpServerName=null;
+- log.error( "Can't load the MX4J http adapter ", t );
+- }
+- }
+-
+- if ((jrmpport != -1) && classExists("mx4j.tools.naming.NamingService")) {
+- try {
+- jrmpServerName = registerObject("mx4j.tools.naming.NamingService",
+- "Naming:name=rmiregistry");
+- mserver.setAttribute(jrmpServerName, new Attribute("Port",
+- new Integer(jrmpport)));
+- mserver.invoke(jrmpServerName, "start", null, null);
+- if(log.isInfoEnabled())
+- log.info( "Creating " + jrmpServerName );
+-
+- // Create the JRMP adaptor
+- ObjectName adaptor = registerObject("mx4j.adaptor.rmi.jrmp.JRMPAdaptor",
+- "Adaptor:protocol=jrmp");
+-
+-
+- mserver.setAttribute(adaptor, new Attribute("JNDIName", "jrmp"));
+-
+- mserver.invoke( adaptor, "putNamingProperty",
+- new Object[] {
+- javax.naming.Context.INITIAL_CONTEXT_FACTORY,
+- "com.sun.jndi.rmi.registry.RegistryContextFactory"},
+- new String[] { "java.lang.Object", "java.lang.Object" });
+-
+- String jrpmurl = "rmi://" + jrmphost + ":" + Integer.toString(jrmpport) ;
+-
+- mserver.invoke( adaptor, "putNamingProperty",
+- new Object[] {
+- javax.naming.Context.PROVIDER_URL,
+- jrpmurl},
+- new String[] { "java.lang.Object", "java.lang.Object" });
+-
+- // Registers the JRMP adaptor in JNDI and starts it
+- mserver.invoke(adaptor, "start", null, null);
+- if(log.isInfoEnabled())
+- log.info( "Creating " + adaptor + " on host " + jrmphost + " at port " + jrmpport);
+-
+- jrmpAdapterLoaded = true;
+-
+- } catch( Exception ex ) {
+- jrmpServerName = null;
+- log.error( "MX4j RMI adapter not loaded: " + ex.toString());
+- }
+- }
+-
+- if ((httpport != -1) && (! httpAdapterLoaded) && classExists("com.sun.jdmk.comm.HtmlAdaptorServer")) {
+- try {
+- httpServerName=registerObject("com.sun.jdmk.comm.HtmlAdaptorServer",
+- "Adaptor:name=html,port=" + httpport);
+- if(log.isInfoEnabled())
+- log.info("Registering the JMX_RI html adapter " + httpServerName + " at port " + httpport);
+-
+- mserver.setAttribute(httpServerName,
+- new Attribute("Port", new Integer(httpport)));
+-
+- mserver.invoke(httpServerName, "start", null, null);
+-
+- httpAdapterLoaded = true;
+- } catch( Throwable t ) {
+- httpServerName = null;
+- log.error( "Can't load the JMX_RI http adapter " + t.toString() );
+- }
+- }
+-
+- if ((!httpAdapterLoaded) && (!jrmpAdapterLoaded))
+- log.warn( "No adaptors were loaded but mx.enabled was defined.");
+-
+- }
+-
+- public void destroy() {
+- try {
+- if(log.isInfoEnabled())
+- log.info("Stoping JMX ");
+-
+- if( httpServerName!=null ) {
+- mserver.invoke(httpServerName, "stop", null, null);
+- }
+- if( jrmpServerName!=null ) {
+- mserver.invoke(jrmpServerName, "stop", null, null);
+- }
+- } catch( Throwable t ) {
+- log.error( "Destroy error" + t );
+- }
+- }
+-
+- public void init() throws IOException {
+- try {
+- mserver = getMBeanServer();
+-
+- if( enabled ) {
+- loadAdapter();
+- }
+- if( log4jEnabled) {
+- try {
+- registerObject("org.apache.log4j.jmx.HierarchyDynamicMBean" ,
+- "log4j:hierarchy=default");
+- if(log.isInfoEnabled())
+- log.info("Registering the JMX hierarchy for Log4J ");
+- } catch( Throwable t ) {
+- if(log.isInfoEnabled())
+- log.info("Can't enable log4j mx: ",t);
+- }
+- }
+- } catch( Throwable t ) {
+- log.error( "Init error", t );
+- }
+- }
+-
+- public void addHandlerCallback( JkHandler w ) {
+- }
+-
+- MBeanServer getMBeanServer() {
+- MBeanServer server;
+- if( MBeanServerFactory.findMBeanServer(null).size() > 0 ) {
+- server=(MBeanServer)MBeanServerFactory.findMBeanServer(null).get(0);
+- } else {
+- server=MBeanServerFactory.createMBeanServer();
+- }
+- return (server);
+- }
+-
+-
+- private static boolean classExists(String className) {
+- try {
+- Thread.currentThread().getContextClassLoader().loadClass(className);
+- return true;
+- } catch(Throwable e) {
+- if (log.isInfoEnabled())
+- log.info( "className [" + className + "] does not exist");
+- return false;
+- }
+- }
+-
+- private ObjectName registerObject(String className, String oName)
+- throws Exception {
+- Class c = Class.forName(className);
+- Object o = c.newInstance();
+- ObjectName objN = new ObjectName(oName);
+- mserver.registerMBean(o, objN);
+- return objN;
+- }
+-
+- private static org.apache.juli.logging.Log log=
+- org.apache.juli.logging.LogFactory.getLog( JkMX.class );
+-
+-
+-}
+-
+Index: java/org/apache/jk/common/ChannelUn.java
+===================================================================
+--- java/org/apache/jk/common/ChannelUn.java (revision 590752)
++++ java/org/apache/jk/common/ChannelUn.java (working copy)
+@@ -1,395 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.common;
+-
+-import java.net.URLEncoder;
+-import java.io.File;
+-import java.io.FileOutputStream;
+-import java.io.IOException;
+-import javax.management.ObjectName;
+-
+-import org.apache.jk.core.JkHandler;
+-import org.apache.jk.core.Msg;
+-import org.apache.jk.core.MsgContext;
+-import org.apache.jk.core.JkChannel;
+-import org.apache.jk.core.WorkerEnv;
+-import org.apache.coyote.Request;
+-import org.apache.coyote.RequestGroupInfo;
+-import org.apache.coyote.RequestInfo;
+-import org.apache.tomcat.util.modeler.Registry;
+-import org.apache.tomcat.util.threads.ThreadPool;
+-import org.apache.tomcat.util.threads.ThreadPoolRunnable;
+-
+-
+-/** Pass messages using unix domain sockets.
+- *
+- * @author Costin Manolache
+- */
+-public class ChannelUn extends JniHandler implements JkChannel {
+- static final int CH_OPEN=4;
+- static final int CH_CLOSE=5;
+- static final int CH_READ=6;
+- static final int CH_WRITE=7;
+-
+- String file;
+- ThreadPool tp = ThreadPool.createThreadPool(true);
+-
+- /* ==================== Tcp socket options ==================== */
+-
+- public ThreadPool getThreadPool() {
+- return tp;
+- }
+-
+- public void setFile( String f ) {
+- file=f;
+- }
+-
+- public String getFile() {
+- return file;
+- }
+-
+- /* ==================== ==================== */
+- int socketNote=1;
+- int isNote=2;
+- int osNote=3;
+-
+- int localId=0;
+-
+- public void init() throws IOException {
+- if( file==null ) {
+- log.debug("No file, disabling unix channel");
+- return;
+- //throw new IOException( "No file for the unix socket channel");
+- }
+- if( wEnv!=null && wEnv.getLocalId() != 0 ) {
+- localId=wEnv.getLocalId();
+- }
+-
+- if( localId != 0 ) {
+- file=file+ localId;
+- }
+- File socketFile=new File( file );
+- if( !socketFile.isAbsolute() ) {
+- String home=wEnv.getJkHome();
+- if( home==null ) {
+- log.debug("No jkhome");
+- } else {
+- File homef=new File( home );
+- socketFile=new File( homef, file );
+- log.debug( "Making the file absolute " +socketFile);
+- }
+- }
+-
+- if( ! socketFile.exists() ) {
+- try {
+- FileOutputStream fos=new FileOutputStream(socketFile);
+- fos.write( 1 );
+- fos.close();
+- } catch( Throwable t ) {
+- log.error("Attempting to create the file failed, disabling channel"
+- + socketFile);
+- return;
+- }
+- }
+- // The socket file cannot be removed ...
+- if (!socketFile.delete()) {
+- log.error( "Can't remove socket file " + socketFile);
+- return;
+- }
+-
+-
+- super.initNative( "channel.un:" + file );
+-
+- if( apr==null || ! apr.isLoaded() ) {
+- log.debug("Apr is not available, disabling unix channel ");
+- apr=null;
+- return;
+- }
+-
+- // Set properties and call init.
+- setNativeAttribute( "file", file );
+- // unixListenSocket=apr.unSocketListen( file, 10 );
+-
+- setNativeAttribute( "listen", "10" );
+- // setNativeAttribute( "debug", "10" );
+-
+- // Initialize the thread pool and execution chain
+- if( next==null && wEnv!=null ) {
+- if( nextName!=null )
+- setNext( wEnv.getHandler( nextName ) );
+- if( next==null )
+- next=wEnv.getHandler( "dispatch" );
+- if( next==null )
+- next=wEnv.getHandler( "request" );
+- }
+-
+- super.initJkComponent();
+- JMXRequestNote =wEnv.getNoteId( WorkerEnv.ENDPOINT_NOTE, "requestNote");
+- // Run a thread that will accept connections.
+- if( this.domain != null ) {
+- try {
+- tpOName=new ObjectName(domain + ":type=ThreadPool,name=" +
+- getChannelName());
+-
+- Registry.getRegistry(null, null)
+- .registerComponent(tp, tpOName, null);
+-
+- rgOName = new ObjectName
+- (domain+":type=GlobalRequestProcessor,name=" + getChannelName());
+- Registry.getRegistry(null, null)
+- .registerComponent(global, rgOName, null);
+- } catch (Exception e) {
+- log.error("Can't register threadpool" );
+- }
+- }
+- tp.start();
+- AprAcceptor acceptAjp=new AprAcceptor( this );
+- tp.runIt( acceptAjp);
+- log.info("JK: listening on unix socket: " + file );
+-
+- }
+-
+- ObjectName tpOName;
+- ObjectName rgOName;
+- RequestGroupInfo global=new RequestGroupInfo();
+- int count = 0;
+- int JMXRequestNote;
+-
+- public void start() throws IOException {
+- }
+-
+- public void destroy() throws IOException {
+- if( apr==null ) return;
+- try {
+- if( tp != null )
+- tp.shutdown();
+-
+- //apr.unSocketClose( unixListenSocket,3);
+- super.destroyJkComponent();
+-
+- if(tpOName != null) {
+- Registry.getRegistry(null, null).unregisterComponent(tpOName);
+- }
+- if(rgOName != null) {
+- Registry.getRegistry(null, null).unregisterComponent(rgOName);
+- }
+- } catch(Exception e) {
+- log.error("Error in destroy",e);
+- }
+- }
+-
+- public void registerRequest(Request req, MsgContext ep, int count) {
+- if(this.domain != null) {
+- try {
+-
+- RequestInfo rp=req.getRequestProcessor();
+- rp.setGlobalProcessor(global);
+- ObjectName roname = new ObjectName
+- (getDomain() + ":type=RequestProcessor,worker="+
+- getChannelName()+",name=JkRequest" +count);
+- ep.setNote(JMXRequestNote, roname);
+-
+- Registry.getRegistry(null, null).registerComponent( rp, roname, null);
+- } catch( Exception ex ) {
+- log.warn("Error registering request");
+- }
+- }
+- }
+-
+-
+- /** Open a connection - since we're listening that will block in
+- accept
+- */
+- public int open(MsgContext ep) throws IOException {
+- // Will associate a jk_endpoint with ep and call open() on it.
+- // jk_channel_un will accept a connection and set the socket info
+- // in the endpoint. MsgContext will represent an active connection.
+- return super.nativeDispatch( ep.getMsg(0), ep, CH_OPEN, 1 );
+- }
+-
+- public void close(MsgContext ep) throws IOException {
+- super.nativeDispatch( ep.getMsg(0), ep, CH_CLOSE, 1 );
+- }
+-
+- public int send( Msg msg, MsgContext ep)
+- throws IOException
+- {
+- return super.nativeDispatch( msg, ep, CH_WRITE, 0 );
+- }
+-
+- public int receive( Msg msg, MsgContext ep )
+- throws IOException
+- {
+- int rc=super.nativeDispatch( msg, ep, CH_READ, 1 );
+-
+- if( rc!=0 ) {
+- log.error("receive error: " + rc, new Throwable());
+- return -1;
+- }
+-
+- msg.processHeader();
+-
+- if (log.isDebugEnabled())
+- log.debug("receive: total read = " + msg.getLen());
+-
+- return msg.getLen();
+- }
+-
+- public int flush( Msg msg, MsgContext ep) throws IOException {
+- return OK;
+- }
+-
+- public boolean isSameAddress( MsgContext ep ) {
+- return false; // Not supporting shutdown on this channel.
+- }
+-
+- boolean running=true;
+-
+- /** Accept incoming connections, dispatch to the thread pool
+- */
+- void acceptConnections() {
+- if( apr==null ) return;
+-
+- if( log.isDebugEnabled() )
+- log.debug("Accepting ajp connections on " + file);
+-
+- while( running ) {
+- try {
+- MsgContext ep=this.createMsgContext();
+-
+- // blocking - opening a server connection.
+- int status=this.open(ep);
+- if( status != 0 && status != 2 ) {
+- log.error( "Error acceptin connection on " + file );
+- break;
+- }
+-
+- // if( log.isDebugEnabled() )
+- // log.debug("Accepted ajp connections ");
+-
+- AprConnection ajpConn= new AprConnection(this, ep);
+- tp.runIt( ajpConn );
+- } catch( Exception ex ) {
+- ex.printStackTrace();
+- }
+- }
+- }
+-
+- /** Process a single ajp connection.
+- */
+- void processConnection(MsgContext ep) {
+- if( log.isDebugEnabled() )
+- log.debug( "New ajp connection ");
+- try {
+- MsgAjp recv=new MsgAjp();
+- while( running ) {
+- int res=this.receive( recv, ep );
+- if( res<0 ) {
+- // EOS
+- break;
+- }
+- ep.setType(0);
+- log.debug( "Process msg ");
+- int status=next.invoke( recv, ep );
+- }
+- if( log.isDebugEnabled() )
+- log.debug( "Closing un channel");
+- try{
+- Request req = (Request)ep.getRequest();
+- if( req != null ) {
+- ObjectName roname = (ObjectName)ep.getNote(JMXRequestNote);
+- if( roname != null ) {
+- Registry.getRegistry(null, null).unregisterComponent(roname);
+- }
+- req.getRequestProcessor().setGlobalProcessor(null);
+- }
+- } catch( Exception ee) {
+- log.error( "Error, releasing connection",ee);
+- }
+- this.close( ep );
+- } catch( Exception ex ) {
+- ex.printStackTrace();
+- }
+- }
+-
+- public int invoke( Msg msg, MsgContext ep ) throws IOException {
+- int type=ep.getType();
+-
+- switch( type ) {
+- case JkHandler.HANDLE_RECEIVE_PACKET:
+- return receive( msg, ep );
+- case JkHandler.HANDLE_SEND_PACKET:
+- return send( msg, ep );
+- case JkHandler.HANDLE_FLUSH:
+- return flush( msg, ep );
+- }
+-
+- // return next.invoke( msg, ep );
+- return OK;
+- }
+-
+- public String getChannelName() {
+- String encodedAddr = "";
+- String address = file;
+- if (address != null) {
+- encodedAddr = "" + address;
+- if (encodedAddr.startsWith("/"))
+- encodedAddr = encodedAddr.substring(1);
+- encodedAddr = URLEncoder.encode(encodedAddr) ;
+- }
+- return ("jk-" + encodedAddr);
+- }
+-
+- private static org.apache.juli.logging.Log log=
+- org.apache.juli.logging.LogFactory.getLog( ChannelUn.class );
+-}
+-
+-class AprAcceptor implements ThreadPoolRunnable {
+- ChannelUn wajp;
+-
+- AprAcceptor(ChannelUn wajp ) {
+- this.wajp=wajp;
+- }
+-
+- public Object[] getInitData() {
+- return null;
+- }
+-
+- public void runIt(Object thD[]) {
+- wajp.acceptConnections();
+- }
+-}
+-
+-class AprConnection implements ThreadPoolRunnable {
+- ChannelUn wajp;
+- MsgContext ep;
+-
+- AprConnection(ChannelUn wajp, MsgContext ep) {
+- this.wajp=wajp;
+- this.ep=ep;
+- }
+-
+-
+- public Object[] getInitData() {
+- return null;
+- }
+-
+- public void runIt(Object perTh[]) {
+- wajp.processConnection(ep);
+- }
+-}
+Index: java/org/apache/jk/common/JniHandler.java
+===================================================================
+--- java/org/apache/jk/common/JniHandler.java (revision 590752)
++++ java/org/apache/jk/common/JniHandler.java (working copy)
+@@ -1,318 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.common;
+-
+-import java.io.IOException;
+-
+-import javax.management.ObjectName;
+-
+-import org.apache.jk.apr.AprImpl;
+-import org.apache.jk.core.JkHandler;
+-import org.apache.jk.core.Msg;
+-import org.apache.jk.core.MsgContext;
+-import org.apache.jk.core.JkChannel;
+-import org.apache.tomcat.util.buf.ByteChunk;
+-import org.apache.tomcat.util.buf.C2BConverter;
+-import org.apache.tomcat.util.buf.MessageBytes;
+-import org.apache.tomcat.util.modeler.Registry;
+-
+-
+-/**
+- * Base class for components using native code ( libjkjni.so ).
+- * It allows to access the jk_env and wrap ( 'box' ? ) a native
+- * jk component, and call it's methods.
+- *
+- * Note that get/setAttribute are expensive ( Strings, etc ),
+- * invoke() is were all optimizations are done. We do recycle
+- * all memory on both C and java sides ( the only exception is
+- * when we attempt pinning but the VM doesn't support it ). The
+- * low level optimizations from ByteBuffer, etc are used to
+- * reduce the overhead of passing strings.
+- *
+- * @author Costin Manolache
+- */
+-public class JniHandler extends JkHandler {
+- protected AprImpl apr;
+-
+- // The native side handler
+- protected long nativeJkHandlerP;
+-
+- protected String jkHome;
+-
+- // Dispatch table codes. Hardcoded for now, will change when we have more handlers.
+- public static final int JK_HANDLE_JNI_DISPATCH=0x15;
+- public static final int JK_HANDLE_SHM_DISPATCH=0x16;
+-
+-
+- public static final int MSG_NOTE=0;
+- public static final int MB_NOTE=2;
+- private boolean paused = false;
+-
+-
+- public JniHandler() {
+- }
+-
+- /**
+- */
+- public void setJkHome( String s ) {
+- jkHome=s;
+- }
+-
+- public String getJkHome() {
+- return jkHome;
+- }
+-
+- /** You must call initNative() inside the component init()
+- */
+- public void init() throws IOException {
+- // static field init, temp
+- }
+-
+- protected void initNative(String nativeComponentName) {
+- apr=(AprImpl)wEnv.getHandler("apr");
+- if( apr==null ) {
+- // In most cases we can just load it automatically.
+- // that requires all libs to be installed in standard places
+- // ( LD_LIBRARY_PATH, /usr/lib
+- try {
+- apr=new AprImpl();
+- wEnv.addHandler("apr", apr);
+- apr.init();
+- if( oname != null ) {
+- ObjectName aprname=new ObjectName(oname.getDomain() +
+- ":type=JkHandler, name=apr");
+- Registry.getRegistry(null, null).registerComponent(apr, aprname, null);
+- }
+- } catch( Throwable t ) {
+- log.debug("Can't load apr", t);
+- apr=null;
+- }
+- }
+- if( apr==null || ! apr.isLoaded() ) {
+- if( log.isDebugEnabled() )
+- log.debug("No apr, disabling jni proxy ");
+- apr=null;
+- return;
+- }
+-
+- try {
+- long xEnv=apr.getJkEnv();
+- nativeJkHandlerP=apr.getJkHandler(xEnv, nativeComponentName );
+-
+- if( nativeJkHandlerP==0 ) {
+- log.debug("Component not found, creating it " + nativeComponentName );
+- nativeJkHandlerP=apr.createJkHandler(xEnv, nativeComponentName);
+- }
+- log.debug("Native proxy " + nativeJkHandlerP );
+- apr.releaseJkEnv(xEnv);
+- } catch( Throwable t ) {
+- apr=null;
+- log.info("Error calling apr ", t);
+- }
+- }
+-
+- public void appendString( Msg msg, String s, C2BConverter charsetDecoder)
+- throws IOException
+- {
+- ByteChunk bc=charsetDecoder.getByteChunk();
+- charsetDecoder.recycle();
+- charsetDecoder.convert( s );
+- charsetDecoder.flushBuffer();
+- msg.appendByteChunk( bc );
+- }
+-
+- public void pause() throws Exception {
+- synchronized(this) {
+- paused = true;
+- }
+- }
+-
+- public void resume() throws Exception {
+- synchronized(this) {
+- paused = false;
+- notifyAll();
+- }
+- }
+-
+-
+- /** Create a msg context to be used with the shm channel
+- */
+- public MsgContext createMsgContext() {
+- if( nativeJkHandlerP==0 || apr==null )
+- return null;
+-
+- synchronized(this) {
+- try{
+- while(paused) {
+- wait();
+- }
+- }catch(InterruptedException ie) {
+- // Ignore, since it can't happen
+- }
+- }
+-
+- try {
+- MsgContext msgCtx=new MsgContext();
+- MsgAjp msg=new MsgAjp();
+-
+- msgCtx.setSource( (JkChannel)this );
+- msgCtx.setWorkerEnv( wEnv );
+-
+- msgCtx.setNext( this );
+-
+- msgCtx.setMsg( MSG_NOTE, msg); // XXX Use noteId
+-
+- C2BConverter c2b=new C2BConverter( "iso-8859-1" );
+- msgCtx.setConverter( c2b );
+-
+- MessageBytes tmpMB= MessageBytes.newInstance();
+- msgCtx.setNote( MB_NOTE, tmpMB );
+- return msgCtx;
+- } catch( Exception ex ) {
+- log.error("Can't create endpoint", ex);
+- return null;
+- }
+- }
+-
+- public void setNativeAttribute(String name, String val) throws IOException {
+- if( apr==null ) return;
+-
+- if( nativeJkHandlerP == 0 ) {
+- log.error( "Unitialized component " + name+ " " + val );
+- return;
+- }
+-
+- long xEnv=apr.getJkEnv();
+-
+- apr.jkSetAttribute( xEnv, nativeJkHandlerP, name, val );
+-
+- apr.releaseJkEnv( xEnv );
+- }
+-
+- public void initJkComponent() throws IOException {
+- if( apr==null ) return;
+-
+- if( nativeJkHandlerP == 0 ) {
+- log.error( "Unitialized component " );
+- return;
+- }
+-
+- long xEnv=apr.getJkEnv();
+-
+- apr.jkInit( xEnv, nativeJkHandlerP );
+-
+- apr.releaseJkEnv( xEnv );
+- }
+-
+- public void destroyJkComponent() throws IOException {
+- if( apr==null ) return;
+-
+- if( nativeJkHandlerP == 0 ) {
+- log.error( "Unitialized component " );
+- return;
+- }
+-
+- long xEnv=apr.getJkEnv();
+-
+- apr.jkDestroy( xEnv, nativeJkHandlerP );
+-
+- apr.releaseJkEnv( xEnv );
+- }
+-
+-
+-
+- protected void setNativeEndpoint(MsgContext msgCtx) {
+- long xEnv=apr.getJkEnv();
+- msgCtx.setJniEnv( xEnv );
+-
+- long epP=apr.createJkHandler(xEnv, "endpoint");
+- log.debug("create ep " + epP );
+- if( epP == 0 ) return;
+- apr.jkInit( xEnv, epP );
+- msgCtx.setJniContext( epP );
+-
+- }
+-
+- protected void recycleNative(MsgContext ep) {
+- apr.jkRecycle(ep.getJniEnv(), ep.getJniContext());
+- }
+-
+- /** send and get the response in the same buffer. This calls the
+- * method on the wrapped jk_bean object.
+- */
+- protected int nativeDispatch( Msg msg, MsgContext ep, int code, int raw )
+- throws IOException
+- {
+- if( log.isDebugEnabled() )
+- log.debug( "Sending packet " + code + " " + raw);
+-
+- if( raw == 0 ) {
+- msg.end();
+-
+- if( log.isTraceEnabled() ) msg.dump("OUT:" );
+- }
+-
+- // Create ( or reuse ) the jk_endpoint ( the native pair of
+- // MsgContext )
+- long xEnv=ep.getJniEnv();
+- long nativeContext=ep.getJniContext();
+- if( nativeContext==0 || xEnv==0 ) {
+- setNativeEndpoint( ep );
+- xEnv=ep.getJniEnv();
+- nativeContext=ep.getJniContext();
+- }
+-
+- if( xEnv==0 || nativeContext==0 || nativeJkHandlerP==0 ) {
+- log.error("invokeNative: Null pointer ");
+- return -1;
+- }
+-
+- // Will process the message in the current thread.
+- // No wait needed to receive the response, if any
+- int status=AprImpl.jkInvoke( xEnv,
+- nativeJkHandlerP,
+- nativeContext,
+- code, msg.getBuffer(), 0, msg.getLen(), raw );
+-
+- if( status != 0 && status != 2 ) {
+- log.error( "nativeDispatch: error " + status, new Throwable() );
+- }
+-
+- if( log.isDebugEnabled() ) log.debug( "Sending packet - done " + status);
+- return status;
+- }
+-
+- /** Base implementation for invoke. Dispatch the action to the native
+- * code, where invoke() is called on the wrapped jk_bean.
+- */
+- public int invoke(Msg msg, MsgContext ep )
+- throws IOException
+- {
+- long xEnv=ep.getJniEnv();
+- int type=ep.getType();
+-
+- int status=nativeDispatch(msg, ep, type, 0 );
+-
+- apr.jkRecycle(xEnv, ep.getJniContext());
+- apr.releaseJkEnv( xEnv );
+- return status;
+- }
+-
+- private static org.apache.juli.logging.Log log=
+- org.apache.juli.logging.LogFactory.getLog( JniHandler.class );
+-}
+Index: java/org/apache/jk/common/ChannelShm.java
+===================================================================
+--- java/org/apache/jk/common/ChannelShm.java (revision 590752)
++++ java/org/apache/jk/common/ChannelShm.java (working copy)
+@@ -1,33 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.common;
+-
+-import org.apache.jk.core.JkHandler;
+-
+-
+-
+-/** Channel using shm.
+- *
+- * @author Costin Manolache
+- */
+-public class ChannelShm extends JkHandler {
+-
+- // Not implemented yet.
+-
+-
+-}
+Index: java/org/apache/jk/common/ChannelSocket.java
+===================================================================
+--- java/org/apache/jk/common/ChannelSocket.java (revision 590752)
++++ java/org/apache/jk/common/ChannelSocket.java (working copy)
+@@ -1,895 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.common;
+-
+-import java.io.BufferedInputStream;
+-import java.io.BufferedOutputStream;
+-import java.io.IOException;
+-import java.io.InputStream;
+-import java.io.OutputStream;
+-import java.net.URLEncoder;
+-import java.net.InetAddress;
+-import java.net.ServerSocket;
+-import java.net.Socket;
+-import java.net.SocketException;
+-
+-import javax.management.ListenerNotFoundException;
+-import javax.management.MBeanNotificationInfo;
+-import javax.management.Notification;
+-import javax.management.NotificationBroadcaster;
+-import javax.management.NotificationBroadcasterSupport;
+-import javax.management.NotificationFilter;
+-import javax.management.NotificationListener;
+-import javax.management.ObjectName;
+-
+-import org.apache.jk.core.JkHandler;
+-import org.apache.jk.core.Msg;
+-import org.apache.jk.core.MsgContext;
+-import org.apache.jk.core.JkChannel;
+-import org.apache.jk.core.WorkerEnv;
+-import org.apache.coyote.Request;
+-import org.apache.coyote.RequestGroupInfo;
+-import org.apache.coyote.RequestInfo;
+-import org.apache.tomcat.util.modeler.Registry;
+-import org.apache.tomcat.util.threads.ThreadPool;
+-import org.apache.tomcat.util.threads.ThreadPoolRunnable;
+-
+-/**
+- * Accept ( and send ) TCP messages.
+- *
+- * @author Costin Manolache
+- * @author Bill Barker
+- * jmx:mbean name="jk:service=ChannelNioSocket"
+- * description="Accept socket connections"
+- * jmx:notification name="org.apache.coyote.INVOKE
+- * jmx:notification-handler name="org.apache.jk.JK_SEND_PACKET
+- * jmx:notification-handler name="org.apache.jk.JK_RECEIVE_PACKET
+- * jmx:notification-handler name="org.apache.jk.JK_FLUSH
+- *
+- * Jk can use multiple protocols/transports.
+- * Various container adapters should load this object ( as a bean ),
+- * set configurations and use it. Note that the connector will handle
+- * all incoming protocols - it's not specific to ajp1x. The protocol
+- * is abstracted by MsgContext/Message/Channel.
+- *
+- * A lot of the 'original' behavior is hardcoded - this uses Ajp13 wire protocol,
+- * TCP, Ajp14 API etc.
+- * As we add other protocols/transports/APIs this will change, the current goal
+- * is to get the same level of functionality as in the original jk connector.
+- *
+- * XXX Make the 'message type' pluggable
+- */
+-public class ChannelSocket extends JkHandler
+- implements NotificationBroadcaster, JkChannel {
+- private static org.apache.juli.logging.Log log =
+- org.apache.juli.logging.LogFactory.getLog( ChannelSocket.class );
+-
+- private int startPort=8009;
+- private int maxPort=8019; // 0 for backward compat.
+- private int port=startPort;
+- private InetAddress inet;
+- private int serverTimeout;
+- private boolean tcpNoDelay=true; // nodelay to true by default
+- private int linger=100;
+- private int socketTimeout;
+- private int bufferSize = -1;
+- private int packetSize = 8*1024;
+-
+- private long requestCount=0;
+-
+- ThreadPool tp=ThreadPool.createThreadPool(true);
+-
+- /* ==================== Tcp socket options ==================== */
+-
+- /**
+- * jmx:managed-constructor description="default constructor"
+- */
+- public ChannelSocket() {
+- // This should be integrated with the domain setup
+- }
+-
+- public ThreadPool getThreadPool() {
+- return tp;
+- }
+-
+- public long getRequestCount() {
+- return requestCount;
+- }
+-
+- /** Set the port for the ajp13 channel.
+- * To support seemless load balancing and jni, we treat this
+- * as the 'base' port - we'll try up until we find one that is not
+- * used. We'll also provide the 'difference' to the main coyote
+- * handler - that will be our 'sessionID' and the position in
+- * the scoreboard and the suffix for the unix domain socket.
+- *
+- * jmx:managed-attribute description="Port to listen" access="READ_WRITE"
+- */
+- public void setPort( int port ) {
+- this.startPort=port;
+- this.port=port;
+- this.maxPort=port+10;
+- }
+-
+- public int getPort() {
+- return port;
+- }
+-
+- public void setAddress(InetAddress inet) {
+- this.inet=inet;
+- }
+-
+- /**
+- * jmx:managed-attribute description="Bind on a specified address" access="READ_WRITE"
+- */
+- public void setAddress(String inet) {
+- try {
+- this.inet= InetAddress.getByName( inet );
+- } catch( Exception ex ) {
+- log.error("Error parsing "+inet,ex);
+- }
+- }
+-
+- public String getAddress() {
+- if( inet!=null)
+- return inet.toString();
+- return "/0.0.0.0";
+- }
+-
+- /**
+- * Sets the timeout in ms of the server sockets created by this
+- * server. This method allows the developer to make servers
+- * more or less responsive to having their server sockets
+- * shut down.
+- *
+- * <p>By default this value is 1000ms.
+- */
+- public void setServerTimeout(int timeout) {
+- this.serverTimeout = timeout;
+- }
+- public int getServerTimeout() {
+- return serverTimeout;
+- }
+-
+- public void setTcpNoDelay( boolean b ) {
+- tcpNoDelay=b;
+- }
+-
+- public boolean getTcpNoDelay() {
+- return tcpNoDelay;
+- }
+-
+- public void setSoLinger( int i ) {
+- linger=i;
+- }
+-
+- public int getSoLinger() {
+- return linger;
+- }
+-
+- public void setSoTimeout( int i ) {
+- socketTimeout=i;
+- }
+-
+- public int getSoTimeout() {
+- return socketTimeout;
+- }
+-
+- public void setMaxPort( int i ) {
+- maxPort=i;
+- }
+-
+- public int getMaxPort() {
+- return maxPort;
+- }
+-
+- public void setBufferSize(int bs) {
+- bufferSize = bs;
+- }
+-
+- public int getBufferSize() {
+- return bufferSize;
+- }
+-
+- public void setPacketSize(int ps) {
+- if(ps < 8*1024) {
+- ps = 8*1024;
+- }
+- packetSize = ps;
+- }
+-
+- public int getPacketSize() {
+- return packetSize;
+- }
+-
+- /** At startup we'll look for the first free port in the range.
+- The difference between this port and the beggining of the range
+- is the 'id'.
+- This is usefull for lb cases ( less config ).
+- */
+- public int getInstanceId() {
+- return port-startPort;
+- }
+-
+- /** If set to false, the thread pool will be created in
+- * non-daemon mode, and will prevent main from exiting
+- */
+- public void setDaemon( boolean b ) {
+- tp.setDaemon( b );
+- }
+-
+- public boolean getDaemon() {
+- return tp.getDaemon();
+- }
+-
+-
+- public void setMaxThreads( int i ) {
+- if( log.isDebugEnabled()) log.debug("Setting maxThreads " + i);
+- tp.setMaxThreads(i);
+- }
+-
+- public void setMinSpareThreads( int i ) {
+- if( log.isDebugEnabled()) log.debug("Setting minSpareThreads " + i);
+- tp.setMinSpareThreads(i);
+- }
+-
+- public void setMaxSpareThreads( int i ) {
+- if( log.isDebugEnabled()) log.debug("Setting maxSpareThreads " + i);
+- tp.setMaxSpareThreads(i);
+- }
+-
+- public int getMaxThreads() {
+- return tp.getMaxThreads();
+- }
+-
+- public int getMinSpareThreads() {
+- return tp.getMinSpareThreads();
+- }
+-
+- public int getMaxSpareThreads() {
+- return tp.getMaxSpareThreads();
+- }
+-
+- public void setBacklog(int i) {
+- }
+-
+-
+- /* ==================== ==================== */
+- ServerSocket sSocket;
+- final int socketNote=1;
+- final int isNote=2;
+- final int osNote=3;
+- final int notifNote=4;
+- boolean paused = false;
+-
+- public void pause() throws Exception {
+- synchronized(this) {
+- paused = true;
+- unLockSocket();
+- }
+- }
+-
+- public void resume() throws Exception {
+- synchronized(this) {
+- paused = false;
+- notify();
+- }
+- }
+-
+-
+- public void accept( MsgContext ep ) throws IOException {
+- if( sSocket==null ) return;
+- synchronized(this) {
+- while(paused) {
+- try{
+- wait();
+- } catch(InterruptedException ie) {
+- //Ignore, since can't happen
+- }
+- }
+- }
+- Socket s=sSocket.accept();
+- ep.setNote( socketNote, s );
+- if(log.isDebugEnabled() )
+- log.debug("Accepted socket " + s );
+-
+- try {
+- setSocketOptions(s);
+- } catch(SocketException sex) {
+- log.debug("Error initializing Socket Options", sex);
+- }
+-
+- requestCount++;
+-
+- InputStream is=new BufferedInputStream(s.getInputStream());
+- OutputStream os;
+- if( bufferSize > 0 )
+- os = new BufferedOutputStream( s.getOutputStream(), bufferSize);
+- else
+- os = s.getOutputStream();
+- ep.setNote( isNote, is );
+- ep.setNote( osNote, os );
+- ep.setControl( tp );
+- }
+-
+- private void setSocketOptions(Socket s) throws SocketException {
+- if( socketTimeout > 0 )
+- s.setSoTimeout( socketTimeout );
+-
+- s.setTcpNoDelay( tcpNoDelay ); // set socket tcpnodelay state
+-
+- if( linger > 0 )
+- s.setSoLinger( true, linger);
+- }
+-
+- public void resetCounters() {
+- requestCount=0;
+- }
+-
+- /** Called after you change some fields at runtime using jmx.
+- Experimental for now.
+- */
+- public void reinit() throws IOException {
+- destroy();
+- init();
+- }
+-
+- /**
+- * jmx:managed-operation
+- */
+- public void init() throws IOException {
+- // Find a port.
+- if (startPort == 0) {
+- port = 0;
+- if(log.isInfoEnabled())
+- log.info("JK: ajp13 disabling channelSocket");
+- running = true;
+- return;
+- }
+- if (maxPort < startPort)
+- maxPort = startPort;
+- for( int i=startPort; i<=maxPort; i++ ) {
+- try {
+- if( inet == null ) {
+- sSocket = new ServerSocket( i, 0 );
+- } else {
+- sSocket=new ServerSocket( i, 0, inet );
+- }
+- port=i;
+- break;
+- } catch( IOException ex ) {
+- if(log.isInfoEnabled())
+- log.info("Port busy " + i + " " + ex.toString());
+- continue;
+- }
+- }
+-
+- if( sSocket==null ) {
+- log.error("Can't find free port " + startPort + " " + maxPort );
+- return;
+- }
+- if(log.isInfoEnabled())
+- log.info("JK: ajp13 listening on " + getAddress() + ":" + port );
+-
+- // If this is not the base port and we are the 'main' channleSocket and
+- // SHM didn't already set the localId - we'll set the instance id
+- if( "channelSocket".equals( name ) &&
+- port != startPort &&
+- (wEnv.getLocalId()==0) ) {
+- wEnv.setLocalId( port - startPort );
+- }
+- if( serverTimeout > 0 )
+- sSocket.setSoTimeout( serverTimeout );
+-
+- // XXX Reverse it -> this is a notification generator !!
+- if( next==null && wEnv!=null ) {
+- if( nextName!=null )
+- setNext( wEnv.getHandler( nextName ) );
+- if( next==null )
+- next=wEnv.getHandler( "dispatch" );
+- if( next==null )
+- next=wEnv.getHandler( "request" );
+- }
+- JMXRequestNote =wEnv.getNoteId( WorkerEnv.ENDPOINT_NOTE, "requestNote");
+- running = true;
+-
+- // Run a thread that will accept connections.
+- // XXX Try to find a thread first - not sure how...
+- if( this.domain != null ) {
+- try {
+- tpOName=new ObjectName(domain + ":type=ThreadPool,name=" +
+- getChannelName());
+-
+- Registry.getRegistry(null, null)
+- .registerComponent(tp, tpOName, null);
+-
+- rgOName = new ObjectName
+- (domain+":type=GlobalRequestProcessor,name=" + getChannelName());
+- Registry.getRegistry(null, null)
+- .registerComponent(global, rgOName, null);
+- } catch (Exception e) {
+- log.error("Can't register threadpool" );
+- }
+- }
+-
+- tp.start();
+- SocketAcceptor acceptAjp=new SocketAcceptor( this );
+- tp.runIt( acceptAjp);
+-
+- }
+-
+- ObjectName tpOName;
+- ObjectName rgOName;
+- RequestGroupInfo global=new RequestGroupInfo();
+- int JMXRequestNote;
+-
+- public void start() throws IOException{
+- if( sSocket==null )
+- init();
+- }
+-
+- public void stop() throws IOException {
+- destroy();
+- }
+-
+- public void registerRequest(Request req, MsgContext ep, int count) {
+- if(this.domain != null) {
+- try {
+- RequestInfo rp=req.getRequestProcessor();
+- rp.setGlobalProcessor(global);
+- ObjectName roname = new ObjectName
+- (getDomain() + ":type=RequestProcessor,worker="+
+- getChannelName()+",name=JkRequest" +count);
+- ep.setNote(JMXRequestNote, roname);
+-
+- Registry.getRegistry(null, null).registerComponent( rp, roname, null);
+- } catch( Exception ex ) {
+- log.warn("Error registering request");
+- }
+- }
+- }
+-
+- public void open(MsgContext ep) throws IOException {
+- }
+-
+-
+- public void close(MsgContext ep) throws IOException {
+- Socket s=(Socket)ep.getNote( socketNote );
+- s.close();
+- }
+-
+- private void unLockSocket() throws IOException {
+- // Need to create a connection to unlock the accept();
+- Socket s;
+- InetAddress ladr = inet;
+-
+- if(port == 0)
+- return;
+- if (ladr == null || "0.0.0.0".equals(ladr.getHostAddress())) {
+- ladr = InetAddress.getLocalHost();
+- }
+- s=new Socket(ladr, port );
+- // setting soLinger to a small value will help shutdown the
+- // connection quicker
+- s.setSoLinger(true, 0);
+-
+- s.close();
+- }
+-
+- public void destroy() throws IOException {
+- running = false;
+- try {
+- /* If we disabled the channel return */
+- if (port == 0)
+- return;
+- tp.shutdown();
+-
+- if(!paused) {
+- unLockSocket();
+- }
+-
+- sSocket.close(); // XXX?
+-
+- if( tpOName != null ) {
+- Registry.getRegistry(null, null).unregisterComponent(tpOName);
+- }
+- if( rgOName != null ) {
+- Registry.getRegistry(null, null).unregisterComponent(rgOName);
+- }
+- } catch(Exception e) {
+- log.info("Error shutting down the channel " + port + " " +
+- e.toString());
+- if( log.isDebugEnabled() ) log.debug("Trace", e);
+- }
+- }
+-
+- public int send( Msg msg, MsgContext ep)
+- throws IOException {
+- msg.end(); // Write the packet header
+- byte buf[]=msg.getBuffer();
+- int len=msg.getLen();
+-
+- if(log.isTraceEnabled() )
+- log.trace("send() " + len + " " + buf[4] );
+-
+- OutputStream os=(OutputStream)ep.getNote( osNote );
+- os.write( buf, 0, len );
+- return len;
+- }
+-
+- public int flush( Msg msg, MsgContext ep)
+- throws IOException {
+- if( bufferSize > 0 ) {
+- OutputStream os=(OutputStream)ep.getNote( osNote );
+- os.flush();
+- }
+- return 0;
+- }
+-
+- public int receive( Msg msg, MsgContext ep )
+- throws IOException {
+- if (log.isDebugEnabled()) {
+- log.debug("receive() ");
+- }
+-
+- byte buf[]=msg.getBuffer();
+- int hlen=msg.getHeaderLength();
+-
+- // XXX If the length in the packet header doesn't agree with the
+- // actual number of bytes read, it should probably return an error
+- // value. Also, callers of this method never use the length
+- // returned -- should probably return true/false instead.
+-
+- int rd = this.read(ep, buf, 0, hlen );
+-
+- if(rd < 0) {
+- // Most likely normal apache restart.
+- // log.warn("Wrong message " + rd );
+- return rd;
+- }
+-
+- msg.processHeader();
+-
+- /* After processing the header we know the body
+- length
+- */
+- int blen=msg.getLen();
+-
+- // XXX check if enough space - it's assert()-ed !!!
+-
+- int total_read = 0;
+-
+- total_read = this.read(ep, buf, hlen, blen);
+-
+- if ((total_read <= 0) && (blen > 0)) {
+- log.warn("can't read body, waited #" + blen);
+- return -1;
+- }
+-
+- if (total_read != blen) {
+- log.warn( "incomplete read, waited #" + blen +
+- " got only " + total_read);
+- return -2;
+- }
+-
+- return total_read;
+- }
+-
+- /**
+- * Read N bytes from the InputStream, and ensure we got them all
+- * Under heavy load we could experience many fragmented packets
+- * just read Unix Network Programming to recall that a call to
+- * read didn't ensure you got all the data you want
+- *
+- * from read() Linux manual
+- *
+- * On success, the number of bytes read is returned (zero indicates end
+- * of file),and the file position is advanced by this number.
+- * It is not an error if this number is smaller than the number of bytes
+- * requested; this may happen for example because fewer bytes
+- * are actually available right now (maybe because we were close to
+- * end-of-file, or because we are reading from a pipe, or from a
+- * terminal), or because read() was interrupted by a signal.
+- * On error, -1 is returned, and errno is set appropriately. In this
+- * case it is left unspecified whether the file position (if any) changes.
+- *
+- **/
+- public int read( MsgContext ep, byte[] b, int offset, int len)
+- throws IOException {
+- InputStream is=(InputStream)ep.getNote( isNote );
+- int pos = 0;
+- int got;
+-
+- while(pos < len) {
+- try {
+- got = is.read(b, pos + offset, len - pos);
+- } catch(SocketException sex) {
+- if(pos > 0) {
+- log.info("Error reading data after "+pos+"bytes",sex);
+- } else {
+- log.debug("Error reading data", sex);
+- }
+- got = -1;
+- }
+- if (log.isTraceEnabled()) {
+- log.trace("read() " + b + " " + (b==null ? 0: b.length) + " " +
+- offset + " " + len + " = " + got );
+- }
+-
+- // connection just closed by remote.
+- if (got <= 0) {
+- // This happens periodically, as apache restarts
+- // periodically.
+- // It should be more gracefull ! - another feature for Ajp14
+- // log.warn( "server has closed the current connection (-1)" );
+- return -3;
+- }
+-
+- pos += got;
+- }
+- return pos;
+- }
+-
+- protected boolean running=true;
+-
+- /** Accept incoming connections, dispatch to the thread pool
+- */
+- void acceptConnections() {
+- if( log.isDebugEnabled() )
+- log.debug("Accepting ajp connections on " + port);
+- while( running ) {
+- try{
+- MsgContext ep=createMsgContext(packetSize);
+- ep.setSource(this);
+- ep.setWorkerEnv( wEnv );
+- this.accept(ep);
+-
+- if( !running ) break;
+-
+- // Since this is a long-running connection, we don't care
+- // about the small GC
+- SocketConnection ajpConn=
+- new SocketConnection(this, ep);
+- tp.runIt( ajpConn );
+- }catch(Exception ex) {
+- if (running)
+- log.warn("Exception executing accept" ,ex);
+- }
+- }
+- }
+-
+- /** Process a single ajp connection.
+- */
+- void processConnection(MsgContext ep) {
+- try {
+- MsgAjp recv=new MsgAjp(packetSize);
+- while( running ) {
+- if(paused) { // Drop the connection on pause
+- break;
+- }
+- int status= this.receive( recv, ep );
+- if( status <= 0 ) {
+- if( status==-3)
+- log.debug( "server has been restarted or reset this connection" );
+- else
+- log.warn("Closing ajp connection " + status );
+- break;
+- }
+- ep.setLong( MsgContext.TIMER_RECEIVED, System.currentTimeMillis());
+-
+- ep.setType( 0 );
+- // Will call next
+- status= this.invoke( recv, ep );
+- if( status!= JkHandler.OK ) {
+- log.warn("processCallbacks status " + status );
+- break;
+- }
+- }
+- } catch( Exception ex ) {
+- String msg = ex.getMessage();
+- if( msg != null && msg.indexOf( "Connection reset" ) >= 0)
+- log.debug( "Server has been restarted or reset this connection");
+- else if (msg != null && msg.indexOf( "Read timed out" ) >=0 )
+- log.debug( "connection timeout reached");
+- else
+- log.error( "Error, processing connection", ex);
+- } finally {
+- /*
+- * Whatever happened to this connection (remote closed it, timeout, read error)
+- * the socket SHOULD be closed, or we may be in situation where the webserver
+- * will continue to think the socket is still open and will forward request
+- * to tomcat without receiving ever a reply
+- */
+- try {
+- this.close( ep );
+- }
+- catch( Exception e) {
+- log.error( "Error, closing connection", e);
+- }
+- try{
+- Request req = (Request)ep.getRequest();
+- if( req != null ) {
+- ObjectName roname = (ObjectName)ep.getNote(JMXRequestNote);
+- if( roname != null ) {
+- Registry.getRegistry(null, null).unregisterComponent(roname);
+- }
+- req.getRequestProcessor().setGlobalProcessor(null);
+- }
+- } catch( Exception ee) {
+- log.error( "Error, releasing connection",ee);
+- }
+- }
+- }
+-
+- // XXX This should become handleNotification
+- public int invoke( Msg msg, MsgContext ep ) throws IOException {
+- int type=ep.getType();
+-
+- switch( type ) {
+- case JkHandler.HANDLE_RECEIVE_PACKET:
+- if( log.isDebugEnabled()) log.debug("RECEIVE_PACKET ?? ");
+- return receive( msg, ep );
+- case JkHandler.HANDLE_SEND_PACKET:
+- return send( msg, ep );
+- case JkHandler.HANDLE_FLUSH:
+- return flush( msg, ep );
+- }
+-
+- if( log.isDebugEnabled() )
+- log.debug("Call next " + type + " " + next);
+-
+- // Send notification
+- if( nSupport!=null ) {
+- Notification notif=(Notification)ep.getNote(notifNote);
+- if( notif==null ) {
+- notif=new Notification("channelSocket.message", ep, requestCount );
+- ep.setNote( notifNote, notif);
+- }
+- nSupport.sendNotification(notif);
+- }
+-
+- if( next != null ) {
+- return next.invoke( msg, ep );
+- } else {
+- log.info("No next ");
+- }
+-
+- return OK;
+- }
+-
+- public boolean isSameAddress(MsgContext ep) {
+- Socket s=(Socket)ep.getNote( socketNote );
+- return isSameAddress( s.getLocalAddress(), s.getInetAddress());
+- }
+-
+- public String getChannelName() {
+- String encodedAddr = "";
+- if (inet != null && !"0.0.0.0".equals(inet.getHostAddress())) {
+- encodedAddr = getAddress();
+- if (encodedAddr.startsWith("/"))
+- encodedAddr = encodedAddr.substring(1);
+- encodedAddr = URLEncoder.encode(encodedAddr) + "-";
+- }
+- return ("jk-" + encodedAddr + port);
+- }
+-
+- /**
+- * Return <code>true</code> if the specified client and server addresses
+- * are the same. This method works around a bug in the IBM 1.1.8 JVM on
+- * Linux, where the address bytes are returned reversed in some
+- * circumstances.
+- *
+- * @param server The server's InetAddress
+- * @param client The client's InetAddress
+- */
+- public static boolean isSameAddress(InetAddress server, InetAddress client)
+- {
+- // Compare the byte array versions of the two addresses
+- byte serverAddr[] = server.getAddress();
+- byte clientAddr[] = client.getAddress();
+- if (serverAddr.length != clientAddr.length)
+- return (false);
+- boolean match = true;
+- for (int i = 0; i < serverAddr.length; i++) {
+- if (serverAddr[i] != clientAddr[i]) {
+- match = false;
+- break;
+- }
+- }
+- if (match)
+- return (true);
+-
+- // Compare the reversed form of the two addresses
+- for (int i = 0; i < serverAddr.length; i++) {
+- if (serverAddr[i] != clientAddr[(serverAddr.length-1)-i])
+- return (false);
+- }
+- return (true);
+- }
+-
+- public void sendNewMessageNotification(Notification notification) {
+- if( nSupport!= null )
+- nSupport.sendNotification(notification);
+- }
+-
+- private NotificationBroadcasterSupport nSupport= null;
+-
+- public void addNotificationListener(NotificationListener listener,
+- NotificationFilter filter,
+- Object handback)
+- throws IllegalArgumentException
+- {
+- if( nSupport==null ) nSupport=new NotificationBroadcasterSupport();
+- nSupport.addNotificationListener(listener, filter, handback);
+- }
+-
+- public void removeNotificationListener(NotificationListener listener)
+- throws ListenerNotFoundException
+- {
+- if( nSupport!=null)
+- nSupport.removeNotificationListener(listener);
+- }
+-
+- MBeanNotificationInfo notifInfo[]=new MBeanNotificationInfo[0];
+-
+- public void setNotificationInfo( MBeanNotificationInfo info[]) {
+- this.notifInfo=info;
+- }
+-
+- public MBeanNotificationInfo[] getNotificationInfo() {
+- return notifInfo;
+- }
+-
+- static class SocketAcceptor implements ThreadPoolRunnable {
+- ChannelSocket wajp;
+-
+- SocketAcceptor(ChannelSocket wajp ) {
+- this.wajp=wajp;
+- }
+-
+- public Object[] getInitData() {
+- return null;
+- }
+-
+- public void runIt(Object thD[]) {
+- wajp.acceptConnections();
+- }
+- }
+-
+- static class SocketConnection implements ThreadPoolRunnable {
+- ChannelSocket wajp;
+- MsgContext ep;
+-
+- SocketConnection(ChannelSocket wajp, MsgContext ep) {
+- this.wajp=wajp;
+- this.ep=ep;
+- }
+-
+-
+- public Object[] getInitData() {
+- return null;
+- }
+-
+- public void runIt(Object perTh[]) {
+- wajp.processConnection(ep);
+- ep = null;
+- }
+- }
+-
+-}
+-
+Index: java/org/apache/jk/common/MsgAjp.java
+===================================================================
+--- java/org/apache/jk/common/MsgAjp.java (revision 590752)
++++ java/org/apache/jk/common/MsgAjp.java (working copy)
+@@ -1,354 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.common;
+-
+-import java.io.IOException;
+-
+-import org.apache.jk.core.Msg;
+-import org.apache.tomcat.util.buf.ByteChunk;
+-import org.apache.tomcat.util.buf.MessageBytes;
+-
+-/**
+- * A single packet for communication between the web server and the
+- * container. Designed to be reused many times with no creation of
+- * garbage. Understands the format of data types for these packets.
+- * Can be used (somewhat confusingly) for both incoming and outgoing
+- * packets.
+- *
+- * See Ajp14/Ajp13Packet.java.
+- *
+- * @author Henri Gomez [hgomez(a)apache.org]
+- * @author Dan Milstein [danmil(a)shore.net]
+- * @author Keith Wannamaker [Keith(a)Wannamaker.org]
+- * @author Kevin Seguin
+- * @author Costin Manolache
+- */
+-public class MsgAjp extends Msg {
+- private static org.apache.juli.logging.Log log=
+- org.apache.juli.logging.LogFactory.getLog( MsgAjp.class );
+-
+- // that's the original buffer size in ajp13 - otherwise we'll get interoperability problems.
+- private byte buf[];
+- // The current read or write position in the buffer
+- private int pos;
+- /**
+- * This actually means different things depending on whether the
+- * packet is read or write. For read, it's the length of the
+- * payload (excluding the header). For write, it's the length of
+- * the packet as a whole (counting the header). Oh, well.
+- */
+- private int len;
+-
+- /**
+- * The maximum packet size
+- */
+- private int bufsize;
+-
+- /**
+- * Constructor that takes a buffer size
+- */
+- public MsgAjp(int bsize) {
+- if(bsize < 8*1024) {
+- bsize = 8*1024;
+- }
+- bufsize = bsize;
+- buf = new byte[bsize];
+-
+- }
+-
+- /**
+- * No arg constructor.
+- * @deprecated Use the buffer size constructor.
+- */
+- public MsgAjp() {
+- this(8*1024);
+- }
+-
+- /**
+- * Prepare this packet for accumulating a message from the container to
+- * the web server. Set the write position to just after the header
+- * (but leave the length unwritten, because it is as yet unknown).
+- */
+- public void reset() {
+- len = 4;
+- pos = 4;
+- }
+-
+- /**
+- * For a packet to be sent to the web server, finish the process of
+- * accumulating data and write the length of the data payload into
+- * the header.
+- */
+- public void end() {
+- len=pos;
+- int dLen=len-4;
+-
+- buf[0] = (byte)0x41;
+- buf[1] = (byte)0x42;
+- buf[2]= (byte)((dLen>>>8 ) & 0xFF );
+- buf[3] = (byte)(dLen & 0xFF);
+- }
+-
+- public byte[] getBuffer() {
+- return buf;
+- }
+-
+- public int getLen() {
+- return len;
+- }
+-
+- // ============ Data Writing Methods ===================
+-
+- /**
+- * Add an int.
+- *
+- * @param val The integer to write.
+- */
+- public void appendInt( int val ) {
+- buf[pos++] = (byte) ((val >>> 8) & 0xFF);
+- buf[pos++] = (byte) (val & 0xFF);
+- }
+-
+- public void appendByte( int val ) {
+- buf[pos++] = (byte)val;
+- }
+-
+- public void appendLongInt( int val ) {
+- buf[pos++] = (byte) ((val >>> 24) & 0xFF);
+- buf[pos++] = (byte) ((val >>> 16) & 0xFF);
+- buf[pos++] = (byte) ((val >>> 8) & 0xFF);
+- buf[pos++] = (byte) (val & 0xFF);
+- }
+-
+- /**
+- * Write a String out at the current write position. Strings are
+- * encoded with the length in two bytes first, then the string, and
+- * then a terminating \0 (which is <B>not</B> included in the
+- * encoded length). The terminator is for the convenience of the C
+- * code, where it saves a round of copying. A null string is
+- * encoded as a string with length 0.
+- */
+- public void appendBytes(MessageBytes mb) throws IOException {
+- if(mb==null || mb.isNull() ) {
+- appendInt( 0);
+- appendByte(0);
+- return;
+- }
+-
+- // XXX Convert !!
+- ByteChunk bc= mb.getByteChunk();
+- appendByteChunk(bc);
+- }
+-
+- public void appendByteChunk(ByteChunk bc) throws IOException {
+- if(bc==null) {
+- log.error("appendByteChunk() null");
+- appendInt( 0);
+- appendByte(0);
+- return;
+- }
+-
+- byte[] bytes = bc.getBytes();
+- int start=bc.getStart();
+- int length = bc.getLength();
+- appendInt( length );
+- cpBytes(bytes, start, length);
+- appendByte(0);
+- }
+-
+- /**
+- * Copy a chunk of bytes into the packet, starting at the current
+- * write position. The chunk of bytes is encoded with the length
+- * in two bytes first, then the data itself, and finally a
+- * terminating \0 (which is <B>not</B> included in the encoded
+- * length).
+- *
+- * @param b The array from which to copy bytes.
+- * @param off The offset into the array at which to start copying
+- * @param numBytes The number of bytes to copy.
+- */
+- public void appendBytes( byte b[], int off, int numBytes ) {
+- appendInt( numBytes );
+- cpBytes( b, off, numBytes );
+- appendByte(0);
+- }
+-
+- private void cpBytes( byte b[], int off, int numBytes ) {
+- if( pos + numBytes >= buf.length ) {
+- log.error("Buffer overflow: buffer.len=" + buf.length + " pos=" +
+- pos + " data=" + numBytes );
+- dump("Overflow/coBytes");
+- log.error( "Overflow ", new Throwable());
+- return;
+- }
+- System.arraycopy( b, off, buf, pos, numBytes);
+- pos += numBytes;
+- // buf[pos + numBytes] = 0; // Terminating \0
+- }
+-
+-
+-
+- // ============ Data Reading Methods ===================
+-
+- /**
+- * Read an integer from packet, and advance the read position past
+- * it. Integers are encoded as two unsigned bytes with the
+- * high-order byte first, and, as far as I can tell, in
+- * little-endian order within each byte.
+- */
+- public int getInt() {
+- int b1 = buf[pos++] & 0xFF; // No swap, Java order
+- int b2 = buf[pos++] & 0xFF;
+-
+- return (b1<<8) + b2;
+- }
+-
+- public int peekInt() {
+- int b1 = buf[pos] & 0xFF; // No swap, Java order
+- int b2 = buf[pos+1] & 0xFF;
+-
+- return (b1<<8) + b2;
+- }
+-
+- public byte getByte() {
+- byte res = buf[pos++];
+- return res;
+- }
+-
+- public byte peekByte() {
+- byte res = buf[pos];
+- return res;
+- }
+-
+- public void getBytes(MessageBytes mb) {
+- int length = getInt();
+- if( (length == 0xFFFF) || (length == -1) ) {
+- mb.recycle();
+- return;
+- }
+- mb.setBytes( buf, pos, length );
+- mb.getCharChunk().recycle();
+- pos += length;
+- pos++; // Skip the terminating \0
+- }
+-
+- /**
+- * Copy a chunk of bytes from the packet into an array and advance
+- * the read position past the chunk. See appendBytes() for details
+- * on the encoding.
+- *
+- * @return The number of bytes copied.
+- */
+- public int getBytes(byte dest[]) {
+- int length = getInt();
+- if( length > buf.length ) {
+- // XXX Should be if(pos + length > buff.legth)?
+- log.error("getBytes() buffer overflow " + length + " " + buf.length );
+- }
+-
+- if( (length == 0xFFFF) || (length == -1) ) {
+- log.info("Null string " + length);
+- return 0;
+- }
+-
+- System.arraycopy( buf, pos, dest, 0, length );
+- pos += length;
+- pos++; // Skip terminating \0 XXX I believe this is wrong but harmless
+- return length;
+- }
+-
+- /**
+- * Read a 32 bits integer from packet, and advance the read position past
+- * it. Integers are encoded as four unsigned bytes with the
+- * high-order byte first, and, as far as I can tell, in
+- * little-endian order within each byte.
+- */
+- public int getLongInt() {
+- int b1 = buf[pos++] & 0xFF; // No swap, Java order
+- b1 <<= 8;
+- b1 |= (buf[pos++] & 0xFF);
+- b1 <<= 8;
+- b1 |= (buf[pos++] & 0xFF);
+- b1 <<=8;
+- b1 |= (buf[pos++] & 0xFF);
+- return b1;
+- }
+-
+- public int getHeaderLength() {
+- return 4;
+- }
+-
+- public int processHeader() {
+- pos = 0;
+- int mark = getInt();
+- len = getInt();
+-
+- if( mark != 0x1234 && mark != 0x4142 ) {
+- // XXX Logging
+- log.error("BAD packet signature " + mark);
+- dump( "In: " );
+- return -1;
+- }
+-
+- if( log.isDebugEnabled() )
+- log.debug( "Received " + len + " " + buf[0] );
+- return len;
+- }
+-
+- public void dump(String msg) {
+- if( log.isDebugEnabled() )
+- log.debug( msg + ": " + buf + " " + pos +"/" + (len + 4));
+- int max=pos;
+- if( len + 4 > pos )
+- max=len+4;
+- if( max >1000 ) max=1000;
+- if( log.isDebugEnabled() )
+- for( int j=0; j < max; j+=16 )
+- log.debug( hexLine( buf, j, len ));
+-
+- }
+-
+- /* -------------------- Utilities -------------------- */
+- // XXX Move to util package
+-
+- public static String hexLine( byte buf[], int start, int len ) {
+- StringBuffer sb=new StringBuffer();
+- for( int i=start; i< start+16 ; i++ ) {
+- if( i < len + 4)
+- sb.append( hex( buf[i] ) + " ");
+- else
+- sb.append( " " );
+- }
+- sb.append(" | ");
+- for( int i=start; i < start+16 && i < len + 4; i++ ) {
+- if( ! Character.isISOControl( (char)buf[i] ))
+- sb.append( new Character((char)buf[i]) );
+- else
+- sb.append( "." );
+- }
+- return sb.toString();
+- }
+-
+- private static String hex( int x ) {
+- // if( x < 0) x=256 + x;
+- String h=Integer.toHexString( x );
+- if( h.length() == 1 ) h = "0" + h;
+- return h.substring( h.length() - 2 );
+- }
+-
+-}
+Index: java/org/apache/jk/common/WorkerDummy.java
+===================================================================
+--- java/org/apache/jk/common/WorkerDummy.java (revision 590752)
++++ java/org/apache/jk/common/WorkerDummy.java (working copy)
+@@ -1,91 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.common;
+-
+-import java.io.IOException;
+-
+-import org.apache.jk.core.JkHandler;
+-import org.apache.jk.core.Msg;
+-import org.apache.jk.core.MsgContext;
+-import org.apache.jk.core.WorkerEnv;
+-import org.apache.tomcat.util.buf.MessageBytes;
+-
+-
+-/** A dummy worker, will just send back a dummy response.
+- * Used for testing and tunning.
+- */
+-public class WorkerDummy extends JkHandler
+-{
+- public WorkerDummy()
+- {
+- String msg="HelloWorld";
+- byte b[]=msg.getBytes();
+- body.setBytes(b, 0, b.length);
+- }
+-
+- /* ==================== Start/stop ==================== */
+-
+- /** Initialize the worker. After this call the worker will be
+- * ready to accept new requests.
+- */
+- public void init() throws IOException {
+- headersMsgNote=wEnv.getNoteId( WorkerEnv.ENDPOINT_NOTE, "headerMsg" );
+- }
+-
+- MessageBytes body=MessageBytes.newInstance();
+- private int headersMsgNote;
+-
+- public int invoke( Msg in, MsgContext ep )
+- throws IOException
+- {
+- MsgAjp msg=(MsgAjp)ep.getNote( headersMsgNote );
+- if( msg==null ) {
+- msg=new MsgAjp();
+- ep.setNote( headersMsgNote, msg );
+- }
+-
+- msg.reset();
+- msg.appendByte(AjpConstants.JK_AJP13_SEND_HEADERS);
+- msg.appendInt(200);
+- msg.appendBytes(null);
+-
+- msg.appendInt(0);
+-
+- ep.setType( JkHandler.HANDLE_SEND_PACKET );
+- ep.getSource().invoke( msg, ep );
+- // msg.dump("out:" );
+-
+- msg.reset();
+- msg.appendByte( AjpConstants.JK_AJP13_SEND_BODY_CHUNK);
+- msg.appendInt( body.getLength() );
+- msg.appendBytes( body );
+-
+-
+- ep.getSource().invoke(msg, ep);
+-
+- msg.reset();
+- msg.appendByte( AjpConstants.JK_AJP13_END_RESPONSE );
+- msg.appendInt( 1 );
+-
+- ep.getSource().invoke(msg, ep );
+- return OK;
+- }
+-
+- private static final int dL=0;
+-}
+-
+Index: java/org/apache/jk/common/HandlerRequest.java
+===================================================================
+--- java/org/apache/jk/common/HandlerRequest.java (revision 590752)
++++ java/org/apache/jk/common/HandlerRequest.java (working copy)
+@@ -1,669 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.common;
+-
+-import java.io.File;
+-import java.io.FileOutputStream;
+-import java.io.IOException;
+-import java.io.CharConversionException;
+-import java.net.InetAddress;
+-import java.util.Properties;
+-
+-import org.apache.coyote.Request;
+-import org.apache.coyote.RequestInfo;
+-import org.apache.coyote.Response;
+-import org.apache.coyote.Constants;
+-import org.apache.jk.core.JkHandler;
+-import org.apache.jk.core.Msg;
+-import org.apache.jk.core.MsgContext;
+-import org.apache.jk.core.WorkerEnv;
+-import org.apache.jk.core.JkChannel;
+-import org.apache.tomcat.util.buf.ByteChunk;
+-import org.apache.tomcat.util.buf.CharChunk;
+-import org.apache.tomcat.util.buf.HexUtils;
+-import org.apache.tomcat.util.buf.MessageBytes;
+-import org.apache.tomcat.util.http.MimeHeaders;
+-import org.apache.tomcat.util.net.SSLSupport;
+-import org.apache.tomcat.util.threads.ThreadWithAttributes;
+-
+-/**
+- * Handle messages related with basic request information.
+- *
+- * This object can handle the following incoming messages:
+- * - "FORWARD_REQUEST" input message ( sent when a request is passed from the
+- * web server )
+- * - "RECEIVE_BODY_CHUNK" input ( sent by container to pass more body, in
+- * response to GET_BODY_CHUNK )
+- *
+- * It can handle the following outgoing messages:
+- * - SEND_HEADERS. Pass the status code and headers.
+- * - SEND_BODY_CHUNK. Send a chunk of body
+- * - GET_BODY_CHUNK. Request a chunk of body data
+- * - END_RESPONSE. Notify the end of a request processing.
+- *
+- * @author Henri Gomez [hgomez(a)apache.org]
+- * @author Dan Milstein [danmil(a)shore.net]
+- * @author Keith Wannamaker [Keith(a)Wannamaker.org]
+- * @author Costin Manolache
+- */
+-public class HandlerRequest extends JkHandler
+-{
+- private static org.apache.juli.logging.Log log=
+- org.apache.juli.logging.LogFactory.getLog( HandlerRequest.class );
+-
+- /*
+- * Note for Host parsing.
+- */
+- public static final int HOSTBUFFER = 10;
+-
+- /**
+- * Thread lock.
+- */
+- private static Object lock = new Object();
+-
+- private HandlerDispatch dispatch;
+- private String ajpidDir="conf";
+-
+-
+- public HandlerRequest() {
+- }
+-
+- public void init() {
+- dispatch=(HandlerDispatch)wEnv.getHandler( "dispatch" );
+- if( dispatch != null ) {
+- // register incoming message handlers
+- dispatch.registerMessageType( AjpConstants.JK_AJP13_FORWARD_REQUEST,
+- "JK_AJP13_FORWARD_REQUEST",
+- this, null); // 2
+-
+- dispatch.registerMessageType( AjpConstants.JK_AJP13_SHUTDOWN,
+- "JK_AJP13_SHUTDOWN",
+- this, null); // 7
+-
+- dispatch.registerMessageType( AjpConstants.JK_AJP13_CPING_REQUEST,
+- "JK_AJP13_CPING_REQUEST",
+- this, null); // 10
+- dispatch.registerMessageType( HANDLE_THREAD_END,
+- "HANDLE_THREAD_END",
+- this, null);
+- // register outgoing messages handler
+- dispatch.registerMessageType( AjpConstants.JK_AJP13_SEND_BODY_CHUNK, // 3
+- "JK_AJP13_SEND_BODY_CHUNK",
+- this,null );
+- }
+-
+- tmpBufNote=wEnv.getNoteId( WorkerEnv.ENDPOINT_NOTE, "tmpBuf" );
+- secretNote=wEnv.getNoteId( WorkerEnv.ENDPOINT_NOTE, "secret" );
+-
+- if( next==null )
+- next=wEnv.getHandler( "container" );
+- if( log.isDebugEnabled() )
+- log.debug( "Container handler " + next + " " + next.getName() +
+- " " + next.getClass().getName());
+-
+- // should happen on start()
+- generateAjp13Id();
+- }
+-
+- public void setSecret( String s ) {
+- requiredSecret=s;
+- }
+-
+- public void setUseSecret( boolean b ) {
+- if(b) {
+- requiredSecret=Double.toString(Math.random());
+- }
+- }
+-
+- public void setDecodedUri( boolean b ) {
+- decoded=b;
+- }
+-
+- public boolean isTomcatAuthentication() {
+- return tomcatAuthentication;
+- }
+-
+- public void setShutdownEnabled(boolean se) {
+- shutdownEnabled = se;
+- }
+-
+- public boolean getShutdownEnabled() {
+- return shutdownEnabled;
+- }
+-
+- public void setTomcatAuthentication(boolean newTomcatAuthentication) {
+- tomcatAuthentication = newTomcatAuthentication;
+- }
+-
+- public void setAjpidDir( String path ) {
+- if( "".equals( path ) ) path=null;
+- ajpidDir=path;
+- }
+-
+- /**
+- * Set the flag to tell if we JMX register requests.
+- */
+- public void setRegisterRequests(boolean srr) {
+- registerRequests = srr;
+- }
+-
+- /**
+- * Get the flag to tell if we JMX register requests.
+- */
+- public boolean getRegisterRequests() {
+- return registerRequests;
+- }
+-
+- /**
+- * Set the flag to delay the initial body read
+- */
+- public void setDelayInitialRead(boolean dir) {
+- delayInitialRead = dir;
+- }
+-
+- /**
+- * Get the flag to tell if we delay the initial body read
+- */
+- public boolean getDelayInitialRead() {
+- return delayInitialRead;
+- }
+-
+- // -------------------- Ajp13.id --------------------
+-
+- private void generateAjp13Id() {
+- int portInt=8009; // tcpCon.getPort();
+- InetAddress address=null; // tcpCon.getAddress();
+-
+- if( requiredSecret == null || !shutdownEnabled )
+- return;
+-
+- File f1=new File( wEnv.getJkHome() );
+- File f2=new File( f1, "conf" );
+-
+- if( ! f2.exists() ) {
+- log.error( "No conf dir for ajp13.id " + f2 );
+- return;
+- }
+-
+- File sf=new File( f2, "ajp13.id");
+-
+- if( log.isDebugEnabled())
+- log.debug( "Using stop file: "+sf);
+-
+- try {
+- Properties props=new Properties();
+-
+- props.put( "port", Integer.toString( portInt ));
+- if( address!=null ) {
+- props.put( "address", address.getHostAddress() );
+- }
+- if( requiredSecret !=null ) {
+- props.put( "secret", requiredSecret );
+- }
+-
+- FileOutputStream stopF=new FileOutputStream( sf );
+- props.store( stopF, "Automatically generated, don't edit" );
+- } catch( IOException ex ) {
+- if(log.isDebugEnabled())
+- log.debug( "Can't create stop file: "+sf,ex );
+- }
+- }
+-
+- // -------------------- Incoming message --------------------
+- private String requiredSecret=null;
+- private int secretNote;
+- private int tmpBufNote;
+-
+- private boolean decoded=true;
+- private boolean tomcatAuthentication=true;
+- private boolean registerRequests=true;
+- private boolean shutdownEnabled=false;
+- private boolean delayInitialRead = true;
+-
+- public int invoke(Msg msg, MsgContext ep )
+- throws IOException {
+- int type=msg.getByte();
+- ThreadWithAttributes twa = null;
+- if (Thread.currentThread() instanceof ThreadWithAttributes) {
+- twa = (ThreadWithAttributes) Thread.currentThread();
+- }
+- Object control=ep.getControl();
+- MessageBytes tmpMB=(MessageBytes)ep.getNote( tmpBufNote );
+- if( tmpMB==null ) {
+- tmpMB= MessageBytes.newInstance();
+- ep.setNote( tmpBufNote, tmpMB);
+- }
+-
+- if( log.isDebugEnabled() )
+- log.debug( "Handling " + type );
+-
+- switch( type ) {
+- case AjpConstants.JK_AJP13_FORWARD_REQUEST:
+- try {
+- if (twa != null) {
+- twa.setCurrentStage(control, "JkDecode");
+- }
+- decodeRequest( msg, ep, tmpMB );
+- if (twa != null) {
+- twa.setCurrentStage(control, "JkService");
+- twa.setParam(control,
+- ((Request)ep.getRequest()).unparsedURI());
+- }
+- } catch( Exception ex ) {
+- log.error( "Error decoding request ", ex );
+- msg.dump( "Incomming message");
+- return ERROR;
+- }
+-
+- if( requiredSecret != null ) {
+- String epSecret=(String)ep.getNote( secretNote );
+- if( epSecret==null || ! requiredSecret.equals( epSecret ) )
+- return ERROR;
+- }
+- /* XXX it should be computed from request, by workerEnv */
+- if(log.isDebugEnabled() )
+- log.debug("Calling next " + next.getName() + " " +
+- next.getClass().getName());
+-
+- int err= next.invoke( msg, ep );
+- if (twa != null) {
+- twa.setCurrentStage(control, "JkDone");
+- }
+-
+- if( log.isDebugEnabled() )
+- log.debug( "Invoke returned " + err );
+- return err;
+- case AjpConstants.JK_AJP13_SHUTDOWN:
+- String epSecret=null;
+- if( msg.getLen() > 3 ) {
+- // we have a secret
+- msg.getBytes( tmpMB );
+- epSecret=tmpMB.toString();
+- }
+-
+- if( requiredSecret != null &&
+- requiredSecret.equals( epSecret ) ) {
+- if( log.isDebugEnabled() )
+- log.debug("Received wrong secret, no shutdown ");
+- return ERROR;
+- }
+-
+- // XXX add isSameAddress check
+- JkChannel ch=ep.getSource();
+- if( !ch.isSameAddress(ep) ) {
+- log.error("Shutdown request not from 'same address' ");
+- return ERROR;
+- }
+-
+- if( !shutdownEnabled ) {
+- log.warn("Ignoring shutdown request: shutdown not enabled");
+- return ERROR;
+- }
+- // forward to the default handler - it'll do the shutdown
+- checkRequest(ep);
+- next.invoke( msg, ep );
+-
+- if(log.isInfoEnabled())
+- log.info("Exiting");
+- System.exit(0);
+-
+- return OK;
+-
+- // We got a PING REQUEST, quickly respond with a PONG
+- case AjpConstants.JK_AJP13_CPING_REQUEST:
+- msg.reset();
+- msg.appendByte(AjpConstants.JK_AJP13_CPONG_REPLY);
+- ep.getSource().send( msg, ep );
+- ep.getSource().flush( msg, ep ); // Server needs to get it
+- return OK;
+-
+- case HANDLE_THREAD_END:
+- return OK;
+-
+- default:
+- if(log.isInfoEnabled())
+- log.info("Unknown message " + type);
+- }
+-
+- return OK;
+- }
+-
+- static int count = 0;
+-
+- private Request checkRequest(MsgContext ep) {
+- Request req=ep.getRequest();
+- if( req==null ) {
+- req=new Request();
+- Response res=new Response();
+- req.setResponse(res);
+- ep.setRequest( req );
+- if( registerRequests ) {
+- synchronized(lock) {
+- ep.getSource().registerRequest(req, ep, count++);
+- }
+- }
+- }
+- return req;
+- }
+-
+- private int decodeRequest( Msg msg, MsgContext ep, MessageBytes tmpMB )
+- throws IOException {
+- // FORWARD_REQUEST handler
+- Request req = checkRequest(ep);
+-
+- RequestInfo rp = req.getRequestProcessor();
+- rp.setStage(Constants.STAGE_PARSE);
+- MessageBytes tmpMB2 = (MessageBytes)req.getNote(WorkerEnv.SSL_CERT_NOTE);
+- if(tmpMB2 != null) {
+- tmpMB2.recycle();
+- }
+- req.setStartTime(System.currentTimeMillis());
+-
+- // Translate the HTTP method code to a String.
+- byte methodCode = msg.getByte();
+- if (methodCode != AjpConstants.SC_M_JK_STORED) {
+- String mName=AjpConstants.methodTransArray[(int)methodCode - 1];
+- req.method().setString(mName);
+- }
+-
+- msg.getBytes(req.protocol());
+- msg.getBytes(req.requestURI());
+-
+- msg.getBytes(req.remoteAddr());
+- msg.getBytes(req.remoteHost());
+- msg.getBytes(req.localName());
+- req.setLocalPort(msg.getInt());
+-
+- boolean isSSL = msg.getByte() != 0;
+- if( isSSL ) {
+- // XXX req.setSecure( true );
+- req.scheme().setString("https");
+- }
+-
+- decodeHeaders( ep, msg, req, tmpMB );
+-
+- decodeAttributes( ep, msg, req, tmpMB );
+-
+- rp.setStage(Constants.STAGE_PREPARE);
+- MessageBytes valueMB = req.getMimeHeaders().getValue("host");
+- parseHost(valueMB, req);
+- // set cookies on request now that we have all headers
+- req.getCookies().setHeaders(req.getMimeHeaders());
+-
+- // Check to see if there should be a body packet coming along
+- // immediately after
+- long cl=req.getContentLengthLong();
+- if(cl > 0) {
+- JkInputStream jkIS = ep.getInputStream();
+- jkIS.setIsReadRequired(true);
+- if(!delayInitialRead) {
+- jkIS.receive();
+- }
+- }
+-
+- if (log.isTraceEnabled()) {
+- log.trace(req.toString());
+- }
+-
+- return OK;
+- }
+-
+- private int decodeAttributes( MsgContext ep, Msg msg, Request req,
+- MessageBytes tmpMB) {
+- boolean moreAttr=true;
+-
+- while( moreAttr ) {
+- byte attributeCode=msg.getByte();
+- if( attributeCode == AjpConstants.SC_A_ARE_DONE )
+- return 200;
+-
+- /* Special case ( XXX in future API make it separate type !)
+- */
+- if( attributeCode == AjpConstants.SC_A_SSL_KEY_SIZE ) {
+- // Bug 1326: it's an Integer.
+- req.setAttribute(SSLSupport.KEY_SIZE_KEY,
+- new Integer( msg.getInt()));
+- //Integer.toString(msg.getInt()));
+- }
+-
+- if( attributeCode == AjpConstants.SC_A_REQ_ATTRIBUTE ) {
+- // 2 strings ???...
+- msg.getBytes( tmpMB );
+- String n=tmpMB.toString();
+- msg.getBytes( tmpMB );
+- String v=tmpMB.toString();
+- req.setAttribute(n, v );
+- if(log.isTraceEnabled())
+- log.trace("jk Attribute set " + n + "=" + v);
+- }
+-
+-
+- // 1 string attributes
+- switch(attributeCode) {
+- case AjpConstants.SC_A_CONTEXT :
+- msg.getBytes( tmpMB );
+- // nothing
+- break;
+-
+- case AjpConstants.SC_A_SERVLET_PATH :
+- msg.getBytes( tmpMB );
+- // nothing
+- break;
+-
+- case AjpConstants.SC_A_REMOTE_USER :
+- if( tomcatAuthentication ) {
+- // ignore server
+- msg.getBytes( tmpMB );
+- } else {
+- msg.getBytes(req.getRemoteUser());
+- }
+- break;
+-
+- case AjpConstants.SC_A_AUTH_TYPE :
+- if( tomcatAuthentication ) {
+- // ignore server
+- msg.getBytes( tmpMB );
+- } else {
+- msg.getBytes(req.getAuthType());
+- }
+- break;
+-
+- case AjpConstants.SC_A_QUERY_STRING :
+- msg.getBytes(req.queryString());
+- break;
+-
+- case AjpConstants.SC_A_JVM_ROUTE :
+- msg.getBytes(req.instanceId());
+- break;
+-
+- case AjpConstants.SC_A_SSL_CERT :
+- req.scheme().setString( "https" );
+- // Transform the string into certificate.
+- MessageBytes tmpMB2 = (MessageBytes)req.getNote(WorkerEnv.SSL_CERT_NOTE);
+- if(tmpMB2 == null) {
+- tmpMB2 = MessageBytes.newInstance();
+- req.setNote(WorkerEnv.SSL_CERT_NOTE, tmpMB2);
+- }
+- // SSL certificate extraction is costy, moved to JkCoyoteHandler
+- msg.getBytes(tmpMB2);
+- break;
+-
+- case AjpConstants.SC_A_SSL_CIPHER :
+- req.scheme().setString( "https" );
+- msg.getBytes(tmpMB);
+- req.setAttribute(SSLSupport.CIPHER_SUITE_KEY,
+- tmpMB.toString());
+- break;
+-
+- case AjpConstants.SC_A_SSL_SESSION :
+- req.scheme().setString( "https" );
+- msg.getBytes(tmpMB);
+- req.setAttribute(SSLSupport.SESSION_ID_KEY,
+- tmpMB.toString());
+- break;
+-
+- case AjpConstants.SC_A_SECRET :
+- msg.getBytes(tmpMB);
+- String secret=tmpMB.toString();
+- if(log.isTraceEnabled())
+- log.trace("Secret: " + secret );
+- // endpoint note
+- ep.setNote( secretNote, secret );
+- break;
+-
+- case AjpConstants.SC_A_STORED_METHOD:
+- msg.getBytes(req.method());
+- break;
+-
+- default:
+- break; // ignore, we don't know about it - backward compat
+- }
+- }
+- return 200;
+- }
+-
+- private void decodeHeaders( MsgContext ep, Msg msg, Request req,
+- MessageBytes tmpMB ) {
+- // Decode headers
+- MimeHeaders headers = req.getMimeHeaders();
+-
+- int hCount = msg.getInt();
+- for(int i = 0 ; i < hCount ; i++) {
+- String hName = null;
+-
+- // Header names are encoded as either an integer code starting
+- // with 0xA0, or as a normal string (in which case the first
+- // two bytes are the length).
+- int isc = msg.peekInt();
+- int hId = isc & 0xFF;
+-
+- MessageBytes vMB=null;
+- isc &= 0xFF00;
+- if(0xA000 == isc) {
+- msg.getInt(); // To advance the read position
+- hName = AjpConstants.headerTransArray[hId - 1];
+- vMB=headers.addValue( hName );
+- } else {
+- // reset hId -- if the header currently being read
+- // happens to be 7 or 8 bytes long, the code below
+- // will think it's the content-type header or the
+- // content-length header - SC_REQ_CONTENT_TYPE=7,
+- // SC_REQ_CONTENT_LENGTH=8 - leading to unexpected
+- // behaviour. see bug 5861 for more information.
+- hId = -1;
+- msg.getBytes( tmpMB );
+- ByteChunk bc=tmpMB.getByteChunk();
+- vMB=headers.addValue( bc.getBuffer(),
+- bc.getStart(), bc.getLength() );
+- }
+-
+- msg.getBytes(vMB);
+-
+- if (hId == AjpConstants.SC_REQ_CONTENT_LENGTH ||
+- (hId == -1 && tmpMB.equalsIgnoreCase("Content-Length"))) {
+- // just read the content-length header, so set it
+- long cl = vMB.getLong();
+- if(cl < Integer.MAX_VALUE)
+- req.setContentLength( (int)cl );
+- } else if (hId == AjpConstants.SC_REQ_CONTENT_TYPE ||
+- (hId == -1 && tmpMB.equalsIgnoreCase("Content-Type"))) {
+- // just read the content-type header, so set it
+- ByteChunk bchunk = vMB.getByteChunk();
+- req.contentType().setBytes(bchunk.getBytes(),
+- bchunk.getOffset(),
+- bchunk.getLength());
+- }
+- }
+- }
+-
+- /**
+- * Parse host.
+- */
+- private void parseHost(MessageBytes valueMB, Request request)
+- throws IOException {
+-
+- if (valueMB == null || valueMB.isNull()) {
+- // HTTP/1.0
+- // Default is what the socket tells us. Overriden if a host is
+- // found/parsed
+- request.setServerPort(request.getLocalPort());
+- request.serverName().duplicate(request.localName());
+- return;
+- }
+-
+- ByteChunk valueBC = valueMB.getByteChunk();
+- byte[] valueB = valueBC.getBytes();
+- int valueL = valueBC.getLength();
+- int valueS = valueBC.getStart();
+- int colonPos = -1;
+- CharChunk hostNameC = (CharChunk)request.getNote(HOSTBUFFER);
+- if(hostNameC == null) {
+- hostNameC = new CharChunk(valueL);
+- request.setNote(HOSTBUFFER, hostNameC);
+- }
+- hostNameC.recycle();
+-
+- boolean ipv6 = (valueB[valueS] == '[');
+- boolean bracketClosed = false;
+- for (int i = 0; i < valueL; i++) {
+- char b = (char) valueB[i + valueS];
+- hostNameC.append(b);
+- if (b == ']') {
+- bracketClosed = true;
+- } else if (b == ':') {
+- if (!ipv6 || bracketClosed) {
+- colonPos = i;
+- break;
+- }
+- }
+- }
+-
+- if (colonPos < 0) {
+- if (request.scheme().equalsIgnoreCase("https")) {
+- // 80 - Default HTTTP port
+- request.setServerPort(443);
+- } else {
+- // 443 - Default HTTPS port
+- request.setServerPort(80);
+- }
+- request.serverName().setChars(hostNameC.getChars(),
+- hostNameC.getStart(),
+- hostNameC.getLength());
+- } else {
+-
+- request.serverName().setChars(hostNameC.getChars(),
+- hostNameC.getStart(), colonPos);
+-
+- int port = 0;
+- int mult = 1;
+- for (int i = valueL - 1; i > colonPos; i--) {
+- int charValue = HexUtils.DEC[(int) valueB[i + valueS]];
+- if (charValue == -1) {
+- // Invalid character
+- throw new CharConversionException("Invalid char in port: " + valueB[i + valueS]);
+- }
+- port = port + (charValue * mult);
+- mult = 10 * mult;
+- }
+- request.setServerPort(port);
+-
+- }
+-
+- }
+-
+-}
+Index: java/org/apache/jk/common/ChannelNioSocket.java
+===================================================================
+--- java/org/apache/jk/common/ChannelNioSocket.java (revision 590752)
++++ java/org/apache/jk/common/ChannelNioSocket.java (working copy)
+@@ -1,1199 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.common;
+-
+-import java.util.Set;
+-import java.util.Iterator;
+-import java.io.IOException;
+-import java.io.InputStream;
+-import java.io.OutputStream;
+-import java.nio.ByteBuffer;
+-import java.nio.channels.Selector;
+-import java.nio.channels.SelectionKey;
+-import java.nio.channels.SocketChannel;
+-import java.nio.channels.ClosedSelectorException;
+-import java.nio.channels.ServerSocketChannel;
+-import java.nio.channels.CancelledKeyException;
+-import java.nio.channels.ClosedChannelException;
+-import java.net.URLEncoder;
+-import java.net.InetAddress;
+-import java.net.InetSocketAddress;
+-import java.net.ServerSocket;
+-import java.net.Socket;
+-import java.net.SocketException;
+-import java.net.SocketTimeoutException;
+-
+-import javax.management.ListenerNotFoundException;
+-import javax.management.MBeanNotificationInfo;
+-import javax.management.Notification;
+-import javax.management.NotificationBroadcaster;
+-import javax.management.NotificationBroadcasterSupport;
+-import javax.management.NotificationFilter;
+-import javax.management.NotificationListener;
+-import javax.management.ObjectName;
+-
+-import org.apache.tomcat.util.modeler.Registry;
+-import org.apache.jk.core.JkHandler;
+-import org.apache.jk.core.Msg;
+-import org.apache.jk.core.MsgContext;
+-import org.apache.jk.core.JkChannel;
+-import org.apache.jk.core.WorkerEnv;
+-import org.apache.coyote.Request;
+-import org.apache.coyote.RequestGroupInfo;
+-import org.apache.coyote.RequestInfo;
+-import org.apache.tomcat.util.threads.ThreadPool;
+-import org.apache.tomcat.util.threads.ThreadPoolRunnable;
+-
+-/**
+- * Accept ( and send ) TCP messages.
+- *
+- * @author Costin Manolache
+- * @author Bill Barker
+- * jmx:mbean name="jk:service=ChannelNioSocket"
+- * description="Accept socket connections"
+- * jmx:notification name="org.apache.coyote.INVOKE
+- * jmx:notification-handler name="org.apache.jk.JK_SEND_PACKET
+- * jmx:notification-handler name="org.apache.jk.JK_RECEIVE_PACKET
+- * jmx:notification-handler name="org.apache.jk.JK_FLUSH
+- *
+- * Jk can use multiple protocols/transports.
+- * Various container adapters should load this object ( as a bean ),
+- * set configurations and use it. Note that the connector will handle
+- * all incoming protocols - it's not specific to ajp1x. The protocol
+- * is abstracted by MsgContext/Message/Channel.
+- *
+- * A lot of the 'original' behavior is hardcoded - this uses Ajp13 wire protocol,
+- * TCP, Ajp14 API etc.
+- * As we add other protocols/transports/APIs this will change, the current goal
+- * is to get the same level of functionality as in the original jk connector.
+- *
+- * XXX Make the 'message type' pluggable
+- */
+-public class ChannelNioSocket extends JkHandler
+- implements NotificationBroadcaster, JkChannel {
+- private static org.apache.juli.logging.Log log =
+- org.apache.juli.logging.LogFactory.getLog( ChannelNioSocket.class );
+-
+- private int startPort=8009;
+- private int maxPort=8019; // 0 for backward compat.
+- private int port=startPort;
+- private InetAddress inet;
+- private int serverTimeout = 0;
+- private boolean tcpNoDelay=true; // nodelay to true by default
+- private int linger=100;
+- private int socketTimeout = 0;
+- private boolean nioIsBroken = false;
+- private Selector selector = null;
+- private int bufferSize = 8*1024;
+- private int packetSize = 8*1024;
+-
+- private long requestCount=0;
+-
+- /* Turning this to true will reduce the latency with about 20%.
+- But it requires changes in tomcat to make sure client-requested
+- flush() is honored ( on my test, I got 367->433 RPS and
+- 52->35ms average time with a simple servlet )
+- */
+-
+- ThreadPool tp=ThreadPool.createThreadPool(true);
+-
+- /* ==================== Tcp socket options ==================== */
+-
+- /**
+- * jmx:managed-constructor description="default constructor"
+- */
+- public ChannelNioSocket() {
+- // This should be integrated with the domain setup
+- }
+-
+- public ThreadPool getThreadPool() {
+- return tp;
+- }
+-
+- public long getRequestCount() {
+- return requestCount;
+- }
+-
+- /** Set the port for the ajp13 channel.
+- * To support seemless load balancing and jni, we treat this
+- * as the 'base' port - we'll try up until we find one that is not
+- * used. We'll also provide the 'difference' to the main coyote
+- * handler - that will be our 'sessionID' and the position in
+- * the scoreboard and the suffix for the unix domain socket.
+- *
+- * jmx:managed-attribute description="Port to listen" access="READ_WRITE"
+- */
+- public void setPort( int port ) {
+- this.startPort=port;
+- this.port=port;
+- this.maxPort=port+10;
+- }
+-
+- public int getPort() {
+- return port;
+- }
+-
+- public void setAddress(InetAddress inet) {
+- this.inet=inet;
+- }
+-
+- public void setBufferSize(int bs) {
+- if(bs > 8*1024) {
+- bufferSize = bs;
+- }
+- }
+-
+- public int getBufferSize() {
+- return bufferSize;
+- }
+-
+- public void setPacketSize(int ps) {
+- if(ps < 8*1024) {
+- ps = 8*1024;
+- }
+- packetSize = ps;
+- }
+-
+- public int getPacketSize() {
+- return packetSize;
+- }
+-
+- /**
+- * jmx:managed-attribute description="Bind on a specified address" access="READ_WRITE"
+- */
+- public void setAddress(String inet) {
+- try {
+- this.inet= InetAddress.getByName( inet );
+- } catch( Exception ex ) {
+- log.error("Error parsing "+inet,ex);
+- }
+- }
+-
+- public String getAddress() {
+- if( inet!=null)
+- return inet.toString();
+- return "/0.0.0.0";
+- }
+-
+- /**
+- * Sets the timeout in ms of the server sockets created by this
+- * server. This method allows the developer to make servers
+- * more or less responsive to having their server sockets
+- * shut down.
+- *
+- * <p>By default this value is 1000ms.
+- */
+- public void setServerTimeout(int timeout) {
+- this.serverTimeout = timeout;
+- }
+- public int getServerTimeout() {
+- return serverTimeout;
+- }
+-
+- public void setTcpNoDelay( boolean b ) {
+- tcpNoDelay=b;
+- }
+-
+- public boolean getTcpNoDelay() {
+- return tcpNoDelay;
+- }
+-
+- public void setSoLinger( int i ) {
+- linger=i;
+- }
+-
+- public int getSoLinger() {
+- return linger;
+- }
+-
+- public void setSoTimeout( int i ) {
+- socketTimeout=i;
+- }
+-
+- public int getSoTimeout() {
+- return socketTimeout;
+- }
+-
+- public void setMaxPort( int i ) {
+- maxPort=i;
+- }
+-
+- public int getMaxPort() {
+- return maxPort;
+- }
+-
+- /** At startup we'll look for the first free port in the range.
+- The difference between this port and the beggining of the range
+- is the 'id'.
+- This is usefull for lb cases ( less config ).
+- */
+- public int getInstanceId() {
+- return port-startPort;
+- }
+-
+- /** If set to false, the thread pool will be created in
+- * non-daemon mode, and will prevent main from exiting
+- */
+- public void setDaemon( boolean b ) {
+- tp.setDaemon( b );
+- }
+-
+- public boolean getDaemon() {
+- return tp.getDaemon();
+- }
+-
+-
+- public void setMaxThreads( int i ) {
+- if( log.isDebugEnabled()) log.debug("Setting maxThreads " + i);
+- tp.setMaxThreads(i);
+- }
+-
+- public void setMinSpareThreads( int i ) {
+- if( log.isDebugEnabled()) log.debug("Setting minSpareThreads " + i);
+- tp.setMinSpareThreads(i);
+- }
+-
+- public void setMaxSpareThreads( int i ) {
+- if( log.isDebugEnabled()) log.debug("Setting maxSpareThreads " + i);
+- tp.setMaxSpareThreads(i);
+- }
+-
+- public int getMaxThreads() {
+- return tp.getMaxThreads();
+- }
+-
+- public int getMinSpareThreads() {
+- return tp.getMinSpareThreads();
+- }
+-
+- public int getMaxSpareThreads() {
+- return tp.getMaxSpareThreads();
+- }
+-
+- public void setBacklog(int i) {
+- }
+-
+- public void setNioIsBroken(boolean nib) {
+- nioIsBroken = nib;
+- }
+-
+- public boolean getNioIsBroken() {
+- return nioIsBroken;
+- }
+-
+- /* ==================== ==================== */
+- ServerSocket sSocket;
+- final int socketNote=1;
+- final int isNote=2;
+- final int osNote=3;
+- final int notifNote=4;
+- boolean paused = false;
+-
+- public void pause() throws Exception {
+- synchronized(this) {
+- paused = true;
+- }
+- }
+-
+- public void resume() {
+- synchronized(this) {
+- paused = false;
+- notify();
+- }
+- }
+-
+-
+- public void accept( MsgContext ep ) throws IOException {
+- if( sSocket==null ) return;
+- synchronized(this) {
+- while(paused) {
+- try{
+- wait();
+- } catch(InterruptedException ie) {
+- //Ignore, since can't happen
+- }
+- }
+- }
+- SocketChannel sc=sSocket.getChannel().accept();
+- Socket s = sc.socket();
+- ep.setNote( socketNote, s );
+- if(log.isDebugEnabled() )
+- log.debug("Accepted socket " + s +" channel " + sc.isBlocking());
+-
+- try {
+- setSocketOptions(s);
+- } catch(SocketException sex) {
+- log.debug("Error initializing Socket Options", sex);
+- }
+-
+- requestCount++;
+-
+- sc.configureBlocking(false);
+- InputStream is=new SocketInputStream(sc);
+- OutputStream os = new SocketOutputStream(sc);
+- ep.setNote( isNote, is );
+- ep.setNote( osNote, os );
+- ep.setControl( tp );
+- }
+-
+- private void setSocketOptions(Socket s) throws SocketException {
+- if( socketTimeout > 0 )
+- s.setSoTimeout( socketTimeout );
+-
+- s.setTcpNoDelay( tcpNoDelay ); // set socket tcpnodelay state
+-
+- if( linger > 0 )
+- s.setSoLinger( true, linger);
+- }
+-
+- public void resetCounters() {
+- requestCount=0;
+- }
+-
+- /** Called after you change some fields at runtime using jmx.
+- Experimental for now.
+- */
+- public void reinit() throws IOException {
+- destroy();
+- init();
+- }
+-
+- /**
+- * jmx:managed-operation
+- */
+- public void init() throws IOException {
+- // Find a port.
+- if (startPort == 0) {
+- port = 0;
+- if(log.isInfoEnabled())
+- log.info("JK: ajp13 disabling channelNioSocket");
+- running = true;
+- return;
+- }
+- if (maxPort < startPort)
+- maxPort = startPort;
+- ServerSocketChannel ssc = ServerSocketChannel.open();
+- ssc.configureBlocking(false);
+- for( int i=startPort; i<=maxPort; i++ ) {
+- try {
+- InetSocketAddress iddr = null;
+- if( inet == null ) {
+- iddr = new InetSocketAddress( i);
+- } else {
+- iddr=new InetSocketAddress( inet, i);
+- }
+- sSocket = ssc.socket();
+- sSocket.bind(iddr);
+- port=i;
+- break;
+- } catch( IOException ex ) {
+- if(log.isInfoEnabled())
+- log.info("Port busy " + i + " " + ex.toString());
+- sSocket = null;
+- }
+- }
+-
+- if( sSocket==null ) {
+- log.error("Can't find free port " + startPort + " " + maxPort );
+- return;
+- }
+- if(log.isInfoEnabled())
+- log.info("JK: ajp13 listening on " + getAddress() + ":" + port );
+-
+- selector = Selector.open();
+- ssc.register(selector, SelectionKey.OP_ACCEPT);
+- // If this is not the base port and we are the 'main' channleSocket and
+- // SHM didn't already set the localId - we'll set the instance id
+- if( "channelNioSocket".equals( name ) &&
+- port != startPort &&
+- (wEnv.getLocalId()==0) ) {
+- wEnv.setLocalId( port - startPort );
+- }
+-
+- // XXX Reverse it -> this is a notification generator !!
+- if( next==null && wEnv!=null ) {
+- if( nextName!=null )
+- setNext( wEnv.getHandler( nextName ) );
+- if( next==null )
+- next=wEnv.getHandler( "dispatch" );
+- if( next==null )
+- next=wEnv.getHandler( "request" );
+- }
+- JMXRequestNote =wEnv.getNoteId( WorkerEnv.ENDPOINT_NOTE, "requestNote");
+- running = true;
+-
+- // Run a thread that will accept connections.
+- // XXX Try to find a thread first - not sure how...
+- if( this.domain != null ) {
+- try {
+- tpOName=new ObjectName(domain + ":type=ThreadPool,name=" +
+- getChannelName());
+-
+- Registry.getRegistry(null, null)
+- .registerComponent(tp, tpOName, null);
+-
+- rgOName = new ObjectName
+- (domain+":type=GlobalRequestProcessor,name=" + getChannelName());
+- Registry.getRegistry(null, null)
+- .registerComponent(global, rgOName, null);
+- } catch (Exception e) {
+- log.error("Can't register threadpool" );
+- }
+- }
+-
+- tp.start();
+- Poller pollAjp = new Poller();
+- tp.runIt(pollAjp);
+- }
+-
+- ObjectName tpOName;
+- ObjectName rgOName;
+- RequestGroupInfo global=new RequestGroupInfo();
+- int JMXRequestNote;
+-
+- public void start() throws IOException{
+- if( sSocket==null )
+- init();
+- resume();
+- }
+-
+- public void stop() throws IOException {
+- destroy();
+- }
+-
+- public void registerRequest(Request req, MsgContext ep, int count) {
+- if(this.domain != null) {
+- try {
+- RequestInfo rp=req.getRequestProcessor();
+- rp.setGlobalProcessor(global);
+- ObjectName roname = new ObjectName
+- (getDomain() + ":type=RequestProcessor,worker="+
+- getChannelName()+",name=JkRequest" +count);
+- ep.setNote(JMXRequestNote, roname);
+-
+- Registry.getRegistry(null, null).registerComponent( rp, roname, null);
+- } catch( Exception ex ) {
+- log.warn("Error registering request");
+- }
+- }
+- }
+-
+- public void open(MsgContext ep) throws IOException {
+- }
+-
+-
+- public void close(MsgContext ep) throws IOException {
+- Socket s=(Socket)ep.getNote( socketNote );
+- SelectionKey key = s.getChannel().keyFor(selector);
+- if(key != null) {
+- key.cancel();
+- }
+- s.close();
+- }
+-
+- public void destroy() throws IOException {
+- running = false;
+- try {
+- /* If we disabled the channel return */
+- if (port == 0)
+- return;
+- tp.shutdown();
+-
+- selector.wakeup().close();
+- sSocket.close(); // XXX?
+-
+- if( tpOName != null ) {
+- Registry.getRegistry(null, null).unregisterComponent(tpOName);
+- }
+- if( rgOName != null ) {
+- Registry.getRegistry(null, null).unregisterComponent(rgOName);
+- }
+- } catch(Exception e) {
+- log.info("Error shutting down the channel " + port + " " +
+- e.toString());
+- if( log.isDebugEnabled() ) log.debug("Trace", e);
+- }
+- }
+-
+- public int send( Msg msg, MsgContext ep)
+- throws IOException {
+- msg.end(); // Write the packet header
+- byte buf[]=msg.getBuffer();
+- int len=msg.getLen();
+-
+- if(log.isTraceEnabled() )
+- log.trace("send() " + len + " " + buf[4] );
+-
+- OutputStream os=(OutputStream)ep.getNote( osNote );
+- os.write( buf, 0, len );
+- return len;
+- }
+-
+- public int flush( Msg msg, MsgContext ep)
+- throws IOException {
+- OutputStream os=(OutputStream)ep.getNote( osNote );
+- os.flush();
+- return 0;
+- }
+-
+- public int receive( Msg msg, MsgContext ep )
+- throws IOException {
+- if (log.isTraceEnabled()) {
+- log.trace("receive() ");
+- }
+-
+- byte buf[]=msg.getBuffer();
+- int hlen=msg.getHeaderLength();
+-
+- // XXX If the length in the packet header doesn't agree with the
+- // actual number of bytes read, it should probably return an error
+- // value. Also, callers of this method never use the length
+- // returned -- should probably return true/false instead.
+-
+- int rd = this.read(ep, buf, 0, hlen );
+-
+- if(rd < 0) {
+- // Most likely normal apache restart.
+- // log.warn("Wrong message " + rd );
+- return rd;
+- }
+-
+- msg.processHeader();
+-
+- /* After processing the header we know the body
+- length
+- */
+- int blen=msg.getLen();
+-
+- // XXX check if enough space - it's assert()-ed !!!
+-
+- int total_read = 0;
+-
+- total_read = this.read(ep, buf, hlen, blen);
+-
+- if ((total_read <= 0) && (blen > 0)) {
+- log.warn("can't read body, waited #" + blen);
+- return -1;
+- }
+-
+- if (total_read != blen) {
+- log.warn( "incomplete read, waited #" + blen +
+- " got only " + total_read);
+- return -2;
+- }
+-
+- return total_read;
+- }
+-
+- /**
+- * Read N bytes from the InputStream, and ensure we got them all
+- * Under heavy load we could experience many fragmented packets
+- * just read Unix Network Programming to recall that a call to
+- * read didn't ensure you got all the data you want
+- *
+- * from read() Linux manual
+- *
+- * On success, the number of bytes read is returned (zero indicates end
+- * of file),and the file position is advanced by this number.
+- * It is not an error if this number is smaller than the number of bytes
+- * requested; this may happen for example because fewer bytes
+- * are actually available right now (maybe because we were close to
+- * end-of-file, or because we are reading from a pipe, or from a
+- * terminal), or because read() was interrupted by a signal.
+- * On error, -1 is returned, and errno is set appropriately. In this
+- * case it is left unspecified whether the file position (if any) changes.
+- *
+- **/
+- public int read( MsgContext ep, byte[] b, int offset, int len)
+- throws IOException
+- {
+- InputStream is=(InputStream)ep.getNote( isNote );
+- int pos = 0;
+- int got;
+-
+- while(pos < len) {
+- try {
+- got = is.read(b, pos + offset, len - pos);
+- } catch(ClosedChannelException sex) {
+- if(pos > 0) {
+- log.info("Error reading data after "+pos+"bytes",sex);
+- } else {
+- log.debug("Error reading data", sex);
+- }
+- got = -1;
+- }
+- if (log.isTraceEnabled()) {
+- log.trace("read() " + b + " " + (b==null ? 0: b.length) + " " +
+- offset + " " + len + " = " + got );
+- }
+-
+- // connection just closed by remote.
+- if (got <= 0) {
+- // This happens periodically, as apache restarts
+- // periodically.
+- // It should be more gracefull ! - another feature for Ajp14
+- // log.warn( "server has closed the current connection (-1)" );
+- return -3;
+- }
+-
+- pos += got;
+- }
+- return pos;
+- }
+-
+- protected boolean running=true;
+-
+- /** Accept incoming connections, dispatch to the thread pool
+- */
+- void acceptConnections() {
+- if( running ) {
+- try{
+- MsgContext ep=createMsgContext();
+- ep.setSource(this);
+- ep.setWorkerEnv( wEnv );
+- this.accept(ep);
+-
+- if( !running ) return;
+-
+- // Since this is a long-running connection, we don't care
+- // about the small GC
+- SocketConnection ajpConn=
+- new SocketConnection( ep);
+- ajpConn.register(ep);
+- }catch(Exception ex) {
+- if (running)
+- log.warn("Exception executing accept" ,ex);
+- }
+- }
+- }
+-
+-
+- // XXX This should become handleNotification
+- public int invoke( Msg msg, MsgContext ep ) throws IOException {
+- int type=ep.getType();
+-
+- switch( type ) {
+- case JkHandler.HANDLE_RECEIVE_PACKET:
+- if( log.isDebugEnabled()) log.debug("RECEIVE_PACKET ?? ");
+- return receive( msg, ep );
+- case JkHandler.HANDLE_SEND_PACKET:
+- return send( msg, ep );
+- case JkHandler.HANDLE_FLUSH:
+- return flush( msg, ep );
+- }
+-
+- if( log.isTraceEnabled() )
+- log.trace("Call next " + type + " " + next);
+-
+- // Send notification
+- if( nSupport!=null ) {
+- Notification notif=(Notification)ep.getNote(notifNote);
+- if( notif==null ) {
+- notif=new Notification("channelNioSocket.message", ep, requestCount );
+- ep.setNote( notifNote, notif);
+- }
+- nSupport.sendNotification(notif);
+- }
+-
+- if( next != null ) {
+- return next.invoke( msg, ep );
+- } else {
+- log.info("No next ");
+- }
+-
+- return OK;
+- }
+-
+- public boolean isSameAddress(MsgContext ep) {
+- Socket s=(Socket)ep.getNote( socketNote );
+- return isSameAddress( s.getLocalAddress(), s.getInetAddress());
+- }
+-
+- public String getChannelName() {
+- String encodedAddr = "";
+- if (inet != null && !"0.0.0.0".equals(inet.getHostAddress())) {
+- encodedAddr = getAddress();
+- if (encodedAddr.startsWith("/"))
+- encodedAddr = encodedAddr.substring(1);
+- encodedAddr = URLEncoder.encode(encodedAddr) + "-";
+- }
+- return ("jk-" + encodedAddr + port);
+- }
+-
+- /**
+- * Return <code>true</code> if the specified client and server addresses
+- * are the same. This method works around a bug in the IBM 1.1.8 JVM on
+- * Linux, where the address bytes are returned reversed in some
+- * circumstances.
+- *
+- * @param server The server's InetAddress
+- * @param client The client's InetAddress
+- */
+- public static boolean isSameAddress(InetAddress server, InetAddress client)
+- {
+- // Compare the byte array versions of the two addresses
+- byte serverAddr[] = server.getAddress();
+- byte clientAddr[] = client.getAddress();
+- if (serverAddr.length != clientAddr.length)
+- return (false);
+- boolean match = true;
+- for (int i = 0; i < serverAddr.length; i++) {
+- if (serverAddr[i] != clientAddr[i]) {
+- match = false;
+- break;
+- }
+- }
+- if (match)
+- return (true);
+-
+- // Compare the reversed form of the two addresses
+- for (int i = 0; i < serverAddr.length; i++) {
+- if (serverAddr[i] != clientAddr[(serverAddr.length-1)-i])
+- return (false);
+- }
+- return (true);
+- }
+-
+- public void sendNewMessageNotification(Notification notification) {
+- if( nSupport!= null )
+- nSupport.sendNotification(notification);
+- }
+-
+- private NotificationBroadcasterSupport nSupport= null;
+-
+- public void addNotificationListener(NotificationListener listener,
+- NotificationFilter filter,
+- Object handback)
+- throws IllegalArgumentException
+- {
+- if( nSupport==null ) nSupport=new NotificationBroadcasterSupport();
+- nSupport.addNotificationListener(listener, filter, handback);
+- }
+-
+- public void removeNotificationListener(NotificationListener listener)
+- throws ListenerNotFoundException
+- {
+- if( nSupport!=null)
+- nSupport.removeNotificationListener(listener);
+- }
+-
+- MBeanNotificationInfo notifInfo[]=new MBeanNotificationInfo[0];
+-
+- public void setNotificationInfo( MBeanNotificationInfo info[]) {
+- this.notifInfo=info;
+- }
+-
+- public MBeanNotificationInfo[] getNotificationInfo() {
+- return notifInfo;
+- }
+-
+- protected class SocketConnection implements ThreadPoolRunnable {
+- MsgContext ep;
+- MsgAjp recv = new MsgAjp(packetSize);
+- boolean inProgress = false;
+-
+- SocketConnection(MsgContext ep) {
+- this.ep=ep;
+- }
+-
+- public Object[] getInitData() {
+- return null;
+- }
+-
+- public void runIt(Object perTh[]) {
+- if(!processConnection(ep)) {
+- unregister(ep);
+- }
+- }
+-
+- public boolean isRunning() {
+- return inProgress;
+- }
+-
+- public void setFinished() {
+- inProgress = false;
+- }
+-
+- /** Process a single ajp connection.
+- */
+- boolean processConnection(MsgContext ep) {
+- try {
+- InputStream sis = (InputStream)ep.getNote(isNote);
+- boolean haveInput = true;
+- while(haveInput) {
+- if( !running || paused ) {
+- return false;
+- }
+- int status= receive( recv, ep );
+- if( status <= 0 ) {
+- if( status==-3)
+- log.debug( "server has been restarted or reset this connection" );
+- else
+- log.warn("Closing ajp connection " + status );
+- return false;
+- }
+- ep.setLong( MsgContext.TIMER_RECEIVED, System.currentTimeMillis());
+-
+- ep.setType( 0 );
+- // Will call next
+- status= invoke( recv, ep );
+- if( status != JkHandler.OK ) {
+- log.warn("processCallbacks status " + status );
+- return false;
+- }
+- synchronized(this) {
+- synchronized(sis) {
+- haveInput = sis.available() > 0;
+- }
+- if(!haveInput) {
+- setFinished();
+- } else {
+- if(log.isDebugEnabled())
+- log.debug("KeepAlive: "+sis.available());
+- }
+- }
+- }
+- } catch( Exception ex ) {
+- String msg = ex.getMessage();
+- if( msg != null && msg.indexOf( "Connection reset" ) >= 0)
+- log.debug( "Server has been restarted or reset this connection");
+- else if (msg != null && msg.indexOf( "Read timed out" ) >=0 )
+- log.debug( "connection timeout reached");
+- else
+- log.error( "Error, processing connection", ex);
+- return false;
+- }
+- return true;
+- }
+-
+- synchronized void process(SelectionKey sk) {
+- if(!sk.isValid()) {
+- SocketInputStream sis = (SocketInputStream)ep.getNote(isNote);
+- sis.closeIt();
+- return;
+- }
+- if(sk.isReadable()) {
+- SocketInputStream sis = (SocketInputStream)ep.getNote(isNote);
+- boolean isok = sis.readAvailable();
+- if(!inProgress) {
+- if(isok) {
+- if(sis.available() > 0 || !nioIsBroken){
+- inProgress = true;
+- tp.runIt(this);
+- }
+- } else {
+- unregister(ep);
+- return;
+- }
+- }
+- }
+- if(sk.isWritable()) {
+- Object os = ep.getNote(osNote);
+- synchronized(os) {
+- os.notify();
+- }
+- }
+- }
+-
+- synchronized void unregister(MsgContext ep) {
+- try{
+- close(ep);
+- } catch(Exception e) {
+- log.error("Error closing connection", e);
+- }
+- try{
+- Request req = (Request)ep.getRequest();
+- if( req != null ) {
+- ObjectName roname = (ObjectName)ep.getNote(JMXRequestNote);
+- if( roname != null ) {
+- Registry.getRegistry(null, null).unregisterComponent(roname);
+- }
+- req.getRequestProcessor().setGlobalProcessor(null);
+- }
+- } catch( Exception ee) {
+- log.error( "Error, releasing connection",ee);
+- }
+- }
+-
+- void register(MsgContext ep) {
+- Socket s = (Socket)ep.getNote(socketNote);
+- try {
+- s.getChannel().register(selector, SelectionKey.OP_READ, this);
+- } catch(IOException iex) {
+- log.error("Unable to register connection",iex);
+- unregister(ep);
+- }
+- }
+-
+- }
+-
+- protected class Poller implements ThreadPoolRunnable {
+-
+- Poller() {
+- }
+-
+- public Object[] getInitData() {
+- return null;
+- }
+-
+- public void runIt(Object perTh[]) {
+- while(running) {
+- try {
+- int ns = selector.select(serverTimeout);
+- if(log.isDebugEnabled())
+- log.debug("Selecting "+ns+" channels");
+- if(ns > 0) {
+- Set sels = selector.selectedKeys();
+- Iterator it = sels.iterator();
+- while(it.hasNext()) {
+- SelectionKey sk = (SelectionKey)it.next();
+- if(sk.isAcceptable()) {
+- acceptConnections();
+- } else {
+- SocketConnection sc = (SocketConnection)sk.attachment();
+- sc.process(sk);
+- }
+- it.remove();
+- }
+- }
+- } catch(ClosedSelectorException cse) {
+- log.debug("Selector is closed");
+- return;
+- } catch(CancelledKeyException cke) {
+- log.debug("Key Cancelled", cke);
+- } catch(IOException iex) {
+- log.warn("IO Error in select",iex);
+- } catch(Exception ex) {
+- log.warn("Error processing select",ex);
+- }
+- }
+- }
+- }
+-
+- protected class SocketInputStream extends InputStream {
+- final int BUFFER_SIZE = 8200;
+- private ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
+- private SocketChannel channel;
+- private boolean blocking = false;
+- private boolean isClosed = false;
+- private volatile boolean dataAvailable = false;
+-
+- SocketInputStream(SocketChannel channel) {
+- this.channel = channel;
+- buffer.limit(0);
+- }
+-
+- public int available() {
+- return buffer.remaining();
+- }
+-
+- public void mark(int readlimit) {
+- buffer.mark();
+- }
+-
+- public boolean markSupported() {
+- return true;
+- }
+-
+- public void reset() {
+- buffer.reset();
+- }
+-
+- public synchronized int read() throws IOException {
+- if(!checkAvailable(1)) {
+- block(1);
+- }
+- return buffer.get();
+- }
+-
+- private boolean checkAvailable(int nbyte) throws IOException {
+- if(isClosed) {
+- throw new ClosedChannelException();
+- }
+- return buffer.remaining() >= nbyte;
+- }
+-
+- private int fill(int nbyte) throws IOException {
+- int rem = nbyte;
+- int read = 0;
+- boolean eof = false;
+- byte [] oldData = null;
+- if(buffer.remaining() > 0) {
+- // should rarely happen, so short-lived GC shouldn't hurt
+- // as much as allocating a long-lived buffer for this
+- if(log.isDebugEnabled())
+- log.debug("Saving old buffer: "+buffer.remaining());
+- oldData = new byte[buffer.remaining()];
+- buffer.get(oldData);
+- }
+- buffer.clear();
+- if(oldData != null) {
+- buffer.put(oldData);
+- }
+- while(rem > 0) {
+- int count = channel.read(buffer);
+- if(count < 0) {
+- eof = true;
+- break;
+- } else if(count == 0) {
+- log.debug("Failed to recieve signaled read: ");
+- break;
+- }
+- read += count;
+- rem -= count;
+- }
+- buffer.flip();
+- return eof ? -1 : read;
+- }
+-
+- synchronized boolean readAvailable() {
+- if(blocking) {
+- dataAvailable = true;
+- notify();
+- } else if(dataAvailable) {
+- log.debug("Race Condition");
+- } else {
+- int nr=0;
+-
+- try {
+- nr = fill(1);
+- } catch(ClosedChannelException cce) {
+- log.debug("Channel is closed",cce);
+- nr = -1;
+- } catch(IOException iex) {
+- log.warn("Exception processing read",iex);
+- nr = -1; // Can't handle this yet
+- }
+- if(nr < 0) {
+- closeIt();
+- return false;
+- } else if(nr == 0) {
+- if(!nioIsBroken) {
+- dataAvailable = (buffer.remaining() <= 0);
+- }
+- }
+- }
+- return true;
+- }
+-
+- synchronized void closeIt() {
+- isClosed = true;
+- if(blocking)
+- notify();
+- }
+-
+- public int read(byte [] data) throws IOException {
+- return read(data, 0, data.length);
+- }
+-
+- public synchronized int read(byte [] data, int offset, int len) throws IOException {
+- int olen = len;
+- while(!checkAvailable(len)) {
+- int avail = buffer.remaining();
+- if(avail > 0) {
+- buffer.get(data, offset, avail);
+- }
+- len -= avail;
+- offset += avail;
+- block(len);
+- }
+- buffer.get(data, offset, len);
+- return olen;
+- }
+-
+- private void block(int len) throws IOException {
+- if(len <= 0) {
+- return;
+- }
+- if(!dataAvailable) {
+- blocking = true;
+- if(log.isDebugEnabled())
+- log.debug("Waiting for "+len+" bytes to be available");
+- try{
+- wait(socketTimeout);
+- }catch(InterruptedException iex) {
+- log.debug("Interrupted",iex);
+- }
+- blocking = false;
+- }
+- if(dataAvailable) {
+- dataAvailable = false;
+- if(fill(len) < 0) {
+- isClosed = true;
+- }
+- } else if(!isClosed) {
+- throw new SocketTimeoutException("Read request timed out");
+- }
+- }
+- }
+-
+- protected class SocketOutputStream extends OutputStream {
+- ByteBuffer buffer = ByteBuffer.allocateDirect(bufferSize);
+- SocketChannel channel;
+-
+- SocketOutputStream(SocketChannel channel) {
+- this.channel = channel;
+- }
+-
+- public void write(int b) throws IOException {
+- if(!checkAvailable(1)) {
+- flush();
+- }
+- buffer.put((byte)b);
+- }
+-
+- public void write(byte [] data) throws IOException {
+- write(data, 0, data.length);
+- }
+-
+- public void write(byte [] data, int offset, int len) throws IOException {
+- if(!checkAvailable(len)) {
+- flush();
+- }
+- buffer.put(data, offset, len);
+- }
+-
+- public void flush() throws IOException {
+- buffer.flip();
+- while(buffer.hasRemaining()) {
+- int count = channel.write(buffer);
+- if(count == 0) {
+- synchronized(this) {
+- SelectionKey key = channel.keyFor(selector);
+- key.interestOps(SelectionKey.OP_WRITE);
+- if(log.isDebugEnabled())
+- log.debug("Blocking for channel write: "+buffer.remaining());
+- try {
+- wait();
+- } catch(InterruptedException iex) {
+- // ignore, since can't happen
+- }
+- key.interestOps(SelectionKey.OP_READ);
+- }
+- }
+- }
+- buffer.clear();
+- }
+-
+- private boolean checkAvailable(int len) {
+- return buffer.remaining() >= len;
+- }
+- }
+-
+-}
+-
+Index: java/org/apache/jk/common/Shm.java
+===================================================================
+--- java/org/apache/jk/common/Shm.java (revision 590752)
++++ java/org/apache/jk/common/Shm.java (working copy)
+@@ -1,331 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.common;
+-
+-import java.io.IOException;
+-import java.util.Vector;
+-
+-import org.apache.jk.apr.AprImpl;
+-import org.apache.jk.core.Msg;
+-import org.apache.jk.core.MsgContext;
+-import org.apache.jk.core.WorkerEnv;
+-import org.apache.tomcat.util.IntrospectionUtils;
+-import org.apache.tomcat.util.buf.C2BConverter;
+-
+-/* The code is a bit confusing at this moment - the class is used as
+- a Bean, or ant Task, or CLI - i.e. you set properties and call execute.
+-
+- That's different from the rest of jk handlers wich are stateless ( but
+- similar with Coyote-http ).
+-*/
+-
+-
+-/** Handle the shared memory objects.
+- *
+- * @author Costin Manolache
+- */
+-public class Shm extends JniHandler {
+- String file="/tmp/shm.file";
+- int size;
+- String host="localhost";
+- int port=8009;
+- String unixSocket;
+-
+- boolean help=false;
+- boolean unregister=false;
+- boolean reset=false;
+- String dumpFile=null;
+-
+- Vector groups=new Vector();
+-
+- // Will be dynamic ( getMethodId() ) after things are stable
+- static final int SHM_WRITE_SLOT=2;
+- static final int SHM_RESET=5;
+- static final int SHM_DUMP=6;
+-
+- public Shm() {
+- }
+-
+- /** Scoreboard location
+- */
+- public void setFile( String f ) {
+- file=f;
+- }
+-
+- /** Copy the scoreboard in a file for debugging
+- * Will also log a lot of information about what's in the scoreboard.
+- */
+- public void setDump( String dumpFile ) {
+- this.dumpFile=dumpFile;
+- }
+-
+- /** Size. Used only if the scoreboard is to be created.
+- */
+- public void setSize( int size ) {
+- this.size=size;
+- }
+-
+- /** Set this to get the scoreboard reset.
+- * The shm segment will be destroyed and a new one created,
+- * with the provided size.
+- *
+- * Requires "file" and "size".
+- */
+- public void setReset(boolean b) {
+- reset=true;
+- }
+-
+- /** Ajp13 host
+- */
+- public void setHost( String host ) {
+- this.host=host;
+- }
+-
+- /** Mark this instance as belonging to a group
+- */
+- public void setGroup( String grp ) {
+- groups.addElement( grp );
+- }
+-
+- /** Ajp13 port
+- */
+- public void setPort( int port ) {
+- this.port=port;
+- }
+-
+- /** Unix socket where tomcat is listening.
+- * Use it only if tomcat is on the same host, of course
+- */
+- public void setUnixSocket( String unixSocket ) {
+- this.unixSocket=unixSocket;
+- }
+-
+- /** Set this option to mark the tomcat instance as
+- 'down', so apache will no longer forward messages to it.
+- Note that requests with a session will still try this
+- host first.
+-
+- This can be used to implement gracefull shutdown.
+-
+- Host and port are still required, since they are used
+- to identify tomcat.
+- */
+- public void setUnregister( boolean unregister ) {
+- this.unregister=true;
+- }
+-
+- public void init() throws IOException {
+- super.initNative( "shm" );
+- if( apr==null ) return;
+- if( file==null ) {
+- log.error("No shm file, disabling shared memory");
+- apr=null;
+- return;
+- }
+-
+- // Set properties and call init.
+- setNativeAttribute( "file", file );
+- if( size > 0 )
+- setNativeAttribute( "size", Integer.toString( size ) );
+-
+- initJkComponent();
+- }
+-
+- public void resetScoreboard() throws IOException {
+- if( apr==null ) return;
+- MsgContext mCtx=createMsgContext();
+- Msg msg=(Msg)mCtx.getMsg(0);
+- msg.reset();
+-
+- msg.appendByte( SHM_RESET );
+-
+- this.invoke( msg, mCtx );
+- }
+-
+- public void dumpScoreboard(String fname) throws IOException {
+- if( apr==null ) return;
+- MsgContext mCtx=createMsgContext();
+- Msg msg=(Msg)mCtx.getMsg(0);
+- C2BConverter c2b=mCtx.getConverter();
+- msg.reset();
+-
+- msg.appendByte( SHM_DUMP );
+-
+- appendString( msg, fname, c2b);
+-
+- this.invoke( msg, mCtx );
+- }
+-
+- /** Register a tomcat instance
+- * XXX make it more flexible
+- */
+- public void registerTomcat(String host, int port, String unixDomain)
+- throws IOException
+- {
+- String instanceId=host+":" + port;
+-
+- String slotName="TOMCAT:" + instanceId;
+- MsgContext mCtx=createMsgContext();
+- Msg msg=(Msg)mCtx.getMsg(0);
+- msg.reset();
+- C2BConverter c2b=mCtx.getConverter();
+-
+- msg.appendByte( SHM_WRITE_SLOT );
+- appendString( msg, slotName, c2b );
+-
+- int channelCnt=1;
+- if( unixDomain != null ) channelCnt++;
+-
+- // number of groups. 0 means the default lb.
+- msg.appendInt( groups.size() );
+- for( int i=0; i<groups.size(); i++ ) {
+- appendString( msg, (String)groups.elementAt( i ), c2b);
+- appendString( msg, instanceId, c2b);
+- }
+-
+- // number of channels for this instance
+- msg.appendInt( channelCnt );
+-
+- // The body:
+- appendString(msg, "channel.socket:" + host + ":" + port, c2b );
+- msg.appendInt( 1 );
+- appendString(msg, "tomcatId", c2b);
+- appendString(msg, instanceId, c2b);
+-
+- if( unixDomain != null ) {
+- appendString(msg, "channel.apr:" + unixDomain, c2b );
+- msg.appendInt(1);
+- appendString(msg, "tomcatId", c2b);
+- appendString(msg, instanceId, c2b);
+- }
+-
+- if (log.isDebugEnabled())
+- log.debug("Register " + instanceId );
+- this.invoke( msg, mCtx );
+- }
+-
+- public void unRegisterTomcat(String host, int port)
+- throws IOException
+- {
+- String slotName="TOMCAT:" + host + ":" + port;
+- MsgContext mCtx=createMsgContext();
+- Msg msg=(Msg)mCtx.getMsg(0);
+- msg.reset();
+- C2BConverter c2b=mCtx.getConverter();
+-
+- msg.appendByte( SHM_WRITE_SLOT );
+- appendString( msg, slotName, c2b );
+-
+- // number of channels for this instance
+- msg.appendInt( 0 );
+- msg.appendInt( 0 );
+-
+- if (log.isDebugEnabled())
+- log.debug("UnRegister " + slotName );
+- this.invoke( msg, mCtx );
+- }
+-
+- public void destroy() throws IOException {
+- destroyJkComponent();
+- }
+-
+-
+- public int invoke(Msg msg, MsgContext ep )
+- throws IOException
+- {
+- if( apr==null ) return 0;
+- log.debug("ChannelShm.invoke: " + ep );
+- super.nativeDispatch( msg, ep, JK_HANDLE_SHM_DISPATCH, 0 );
+- return 0;
+- }
+-
+- private static org.apache.juli.logging.Log log=
+- org.apache.juli.logging.LogFactory.getLog( Shm.class );
+-
+-
+- //-------------------- Main - use the shm functions from ant or CLI ------
+-
+- /** Local initialization - for standalone use
+- */
+- public void initCli() throws IOException {
+- WorkerEnv wEnv=new WorkerEnv();
+- AprImpl apr=new AprImpl();
+- wEnv.addHandler( "apr", apr );
+- wEnv.addHandler( "shm", this );
+- apr.init();
+- if( ! apr.isLoaded() ) {
+- log.error( "No native support. " +
+- "Make sure libapr.so and libjkjni.so are available in LD_LIBRARY_PATH");
+- return;
+- }
+- }
+-
+- public void execute() {
+- try {
+- if( help ) return;
+- initCli();
+- init();
+-
+- if( reset ) {
+- resetScoreboard();
+- } else if( dumpFile!=null ) {
+- dumpScoreboard(dumpFile);
+- } else if( unregister ) {
+- unRegisterTomcat( host, port );
+- } else {
+- registerTomcat( host, port, unixSocket );
+- }
+- } catch (Exception ex ) {
+- log.error( "Error executing Shm", ex);
+- }
+- }
+-
+- public void setHelp( boolean b ) {
+- if (log.isDebugEnabled()) {
+- log.debug("Usage: ");
+- log.debug(" Shm [OPTIONS]");
+- log.debug("");
+- log.debug(" -file SHM_FILE");
+- log.debug(" -group GROUP ( can be specified multiple times )");
+- log.debug(" -host HOST");
+- log.debug(" -port PORT");
+- log.debug(" -unixSocket UNIX_FILE");
+- // log.debug(" -priority XXX");
+- // log.debug(" -lbFactor XXX");
+- }
+- help=true;
+- return;
+- }
+-
+- public static void main( String args[] ) {
+- try {
+- Shm shm=new Shm();
+-
+- if( args.length == 0 ||
+- ( "-?".equals(args[0]) ) ) {
+- shm.setHelp( true );
+- return;
+- }
+-
+- IntrospectionUtils.processArgs( shm, args);
+- shm.execute();
+- } catch( Exception ex ) {
+- ex.printStackTrace();
+- }
+- }
+-}
+Index: java/org/apache/jk/common/Shm14.java
+===================================================================
+--- java/org/apache/jk/common/Shm14.java (revision 590752)
++++ java/org/apache/jk/common/Shm14.java (working copy)
+@@ -1,97 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.common;
+-
+-import java.io.IOException;
+-import java.io.RandomAccessFile;
+-import java.nio.MappedByteBuffer;
+-import java.nio.channels.FileChannel;
+-
+-import org.apache.jk.core.Msg;
+-import org.apache.jk.core.MsgContext;
+-import org.apache.tomcat.util.IntrospectionUtils;
+-
+-/** Shm implementation using JDK1.4 nio.
+- *
+- *
+- * @author Costin Manolache
+- */
+-public class Shm14 extends Shm {
+-
+-
+- // Not ready yet.
+-
+- private static org.apache.juli.logging.Log log=
+- org.apache.juli.logging.LogFactory.getLog( Shm14.class );
+-
+- MappedByteBuffer bb;
+-
+- public void init() {
+- try {
+- RandomAccessFile f=new RandomAccessFile( file, "rw" );
+- FileChannel fc=f.getChannel();
+-
+- bb=fc.map( FileChannel.MapMode.READ_WRITE, 0, f.length());
+- } catch( IOException ex ) {
+- ex.printStackTrace();
+- }
+- }
+-
+- public void dumpScoreboard(String file) {
+- // We can only sync with our backing store.
+- bb.force();
+- // XXX we should copy the content to the file
+- }
+-
+- public void resetScoreboard() throws IOException {
+- // XXX Need to write the head
+- }
+-
+-
+- public int invoke(Msg msg, MsgContext ep )
+- throws IOException
+- {
+- if (log.isDebugEnabled())
+- log.debug("ChannelShm14.invoke: " + ep );
+-
+- //
+-
+- return 0;
+- }
+-
+- public void initCli() {
+- }
+-
+- public static void main( String args[] ) {
+- try {
+- Shm14 shm=new Shm14();
+-
+- if( args.length == 0 ||
+- ( "-?".equals(args[0]) ) ) {
+- shm.setHelp( true );
+- return;
+- }
+-
+- IntrospectionUtils.processArgs( shm, args);
+- shm.execute();
+- } catch( Exception ex ) {
+- ex.printStackTrace();
+- }
+- }
+-
+-}
+Index: java/org/apache/jk/common/HandlerDispatch.java
+===================================================================
+--- java/org/apache/jk/common/HandlerDispatch.java (revision 590752)
++++ java/org/apache/jk/common/HandlerDispatch.java (working copy)
+@@ -1,101 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.common;
+-
+-import java.io.IOException;
+-
+-import org.apache.jk.core.JkHandler;
+-import org.apache.jk.core.Msg;
+-import org.apache.jk.core.MsgContext;
+-
+-
+-
+-
+-/**
+- * 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 );
+- }
+-
+- }
+Index: java/org/apache/jk/core/JkHandler.java
+===================================================================
+--- java/org/apache/jk/core/JkHandler.java (revision 590752)
++++ java/org/apache/jk/core/JkHandler.java (working copy)
+@@ -1,201 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.core;
+-
+-import java.io.IOException;
+-import java.util.Properties;
+-
+-import javax.management.MBeanRegistration;
+-import javax.management.MBeanServer;
+-import javax.management.Notification;
+-import javax.management.NotificationListener;
+-import javax.management.ObjectName;
+-
+-import org.apache.tomcat.util.modeler.Registry;
+-
+-/**
+- *
+- * @author Costin Manolache
+- */
+-public class JkHandler implements MBeanRegistration, NotificationListener {
+- public static final int OK=0;
+- public static final int LAST=1;
+- public static final int ERROR=2;
+-
+- protected Properties properties=new Properties();
+- protected WorkerEnv wEnv;
+- protected JkHandler next;
+- protected String nextName=null;
+- protected String name;
+- protected int id;
+-
+- // XXX Will be replaced with notes and (configurable) ids
+- // Each represents a 'chain' - similar with ActionCode in Coyote ( the concepts
+- // will be merged ).
+- public static final int HANDLE_RECEIVE_PACKET = 10;
+- public static final int HANDLE_SEND_PACKET = 11;
+- public static final int HANDLE_FLUSH = 12;
+- public static final int HANDLE_THREAD_END = 13;
+-
+- public void setWorkerEnv( WorkerEnv we ) {
+- this.wEnv=we;
+- }
+-
+- /** Set the name of the handler. Will allways be called by
+- * worker env after creating the worker.
+- */
+- public void setName(String s ) {
+- name=s;
+- }
+-
+- public String getName() {
+- return name;
+- }
+-
+- /** Set the id of the worker. We use an id for faster dispatch.
+- * Since we expect a decent number of handler in system, the
+- * id is unique - that means we may have to allocate bigger
+- * dispatch tables. ( easy to fix if needed )
+- */
+- public void setId( int id ) {
+- this.id=id;
+- }
+-
+- public int getId() {
+- return id;
+- }
+-
+- /** Catalina-style "recursive" invocation.
+- * A chain is used for Apache/3.3 style iterative invocation.
+- */
+- public void setNext( JkHandler h ) {
+- next=h;
+- }
+-
+- public void setNext( String s ) {
+- nextName=s;
+- }
+-
+- public String getNext() {
+- if( nextName==null ) {
+- if( next!=null)
+- nextName=next.getName();
+- }
+- return nextName;
+- }
+-
+- /** Should register the request types it can handle,
+- * same style as apache2.
+- */
+- public void init() throws IOException {
+- }
+-
+- /** Clean up and stop the handler
+- */
+- public void destroy() throws IOException {
+- }
+-
+- public MsgContext createMsgContext() {
+- return new MsgContext(8*1024);
+- }
+-
+- public MsgContext createMsgContext(int bsize) {
+- return new MsgContext(bsize);
+- }
+-
+- public int invoke(Msg msg, MsgContext mc ) throws IOException {
+- return OK;
+- }
+-
+- public void setProperty( String name, String value ) {
+- properties.put( name, value );
+- }
+-
+- public String getProperty( String name ) {
+- return properties.getProperty(name) ;
+- }
+-
+- /** Experimental, will be replaced. This allows handlers to be
+- * notified when other handlers are added.
+- */
+- public void addHandlerCallback( JkHandler w ) {
+-
+- }
+-
+- public void handleNotification(Notification notification, Object handback)
+- {
+-// BaseNotification bNot=(BaseNotification)notification;
+-// int code=bNot.getCode();
+-//
+-// MsgContext ctx=(MsgContext)bNot.getSource();
+-
+-
+- }
+-
+- 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 oname) throws Exception {
+- this.oname=oname;
+- mserver=server;
+- domain=oname.getDomain();
+- if( name==null ) {
+- name=oname.getKeyProperty("name");
+- }
+-
+- // we need to create a workerEnv or set one.
+- ObjectName wEnvName=new ObjectName(domain + ":type=JkWorkerEnv");
+- if ( wEnv == null ) {
+- wEnv=new WorkerEnv();
+- }
+- if( ! mserver.isRegistered(wEnvName )) {
+- Registry.getRegistry(null, null).registerComponent(wEnv, wEnvName, null);
+- }
+- mserver.invoke( wEnvName, "addHandler",
+- new Object[] {name, this},
+- new String[] {"java.lang.String",
+- "org.apache.jk.core.JkHandler"});
+- return oname;
+- }
+-
+- public void postRegister(Boolean registrationDone) {
+- }
+-
+- public void preDeregister() throws Exception {
+- }
+-
+- public void postDeregister() {
+- }
+-
+- public void pause() throws Exception {
+- }
+-
+- public void resume() throws Exception {
+- }
+-
+-}
+Index: java/org/apache/jk/core/WorkerEnv.java
+===================================================================
+--- java/org/apache/jk/core/WorkerEnv.java (revision 590752)
++++ java/org/apache/jk/core/WorkerEnv.java (working copy)
+@@ -1,145 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.core;
+-
+-import java.util.Hashtable;
+-import javax.management.ObjectName;
+-
+-/**
+- * The controller object. It manages all other jk objects, acting as the root of
+- * the jk object model.
+- *
+- * @author Gal Shachor
+- * @author Henri Gomez [hgomez(a)apache.org]
+- * @author Dan Milstein [danmil(a)shore.net]
+- * @author Keith Wannamaker [Keith(a)Wannamaker.org]
+- * @author Kevin Seguin
+- * @author Costin Manolache
+- */
+-public class WorkerEnv {
+-
+- Hashtable properties;
+-
+- public static final int ENDPOINT_NOTE=0;
+- public static final int REQUEST_NOTE=1;
+- public static final int SSL_CERT_NOTE=16;
+- int noteId[]=new int[4];
+- String noteName[][]=new String[4][];
+- private Object notes[]=new Object[32];
+-
+- Hashtable handlersMap=new Hashtable();
+- JkHandler handlersTable[]=new JkHandler[20];
+- int handlerCount=0;
+-
+- // base dir for the jk webapp
+- String home;
+- int localId=0;
+-
+- public WorkerEnv() {
+- for( int i=0; i<noteId.length; i++ ) {
+- noteId[i]=7;
+- noteName[i]=new String[20];
+- }
+- }
+-
+- public void setLocalId(int id) {
+- localId=id;
+- }
+-
+- public int getLocalId() {
+- return localId;
+- }
+-
+- public void setJkHome( String s ) {
+- home=s;
+- }
+-
+- public String getJkHome() {
+- return home;
+- }
+-
+- public final Object getNote(int i ) {
+- return notes[i];
+- }
+-
+- public final void setNote(int i, Object o ) {
+- notes[i]=o;
+- }
+-
+- public int getNoteId( int type, String name ) {
+- for( int i=0; i<noteId[type]; i++ ) {
+- if( name.equals( noteName[type][i] ))
+- return i;
+- }
+- int id=noteId[type]++;
+- noteName[type][id]=name;
+- return id;
+- }
+-
+- public void addHandler( String name, JkHandler w ) {
+- JkHandler oldH = getHandler(name);
+- if(oldH == w) {
+- // Already added
+- return;
+- }
+- w.setWorkerEnv( this );
+- w.setName( name );
+- handlersMap.put( name, w );
+- if( handlerCount > handlersTable.length ) {
+- JkHandler newT[]=new JkHandler[ 2 * handlersTable.length ];
+- System.arraycopy( handlersTable, 0, newT, 0, handlersTable.length );
+- handlersTable=newT;
+- }
+- if(oldH == null) {
+- handlersTable[handlerCount]=w;
+- w.setId( handlerCount );
+- handlerCount++;
+- } else {
+- handlersTable[oldH.getId()]=w;
+- w.setId(oldH.getId());
+- }
+-
+- // Notify all other handlers of the new one
+- // XXX Could be a Coyote action ?
+- for( int i=0; i< handlerCount ; i++ ) {
+- handlersTable[i].addHandlerCallback( w );
+- }
+- }
+-
+- public final JkHandler getHandler( String name ) {
+- return (JkHandler)handlersMap.get(name);
+- }
+-
+- public final JkHandler getHandler( int id ) {
+- return handlersTable[id];
+- }
+-
+- public final int getHandlerCount() {
+- return handlerCount;
+- }
+-
+- public ObjectName[] getHandlersObjectName() {
+-
+- ObjectName onames[]=new ObjectName[ handlerCount ];
+- for( int i=0; i<handlerCount; i++ ) {
+- onames[i]=handlersTable[i].getObjectName();
+- }
+- return onames;
+- }
+-
+-}
+Index: java/org/apache/jk/core/Msg.java
+===================================================================
+--- java/org/apache/jk/core/Msg.java (revision 590752)
++++ java/org/apache/jk/core/Msg.java (working copy)
+@@ -1,154 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.core;
+-
+-import java.io.IOException;
+-
+-import org.apache.tomcat.util.buf.ByteChunk;
+-import org.apache.tomcat.util.buf.MessageBytes;
+-
+-
+-/**
+- * A single packet for communication between the web server and the
+- * container.
+- *
+- * In a more generic sense, it's the event that drives the processing chain.
+- * XXX Use Event, make Msg a particular case.
+- *
+- * @author Henri Gomez [hgomez(a)apache.org]
+- * @author Dan Milstein [danmil(a)shore.net]
+- * @author Keith Wannamaker [Keith(a)Wannamaker.org]
+- * @author Kevin Seguin
+- * @author Costin Manolache
+- */
+-public abstract class Msg {
+-
+-
+-
+- /**
+- * Prepare this packet for accumulating a message from the container to
+- * the web server. Set the write position to just after the header
+- * (but leave the length unwritten, because it is as yet unknown).
+- */
+- public abstract void reset();
+-
+- /**
+- * For a packet to be sent to the web server, finish the process of
+- * accumulating data and write the length of the data payload into
+- * the header.
+- */
+- public abstract void end();
+-
+- public abstract void appendInt( int val );
+-
+- public abstract void appendByte( int val );
+-
+- public abstract void appendLongInt( int val );
+-
+- /**
+- */
+- public abstract void appendBytes(MessageBytes mb) throws IOException;
+-
+- public abstract void appendByteChunk(ByteChunk bc) throws IOException;
+-
+- /**
+- * Copy a chunk of bytes into the packet, starting at the current
+- * write position. The chunk of bytes is encoded with the length
+- * in two bytes first, then the data itself, and finally a
+- * terminating \0 (which is <B>not</B> included in the encoded
+- * length).
+- *
+- * @param b The array from which to copy bytes.
+- * @param off The offset into the array at which to start copying
+- * @param numBytes The number of bytes to copy.
+- */
+- public abstract void appendBytes( byte b[], int off, int numBytes );
+-
+- /**
+- * Read an integer from packet, and advance the read position past
+- * it. Integers are encoded as two unsigned bytes with the
+- * high-order byte first, and, as far as I can tell, in
+- * little-endian order within each byte.
+- */
+- public abstract int getInt();
+-
+- public abstract int peekInt();
+-
+- public abstract byte getByte();
+-
+- public abstract byte peekByte();
+-
+- public abstract void getBytes(MessageBytes mb);
+-
+- /**
+- * Copy a chunk of bytes from the packet into an array and advance
+- * the read position past the chunk. See appendBytes() for details
+- * on the encoding.
+- *
+- * @return The number of bytes copied.
+- */
+- public abstract int getBytes(byte dest[]);
+-
+- /**
+- * Read a 32 bits integer from packet, and advance the read position past
+- * it. Integers are encoded as four unsigned bytes with the
+- * high-order byte first, and, as far as I can tell, in
+- * little-endian order within each byte.
+- */
+- public abstract int getLongInt();
+-
+- public abstract int getHeaderLength();
+-
+- public abstract int processHeader();
+-
+- public abstract byte[] getBuffer();
+-
+- public abstract int getLen();
+-
+- public abstract void dump(String msg);
+-
+- /* -------------------- Utilities -------------------- */
+- // XXX Move to util package
+-
+- public static String hexLine( byte buf[], int start, int len ) {
+- StringBuffer sb=new StringBuffer();
+- for( int i=start; i< start+16 ; i++ ) {
+- if( i < len + 4)
+- sb.append( hex( buf[i] ) + " ");
+- else
+- sb.append( " " );
+- }
+- sb.append(" | ");
+- for( int i=start; i < start+16 && i < len + 4; i++ ) {
+- if( ! Character.isISOControl( (char)buf[i] ))
+- sb.append( new Character((char)buf[i]) );
+- else
+- sb.append( "." );
+- }
+- return sb.toString();
+- }
+-
+- private static String hex( int x ) {
+- // if( x < 0) x=256 + x;
+- String h=Integer.toHexString( x );
+- if( h.length() == 1 ) h = "0" + h;
+- return h.substring( h.length() - 2 );
+- }
+-
+-
+-}
+Index: java/org/apache/jk/core/MsgContext.java
+===================================================================
+--- java/org/apache/jk/core/MsgContext.java (revision 590752)
++++ java/org/apache/jk/core/MsgContext.java (working copy)
+@@ -1,393 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.core;
+-
+-import java.io.IOException;
+-import java.io.ByteArrayInputStream;
+-import java.net.InetAddress;
+-import java.security.cert.CertificateFactory;
+-import java.security.cert.X509Certificate;
+-
+-import org.apache.coyote.ActionCode;
+-import org.apache.coyote.ActionHook;
+-import org.apache.coyote.Request;
+-import org.apache.coyote.Response;
+-
+-import org.apache.tomcat.util.buf.C2BConverter;
+-import org.apache.tomcat.util.buf.MessageBytes;
+-import org.apache.tomcat.util.buf.ByteChunk;
+-import org.apache.tomcat.util.net.SSLSupport;
+-import org.apache.jk.common.JkInputStream;
+-
+-
+-/**
+- *
+- * @author Henri Gomez [hgomez(a)apache.org]
+- * @author Dan Milstein [danmil(a)shore.net]
+- * @author Keith Wannamaker [Keith(a)Wannamaker.org]
+- * @author Kevin Seguin
+- * @author Costin Manolache
+- */
+-public class MsgContext implements ActionHook {
+- private static org.apache.juli.logging.Log log =
+- org.apache.juli.logging.LogFactory.getLog(MsgContext.class);
+- private static org.apache.juli.logging.Log logTime=
+- org.apache.juli.logging.LogFactory.getLog( "org.apache.jk.REQ_TIME" );
+-
+- private int type;
+- private Object notes[]=new Object[32];
+- private JkHandler next;
+- private JkChannel source;
+- private JkInputStream jkIS;
+- private C2BConverter c2b;
+- private Request req;
+- private WorkerEnv wEnv;
+- private Msg msgs[]=new Msg[10];
+- private int status=0;
+- // Control object
+- private Object control;
+-
+- // Application managed, like notes
+- private long timers[]=new long[20];
+-
+- // The context can be used by JNI components as well
+- private long jkEndpointP;
+- private long xEnvP;
+-
+- // Temp: use notes and dynamic strings
+- public static final int TIMER_RECEIVED=0;
+- public static final int TIMER_PRE_REQUEST=1;
+- public static final int TIMER_POST_REQUEST=2;
+-
+- // Status codes
+- public static final int JK_STATUS_NEW=0;
+- public static final int JK_STATUS_HEAD=1;
+- public static final int JK_STATUS_CLOSED=2;
+- public static final int JK_STATUS_ERROR=3;
+-
+- public MsgContext(int bsize) {
+- try {
+- c2b = new C2BConverter("iso-8859-1");
+- } catch(IOException iex) {
+- log.warn("Can't happen", iex);
+- }
+- jkIS = new JkInputStream(this, bsize);
+- }
+- /**
+- * @deprecated
+- */
+- public MsgContext() {
+- this(8*1024);
+- }
+-
+- public final Object getNote( int id ) {
+- return notes[id];
+- }
+-
+- public final void setNote( int id, Object o ) {
+- notes[id]=o;
+- }
+-
+- /** The id of the chain */
+- public final int getType() {
+- return type;
+- }
+-
+- public final void setType(int i) {
+- type=i;
+- }
+-
+- public final void setLong( int i, long l) {
+- timers[i]=l;
+- }
+-
+- public final long getLong( int i) {
+- return timers[i];
+- }
+-
+- // Common attributes ( XXX should be notes for flexibility ? )
+-
+- public final WorkerEnv getWorkerEnv() {
+- return wEnv;
+- }
+-
+- public final void setWorkerEnv( WorkerEnv we ) {
+- this.wEnv=we;
+- }
+-
+- public final JkChannel getSource() {
+- return source;
+- }
+-
+- public final void setSource(JkChannel ch) {
+- this.source=ch;
+- }
+-
+- public final int getStatus() {
+- return status;
+- }
+-
+- public final void setStatus( int s ) {
+- status=s;
+- }
+-
+- public final JkHandler getNext() {
+- return next;
+- }
+-
+- public final void setNext(JkHandler ch) {
+- this.next=ch;
+- }
+-
+- /** The high level request object associated with this context
+- */
+- public final void setRequest( Request req ) {
+- this.req=req;
+- req.setInputBuffer(jkIS);
+- Response res = req.getResponse();
+- res.setOutputBuffer(jkIS);
+- res.setHook(this);
+- }
+-
+- public final Request getRequest() {
+- return req;
+- }
+-
+- /** The context may store a number of messages ( buffers + marshalling )
+- */
+- public final Msg getMsg(int i) {
+- return msgs[i];
+- }
+-
+- public final void setMsg(int i, Msg msg) {
+- this.msgs[i]=msg;
+- }
+-
+- public final C2BConverter getConverter() {
+- return c2b;
+- }
+-
+- public final void setConverter(C2BConverter c2b) {
+- this.c2b = c2b;
+- }
+-
+- public final boolean isLogTimeEnabled() {
+- return logTime.isDebugEnabled();
+- }
+-
+- public JkInputStream getInputStream() {
+- return jkIS;
+- }
+-
+- /** Each context contains a number of byte[] buffers used for communication.
+- * The C side will contain a char * equivalent - both buffers are long-lived
+- * and recycled.
+- *
+- * This will be called at init time. A long-lived global reference to the byte[]
+- * will be stored in the C context.
+- */
+- public byte[] getBuffer( int id ) {
+- // We use a single buffer right now.
+- if( msgs[id]==null ) {
+- return null;
+- }
+- return msgs[id].getBuffer();
+- }
+-
+- /** Invoke a java hook. The xEnv is the representation of the current execution
+- * environment ( the jni_env_t * )
+- */
+- public int execute() throws IOException {
+- int status=next.invoke(msgs[0], this);
+- return status;
+- }
+-
+- // -------------------- Jni support --------------------
+-
+- /** Store native execution context data when this handler is called
+- * from JNI. This will change on each call, represent temproary
+- * call data.
+- */
+- public void setJniEnv( long xEnvP ) {
+- this.xEnvP=xEnvP;
+- }
+-
+- public long getJniEnv() {
+- return xEnvP;
+- }
+-
+- /** The long-lived JNI context associated with this java context.
+- * The 2 share pointers to buffers and cache data to avoid expensive
+- * jni calls.
+- */
+- public void setJniContext( long cContext ) {
+- this.jkEndpointP=cContext;
+- }
+-
+- public long getJniContext() {
+- return jkEndpointP;
+- }
+-
+- public Object getControl() {
+- return control;
+- }
+-
+- public void setControl(Object control) {
+- this.control = control;
+- }
+-
+- // -------------------- Coyote Action implementation --------------------
+-
+- public void action(ActionCode actionCode, Object param) {
+- if( actionCode==ActionCode.ACTION_COMMIT ) {
+- if( log.isDebugEnabled() ) log.debug("COMMIT " );
+- Response res=(Response)param;
+-
+- if( res.isCommitted() ) {
+- if( log.isDebugEnabled() )
+- log.debug("Response already committed " );
+- } else {
+- try {
+- jkIS.appendHead( res );
+- } catch(IOException iex) {
+- log.warn("Unable to send headers",iex);
+- setStatus(JK_STATUS_ERROR);
+- }
+- }
+- } else if( actionCode==ActionCode.ACTION_RESET ) {
+- if( log.isDebugEnabled() )
+- log.debug("RESET " );
+-
+- } else if( actionCode==ActionCode.ACTION_CLIENT_FLUSH ) {
+- if( log.isDebugEnabled() ) log.debug("CLIENT_FLUSH " );
+- Response res = (Response)param;
+- if(!res.isCommitted()) {
+- action(ActionCode.ACTION_COMMIT, res);
+- }
+- try {
+- source.flush( null, this );
+- } catch(IOException iex) {
+- // This is logged elsewhere, so debug only here
+- log.debug("Error during flush",iex);
+- res.setErrorException(iex);
+- setStatus(JK_STATUS_ERROR);
+- }
+-
+- } else if( actionCode==ActionCode.ACTION_CLOSE ) {
+- if( log.isDebugEnabled() ) log.debug("CLOSE " );
+-
+- Response res=(Response)param;
+- if( getStatus()== JK_STATUS_CLOSED || getStatus() == JK_STATUS_ERROR) {
+- // Double close - it may happen with forward
+- if( log.isDebugEnabled() ) log.debug("Double CLOSE - forward ? " + res.getRequest().requestURI() );
+- return;
+- }
+-
+- if( !res.isCommitted() )
+- this.action( ActionCode.ACTION_COMMIT, param );
+- try {
+- jkIS.endMessage();
+- } catch(IOException iex) {
+- log.warn("Error sending end packet",iex);
+- setStatus(JK_STATUS_ERROR);
+- }
+- if(getStatus() != JK_STATUS_ERROR) {
+- setStatus(JK_STATUS_CLOSED );
+- }
+-
+- if( logTime.isDebugEnabled() )
+- logTime(res.getRequest(), res);
+- } else if( actionCode==ActionCode.ACTION_REQ_SSL_ATTRIBUTE ) {
+- Request req=(Request)param;
+-
+- // Extract SSL certificate information (if requested)
+- MessageBytes certString = (MessageBytes)req.getNote(WorkerEnv.SSL_CERT_NOTE);
+- if( certString != null && !certString.isNull() ) {
+- ByteChunk certData = certString.getByteChunk();
+- ByteArrayInputStream bais =
+- new ByteArrayInputStream(certData.getBytes(),
+- certData.getStart(),
+- certData.getLength());
+-
+- // Fill the first element.
+- X509Certificate jsseCerts[] = null;
+- try {
+- CertificateFactory cf =
+- CertificateFactory.getInstance("X.509");
+- X509Certificate cert = (X509Certificate)
+- cf.generateCertificate(bais);
+- jsseCerts = new X509Certificate[1];
+- jsseCerts[0] = cert;
+- } catch(java.security.cert.CertificateException e) {
+- log.error("Certificate convertion failed" , e );
+- return;
+- }
+-
+- req.setAttribute(SSLSupport.CERTIFICATE_KEY,
+- jsseCerts);
+- }
+-
+- } else if( actionCode==ActionCode.ACTION_REQ_HOST_ATTRIBUTE ) {
+- Request req=(Request)param;
+-
+- // If remoteHost not set by JK, get it's name from it's remoteAddr
+- if( req.remoteHost().isNull()) {
+- try {
+- req.remoteHost().setString(InetAddress.getByName(
+- req.remoteAddr().toString()).
+- getHostName());
+- } catch(IOException iex) {
+- if(log.isDebugEnabled())
+- log.debug("Unable to resolve "+req.remoteAddr());
+- }
+- }
+- } else if( actionCode==ActionCode.ACTION_ACK ) {
+- if( log.isTraceEnabled() )
+- log.trace("ACK " );
+- } else if ( actionCode == ActionCode.ACTION_REQ_SET_BODY_REPLAY ) {
+- if( log.isTraceEnabled() )
+- log.trace("Replay ");
+- ByteChunk bc = (ByteChunk)param;
+- req.setContentLength(bc.getLength());
+- jkIS.setReplay(bc);
+- }
+- }
+-
+-
+- private void logTime(Request req, Response res ) {
+- // called after the request
+- // org.apache.coyote.Request req=(org.apache.coyote.Request)param;
+- // Response res=req.getResponse();
+- String uri=req.requestURI().toString();
+- if( uri.indexOf( ".gif" ) >0 ) return;
+-
+- setLong( MsgContext.TIMER_POST_REQUEST, System.currentTimeMillis());
+- long t1= getLong( MsgContext.TIMER_PRE_REQUEST ) -
+- getLong( MsgContext.TIMER_RECEIVED );
+- long t2= getLong( MsgContext.TIMER_POST_REQUEST ) -
+- getLong( MsgContext.TIMER_PRE_REQUEST );
+-
+- logTime.debug("Time pre=" + t1 + "/ service=" + t2 + " " +
+- res.getContentLength() + " " +
+- uri );
+- }
+-
+- public void recycle() {
+- jkIS.recycle();
+- }
+-}
+Index: java/org/apache/jk/core/JkChannel.java
+===================================================================
+--- java/org/apache/jk/core/JkChannel.java (revision 590752)
++++ java/org/apache/jk/core/JkChannel.java (working copy)
+@@ -1,77 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.core;
+-
+-import java.io.IOException;
+-
+-import org.apache.coyote.Request;
+-
+-/**
+- * A Channel represents a connection point to the outside world.
+- *
+- * @author Bill Barker
+- */
+-
+-public interface JkChannel {
+-
+-
+- /**
+- * Return the identifying name of this Channel.
+- */
+- public String getChannelName();
+-
+- /**
+- * Send a message back to the client.
+- * @param msg The message to send.
+- * @param ep The connection point for this request.
+- */
+- public int send(Msg msg, MsgContext ep) throws IOException;
+-
+- /**
+- * Recieve a message from the client.
+- * @param msg The place to recieve the data into.
+- * @param ep The connection point for this request.
+- */
+- public int receive(Msg msg, MsgContext ep) throws IOException;
+-
+- /**
+- * Flush the data to the client.
+- */
+- public int flush(Msg msg, MsgContext ep) throws IOException;
+-
+- /**
+- * Invoke the request chain.
+- */
+- public int invoke(Msg msg, MsgContext ep) throws IOException;
+-
+- /**
+- * Confirm that a shutdown request was recieved form us.
+- */
+- public boolean isSameAddress(MsgContext ep);
+-
+- /**
+- * Register a new Request in the Request pool.
+- */
+- public void registerRequest(Request req, MsgContext ep, int count);
+-
+- /**
+- * Create a new request endpoint.
+- */
+- public MsgContext createMsgContext();
+-
+-}
+Index: java/org/apache/jk/core/package.html
+===================================================================
+--- java/org/apache/jk/core/package.html (revision 590752)
++++ java/org/apache/jk/core/package.html (working copy)
+@@ -1,32 +0,0 @@
+-<!--
+- 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.
+--->
+-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+-<html>
+-<head>
+- <title></title>
+-</head>
+-<body>
+-<h2>Jk2 interfaces</h2>
+-<p>Core interfaces for jk2, similar with the interfaces defined in the C
+-side ( jk2/include ).<br>
+-</p>
+-<p>The implementations are in common/ and server/.<br>
+-</p>
+-<p><br>
+-</p>
+-</body>
+-</html>
16 years, 6 months
JBossWeb SVN: r333 - sandbox/tomcat/tomcat6.
by jbossweb-commits@lists.jboss.org
Author: jfrederic.clere(a)jboss.com
Date: 2007-11-01 12:27:49 -0400 (Thu, 01 Nov 2007)
New Revision: 333
Added:
sandbox/tomcat/tomcat6/nio.patch
Log:
Add the remove of nio.
Added: sandbox/tomcat/tomcat6/nio.patch
===================================================================
--- sandbox/tomcat/tomcat6/nio.patch (rev 0)
+++ sandbox/tomcat/tomcat6/nio.patch 2007-11-01 16:27:49 UTC (rev 333)
@@ -0,0 +1,12434 @@
+Index: webapps/docs/config/http.xml
+===================================================================
+--- webapps/docs/config/http.xml (revision 590752)
++++ webapps/docs/config/http.xml (working copy)
+@@ -386,191 +386,10 @@
+
+ </subsection>
+
+- <subsection name="Nio Implementation">
++<subsection name="Nio Implementation">
+
+- <p>The NIO connector exposes all the low level socket properties that can be used to tune the connector.
+- Most of these attributes are directly linked to the socket implementation in the JDK so you can find out
+- about the actual meaning in the JDK API documentation.<br/>
+- <strong>Note</strong>On some JDK versions, setTrafficClass causes a problem, a work around for this is to add
+- the <code>-Djava.net.preferIPv4Stack=true</code> value to your command line</p>
++ <p>The NIO connector is not supported in this distribution, you may use <a href="apr.html">APR</a> instead</p>
+
+- <attributes>
+- <attribute name="useSendfile" required="false">
+- <p>(bool)Use this attribute to enable or disable sendfile capability.
+- The default value is <code>true</code>
+- </p>
+- </attribute>
+- <attribute name="useExecutor" required="false">
+- <p>(bool)Set to true to use the NIO thread pool executor. The default value is <code>true</code>.
+- If set to false, it uses a thread pool based on a stack for its execution.
+- Generally, using the executor yields a little bit slower performance, but yields a better
+- fairness for processing connections in a high load environment as the traffic gets queued through a
+- FIFO queue. If set to true(default) then the max pool size is the <code>maxThreads</code> attribute
+- and the core pool size is the <code>minSpareThreads</code>.
+- This value is ignored if the <code>executor</code> attribute is present and points to a valid shared thread pool.
+- </p>
+- </attribute>
+- <attribute name="acceptorThreadCount" required="false">
+- <p>(int)The number of threads to be used to accept connections. Increase this value on a multi CPU machine,
+- although you would never really need more than <code>2</code>. Also, with a lot of non keep alive connections,
+- you might want to increase this value as well. Default value is <code>1</code>.</p>
+- </attribute>
+- <attribute name="pollerThreadCount" required="false">
+- <p>(int)The number of threads to be used to run for the polling events. Default value is <code>1</code>.
+- Can't see a reason to go above that. But experiment and find your own results.</p>
+- </attribute>
+- <attribute name="pollerThreadPriority" required="false">
+- <p>(int)The priority of the poller threads.
+- The default value is <code>java.lang.Thread#NORM_PRIORITY</code>.
+- See the JavaDoc for the java.lang.Thread class for more details on
+- what this priority means.
+- </p>
+- </attribute>
+- <attribute name="acceptorThreadPriority" required="false">
+- <p>(int)The priority of the acceptor threads. The threads used to accept new connections.
+- The default value is <code>java.lang.Thread#NORM_PRIORITY</code>.
+- See the JavaDoc for the java.lang.Thread class for more details on
+- what this priority means.
+- </p>
+- </attribute>
+-
+- <attribute name="selectorTimeout" required="false">
+- <p>(int)The time in milliseconds to timeout on a select() for the poller.
+- This value is important, since connection clean up is done on the same thread, so dont set this
+- value to an extremely high one. The default value is <code>1000</code> milliseconds.</p>
+- </attribute>
+- <attribute name="useComet" required="false">
+- <p>(bool)Whether to allow comet servlets or not, Default value is <code>true</code>.</p>
+- </attribute>
+- <attribute name="processCache" required="false">
+- <p>(int)The protocol handler caches Http11NioProcessor objects to speed up performance.
+- This setting dictates how many of these objects get cached.
+- <code>-1</code> means unlimited, default is <code>200</code>. Set this value somewhere close to your maxThreads value.
+- </p>
+- </attribute>
+- <attribute name="socket.directBuffer" required="false">
+- <p>(bool)Boolean value, whether to use direct ByteBuffers or java mapped ByteBuffers. Default is <code>false</code>
+- <br/>When you are using direct buffers, make sure you allocate the appropriate amount of memory for the
+- direct memory space. On Sun's JDK that would be something like <code>-XX:MaxDirectMemorySize=256m</code></p>
+- </attribute>
+- <attribute name="socket.rxBufSize" required="false">
+- <p>(int)The socket receive buffer (SO_RCVBUF) size in bytes. Default value is <code>25188</code></p>
+- </attribute>
+- <attribute name="socket.txBufSize" required="false">
+- <p>(int)The socket send buffer (SO_SNDBUF) size in bytes. Default value is <code>43800</code></p>
+- </attribute>
+- <attribute name="socket.appReadBufSize" required="false">
+- <p>(int)Each connection that is opened up in Tomcat get associated with a read and a write ByteBuffer
+- This attribute controls the size of these buffers. By default this read buffer is sized at <code>8192</code> bytes.
+- For lower concurrency, you can increase this to buffer more data.
+- For an extreme amount of keep alive connections, decrease this number or increase your heap size.</p>
+- </attribute>
+- <attribute name="socket.appWriteBufSize" required="false">
+- <p>(int)Each connection that is opened up in Tomcat get associated with a read and a write ByteBuffer
+- This attribute controls the size of these buffers. By default this write buffer is sized at <code>8192</code> bytes.
+- For low concurrency you can increase this to buffer more response data.
+- For an extreme amount of keep alive connections, decrease this number or increase your heap size.
+- <br/>
+- The default value here is pretty low, you should up it if you are not dealing with tens of thousands
+- concurrent connections.</p>
+- </attribute>
+- <attribute name="socket.bufferPool" required="false">
+- <p>(int)The Nio connector uses a class called NioChannel that holds elements linked to a socket.
+- To reduce garbage collection, the Nio connector caches these channel objects.
+- This value specifies the size of this cache.
+- The default value is <code>500</code>, and represents that the cache will hold 500 NioChannel objects.
+- Other values are <code>-1</code>. unlimited cache, and <code>0</code>, no cache.</p>
+- </attribute>
+- <attribute name="socket.bufferPoolSize" required="false">
+- <p>(int)The NioChannel pool can also be size based, not used object based. The size is calculated as follows:<br/>
+- NioChannel <code>buffer size = read buffer size + write buffer size</code><br/>
+- SecureNioChannel <code>buffer size = application read buffer size + application write buffer size + network read buffer size + network write buffer size</code><br/>
+- The value is in bytes, the default value is <code>1024*1024*100</code> (100MB)
+- </p>
+- </attribute>
+- <attribute name="socket.processorCache" required="false">
+- <p>(int)Tomcat will cache SocketProcessor objects to reduce garbage collection.
+- The integer value specifies how many objects to keep in the cache at most.
+- The default is <code>500</code>.
+- Other values are <code>-1</code>. unlimited cache, and <code>0</code>, no cache.</p>
+- </attribute>
+- <attribute name="socket.keyCache" required="false">
+- <p>(int)Tomcat will cache KeyAttachment objects to reduce garbage collection.
+- The integer value specifies how many objects to keep in the cache at most.
+- The default is <code>500</code>.
+- Other values are <code>-1</code>. unlimited cache, and <code>0</code>, no cache.</p>
+- </attribute>
+- <attribute name="socket.eventCache" required="false">
+- <p>(int)Tomcat will cache PollerEvent objects to reduce garbage collection.
+- The integer value specifies how many objects to keep in the cache at most.
+- The default is <code>500</code>.
+- Other values are <code>-1</code>. unlimited cache, and <code>0</code>, no cache.</p>
+- </attribute>
+- <attribute name="socket.tcpNoDelay" required="false">
+- <p>(bool)same as the standard setting <code>tcpNoDelay</code>. Default value is <code>false</code></p>
+- </attribute>
+- <attribute name="socket.soKeepAlive" required="false">
+- <p>(bool)Boolean value for the socket's keep alive setting (SO_KEEPALIVE). Default is <code>false</code>. </p>
+- </attribute>
+- <attribute name="socket.ooBInline" required="false">
+- <p>(bool)Boolean value for the socket OOBINLINE setting. Default value is <code>true</code></p>
+- </attribute>
+- <attribute name="socket.soReuseAddress" required="false">
+- <p>(bool)Boolean value for the sockets reuse address option (SO_REUSEADDR). Default value is <code>true</code></p>
+- </attribute>
+- <attribute name="socket.soLingerOn" required="false">
+- <p>(bool)Boolean value for the sockets so linger option (SO_LINGER). Default value is <code>true</code>.
+- This option is paired with the <code>soLingerTime</code> value.</p>
+- </attribute>
+- <attribute name="socket.soLingerTime" required="false">
+- <p>(bool)Value in seconds for the sockets so linger option (SO_LINGER). Default value is <code>25</code> seconds.
+- This option is paired with the soLinger value.</p>
+- </attribute>
+- <attribute name="socket.soTimeout" required="false">
+- <p>(int)Value in milliseconds for the sockets read timeout (SO_TIMEOUT). Default value is <code>5000</code> milliseconds.</p>
+- </attribute>
+- <attribute name="socket.soTrafficClass" required="false">
+- <p>(byte)Value between <code>0</code> and <code>255</code> for the traffic class on the socket, <code>0x04 | 0x08 | 0x010</code></p>
+- </attribute>
+- <attribute name="socket.performanceConnectionTime" required="false">
+- <p>(int)The first value for the performance settings. Default is <code>1</code>, see <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/net/Socket.html#setPerforman...">Socket Performance Options</a></p>
+- </attribute>
+- <attribute name="socket.performanceLatency" required="false">
+- <p>(int)The second value for the performance settings. Default is <code>0</code>, see <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/net/Socket.html#setPerforman...">Socket Performance Options</a></p>
+- </attribute>
+- <attribute name="socket.performanceBandwidth" required="false">
+- <p>(int)The third value for the performance settings. Default is <code>1</code>, see <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/net/Socket.html#setPerforman...">Socket Performance Options</a></p>
+- </attribute>
+- <attribute name="selectorPool.maxSelectors" required="false">
+- <p>(int)The max selectors to be used in the pool, to reduce selector contention.
+- Use this option when the command line <code>org.apache.tomcat.util.net.NioSelectorShared</code> value is set to false.
+- Default value is <code>200</code>.</p>
+- </attribute>
+- <attribute name="selectorPool.maxSpareSelectors" required="false">
+- <p>(int)The max spare selectors to be used in the pool, to reduce selector contention.
+- When a selector is returned to the pool, the system can decide to keep it or let it be GC:ed.
+- Use this option when the command line <code>org.apache.tomcat.util.net.NioSelectorShared</code> value is set to false.
+- Default value is <code>-1</code> (unlimited)</p>
+- </attribute>
+- <attribute name="command-line-options" required="false">
+- <p>The following command line options are available for the NIO connector:<br/>
+- <code>-Dorg.apache.tomcat.util.net.NioSelectorShared=true|false</code> - default is <code>true</code>.
+- Set this value to false if you wish to use a selector for each thread.
+- the property. If you do set it to false, you can control the size of the pool of selectors by using the
+- selectorPool.maxSelectors attribute</p>
+- </attribute>
+- <attribute name="oomParachute" required="false">
+- <p>(int)The NIO connector implements an OutOfMemoryError strategy called parachute.
+- It holds a chunk of data as a byte array. In case of an OOM,
+- this chunk of data is released and the error is reported. This will give the VM enough room
+- to clean up. The <code>oomParachute</code> represent the size in bytes of the parachute(the byte array).
+- The default value is <code>1024*1024</code>(1MB).
+- Please note, this only works for OOM errors regarding the Java Heap space, and there is absolutely no
+- guarantee that you will be able to recover at all.
+- If you have an OOM outside of the Java Heap, then this parachute trick will not help.
+- </p>
+- </attribute>
+- </attributes>
+ </subsection>
+
+ </section>
+Index: java/org/apache/tomcat/util/net/NioBlockingSelector.java
+===================================================================
+--- java/org/apache/tomcat/util/net/NioBlockingSelector.java (revision 590752)
++++ java/org/apache/tomcat/util/net/NioBlockingSelector.java (working copy)
+@@ -1,161 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.tomcat.util.net;
+-
+-import java.io.EOFException;
+-import java.io.IOException;
+-import java.net.SocketTimeoutException;
+-import java.nio.ByteBuffer;
+-import java.nio.channels.SelectionKey;
+-import java.util.concurrent.TimeUnit;
+-
+-import org.apache.tomcat.util.net.NioEndpoint.KeyAttachment;
+-
+-public class NioBlockingSelector {
+- public NioBlockingSelector() {
+- }
+-
+- /**
+- * Performs a blocking write using the bytebuffer for data to be written
+- * If the <code>selector</code> parameter is null, then it will perform a busy write that could
+- * take up a lot of CPU cycles.
+- * @param buf ByteBuffer - the buffer containing the data, we will write as long as <code>(buf.hasRemaining()==true)</code>
+- * @param socket SocketChannel - the socket to write data to
+- * @param writeTimeout long - the timeout for this write operation in milliseconds, -1 means no timeout
+- * @return int - returns the number of bytes written
+- * @throws EOFException if write returns -1
+- * @throws SocketTimeoutException if the write times out
+- * @throws IOException if an IO Exception occurs in the underlying socket logic
+- */
+- public static int write(ByteBuffer buf, NioChannel socket, long writeTimeout) throws IOException {
+- SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
+- int written = 0;
+- boolean timedout = false;
+- int keycount = 1; //assume we can write
+- long time = System.currentTimeMillis(); //start the timeout timer
+- try {
+- while ( (!timedout) && buf.hasRemaining()) {
+- if (keycount > 0) { //only write if we were registered for a write
+- int cnt = socket.write(buf); //write the data
+- if (cnt == -1)
+- throw new EOFException();
+- written += cnt;
+- if (cnt > 0) {
+- time = System.currentTimeMillis(); //reset our timeout timer
+- continue; //we successfully wrote, try again without a selector
+- }
+- }
+- if ( key == null ) throw new IOException("Key no longer registered");
+- KeyAttachment att = (KeyAttachment) key.attachment();
+- try {
+- if ( att.getWriteLatch()==null || att.getWriteLatch().getCount()==0) att.startWriteLatch(1);
+- //only register for write if a write has not yet been issued
+- if ( (att.interestOps() & SelectionKey.OP_WRITE) == 0) socket.getPoller().add(socket,SelectionKey.OP_WRITE);
+- att.awaitWriteLatch(writeTimeout,TimeUnit.MILLISECONDS);
+- }catch (InterruptedException ignore) {
+- Thread.interrupted();
+- }
+- if ( att.getWriteLatch()!=null && att.getWriteLatch().getCount()> 0) {
+- //we got interrupted, but we haven't received notification from the poller.
+- keycount = 0;
+- }else {
+- //latch countdown has happened
+- keycount = 1;
+- att.resetWriteLatch();
+- }
+-
+- if (writeTimeout > 0 && (keycount == 0))
+- timedout = (System.currentTimeMillis() - time) >= writeTimeout;
+- } //while
+- if (timedout)
+- throw new SocketTimeoutException();
+- } finally {
+- if (timedout && key != null) {
+- cancelKey(socket, key);
+- }
+- }
+- return written;
+- }
+-
+- private static void cancelKey(final NioChannel socket, final SelectionKey key) {
+- socket.getPoller().addEvent(
+- new Runnable() {
+- public void run() {
+- key.cancel();
+- }
+- });
+- }
+-
+- /**
+- * Performs a blocking read using the bytebuffer for data to be read
+- * If the <code>selector</code> parameter is null, then it will perform a busy read that could
+- * take up a lot of CPU cycles.
+- * @param buf ByteBuffer - the buffer containing the data, we will read as until we have read at least one byte or we timed out
+- * @param socket SocketChannel - the socket to write data to
+- * @param selector Selector - the selector to use for blocking, if null then a busy read will be initiated
+- * @param readTimeout long - the timeout for this read operation in milliseconds, -1 means no timeout
+- * @return int - returns the number of bytes read
+- * @throws EOFException if read returns -1
+- * @throws SocketTimeoutException if the read times out
+- * @throws IOException if an IO Exception occurs in the underlying socket logic
+- */
+- public static int read(ByteBuffer buf, NioChannel socket, long readTimeout) throws IOException {
+- final SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
+- int read = 0;
+- boolean timedout = false;
+- int keycount = 1; //assume we can write
+- long time = System.currentTimeMillis(); //start the timeout timer
+- try {
+- while ( (!timedout) && read == 0) {
+- if (keycount > 0) { //only read if we were registered for a read
+- int cnt = socket.read(buf);
+- if (cnt == -1)
+- throw new EOFException();
+- read += cnt;
+- if (cnt > 0)
+- break;
+- }
+- KeyAttachment att = (KeyAttachment) key.attachment();
+- try {
+- if ( att.getReadLatch()==null || att.getReadLatch().getCount()==0) att.startReadLatch(1);
+- if ( att.interestOps() == 0) socket.getPoller().add(socket,SelectionKey.OP_READ);
+- att.awaitReadLatch(readTimeout,TimeUnit.MILLISECONDS);
+- }catch (InterruptedException ignore) {
+- Thread.interrupted();
+- }
+- if ( att.getReadLatch()!=null && att.getReadLatch().getCount()> 0) {
+- //we got interrupted, but we haven't received notification from the poller.
+- keycount = 0;
+- }else {
+- //latch countdown has happened
+- keycount = 1;
+- att.resetReadLatch();
+- }
+- if (readTimeout > 0 && (keycount == 0))
+- timedout = (System.currentTimeMillis() - time) >= readTimeout;
+- } //while
+- if (timedout)
+- throw new SocketTimeoutException();
+- } finally {
+- if (timedout && key != null) {
+- cancelKey(socket,key);
+- }
+- }
+- return read;
+- }
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/tomcat/util/net/NioChannel.java
+===================================================================
+--- java/org/apache/tomcat/util/net/NioChannel.java (revision 590752)
++++ java/org/apache/tomcat/util/net/NioChannel.java (working copy)
+@@ -1,193 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-
+-package org.apache.tomcat.util.net;
+-
+-import java.io.IOException;
+-import java.nio.ByteBuffer;
+-import java.nio.channels.ByteChannel;
+-import java.nio.channels.SocketChannel;
+-
+-import org.apache.tomcat.util.net.NioEndpoint.Poller;
+-import org.apache.tomcat.util.net.SecureNioChannel.ApplicationBufferHandler;
+-import java.nio.channels.Selector;
+-import java.nio.channels.SelectionKey;
+-
+-/**
+- *
+- * Base class for a SocketChannel wrapper used by the endpoint.
+- * This way, logic for a SSL socket channel remains the same as for
+- * a non SSL, making sure we don't need to code for any exception cases.
+- *
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public class NioChannel implements ByteChannel{
+-
+- protected static ByteBuffer emptyBuf = ByteBuffer.allocate(0);
+-
+- protected SocketChannel sc = null;
+-
+- protected ApplicationBufferHandler bufHandler;
+-
+- protected Poller poller;
+-
+- public NioChannel(SocketChannel channel, ApplicationBufferHandler bufHandler) throws IOException {
+- this.sc = channel;
+- this.bufHandler = bufHandler;
+- }
+-
+- public void reset() throws IOException {
+- bufHandler.getReadBuffer().clear();
+- bufHandler.getWriteBuffer().clear();
+- }
+-
+- public int getBufferSize() {
+- if ( bufHandler == null ) return 0;
+- int size = 0;
+- size += bufHandler.getReadBuffer()!=null?bufHandler.getReadBuffer().capacity():0;
+- size += bufHandler.getWriteBuffer()!=null?bufHandler.getWriteBuffer().capacity():0;
+- return size;
+- }
+-
+- /**
+- * returns true if the network buffer has
+- * been flushed out and is empty
+- * @return boolean
+- */
+- public boolean flush(boolean block, Selector s,long timeout) throws IOException {
+- return true; //no network buffer in the regular channel
+- }
+-
+-
+- /**
+- * Closes this channel.
+- *
+- * @throws IOException If an I/O error occurs
+- * @todo Implement this java.nio.channels.Channel method
+- */
+- public void close() throws IOException {
+- getIOChannel().socket().close();
+- getIOChannel().close();
+- }
+-
+- public void close(boolean force) throws IOException {
+- if (isOpen() || force ) close();
+- }
+- /**
+- * Tells whether or not this channel is open.
+- *
+- * @return <tt>true</tt> if, and only if, this channel is open
+- * @todo Implement this java.nio.channels.Channel method
+- */
+- public boolean isOpen() {
+- return sc.isOpen();
+- }
+-
+- /**
+- * Writes a sequence of bytes to this channel from the given buffer.
+- *
+- * @param src The buffer from which bytes are to be retrieved
+- * @return The number of bytes written, possibly zero
+- * @throws IOException If some other I/O error occurs
+- * @todo Implement this java.nio.channels.WritableByteChannel method
+- */
+- public int write(ByteBuffer src) throws IOException {
+- return sc.write(src);
+- }
+-
+- /**
+- * Reads a sequence of bytes from this channel into the given buffer.
+- *
+- * @param dst The buffer into which bytes are to be transferred
+- * @return The number of bytes read, possibly zero, or <tt>-1</tt> if the channel has reached end-of-stream
+- * @throws IOException If some other I/O error occurs
+- * @todo Implement this java.nio.channels.ReadableByteChannel method
+- */
+- public int read(ByteBuffer dst) throws IOException {
+- return sc.read(dst);
+- }
+-
+- public Object getAttachment(boolean remove) {
+- Poller pol = getPoller();
+- Selector sel = pol!=null?pol.getSelector():null;
+- SelectionKey key = sel!=null?getIOChannel().keyFor(sel):null;
+- Object att = key!=null?key.attachment():null;
+- if (key != null && att != null && remove ) key.attach(null);
+- return att;
+- }
+- /**
+- * getBufHandler
+- *
+- * @return ApplicationBufferHandler
+- * @todo Implement this org.apache.tomcat.util.net.SecureNioChannel method
+- */
+- public ApplicationBufferHandler getBufHandler() {
+- return bufHandler;
+- }
+-
+- public Poller getPoller() {
+- return poller;
+- }
+- /**
+- * getIOChannel
+- *
+- * @return SocketChannel
+- * @todo Implement this org.apache.tomcat.util.net.SecureNioChannel method
+- */
+- public SocketChannel getIOChannel() {
+- return sc;
+- }
+-
+- /**
+- * isClosing
+- *
+- * @return boolean
+- * @todo Implement this org.apache.tomcat.util.net.SecureNioChannel method
+- */
+- public boolean isClosing() {
+- return false;
+- }
+-
+- /**
+- * isInitHandshakeComplete
+- *
+- * @return boolean
+- * @todo Implement this org.apache.tomcat.util.net.SecureNioChannel method
+- */
+- public boolean isInitHandshakeComplete() {
+- return true;
+- }
+-
+- public int handshake(boolean read, boolean write) throws IOException {
+- return 0;
+- }
+-
+- public void setPoller(Poller poller) {
+- this.poller = poller;
+- }
+-
+- public void setIOChannel(SocketChannel IOChannel) {
+- this.sc = IOChannel;
+- }
+-
+- public String toString() {
+- return super.toString()+":"+this.sc.toString();
+- }
+-
+-}
+Index: java/org/apache/tomcat/util/net/NioSelectorPool.java
+===================================================================
+--- java/org/apache/tomcat/util/net/NioSelectorPool.java (revision 590752)
++++ java/org/apache/tomcat/util/net/NioSelectorPool.java (working copy)
+@@ -1,264 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.tomcat.util.net;
+-
+-import java.util.concurrent.atomic.AtomicInteger;
+-import java.nio.channels.Selector;
+-import java.io.IOException;
+-import java.util.NoSuchElementException;
+-import java.nio.ByteBuffer;
+-import java.nio.channels.SelectionKey;
+-import java.io.EOFException;
+-import java.net.SocketTimeoutException;
+-import java.util.concurrent.ConcurrentLinkedQueue;
+-import org.apache.juli.logging.Log;
+-import org.apache.juli.logging.LogFactory;
+-
+-/**
+- *
+- * Thread safe non blocking selector pool
+- * @author Filip Hanik
+- * @version 1.0
+- * @since 6.0
+- */
+-
+-public class NioSelectorPool {
+- protected static Log log = LogFactory.getLog(NioSelectorPool.class);
+-
+- protected final static boolean SHARED =
+- Boolean.valueOf(System.getProperty("org.apache.tomcat.util.net.NioSelectorShared", "true")).booleanValue();
+- protected static Selector SHARED_SELECTOR;
+-
+- protected int maxSelectors = 200;
+- protected int maxSpareSelectors = -1;
+- protected boolean enabled = true;
+- protected AtomicInteger active = new AtomicInteger(0);
+- protected AtomicInteger spare = new AtomicInteger(0);
+- protected ConcurrentLinkedQueue<Selector> selectors = new ConcurrentLinkedQueue<Selector>();
+-
+- protected static Selector getSharedSelector() throws IOException {
+- if (SHARED && SHARED_SELECTOR == null) {
+- synchronized ( NioSelectorPool.class ) {
+- if ( SHARED_SELECTOR == null ) {
+- SHARED_SELECTOR = Selector.open();
+- log.info("Using a shared selector for servlet write/read");
+- }
+- }
+- }
+- return SHARED_SELECTOR;
+- }
+-
+- public Selector get() throws IOException{
+- if ( SHARED ) {
+- return getSharedSelector();
+- }
+- if ( (!enabled) || active.incrementAndGet() >= maxSelectors ) {
+- if ( enabled ) active.decrementAndGet();
+- return null;
+- }
+- Selector s = null;
+- try {
+- s = selectors.size()>0?selectors.poll():null;
+- if (s == null) s = Selector.open();
+- else spare.decrementAndGet();
+-
+- }catch (NoSuchElementException x ) {
+- try {s = Selector.open();}catch (IOException iox){}
+- } finally {
+- if ( s == null ) active.decrementAndGet();//we were unable to find a selector
+- }
+- return s;
+- }
+-
+-
+-
+- public void put(Selector s) throws IOException {
+- if ( SHARED ) return;
+- if ( enabled ) active.decrementAndGet();
+- if ( enabled && (maxSpareSelectors==-1 || spare.get() < Math.min(maxSpareSelectors,maxSelectors)) ) {
+- spare.incrementAndGet();
+- selectors.offer(s);
+- }
+- else s.close();
+- }
+-
+- public void close() throws IOException {
+- enabled = false;
+- Selector s;
+- while ( (s = selectors.poll()) != null ) s.close();
+- spare.set(0);
+- active.set(0);
+- if ( SHARED && getSharedSelector()!=null ) {
+- getSharedSelector().close();
+- SHARED_SELECTOR = null;
+- }
+- }
+-
+- public void open() throws IOException {
+- enabled = true;
+- getSharedSelector();
+- }
+-
+- /**
+- * Performs a blocking write using the bytebuffer for data to be written and a selector to block.
+- * If the <code>selector</code> parameter is null, then it will perform a busy write that could
+- * take up a lot of CPU cycles.
+- * @param buf ByteBuffer - the buffer containing the data, we will write as long as <code>(buf.hasRemaining()==true)</code>
+- * @param socket SocketChannel - the socket to write data to
+- * @param selector Selector - the selector to use for blocking, if null then a busy write will be initiated
+- * @param writeTimeout long - the timeout for this write operation in milliseconds, -1 means no timeout
+- * @return int - returns the number of bytes written
+- * @throws EOFException if write returns -1
+- * @throws SocketTimeoutException if the write times out
+- * @throws IOException if an IO Exception occurs in the underlying socket logic
+- */
+- public int write(ByteBuffer buf, NioChannel socket, Selector selector, long writeTimeout) throws IOException {
+- return write(buf,socket,selector,writeTimeout,true);
+- }
+-
+- public int write(ByteBuffer buf, NioChannel socket, Selector selector, long writeTimeout, boolean block) throws IOException {
+- if ( SHARED && block) {
+- return NioBlockingSelector.write(buf,socket,writeTimeout);
+- }
+- SelectionKey key = null;
+- int written = 0;
+- boolean timedout = false;
+- int keycount = 1; //assume we can write
+- long time = System.currentTimeMillis(); //start the timeout timer
+- try {
+- while ( (!timedout) && buf.hasRemaining() ) {
+- int cnt = 0;
+- if ( keycount > 0 ) { //only write if we were registered for a write
+- cnt = socket.write(buf); //write the data
+- if (cnt == -1) throw new EOFException();
+- written += cnt;
+- if (cnt > 0) {
+- time = System.currentTimeMillis(); //reset our timeout timer
+- continue; //we successfully wrote, try again without a selector
+- }
+- if (cnt==0 && (!block)) break; //don't block
+- }
+- if ( selector != null ) {
+- //register OP_WRITE to the selector
+- if (key==null) key = socket.getIOChannel().register(selector, SelectionKey.OP_WRITE);
+- else key.interestOps(SelectionKey.OP_WRITE);
+- keycount = selector.select(writeTimeout);
+- }
+- if (writeTimeout > 0 && (selector == null || keycount == 0) ) timedout = (System.currentTimeMillis()-time)>=writeTimeout;
+- }//while
+- if ( timedout ) throw new SocketTimeoutException();
+- } finally {
+- if (key != null) {
+- key.cancel();
+- if (selector != null) selector.selectNow();//removes the key from this selector
+- }
+- }
+- return written;
+- }
+-
+- /**
+- * Performs a blocking read using the bytebuffer for data to be read and a selector to block.
+- * If the <code>selector</code> parameter is null, then it will perform a busy read that could
+- * take up a lot of CPU cycles.
+- * @param buf ByteBuffer - the buffer containing the data, we will read as until we have read at least one byte or we timed out
+- * @param socket SocketChannel - the socket to write data to
+- * @param selector Selector - the selector to use for blocking, if null then a busy read will be initiated
+- * @param readTimeout long - the timeout for this read operation in milliseconds, -1 means no timeout
+- * @return int - returns the number of bytes read
+- * @throws EOFException if read returns -1
+- * @throws SocketTimeoutException if the read times out
+- * @throws IOException if an IO Exception occurs in the underlying socket logic
+- */
+- public int read(ByteBuffer buf, NioChannel socket, Selector selector, long readTimeout) throws IOException {
+- return read(buf,socket,selector,readTimeout,true);
+- }
+- /**
+- * Performs a read using the bytebuffer for data to be read and a selector to register for events should
+- * you have the block=true.
+- * If the <code>selector</code> parameter is null, then it will perform a busy read that could
+- * take up a lot of CPU cycles.
+- * @param buf ByteBuffer - the buffer containing the data, we will read as until we have read at least one byte or we timed out
+- * @param socket SocketChannel - the socket to write data to
+- * @param selector Selector - the selector to use for blocking, if null then a busy read will be initiated
+- * @param readTimeout long - the timeout for this read operation in milliseconds, -1 means no timeout
+- * @param block - true if you want to block until data becomes available or timeout time has been reached
+- * @return int - returns the number of bytes read
+- * @throws EOFException if read returns -1
+- * @throws SocketTimeoutException if the read times out
+- * @throws IOException if an IO Exception occurs in the underlying socket logic
+- */
+- public int read(ByteBuffer buf, NioChannel socket, Selector selector, long readTimeout, boolean block) throws IOException {
+- if ( SHARED && block) {
+- return NioBlockingSelector.read(buf,socket,readTimeout);
+- }
+- SelectionKey key = null;
+- int read = 0;
+- boolean timedout = false;
+- int keycount = 1; //assume we can write
+- long time = System.currentTimeMillis(); //start the timeout timer
+- try {
+- while ( (!timedout) ) {
+- int cnt = 0;
+- if ( keycount > 0 ) { //only read if we were registered for a read
+- cnt = socket.read(buf);
+- if (cnt == -1) throw new EOFException();
+- read += cnt;
+- if (cnt > 0) continue; //read some more
+- if (cnt==0 && (read>0 || (!block) ) ) break; //we are done reading
+- }
+- if ( selector != null ) {//perform a blocking read
+- //register OP_WRITE to the selector
+- if (key==null) key = socket.getIOChannel().register(selector, SelectionKey.OP_READ);
+- else key.interestOps(SelectionKey.OP_READ);
+- keycount = selector.select(readTimeout);
+- }
+- if (readTimeout > 0 && (selector == null || keycount == 0) ) timedout = (System.currentTimeMillis()-time)>=readTimeout;
+- }//while
+- if ( timedout ) throw new SocketTimeoutException();
+- } finally {
+- if (key != null) {
+- key.cancel();
+- if (selector != null) selector.selectNow();//removes the key from this selector
+- }
+- }
+- return read;
+- }
+-
+- public void setMaxSelectors(int maxSelectors) {
+- this.maxSelectors = maxSelectors;
+- }
+-
+- public void setMaxSpareSelectors(int maxSpareSelectors) {
+- this.maxSpareSelectors = maxSpareSelectors;
+- }
+-
+- public void setEnabled(boolean enabled) {
+- this.enabled = enabled;
+- }
+-
+- public int getMaxSelectors() {
+- return maxSelectors;
+- }
+-
+- public int getMaxSpareSelectors() {
+- return maxSpareSelectors;
+- }
+-
+- public boolean isEnabled() {
+- return enabled;
+- }
+-}
+\ No newline at end of file
+Index: java/org/apache/tomcat/util/net/SecureNioChannel.java
+===================================================================
+--- java/org/apache/tomcat/util/net/SecureNioChannel.java (revision 590752)
++++ java/org/apache/tomcat/util/net/SecureNioChannel.java (working copy)
+@@ -1,473 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.tomcat.util.net;
+-
+-import java.io.IOException;
+-import java.nio.ByteBuffer;
+-import java.nio.channels.SelectionKey;
+-import java.nio.channels.SocketChannel;
+-import javax.net.ssl.SSLEngine;
+-import javax.net.ssl.SSLEngineResult;
+-import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+-import javax.net.ssl.SSLEngineResult.Status;
+-import java.nio.channels.Selector;
+-
+-/**
+- *
+- * Implementation of a secure socket channel
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-
+-public class SecureNioChannel extends NioChannel {
+-
+- protected ByteBuffer netInBuffer;
+- protected ByteBuffer netOutBuffer;
+-
+- protected SSLEngine sslEngine;
+-
+- protected boolean initHandshakeComplete = false;
+- protected HandshakeStatus initHandshakeStatus; //gets set by begin handshake
+-
+- protected boolean closed = false;
+- protected boolean closing = false;
+-
+- protected NioSelectorPool pool;
+-
+- public SecureNioChannel(SocketChannel channel, SSLEngine engine,
+- ApplicationBufferHandler bufHandler, NioSelectorPool pool) throws IOException {
+- super(channel,bufHandler);
+- this.sslEngine = engine;
+- int appBufSize = sslEngine.getSession().getApplicationBufferSize();
+- int netBufSize = sslEngine.getSession().getPacketBufferSize();
+- //allocate network buffers - TODO, add in optional direct non-direct buffers
+- if ( netInBuffer == null ) netInBuffer = ByteBuffer.allocateDirect(netBufSize);
+- if ( netOutBuffer == null ) netOutBuffer = ByteBuffer.allocateDirect(netBufSize);
+-
+- //selector pool for blocking operations
+- this.pool = pool;
+-
+- //ensure that the application has a large enough read/write buffers
+- //by doing this, we should not encounter any buffer overflow errors
+- bufHandler.expand(bufHandler.getReadBuffer(), appBufSize);
+- bufHandler.expand(bufHandler.getWriteBuffer(), appBufSize);
+- reset();
+- }
+-
+- public void reset(SSLEngine engine) throws IOException {
+- this.sslEngine = engine;
+- reset();
+- }
+- public void reset() throws IOException {
+- super.reset();
+- netOutBuffer.position(0);
+- netOutBuffer.limit(0);
+- netInBuffer.position(0);
+- netInBuffer.limit(0);
+- initHandshakeComplete = false;
+- closed = false;
+- closing = false;
+- //initiate handshake
+- sslEngine.beginHandshake();
+- initHandshakeStatus = sslEngine.getHandshakeStatus();
+- }
+-
+- public int getBufferSize() {
+- int size = super.getBufferSize();
+- size += netInBuffer!=null?netInBuffer.capacity():0;
+- size += netOutBuffer!=null?netOutBuffer.capacity():0;
+- return size;
+- }
+-
+-
+-//===========================================================================================
+-// NIO SSL METHODS
+-//===========================================================================================
+- /**
+- * returns true if the network buffer has
+- * been flushed out and is empty
+- * @return boolean
+- */
+- public boolean flush(boolean block, Selector s, long timeout) throws IOException {
+- if (!block) {
+- flush(netOutBuffer);
+- } else {
+- pool.write(netOutBuffer, this, s, timeout);
+- }
+- return !netOutBuffer.hasRemaining();
+- }
+-
+- /**
+- * Flushes the buffer to the network, non blocking
+- * @param buf ByteBuffer
+- * @return boolean true if the buffer has been emptied out, false otherwise
+- * @throws IOException
+- */
+- protected boolean flush(ByteBuffer buf) throws IOException {
+- int remaining = buf.remaining();
+- if ( remaining > 0 ) {
+- int written = sc.write(buf);
+- return written >= remaining;
+- }else {
+- return true;
+- }
+- }
+-
+- /**
+- * Performs SSL handshake, non blocking, but performs NEED_TASK on the same thread.<br>
+- * Hence, you should never call this method using your Acceptor thread, as you would slow down
+- * your system significantly.<br>
+- * The return for this operation is 0 if the handshake is complete and a positive value if it is not complete.
+- * In the event of a positive value coming back, reregister the selection key for the return values interestOps.
+- * @param read boolean - true if the underlying channel is readable
+- * @param write boolean - true if the underlying channel is writable
+- * @return int - 0 if hand shake is complete, otherwise it returns a SelectionKey interestOps value
+- * @throws IOException
+- */
+- public int handshake(boolean read, boolean write) throws IOException {
+- if ( initHandshakeComplete ) return 0; //we have done our initial handshake
+-
+- if (!flush(netOutBuffer)) return SelectionKey.OP_WRITE; //we still have data to write
+-
+- SSLEngineResult handshake = null;
+-
+- while (!initHandshakeComplete) {
+- switch ( initHandshakeStatus ) {
+- case NOT_HANDSHAKING: {
+- //should never happen
+- throw new IOException("NOT_HANDSHAKING during handshake");
+- }
+- case FINISHED: {
+- //we are complete if we have delivered the last package
+- initHandshakeComplete = !netOutBuffer.hasRemaining();
+- //return 0 if we are complete, otherwise we still have data to write
+- return initHandshakeComplete?0:SelectionKey.OP_WRITE;
+- }
+- case NEED_WRAP: {
+- //perform the wrap function
+- handshake = handshakeWrap(write);
+- if ( handshake.getStatus() == Status.OK ){
+- if (initHandshakeStatus == HandshakeStatus.NEED_TASK)
+- initHandshakeStatus = tasks();
+- } else {
+- //wrap should always work with our buffers
+- throw new IOException("Unexpected status:" + handshake.getStatus() + " during handshake WRAP.");
+- }
+- if ( initHandshakeStatus != HandshakeStatus.NEED_UNWRAP || (!flush(netOutBuffer)) ) {
+- //should actually return OP_READ if we have NEED_UNWRAP
+- return SelectionKey.OP_WRITE;
+- }
+- //fall down to NEED_UNWRAP on the same call, will result in a
+- //BUFFER_UNDERFLOW if it needs data
+- }
+- case NEED_UNWRAP: {
+- //perform the unwrap function
+- handshake = handshakeUnwrap(read);
+- if ( handshake.getStatus() == Status.OK ) {
+- if (initHandshakeStatus == HandshakeStatus.NEED_TASK)
+- initHandshakeStatus = tasks();
+- } else if ( handshake.getStatus() == Status.BUFFER_UNDERFLOW ){
+- //read more data, reregister for OP_READ
+- return SelectionKey.OP_READ;
+- } else {
+- throw new IOException("Invalid handshake status:"+initHandshakeStatus+" during handshake UNWRAP.");
+- }//switch
+- break;
+- }
+- case NEED_TASK: {
+- initHandshakeStatus = tasks();
+- break;
+- }
+- default: throw new IllegalStateException("Invalid handshake status:"+initHandshakeStatus);
+- }//switch
+- }//while
+- //return 0 if we are complete, otherwise reregister for any activity that
+- //would cause this method to be called again.
+- return initHandshakeComplete?0:(SelectionKey.OP_WRITE|SelectionKey.OP_READ);
+- }
+-
+- /**
+- * Executes all the tasks needed on the same thread.
+- * @return HandshakeStatus
+- */
+- protected SSLEngineResult.HandshakeStatus tasks() {
+- Runnable r = null;
+- while ( (r = sslEngine.getDelegatedTask()) != null) {
+- r.run();
+- }
+- return sslEngine.getHandshakeStatus();
+- }
+-
+- /**
+- * Performs the WRAP function
+- * @param doWrite boolean
+- * @return SSLEngineResult
+- * @throws IOException
+- */
+- protected SSLEngineResult handshakeWrap(boolean doWrite) throws IOException {
+- //this should never be called with a network buffer that contains data
+- //so we can clear it here.
+- netOutBuffer.clear();
+- //perform the wrap
+- SSLEngineResult result = sslEngine.wrap(bufHandler.getWriteBuffer(), netOutBuffer);
+- //prepare the results to be written
+- netOutBuffer.flip();
+- //set the status
+- initHandshakeStatus = result.getHandshakeStatus();
+- //optimization, if we do have a writable channel, write it now
+- if ( doWrite ) flush(netOutBuffer);
+- return result;
+- }
+-
+- /**
+- * Perform handshake unwrap
+- * @param doread boolean
+- * @return SSLEngineResult
+- * @throws IOException
+- */
+- protected SSLEngineResult handshakeUnwrap(boolean doread) throws IOException {
+-
+- if (netInBuffer.position() == netInBuffer.limit()) {
+- //clear the buffer if we have emptied it out on data
+- netInBuffer.clear();
+- }
+- if ( doread ) {
+- //if we have data to read, read it
+- int read = sc.read(netInBuffer);
+- if (read == -1) throw new IOException("EOF encountered during handshake.");
+- }
+- SSLEngineResult result;
+- boolean cont = false;
+- //loop while we can perform pure SSLEngine data
+- do {
+- //prepare the buffer with the incoming data
+- netInBuffer.flip();
+- //call unwrap
+- result = sslEngine.unwrap(netInBuffer, bufHandler.getReadBuffer());
+- //compact the buffer, this is an optional method, wonder what would happen if we didn't
+- netInBuffer.compact();
+- //read in the status
+- initHandshakeStatus = result.getHandshakeStatus();
+- if ( result.getStatus() == SSLEngineResult.Status.OK &&
+- result.getHandshakeStatus() == HandshakeStatus.NEED_TASK ) {
+- //execute tasks if we need to
+- initHandshakeStatus = tasks();
+- }
+- //perform another unwrap?
+- cont = result.getStatus() == SSLEngineResult.Status.OK &&
+- initHandshakeStatus == HandshakeStatus.NEED_UNWRAP;
+- }while ( cont );
+- return result;
+- }
+-
+- /**
+- * Sends a SSL close message, will not physically close the connection here.<br>
+- * To close the connection, you could do something like
+- * <pre><code>
+- * close();
+- * while (isOpen() && !myTimeoutFunction()) Thread.sleep(25);
+- * if ( isOpen() ) close(true); //forces a close if you timed out
+- * </code></pre>
+- * @throws IOException if an I/O error occurs
+- * @throws IOException if there is data on the outgoing network buffer and we are unable to flush it
+- * @todo Implement this java.io.Closeable method
+- */
+- public void close() throws IOException {
+- if (closing) return;
+- closing = true;
+- sslEngine.closeOutbound();
+-
+- if (!flush(netOutBuffer)) {
+- throw new IOException("Remaining data in the network buffer, can't send SSL close message, force a close with close(true) instead");
+- }
+- //prep the buffer for the close message
+- netOutBuffer.clear();
+- //perform the close, since we called sslEngine.closeOutbound
+- SSLEngineResult handshake = sslEngine.wrap(getEmptyBuf(), netOutBuffer);
+- //we should be in a close state
+- if (handshake.getStatus() != SSLEngineResult.Status.CLOSED) {
+- throw new IOException("Invalid close state, will not send network data.");
+- }
+- //prepare the buffer for writing
+- netOutBuffer.flip();
+- //if there is data to be written
+- flush(netOutBuffer);
+-
+- //is the channel closed?
+- closed = (!netOutBuffer.hasRemaining() && (handshake.getHandshakeStatus() != HandshakeStatus.NEED_WRAP));
+- }
+-
+- /**
+- * Force a close, can throw an IOException
+- * @param force boolean
+- * @throws IOException
+- */
+- public void close(boolean force) throws IOException {
+- try {
+- close();
+- }finally {
+- if ( force || closed ) {
+- closed = true;
+- sc.socket().close();
+- sc.close();
+- }
+- }
+- }
+-
+- /**
+- * Reads a sequence of bytes from this channel into the given buffer.
+- *
+- * @param dst The buffer into which bytes are to be transferred
+- * @return The number of bytes read, possibly zero, or <tt>-1</tt> if the channel has reached end-of-stream
+- * @throws IOException If some other I/O error occurs
+- * @throws IllegalArgumentException if the destination buffer is different than bufHandler.getReadBuffer()
+- * @todo Implement this java.nio.channels.ReadableByteChannel method
+- */
+- public int read(ByteBuffer dst) throws IOException {
+- //if we want to take advantage of the expand function, make sure we only use the ApplicationBufferHandler's buffers
+- if ( dst != bufHandler.getReadBuffer() ) throw new IllegalArgumentException("You can only read using the application read buffer provided by the handler.");
+- //are we in the middle of closing or closed?
+- if ( closing || closed) return -1;
+- //did we finish our handshake?
+- if (!initHandshakeComplete) throw new IllegalStateException("Handshake incomplete, you must complete handshake before reading data.");
+-
+- //read from the network
+- int netread = sc.read(netInBuffer);
+- //did we reach EOF? if so send EOF up one layer.
+- if (netread == -1) return -1;
+-
+- //the data read
+- int read = 0;
+- //the SSL engine result
+- SSLEngineResult unwrap;
+- do {
+- //prepare the buffer
+- netInBuffer.flip();
+- //unwrap the data
+- unwrap = sslEngine.unwrap(netInBuffer, dst);
+- //compact the buffer
+- netInBuffer.compact();
+-
+- if ( unwrap.getStatus()==Status.OK || unwrap.getStatus()==Status.BUFFER_UNDERFLOW ) {
+- //we did receive some data, add it to our total
+- read += unwrap.bytesProduced();
+- //perform any tasks if needed
+- if (unwrap.getHandshakeStatus() == HandshakeStatus.NEED_TASK) tasks();
+- //if we need more network data, then bail out for now.
+- if ( unwrap.getStatus() == Status.BUFFER_UNDERFLOW ) break;
+- }else if ( unwrap.getStatus()==Status.BUFFER_OVERFLOW && read>0 ) {
+- //buffer overflow can happen, if we have read data, then
+- //empty out the dst buffer before we do another read
+- break;
+- }else {
+- //here we should trap BUFFER_OVERFLOW and call expand on the buffer
+- //for now, throw an exception, as we initialized the buffers
+- //in the constructor
+- throw new IOException("Unable to unwrap data, invalid status: " + unwrap.getStatus());
+- }
+- } while ( (netInBuffer.position() != 0)); //continue to unwrapping as long as the input buffer has stuff
+- return (read);
+- }
+-
+- /**
+- * Writes a sequence of bytes to this channel from the given buffer.
+- *
+- * @param src The buffer from which bytes are to be retrieved
+- * @return The number of bytes written, possibly zero
+- * @throws IOException If some other I/O error occurs
+- * @todo Implement this java.nio.channels.WritableByteChannel method
+- */
+- public int write(ByteBuffer src) throws IOException {
+- if ( src == this.netOutBuffer ) {
+- //we can get here through a recursive call
+- //by using the NioBlockingSelector
+- int written = sc.write(src);
+- return written;
+- } else {
+- //make sure we can handle expand, and that we only use on buffer
+- if ( src != bufHandler.getWriteBuffer() ) throw new IllegalArgumentException("You can only write using the application write buffer provided by the handler.");
+- //are we closing or closed?
+- if ( closing || closed) throw new IOException("Channel is in closing state.");
+-
+- //the number of bytes written
+- int written = 0;
+-
+- if (!flush(netOutBuffer)) {
+- //we haven't emptied out the buffer yet
+- return written;
+- }
+-
+- /*
+- * The data buffer is empty, we can reuse the entire buffer.
+- */
+- netOutBuffer.clear();
+-
+- SSLEngineResult result = sslEngine.wrap(src, netOutBuffer);
+- written = result.bytesConsumed();
+- netOutBuffer.flip();
+-
+- if (result.getStatus() == Status.OK) {
+- if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) tasks();
+- } else {
+- throw new IOException("Unable to wrap data, invalid engine state: " +result.getStatus());
+- }
+-
+- //force a flush
+- flush(netOutBuffer);
+- return written;
+- }
+- }
+-
+- /**
+- * Callback interface to be able to expand buffers
+- * when buffer overflow exceptions happen
+- */
+- public static interface ApplicationBufferHandler {
+- public ByteBuffer expand(ByteBuffer buffer, int remaining);
+- public ByteBuffer getReadBuffer();
+- public ByteBuffer getWriteBuffer();
+- }
+-
+- public ApplicationBufferHandler getBufHandler() {
+- return bufHandler;
+- }
+-
+- public boolean isInitHandshakeComplete() {
+- return initHandshakeComplete;
+- }
+-
+- public boolean isClosing() {
+- return closing;
+- }
+-
+- public SSLEngine getSslEngine() {
+- return sslEngine;
+- }
+-
+- public ByteBuffer getEmptyBuf() {
+- return emptyBuf;
+- }
+-
+- public void setBufHandler(ApplicationBufferHandler bufHandler) {
+- this.bufHandler = bufHandler;
+- }
+-
+- public SocketChannel getIOChannel() {
+- return sc;
+- }
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/tomcat/util/net/NioEndpoint.java
+===================================================================
+--- java/org/apache/tomcat/util/net/NioEndpoint.java (revision 590752)
++++ java/org/apache/tomcat/util/net/NioEndpoint.java (working copy)
+@@ -1,2197 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.tomcat.util.net;
+-
+-import java.io.File;
+-import java.io.FileInputStream;
+-import java.io.IOException;
+-import java.net.InetAddress;
+-import java.net.InetSocketAddress;
+-import java.net.Socket;
+-import java.nio.ByteBuffer;
+-import java.nio.channels.CancelledKeyException;
+-import java.nio.channels.FileChannel;
+-import java.nio.channels.SelectionKey;
+-import java.nio.channels.Selector;
+-import java.nio.channels.ServerSocketChannel;
+-import java.nio.channels.SocketChannel;
+-import java.security.KeyStore;
+-import java.util.Collection;
+-import java.util.Iterator;
+-import java.util.Set;
+-import java.util.StringTokenizer;
+-import java.util.concurrent.ConcurrentLinkedQueue;
+-import java.util.concurrent.CountDownLatch;
+-import java.util.concurrent.Executor;
+-import java.util.concurrent.LinkedBlockingQueue;
+-import java.util.concurrent.ThreadFactory;
+-import java.util.concurrent.ThreadPoolExecutor;
+-import java.util.concurrent.TimeUnit;
+-import java.util.concurrent.atomic.AtomicInteger;
+-import java.util.concurrent.atomic.AtomicLong;
+-import javax.net.ssl.KeyManagerFactory;
+-import javax.net.ssl.SSLContext;
+-import javax.net.ssl.SSLEngine;
+-import javax.net.ssl.TrustManagerFactory;
+-
+-import org.apache.juli.logging.Log;
+-import org.apache.juli.logging.LogFactory;
+-import org.apache.tomcat.util.IntrospectionUtils;
+-import org.apache.tomcat.util.net.SecureNioChannel.ApplicationBufferHandler;
+-import org.apache.tomcat.util.res.StringManager;
+-
+-/**
+- * NIO tailored thread pool, providing the following services:
+- * <ul>
+- * <li>Socket acceptor thread</li>
+- * <li>Socket poller thread</li>
+- * <li>Worker threads pool</li>
+- * </ul>
+- *
+- * When switching to Java 5, there's an opportunity to use the virtual
+- * machine's thread pool.
+- *
+- * @author Mladen Turk
+- * @author Remy Maucherat
+- * @author Filip Hanik
+- */
+-public class NioEndpoint {
+-
+-
+- // -------------------------------------------------------------- Constants
+-
+-
+- protected static Log log = LogFactory.getLog(NioEndpoint.class);
+-
+- protected static StringManager sm =
+- StringManager.getManager("org.apache.tomcat.util.net.res");
+-
+-
+- /**
+- * The Request attribute key for the cipher suite.
+- */
+- public static final String CIPHER_SUITE_KEY = "javax.servlet.request.cipher_suite";
+-
+- /**
+- * The Request attribute key for the key size.
+- */
+- public static final String KEY_SIZE_KEY = "javax.servlet.request.key_size";
+-
+- /**
+- * The Request attribute key for the client certificate chain.
+- */
+- public static final String CERTIFICATE_KEY = "javax.servlet.request.X509Certificate";
+-
+- /**
+- * The Request attribute key for the session id.
+- * This one is a Tomcat extension to the Servlet spec.
+- */
+- public static final String SESSION_ID_KEY = "javax.servlet.request.ssl_session";
+-
+- public static final int OP_REGISTER = -1; //register interest op
+-
+- // ----------------------------------------------------------------- Fields
+-
+-
+- /**
+- * Available workers.
+- */
+- protected WorkerStack workers = null;
+-
+-
+- /**
+- * Running state of the endpoint.
+- */
+- protected volatile boolean running = false;
+-
+-
+- /**
+- * Will be set to true whenever the endpoint is paused.
+- */
+- protected volatile boolean paused = false;
+-
+-
+- /**
+- * Track the initialization state of the endpoint.
+- */
+- protected boolean initialized = false;
+-
+-
+- /**
+- * Current worker threads busy count.
+- */
+- protected int curThreadsBusy = 0;
+-
+-
+- /**
+- * Current worker threads count.
+- */
+- protected int curThreads = 0;
+-
+-
+- /**
+- * Sequence number used to generate thread names.
+- */
+- protected int sequence = 0;
+-
+- protected NioSelectorPool selectorPool = new NioSelectorPool();
+-
+- /**
+- * Server socket "pointer".
+- */
+- protected ServerSocketChannel serverSock = null;
+-
+- /**
+- * use send file
+- */
+- protected boolean useSendfile = true;
+-
+- /**
+- * The size of the OOM parachute.
+- */
+- protected int oomParachute = 1024*1024;
+- /**
+- * The oom parachute, when an OOM error happens,
+- * will release the data, giving the JVM instantly
+- * a chunk of data to be able to recover with.
+- */
+- protected byte[] oomParachuteData = null;
+-
+- /**
+- * Make sure this string has already been allocated
+- */
+- protected static final String oomParachuteMsg =
+- "SEVERE:Memory usage is low, parachute is non existent, your system may start failing.";
+-
+- /**
+- * Keep track of OOM warning messages.
+- */
+- long lastParachuteCheck = System.currentTimeMillis();
+-
+-
+-
+-
+-
+- /**
+- * Cache for SocketProcessor objects
+- */
+- protected ConcurrentLinkedQueue<SocketProcessor> processorCache = new ConcurrentLinkedQueue<SocketProcessor>() {
+- protected AtomicInteger size = new AtomicInteger(0);
+- public boolean offer(SocketProcessor sc) {
+- sc.reset(null,null);
+- boolean offer = socketProperties.getProcessorCache()==-1?true:size.get()<socketProperties.getProcessorCache();
+- //avoid over growing our cache or add after we have stopped
+- if ( running && (!paused) && (offer) ) {
+- boolean result = super.offer(sc);
+- if ( result ) {
+- size.incrementAndGet();
+- }
+- return result;
+- }
+- else return false;
+- }
+-
+- public SocketProcessor poll() {
+- SocketProcessor result = super.poll();
+- if ( result != null ) {
+- size.decrementAndGet();
+- }
+- return result;
+- }
+-
+- public void clear() {
+- super.clear();
+- size.set(0);
+- }
+- };
+-
+-
+- /**
+- * Cache for key attachment objects
+- */
+- protected ConcurrentLinkedQueue<KeyAttachment> keyCache = new ConcurrentLinkedQueue<KeyAttachment>() {
+- protected AtomicInteger size = new AtomicInteger(0);
+- public boolean offer(KeyAttachment ka) {
+- ka.reset();
+- boolean offer = socketProperties.getKeyCache()==-1?true:size.get()<socketProperties.getKeyCache();
+- //avoid over growing our cache or add after we have stopped
+- if ( running && (!paused) && (offer) ) {
+- boolean result = super.offer(ka);
+- if ( result ) {
+- size.incrementAndGet();
+- }
+- return result;
+- }
+- else return false;
+- }
+-
+- public KeyAttachment poll() {
+- KeyAttachment result = super.poll();
+- if ( result != null ) {
+- size.decrementAndGet();
+- }
+- return result;
+- }
+-
+- public void clear() {
+- super.clear();
+- size.set(0);
+- }
+- };
+-
+-
+- /**
+- * Cache for poller events
+- */
+- protected ConcurrentLinkedQueue<PollerEvent> eventCache = new ConcurrentLinkedQueue<PollerEvent>() {
+- protected AtomicInteger size = new AtomicInteger(0);
+- public boolean offer(PollerEvent pe) {
+- pe.reset();
+- boolean offer = socketProperties.getEventCache()==-1?true:size.get()<socketProperties.getEventCache();
+- //avoid over growing our cache or add after we have stopped
+- if ( running && (!paused) && (offer) ) {
+- boolean result = super.offer(pe);
+- if ( result ) {
+- size.incrementAndGet();
+- }
+- return result;
+- }
+- else return false;
+- }
+-
+- public PollerEvent poll() {
+- PollerEvent result = super.poll();
+- if ( result != null ) {
+- size.decrementAndGet();
+- }
+- return result;
+- }
+-
+- public void clear() {
+- super.clear();
+- size.set(0);
+- }
+- };
+-
+-
+- /**
+- * Bytebuffer cache, each channel holds a set of buffers (two, except for SSL holds four)
+- */
+- protected ConcurrentLinkedQueue<NioChannel> nioChannels = new ConcurrentLinkedQueue<NioChannel>() {
+- protected AtomicInteger size = new AtomicInteger(0);
+- protected AtomicInteger bytes = new AtomicInteger(0);
+- public boolean offer(NioChannel socket) {
+- boolean offer = socketProperties.getBufferPool()==-1?true:size.get()<socketProperties.getBufferPool();
+- offer = offer && (socketProperties.getBufferPoolSize()==-1?true:(bytes.get()+socket.getBufferSize())<socketProperties.getBufferPoolSize());
+- //avoid over growing our cache or add after we have stopped
+- if ( running && (!paused) && (offer) ) {
+- boolean result = super.offer(socket);
+- if ( result ) {
+- size.incrementAndGet();
+- bytes.addAndGet(socket.getBufferSize());
+- }
+- return result;
+- }
+- else return false;
+- }
+-
+- public NioChannel poll() {
+- NioChannel result = super.poll();
+- if ( result != null ) {
+- size.decrementAndGet();
+- bytes.addAndGet(-result.getBufferSize());
+- }
+- return result;
+- }
+-
+- public void clear() {
+- super.clear();
+- size.set(0);
+- bytes.set(0);
+- }
+- };
+-
+-
+-
+- // ------------------------------------------------------------- Properties
+-
+-
+- /**
+- * External Executor based thread pool.
+- */
+- protected Executor executor = null;
+- public void setExecutor(Executor executor) { this.executor = executor; }
+- public Executor getExecutor() { return executor; }
+-
+- protected boolean useExecutor = true;
+- public void setUseExecutor(boolean useexec) { useExecutor = useexec;}
+- public boolean getUseExecutor() { return useExecutor || (executor!=null);}
+-
+- /**
+- * Maximum amount of worker threads.
+- */
+- protected int maxThreads = 400;
+- public void setMaxThreads(int maxThreads) { this.maxThreads = maxThreads; }
+- public int getMaxThreads() { return maxThreads; }
+-
+-
+- /**
+- * Priority of the worker threads.
+- */
+- protected int threadPriority = Thread.NORM_PRIORITY;
+- public void setThreadPriority(int threadPriority) { this.threadPriority = threadPriority; }
+- public int getThreadPriority() { return threadPriority; }
+-
+- /**
+- * Priority of the acceptor threads.
+- */
+- protected int acceptorThreadPriority = Thread.NORM_PRIORITY;
+- public void setAcceptorThreadPriority(int acceptorThreadPriority) { this.acceptorThreadPriority = acceptorThreadPriority; }
+- public int getAcceptorThreadPriority() { return acceptorThreadPriority; }
+-
+- /**
+- * Priority of the poller threads.
+- */
+- protected int pollerThreadPriority = Thread.NORM_PRIORITY;
+- public void setPollerThreadPriority(int pollerThreadPriority) { this.pollerThreadPriority = pollerThreadPriority; }
+- public int getPollerThreadPriority() { return pollerThreadPriority; }
+-
+- /**
+- * Server socket port.
+- */
+- protected int port;
+- public int getPort() { return port; }
+- public void setPort(int port ) { this.port=port; }
+-
+-
+- /**
+- * Address for the server socket.
+- */
+- protected InetAddress address;
+- public InetAddress getAddress() { return address; }
+- public void setAddress(InetAddress address) { this.address = address; }
+-
+-
+- /**
+- * Handling of accepted sockets.
+- */
+- protected Handler handler = null;
+- public void setHandler(Handler handler ) { this.handler = handler; }
+- public Handler getHandler() { return handler; }
+-
+-
+- /**
+- * Allows the server developer to specify the backlog that
+- * should be used for server sockets. By default, this value
+- * is 100.
+- */
+- protected int backlog = 100;
+- public void setBacklog(int backlog) { if (backlog > 0) this.backlog = backlog; }
+- public int getBacklog() { return backlog; }
+-
+- protected SocketProperties socketProperties = new SocketProperties();
+-
+- /**
+- * Socket TCP no delay.
+- */
+- public boolean getTcpNoDelay() { return socketProperties.getTcpNoDelay();}
+- public void setTcpNoDelay(boolean tcpNoDelay) { socketProperties.setTcpNoDelay(tcpNoDelay); }
+-
+-
+- /**
+- * Socket linger.
+- */
+- public int getSoLinger() { return socketProperties.getSoLingerTime(); }
+- public void setSoLinger(int soLinger) {
+- socketProperties.setSoLingerTime(soLinger);
+- socketProperties.setSoLingerOn(soLinger>=0);
+- }
+-
+-
+- /**
+- * Socket timeout.
+- */
+- public int getSoTimeout() { return socketProperties.getSoTimeout(); }
+- public void setSoTimeout(int soTimeout) { socketProperties.setSoTimeout(soTimeout); }
+-
+- /**
+- * The default is true - the created threads will be
+- * in daemon mode. If set to false, the control thread
+- * will not be daemon - and will keep the process alive.
+- */
+- protected boolean daemon = true;
+- public void setDaemon(boolean b) { daemon = b; }
+- public boolean getDaemon() { return daemon; }
+-
+-
+- /**
+- * Name of the thread pool, which will be used for naming child threads.
+- */
+- protected String name = "TP";
+- public void setName(String name) { this.name = name; }
+- public String getName() { return name; }
+-
+-
+-
+- /**
+- * Allow comet request handling.
+- */
+- protected boolean useComet = true;
+- public void setUseComet(boolean useComet) { this.useComet = useComet; }
+- public boolean getUseComet() { return useComet; }
+-
+-
+- /**
+- * Acceptor thread count.
+- */
+- protected int acceptorThreadCount = 1;
+- public void setAcceptorThreadCount(int acceptorThreadCount) { this.acceptorThreadCount = acceptorThreadCount; }
+- public int getAcceptorThreadCount() { return acceptorThreadCount; }
+-
+-
+-
+- /**
+- * Poller thread count.
+- */
+- protected int pollerThreadCount = 1;
+- public void setPollerThreadCount(int pollerThreadCount) { this.pollerThreadCount = pollerThreadCount; }
+- public int getPollerThreadCount() { return pollerThreadCount; }
+-
+- protected long selectorTimeout = 1000;
+- public void setSelectorTimeout(long timeout){ this.selectorTimeout = timeout;}
+- public long getSelectorTimeout(){ return this.selectorTimeout; }
+- /**
+- * The socket poller.
+- */
+- protected Poller[] pollers = null;
+- protected int pollerRoundRobin = 0;
+- public Poller getPoller0() {
+- pollerRoundRobin = (pollerRoundRobin + 1) % pollers.length;
+- Poller poller = pollers[pollerRoundRobin];
+- return poller;
+- }
+-
+-
+- /**
+- * The socket poller used for Comet support.
+- */
+- public Poller getCometPoller0() {
+- Poller poller = getPoller0();
+- return poller;
+- }
+-
+-
+- /**
+- * Dummy maxSpareThreads property.
+- */
+- public int getMaxSpareThreads() { return Math.min(getMaxThreads(),5); }
+-
+-
+- /**
+- * Dummy minSpareThreads property.
+- */
+- public int getMinSpareThreads() { return Math.min(getMaxThreads(),5); }
+-
+- /**
+- * Generic properties, introspected
+- */
+- public boolean setProperty(String name, String value) {
+- final String selectorPoolName = "selectorPool.";
+- final String socketName = "socket.";
+- try {
+- if (name.startsWith(selectorPoolName)) {
+- return IntrospectionUtils.setProperty(selectorPool, name.substring(selectorPoolName.length()), value);
+- } else if (name.startsWith(socketName)) {
+- return IntrospectionUtils.setProperty(socketProperties, name.substring(socketName.length()), value);
+- } else {
+- return IntrospectionUtils.setProperty(this,name,value);
+- }
+- }catch ( Exception x ) {
+- log.error("Unable to set attribute \""+name+"\" to \""+value+"\"",x);
+- return false;
+- }
+- }
+-
+-
+- public String adjustRelativePath(String path, String relativeTo) {
+- File f = new File(path);
+- if ( !f.isAbsolute()) {
+- path = relativeTo + File.separator + path;
+- f = new File(path);
+- }
+- if (!f.exists()) {
+- log.warn("configured file:["+path+"] does not exist.");
+- }
+- return path;
+- }
+-
+- public String defaultIfNull(String val, String defaultValue) {
+- if (val==null) return defaultValue;
+- else return val;
+- }
+- // -------------------- SSL related properties --------------------
+- protected String truststoreFile = System.getProperty("javax.net.ssl.trustStore");
+- public void setTruststoreFile(String s) {
+- s = adjustRelativePath(s,System.getProperty("catalina.base"));
+- this.truststoreFile = s;
+- }
+- public String getTruststoreFile() {return truststoreFile;}
+- protected String truststorePass = System.getProperty("javax.net.ssl.trustStorePassword");
+- public void setTruststorePass(String truststorePass) {this.truststorePass = truststorePass;}
+- public String getTruststorePass() {return truststorePass;}
+- protected String truststoreType = System.getProperty("javax.net.ssl.trustStoreType");
+- public void setTruststoreType(String truststoreType) {this.truststoreType = truststoreType;}
+- public String getTruststoreType() {return truststoreType;}
+-
+- protected String keystoreFile = System.getProperty("user.home")+"/.keystore";
+- public String getKeystoreFile() { return keystoreFile;}
+- public void setKeystoreFile(String s ) {
+- s = adjustRelativePath(s,System.getProperty("catalina.base"));
+- this.keystoreFile = s;
+- }
+-
+- protected String algorithm = "SunX509";
+- public String getAlgorithm() { return algorithm;}
+- public void setAlgorithm(String s ) { this.algorithm = s;}
+-
+- protected String clientAuth = "false";
+- public String getClientAuth() { return clientAuth;}
+- public void setClientAuth(String s ) { this.clientAuth = s;}
+-
+- protected String keystorePass = "changeit";
+- public String getKeystorePass() { return keystorePass;}
+- public void setKeystorePass(String s ) { this.keystorePass = s;}
+-
+- protected String keystoreType = "JKS";
+- public String getKeystoreType() { return keystoreType;}
+- public void setKeystoreType(String s ) { this.keystoreType = s;}
+-
+- protected String sslProtocol = "TLS";
+-
+- public String getSslProtocol() { return sslProtocol;}
+- public void setSslProtocol(String s) { sslProtocol = s;}
+-
+- protected String sslEnabledProtocols=null; //"TLSv1,SSLv3,SSLv2Hello"
+- protected String[] sslEnabledProtocolsarr = new String[0];
+- public void setSslEnabledProtocols(String s) {
+- this.sslEnabledProtocols = s;
+- StringTokenizer t = new StringTokenizer(s,",");
+- sslEnabledProtocolsarr = new String[t.countTokens()];
+- for (int i=0; i<sslEnabledProtocolsarr.length; i++ ) sslEnabledProtocolsarr[i] = t.nextToken();
+- }
+-
+-
+- protected String ciphers = null;
+- protected String[] ciphersarr = new String[0];
+- public String getCiphers() { return ciphers;}
+- public void setCiphers(String s) {
+- ciphers = s;
+- if ( s == null ) ciphersarr = new String[0];
+- else {
+- StringTokenizer t = new StringTokenizer(s,",");
+- ciphersarr = new String[t.countTokens()];
+- for (int i=0; i<ciphersarr.length; i++ ) ciphersarr[i] = t.nextToken();
+- }
+- }
+-
+- /**
+- * SSL engine.
+- */
+- protected boolean SSLEnabled = false;
+- public boolean isSSLEnabled() { return SSLEnabled;}
+- public void setSSLEnabled(boolean SSLEnabled) {this.SSLEnabled = SSLEnabled;}
+-
+- protected boolean secure = false;
+- public boolean getSecure() { return secure;}
+- public void setSecure(boolean b) { secure = b;}
+-
+- public void setSelectorPool(NioSelectorPool selectorPool) {
+- this.selectorPool = selectorPool;
+- }
+-
+- public void setSocketProperties(SocketProperties socketProperties) {
+- this.socketProperties = socketProperties;
+- }
+-
+- public void setUseSendfile(boolean useSendfile) {
+-
+- this.useSendfile = useSendfile;
+- }
+-
+- public void setOomParachute(int oomParachute) {
+- this.oomParachute = oomParachute;
+- }
+-
+- public void setOomParachuteData(byte[] oomParachuteData) {
+- this.oomParachuteData = oomParachuteData;
+- }
+-
+- protected SSLContext sslContext = null;
+- public SSLContext getSSLContext() { return sslContext;}
+- public void setSSLContext(SSLContext c) { sslContext = c;}
+-
+- // --------------------------------------------------------- OOM Parachute Methods
+-
+- protected void checkParachute() {
+- boolean para = reclaimParachute(false);
+- if (!para && (System.currentTimeMillis()-lastParachuteCheck)>10000) {
+- try {
+- log.fatal(oomParachuteMsg);
+- }catch (Throwable t) {
+- System.err.println(oomParachuteMsg);
+- }
+- lastParachuteCheck = System.currentTimeMillis();
+- }
+- }
+-
+- protected boolean reclaimParachute(boolean force) {
+- if ( oomParachuteData != null ) return true;
+- if ( oomParachute > 0 && ( force || (Runtime.getRuntime().freeMemory() > (oomParachute*2))) )
+- oomParachuteData = new byte[oomParachute];
+- return oomParachuteData != null;
+- }
+-
+- protected void releaseCaches() {
+- this.keyCache.clear();
+- this.nioChannels.clear();
+- this.processorCache.clear();
+- if ( handler != null ) handler.releaseCaches();
+-
+- }
+-
+- // --------------------------------------------------------- Public Methods
+- /**
+- * Number of keepalive sockets.
+- */
+- public int getKeepAliveCount() {
+- if (pollers == null) {
+- return 0;
+- } else {
+- int keepAliveCount = 0;
+- for (int i = 0; i < pollers.length; i++) {
+- keepAliveCount += pollers[i].getKeepAliveCount();
+- }
+- return keepAliveCount;
+- }
+- }
+-
+-
+-
+- /**
+- * Return the amount of threads that are managed by the pool.
+- *
+- * @return the amount of threads that are managed by the pool
+- */
+- public int getCurrentThreadCount() {
+- return curThreads;
+- }
+-
+-
+- /**
+- * Return the amount of threads currently busy.
+- *
+- * @return the amount of threads currently busy
+- */
+- public int getCurrentThreadsBusy() {
+- return curThreadsBusy;
+- }
+-
+-
+- /**
+- * Return the state of the endpoint.
+- *
+- * @return true if the endpoint is running, false otherwise
+- */
+- public boolean isRunning() {
+- return running;
+- }
+-
+-
+- /**
+- * Return the state of the endpoint.
+- *
+- * @return true if the endpoint is paused, false otherwise
+- */
+- public boolean isPaused() {
+- return paused;
+- }
+-
+-
+- // ----------------------------------------------- Public Lifecycle Methods
+-
+-
+- /**
+- * Initialize the endpoint.
+- */
+- public void init()
+- throws Exception {
+-
+- if (initialized)
+- return;
+-
+- serverSock = ServerSocketChannel.open();
+- serverSock.socket().setPerformancePreferences(socketProperties.getPerformanceConnectionTime(),
+- socketProperties.getPerformanceLatency(),
+- socketProperties.getPerformanceBandwidth());
+- InetSocketAddress addr = (address!=null?new InetSocketAddress(address,port):new InetSocketAddress(port));
+- serverSock.socket().bind(addr,backlog);
+- serverSock.configureBlocking(true); //mimic APR behavior
+-
+- // Initialize thread count defaults for acceptor, poller
+- if (acceptorThreadCount == 0) {
+- // FIXME: Doesn't seem to work that well with multiple accept threads
+- acceptorThreadCount = 1;
+- }
+- if (pollerThreadCount <= 0) {
+- //minimum one poller thread
+- pollerThreadCount = 1;
+- }
+-
+- // Initialize SSL if needed
+- if (isSSLEnabled()) {
+- // Initialize SSL
+- char[] passphrase = getKeystorePass().toCharArray();
+-
+- KeyStore ks = KeyStore.getInstance(getKeystoreType());
+- ks.load(new FileInputStream(getKeystoreFile()), passphrase);
+-
+- KeyManagerFactory kmf = KeyManagerFactory.getInstance(getAlgorithm());
+- kmf.init(ks, passphrase);
+-
+- char[] tpassphrase = (getTruststorePass()!=null)?getTruststorePass().toCharArray():passphrase;
+- String ttype = (getTruststoreType()!=null)?getTruststoreType():getKeystoreType();
+-
+- KeyStore ts = null;
+- if (getTruststoreFile()==null) {
+- ts = KeyStore.getInstance(getKeystoreType());
+- ts.load(new FileInputStream(getKeystoreFile()), passphrase);
+- }else {
+- ts = KeyStore.getInstance(ttype);
+- ts.load(new FileInputStream(getTruststoreFile()), tpassphrase);
+- }
+-
+- TrustManagerFactory tmf = TrustManagerFactory.getInstance(getAlgorithm());
+- tmf.init(ts);
+-
+- sslContext = SSLContext.getInstance(getSslProtocol());
+- sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+-
+- }
+-
+- if (oomParachute>0) reclaimParachute(true);
+-
+- initialized = true;
+-
+- }
+-
+-
+- /**
+- * Start the NIO endpoint, creating acceptor, poller threads.
+- */
+- public void start()
+- throws Exception {
+- // Initialize socket if not done before
+- if (!initialized) {
+- init();
+- }
+- if (!running) {
+- running = true;
+- paused = false;
+-
+- // Create worker collection
+- if (getUseExecutor()) {
+- if ( executor == null ) {
+- TaskQueue taskqueue = new TaskQueue();
+- TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-");
+- executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
+- taskqueue.setParent( (ThreadPoolExecutor) executor);
+- }
+- } else if ( executor == null ) {//avoid two thread pools being created
+- workers = new WorkerStack(maxThreads);
+- }
+-
+- // Start acceptor threads
+- for (int i = 0; i < acceptorThreadCount; i++) {
+- Thread acceptorThread = new Thread(new Acceptor(), getName() + "-Acceptor-" + i);
+- acceptorThread.setPriority(threadPriority);
+- acceptorThread.setDaemon(daemon);
+- acceptorThread.start();
+- }
+-
+- // Start poller threads
+- pollers = new Poller[pollerThreadCount];
+- for (int i = 0; i < pollerThreadCount; i++) {
+- pollers[i] = new Poller();
+- pollers[i].init();
+- Thread pollerThread = new Thread(pollers[i], getName() + "-Poller-" + i);
+- pollerThread.setPriority(threadPriority);
+- pollerThread.setDaemon(true);
+- pollerThread.start();
+- }
+- }
+- }
+-
+-
+- /**
+- * Pause the endpoint, which will make it stop accepting new sockets.
+- */
+- public void pause() {
+- if (running && !paused) {
+- paused = true;
+- unlockAccept();
+- }
+- }
+-
+-
+- /**
+- * Resume the endpoint, which will make it start accepting new sockets
+- * again.
+- */
+- public void resume() {
+- if (running) {
+- paused = false;
+- }
+- }
+-
+-
+- /**
+- * Stop the endpoint. This will cause all processing threads to stop.
+- */
+- public void stop() {
+- if (running) {
+- running = false;
+- unlockAccept();
+- for (int i = 0; i < pollers.length; i++) {
+- pollers[i].destroy();
+- }
+- pollers = null;
+- }
+- eventCache.clear();
+- keyCache.clear();
+- nioChannels.clear();
+- processorCache.clear();
+- if ( executor!=null ) {
+- if ( executor instanceof ThreadPoolExecutor ) {
+- //this is our internal one, so we need to shut it down
+- ThreadPoolExecutor tpe = (ThreadPoolExecutor) executor;
+- tpe.shutdown();
+- TaskQueue queue = (TaskQueue) tpe.getQueue();
+- queue.setParent(null);
+- }
+- executor = null;
+- }
+- }
+-
+-
+- /**
+- * Deallocate NIO memory pools, and close server socket.
+- */
+- public void destroy() throws Exception {
+- if (running) {
+- stop();
+- }
+- // Close server socket
+- serverSock.socket().close();
+- serverSock.close();
+- serverSock = null;
+- sslContext = null;
+- initialized = false;
+- releaseCaches();
+- }
+-
+-
+- // ------------------------------------------------------ Protected Methods
+-
+-
+- /**
+- * Get a sequence number used for thread naming.
+- */
+- protected int getSequence() {
+- return sequence++;
+- }
+-
+- public int getWriteBufSize() {
+- return socketProperties.getTxBufSize();
+- }
+-
+- public int getReadBufSize() {
+- return socketProperties.getRxBufSize();
+- }
+-
+- public NioSelectorPool getSelectorPool() {
+- return selectorPool;
+- }
+-
+- public SocketProperties getSocketProperties() {
+- return socketProperties;
+- }
+-
+- public boolean getUseSendfile() {
+- //send file doesn't work with SSL
+- return useSendfile && (!isSSLEnabled());
+- }
+-
+- public int getOomParachute() {
+- return oomParachute;
+- }
+-
+- public byte[] getOomParachuteData() {
+- return oomParachuteData;
+- }
+-
+- /**
+- * Unlock the server socket accept using a bogus connection.
+- */
+- protected void unlockAccept() {
+- java.net.Socket s = null;
+- try {
+- // Need to create a connection to unlock the accept();
+- if (address == null) {
+- s = new java.net.Socket("127.0.0.1", port);
+- } else {
+- s = new java.net.Socket(address, port);
+- // setting soLinger to a small value will help shutdown the
+- // connection quicker
+- s.setSoLinger(true, 0);
+- }
+- } catch(Exception e) {
+- if (log.isDebugEnabled()) {
+- log.debug(sm.getString("endpoint.debug.unlock", "" + port), e);
+- }
+- } finally {
+- if (s != null) {
+- try {
+- s.close();
+- } catch (Exception e) {
+- // Ignore
+- }
+- }
+- }
+- }
+-
+-
+- /**
+- * Process the specified connection.
+- */
+- protected boolean setSocketOptions(SocketChannel socket) {
+- // Process the connection
+- try {
+- //disable blocking, APR style, we are gonna be polling it
+- socket.configureBlocking(false);
+- Socket sock = socket.socket();
+- socketProperties.setProperties(sock);
+-
+- NioChannel channel = nioChannels.poll();
+- if ( channel == null ) {
+- // SSL setup
+- if (sslContext != null) {
+- SSLEngine engine = createSSLEngine();
+- int appbufsize = engine.getSession().getApplicationBufferSize();
+- NioBufferHandler bufhandler = new NioBufferHandler(Math.max(appbufsize,socketProperties.getAppReadBufSize()),
+- Math.max(appbufsize,socketProperties.getAppWriteBufSize()),
+- socketProperties.getDirectBuffer());
+- channel = new SecureNioChannel(socket, engine, bufhandler, selectorPool);
+- } else {
+- // normal tcp setup
+- NioBufferHandler bufhandler = new NioBufferHandler(socketProperties.getAppReadBufSize(),
+- socketProperties.getAppWriteBufSize(),
+- socketProperties.getDirectBuffer());
+-
+- channel = new NioChannel(socket, bufhandler);
+- }
+- } else {
+- channel.setIOChannel(socket);
+- if ( channel instanceof SecureNioChannel ) {
+- SSLEngine engine = createSSLEngine();
+- ((SecureNioChannel)channel).reset(engine);
+- } else {
+- channel.reset();
+- }
+- }
+- getPoller0().register(channel);
+- } catch (Throwable t) {
+- try {
+- log.error("",t);
+- }catch ( Throwable tt){}
+- // Tell to close the socket
+- return false;
+- }
+- return true;
+- }
+-
+- protected SSLEngine createSSLEngine() {
+- SSLEngine engine = sslContext.createSSLEngine();
+- if ("false".equals(getClientAuth())) {
+- engine.setNeedClientAuth(false);
+- engine.setWantClientAuth(false);
+- } else if ("true".equals(getClientAuth()) || "yes".equals(getClientAuth())){
+- engine.setNeedClientAuth(true);
+- } else if ("want".equals(getClientAuth())) {
+- engine.setWantClientAuth(true);
+- }
+- engine.setUseClientMode(false);
+- if ( ciphersarr.length > 0 ) engine.setEnabledCipherSuites(ciphersarr);
+- if ( sslEnabledProtocolsarr.length > 0 ) engine.setEnabledProtocols(sslEnabledProtocolsarr);
+-
+- return engine;
+- }
+-
+-
+- /**
+- * Returns true if a worker thread is available for processing.
+- * @return boolean
+- */
+- protected boolean isWorkerAvailable() {
+- if ( executor != null ) {
+- return true;
+- } else {
+- if (workers.size() > 0) {
+- return true;
+- }
+- if ( (maxThreads > 0) && (curThreads < maxThreads)) {
+- return true;
+- } else {
+- if (maxThreads < 0) {
+- return true;
+- } else {
+- return false;
+- }
+- }
+- }
+- }
+- /**
+- * Create (or allocate) and return an available processor for use in
+- * processing a specific HTTP request, if possible. If the maximum
+- * allowed processors have already been created and are in use, return
+- * <code>null</code> instead.
+- */
+- protected Worker createWorkerThread() {
+-
+- synchronized (workers) {
+- if (workers.size() > 0) {
+- curThreadsBusy++;
+- return (workers.pop());
+- }
+- if ((maxThreads > 0) && (curThreads < maxThreads)) {
+- curThreadsBusy++;
+- return (newWorkerThread());
+- } else {
+- if (maxThreads < 0) {
+- curThreadsBusy++;
+- return (newWorkerThread());
+- } else {
+- return (null);
+- }
+- }
+- }
+- }
+-
+-
+- /**
+- * Create and return a new processor suitable for processing HTTP
+- * requests and returning the corresponding responses.
+- */
+- protected Worker newWorkerThread() {
+-
+- Worker workerThread = new Worker();
+- workerThread.start();
+- return (workerThread);
+-
+- }
+-
+-
+- /**
+- * Return a new worker thread, and block while to worker is available.
+- */
+- protected Worker getWorkerThread() {
+- // Allocate a new worker thread
+- Worker workerThread = createWorkerThread();
+- while (workerThread == null) {
+- try {
+- synchronized (workers) {
+- workerThread = createWorkerThread();
+- if ( workerThread == null ) workers.wait();
+- }
+- } catch (InterruptedException e) {
+- // Ignore
+- }
+- if ( workerThread == null ) workerThread = createWorkerThread();
+- }
+- return workerThread;
+- }
+-
+-
+- /**
+- * Recycle the specified Processor so that it can be used again.
+- *
+- * @param workerThread The processor to be recycled
+- */
+- protected void recycleWorkerThread(Worker workerThread) {
+- synchronized (workers) {
+- workers.push(workerThread);
+- curThreadsBusy--;
+- workers.notify();
+- }
+- }
+- /**
+- * Process given socket.
+- */
+- protected boolean processSocket(NioChannel socket) {
+- return processSocket(socket,null);
+- }
+-
+-
+- /**
+- * Process given socket for an event.
+- */
+- protected boolean processSocket(NioChannel socket, SocketStatus status) {
+- return processSocket(socket,status,true);
+- }
+-
+- protected boolean processSocket(NioChannel socket, SocketStatus status, boolean dispatch) {
+- try {
+- if (executor == null) {
+- getWorkerThread().assign(socket, status);
+- } else {
+- SocketProcessor sc = processorCache.poll();
+- if ( sc == null ) sc = new SocketProcessor(socket,status);
+- else sc.reset(socket,status);
+- if ( dispatch ) executor.execute(sc);
+- else sc.run();
+- }
+- } catch (Throwable t) {
+- // This means we got an OOM or similar creating a thread, or that
+- // the pool and its queue are full
+- log.error(sm.getString("endpoint.process.fail"), t);
+- return false;
+- }
+- return true;
+- }
+-
+-
+- // --------------------------------------------------- Acceptor Inner Class
+-
+-
+- /**
+- * Server socket acceptor thread.
+- */
+- protected class Acceptor implements Runnable {
+- /**
+- * The background thread that listens for incoming TCP/IP connections and
+- * hands them off to an appropriate processor.
+- */
+- public void run() {
+- // Loop until we receive a shutdown command
+- while (running) {
+- // Loop if endpoint is paused
+- while (paused) {
+- try {
+- Thread.sleep(1000);
+- } catch (InterruptedException e) {
+- // Ignore
+- }
+- }
+- try {
+- // Accept the next incoming connection from the server socket
+- SocketChannel socket = serverSock.accept();
+- // Hand this socket off to an appropriate processor
+- //TODO FIXME - this is currently a blocking call, meaning we will be blocking
+- //further accepts until there is a thread available.
+- if ( running && (!paused) && socket != null ) {
+- //processSocket(socket);
+- if (!setSocketOptions(socket)) {
+- try {
+- socket.socket().close();
+- socket.close();
+- } catch (IOException ix) {
+- if (log.isDebugEnabled())
+- log.debug("", ix);
+- }
+- }
+- }
+- }catch ( IOException x ) {
+- if ( running ) log.error(sm.getString("endpoint.accept.fail"), x);
+- } catch (OutOfMemoryError oom) {
+- try {
+- oomParachuteData = null;
+- releaseCaches();
+- log.error("", oom);
+- }catch ( Throwable oomt ) {
+- try {
+- try {
+- System.err.println(oomParachuteMsg);
+- oomt.printStackTrace();
+- }catch (Throwable letsHopeWeDontGetHere){}
+- }catch (Throwable letsHopeWeDontGetHere){}
+- }
+- } catch (Throwable t) {
+- log.error(sm.getString("endpoint.accept.fail"), t);
+- }
+- }//while
+- }//run
+- }
+-
+-
+- // ----------------------------------------------------- Poller Inner Classes
+-
+- /**
+- *
+- * PollerEvent, cacheable object for poller events to avoid GC
+- */
+- public class PollerEvent implements Runnable {
+-
+- protected NioChannel socket;
+- protected int interestOps;
+- protected KeyAttachment key;
+- public PollerEvent(NioChannel ch, KeyAttachment k, int intOps) {
+- reset(ch, k, intOps);
+- }
+-
+- public void reset(NioChannel ch, KeyAttachment k, int intOps) {
+- socket = ch;
+- interestOps = intOps;
+- key = k;
+- }
+-
+- public void reset() {
+- reset(null, null, 0);
+- }
+-
+- public void run() {
+- if ( interestOps == OP_REGISTER ) {
+- try {
+- socket.getIOChannel().register(socket.getPoller().getSelector(), SelectionKey.OP_READ, key);
+- } catch (Exception x) {
+- log.error("", x);
+- }
+- } else {
+- final SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
+- try {
+- boolean cancel = false;
+- if (key != null) {
+- final KeyAttachment att = (KeyAttachment) key.attachment();
+- if ( att!=null ) {
+- att.access();//to prevent timeout
+- //we are registering the key to start with, reset the fairness counter.
+- att.setFairness(0);
+- att.interestOps(interestOps);
+- key.interestOps(interestOps);
+- } else {
+- cancel = true;
+- }
+- } else {
+- cancel = true;
+- }
+- if ( cancel ) getPoller0().cancelledKey(key,SocketStatus.ERROR,false);
+- }catch (CancelledKeyException ckx) {
+- try {
+- getPoller0().cancelledKey(key,SocketStatus.DISCONNECT,true);
+- }catch (Exception ignore) {}
+- }
+- }//end if
+- }//run
+-
+- public String toString() {
+- return super.toString()+"[intOps="+this.interestOps+"]";
+- }
+- }
+- /**
+- * Poller class.
+- */
+- public class Poller implements Runnable {
+-
+- protected Selector selector;
+- protected ConcurrentLinkedQueue<Runnable> events = new ConcurrentLinkedQueue<Runnable>();
+-
+- protected boolean close = false;
+- protected long nextExpiration = 0;//optimize expiration handling
+-
+- protected int keepAliveCount = 0;
+- public int getKeepAliveCount() { return keepAliveCount; }
+-
+- protected AtomicLong wakeupCounter = new AtomicLong(0l);
+-
+- protected CountDownLatch stopLatch = new CountDownLatch(1);
+-
+-
+-
+- public Poller() throws IOException {
+- this.selector = Selector.open();
+- }
+-
+- public Selector getSelector() { return selector;}
+-
+- /**
+- * Create the poller. With some versions of APR, the maximum poller size will
+- * be 62 (reocmpiling APR is necessary to remove this limitation).
+- */
+- protected void init() {
+- keepAliveCount = 0;
+- }
+-
+- /**
+- * Destroy the poller.
+- */
+- protected void destroy() {
+- // Wait for polltime before doing anything, so that the poller threads
+- // exit, otherwise parallel descturction of sockets which are still
+- // in the poller can cause problems
+- close = true;
+- events.clear();
+- selector.wakeup();
+- try { stopLatch.await(5,TimeUnit.SECONDS); } catch (InterruptedException ignore ) {}
+- }
+-
+- public void addEvent(Runnable event) {
+- events.offer(event);
+- if ( wakeupCounter.incrementAndGet() < 3 ) selector.wakeup();
+- }
+-
+- /**
+- * Add specified socket and associated pool to the poller. The socket will
+- * be added to a temporary array, and polled first after a maximum amount
+- * of time equal to pollTime (in most cases, latency will be much lower,
+- * however).
+- *
+- * @param socket to add to the poller
+- */
+- public void add(final NioChannel socket) {
+- add(socket,SelectionKey.OP_READ);
+- }
+-
+- public void add(final NioChannel socket, final int interestOps) {
+- PollerEvent r = eventCache.poll();
+- if ( r==null) r = new PollerEvent(socket,null,interestOps);
+- else r.reset(socket,null,interestOps);
+- addEvent(r);
+- }
+-
+- public boolean events() {
+- boolean result = false;
+- //synchronized (events) {
+- Runnable r = null;
+- result = (events.size() > 0);
+- while ( (r = (Runnable)events.poll()) != null ) {
+- try {
+- r.run();
+- if ( r instanceof PollerEvent ) {
+- ((PollerEvent)r).reset();
+- eventCache.offer((PollerEvent)r);
+- }
+- } catch ( Throwable x ) {
+- log.error("",x);
+- }
+- }
+- //events.clear();
+- //}
+- return result;
+- }
+-
+- public void register(final NioChannel socket)
+- {
+- socket.setPoller(this);
+- KeyAttachment key = keyCache.poll();
+- final KeyAttachment ka = key!=null?key:new KeyAttachment();
+- ka.reset(this,socket);
+- PollerEvent r = eventCache.poll();
+- ka.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.
+- if ( r==null) r = new PollerEvent(socket,ka,OP_REGISTER);
+- else r.reset(socket,ka,OP_REGISTER);
+- addEvent(r);
+- }
+- public void cancelledKey(SelectionKey key, SocketStatus status, boolean dispatch) {
+- try {
+- if ( key == null ) return;//nothing to do
+- KeyAttachment ka = (KeyAttachment) key.attachment();
+- if (ka != null && ka.getComet() && status != null) {
+- //the comet event takes care of clean up
+- //processSocket(ka.getChannel(), status, dispatch);
+- ka.setComet(false);//to avoid a loop
+- processSocket(ka.getChannel(), status, false);//don't dispatch if the lines below are cancelling the key
+- if (status == SocketStatus.TIMEOUT ) return; // don't close on comet timeout
+- }
+- if (key.isValid()) key.cancel();
+- if (key.channel().isOpen()) try {key.channel().close();}catch (Exception ignore){}
+- try {ka.channel.close(true);}catch (Exception ignore){}
+- key.attach(null);
+- } catch (Throwable e) {
+- if ( log.isDebugEnabled() ) log.error("",e);
+- // Ignore
+- }
+- }
+- /**
+- * The background thread that listens for incoming TCP/IP connections and
+- * hands them off to an appropriate processor.
+- */
+- public void run() {
+- // Loop until we receive a shutdown command
+- while (running) {
+- try {
+- // Loop if endpoint is paused
+- while (paused && (!close) ) {
+- try {
+- Thread.sleep(500);
+- } catch (InterruptedException e) {
+- // Ignore
+- }
+- }
+- boolean hasEvents = false;
+-
+- hasEvents = (hasEvents | events());
+- // Time to terminate?
+- if (close) {
+- timeout(0, false);
+- stopLatch.countDown();
+- return;
+- }
+- int keyCount = 0;
+- try {
+- if ( !close ) {
+- keyCount = selector.select(selectorTimeout);
+- wakeupCounter.set(0);
+- }
+- if (close) {
+- timeout(0, false);
+- stopLatch.countDown();
+- selector.close();
+- return;
+- }
+- } catch ( NullPointerException x ) {
+- //sun bug 5076772 on windows JDK 1.5
+- if ( wakeupCounter == null || selector == null ) throw x;
+- continue;
+- } catch ( CancelledKeyException x ) {
+- //sun bug 5076772 on windows JDK 1.5
+- if ( wakeupCounter == null || selector == null ) throw x;
+- continue;
+- } catch (Throwable x) {
+- log.error("",x);
+- continue;
+- }
+- //either we timed out or we woke up, process events first
+- if ( keyCount == 0 ) hasEvents = (hasEvents | events());
+-
+- Iterator iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null;
+- // Walk through the collection of ready keys and dispatch
+- // any active event.
+- while (iterator != null && iterator.hasNext()) {
+- SelectionKey sk = (SelectionKey) iterator.next();
+- KeyAttachment attachment = (KeyAttachment)sk.attachment();
+- iterator.remove();
+- processKey(sk, attachment);
+- }//while
+-
+- //process timeouts
+- timeout(keyCount,hasEvents);
+- if ( oomParachute > 0 && oomParachuteData == null ) checkParachute();
+- } catch (OutOfMemoryError oom) {
+- try {
+- oomParachuteData = null;
+- releaseCaches();
+- log.error("", oom);
+- }catch ( Throwable oomt ) {
+- try {
+- System.err.println(oomParachuteMsg);
+- oomt.printStackTrace();
+- }catch (Throwable letsHopeWeDontGetHere){}
+- }
+- }
+- }//while
+- synchronized (this) {
+- this.notifyAll();
+- }
+- stopLatch.countDown();
+-
+- }
+-
+- protected boolean processKey(SelectionKey sk, KeyAttachment attachment) {
+- boolean result = true;
+- try {
+- if ( close ) {
+- cancelledKey(sk, SocketStatus.STOP, false);
+- } else if ( sk.isValid() && attachment != null ) {
+- attachment.access();//make sure we don't time out valid sockets
+- sk.attach(attachment);//cant remember why this is here
+- NioChannel channel = attachment.getChannel();
+- if (sk.isReadable() || sk.isWritable() ) {
+- if ( sk.isReadable() && attachment.getReadLatch() != null ) {
+- unreg(sk, attachment,SelectionKey.OP_READ);
+- attachment.getReadLatch().countDown();
+- } else if ( sk.isWritable() && attachment.getWriteLatch() != null ) {
+- unreg(sk, attachment,SelectionKey.OP_WRITE);
+- attachment.getWriteLatch().countDown();
+- } else if ( attachment.getSendfileData() != null ) {
+- processSendfile(sk,attachment,true);
+- } else if ( attachment.getComet() ) {
+- //check if thread is available
+- if ( isWorkerAvailable() ) {
+- unreg(sk, attachment, sk.readyOps());
+- if (!processSocket(channel, SocketStatus.OPEN))
+- processSocket(channel, SocketStatus.DISCONNECT);
+- attachment.setFairness(0);
+- } else {
+- //increase the fairness counter
+- attachment.incFairness();
+- result = false;
+- }
+- } else {
+- //later on, improve latch behavior
+- if ( isWorkerAvailable() ) {
+- unreg(sk, attachment,sk.readyOps());
+- boolean close = (!processSocket(channel));
+- if (close) {
+- cancelledKey(sk,SocketStatus.DISCONNECT,false);
+- }
+- attachment.setFairness(0);
+- } else {
+- //increase the fairness counter
+- attachment.incFairness();
+- result = false;
+- }
+- }
+- }
+- } else {
+- //invalid key
+- cancelledKey(sk, SocketStatus.ERROR,false);
+- }
+- } catch ( CancelledKeyException ckx ) {
+- cancelledKey(sk, SocketStatus.ERROR,false);
+- } catch (Throwable t) {
+- log.error("",t);
+- }
+- return result;
+- }
+-
+- public boolean processSendfile(SelectionKey sk, KeyAttachment attachment, boolean reg) {
+- try {
+- //unreg(sk,attachment);//only do this if we do process send file on a separate thread
+- SendfileData sd = attachment.getSendfileData();
+- if ( sd.fchannel == null ) {
+- File f = new File(sd.fileName);
+- if ( !f.exists() ) {
+- cancelledKey(sk,SocketStatus.ERROR,false);
+- return false;
+- }
+- sd.fchannel = new FileInputStream(f).getChannel();
+- }
+- SocketChannel sc = attachment.getChannel().getIOChannel();
+- long written = sd.fchannel.transferTo(sd.pos,sd.length,sc);
+- if ( written > 0 ) {
+- sd.pos += written;
+- sd.length -= written;
+- }
+- if ( sd.length <= 0 ) {
+- attachment.setSendfileData(null);
+- if ( sd.keepAlive )
+- if (reg) reg(sk,attachment,SelectionKey.OP_READ);
+- else
+- cancelledKey(sk,SocketStatus.STOP,false);
+- } else if ( attachment.interestOps() == 0 && reg ) {
+- reg(sk,attachment,SelectionKey.OP_WRITE);
+- }
+- }catch ( IOException x ) {
+- if ( log.isDebugEnabled() ) log.warn("Unable to complete sendfile request:", x);
+- cancelledKey(sk,SocketStatus.ERROR,false);
+- return false;
+- }catch ( Throwable t ) {
+- log.error("",t);
+- cancelledKey(sk, SocketStatus.ERROR, false);
+- return false;
+- }
+- return true;
+- }
+-
+- protected void unreg(SelectionKey sk, KeyAttachment attachment, int readyOps) {
+- //this is a must, so that we don't have multiple threads messing with the socket
+- reg(sk,attachment,sk.interestOps()& (~readyOps));
+- }
+-
+- protected void reg(SelectionKey sk, KeyAttachment attachment, int intops) {
+- sk.interestOps(intops);
+- attachment.interestOps(intops);
+- }
+-
+- protected void timeout(int keyCount, boolean hasEvents) {
+- long now = System.currentTimeMillis();
+- //don't process timeouts too frequently, but if the selector simply timed out
+- //then we can check timeouts to avoid gaps
+- if ( ((keyCount>0 || hasEvents) ||(now < nextExpiration)) && (!close) ) {
+- return;
+- }
+- long prevExp = nextExpiration;
+- nextExpiration = now + socketProperties.getTimeoutInterval();
+- //timeout
+- Set<SelectionKey> keys = selector.keys();
+- int keycount = 0;
+- for (Iterator<SelectionKey> iter = keys.iterator(); iter.hasNext(); ) {
+- SelectionKey key = iter.next();
+- keycount++;
+- try {
+- KeyAttachment ka = (KeyAttachment) key.attachment();
+- if ( ka == null ) {
+- cancelledKey(key, SocketStatus.ERROR,false); //we don't support any keys without attachments
+- } else if ( ka.getError() ) {
+- cancelledKey(key, SocketStatus.ERROR,true);
+- }else if ((ka.interestOps()&SelectionKey.OP_READ) == SelectionKey.OP_READ) {
+- //only timeout sockets that we are waiting for a read from
+- long delta = now - ka.getLastAccess();
+- long timeout = (ka.getTimeout()==-1)?((long) socketProperties.getSoTimeout()):(ka.getTimeout());
+- boolean isTimedout = delta > timeout;
+- if ( close ) {
+- key.interestOps(0);
+- ka.interestOps(0); //avoid duplicate stop calls
+- processKey(key,ka);
+- } else if (isTimedout) {
+- key.interestOps(0);
+- ka.interestOps(0); //avoid duplicate timeout calls
+- cancelledKey(key, SocketStatus.TIMEOUT,true);
+- } else {
+- long nextTime = now+(timeout-delta);
+- nextExpiration = (nextTime < nextExpiration)?nextTime:nextExpiration;
+- }
+- }//end if
+- }catch ( CancelledKeyException ckx ) {
+- cancelledKey(key, SocketStatus.ERROR,false);
+- }
+- }//for
+- if ( log.isDebugEnabled() ) log.debug("timeout completed: keycount="+keycount+"; now="+now+"; nextExpiration="+prevExp+"; "+
+- "keyCount="+keyCount+"; hasEvents="+hasEvents +"; eval="+( (now < prevExp) && (keyCount>0 || hasEvents) && (!close) ));
+-
+- }
+- }
+-
+-// ----------------------------------------------------- Key Attachment Class
+- public static class KeyAttachment {
+-
+- public KeyAttachment() {
+-
+- }
+- public void reset(Poller poller, NioChannel channel) {
+- this.channel = channel;
+- this.poller = poller;
+- lastAccess = System.currentTimeMillis();
+- currentAccess = false;
+- comet = false;
+- timeout = -1;
+- error = false;
+- fairness = 0;
+- lastRegistered = 0;
+- sendfileData = null;
+- if ( readLatch!=null ) try {for (int i=0; i<(int)readLatch.getCount();i++) readLatch.countDown();}catch (Exception ignore){}
+- readLatch = null;
+- if ( writeLatch!=null ) try {for (int i=0; i<(int)writeLatch.getCount();i++) writeLatch.countDown();}catch (Exception ignore){}
+- writeLatch = null;
+- }
+-
+- public void reset() {
+- reset(null,null);
+- }
+-
+- public Poller getPoller() { return poller;}
+- public void setPoller(Poller poller){this.poller = poller;}
+- public long getLastAccess() { return lastAccess; }
+- public void access() { access(System.currentTimeMillis()); }
+- public void access(long access) { lastAccess = access; }
+- public void setComet(boolean comet) { this.comet = comet; }
+- public boolean getComet() { return comet; }
+- public boolean getCurrentAccess() { return currentAccess; }
+- public void setCurrentAccess(boolean access) { currentAccess = access; }
+- public Object getMutex() {return mutex;}
+- public void setTimeout(long timeout) {this.timeout = timeout;}
+- public long getTimeout() {return this.timeout;}
+- public boolean getError() { return error; }
+- public void setError(boolean error) { this.error = error; }
+- public NioChannel getChannel() { return channel;}
+- public void setChannel(NioChannel channel) { this.channel = channel;}
+- protected Poller poller = null;
+- protected int interestOps = 0;
+- public int interestOps() { return interestOps;}
+- public int interestOps(int ops) { this.interestOps = ops; return ops; }
+- public CountDownLatch getReadLatch() { return readLatch; }
+- public CountDownLatch getWriteLatch() { return writeLatch; }
+- protected CountDownLatch resetLatch(CountDownLatch latch) {
+- if ( latch.getCount() == 0 ) return null;
+- else throw new IllegalStateException("Latch must be at count 0");
+- }
+- public void resetReadLatch() { readLatch = resetLatch(readLatch); }
+- public void resetWriteLatch() { writeLatch = resetLatch(writeLatch); }
+-
+- protected CountDownLatch startLatch(CountDownLatch latch, int cnt) {
+- if ( latch == null || latch.getCount() == 0 ) {
+- return new CountDownLatch(cnt);
+- }
+- else throw new IllegalStateException("Latch must be at count 0 or null.");
+- }
+- public void startReadLatch(int cnt) { readLatch = startLatch(readLatch,cnt);}
+- public void startWriteLatch(int cnt) { writeLatch = startLatch(writeLatch,cnt);}
+-
+-
+- protected void awaitLatch(CountDownLatch latch, long timeout, TimeUnit unit) throws InterruptedException {
+- if ( latch == null ) throw new IllegalStateException("Latch cannot be null");
+- latch.await(timeout,unit);
+- }
+- public void awaitReadLatch(long timeout, TimeUnit unit) throws InterruptedException { awaitLatch(readLatch,timeout,unit);}
+- public void awaitWriteLatch(long timeout, TimeUnit unit) throws InterruptedException { awaitLatch(writeLatch,timeout,unit);}
+-
+- public int getFairness() { return fairness; }
+- public void setFairness(int f) { fairness = f;}
+- public void incFairness() { fairness++; }
+- public long getLastRegistered() { return lastRegistered; };
+- public void setLastRegistered(long reg) { lastRegistered = reg; }
+-
+- public void setSendfileData(SendfileData sf) { this.sendfileData = sf;}
+- public SendfileData getSendfileData() { return this.sendfileData;}
+-
+- protected Object mutex = new Object();
+- protected long lastAccess = -1;
+- protected boolean currentAccess = false;
+- protected boolean comet = false;
+- protected long timeout = -1;
+- protected boolean error = false;
+- protected NioChannel channel = null;
+- protected CountDownLatch readLatch = null;
+- protected CountDownLatch writeLatch = null;
+- protected int fairness = 0;
+- protected long lastRegistered = 0;
+- protected SendfileData sendfileData = null;
+- }
+- // ----------------------------------------------------- Worker Inner Class
+-
+-
+- /**
+- * Server processor class.
+- */
+- protected class Worker implements Runnable {
+-
+-
+- protected Thread thread = null;
+- protected boolean available = false;
+- protected Object socket = null;
+- protected SocketStatus status = null;
+-
+-
+- /**
+- * Process an incoming TCP/IP connection on the specified socket. Any
+- * exception that occurs during processing must be logged and swallowed.
+- * <b>NOTE</b>: This method is called from our Connector's thread. We
+- * must assign it to our own thread so that multiple simultaneous
+- * requests can be handled.
+- *
+- * @param socket TCP socket to process
+- */
+- protected synchronized void assign(Object socket) {
+-
+- // Wait for the Processor to get the previous Socket
+- while (available) {
+- try {
+- wait();
+- } catch (InterruptedException e) {
+- }
+- }
+- // Store the newly available Socket and notify our thread
+- this.socket = socket;
+- status = null;
+- available = true;
+- notifyAll();
+-
+- }
+-
+-
+- protected synchronized void assign(Object socket, SocketStatus status) {
+-
+- // Wait for the Processor to get the previous Socket
+- while (available) {
+- try {
+- wait();
+- } catch (InterruptedException e) {
+- }
+- }
+-
+- // Store the newly available Socket and notify our thread
+- this.socket = socket;
+- this.status = status;
+- available = true;
+- notifyAll();
+- }
+-
+-
+- /**
+- * Await a newly assigned Socket from our Connector, or <code>null</code>
+- * if we are supposed to shut down.
+- */
+- protected synchronized Object await() {
+-
+- // Wait for the Connector to provide a new Socket
+- while (!available) {
+- try {
+- wait();
+- } catch (InterruptedException e) {
+- }
+- }
+-
+- // Notify the Connector that we have received this Socket
+- Object socket = this.socket;
+- available = false;
+- notifyAll();
+-
+- return (socket);
+-
+- }
+-
+-
+- /**
+- * The background thread that listens for incoming TCP/IP connections and
+- * hands them off to an appropriate processor.
+- */
+- public void run() {
+-
+- // Process requests until we receive a shutdown signal
+- while (running) {
+- NioChannel socket = null;
+- SelectionKey key = null;
+- try {
+- // Wait for the next socket to be assigned
+- Object channel = await();
+- if (channel == null)
+- continue;
+-
+- if ( channel instanceof SocketChannel) {
+- SocketChannel sc = (SocketChannel)channel;
+- if ( !setSocketOptions(sc) ) {
+- try {
+- sc.socket().close();
+- sc.close();
+- }catch ( IOException ix ) {
+- if ( log.isDebugEnabled() ) log.debug("",ix);
+- }
+- } else {
+- //now we have it registered, remove it from the cache
+-
+- }
+- } else {
+- socket = (NioChannel)channel;
+- SocketProcessor sc = processorCache.poll();
+- if ( sc == null ) sc = new SocketProcessor(socket,status);
+- else sc.reset(socket,status);
+- sc.run();
+- }
+- }catch(CancelledKeyException cx) {
+- if (socket!=null && key!=null) socket.getPoller().cancelledKey(key,null,false);
+- } catch (OutOfMemoryError oom) {
+- try {
+- oomParachuteData = null;
+- releaseCaches();
+- log.error("", oom);
+- }catch ( Throwable oomt ) {
+- try {
+- System.err.println(oomParachuteMsg);
+- oomt.printStackTrace();
+- }catch (Throwable letsHopeWeDontGetHere){}
+- }
+- } finally {
+- //dereference socket to let GC do its job
+- socket = null;
+- // Finish up this request
+- recycleWorkerThread(this);
+- }
+- }
+- }
+-
+-
+- /**
+- * Start the background processing thread.
+- */
+- public void start() {
+- thread = new Thread(this);
+- thread.setName(getName() + "-" + (++curThreads));
+- thread.setDaemon(true);
+- thread.setPriority(getThreadPriority());
+- thread.start();
+- }
+-
+-
+- }
+-
+- // ------------------------------------------------ Application Buffer Handler
+- public class NioBufferHandler implements ApplicationBufferHandler {
+- protected ByteBuffer readbuf = null;
+- protected ByteBuffer writebuf = null;
+-
+- public NioBufferHandler(int readsize, int writesize, boolean direct) {
+- if ( direct ) {
+- readbuf = ByteBuffer.allocateDirect(readsize);
+- writebuf = ByteBuffer.allocateDirect(writesize);
+- }else {
+- readbuf = ByteBuffer.allocate(readsize);
+- writebuf = ByteBuffer.allocate(writesize);
+- }
+- }
+-
+- public ByteBuffer expand(ByteBuffer buffer, int remaining) {return buffer;}
+- public ByteBuffer getReadBuffer() {return readbuf;}
+- public ByteBuffer getWriteBuffer() {return writebuf;}
+-
+- }
+-
+- // ------------------------------------------------ Handler Inner Interface
+-
+-
+- /**
+- * Bare bones interface used for socket processing. Per thread data is to be
+- * stored in the ThreadWithAttributes extra folders, or alternately in
+- * thread local fields.
+- */
+- public interface Handler {
+- public enum SocketState {
+- OPEN, CLOSED, LONG
+- }
+- public SocketState process(NioChannel socket);
+- public SocketState event(NioChannel socket, SocketStatus status);
+- public void releaseCaches();
+- }
+-
+-
+- // ------------------------------------------------- WorkerStack Inner Class
+-
+-
+- public class WorkerStack {
+-
+- protected Worker[] workers = null;
+- protected int end = 0;
+-
+- public WorkerStack(int size) {
+- workers = new Worker[size];
+- }
+-
+- /**
+- * Put the object into the queue.
+- *
+- * @param object the object to be appended to the queue (first element).
+- */
+- public void push(Worker worker) {
+- workers[end++] = worker;
+- }
+-
+- /**
+- * Get the first object out of the queue. Return null if the queue
+- * is empty.
+- */
+- public Worker pop() {
+- if (end > 0) {
+- return workers[--end];
+- }
+- return null;
+- }
+-
+- /**
+- * Get the first object out of the queue, Return null if the queue
+- * is empty.
+- */
+- public Worker peek() {
+- return workers[end];
+- }
+-
+- /**
+- * Is the queue empty?
+- */
+- public boolean isEmpty() {
+- return (end == 0);
+- }
+-
+- /**
+- * How many elements are there in this queue?
+- */
+- public int size() {
+- return (end);
+- }
+- }
+-
+-
+- // ---------------------------------------------- SocketProcessor Inner Class
+-
+-
+- /**
+- * This class is the equivalent of the Worker, but will simply use in an
+- * external Executor thread pool.
+- */
+- protected class SocketProcessor implements Runnable {
+-
+- protected NioChannel socket = null;
+- protected SocketStatus status = null;
+-
+- public SocketProcessor(NioChannel socket, SocketStatus status) {
+- reset(socket,status);
+- }
+-
+- public void reset(NioChannel socket, SocketStatus status) {
+- this.socket = socket;
+- this.status = status;
+- }
+-
+- public void run() {
+- SelectionKey key = null;
+- try {
+- key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
+- int handshake = -1;
+-
+- try {
+- if (key!=null) handshake = socket.handshake(key.isReadable(), key.isWritable());
+- }catch ( IOException x ) {
+- handshake = -1;
+- if ( log.isDebugEnabled() ) log.debug("Error during SSL handshake",x);
+- }catch ( CancelledKeyException ckx ) {
+- handshake = -1;
+- }
+- if ( handshake == 0 ) {
+- // Process the request from this socket
+- boolean closed = (status==null)?(handler.process(socket)==Handler.SocketState.CLOSED) :
+- (handler.event(socket,status)==Handler.SocketState.CLOSED);
+-
+- if (closed) {
+- // Close socket and pool
+- try {
+- KeyAttachment ka = null;
+- if (key!=null) {
+- ka = (KeyAttachment) key.attachment();
+- if (ka!=null) ka.setComet(false);
+- socket.getPoller().cancelledKey(key, SocketStatus.ERROR, false);
+- }
+- if (socket!=null) nioChannels.offer(socket);
+- socket = null;
+- if ( ka!=null ) keyCache.offer(ka);
+- ka = null;
+- }catch ( Exception x ) {
+- log.error("",x);
+- }
+- }
+- } else if (handshake == -1 ) {
+- KeyAttachment ka = null;
+- if (key!=null) {
+- ka = (KeyAttachment) key.attachment();
+- socket.getPoller().cancelledKey(key, SocketStatus.DISCONNECT, false);
+- }
+- if (socket!=null) nioChannels.offer(socket);
+- socket = null;
+- if ( ka!=null ) keyCache.offer(ka);
+- ka = null;
+- } else {
+- final SelectionKey fk = key;
+- final int intops = handshake;
+- final KeyAttachment ka = (KeyAttachment)fk.attachment();
+- ka.getPoller().add(socket,intops);
+- }
+- }catch(CancelledKeyException cx) {
+- socket.getPoller().cancelledKey(key,null,false);
+- } catch (OutOfMemoryError oom) {
+- try {
+- oomParachuteData = null;
+- socket.getPoller().cancelledKey(key,SocketStatus.ERROR,false);
+- releaseCaches();
+- log.error("", oom);
+- }catch ( Throwable oomt ) {
+- try {
+- System.err.println(oomParachuteMsg);
+- oomt.printStackTrace();
+- }catch (Throwable letsHopeWeDontGetHere){}
+- }
+- }catch ( Throwable t ) {
+- log.error("",t);
+- socket.getPoller().cancelledKey(key,SocketStatus.ERROR,false);
+- } finally {
+- socket = null;
+- status = null;
+- //return to cache
+- processorCache.offer(this);
+- }
+- }
+-
+- }
+-
+- // ---------------------------------------------- TaskQueue Inner Class
+- public static class TaskQueue extends LinkedBlockingQueue<Runnable> {
+- ThreadPoolExecutor parent = null;
+-
+- public TaskQueue() {
+- super();
+- }
+-
+- public TaskQueue(int initialCapacity) {
+- super(initialCapacity);
+- }
+-
+- public TaskQueue(Collection<? extends Runnable> c) {
+- super(c);
+- }
+-
+-
+- public void setParent(ThreadPoolExecutor tp) {
+- parent = tp;
+- }
+-
+- public boolean offer(Runnable o) {
+- //we can't do any checks
+- if (parent==null) return super.offer(o);
+- //we are maxed out on threads, simply queue the object
+- if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o);
+- //we have idle threads, just add it to the queue
+- //this is an approximation, so it could use some tuning
+- if (parent.getActiveCount()<(parent.getPoolSize())) return super.offer(o);
+- //if we have less threads than maximum force creation of a new thread
+- if (parent.getPoolSize()<parent.getMaximumPoolSize()) return false;
+- //if we reached here, we need to add it to the queue
+- return super.offer(o);
+- }
+- }
+-
+- // ---------------------------------------------- ThreadFactory Inner Class
+- class TaskThreadFactory implements ThreadFactory {
+- final ThreadGroup group;
+- final AtomicInteger threadNumber = new AtomicInteger(1);
+- final String namePrefix;
+-
+- TaskThreadFactory(String namePrefix) {
+- SecurityManager s = System.getSecurityManager();
+- group = (s != null)? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
+- this.namePrefix = namePrefix;
+- }
+-
+- public Thread newThread(Runnable r) {
+- Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement());
+- t.setDaemon(daemon);
+- t.setPriority(getThreadPriority());
+- return t;
+- }
+- }
+-
+- // ----------------------------------------------- SendfileData Inner Class
+-
+-
+- /**
+- * SendfileData class.
+- */
+- public static class SendfileData {
+- // File
+- public String fileName;
+- public FileChannel fchannel;
+- public long pos;
+- public long length;
+- // KeepAlive flag
+- public boolean keepAlive;
+- }
+-
+-}
+Index: java/org/apache/jk/common/ChannelNioSocket.java
+===================================================================
+--- java/org/apache/jk/common/ChannelNioSocket.java (revision 590752)
++++ java/org/apache/jk/common/ChannelNioSocket.java (working copy)
+@@ -1,1199 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.jk.common;
+-
+-import java.util.Set;
+-import java.util.Iterator;
+-import java.io.IOException;
+-import java.io.InputStream;
+-import java.io.OutputStream;
+-import java.nio.ByteBuffer;
+-import java.nio.channels.Selector;
+-import java.nio.channels.SelectionKey;
+-import java.nio.channels.SocketChannel;
+-import java.nio.channels.ClosedSelectorException;
+-import java.nio.channels.ServerSocketChannel;
+-import java.nio.channels.CancelledKeyException;
+-import java.nio.channels.ClosedChannelException;
+-import java.net.URLEncoder;
+-import java.net.InetAddress;
+-import java.net.InetSocketAddress;
+-import java.net.ServerSocket;
+-import java.net.Socket;
+-import java.net.SocketException;
+-import java.net.SocketTimeoutException;
+-
+-import javax.management.ListenerNotFoundException;
+-import javax.management.MBeanNotificationInfo;
+-import javax.management.Notification;
+-import javax.management.NotificationBroadcaster;
+-import javax.management.NotificationBroadcasterSupport;
+-import javax.management.NotificationFilter;
+-import javax.management.NotificationListener;
+-import javax.management.ObjectName;
+-
+-import org.apache.tomcat.util.modeler.Registry;
+-import org.apache.jk.core.JkHandler;
+-import org.apache.jk.core.Msg;
+-import org.apache.jk.core.MsgContext;
+-import org.apache.jk.core.JkChannel;
+-import org.apache.jk.core.WorkerEnv;
+-import org.apache.coyote.Request;
+-import org.apache.coyote.RequestGroupInfo;
+-import org.apache.coyote.RequestInfo;
+-import org.apache.tomcat.util.threads.ThreadPool;
+-import org.apache.tomcat.util.threads.ThreadPoolRunnable;
+-
+-/**
+- * Accept ( and send ) TCP messages.
+- *
+- * @author Costin Manolache
+- * @author Bill Barker
+- * jmx:mbean name="jk:service=ChannelNioSocket"
+- * description="Accept socket connections"
+- * jmx:notification name="org.apache.coyote.INVOKE
+- * jmx:notification-handler name="org.apache.jk.JK_SEND_PACKET
+- * jmx:notification-handler name="org.apache.jk.JK_RECEIVE_PACKET
+- * jmx:notification-handler name="org.apache.jk.JK_FLUSH
+- *
+- * Jk can use multiple protocols/transports.
+- * Various container adapters should load this object ( as a bean ),
+- * set configurations and use it. Note that the connector will handle
+- * all incoming protocols - it's not specific to ajp1x. The protocol
+- * is abstracted by MsgContext/Message/Channel.
+- *
+- * A lot of the 'original' behavior is hardcoded - this uses Ajp13 wire protocol,
+- * TCP, Ajp14 API etc.
+- * As we add other protocols/transports/APIs this will change, the current goal
+- * is to get the same level of functionality as in the original jk connector.
+- *
+- * XXX Make the 'message type' pluggable
+- */
+-public class ChannelNioSocket extends JkHandler
+- implements NotificationBroadcaster, JkChannel {
+- private static org.apache.juli.logging.Log log =
+- org.apache.juli.logging.LogFactory.getLog( ChannelNioSocket.class );
+-
+- private int startPort=8009;
+- private int maxPort=8019; // 0 for backward compat.
+- private int port=startPort;
+- private InetAddress inet;
+- private int serverTimeout = 0;
+- private boolean tcpNoDelay=true; // nodelay to true by default
+- private int linger=100;
+- private int socketTimeout = 0;
+- private boolean nioIsBroken = false;
+- private Selector selector = null;
+- private int bufferSize = 8*1024;
+- private int packetSize = 8*1024;
+-
+- private long requestCount=0;
+-
+- /* Turning this to true will reduce the latency with about 20%.
+- But it requires changes in tomcat to make sure client-requested
+- flush() is honored ( on my test, I got 367->433 RPS and
+- 52->35ms average time with a simple servlet )
+- */
+-
+- ThreadPool tp=ThreadPool.createThreadPool(true);
+-
+- /* ==================== Tcp socket options ==================== */
+-
+- /**
+- * jmx:managed-constructor description="default constructor"
+- */
+- public ChannelNioSocket() {
+- // This should be integrated with the domain setup
+- }
+-
+- public ThreadPool getThreadPool() {
+- return tp;
+- }
+-
+- public long getRequestCount() {
+- return requestCount;
+- }
+-
+- /** Set the port for the ajp13 channel.
+- * To support seemless load balancing and jni, we treat this
+- * as the 'base' port - we'll try up until we find one that is not
+- * used. We'll also provide the 'difference' to the main coyote
+- * handler - that will be our 'sessionID' and the position in
+- * the scoreboard and the suffix for the unix domain socket.
+- *
+- * jmx:managed-attribute description="Port to listen" access="READ_WRITE"
+- */
+- public void setPort( int port ) {
+- this.startPort=port;
+- this.port=port;
+- this.maxPort=port+10;
+- }
+-
+- public int getPort() {
+- return port;
+- }
+-
+- public void setAddress(InetAddress inet) {
+- this.inet=inet;
+- }
+-
+- public void setBufferSize(int bs) {
+- if(bs > 8*1024) {
+- bufferSize = bs;
+- }
+- }
+-
+- public int getBufferSize() {
+- return bufferSize;
+- }
+-
+- public void setPacketSize(int ps) {
+- if(ps < 8*1024) {
+- ps = 8*1024;
+- }
+- packetSize = ps;
+- }
+-
+- public int getPacketSize() {
+- return packetSize;
+- }
+-
+- /**
+- * jmx:managed-attribute description="Bind on a specified address" access="READ_WRITE"
+- */
+- public void setAddress(String inet) {
+- try {
+- this.inet= InetAddress.getByName( inet );
+- } catch( Exception ex ) {
+- log.error("Error parsing "+inet,ex);
+- }
+- }
+-
+- public String getAddress() {
+- if( inet!=null)
+- return inet.toString();
+- return "/0.0.0.0";
+- }
+-
+- /**
+- * Sets the timeout in ms of the server sockets created by this
+- * server. This method allows the developer to make servers
+- * more or less responsive to having their server sockets
+- * shut down.
+- *
+- * <p>By default this value is 1000ms.
+- */
+- public void setServerTimeout(int timeout) {
+- this.serverTimeout = timeout;
+- }
+- public int getServerTimeout() {
+- return serverTimeout;
+- }
+-
+- public void setTcpNoDelay( boolean b ) {
+- tcpNoDelay=b;
+- }
+-
+- public boolean getTcpNoDelay() {
+- return tcpNoDelay;
+- }
+-
+- public void setSoLinger( int i ) {
+- linger=i;
+- }
+-
+- public int getSoLinger() {
+- return linger;
+- }
+-
+- public void setSoTimeout( int i ) {
+- socketTimeout=i;
+- }
+-
+- public int getSoTimeout() {
+- return socketTimeout;
+- }
+-
+- public void setMaxPort( int i ) {
+- maxPort=i;
+- }
+-
+- public int getMaxPort() {
+- return maxPort;
+- }
+-
+- /** At startup we'll look for the first free port in the range.
+- The difference between this port and the beggining of the range
+- is the 'id'.
+- This is usefull for lb cases ( less config ).
+- */
+- public int getInstanceId() {
+- return port-startPort;
+- }
+-
+- /** If set to false, the thread pool will be created in
+- * non-daemon mode, and will prevent main from exiting
+- */
+- public void setDaemon( boolean b ) {
+- tp.setDaemon( b );
+- }
+-
+- public boolean getDaemon() {
+- return tp.getDaemon();
+- }
+-
+-
+- public void setMaxThreads( int i ) {
+- if( log.isDebugEnabled()) log.debug("Setting maxThreads " + i);
+- tp.setMaxThreads(i);
+- }
+-
+- public void setMinSpareThreads( int i ) {
+- if( log.isDebugEnabled()) log.debug("Setting minSpareThreads " + i);
+- tp.setMinSpareThreads(i);
+- }
+-
+- public void setMaxSpareThreads( int i ) {
+- if( log.isDebugEnabled()) log.debug("Setting maxSpareThreads " + i);
+- tp.setMaxSpareThreads(i);
+- }
+-
+- public int getMaxThreads() {
+- return tp.getMaxThreads();
+- }
+-
+- public int getMinSpareThreads() {
+- return tp.getMinSpareThreads();
+- }
+-
+- public int getMaxSpareThreads() {
+- return tp.getMaxSpareThreads();
+- }
+-
+- public void setBacklog(int i) {
+- }
+-
+- public void setNioIsBroken(boolean nib) {
+- nioIsBroken = nib;
+- }
+-
+- public boolean getNioIsBroken() {
+- return nioIsBroken;
+- }
+-
+- /* ==================== ==================== */
+- ServerSocket sSocket;
+- final int socketNote=1;
+- final int isNote=2;
+- final int osNote=3;
+- final int notifNote=4;
+- boolean paused = false;
+-
+- public void pause() throws Exception {
+- synchronized(this) {
+- paused = true;
+- }
+- }
+-
+- public void resume() {
+- synchronized(this) {
+- paused = false;
+- notify();
+- }
+- }
+-
+-
+- public void accept( MsgContext ep ) throws IOException {
+- if( sSocket==null ) return;
+- synchronized(this) {
+- while(paused) {
+- try{
+- wait();
+- } catch(InterruptedException ie) {
+- //Ignore, since can't happen
+- }
+- }
+- }
+- SocketChannel sc=sSocket.getChannel().accept();
+- Socket s = sc.socket();
+- ep.setNote( socketNote, s );
+- if(log.isDebugEnabled() )
+- log.debug("Accepted socket " + s +" channel " + sc.isBlocking());
+-
+- try {
+- setSocketOptions(s);
+- } catch(SocketException sex) {
+- log.debug("Error initializing Socket Options", sex);
+- }
+-
+- requestCount++;
+-
+- sc.configureBlocking(false);
+- InputStream is=new SocketInputStream(sc);
+- OutputStream os = new SocketOutputStream(sc);
+- ep.setNote( isNote, is );
+- ep.setNote( osNote, os );
+- ep.setControl( tp );
+- }
+-
+- private void setSocketOptions(Socket s) throws SocketException {
+- if( socketTimeout > 0 )
+- s.setSoTimeout( socketTimeout );
+-
+- s.setTcpNoDelay( tcpNoDelay ); // set socket tcpnodelay state
+-
+- if( linger > 0 )
+- s.setSoLinger( true, linger);
+- }
+-
+- public void resetCounters() {
+- requestCount=0;
+- }
+-
+- /** Called after you change some fields at runtime using jmx.
+- Experimental for now.
+- */
+- public void reinit() throws IOException {
+- destroy();
+- init();
+- }
+-
+- /**
+- * jmx:managed-operation
+- */
+- public void init() throws IOException {
+- // Find a port.
+- if (startPort == 0) {
+- port = 0;
+- if(log.isInfoEnabled())
+- log.info("JK: ajp13 disabling channelNioSocket");
+- running = true;
+- return;
+- }
+- if (maxPort < startPort)
+- maxPort = startPort;
+- ServerSocketChannel ssc = ServerSocketChannel.open();
+- ssc.configureBlocking(false);
+- for( int i=startPort; i<=maxPort; i++ ) {
+- try {
+- InetSocketAddress iddr = null;
+- if( inet == null ) {
+- iddr = new InetSocketAddress( i);
+- } else {
+- iddr=new InetSocketAddress( inet, i);
+- }
+- sSocket = ssc.socket();
+- sSocket.bind(iddr);
+- port=i;
+- break;
+- } catch( IOException ex ) {
+- if(log.isInfoEnabled())
+- log.info("Port busy " + i + " " + ex.toString());
+- sSocket = null;
+- }
+- }
+-
+- if( sSocket==null ) {
+- log.error("Can't find free port " + startPort + " " + maxPort );
+- return;
+- }
+- if(log.isInfoEnabled())
+- log.info("JK: ajp13 listening on " + getAddress() + ":" + port );
+-
+- selector = Selector.open();
+- ssc.register(selector, SelectionKey.OP_ACCEPT);
+- // If this is not the base port and we are the 'main' channleSocket and
+- // SHM didn't already set the localId - we'll set the instance id
+- if( "channelNioSocket".equals( name ) &&
+- port != startPort &&
+- (wEnv.getLocalId()==0) ) {
+- wEnv.setLocalId( port - startPort );
+- }
+-
+- // XXX Reverse it -> this is a notification generator !!
+- if( next==null && wEnv!=null ) {
+- if( nextName!=null )
+- setNext( wEnv.getHandler( nextName ) );
+- if( next==null )
+- next=wEnv.getHandler( "dispatch" );
+- if( next==null )
+- next=wEnv.getHandler( "request" );
+- }
+- JMXRequestNote =wEnv.getNoteId( WorkerEnv.ENDPOINT_NOTE, "requestNote");
+- running = true;
+-
+- // Run a thread that will accept connections.
+- // XXX Try to find a thread first - not sure how...
+- if( this.domain != null ) {
+- try {
+- tpOName=new ObjectName(domain + ":type=ThreadPool,name=" +
+- getChannelName());
+-
+- Registry.getRegistry(null, null)
+- .registerComponent(tp, tpOName, null);
+-
+- rgOName = new ObjectName
+- (domain+":type=GlobalRequestProcessor,name=" + getChannelName());
+- Registry.getRegistry(null, null)
+- .registerComponent(global, rgOName, null);
+- } catch (Exception e) {
+- log.error("Can't register threadpool" );
+- }
+- }
+-
+- tp.start();
+- Poller pollAjp = new Poller();
+- tp.runIt(pollAjp);
+- }
+-
+- ObjectName tpOName;
+- ObjectName rgOName;
+- RequestGroupInfo global=new RequestGroupInfo();
+- int JMXRequestNote;
+-
+- public void start() throws IOException{
+- if( sSocket==null )
+- init();
+- resume();
+- }
+-
+- public void stop() throws IOException {
+- destroy();
+- }
+-
+- public void registerRequest(Request req, MsgContext ep, int count) {
+- if(this.domain != null) {
+- try {
+- RequestInfo rp=req.getRequestProcessor();
+- rp.setGlobalProcessor(global);
+- ObjectName roname = new ObjectName
+- (getDomain() + ":type=RequestProcessor,worker="+
+- getChannelName()+",name=JkRequest" +count);
+- ep.setNote(JMXRequestNote, roname);
+-
+- Registry.getRegistry(null, null).registerComponent( rp, roname, null);
+- } catch( Exception ex ) {
+- log.warn("Error registering request");
+- }
+- }
+- }
+-
+- public void open(MsgContext ep) throws IOException {
+- }
+-
+-
+- public void close(MsgContext ep) throws IOException {
+- Socket s=(Socket)ep.getNote( socketNote );
+- SelectionKey key = s.getChannel().keyFor(selector);
+- if(key != null) {
+- key.cancel();
+- }
+- s.close();
+- }
+-
+- public void destroy() throws IOException {
+- running = false;
+- try {
+- /* If we disabled the channel return */
+- if (port == 0)
+- return;
+- tp.shutdown();
+-
+- selector.wakeup().close();
+- sSocket.close(); // XXX?
+-
+- if( tpOName != null ) {
+- Registry.getRegistry(null, null).unregisterComponent(tpOName);
+- }
+- if( rgOName != null ) {
+- Registry.getRegistry(null, null).unregisterComponent(rgOName);
+- }
+- } catch(Exception e) {
+- log.info("Error shutting down the channel " + port + " " +
+- e.toString());
+- if( log.isDebugEnabled() ) log.debug("Trace", e);
+- }
+- }
+-
+- public int send( Msg msg, MsgContext ep)
+- throws IOException {
+- msg.end(); // Write the packet header
+- byte buf[]=msg.getBuffer();
+- int len=msg.getLen();
+-
+- if(log.isTraceEnabled() )
+- log.trace("send() " + len + " " + buf[4] );
+-
+- OutputStream os=(OutputStream)ep.getNote( osNote );
+- os.write( buf, 0, len );
+- return len;
+- }
+-
+- public int flush( Msg msg, MsgContext ep)
+- throws IOException {
+- OutputStream os=(OutputStream)ep.getNote( osNote );
+- os.flush();
+- return 0;
+- }
+-
+- public int receive( Msg msg, MsgContext ep )
+- throws IOException {
+- if (log.isTraceEnabled()) {
+- log.trace("receive() ");
+- }
+-
+- byte buf[]=msg.getBuffer();
+- int hlen=msg.getHeaderLength();
+-
+- // XXX If the length in the packet header doesn't agree with the
+- // actual number of bytes read, it should probably return an error
+- // value. Also, callers of this method never use the length
+- // returned -- should probably return true/false instead.
+-
+- int rd = this.read(ep, buf, 0, hlen );
+-
+- if(rd < 0) {
+- // Most likely normal apache restart.
+- // log.warn("Wrong message " + rd );
+- return rd;
+- }
+-
+- msg.processHeader();
+-
+- /* After processing the header we know the body
+- length
+- */
+- int blen=msg.getLen();
+-
+- // XXX check if enough space - it's assert()-ed !!!
+-
+- int total_read = 0;
+-
+- total_read = this.read(ep, buf, hlen, blen);
+-
+- if ((total_read <= 0) && (blen > 0)) {
+- log.warn("can't read body, waited #" + blen);
+- return -1;
+- }
+-
+- if (total_read != blen) {
+- log.warn( "incomplete read, waited #" + blen +
+- " got only " + total_read);
+- return -2;
+- }
+-
+- return total_read;
+- }
+-
+- /**
+- * Read N bytes from the InputStream, and ensure we got them all
+- * Under heavy load we could experience many fragmented packets
+- * just read Unix Network Programming to recall that a call to
+- * read didn't ensure you got all the data you want
+- *
+- * from read() Linux manual
+- *
+- * On success, the number of bytes read is returned (zero indicates end
+- * of file),and the file position is advanced by this number.
+- * It is not an error if this number is smaller than the number of bytes
+- * requested; this may happen for example because fewer bytes
+- * are actually available right now (maybe because we were close to
+- * end-of-file, or because we are reading from a pipe, or from a
+- * terminal), or because read() was interrupted by a signal.
+- * On error, -1 is returned, and errno is set appropriately. In this
+- * case it is left unspecified whether the file position (if any) changes.
+- *
+- **/
+- public int read( MsgContext ep, byte[] b, int offset, int len)
+- throws IOException
+- {
+- InputStream is=(InputStream)ep.getNote( isNote );
+- int pos = 0;
+- int got;
+-
+- while(pos < len) {
+- try {
+- got = is.read(b, pos + offset, len - pos);
+- } catch(ClosedChannelException sex) {
+- if(pos > 0) {
+- log.info("Error reading data after "+pos+"bytes",sex);
+- } else {
+- log.debug("Error reading data", sex);
+- }
+- got = -1;
+- }
+- if (log.isTraceEnabled()) {
+- log.trace("read() " + b + " " + (b==null ? 0: b.length) + " " +
+- offset + " " + len + " = " + got );
+- }
+-
+- // connection just closed by remote.
+- if (got <= 0) {
+- // This happens periodically, as apache restarts
+- // periodically.
+- // It should be more gracefull ! - another feature for Ajp14
+- // log.warn( "server has closed the current connection (-1)" );
+- return -3;
+- }
+-
+- pos += got;
+- }
+- return pos;
+- }
+-
+- protected boolean running=true;
+-
+- /** Accept incoming connections, dispatch to the thread pool
+- */
+- void acceptConnections() {
+- if( running ) {
+- try{
+- MsgContext ep=createMsgContext();
+- ep.setSource(this);
+- ep.setWorkerEnv( wEnv );
+- this.accept(ep);
+-
+- if( !running ) return;
+-
+- // Since this is a long-running connection, we don't care
+- // about the small GC
+- SocketConnection ajpConn=
+- new SocketConnection( ep);
+- ajpConn.register(ep);
+- }catch(Exception ex) {
+- if (running)
+- log.warn("Exception executing accept" ,ex);
+- }
+- }
+- }
+-
+-
+- // XXX This should become handleNotification
+- public int invoke( Msg msg, MsgContext ep ) throws IOException {
+- int type=ep.getType();
+-
+- switch( type ) {
+- case JkHandler.HANDLE_RECEIVE_PACKET:
+- if( log.isDebugEnabled()) log.debug("RECEIVE_PACKET ?? ");
+- return receive( msg, ep );
+- case JkHandler.HANDLE_SEND_PACKET:
+- return send( msg, ep );
+- case JkHandler.HANDLE_FLUSH:
+- return flush( msg, ep );
+- }
+-
+- if( log.isTraceEnabled() )
+- log.trace("Call next " + type + " " + next);
+-
+- // Send notification
+- if( nSupport!=null ) {
+- Notification notif=(Notification)ep.getNote(notifNote);
+- if( notif==null ) {
+- notif=new Notification("channelNioSocket.message", ep, requestCount );
+- ep.setNote( notifNote, notif);
+- }
+- nSupport.sendNotification(notif);
+- }
+-
+- if( next != null ) {
+- return next.invoke( msg, ep );
+- } else {
+- log.info("No next ");
+- }
+-
+- return OK;
+- }
+-
+- public boolean isSameAddress(MsgContext ep) {
+- Socket s=(Socket)ep.getNote( socketNote );
+- return isSameAddress( s.getLocalAddress(), s.getInetAddress());
+- }
+-
+- public String getChannelName() {
+- String encodedAddr = "";
+- if (inet != null && !"0.0.0.0".equals(inet.getHostAddress())) {
+- encodedAddr = getAddress();
+- if (encodedAddr.startsWith("/"))
+- encodedAddr = encodedAddr.substring(1);
+- encodedAddr = URLEncoder.encode(encodedAddr) + "-";
+- }
+- return ("jk-" + encodedAddr + port);
+- }
+-
+- /**
+- * Return <code>true</code> if the specified client and server addresses
+- * are the same. This method works around a bug in the IBM 1.1.8 JVM on
+- * Linux, where the address bytes are returned reversed in some
+- * circumstances.
+- *
+- * @param server The server's InetAddress
+- * @param client The client's InetAddress
+- */
+- public static boolean isSameAddress(InetAddress server, InetAddress client)
+- {
+- // Compare the byte array versions of the two addresses
+- byte serverAddr[] = server.getAddress();
+- byte clientAddr[] = client.getAddress();
+- if (serverAddr.length != clientAddr.length)
+- return (false);
+- boolean match = true;
+- for (int i = 0; i < serverAddr.length; i++) {
+- if (serverAddr[i] != clientAddr[i]) {
+- match = false;
+- break;
+- }
+- }
+- if (match)
+- return (true);
+-
+- // Compare the reversed form of the two addresses
+- for (int i = 0; i < serverAddr.length; i++) {
+- if (serverAddr[i] != clientAddr[(serverAddr.length-1)-i])
+- return (false);
+- }
+- return (true);
+- }
+-
+- public void sendNewMessageNotification(Notification notification) {
+- if( nSupport!= null )
+- nSupport.sendNotification(notification);
+- }
+-
+- private NotificationBroadcasterSupport nSupport= null;
+-
+- public void addNotificationListener(NotificationListener listener,
+- NotificationFilter filter,
+- Object handback)
+- throws IllegalArgumentException
+- {
+- if( nSupport==null ) nSupport=new NotificationBroadcasterSupport();
+- nSupport.addNotificationListener(listener, filter, handback);
+- }
+-
+- public void removeNotificationListener(NotificationListener listener)
+- throws ListenerNotFoundException
+- {
+- if( nSupport!=null)
+- nSupport.removeNotificationListener(listener);
+- }
+-
+- MBeanNotificationInfo notifInfo[]=new MBeanNotificationInfo[0];
+-
+- public void setNotificationInfo( MBeanNotificationInfo info[]) {
+- this.notifInfo=info;
+- }
+-
+- public MBeanNotificationInfo[] getNotificationInfo() {
+- return notifInfo;
+- }
+-
+- protected class SocketConnection implements ThreadPoolRunnable {
+- MsgContext ep;
+- MsgAjp recv = new MsgAjp(packetSize);
+- boolean inProgress = false;
+-
+- SocketConnection(MsgContext ep) {
+- this.ep=ep;
+- }
+-
+- public Object[] getInitData() {
+- return null;
+- }
+-
+- public void runIt(Object perTh[]) {
+- if(!processConnection(ep)) {
+- unregister(ep);
+- }
+- }
+-
+- public boolean isRunning() {
+- return inProgress;
+- }
+-
+- public void setFinished() {
+- inProgress = false;
+- }
+-
+- /** Process a single ajp connection.
+- */
+- boolean processConnection(MsgContext ep) {
+- try {
+- InputStream sis = (InputStream)ep.getNote(isNote);
+- boolean haveInput = true;
+- while(haveInput) {
+- if( !running || paused ) {
+- return false;
+- }
+- int status= receive( recv, ep );
+- if( status <= 0 ) {
+- if( status==-3)
+- log.debug( "server has been restarted or reset this connection" );
+- else
+- log.warn("Closing ajp connection " + status );
+- return false;
+- }
+- ep.setLong( MsgContext.TIMER_RECEIVED, System.currentTimeMillis());
+-
+- ep.setType( 0 );
+- // Will call next
+- status= invoke( recv, ep );
+- if( status != JkHandler.OK ) {
+- log.warn("processCallbacks status " + status );
+- return false;
+- }
+- synchronized(this) {
+- synchronized(sis) {
+- haveInput = sis.available() > 0;
+- }
+- if(!haveInput) {
+- setFinished();
+- } else {
+- if(log.isDebugEnabled())
+- log.debug("KeepAlive: "+sis.available());
+- }
+- }
+- }
+- } catch( Exception ex ) {
+- String msg = ex.getMessage();
+- if( msg != null && msg.indexOf( "Connection reset" ) >= 0)
+- log.debug( "Server has been restarted or reset this connection");
+- else if (msg != null && msg.indexOf( "Read timed out" ) >=0 )
+- log.debug( "connection timeout reached");
+- else
+- log.error( "Error, processing connection", ex);
+- return false;
+- }
+- return true;
+- }
+-
+- synchronized void process(SelectionKey sk) {
+- if(!sk.isValid()) {
+- SocketInputStream sis = (SocketInputStream)ep.getNote(isNote);
+- sis.closeIt();
+- return;
+- }
+- if(sk.isReadable()) {
+- SocketInputStream sis = (SocketInputStream)ep.getNote(isNote);
+- boolean isok = sis.readAvailable();
+- if(!inProgress) {
+- if(isok) {
+- if(sis.available() > 0 || !nioIsBroken){
+- inProgress = true;
+- tp.runIt(this);
+- }
+- } else {
+- unregister(ep);
+- return;
+- }
+- }
+- }
+- if(sk.isWritable()) {
+- Object os = ep.getNote(osNote);
+- synchronized(os) {
+- os.notify();
+- }
+- }
+- }
+-
+- synchronized void unregister(MsgContext ep) {
+- try{
+- close(ep);
+- } catch(Exception e) {
+- log.error("Error closing connection", e);
+- }
+- try{
+- Request req = (Request)ep.getRequest();
+- if( req != null ) {
+- ObjectName roname = (ObjectName)ep.getNote(JMXRequestNote);
+- if( roname != null ) {
+- Registry.getRegistry(null, null).unregisterComponent(roname);
+- }
+- req.getRequestProcessor().setGlobalProcessor(null);
+- }
+- } catch( Exception ee) {
+- log.error( "Error, releasing connection",ee);
+- }
+- }
+-
+- void register(MsgContext ep) {
+- Socket s = (Socket)ep.getNote(socketNote);
+- try {
+- s.getChannel().register(selector, SelectionKey.OP_READ, this);
+- } catch(IOException iex) {
+- log.error("Unable to register connection",iex);
+- unregister(ep);
+- }
+- }
+-
+- }
+-
+- protected class Poller implements ThreadPoolRunnable {
+-
+- Poller() {
+- }
+-
+- public Object[] getInitData() {
+- return null;
+- }
+-
+- public void runIt(Object perTh[]) {
+- while(running) {
+- try {
+- int ns = selector.select(serverTimeout);
+- if(log.isDebugEnabled())
+- log.debug("Selecting "+ns+" channels");
+- if(ns > 0) {
+- Set sels = selector.selectedKeys();
+- Iterator it = sels.iterator();
+- while(it.hasNext()) {
+- SelectionKey sk = (SelectionKey)it.next();
+- if(sk.isAcceptable()) {
+- acceptConnections();
+- } else {
+- SocketConnection sc = (SocketConnection)sk.attachment();
+- sc.process(sk);
+- }
+- it.remove();
+- }
+- }
+- } catch(ClosedSelectorException cse) {
+- log.debug("Selector is closed");
+- return;
+- } catch(CancelledKeyException cke) {
+- log.debug("Key Cancelled", cke);
+- } catch(IOException iex) {
+- log.warn("IO Error in select",iex);
+- } catch(Exception ex) {
+- log.warn("Error processing select",ex);
+- }
+- }
+- }
+- }
+-
+- protected class SocketInputStream extends InputStream {
+- final int BUFFER_SIZE = 8200;
+- private ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
+- private SocketChannel channel;
+- private boolean blocking = false;
+- private boolean isClosed = false;
+- private volatile boolean dataAvailable = false;
+-
+- SocketInputStream(SocketChannel channel) {
+- this.channel = channel;
+- buffer.limit(0);
+- }
+-
+- public int available() {
+- return buffer.remaining();
+- }
+-
+- public void mark(int readlimit) {
+- buffer.mark();
+- }
+-
+- public boolean markSupported() {
+- return true;
+- }
+-
+- public void reset() {
+- buffer.reset();
+- }
+-
+- public synchronized int read() throws IOException {
+- if(!checkAvailable(1)) {
+- block(1);
+- }
+- return buffer.get();
+- }
+-
+- private boolean checkAvailable(int nbyte) throws IOException {
+- if(isClosed) {
+- throw new ClosedChannelException();
+- }
+- return buffer.remaining() >= nbyte;
+- }
+-
+- private int fill(int nbyte) throws IOException {
+- int rem = nbyte;
+- int read = 0;
+- boolean eof = false;
+- byte [] oldData = null;
+- if(buffer.remaining() > 0) {
+- // should rarely happen, so short-lived GC shouldn't hurt
+- // as much as allocating a long-lived buffer for this
+- if(log.isDebugEnabled())
+- log.debug("Saving old buffer: "+buffer.remaining());
+- oldData = new byte[buffer.remaining()];
+- buffer.get(oldData);
+- }
+- buffer.clear();
+- if(oldData != null) {
+- buffer.put(oldData);
+- }
+- while(rem > 0) {
+- int count = channel.read(buffer);
+- if(count < 0) {
+- eof = true;
+- break;
+- } else if(count == 0) {
+- log.debug("Failed to recieve signaled read: ");
+- break;
+- }
+- read += count;
+- rem -= count;
+- }
+- buffer.flip();
+- return eof ? -1 : read;
+- }
+-
+- synchronized boolean readAvailable() {
+- if(blocking) {
+- dataAvailable = true;
+- notify();
+- } else if(dataAvailable) {
+- log.debug("Race Condition");
+- } else {
+- int nr=0;
+-
+- try {
+- nr = fill(1);
+- } catch(ClosedChannelException cce) {
+- log.debug("Channel is closed",cce);
+- nr = -1;
+- } catch(IOException iex) {
+- log.warn("Exception processing read",iex);
+- nr = -1; // Can't handle this yet
+- }
+- if(nr < 0) {
+- closeIt();
+- return false;
+- } else if(nr == 0) {
+- if(!nioIsBroken) {
+- dataAvailable = (buffer.remaining() <= 0);
+- }
+- }
+- }
+- return true;
+- }
+-
+- synchronized void closeIt() {
+- isClosed = true;
+- if(blocking)
+- notify();
+- }
+-
+- public int read(byte [] data) throws IOException {
+- return read(data, 0, data.length);
+- }
+-
+- public synchronized int read(byte [] data, int offset, int len) throws IOException {
+- int olen = len;
+- while(!checkAvailable(len)) {
+- int avail = buffer.remaining();
+- if(avail > 0) {
+- buffer.get(data, offset, avail);
+- }
+- len -= avail;
+- offset += avail;
+- block(len);
+- }
+- buffer.get(data, offset, len);
+- return olen;
+- }
+-
+- private void block(int len) throws IOException {
+- if(len <= 0) {
+- return;
+- }
+- if(!dataAvailable) {
+- blocking = true;
+- if(log.isDebugEnabled())
+- log.debug("Waiting for "+len+" bytes to be available");
+- try{
+- wait(socketTimeout);
+- }catch(InterruptedException iex) {
+- log.debug("Interrupted",iex);
+- }
+- blocking = false;
+- }
+- if(dataAvailable) {
+- dataAvailable = false;
+- if(fill(len) < 0) {
+- isClosed = true;
+- }
+- } else if(!isClosed) {
+- throw new SocketTimeoutException("Read request timed out");
+- }
+- }
+- }
+-
+- protected class SocketOutputStream extends OutputStream {
+- ByteBuffer buffer = ByteBuffer.allocateDirect(bufferSize);
+- SocketChannel channel;
+-
+- SocketOutputStream(SocketChannel channel) {
+- this.channel = channel;
+- }
+-
+- public void write(int b) throws IOException {
+- if(!checkAvailable(1)) {
+- flush();
+- }
+- buffer.put((byte)b);
+- }
+-
+- public void write(byte [] data) throws IOException {
+- write(data, 0, data.length);
+- }
+-
+- public void write(byte [] data, int offset, int len) throws IOException {
+- if(!checkAvailable(len)) {
+- flush();
+- }
+- buffer.put(data, offset, len);
+- }
+-
+- public void flush() throws IOException {
+- buffer.flip();
+- while(buffer.hasRemaining()) {
+- int count = channel.write(buffer);
+- if(count == 0) {
+- synchronized(this) {
+- SelectionKey key = channel.keyFor(selector);
+- key.interestOps(SelectionKey.OP_WRITE);
+- if(log.isDebugEnabled())
+- log.debug("Blocking for channel write: "+buffer.remaining());
+- try {
+- wait();
+- } catch(InterruptedException iex) {
+- // ignore, since can't happen
+- }
+- key.interestOps(SelectionKey.OP_READ);
+- }
+- }
+- }
+- buffer.clear();
+- }
+-
+- private boolean checkAvailable(int len) {
+- return buffer.remaining() >= len;
+- }
+- }
+-
+-}
+-
+Index: java/org/apache/tomcat/util/net/NioBlockingSelector.java
+===================================================================
+--- java/org/apache/tomcat/util/net/NioBlockingSelector.java (revision 590752)
++++ java/org/apache/tomcat/util/net/NioBlockingSelector.java (working copy)
+@@ -1,161 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.tomcat.util.net;
+-
+-import java.io.EOFException;
+-import java.io.IOException;
+-import java.net.SocketTimeoutException;
+-import java.nio.ByteBuffer;
+-import java.nio.channels.SelectionKey;
+-import java.util.concurrent.TimeUnit;
+-
+-import org.apache.tomcat.util.net.NioEndpoint.KeyAttachment;
+-
+-public class NioBlockingSelector {
+- public NioBlockingSelector() {
+- }
+-
+- /**
+- * Performs a blocking write using the bytebuffer for data to be written
+- * If the <code>selector</code> parameter is null, then it will perform a busy write that could
+- * take up a lot of CPU cycles.
+- * @param buf ByteBuffer - the buffer containing the data, we will write as long as <code>(buf.hasRemaining()==true)</code>
+- * @param socket SocketChannel - the socket to write data to
+- * @param writeTimeout long - the timeout for this write operation in milliseconds, -1 means no timeout
+- * @return int - returns the number of bytes written
+- * @throws EOFException if write returns -1
+- * @throws SocketTimeoutException if the write times out
+- * @throws IOException if an IO Exception occurs in the underlying socket logic
+- */
+- public static int write(ByteBuffer buf, NioChannel socket, long writeTimeout) throws IOException {
+- SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
+- int written = 0;
+- boolean timedout = false;
+- int keycount = 1; //assume we can write
+- long time = System.currentTimeMillis(); //start the timeout timer
+- try {
+- while ( (!timedout) && buf.hasRemaining()) {
+- if (keycount > 0) { //only write if we were registered for a write
+- int cnt = socket.write(buf); //write the data
+- if (cnt == -1)
+- throw new EOFException();
+- written += cnt;
+- if (cnt > 0) {
+- time = System.currentTimeMillis(); //reset our timeout timer
+- continue; //we successfully wrote, try again without a selector
+- }
+- }
+- if ( key == null ) throw new IOException("Key no longer registered");
+- KeyAttachment att = (KeyAttachment) key.attachment();
+- try {
+- if ( att.getWriteLatch()==null || att.getWriteLatch().getCount()==0) att.startWriteLatch(1);
+- //only register for write if a write has not yet been issued
+- if ( (att.interestOps() & SelectionKey.OP_WRITE) == 0) socket.getPoller().add(socket,SelectionKey.OP_WRITE);
+- att.awaitWriteLatch(writeTimeout,TimeUnit.MILLISECONDS);
+- }catch (InterruptedException ignore) {
+- Thread.interrupted();
+- }
+- if ( att.getWriteLatch()!=null && att.getWriteLatch().getCount()> 0) {
+- //we got interrupted, but we haven't received notification from the poller.
+- keycount = 0;
+- }else {
+- //latch countdown has happened
+- keycount = 1;
+- att.resetWriteLatch();
+- }
+-
+- if (writeTimeout > 0 && (keycount == 0))
+- timedout = (System.currentTimeMillis() - time) >= writeTimeout;
+- } //while
+- if (timedout)
+- throw new SocketTimeoutException();
+- } finally {
+- if (timedout && key != null) {
+- cancelKey(socket, key);
+- }
+- }
+- return written;
+- }
+-
+- private static void cancelKey(final NioChannel socket, final SelectionKey key) {
+- socket.getPoller().addEvent(
+- new Runnable() {
+- public void run() {
+- key.cancel();
+- }
+- });
+- }
+-
+- /**
+- * Performs a blocking read using the bytebuffer for data to be read
+- * If the <code>selector</code> parameter is null, then it will perform a busy read that could
+- * take up a lot of CPU cycles.
+- * @param buf ByteBuffer - the buffer containing the data, we will read as until we have read at least one byte or we timed out
+- * @param socket SocketChannel - the socket to write data to
+- * @param selector Selector - the selector to use for blocking, if null then a busy read will be initiated
+- * @param readTimeout long - the timeout for this read operation in milliseconds, -1 means no timeout
+- * @return int - returns the number of bytes read
+- * @throws EOFException if read returns -1
+- * @throws SocketTimeoutException if the read times out
+- * @throws IOException if an IO Exception occurs in the underlying socket logic
+- */
+- public static int read(ByteBuffer buf, NioChannel socket, long readTimeout) throws IOException {
+- final SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
+- int read = 0;
+- boolean timedout = false;
+- int keycount = 1; //assume we can write
+- long time = System.currentTimeMillis(); //start the timeout timer
+- try {
+- while ( (!timedout) && read == 0) {
+- if (keycount > 0) { //only read if we were registered for a read
+- int cnt = socket.read(buf);
+- if (cnt == -1)
+- throw new EOFException();
+- read += cnt;
+- if (cnt > 0)
+- break;
+- }
+- KeyAttachment att = (KeyAttachment) key.attachment();
+- try {
+- if ( att.getReadLatch()==null || att.getReadLatch().getCount()==0) att.startReadLatch(1);
+- if ( att.interestOps() == 0) socket.getPoller().add(socket,SelectionKey.OP_READ);
+- att.awaitReadLatch(readTimeout,TimeUnit.MILLISECONDS);
+- }catch (InterruptedException ignore) {
+- Thread.interrupted();
+- }
+- if ( att.getReadLatch()!=null && att.getReadLatch().getCount()> 0) {
+- //we got interrupted, but we haven't received notification from the poller.
+- keycount = 0;
+- }else {
+- //latch countdown has happened
+- keycount = 1;
+- att.resetReadLatch();
+- }
+- if (readTimeout > 0 && (keycount == 0))
+- timedout = (System.currentTimeMillis() - time) >= readTimeout;
+- } //while
+- if (timedout)
+- throw new SocketTimeoutException();
+- } finally {
+- if (timedout && key != null) {
+- cancelKey(socket,key);
+- }
+- }
+- return read;
+- }
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/tomcat/util/net/NioChannel.java
+===================================================================
+--- java/org/apache/tomcat/util/net/NioChannel.java (revision 590752)
++++ java/org/apache/tomcat/util/net/NioChannel.java (working copy)
+@@ -1,193 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-
+-package org.apache.tomcat.util.net;
+-
+-import java.io.IOException;
+-import java.nio.ByteBuffer;
+-import java.nio.channels.ByteChannel;
+-import java.nio.channels.SocketChannel;
+-
+-import org.apache.tomcat.util.net.NioEndpoint.Poller;
+-import org.apache.tomcat.util.net.SecureNioChannel.ApplicationBufferHandler;
+-import java.nio.channels.Selector;
+-import java.nio.channels.SelectionKey;
+-
+-/**
+- *
+- * Base class for a SocketChannel wrapper used by the endpoint.
+- * This way, logic for a SSL socket channel remains the same as for
+- * a non SSL, making sure we don't need to code for any exception cases.
+- *
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public class NioChannel implements ByteChannel{
+-
+- protected static ByteBuffer emptyBuf = ByteBuffer.allocate(0);
+-
+- protected SocketChannel sc = null;
+-
+- protected ApplicationBufferHandler bufHandler;
+-
+- protected Poller poller;
+-
+- public NioChannel(SocketChannel channel, ApplicationBufferHandler bufHandler) throws IOException {
+- this.sc = channel;
+- this.bufHandler = bufHandler;
+- }
+-
+- public void reset() throws IOException {
+- bufHandler.getReadBuffer().clear();
+- bufHandler.getWriteBuffer().clear();
+- }
+-
+- public int getBufferSize() {
+- if ( bufHandler == null ) return 0;
+- int size = 0;
+- size += bufHandler.getReadBuffer()!=null?bufHandler.getReadBuffer().capacity():0;
+- size += bufHandler.getWriteBuffer()!=null?bufHandler.getWriteBuffer().capacity():0;
+- return size;
+- }
+-
+- /**
+- * returns true if the network buffer has
+- * been flushed out and is empty
+- * @return boolean
+- */
+- public boolean flush(boolean block, Selector s,long timeout) throws IOException {
+- return true; //no network buffer in the regular channel
+- }
+-
+-
+- /**
+- * Closes this channel.
+- *
+- * @throws IOException If an I/O error occurs
+- * @todo Implement this java.nio.channels.Channel method
+- */
+- public void close() throws IOException {
+- getIOChannel().socket().close();
+- getIOChannel().close();
+- }
+-
+- public void close(boolean force) throws IOException {
+- if (isOpen() || force ) close();
+- }
+- /**
+- * Tells whether or not this channel is open.
+- *
+- * @return <tt>true</tt> if, and only if, this channel is open
+- * @todo Implement this java.nio.channels.Channel method
+- */
+- public boolean isOpen() {
+- return sc.isOpen();
+- }
+-
+- /**
+- * Writes a sequence of bytes to this channel from the given buffer.
+- *
+- * @param src The buffer from which bytes are to be retrieved
+- * @return The number of bytes written, possibly zero
+- * @throws IOException If some other I/O error occurs
+- * @todo Implement this java.nio.channels.WritableByteChannel method
+- */
+- public int write(ByteBuffer src) throws IOException {
+- return sc.write(src);
+- }
+-
+- /**
+- * Reads a sequence of bytes from this channel into the given buffer.
+- *
+- * @param dst The buffer into which bytes are to be transferred
+- * @return The number of bytes read, possibly zero, or <tt>-1</tt> if the channel has reached end-of-stream
+- * @throws IOException If some other I/O error occurs
+- * @todo Implement this java.nio.channels.ReadableByteChannel method
+- */
+- public int read(ByteBuffer dst) throws IOException {
+- return sc.read(dst);
+- }
+-
+- public Object getAttachment(boolean remove) {
+- Poller pol = getPoller();
+- Selector sel = pol!=null?pol.getSelector():null;
+- SelectionKey key = sel!=null?getIOChannel().keyFor(sel):null;
+- Object att = key!=null?key.attachment():null;
+- if (key != null && att != null && remove ) key.attach(null);
+- return att;
+- }
+- /**
+- * getBufHandler
+- *
+- * @return ApplicationBufferHandler
+- * @todo Implement this org.apache.tomcat.util.net.SecureNioChannel method
+- */
+- public ApplicationBufferHandler getBufHandler() {
+- return bufHandler;
+- }
+-
+- public Poller getPoller() {
+- return poller;
+- }
+- /**
+- * getIOChannel
+- *
+- * @return SocketChannel
+- * @todo Implement this org.apache.tomcat.util.net.SecureNioChannel method
+- */
+- public SocketChannel getIOChannel() {
+- return sc;
+- }
+-
+- /**
+- * isClosing
+- *
+- * @return boolean
+- * @todo Implement this org.apache.tomcat.util.net.SecureNioChannel method
+- */
+- public boolean isClosing() {
+- return false;
+- }
+-
+- /**
+- * isInitHandshakeComplete
+- *
+- * @return boolean
+- * @todo Implement this org.apache.tomcat.util.net.SecureNioChannel method
+- */
+- public boolean isInitHandshakeComplete() {
+- return true;
+- }
+-
+- public int handshake(boolean read, boolean write) throws IOException {
+- return 0;
+- }
+-
+- public void setPoller(Poller poller) {
+- this.poller = poller;
+- }
+-
+- public void setIOChannel(SocketChannel IOChannel) {
+- this.sc = IOChannel;
+- }
+-
+- public String toString() {
+- return super.toString()+":"+this.sc.toString();
+- }
+-
+-}
+Index: java/org/apache/tomcat/util/net/NioSelectorPool.java
+===================================================================
+--- java/org/apache/tomcat/util/net/NioSelectorPool.java (revision 590752)
++++ java/org/apache/tomcat/util/net/NioSelectorPool.java (working copy)
+@@ -1,264 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.tomcat.util.net;
+-
+-import java.util.concurrent.atomic.AtomicInteger;
+-import java.nio.channels.Selector;
+-import java.io.IOException;
+-import java.util.NoSuchElementException;
+-import java.nio.ByteBuffer;
+-import java.nio.channels.SelectionKey;
+-import java.io.EOFException;
+-import java.net.SocketTimeoutException;
+-import java.util.concurrent.ConcurrentLinkedQueue;
+-import org.apache.juli.logging.Log;
+-import org.apache.juli.logging.LogFactory;
+-
+-/**
+- *
+- * Thread safe non blocking selector pool
+- * @author Filip Hanik
+- * @version 1.0
+- * @since 6.0
+- */
+-
+-public class NioSelectorPool {
+- protected static Log log = LogFactory.getLog(NioSelectorPool.class);
+-
+- protected final static boolean SHARED =
+- Boolean.valueOf(System.getProperty("org.apache.tomcat.util.net.NioSelectorShared", "true")).booleanValue();
+- protected static Selector SHARED_SELECTOR;
+-
+- protected int maxSelectors = 200;
+- protected int maxSpareSelectors = -1;
+- protected boolean enabled = true;
+- protected AtomicInteger active = new AtomicInteger(0);
+- protected AtomicInteger spare = new AtomicInteger(0);
+- protected ConcurrentLinkedQueue<Selector> selectors = new ConcurrentLinkedQueue<Selector>();
+-
+- protected static Selector getSharedSelector() throws IOException {
+- if (SHARED && SHARED_SELECTOR == null) {
+- synchronized ( NioSelectorPool.class ) {
+- if ( SHARED_SELECTOR == null ) {
+- SHARED_SELECTOR = Selector.open();
+- log.info("Using a shared selector for servlet write/read");
+- }
+- }
+- }
+- return SHARED_SELECTOR;
+- }
+-
+- public Selector get() throws IOException{
+- if ( SHARED ) {
+- return getSharedSelector();
+- }
+- if ( (!enabled) || active.incrementAndGet() >= maxSelectors ) {
+- if ( enabled ) active.decrementAndGet();
+- return null;
+- }
+- Selector s = null;
+- try {
+- s = selectors.size()>0?selectors.poll():null;
+- if (s == null) s = Selector.open();
+- else spare.decrementAndGet();
+-
+- }catch (NoSuchElementException x ) {
+- try {s = Selector.open();}catch (IOException iox){}
+- } finally {
+- if ( s == null ) active.decrementAndGet();//we were unable to find a selector
+- }
+- return s;
+- }
+-
+-
+-
+- public void put(Selector s) throws IOException {
+- if ( SHARED ) return;
+- if ( enabled ) active.decrementAndGet();
+- if ( enabled && (maxSpareSelectors==-1 || spare.get() < Math.min(maxSpareSelectors,maxSelectors)) ) {
+- spare.incrementAndGet();
+- selectors.offer(s);
+- }
+- else s.close();
+- }
+-
+- public void close() throws IOException {
+- enabled = false;
+- Selector s;
+- while ( (s = selectors.poll()) != null ) s.close();
+- spare.set(0);
+- active.set(0);
+- if ( SHARED && getSharedSelector()!=null ) {
+- getSharedSelector().close();
+- SHARED_SELECTOR = null;
+- }
+- }
+-
+- public void open() throws IOException {
+- enabled = true;
+- getSharedSelector();
+- }
+-
+- /**
+- * Performs a blocking write using the bytebuffer for data to be written and a selector to block.
+- * If the <code>selector</code> parameter is null, then it will perform a busy write that could
+- * take up a lot of CPU cycles.
+- * @param buf ByteBuffer - the buffer containing the data, we will write as long as <code>(buf.hasRemaining()==true)</code>
+- * @param socket SocketChannel - the socket to write data to
+- * @param selector Selector - the selector to use for blocking, if null then a busy write will be initiated
+- * @param writeTimeout long - the timeout for this write operation in milliseconds, -1 means no timeout
+- * @return int - returns the number of bytes written
+- * @throws EOFException if write returns -1
+- * @throws SocketTimeoutException if the write times out
+- * @throws IOException if an IO Exception occurs in the underlying socket logic
+- */
+- public int write(ByteBuffer buf, NioChannel socket, Selector selector, long writeTimeout) throws IOException {
+- return write(buf,socket,selector,writeTimeout,true);
+- }
+-
+- public int write(ByteBuffer buf, NioChannel socket, Selector selector, long writeTimeout, boolean block) throws IOException {
+- if ( SHARED && block) {
+- return NioBlockingSelector.write(buf,socket,writeTimeout);
+- }
+- SelectionKey key = null;
+- int written = 0;
+- boolean timedout = false;
+- int keycount = 1; //assume we can write
+- long time = System.currentTimeMillis(); //start the timeout timer
+- try {
+- while ( (!timedout) && buf.hasRemaining() ) {
+- int cnt = 0;
+- if ( keycount > 0 ) { //only write if we were registered for a write
+- cnt = socket.write(buf); //write the data
+- if (cnt == -1) throw new EOFException();
+- written += cnt;
+- if (cnt > 0) {
+- time = System.currentTimeMillis(); //reset our timeout timer
+- continue; //we successfully wrote, try again without a selector
+- }
+- if (cnt==0 && (!block)) break; //don't block
+- }
+- if ( selector != null ) {
+- //register OP_WRITE to the selector
+- if (key==null) key = socket.getIOChannel().register(selector, SelectionKey.OP_WRITE);
+- else key.interestOps(SelectionKey.OP_WRITE);
+- keycount = selector.select(writeTimeout);
+- }
+- if (writeTimeout > 0 && (selector == null || keycount == 0) ) timedout = (System.currentTimeMillis()-time)>=writeTimeout;
+- }//while
+- if ( timedout ) throw new SocketTimeoutException();
+- } finally {
+- if (key != null) {
+- key.cancel();
+- if (selector != null) selector.selectNow();//removes the key from this selector
+- }
+- }
+- return written;
+- }
+-
+- /**
+- * Performs a blocking read using the bytebuffer for data to be read and a selector to block.
+- * If the <code>selector</code> parameter is null, then it will perform a busy read that could
+- * take up a lot of CPU cycles.
+- * @param buf ByteBuffer - the buffer containing the data, we will read as until we have read at least one byte or we timed out
+- * @param socket SocketChannel - the socket to write data to
+- * @param selector Selector - the selector to use for blocking, if null then a busy read will be initiated
+- * @param readTimeout long - the timeout for this read operation in milliseconds, -1 means no timeout
+- * @return int - returns the number of bytes read
+- * @throws EOFException if read returns -1
+- * @throws SocketTimeoutException if the read times out
+- * @throws IOException if an IO Exception occurs in the underlying socket logic
+- */
+- public int read(ByteBuffer buf, NioChannel socket, Selector selector, long readTimeout) throws IOException {
+- return read(buf,socket,selector,readTimeout,true);
+- }
+- /**
+- * Performs a read using the bytebuffer for data to be read and a selector to register for events should
+- * you have the block=true.
+- * If the <code>selector</code> parameter is null, then it will perform a busy read that could
+- * take up a lot of CPU cycles.
+- * @param buf ByteBuffer - the buffer containing the data, we will read as until we have read at least one byte or we timed out
+- * @param socket SocketChannel - the socket to write data to
+- * @param selector Selector - the selector to use for blocking, if null then a busy read will be initiated
+- * @param readTimeout long - the timeout for this read operation in milliseconds, -1 means no timeout
+- * @param block - true if you want to block until data becomes available or timeout time has been reached
+- * @return int - returns the number of bytes read
+- * @throws EOFException if read returns -1
+- * @throws SocketTimeoutException if the read times out
+- * @throws IOException if an IO Exception occurs in the underlying socket logic
+- */
+- public int read(ByteBuffer buf, NioChannel socket, Selector selector, long readTimeout, boolean block) throws IOException {
+- if ( SHARED && block) {
+- return NioBlockingSelector.read(buf,socket,readTimeout);
+- }
+- SelectionKey key = null;
+- int read = 0;
+- boolean timedout = false;
+- int keycount = 1; //assume we can write
+- long time = System.currentTimeMillis(); //start the timeout timer
+- try {
+- while ( (!timedout) ) {
+- int cnt = 0;
+- if ( keycount > 0 ) { //only read if we were registered for a read
+- cnt = socket.read(buf);
+- if (cnt == -1) throw new EOFException();
+- read += cnt;
+- if (cnt > 0) continue; //read some more
+- if (cnt==0 && (read>0 || (!block) ) ) break; //we are done reading
+- }
+- if ( selector != null ) {//perform a blocking read
+- //register OP_WRITE to the selector
+- if (key==null) key = socket.getIOChannel().register(selector, SelectionKey.OP_READ);
+- else key.interestOps(SelectionKey.OP_READ);
+- keycount = selector.select(readTimeout);
+- }
+- if (readTimeout > 0 && (selector == null || keycount == 0) ) timedout = (System.currentTimeMillis()-time)>=readTimeout;
+- }//while
+- if ( timedout ) throw new SocketTimeoutException();
+- } finally {
+- if (key != null) {
+- key.cancel();
+- if (selector != null) selector.selectNow();//removes the key from this selector
+- }
+- }
+- return read;
+- }
+-
+- public void setMaxSelectors(int maxSelectors) {
+- this.maxSelectors = maxSelectors;
+- }
+-
+- public void setMaxSpareSelectors(int maxSpareSelectors) {
+- this.maxSpareSelectors = maxSpareSelectors;
+- }
+-
+- public void setEnabled(boolean enabled) {
+- this.enabled = enabled;
+- }
+-
+- public int getMaxSelectors() {
+- return maxSelectors;
+- }
+-
+- public int getMaxSpareSelectors() {
+- return maxSpareSelectors;
+- }
+-
+- public boolean isEnabled() {
+- return enabled;
+- }
+-}
+\ No newline at end of file
+Index: java/org/apache/tomcat/util/net/SecureNioChannel.java
+===================================================================
+--- java/org/apache/tomcat/util/net/SecureNioChannel.java (revision 590752)
++++ java/org/apache/tomcat/util/net/SecureNioChannel.java (working copy)
+@@ -1,473 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.tomcat.util.net;
+-
+-import java.io.IOException;
+-import java.nio.ByteBuffer;
+-import java.nio.channels.SelectionKey;
+-import java.nio.channels.SocketChannel;
+-import javax.net.ssl.SSLEngine;
+-import javax.net.ssl.SSLEngineResult;
+-import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+-import javax.net.ssl.SSLEngineResult.Status;
+-import java.nio.channels.Selector;
+-
+-/**
+- *
+- * Implementation of a secure socket channel
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-
+-public class SecureNioChannel extends NioChannel {
+-
+- protected ByteBuffer netInBuffer;
+- protected ByteBuffer netOutBuffer;
+-
+- protected SSLEngine sslEngine;
+-
+- protected boolean initHandshakeComplete = false;
+- protected HandshakeStatus initHandshakeStatus; //gets set by begin handshake
+-
+- protected boolean closed = false;
+- protected boolean closing = false;
+-
+- protected NioSelectorPool pool;
+-
+- public SecureNioChannel(SocketChannel channel, SSLEngine engine,
+- ApplicationBufferHandler bufHandler, NioSelectorPool pool) throws IOException {
+- super(channel,bufHandler);
+- this.sslEngine = engine;
+- int appBufSize = sslEngine.getSession().getApplicationBufferSize();
+- int netBufSize = sslEngine.getSession().getPacketBufferSize();
+- //allocate network buffers - TODO, add in optional direct non-direct buffers
+- if ( netInBuffer == null ) netInBuffer = ByteBuffer.allocateDirect(netBufSize);
+- if ( netOutBuffer == null ) netOutBuffer = ByteBuffer.allocateDirect(netBufSize);
+-
+- //selector pool for blocking operations
+- this.pool = pool;
+-
+- //ensure that the application has a large enough read/write buffers
+- //by doing this, we should not encounter any buffer overflow errors
+- bufHandler.expand(bufHandler.getReadBuffer(), appBufSize);
+- bufHandler.expand(bufHandler.getWriteBuffer(), appBufSize);
+- reset();
+- }
+-
+- public void reset(SSLEngine engine) throws IOException {
+- this.sslEngine = engine;
+- reset();
+- }
+- public void reset() throws IOException {
+- super.reset();
+- netOutBuffer.position(0);
+- netOutBuffer.limit(0);
+- netInBuffer.position(0);
+- netInBuffer.limit(0);
+- initHandshakeComplete = false;
+- closed = false;
+- closing = false;
+- //initiate handshake
+- sslEngine.beginHandshake();
+- initHandshakeStatus = sslEngine.getHandshakeStatus();
+- }
+-
+- public int getBufferSize() {
+- int size = super.getBufferSize();
+- size += netInBuffer!=null?netInBuffer.capacity():0;
+- size += netOutBuffer!=null?netOutBuffer.capacity():0;
+- return size;
+- }
+-
+-
+-//===========================================================================================
+-// NIO SSL METHODS
+-//===========================================================================================
+- /**
+- * returns true if the network buffer has
+- * been flushed out and is empty
+- * @return boolean
+- */
+- public boolean flush(boolean block, Selector s, long timeout) throws IOException {
+- if (!block) {
+- flush(netOutBuffer);
+- } else {
+- pool.write(netOutBuffer, this, s, timeout);
+- }
+- return !netOutBuffer.hasRemaining();
+- }
+-
+- /**
+- * Flushes the buffer to the network, non blocking
+- * @param buf ByteBuffer
+- * @return boolean true if the buffer has been emptied out, false otherwise
+- * @throws IOException
+- */
+- protected boolean flush(ByteBuffer buf) throws IOException {
+- int remaining = buf.remaining();
+- if ( remaining > 0 ) {
+- int written = sc.write(buf);
+- return written >= remaining;
+- }else {
+- return true;
+- }
+- }
+-
+- /**
+- * Performs SSL handshake, non blocking, but performs NEED_TASK on the same thread.<br>
+- * Hence, you should never call this method using your Acceptor thread, as you would slow down
+- * your system significantly.<br>
+- * The return for this operation is 0 if the handshake is complete and a positive value if it is not complete.
+- * In the event of a positive value coming back, reregister the selection key for the return values interestOps.
+- * @param read boolean - true if the underlying channel is readable
+- * @param write boolean - true if the underlying channel is writable
+- * @return int - 0 if hand shake is complete, otherwise it returns a SelectionKey interestOps value
+- * @throws IOException
+- */
+- public int handshake(boolean read, boolean write) throws IOException {
+- if ( initHandshakeComplete ) return 0; //we have done our initial handshake
+-
+- if (!flush(netOutBuffer)) return SelectionKey.OP_WRITE; //we still have data to write
+-
+- SSLEngineResult handshake = null;
+-
+- while (!initHandshakeComplete) {
+- switch ( initHandshakeStatus ) {
+- case NOT_HANDSHAKING: {
+- //should never happen
+- throw new IOException("NOT_HANDSHAKING during handshake");
+- }
+- case FINISHED: {
+- //we are complete if we have delivered the last package
+- initHandshakeComplete = !netOutBuffer.hasRemaining();
+- //return 0 if we are complete, otherwise we still have data to write
+- return initHandshakeComplete?0:SelectionKey.OP_WRITE;
+- }
+- case NEED_WRAP: {
+- //perform the wrap function
+- handshake = handshakeWrap(write);
+- if ( handshake.getStatus() == Status.OK ){
+- if (initHandshakeStatus == HandshakeStatus.NEED_TASK)
+- initHandshakeStatus = tasks();
+- } else {
+- //wrap should always work with our buffers
+- throw new IOException("Unexpected status:" + handshake.getStatus() + " during handshake WRAP.");
+- }
+- if ( initHandshakeStatus != HandshakeStatus.NEED_UNWRAP || (!flush(netOutBuffer)) ) {
+- //should actually return OP_READ if we have NEED_UNWRAP
+- return SelectionKey.OP_WRITE;
+- }
+- //fall down to NEED_UNWRAP on the same call, will result in a
+- //BUFFER_UNDERFLOW if it needs data
+- }
+- case NEED_UNWRAP: {
+- //perform the unwrap function
+- handshake = handshakeUnwrap(read);
+- if ( handshake.getStatus() == Status.OK ) {
+- if (initHandshakeStatus == HandshakeStatus.NEED_TASK)
+- initHandshakeStatus = tasks();
+- } else if ( handshake.getStatus() == Status.BUFFER_UNDERFLOW ){
+- //read more data, reregister for OP_READ
+- return SelectionKey.OP_READ;
+- } else {
+- throw new IOException("Invalid handshake status:"+initHandshakeStatus+" during handshake UNWRAP.");
+- }//switch
+- break;
+- }
+- case NEED_TASK: {
+- initHandshakeStatus = tasks();
+- break;
+- }
+- default: throw new IllegalStateException("Invalid handshake status:"+initHandshakeStatus);
+- }//switch
+- }//while
+- //return 0 if we are complete, otherwise reregister for any activity that
+- //would cause this method to be called again.
+- return initHandshakeComplete?0:(SelectionKey.OP_WRITE|SelectionKey.OP_READ);
+- }
+-
+- /**
+- * Executes all the tasks needed on the same thread.
+- * @return HandshakeStatus
+- */
+- protected SSLEngineResult.HandshakeStatus tasks() {
+- Runnable r = null;
+- while ( (r = sslEngine.getDelegatedTask()) != null) {
+- r.run();
+- }
+- return sslEngine.getHandshakeStatus();
+- }
+-
+- /**
+- * Performs the WRAP function
+- * @param doWrite boolean
+- * @return SSLEngineResult
+- * @throws IOException
+- */
+- protected SSLEngineResult handshakeWrap(boolean doWrite) throws IOException {
+- //this should never be called with a network buffer that contains data
+- //so we can clear it here.
+- netOutBuffer.clear();
+- //perform the wrap
+- SSLEngineResult result = sslEngine.wrap(bufHandler.getWriteBuffer(), netOutBuffer);
+- //prepare the results to be written
+- netOutBuffer.flip();
+- //set the status
+- initHandshakeStatus = result.getHandshakeStatus();
+- //optimization, if we do have a writable channel, write it now
+- if ( doWrite ) flush(netOutBuffer);
+- return result;
+- }
+-
+- /**
+- * Perform handshake unwrap
+- * @param doread boolean
+- * @return SSLEngineResult
+- * @throws IOException
+- */
+- protected SSLEngineResult handshakeUnwrap(boolean doread) throws IOException {
+-
+- if (netInBuffer.position() == netInBuffer.limit()) {
+- //clear the buffer if we have emptied it out on data
+- netInBuffer.clear();
+- }
+- if ( doread ) {
+- //if we have data to read, read it
+- int read = sc.read(netInBuffer);
+- if (read == -1) throw new IOException("EOF encountered during handshake.");
+- }
+- SSLEngineResult result;
+- boolean cont = false;
+- //loop while we can perform pure SSLEngine data
+- do {
+- //prepare the buffer with the incoming data
+- netInBuffer.flip();
+- //call unwrap
+- result = sslEngine.unwrap(netInBuffer, bufHandler.getReadBuffer());
+- //compact the buffer, this is an optional method, wonder what would happen if we didn't
+- netInBuffer.compact();
+- //read in the status
+- initHandshakeStatus = result.getHandshakeStatus();
+- if ( result.getStatus() == SSLEngineResult.Status.OK &&
+- result.getHandshakeStatus() == HandshakeStatus.NEED_TASK ) {
+- //execute tasks if we need to
+- initHandshakeStatus = tasks();
+- }
+- //perform another unwrap?
+- cont = result.getStatus() == SSLEngineResult.Status.OK &&
+- initHandshakeStatus == HandshakeStatus.NEED_UNWRAP;
+- }while ( cont );
+- return result;
+- }
+-
+- /**
+- * Sends a SSL close message, will not physically close the connection here.<br>
+- * To close the connection, you could do something like
+- * <pre><code>
+- * close();
+- * while (isOpen() && !myTimeoutFunction()) Thread.sleep(25);
+- * if ( isOpen() ) close(true); //forces a close if you timed out
+- * </code></pre>
+- * @throws IOException if an I/O error occurs
+- * @throws IOException if there is data on the outgoing network buffer and we are unable to flush it
+- * @todo Implement this java.io.Closeable method
+- */
+- public void close() throws IOException {
+- if (closing) return;
+- closing = true;
+- sslEngine.closeOutbound();
+-
+- if (!flush(netOutBuffer)) {
+- throw new IOException("Remaining data in the network buffer, can't send SSL close message, force a close with close(true) instead");
+- }
+- //prep the buffer for the close message
+- netOutBuffer.clear();
+- //perform the close, since we called sslEngine.closeOutbound
+- SSLEngineResult handshake = sslEngine.wrap(getEmptyBuf(), netOutBuffer);
+- //we should be in a close state
+- if (handshake.getStatus() != SSLEngineResult.Status.CLOSED) {
+- throw new IOException("Invalid close state, will not send network data.");
+- }
+- //prepare the buffer for writing
+- netOutBuffer.flip();
+- //if there is data to be written
+- flush(netOutBuffer);
+-
+- //is the channel closed?
+- closed = (!netOutBuffer.hasRemaining() && (handshake.getHandshakeStatus() != HandshakeStatus.NEED_WRAP));
+- }
+-
+- /**
+- * Force a close, can throw an IOException
+- * @param force boolean
+- * @throws IOException
+- */
+- public void close(boolean force) throws IOException {
+- try {
+- close();
+- }finally {
+- if ( force || closed ) {
+- closed = true;
+- sc.socket().close();
+- sc.close();
+- }
+- }
+- }
+-
+- /**
+- * Reads a sequence of bytes from this channel into the given buffer.
+- *
+- * @param dst The buffer into which bytes are to be transferred
+- * @return The number of bytes read, possibly zero, or <tt>-1</tt> if the channel has reached end-of-stream
+- * @throws IOException If some other I/O error occurs
+- * @throws IllegalArgumentException if the destination buffer is different than bufHandler.getReadBuffer()
+- * @todo Implement this java.nio.channels.ReadableByteChannel method
+- */
+- public int read(ByteBuffer dst) throws IOException {
+- //if we want to take advantage of the expand function, make sure we only use the ApplicationBufferHandler's buffers
+- if ( dst != bufHandler.getReadBuffer() ) throw new IllegalArgumentException("You can only read using the application read buffer provided by the handler.");
+- //are we in the middle of closing or closed?
+- if ( closing || closed) return -1;
+- //did we finish our handshake?
+- if (!initHandshakeComplete) throw new IllegalStateException("Handshake incomplete, you must complete handshake before reading data.");
+-
+- //read from the network
+- int netread = sc.read(netInBuffer);
+- //did we reach EOF? if so send EOF up one layer.
+- if (netread == -1) return -1;
+-
+- //the data read
+- int read = 0;
+- //the SSL engine result
+- SSLEngineResult unwrap;
+- do {
+- //prepare the buffer
+- netInBuffer.flip();
+- //unwrap the data
+- unwrap = sslEngine.unwrap(netInBuffer, dst);
+- //compact the buffer
+- netInBuffer.compact();
+-
+- if ( unwrap.getStatus()==Status.OK || unwrap.getStatus()==Status.BUFFER_UNDERFLOW ) {
+- //we did receive some data, add it to our total
+- read += unwrap.bytesProduced();
+- //perform any tasks if needed
+- if (unwrap.getHandshakeStatus() == HandshakeStatus.NEED_TASK) tasks();
+- //if we need more network data, then bail out for now.
+- if ( unwrap.getStatus() == Status.BUFFER_UNDERFLOW ) break;
+- }else if ( unwrap.getStatus()==Status.BUFFER_OVERFLOW && read>0 ) {
+- //buffer overflow can happen, if we have read data, then
+- //empty out the dst buffer before we do another read
+- break;
+- }else {
+- //here we should trap BUFFER_OVERFLOW and call expand on the buffer
+- //for now, throw an exception, as we initialized the buffers
+- //in the constructor
+- throw new IOException("Unable to unwrap data, invalid status: " + unwrap.getStatus());
+- }
+- } while ( (netInBuffer.position() != 0)); //continue to unwrapping as long as the input buffer has stuff
+- return (read);
+- }
+-
+- /**
+- * Writes a sequence of bytes to this channel from the given buffer.
+- *
+- * @param src The buffer from which bytes are to be retrieved
+- * @return The number of bytes written, possibly zero
+- * @throws IOException If some other I/O error occurs
+- * @todo Implement this java.nio.channels.WritableByteChannel method
+- */
+- public int write(ByteBuffer src) throws IOException {
+- if ( src == this.netOutBuffer ) {
+- //we can get here through a recursive call
+- //by using the NioBlockingSelector
+- int written = sc.write(src);
+- return written;
+- } else {
+- //make sure we can handle expand, and that we only use on buffer
+- if ( src != bufHandler.getWriteBuffer() ) throw new IllegalArgumentException("You can only write using the application write buffer provided by the handler.");
+- //are we closing or closed?
+- if ( closing || closed) throw new IOException("Channel is in closing state.");
+-
+- //the number of bytes written
+- int written = 0;
+-
+- if (!flush(netOutBuffer)) {
+- //we haven't emptied out the buffer yet
+- return written;
+- }
+-
+- /*
+- * The data buffer is empty, we can reuse the entire buffer.
+- */
+- netOutBuffer.clear();
+-
+- SSLEngineResult result = sslEngine.wrap(src, netOutBuffer);
+- written = result.bytesConsumed();
+- netOutBuffer.flip();
+-
+- if (result.getStatus() == Status.OK) {
+- if (result.getHandshakeStatus() == HandshakeStatus.NEED_TASK) tasks();
+- } else {
+- throw new IOException("Unable to wrap data, invalid engine state: " +result.getStatus());
+- }
+-
+- //force a flush
+- flush(netOutBuffer);
+- return written;
+- }
+- }
+-
+- /**
+- * Callback interface to be able to expand buffers
+- * when buffer overflow exceptions happen
+- */
+- public static interface ApplicationBufferHandler {
+- public ByteBuffer expand(ByteBuffer buffer, int remaining);
+- public ByteBuffer getReadBuffer();
+- public ByteBuffer getWriteBuffer();
+- }
+-
+- public ApplicationBufferHandler getBufHandler() {
+- return bufHandler;
+- }
+-
+- public boolean isInitHandshakeComplete() {
+- return initHandshakeComplete;
+- }
+-
+- public boolean isClosing() {
+- return closing;
+- }
+-
+- public SSLEngine getSslEngine() {
+- return sslEngine;
+- }
+-
+- public ByteBuffer getEmptyBuf() {
+- return emptyBuf;
+- }
+-
+- public void setBufHandler(ApplicationBufferHandler bufHandler) {
+- this.bufHandler = bufHandler;
+- }
+-
+- public SocketChannel getIOChannel() {
+- return sc;
+- }
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/tomcat/util/net/NioEndpoint.java
+===================================================================
+--- java/org/apache/tomcat/util/net/NioEndpoint.java (revision 590752)
++++ java/org/apache/tomcat/util/net/NioEndpoint.java (working copy)
+@@ -1,2197 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.tomcat.util.net;
+-
+-import java.io.File;
+-import java.io.FileInputStream;
+-import java.io.IOException;
+-import java.net.InetAddress;
+-import java.net.InetSocketAddress;
+-import java.net.Socket;
+-import java.nio.ByteBuffer;
+-import java.nio.channels.CancelledKeyException;
+-import java.nio.channels.FileChannel;
+-import java.nio.channels.SelectionKey;
+-import java.nio.channels.Selector;
+-import java.nio.channels.ServerSocketChannel;
+-import java.nio.channels.SocketChannel;
+-import java.security.KeyStore;
+-import java.util.Collection;
+-import java.util.Iterator;
+-import java.util.Set;
+-import java.util.StringTokenizer;
+-import java.util.concurrent.ConcurrentLinkedQueue;
+-import java.util.concurrent.CountDownLatch;
+-import java.util.concurrent.Executor;
+-import java.util.concurrent.LinkedBlockingQueue;
+-import java.util.concurrent.ThreadFactory;
+-import java.util.concurrent.ThreadPoolExecutor;
+-import java.util.concurrent.TimeUnit;
+-import java.util.concurrent.atomic.AtomicInteger;
+-import java.util.concurrent.atomic.AtomicLong;
+-import javax.net.ssl.KeyManagerFactory;
+-import javax.net.ssl.SSLContext;
+-import javax.net.ssl.SSLEngine;
+-import javax.net.ssl.TrustManagerFactory;
+-
+-import org.apache.juli.logging.Log;
+-import org.apache.juli.logging.LogFactory;
+-import org.apache.tomcat.util.IntrospectionUtils;
+-import org.apache.tomcat.util.net.SecureNioChannel.ApplicationBufferHandler;
+-import org.apache.tomcat.util.res.StringManager;
+-
+-/**
+- * NIO tailored thread pool, providing the following services:
+- * <ul>
+- * <li>Socket acceptor thread</li>
+- * <li>Socket poller thread</li>
+- * <li>Worker threads pool</li>
+- * </ul>
+- *
+- * When switching to Java 5, there's an opportunity to use the virtual
+- * machine's thread pool.
+- *
+- * @author Mladen Turk
+- * @author Remy Maucherat
+- * @author Filip Hanik
+- */
+-public class NioEndpoint {
+-
+-
+- // -------------------------------------------------------------- Constants
+-
+-
+- protected static Log log = LogFactory.getLog(NioEndpoint.class);
+-
+- protected static StringManager sm =
+- StringManager.getManager("org.apache.tomcat.util.net.res");
+-
+-
+- /**
+- * The Request attribute key for the cipher suite.
+- */
+- public static final String CIPHER_SUITE_KEY = "javax.servlet.request.cipher_suite";
+-
+- /**
+- * The Request attribute key for the key size.
+- */
+- public static final String KEY_SIZE_KEY = "javax.servlet.request.key_size";
+-
+- /**
+- * The Request attribute key for the client certificate chain.
+- */
+- public static final String CERTIFICATE_KEY = "javax.servlet.request.X509Certificate";
+-
+- /**
+- * The Request attribute key for the session id.
+- * This one is a Tomcat extension to the Servlet spec.
+- */
+- public static final String SESSION_ID_KEY = "javax.servlet.request.ssl_session";
+-
+- public static final int OP_REGISTER = -1; //register interest op
+-
+- // ----------------------------------------------------------------- Fields
+-
+-
+- /**
+- * Available workers.
+- */
+- protected WorkerStack workers = null;
+-
+-
+- /**
+- * Running state of the endpoint.
+- */
+- protected volatile boolean running = false;
+-
+-
+- /**
+- * Will be set to true whenever the endpoint is paused.
+- */
+- protected volatile boolean paused = false;
+-
+-
+- /**
+- * Track the initialization state of the endpoint.
+- */
+- protected boolean initialized = false;
+-
+-
+- /**
+- * Current worker threads busy count.
+- */
+- protected int curThreadsBusy = 0;
+-
+-
+- /**
+- * Current worker threads count.
+- */
+- protected int curThreads = 0;
+-
+-
+- /**
+- * Sequence number used to generate thread names.
+- */
+- protected int sequence = 0;
+-
+- protected NioSelectorPool selectorPool = new NioSelectorPool();
+-
+- /**
+- * Server socket "pointer".
+- */
+- protected ServerSocketChannel serverSock = null;
+-
+- /**
+- * use send file
+- */
+- protected boolean useSendfile = true;
+-
+- /**
+- * The size of the OOM parachute.
+- */
+- protected int oomParachute = 1024*1024;
+- /**
+- * The oom parachute, when an OOM error happens,
+- * will release the data, giving the JVM instantly
+- * a chunk of data to be able to recover with.
+- */
+- protected byte[] oomParachuteData = null;
+-
+- /**
+- * Make sure this string has already been allocated
+- */
+- protected static final String oomParachuteMsg =
+- "SEVERE:Memory usage is low, parachute is non existent, your system may start failing.";
+-
+- /**
+- * Keep track of OOM warning messages.
+- */
+- long lastParachuteCheck = System.currentTimeMillis();
+-
+-
+-
+-
+-
+- /**
+- * Cache for SocketProcessor objects
+- */
+- protected ConcurrentLinkedQueue<SocketProcessor> processorCache = new ConcurrentLinkedQueue<SocketProcessor>() {
+- protected AtomicInteger size = new AtomicInteger(0);
+- public boolean offer(SocketProcessor sc) {
+- sc.reset(null,null);
+- boolean offer = socketProperties.getProcessorCache()==-1?true:size.get()<socketProperties.getProcessorCache();
+- //avoid over growing our cache or add after we have stopped
+- if ( running && (!paused) && (offer) ) {
+- boolean result = super.offer(sc);
+- if ( result ) {
+- size.incrementAndGet();
+- }
+- return result;
+- }
+- else return false;
+- }
+-
+- public SocketProcessor poll() {
+- SocketProcessor result = super.poll();
+- if ( result != null ) {
+- size.decrementAndGet();
+- }
+- return result;
+- }
+-
+- public void clear() {
+- super.clear();
+- size.set(0);
+- }
+- };
+-
+-
+- /**
+- * Cache for key attachment objects
+- */
+- protected ConcurrentLinkedQueue<KeyAttachment> keyCache = new ConcurrentLinkedQueue<KeyAttachment>() {
+- protected AtomicInteger size = new AtomicInteger(0);
+- public boolean offer(KeyAttachment ka) {
+- ka.reset();
+- boolean offer = socketProperties.getKeyCache()==-1?true:size.get()<socketProperties.getKeyCache();
+- //avoid over growing our cache or add after we have stopped
+- if ( running && (!paused) && (offer) ) {
+- boolean result = super.offer(ka);
+- if ( result ) {
+- size.incrementAndGet();
+- }
+- return result;
+- }
+- else return false;
+- }
+-
+- public KeyAttachment poll() {
+- KeyAttachment result = super.poll();
+- if ( result != null ) {
+- size.decrementAndGet();
+- }
+- return result;
+- }
+-
+- public void clear() {
+- super.clear();
+- size.set(0);
+- }
+- };
+-
+-
+- /**
+- * Cache for poller events
+- */
+- protected ConcurrentLinkedQueue<PollerEvent> eventCache = new ConcurrentLinkedQueue<PollerEvent>() {
+- protected AtomicInteger size = new AtomicInteger(0);
+- public boolean offer(PollerEvent pe) {
+- pe.reset();
+- boolean offer = socketProperties.getEventCache()==-1?true:size.get()<socketProperties.getEventCache();
+- //avoid over growing our cache or add after we have stopped
+- if ( running && (!paused) && (offer) ) {
+- boolean result = super.offer(pe);
+- if ( result ) {
+- size.incrementAndGet();
+- }
+- return result;
+- }
+- else return false;
+- }
+-
+- public PollerEvent poll() {
+- PollerEvent result = super.poll();
+- if ( result != null ) {
+- size.decrementAndGet();
+- }
+- return result;
+- }
+-
+- public void clear() {
+- super.clear();
+- size.set(0);
+- }
+- };
+-
+-
+- /**
+- * Bytebuffer cache, each channel holds a set of buffers (two, except for SSL holds four)
+- */
+- protected ConcurrentLinkedQueue<NioChannel> nioChannels = new ConcurrentLinkedQueue<NioChannel>() {
+- protected AtomicInteger size = new AtomicInteger(0);
+- protected AtomicInteger bytes = new AtomicInteger(0);
+- public boolean offer(NioChannel socket) {
+- boolean offer = socketProperties.getBufferPool()==-1?true:size.get()<socketProperties.getBufferPool();
+- offer = offer && (socketProperties.getBufferPoolSize()==-1?true:(bytes.get()+socket.getBufferSize())<socketProperties.getBufferPoolSize());
+- //avoid over growing our cache or add after we have stopped
+- if ( running && (!paused) && (offer) ) {
+- boolean result = super.offer(socket);
+- if ( result ) {
+- size.incrementAndGet();
+- bytes.addAndGet(socket.getBufferSize());
+- }
+- return result;
+- }
+- else return false;
+- }
+-
+- public NioChannel poll() {
+- NioChannel result = super.poll();
+- if ( result != null ) {
+- size.decrementAndGet();
+- bytes.addAndGet(-result.getBufferSize());
+- }
+- return result;
+- }
+-
+- public void clear() {
+- super.clear();
+- size.set(0);
+- bytes.set(0);
+- }
+- };
+-
+-
+-
+- // ------------------------------------------------------------- Properties
+-
+-
+- /**
+- * External Executor based thread pool.
+- */
+- protected Executor executor = null;
+- public void setExecutor(Executor executor) { this.executor = executor; }
+- public Executor getExecutor() { return executor; }
+-
+- protected boolean useExecutor = true;
+- public void setUseExecutor(boolean useexec) { useExecutor = useexec;}
+- public boolean getUseExecutor() { return useExecutor || (executor!=null);}
+-
+- /**
+- * Maximum amount of worker threads.
+- */
+- protected int maxThreads = 400;
+- public void setMaxThreads(int maxThreads) { this.maxThreads = maxThreads; }
+- public int getMaxThreads() { return maxThreads; }
+-
+-
+- /**
+- * Priority of the worker threads.
+- */
+- protected int threadPriority = Thread.NORM_PRIORITY;
+- public void setThreadPriority(int threadPriority) { this.threadPriority = threadPriority; }
+- public int getThreadPriority() { return threadPriority; }
+-
+- /**
+- * Priority of the acceptor threads.
+- */
+- protected int acceptorThreadPriority = Thread.NORM_PRIORITY;
+- public void setAcceptorThreadPriority(int acceptorThreadPriority) { this.acceptorThreadPriority = acceptorThreadPriority; }
+- public int getAcceptorThreadPriority() { return acceptorThreadPriority; }
+-
+- /**
+- * Priority of the poller threads.
+- */
+- protected int pollerThreadPriority = Thread.NORM_PRIORITY;
+- public void setPollerThreadPriority(int pollerThreadPriority) { this.pollerThreadPriority = pollerThreadPriority; }
+- public int getPollerThreadPriority() { return pollerThreadPriority; }
+-
+- /**
+- * Server socket port.
+- */
+- protected int port;
+- public int getPort() { return port; }
+- public void setPort(int port ) { this.port=port; }
+-
+-
+- /**
+- * Address for the server socket.
+- */
+- protected InetAddress address;
+- public InetAddress getAddress() { return address; }
+- public void setAddress(InetAddress address) { this.address = address; }
+-
+-
+- /**
+- * Handling of accepted sockets.
+- */
+- protected Handler handler = null;
+- public void setHandler(Handler handler ) { this.handler = handler; }
+- public Handler getHandler() { return handler; }
+-
+-
+- /**
+- * Allows the server developer to specify the backlog that
+- * should be used for server sockets. By default, this value
+- * is 100.
+- */
+- protected int backlog = 100;
+- public void setBacklog(int backlog) { if (backlog > 0) this.backlog = backlog; }
+- public int getBacklog() { return backlog; }
+-
+- protected SocketProperties socketProperties = new SocketProperties();
+-
+- /**
+- * Socket TCP no delay.
+- */
+- public boolean getTcpNoDelay() { return socketProperties.getTcpNoDelay();}
+- public void setTcpNoDelay(boolean tcpNoDelay) { socketProperties.setTcpNoDelay(tcpNoDelay); }
+-
+-
+- /**
+- * Socket linger.
+- */
+- public int getSoLinger() { return socketProperties.getSoLingerTime(); }
+- public void setSoLinger(int soLinger) {
+- socketProperties.setSoLingerTime(soLinger);
+- socketProperties.setSoLingerOn(soLinger>=0);
+- }
+-
+-
+- /**
+- * Socket timeout.
+- */
+- public int getSoTimeout() { return socketProperties.getSoTimeout(); }
+- public void setSoTimeout(int soTimeout) { socketProperties.setSoTimeout(soTimeout); }
+-
+- /**
+- * The default is true - the created threads will be
+- * in daemon mode. If set to false, the control thread
+- * will not be daemon - and will keep the process alive.
+- */
+- protected boolean daemon = true;
+- public void setDaemon(boolean b) { daemon = b; }
+- public boolean getDaemon() { return daemon; }
+-
+-
+- /**
+- * Name of the thread pool, which will be used for naming child threads.
+- */
+- protected String name = "TP";
+- public void setName(String name) { this.name = name; }
+- public String getName() { return name; }
+-
+-
+-
+- /**
+- * Allow comet request handling.
+- */
+- protected boolean useComet = true;
+- public void setUseComet(boolean useComet) { this.useComet = useComet; }
+- public boolean getUseComet() { return useComet; }
+-
+-
+- /**
+- * Acceptor thread count.
+- */
+- protected int acceptorThreadCount = 1;
+- public void setAcceptorThreadCount(int acceptorThreadCount) { this.acceptorThreadCount = acceptorThreadCount; }
+- public int getAcceptorThreadCount() { return acceptorThreadCount; }
+-
+-
+-
+- /**
+- * Poller thread count.
+- */
+- protected int pollerThreadCount = 1;
+- public void setPollerThreadCount(int pollerThreadCount) { this.pollerThreadCount = pollerThreadCount; }
+- public int getPollerThreadCount() { return pollerThreadCount; }
+-
+- protected long selectorTimeout = 1000;
+- public void setSelectorTimeout(long timeout){ this.selectorTimeout = timeout;}
+- public long getSelectorTimeout(){ return this.selectorTimeout; }
+- /**
+- * The socket poller.
+- */
+- protected Poller[] pollers = null;
+- protected int pollerRoundRobin = 0;
+- public Poller getPoller0() {
+- pollerRoundRobin = (pollerRoundRobin + 1) % pollers.length;
+- Poller poller = pollers[pollerRoundRobin];
+- return poller;
+- }
+-
+-
+- /**
+- * The socket poller used for Comet support.
+- */
+- public Poller getCometPoller0() {
+- Poller poller = getPoller0();
+- return poller;
+- }
+-
+-
+- /**
+- * Dummy maxSpareThreads property.
+- */
+- public int getMaxSpareThreads() { return Math.min(getMaxThreads(),5); }
+-
+-
+- /**
+- * Dummy minSpareThreads property.
+- */
+- public int getMinSpareThreads() { return Math.min(getMaxThreads(),5); }
+-
+- /**
+- * Generic properties, introspected
+- */
+- public boolean setProperty(String name, String value) {
+- final String selectorPoolName = "selectorPool.";
+- final String socketName = "socket.";
+- try {
+- if (name.startsWith(selectorPoolName)) {
+- return IntrospectionUtils.setProperty(selectorPool, name.substring(selectorPoolName.length()), value);
+- } else if (name.startsWith(socketName)) {
+- return IntrospectionUtils.setProperty(socketProperties, name.substring(socketName.length()), value);
+- } else {
+- return IntrospectionUtils.setProperty(this,name,value);
+- }
+- }catch ( Exception x ) {
+- log.error("Unable to set attribute \""+name+"\" to \""+value+"\"",x);
+- return false;
+- }
+- }
+-
+-
+- public String adjustRelativePath(String path, String relativeTo) {
+- File f = new File(path);
+- if ( !f.isAbsolute()) {
+- path = relativeTo + File.separator + path;
+- f = new File(path);
+- }
+- if (!f.exists()) {
+- log.warn("configured file:["+path+"] does not exist.");
+- }
+- return path;
+- }
+-
+- public String defaultIfNull(String val, String defaultValue) {
+- if (val==null) return defaultValue;
+- else return val;
+- }
+- // -------------------- SSL related properties --------------------
+- protected String truststoreFile = System.getProperty("javax.net.ssl.trustStore");
+- public void setTruststoreFile(String s) {
+- s = adjustRelativePath(s,System.getProperty("catalina.base"));
+- this.truststoreFile = s;
+- }
+- public String getTruststoreFile() {return truststoreFile;}
+- protected String truststorePass = System.getProperty("javax.net.ssl.trustStorePassword");
+- public void setTruststorePass(String truststorePass) {this.truststorePass = truststorePass;}
+- public String getTruststorePass() {return truststorePass;}
+- protected String truststoreType = System.getProperty("javax.net.ssl.trustStoreType");
+- public void setTruststoreType(String truststoreType) {this.truststoreType = truststoreType;}
+- public String getTruststoreType() {return truststoreType;}
+-
+- protected String keystoreFile = System.getProperty("user.home")+"/.keystore";
+- public String getKeystoreFile() { return keystoreFile;}
+- public void setKeystoreFile(String s ) {
+- s = adjustRelativePath(s,System.getProperty("catalina.base"));
+- this.keystoreFile = s;
+- }
+-
+- protected String algorithm = "SunX509";
+- public String getAlgorithm() { return algorithm;}
+- public void setAlgorithm(String s ) { this.algorithm = s;}
+-
+- protected String clientAuth = "false";
+- public String getClientAuth() { return clientAuth;}
+- public void setClientAuth(String s ) { this.clientAuth = s;}
+-
+- protected String keystorePass = "changeit";
+- public String getKeystorePass() { return keystorePass;}
+- public void setKeystorePass(String s ) { this.keystorePass = s;}
+-
+- protected String keystoreType = "JKS";
+- public String getKeystoreType() { return keystoreType;}
+- public void setKeystoreType(String s ) { this.keystoreType = s;}
+-
+- protected String sslProtocol = "TLS";
+-
+- public String getSslProtocol() { return sslProtocol;}
+- public void setSslProtocol(String s) { sslProtocol = s;}
+-
+- protected String sslEnabledProtocols=null; //"TLSv1,SSLv3,SSLv2Hello"
+- protected String[] sslEnabledProtocolsarr = new String[0];
+- public void setSslEnabledProtocols(String s) {
+- this.sslEnabledProtocols = s;
+- StringTokenizer t = new StringTokenizer(s,",");
+- sslEnabledProtocolsarr = new String[t.countTokens()];
+- for (int i=0; i<sslEnabledProtocolsarr.length; i++ ) sslEnabledProtocolsarr[i] = t.nextToken();
+- }
+-
+-
+- protected String ciphers = null;
+- protected String[] ciphersarr = new String[0];
+- public String getCiphers() { return ciphers;}
+- public void setCiphers(String s) {
+- ciphers = s;
+- if ( s == null ) ciphersarr = new String[0];
+- else {
+- StringTokenizer t = new StringTokenizer(s,",");
+- ciphersarr = new String[t.countTokens()];
+- for (int i=0; i<ciphersarr.length; i++ ) ciphersarr[i] = t.nextToken();
+- }
+- }
+-
+- /**
+- * SSL engine.
+- */
+- protected boolean SSLEnabled = false;
+- public boolean isSSLEnabled() { return SSLEnabled;}
+- public void setSSLEnabled(boolean SSLEnabled) {this.SSLEnabled = SSLEnabled;}
+-
+- protected boolean secure = false;
+- public boolean getSecure() { return secure;}
+- public void setSecure(boolean b) { secure = b;}
+-
+- public void setSelectorPool(NioSelectorPool selectorPool) {
+- this.selectorPool = selectorPool;
+- }
+-
+- public void setSocketProperties(SocketProperties socketProperties) {
+- this.socketProperties = socketProperties;
+- }
+-
+- public void setUseSendfile(boolean useSendfile) {
+-
+- this.useSendfile = useSendfile;
+- }
+-
+- public void setOomParachute(int oomParachute) {
+- this.oomParachute = oomParachute;
+- }
+-
+- public void setOomParachuteData(byte[] oomParachuteData) {
+- this.oomParachuteData = oomParachuteData;
+- }
+-
+- protected SSLContext sslContext = null;
+- public SSLContext getSSLContext() { return sslContext;}
+- public void setSSLContext(SSLContext c) { sslContext = c;}
+-
+- // --------------------------------------------------------- OOM Parachute Methods
+-
+- protected void checkParachute() {
+- boolean para = reclaimParachute(false);
+- if (!para && (System.currentTimeMillis()-lastParachuteCheck)>10000) {
+- try {
+- log.fatal(oomParachuteMsg);
+- }catch (Throwable t) {
+- System.err.println(oomParachuteMsg);
+- }
+- lastParachuteCheck = System.currentTimeMillis();
+- }
+- }
+-
+- protected boolean reclaimParachute(boolean force) {
+- if ( oomParachuteData != null ) return true;
+- if ( oomParachute > 0 && ( force || (Runtime.getRuntime().freeMemory() > (oomParachute*2))) )
+- oomParachuteData = new byte[oomParachute];
+- return oomParachuteData != null;
+- }
+-
+- protected void releaseCaches() {
+- this.keyCache.clear();
+- this.nioChannels.clear();
+- this.processorCache.clear();
+- if ( handler != null ) handler.releaseCaches();
+-
+- }
+-
+- // --------------------------------------------------------- Public Methods
+- /**
+- * Number of keepalive sockets.
+- */
+- public int getKeepAliveCount() {
+- if (pollers == null) {
+- return 0;
+- } else {
+- int keepAliveCount = 0;
+- for (int i = 0; i < pollers.length; i++) {
+- keepAliveCount += pollers[i].getKeepAliveCount();
+- }
+- return keepAliveCount;
+- }
+- }
+-
+-
+-
+- /**
+- * Return the amount of threads that are managed by the pool.
+- *
+- * @return the amount of threads that are managed by the pool
+- */
+- public int getCurrentThreadCount() {
+- return curThreads;
+- }
+-
+-
+- /**
+- * Return the amount of threads currently busy.
+- *
+- * @return the amount of threads currently busy
+- */
+- public int getCurrentThreadsBusy() {
+- return curThreadsBusy;
+- }
+-
+-
+- /**
+- * Return the state of the endpoint.
+- *
+- * @return true if the endpoint is running, false otherwise
+- */
+- public boolean isRunning() {
+- return running;
+- }
+-
+-
+- /**
+- * Return the state of the endpoint.
+- *
+- * @return true if the endpoint is paused, false otherwise
+- */
+- public boolean isPaused() {
+- return paused;
+- }
+-
+-
+- // ----------------------------------------------- Public Lifecycle Methods
+-
+-
+- /**
+- * Initialize the endpoint.
+- */
+- public void init()
+- throws Exception {
+-
+- if (initialized)
+- return;
+-
+- serverSock = ServerSocketChannel.open();
+- serverSock.socket().setPerformancePreferences(socketProperties.getPerformanceConnectionTime(),
+- socketProperties.getPerformanceLatency(),
+- socketProperties.getPerformanceBandwidth());
+- InetSocketAddress addr = (address!=null?new InetSocketAddress(address,port):new InetSocketAddress(port));
+- serverSock.socket().bind(addr,backlog);
+- serverSock.configureBlocking(true); //mimic APR behavior
+-
+- // Initialize thread count defaults for acceptor, poller
+- if (acceptorThreadCount == 0) {
+- // FIXME: Doesn't seem to work that well with multiple accept threads
+- acceptorThreadCount = 1;
+- }
+- if (pollerThreadCount <= 0) {
+- //minimum one poller thread
+- pollerThreadCount = 1;
+- }
+-
+- // Initialize SSL if needed
+- if (isSSLEnabled()) {
+- // Initialize SSL
+- char[] passphrase = getKeystorePass().toCharArray();
+-
+- KeyStore ks = KeyStore.getInstance(getKeystoreType());
+- ks.load(new FileInputStream(getKeystoreFile()), passphrase);
+-
+- KeyManagerFactory kmf = KeyManagerFactory.getInstance(getAlgorithm());
+- kmf.init(ks, passphrase);
+-
+- char[] tpassphrase = (getTruststorePass()!=null)?getTruststorePass().toCharArray():passphrase;
+- String ttype = (getTruststoreType()!=null)?getTruststoreType():getKeystoreType();
+-
+- KeyStore ts = null;
+- if (getTruststoreFile()==null) {
+- ts = KeyStore.getInstance(getKeystoreType());
+- ts.load(new FileInputStream(getKeystoreFile()), passphrase);
+- }else {
+- ts = KeyStore.getInstance(ttype);
+- ts.load(new FileInputStream(getTruststoreFile()), tpassphrase);
+- }
+-
+- TrustManagerFactory tmf = TrustManagerFactory.getInstance(getAlgorithm());
+- tmf.init(ts);
+-
+- sslContext = SSLContext.getInstance(getSslProtocol());
+- sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+-
+- }
+-
+- if (oomParachute>0) reclaimParachute(true);
+-
+- initialized = true;
+-
+- }
+-
+-
+- /**
+- * Start the NIO endpoint, creating acceptor, poller threads.
+- */
+- public void start()
+- throws Exception {
+- // Initialize socket if not done before
+- if (!initialized) {
+- init();
+- }
+- if (!running) {
+- running = true;
+- paused = false;
+-
+- // Create worker collection
+- if (getUseExecutor()) {
+- if ( executor == null ) {
+- TaskQueue taskqueue = new TaskQueue();
+- TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-");
+- executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
+- taskqueue.setParent( (ThreadPoolExecutor) executor);
+- }
+- } else if ( executor == null ) {//avoid two thread pools being created
+- workers = new WorkerStack(maxThreads);
+- }
+-
+- // Start acceptor threads
+- for (int i = 0; i < acceptorThreadCount; i++) {
+- Thread acceptorThread = new Thread(new Acceptor(), getName() + "-Acceptor-" + i);
+- acceptorThread.setPriority(threadPriority);
+- acceptorThread.setDaemon(daemon);
+- acceptorThread.start();
+- }
+-
+- // Start poller threads
+- pollers = new Poller[pollerThreadCount];
+- for (int i = 0; i < pollerThreadCount; i++) {
+- pollers[i] = new Poller();
+- pollers[i].init();
+- Thread pollerThread = new Thread(pollers[i], getName() + "-Poller-" + i);
+- pollerThread.setPriority(threadPriority);
+- pollerThread.setDaemon(true);
+- pollerThread.start();
+- }
+- }
+- }
+-
+-
+- /**
+- * Pause the endpoint, which will make it stop accepting new sockets.
+- */
+- public void pause() {
+- if (running && !paused) {
+- paused = true;
+- unlockAccept();
+- }
+- }
+-
+-
+- /**
+- * Resume the endpoint, which will make it start accepting new sockets
+- * again.
+- */
+- public void resume() {
+- if (running) {
+- paused = false;
+- }
+- }
+-
+-
+- /**
+- * Stop the endpoint. This will cause all processing threads to stop.
+- */
+- public void stop() {
+- if (running) {
+- running = false;
+- unlockAccept();
+- for (int i = 0; i < pollers.length; i++) {
+- pollers[i].destroy();
+- }
+- pollers = null;
+- }
+- eventCache.clear();
+- keyCache.clear();
+- nioChannels.clear();
+- processorCache.clear();
+- if ( executor!=null ) {
+- if ( executor instanceof ThreadPoolExecutor ) {
+- //this is our internal one, so we need to shut it down
+- ThreadPoolExecutor tpe = (ThreadPoolExecutor) executor;
+- tpe.shutdown();
+- TaskQueue queue = (TaskQueue) tpe.getQueue();
+- queue.setParent(null);
+- }
+- executor = null;
+- }
+- }
+-
+-
+- /**
+- * Deallocate NIO memory pools, and close server socket.
+- */
+- public void destroy() throws Exception {
+- if (running) {
+- stop();
+- }
+- // Close server socket
+- serverSock.socket().close();
+- serverSock.close();
+- serverSock = null;
+- sslContext = null;
+- initialized = false;
+- releaseCaches();
+- }
+-
+-
+- // ------------------------------------------------------ Protected Methods
+-
+-
+- /**
+- * Get a sequence number used for thread naming.
+- */
+- protected int getSequence() {
+- return sequence++;
+- }
+-
+- public int getWriteBufSize() {
+- return socketProperties.getTxBufSize();
+- }
+-
+- public int getReadBufSize() {
+- return socketProperties.getRxBufSize();
+- }
+-
+- public NioSelectorPool getSelectorPool() {
+- return selectorPool;
+- }
+-
+- public SocketProperties getSocketProperties() {
+- return socketProperties;
+- }
+-
+- public boolean getUseSendfile() {
+- //send file doesn't work with SSL
+- return useSendfile && (!isSSLEnabled());
+- }
+-
+- public int getOomParachute() {
+- return oomParachute;
+- }
+-
+- public byte[] getOomParachuteData() {
+- return oomParachuteData;
+- }
+-
+- /**
+- * Unlock the server socket accept using a bogus connection.
+- */
+- protected void unlockAccept() {
+- java.net.Socket s = null;
+- try {
+- // Need to create a connection to unlock the accept();
+- if (address == null) {
+- s = new java.net.Socket("127.0.0.1", port);
+- } else {
+- s = new java.net.Socket(address, port);
+- // setting soLinger to a small value will help shutdown the
+- // connection quicker
+- s.setSoLinger(true, 0);
+- }
+- } catch(Exception e) {
+- if (log.isDebugEnabled()) {
+- log.debug(sm.getString("endpoint.debug.unlock", "" + port), e);
+- }
+- } finally {
+- if (s != null) {
+- try {
+- s.close();
+- } catch (Exception e) {
+- // Ignore
+- }
+- }
+- }
+- }
+-
+-
+- /**
+- * Process the specified connection.
+- */
+- protected boolean setSocketOptions(SocketChannel socket) {
+- // Process the connection
+- try {
+- //disable blocking, APR style, we are gonna be polling it
+- socket.configureBlocking(false);
+- Socket sock = socket.socket();
+- socketProperties.setProperties(sock);
+-
+- NioChannel channel = nioChannels.poll();
+- if ( channel == null ) {
+- // SSL setup
+- if (sslContext != null) {
+- SSLEngine engine = createSSLEngine();
+- int appbufsize = engine.getSession().getApplicationBufferSize();
+- NioBufferHandler bufhandler = new NioBufferHandler(Math.max(appbufsize,socketProperties.getAppReadBufSize()),
+- Math.max(appbufsize,socketProperties.getAppWriteBufSize()),
+- socketProperties.getDirectBuffer());
+- channel = new SecureNioChannel(socket, engine, bufhandler, selectorPool);
+- } else {
+- // normal tcp setup
+- NioBufferHandler bufhandler = new NioBufferHandler(socketProperties.getAppReadBufSize(),
+- socketProperties.getAppWriteBufSize(),
+- socketProperties.getDirectBuffer());
+-
+- channel = new NioChannel(socket, bufhandler);
+- }
+- } else {
+- channel.setIOChannel(socket);
+- if ( channel instanceof SecureNioChannel ) {
+- SSLEngine engine = createSSLEngine();
+- ((SecureNioChannel)channel).reset(engine);
+- } else {
+- channel.reset();
+- }
+- }
+- getPoller0().register(channel);
+- } catch (Throwable t) {
+- try {
+- log.error("",t);
+- }catch ( Throwable tt){}
+- // Tell to close the socket
+- return false;
+- }
+- return true;
+- }
+-
+- protected SSLEngine createSSLEngine() {
+- SSLEngine engine = sslContext.createSSLEngine();
+- if ("false".equals(getClientAuth())) {
+- engine.setNeedClientAuth(false);
+- engine.setWantClientAuth(false);
+- } else if ("true".equals(getClientAuth()) || "yes".equals(getClientAuth())){
+- engine.setNeedClientAuth(true);
+- } else if ("want".equals(getClientAuth())) {
+- engine.setWantClientAuth(true);
+- }
+- engine.setUseClientMode(false);
+- if ( ciphersarr.length > 0 ) engine.setEnabledCipherSuites(ciphersarr);
+- if ( sslEnabledProtocolsarr.length > 0 ) engine.setEnabledProtocols(sslEnabledProtocolsarr);
+-
+- return engine;
+- }
+-
+-
+- /**
+- * Returns true if a worker thread is available for processing.
+- * @return boolean
+- */
+- protected boolean isWorkerAvailable() {
+- if ( executor != null ) {
+- return true;
+- } else {
+- if (workers.size() > 0) {
+- return true;
+- }
+- if ( (maxThreads > 0) && (curThreads < maxThreads)) {
+- return true;
+- } else {
+- if (maxThreads < 0) {
+- return true;
+- } else {
+- return false;
+- }
+- }
+- }
+- }
+- /**
+- * Create (or allocate) and return an available processor for use in
+- * processing a specific HTTP request, if possible. If the maximum
+- * allowed processors have already been created and are in use, return
+- * <code>null</code> instead.
+- */
+- protected Worker createWorkerThread() {
+-
+- synchronized (workers) {
+- if (workers.size() > 0) {
+- curThreadsBusy++;
+- return (workers.pop());
+- }
+- if ((maxThreads > 0) && (curThreads < maxThreads)) {
+- curThreadsBusy++;
+- return (newWorkerThread());
+- } else {
+- if (maxThreads < 0) {
+- curThreadsBusy++;
+- return (newWorkerThread());
+- } else {
+- return (null);
+- }
+- }
+- }
+- }
+-
+-
+- /**
+- * Create and return a new processor suitable for processing HTTP
+- * requests and returning the corresponding responses.
+- */
+- protected Worker newWorkerThread() {
+-
+- Worker workerThread = new Worker();
+- workerThread.start();
+- return (workerThread);
+-
+- }
+-
+-
+- /**
+- * Return a new worker thread, and block while to worker is available.
+- */
+- protected Worker getWorkerThread() {
+- // Allocate a new worker thread
+- Worker workerThread = createWorkerThread();
+- while (workerThread == null) {
+- try {
+- synchronized (workers) {
+- workerThread = createWorkerThread();
+- if ( workerThread == null ) workers.wait();
+- }
+- } catch (InterruptedException e) {
+- // Ignore
+- }
+- if ( workerThread == null ) workerThread = createWorkerThread();
+- }
+- return workerThread;
+- }
+-
+-
+- /**
+- * Recycle the specified Processor so that it can be used again.
+- *
+- * @param workerThread The processor to be recycled
+- */
+- protected void recycleWorkerThread(Worker workerThread) {
+- synchronized (workers) {
+- workers.push(workerThread);
+- curThreadsBusy--;
+- workers.notify();
+- }
+- }
+- /**
+- * Process given socket.
+- */
+- protected boolean processSocket(NioChannel socket) {
+- return processSocket(socket,null);
+- }
+-
+-
+- /**
+- * Process given socket for an event.
+- */
+- protected boolean processSocket(NioChannel socket, SocketStatus status) {
+- return processSocket(socket,status,true);
+- }
+-
+- protected boolean processSocket(NioChannel socket, SocketStatus status, boolean dispatch) {
+- try {
+- if (executor == null) {
+- getWorkerThread().assign(socket, status);
+- } else {
+- SocketProcessor sc = processorCache.poll();
+- if ( sc == null ) sc = new SocketProcessor(socket,status);
+- else sc.reset(socket,status);
+- if ( dispatch ) executor.execute(sc);
+- else sc.run();
+- }
+- } catch (Throwable t) {
+- // This means we got an OOM or similar creating a thread, or that
+- // the pool and its queue are full
+- log.error(sm.getString("endpoint.process.fail"), t);
+- return false;
+- }
+- return true;
+- }
+-
+-
+- // --------------------------------------------------- Acceptor Inner Class
+-
+-
+- /**
+- * Server socket acceptor thread.
+- */
+- protected class Acceptor implements Runnable {
+- /**
+- * The background thread that listens for incoming TCP/IP connections and
+- * hands them off to an appropriate processor.
+- */
+- public void run() {
+- // Loop until we receive a shutdown command
+- while (running) {
+- // Loop if endpoint is paused
+- while (paused) {
+- try {
+- Thread.sleep(1000);
+- } catch (InterruptedException e) {
+- // Ignore
+- }
+- }
+- try {
+- // Accept the next incoming connection from the server socket
+- SocketChannel socket = serverSock.accept();
+- // Hand this socket off to an appropriate processor
+- //TODO FIXME - this is currently a blocking call, meaning we will be blocking
+- //further accepts until there is a thread available.
+- if ( running && (!paused) && socket != null ) {
+- //processSocket(socket);
+- if (!setSocketOptions(socket)) {
+- try {
+- socket.socket().close();
+- socket.close();
+- } catch (IOException ix) {
+- if (log.isDebugEnabled())
+- log.debug("", ix);
+- }
+- }
+- }
+- }catch ( IOException x ) {
+- if ( running ) log.error(sm.getString("endpoint.accept.fail"), x);
+- } catch (OutOfMemoryError oom) {
+- try {
+- oomParachuteData = null;
+- releaseCaches();
+- log.error("", oom);
+- }catch ( Throwable oomt ) {
+- try {
+- try {
+- System.err.println(oomParachuteMsg);
+- oomt.printStackTrace();
+- }catch (Throwable letsHopeWeDontGetHere){}
+- }catch (Throwable letsHopeWeDontGetHere){}
+- }
+- } catch (Throwable t) {
+- log.error(sm.getString("endpoint.accept.fail"), t);
+- }
+- }//while
+- }//run
+- }
+-
+-
+- // ----------------------------------------------------- Poller Inner Classes
+-
+- /**
+- *
+- * PollerEvent, cacheable object for poller events to avoid GC
+- */
+- public class PollerEvent implements Runnable {
+-
+- protected NioChannel socket;
+- protected int interestOps;
+- protected KeyAttachment key;
+- public PollerEvent(NioChannel ch, KeyAttachment k, int intOps) {
+- reset(ch, k, intOps);
+- }
+-
+- public void reset(NioChannel ch, KeyAttachment k, int intOps) {
+- socket = ch;
+- interestOps = intOps;
+- key = k;
+- }
+-
+- public void reset() {
+- reset(null, null, 0);
+- }
+-
+- public void run() {
+- if ( interestOps == OP_REGISTER ) {
+- try {
+- socket.getIOChannel().register(socket.getPoller().getSelector(), SelectionKey.OP_READ, key);
+- } catch (Exception x) {
+- log.error("", x);
+- }
+- } else {
+- final SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
+- try {
+- boolean cancel = false;
+- if (key != null) {
+- final KeyAttachment att = (KeyAttachment) key.attachment();
+- if ( att!=null ) {
+- att.access();//to prevent timeout
+- //we are registering the key to start with, reset the fairness counter.
+- att.setFairness(0);
+- att.interestOps(interestOps);
+- key.interestOps(interestOps);
+- } else {
+- cancel = true;
+- }
+- } else {
+- cancel = true;
+- }
+- if ( cancel ) getPoller0().cancelledKey(key,SocketStatus.ERROR,false);
+- }catch (CancelledKeyException ckx) {
+- try {
+- getPoller0().cancelledKey(key,SocketStatus.DISCONNECT,true);
+- }catch (Exception ignore) {}
+- }
+- }//end if
+- }//run
+-
+- public String toString() {
+- return super.toString()+"[intOps="+this.interestOps+"]";
+- }
+- }
+- /**
+- * Poller class.
+- */
+- public class Poller implements Runnable {
+-
+- protected Selector selector;
+- protected ConcurrentLinkedQueue<Runnable> events = new ConcurrentLinkedQueue<Runnable>();
+-
+- protected boolean close = false;
+- protected long nextExpiration = 0;//optimize expiration handling
+-
+- protected int keepAliveCount = 0;
+- public int getKeepAliveCount() { return keepAliveCount; }
+-
+- protected AtomicLong wakeupCounter = new AtomicLong(0l);
+-
+- protected CountDownLatch stopLatch = new CountDownLatch(1);
+-
+-
+-
+- public Poller() throws IOException {
+- this.selector = Selector.open();
+- }
+-
+- public Selector getSelector() { return selector;}
+-
+- /**
+- * Create the poller. With some versions of APR, the maximum poller size will
+- * be 62 (reocmpiling APR is necessary to remove this limitation).
+- */
+- protected void init() {
+- keepAliveCount = 0;
+- }
+-
+- /**
+- * Destroy the poller.
+- */
+- protected void destroy() {
+- // Wait for polltime before doing anything, so that the poller threads
+- // exit, otherwise parallel descturction of sockets which are still
+- // in the poller can cause problems
+- close = true;
+- events.clear();
+- selector.wakeup();
+- try { stopLatch.await(5,TimeUnit.SECONDS); } catch (InterruptedException ignore ) {}
+- }
+-
+- public void addEvent(Runnable event) {
+- events.offer(event);
+- if ( wakeupCounter.incrementAndGet() < 3 ) selector.wakeup();
+- }
+-
+- /**
+- * Add specified socket and associated pool to the poller. The socket will
+- * be added to a temporary array, and polled first after a maximum amount
+- * of time equal to pollTime (in most cases, latency will be much lower,
+- * however).
+- *
+- * @param socket to add to the poller
+- */
+- public void add(final NioChannel socket) {
+- add(socket,SelectionKey.OP_READ);
+- }
+-
+- public void add(final NioChannel socket, final int interestOps) {
+- PollerEvent r = eventCache.poll();
+- if ( r==null) r = new PollerEvent(socket,null,interestOps);
+- else r.reset(socket,null,interestOps);
+- addEvent(r);
+- }
+-
+- public boolean events() {
+- boolean result = false;
+- //synchronized (events) {
+- Runnable r = null;
+- result = (events.size() > 0);
+- while ( (r = (Runnable)events.poll()) != null ) {
+- try {
+- r.run();
+- if ( r instanceof PollerEvent ) {
+- ((PollerEvent)r).reset();
+- eventCache.offer((PollerEvent)r);
+- }
+- } catch ( Throwable x ) {
+- log.error("",x);
+- }
+- }
+- //events.clear();
+- //}
+- return result;
+- }
+-
+- public void register(final NioChannel socket)
+- {
+- socket.setPoller(this);
+- KeyAttachment key = keyCache.poll();
+- final KeyAttachment ka = key!=null?key:new KeyAttachment();
+- ka.reset(this,socket);
+- PollerEvent r = eventCache.poll();
+- ka.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.
+- if ( r==null) r = new PollerEvent(socket,ka,OP_REGISTER);
+- else r.reset(socket,ka,OP_REGISTER);
+- addEvent(r);
+- }
+- public void cancelledKey(SelectionKey key, SocketStatus status, boolean dispatch) {
+- try {
+- if ( key == null ) return;//nothing to do
+- KeyAttachment ka = (KeyAttachment) key.attachment();
+- if (ka != null && ka.getComet() && status != null) {
+- //the comet event takes care of clean up
+- //processSocket(ka.getChannel(), status, dispatch);
+- ka.setComet(false);//to avoid a loop
+- processSocket(ka.getChannel(), status, false);//don't dispatch if the lines below are cancelling the key
+- if (status == SocketStatus.TIMEOUT ) return; // don't close on comet timeout
+- }
+- if (key.isValid()) key.cancel();
+- if (key.channel().isOpen()) try {key.channel().close();}catch (Exception ignore){}
+- try {ka.channel.close(true);}catch (Exception ignore){}
+- key.attach(null);
+- } catch (Throwable e) {
+- if ( log.isDebugEnabled() ) log.error("",e);
+- // Ignore
+- }
+- }
+- /**
+- * The background thread that listens for incoming TCP/IP connections and
+- * hands them off to an appropriate processor.
+- */
+- public void run() {
+- // Loop until we receive a shutdown command
+- while (running) {
+- try {
+- // Loop if endpoint is paused
+- while (paused && (!close) ) {
+- try {
+- Thread.sleep(500);
+- } catch (InterruptedException e) {
+- // Ignore
+- }
+- }
+- boolean hasEvents = false;
+-
+- hasEvents = (hasEvents | events());
+- // Time to terminate?
+- if (close) {
+- timeout(0, false);
+- stopLatch.countDown();
+- return;
+- }
+- int keyCount = 0;
+- try {
+- if ( !close ) {
+- keyCount = selector.select(selectorTimeout);
+- wakeupCounter.set(0);
+- }
+- if (close) {
+- timeout(0, false);
+- stopLatch.countDown();
+- selector.close();
+- return;
+- }
+- } catch ( NullPointerException x ) {
+- //sun bug 5076772 on windows JDK 1.5
+- if ( wakeupCounter == null || selector == null ) throw x;
+- continue;
+- } catch ( CancelledKeyException x ) {
+- //sun bug 5076772 on windows JDK 1.5
+- if ( wakeupCounter == null || selector == null ) throw x;
+- continue;
+- } catch (Throwable x) {
+- log.error("",x);
+- continue;
+- }
+- //either we timed out or we woke up, process events first
+- if ( keyCount == 0 ) hasEvents = (hasEvents | events());
+-
+- Iterator iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null;
+- // Walk through the collection of ready keys and dispatch
+- // any active event.
+- while (iterator != null && iterator.hasNext()) {
+- SelectionKey sk = (SelectionKey) iterator.next();
+- KeyAttachment attachment = (KeyAttachment)sk.attachment();
+- iterator.remove();
+- processKey(sk, attachment);
+- }//while
+-
+- //process timeouts
+- timeout(keyCount,hasEvents);
+- if ( oomParachute > 0 && oomParachuteData == null ) checkParachute();
+- } catch (OutOfMemoryError oom) {
+- try {
+- oomParachuteData = null;
+- releaseCaches();
+- log.error("", oom);
+- }catch ( Throwable oomt ) {
+- try {
+- System.err.println(oomParachuteMsg);
+- oomt.printStackTrace();
+- }catch (Throwable letsHopeWeDontGetHere){}
+- }
+- }
+- }//while
+- synchronized (this) {
+- this.notifyAll();
+- }
+- stopLatch.countDown();
+-
+- }
+-
+- protected boolean processKey(SelectionKey sk, KeyAttachment attachment) {
+- boolean result = true;
+- try {
+- if ( close ) {
+- cancelledKey(sk, SocketStatus.STOP, false);
+- } else if ( sk.isValid() && attachment != null ) {
+- attachment.access();//make sure we don't time out valid sockets
+- sk.attach(attachment);//cant remember why this is here
+- NioChannel channel = attachment.getChannel();
+- if (sk.isReadable() || sk.isWritable() ) {
+- if ( sk.isReadable() && attachment.getReadLatch() != null ) {
+- unreg(sk, attachment,SelectionKey.OP_READ);
+- attachment.getReadLatch().countDown();
+- } else if ( sk.isWritable() && attachment.getWriteLatch() != null ) {
+- unreg(sk, attachment,SelectionKey.OP_WRITE);
+- attachment.getWriteLatch().countDown();
+- } else if ( attachment.getSendfileData() != null ) {
+- processSendfile(sk,attachment,true);
+- } else if ( attachment.getComet() ) {
+- //check if thread is available
+- if ( isWorkerAvailable() ) {
+- unreg(sk, attachment, sk.readyOps());
+- if (!processSocket(channel, SocketStatus.OPEN))
+- processSocket(channel, SocketStatus.DISCONNECT);
+- attachment.setFairness(0);
+- } else {
+- //increase the fairness counter
+- attachment.incFairness();
+- result = false;
+- }
+- } else {
+- //later on, improve latch behavior
+- if ( isWorkerAvailable() ) {
+- unreg(sk, attachment,sk.readyOps());
+- boolean close = (!processSocket(channel));
+- if (close) {
+- cancelledKey(sk,SocketStatus.DISCONNECT,false);
+- }
+- attachment.setFairness(0);
+- } else {
+- //increase the fairness counter
+- attachment.incFairness();
+- result = false;
+- }
+- }
+- }
+- } else {
+- //invalid key
+- cancelledKey(sk, SocketStatus.ERROR,false);
+- }
+- } catch ( CancelledKeyException ckx ) {
+- cancelledKey(sk, SocketStatus.ERROR,false);
+- } catch (Throwable t) {
+- log.error("",t);
+- }
+- return result;
+- }
+-
+- public boolean processSendfile(SelectionKey sk, KeyAttachment attachment, boolean reg) {
+- try {
+- //unreg(sk,attachment);//only do this if we do process send file on a separate thread
+- SendfileData sd = attachment.getSendfileData();
+- if ( sd.fchannel == null ) {
+- File f = new File(sd.fileName);
+- if ( !f.exists() ) {
+- cancelledKey(sk,SocketStatus.ERROR,false);
+- return false;
+- }
+- sd.fchannel = new FileInputStream(f).getChannel();
+- }
+- SocketChannel sc = attachment.getChannel().getIOChannel();
+- long written = sd.fchannel.transferTo(sd.pos,sd.length,sc);
+- if ( written > 0 ) {
+- sd.pos += written;
+- sd.length -= written;
+- }
+- if ( sd.length <= 0 ) {
+- attachment.setSendfileData(null);
+- if ( sd.keepAlive )
+- if (reg) reg(sk,attachment,SelectionKey.OP_READ);
+- else
+- cancelledKey(sk,SocketStatus.STOP,false);
+- } else if ( attachment.interestOps() == 0 && reg ) {
+- reg(sk,attachment,SelectionKey.OP_WRITE);
+- }
+- }catch ( IOException x ) {
+- if ( log.isDebugEnabled() ) log.warn("Unable to complete sendfile request:", x);
+- cancelledKey(sk,SocketStatus.ERROR,false);
+- return false;
+- }catch ( Throwable t ) {
+- log.error("",t);
+- cancelledKey(sk, SocketStatus.ERROR, false);
+- return false;
+- }
+- return true;
+- }
+-
+- protected void unreg(SelectionKey sk, KeyAttachment attachment, int readyOps) {
+- //this is a must, so that we don't have multiple threads messing with the socket
+- reg(sk,attachment,sk.interestOps()& (~readyOps));
+- }
+-
+- protected void reg(SelectionKey sk, KeyAttachment attachment, int intops) {
+- sk.interestOps(intops);
+- attachment.interestOps(intops);
+- }
+-
+- protected void timeout(int keyCount, boolean hasEvents) {
+- long now = System.currentTimeMillis();
+- //don't process timeouts too frequently, but if the selector simply timed out
+- //then we can check timeouts to avoid gaps
+- if ( ((keyCount>0 || hasEvents) ||(now < nextExpiration)) && (!close) ) {
+- return;
+- }
+- long prevExp = nextExpiration;
+- nextExpiration = now + socketProperties.getTimeoutInterval();
+- //timeout
+- Set<SelectionKey> keys = selector.keys();
+- int keycount = 0;
+- for (Iterator<SelectionKey> iter = keys.iterator(); iter.hasNext(); ) {
+- SelectionKey key = iter.next();
+- keycount++;
+- try {
+- KeyAttachment ka = (KeyAttachment) key.attachment();
+- if ( ka == null ) {
+- cancelledKey(key, SocketStatus.ERROR,false); //we don't support any keys without attachments
+- } else if ( ka.getError() ) {
+- cancelledKey(key, SocketStatus.ERROR,true);
+- }else if ((ka.interestOps()&SelectionKey.OP_READ) == SelectionKey.OP_READ) {
+- //only timeout sockets that we are waiting for a read from
+- long delta = now - ka.getLastAccess();
+- long timeout = (ka.getTimeout()==-1)?((long) socketProperties.getSoTimeout()):(ka.getTimeout());
+- boolean isTimedout = delta > timeout;
+- if ( close ) {
+- key.interestOps(0);
+- ka.interestOps(0); //avoid duplicate stop calls
+- processKey(key,ka);
+- } else if (isTimedout) {
+- key.interestOps(0);
+- ka.interestOps(0); //avoid duplicate timeout calls
+- cancelledKey(key, SocketStatus.TIMEOUT,true);
+- } else {
+- long nextTime = now+(timeout-delta);
+- nextExpiration = (nextTime < nextExpiration)?nextTime:nextExpiration;
+- }
+- }//end if
+- }catch ( CancelledKeyException ckx ) {
+- cancelledKey(key, SocketStatus.ERROR,false);
+- }
+- }//for
+- if ( log.isDebugEnabled() ) log.debug("timeout completed: keycount="+keycount+"; now="+now+"; nextExpiration="+prevExp+"; "+
+- "keyCount="+keyCount+"; hasEvents="+hasEvents +"; eval="+( (now < prevExp) && (keyCount>0 || hasEvents) && (!close) ));
+-
+- }
+- }
+-
+-// ----------------------------------------------------- Key Attachment Class
+- public static class KeyAttachment {
+-
+- public KeyAttachment() {
+-
+- }
+- public void reset(Poller poller, NioChannel channel) {
+- this.channel = channel;
+- this.poller = poller;
+- lastAccess = System.currentTimeMillis();
+- currentAccess = false;
+- comet = false;
+- timeout = -1;
+- error = false;
+- fairness = 0;
+- lastRegistered = 0;
+- sendfileData = null;
+- if ( readLatch!=null ) try {for (int i=0; i<(int)readLatch.getCount();i++) readLatch.countDown();}catch (Exception ignore){}
+- readLatch = null;
+- if ( writeLatch!=null ) try {for (int i=0; i<(int)writeLatch.getCount();i++) writeLatch.countDown();}catch (Exception ignore){}
+- writeLatch = null;
+- }
+-
+- public void reset() {
+- reset(null,null);
+- }
+-
+- public Poller getPoller() { return poller;}
+- public void setPoller(Poller poller){this.poller = poller;}
+- public long getLastAccess() { return lastAccess; }
+- public void access() { access(System.currentTimeMillis()); }
+- public void access(long access) { lastAccess = access; }
+- public void setComet(boolean comet) { this.comet = comet; }
+- public boolean getComet() { return comet; }
+- public boolean getCurrentAccess() { return currentAccess; }
+- public void setCurrentAccess(boolean access) { currentAccess = access; }
+- public Object getMutex() {return mutex;}
+- public void setTimeout(long timeout) {this.timeout = timeout;}
+- public long getTimeout() {return this.timeout;}
+- public boolean getError() { return error; }
+- public void setError(boolean error) { this.error = error; }
+- public NioChannel getChannel() { return channel;}
+- public void setChannel(NioChannel channel) { this.channel = channel;}
+- protected Poller poller = null;
+- protected int interestOps = 0;
+- public int interestOps() { return interestOps;}
+- public int interestOps(int ops) { this.interestOps = ops; return ops; }
+- public CountDownLatch getReadLatch() { return readLatch; }
+- public CountDownLatch getWriteLatch() { return writeLatch; }
+- protected CountDownLatch resetLatch(CountDownLatch latch) {
+- if ( latch.getCount() == 0 ) return null;
+- else throw new IllegalStateException("Latch must be at count 0");
+- }
+- public void resetReadLatch() { readLatch = resetLatch(readLatch); }
+- public void resetWriteLatch() { writeLatch = resetLatch(writeLatch); }
+-
+- protected CountDownLatch startLatch(CountDownLatch latch, int cnt) {
+- if ( latch == null || latch.getCount() == 0 ) {
+- return new CountDownLatch(cnt);
+- }
+- else throw new IllegalStateException("Latch must be at count 0 or null.");
+- }
+- public void startReadLatch(int cnt) { readLatch = startLatch(readLatch,cnt);}
+- public void startWriteLatch(int cnt) { writeLatch = startLatch(writeLatch,cnt);}
+-
+-
+- protected void awaitLatch(CountDownLatch latch, long timeout, TimeUnit unit) throws InterruptedException {
+- if ( latch == null ) throw new IllegalStateException("Latch cannot be null");
+- latch.await(timeout,unit);
+- }
+- public void awaitReadLatch(long timeout, TimeUnit unit) throws InterruptedException { awaitLatch(readLatch,timeout,unit);}
+- public void awaitWriteLatch(long timeout, TimeUnit unit) throws InterruptedException { awaitLatch(writeLatch,timeout,unit);}
+-
+- public int getFairness() { return fairness; }
+- public void setFairness(int f) { fairness = f;}
+- public void incFairness() { fairness++; }
+- public long getLastRegistered() { return lastRegistered; };
+- public void setLastRegistered(long reg) { lastRegistered = reg; }
+-
+- public void setSendfileData(SendfileData sf) { this.sendfileData = sf;}
+- public SendfileData getSendfileData() { return this.sendfileData;}
+-
+- protected Object mutex = new Object();
+- protected long lastAccess = -1;
+- protected boolean currentAccess = false;
+- protected boolean comet = false;
+- protected long timeout = -1;
+- protected boolean error = false;
+- protected NioChannel channel = null;
+- protected CountDownLatch readLatch = null;
+- protected CountDownLatch writeLatch = null;
+- protected int fairness = 0;
+- protected long lastRegistered = 0;
+- protected SendfileData sendfileData = null;
+- }
+- // ----------------------------------------------------- Worker Inner Class
+-
+-
+- /**
+- * Server processor class.
+- */
+- protected class Worker implements Runnable {
+-
+-
+- protected Thread thread = null;
+- protected boolean available = false;
+- protected Object socket = null;
+- protected SocketStatus status = null;
+-
+-
+- /**
+- * Process an incoming TCP/IP connection on the specified socket. Any
+- * exception that occurs during processing must be logged and swallowed.
+- * <b>NOTE</b>: This method is called from our Connector's thread. We
+- * must assign it to our own thread so that multiple simultaneous
+- * requests can be handled.
+- *
+- * @param socket TCP socket to process
+- */
+- protected synchronized void assign(Object socket) {
+-
+- // Wait for the Processor to get the previous Socket
+- while (available) {
+- try {
+- wait();
+- } catch (InterruptedException e) {
+- }
+- }
+- // Store the newly available Socket and notify our thread
+- this.socket = socket;
+- status = null;
+- available = true;
+- notifyAll();
+-
+- }
+-
+-
+- protected synchronized void assign(Object socket, SocketStatus status) {
+-
+- // Wait for the Processor to get the previous Socket
+- while (available) {
+- try {
+- wait();
+- } catch (InterruptedException e) {
+- }
+- }
+-
+- // Store the newly available Socket and notify our thread
+- this.socket = socket;
+- this.status = status;
+- available = true;
+- notifyAll();
+- }
+-
+-
+- /**
+- * Await a newly assigned Socket from our Connector, or <code>null</code>
+- * if we are supposed to shut down.
+- */
+- protected synchronized Object await() {
+-
+- // Wait for the Connector to provide a new Socket
+- while (!available) {
+- try {
+- wait();
+- } catch (InterruptedException e) {
+- }
+- }
+-
+- // Notify the Connector that we have received this Socket
+- Object socket = this.socket;
+- available = false;
+- notifyAll();
+-
+- return (socket);
+-
+- }
+-
+-
+- /**
+- * The background thread that listens for incoming TCP/IP connections and
+- * hands them off to an appropriate processor.
+- */
+- public void run() {
+-
+- // Process requests until we receive a shutdown signal
+- while (running) {
+- NioChannel socket = null;
+- SelectionKey key = null;
+- try {
+- // Wait for the next socket to be assigned
+- Object channel = await();
+- if (channel == null)
+- continue;
+-
+- if ( channel instanceof SocketChannel) {
+- SocketChannel sc = (SocketChannel)channel;
+- if ( !setSocketOptions(sc) ) {
+- try {
+- sc.socket().close();
+- sc.close();
+- }catch ( IOException ix ) {
+- if ( log.isDebugEnabled() ) log.debug("",ix);
+- }
+- } else {
+- //now we have it registered, remove it from the cache
+-
+- }
+- } else {
+- socket = (NioChannel)channel;
+- SocketProcessor sc = processorCache.poll();
+- if ( sc == null ) sc = new SocketProcessor(socket,status);
+- else sc.reset(socket,status);
+- sc.run();
+- }
+- }catch(CancelledKeyException cx) {
+- if (socket!=null && key!=null) socket.getPoller().cancelledKey(key,null,false);
+- } catch (OutOfMemoryError oom) {
+- try {
+- oomParachuteData = null;
+- releaseCaches();
+- log.error("", oom);
+- }catch ( Throwable oomt ) {
+- try {
+- System.err.println(oomParachuteMsg);
+- oomt.printStackTrace();
+- }catch (Throwable letsHopeWeDontGetHere){}
+- }
+- } finally {
+- //dereference socket to let GC do its job
+- socket = null;
+- // Finish up this request
+- recycleWorkerThread(this);
+- }
+- }
+- }
+-
+-
+- /**
+- * Start the background processing thread.
+- */
+- public void start() {
+- thread = new Thread(this);
+- thread.setName(getName() + "-" + (++curThreads));
+- thread.setDaemon(true);
+- thread.setPriority(getThreadPriority());
+- thread.start();
+- }
+-
+-
+- }
+-
+- // ------------------------------------------------ Application Buffer Handler
+- public class NioBufferHandler implements ApplicationBufferHandler {
+- protected ByteBuffer readbuf = null;
+- protected ByteBuffer writebuf = null;
+-
+- public NioBufferHandler(int readsize, int writesize, boolean direct) {
+- if ( direct ) {
+- readbuf = ByteBuffer.allocateDirect(readsize);
+- writebuf = ByteBuffer.allocateDirect(writesize);
+- }else {
+- readbuf = ByteBuffer.allocate(readsize);
+- writebuf = ByteBuffer.allocate(writesize);
+- }
+- }
+-
+- public ByteBuffer expand(ByteBuffer buffer, int remaining) {return buffer;}
+- public ByteBuffer getReadBuffer() {return readbuf;}
+- public ByteBuffer getWriteBuffer() {return writebuf;}
+-
+- }
+-
+- // ------------------------------------------------ Handler Inner Interface
+-
+-
+- /**
+- * Bare bones interface used for socket processing. Per thread data is to be
+- * stored in the ThreadWithAttributes extra folders, or alternately in
+- * thread local fields.
+- */
+- public interface Handler {
+- public enum SocketState {
+- OPEN, CLOSED, LONG
+- }
+- public SocketState process(NioChannel socket);
+- public SocketState event(NioChannel socket, SocketStatus status);
+- public void releaseCaches();
+- }
+-
+-
+- // ------------------------------------------------- WorkerStack Inner Class
+-
+-
+- public class WorkerStack {
+-
+- protected Worker[] workers = null;
+- protected int end = 0;
+-
+- public WorkerStack(int size) {
+- workers = new Worker[size];
+- }
+-
+- /**
+- * Put the object into the queue.
+- *
+- * @param object the object to be appended to the queue (first element).
+- */
+- public void push(Worker worker) {
+- workers[end++] = worker;
+- }
+-
+- /**
+- * Get the first object out of the queue. Return null if the queue
+- * is empty.
+- */
+- public Worker pop() {
+- if (end > 0) {
+- return workers[--end];
+- }
+- return null;
+- }
+-
+- /**
+- * Get the first object out of the queue, Return null if the queue
+- * is empty.
+- */
+- public Worker peek() {
+- return workers[end];
+- }
+-
+- /**
+- * Is the queue empty?
+- */
+- public boolean isEmpty() {
+- return (end == 0);
+- }
+-
+- /**
+- * How many elements are there in this queue?
+- */
+- public int size() {
+- return (end);
+- }
+- }
+-
+-
+- // ---------------------------------------------- SocketProcessor Inner Class
+-
+-
+- /**
+- * This class is the equivalent of the Worker, but will simply use in an
+- * external Executor thread pool.
+- */
+- protected class SocketProcessor implements Runnable {
+-
+- protected NioChannel socket = null;
+- protected SocketStatus status = null;
+-
+- public SocketProcessor(NioChannel socket, SocketStatus status) {
+- reset(socket,status);
+- }
+-
+- public void reset(NioChannel socket, SocketStatus status) {
+- this.socket = socket;
+- this.status = status;
+- }
+-
+- public void run() {
+- SelectionKey key = null;
+- try {
+- key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
+- int handshake = -1;
+-
+- try {
+- if (key!=null) handshake = socket.handshake(key.isReadable(), key.isWritable());
+- }catch ( IOException x ) {
+- handshake = -1;
+- if ( log.isDebugEnabled() ) log.debug("Error during SSL handshake",x);
+- }catch ( CancelledKeyException ckx ) {
+- handshake = -1;
+- }
+- if ( handshake == 0 ) {
+- // Process the request from this socket
+- boolean closed = (status==null)?(handler.process(socket)==Handler.SocketState.CLOSED) :
+- (handler.event(socket,status)==Handler.SocketState.CLOSED);
+-
+- if (closed) {
+- // Close socket and pool
+- try {
+- KeyAttachment ka = null;
+- if (key!=null) {
+- ka = (KeyAttachment) key.attachment();
+- if (ka!=null) ka.setComet(false);
+- socket.getPoller().cancelledKey(key, SocketStatus.ERROR, false);
+- }
+- if (socket!=null) nioChannels.offer(socket);
+- socket = null;
+- if ( ka!=null ) keyCache.offer(ka);
+- ka = null;
+- }catch ( Exception x ) {
+- log.error("",x);
+- }
+- }
+- } else if (handshake == -1 ) {
+- KeyAttachment ka = null;
+- if (key!=null) {
+- ka = (KeyAttachment) key.attachment();
+- socket.getPoller().cancelledKey(key, SocketStatus.DISCONNECT, false);
+- }
+- if (socket!=null) nioChannels.offer(socket);
+- socket = null;
+- if ( ka!=null ) keyCache.offer(ka);
+- ka = null;
+- } else {
+- final SelectionKey fk = key;
+- final int intops = handshake;
+- final KeyAttachment ka = (KeyAttachment)fk.attachment();
+- ka.getPoller().add(socket,intops);
+- }
+- }catch(CancelledKeyException cx) {
+- socket.getPoller().cancelledKey(key,null,false);
+- } catch (OutOfMemoryError oom) {
+- try {
+- oomParachuteData = null;
+- socket.getPoller().cancelledKey(key,SocketStatus.ERROR,false);
+- releaseCaches();
+- log.error("", oom);
+- }catch ( Throwable oomt ) {
+- try {
+- System.err.println(oomParachuteMsg);
+- oomt.printStackTrace();
+- }catch (Throwable letsHopeWeDontGetHere){}
+- }
+- }catch ( Throwable t ) {
+- log.error("",t);
+- socket.getPoller().cancelledKey(key,SocketStatus.ERROR,false);
+- } finally {
+- socket = null;
+- status = null;
+- //return to cache
+- processorCache.offer(this);
+- }
+- }
+-
+- }
+-
+- // ---------------------------------------------- TaskQueue Inner Class
+- public static class TaskQueue extends LinkedBlockingQueue<Runnable> {
+- ThreadPoolExecutor parent = null;
+-
+- public TaskQueue() {
+- super();
+- }
+-
+- public TaskQueue(int initialCapacity) {
+- super(initialCapacity);
+- }
+-
+- public TaskQueue(Collection<? extends Runnable> c) {
+- super(c);
+- }
+-
+-
+- public void setParent(ThreadPoolExecutor tp) {
+- parent = tp;
+- }
+-
+- public boolean offer(Runnable o) {
+- //we can't do any checks
+- if (parent==null) return super.offer(o);
+- //we are maxed out on threads, simply queue the object
+- if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o);
+- //we have idle threads, just add it to the queue
+- //this is an approximation, so it could use some tuning
+- if (parent.getActiveCount()<(parent.getPoolSize())) return super.offer(o);
+- //if we have less threads than maximum force creation of a new thread
+- if (parent.getPoolSize()<parent.getMaximumPoolSize()) return false;
+- //if we reached here, we need to add it to the queue
+- return super.offer(o);
+- }
+- }
+-
+- // ---------------------------------------------- ThreadFactory Inner Class
+- class TaskThreadFactory implements ThreadFactory {
+- final ThreadGroup group;
+- final AtomicInteger threadNumber = new AtomicInteger(1);
+- final String namePrefix;
+-
+- TaskThreadFactory(String namePrefix) {
+- SecurityManager s = System.getSecurityManager();
+- group = (s != null)? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
+- this.namePrefix = namePrefix;
+- }
+-
+- public Thread newThread(Runnable r) {
+- Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement());
+- t.setDaemon(daemon);
+- t.setPriority(getThreadPriority());
+- return t;
+- }
+- }
+-
+- // ----------------------------------------------- SendfileData Inner Class
+-
+-
+- /**
+- * SendfileData class.
+- */
+- public static class SendfileData {
+- // File
+- public String fileName;
+- public FileChannel fchannel;
+- public long pos;
+- public long length;
+- // KeepAlive flag
+- public boolean keepAlive;
+- }
+-
+-}
+Index: java/org/apache/coyote/http11/Http11NioProtocol.java
+===================================================================
+--- java/org/apache/coyote/http11/Http11NioProtocol.java (revision 590752)
++++ java/org/apache/coyote/http11/Http11NioProtocol.java (working copy)
+@@ -1,849 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.coyote.http11;
+-
+-import java.net.InetAddress;
+-import java.net.URLEncoder;
+-import java.util.Hashtable;
+-import java.util.Iterator;
+-import java.util.concurrent.ConcurrentHashMap;
+-import java.util.concurrent.ConcurrentLinkedQueue;
+-import java.util.concurrent.Executor;
+-import java.util.concurrent.atomic.AtomicInteger;
+-import javax.management.MBeanRegistration;
+-import javax.management.MBeanServer;
+-import javax.management.ObjectName;
+-
+-import org.apache.coyote.ActionCode;
+-import org.apache.coyote.ActionHook;
+-import org.apache.coyote.Adapter;
+-import org.apache.coyote.ProtocolHandler;
+-import org.apache.coyote.RequestGroupInfo;
+-import org.apache.coyote.RequestInfo;
+-import org.apache.tomcat.util.modeler.Registry;
+-import org.apache.tomcat.util.net.NioChannel;
+-import org.apache.tomcat.util.net.NioEndpoint;
+-import org.apache.tomcat.util.net.NioEndpoint.Handler;
+-import org.apache.tomcat.util.net.SSLImplementation;
+-import org.apache.tomcat.util.net.SecureNioChannel;
+-import org.apache.tomcat.util.net.SocketStatus;
+-import org.apache.tomcat.util.res.StringManager;
+-
+-
+-/**
+- * 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
+- * @author Filip Hanik
+- */
+-public class Http11NioProtocol implements ProtocolHandler, MBeanRegistration
+-{
+- protected SSLImplementation sslImplementation = null;
+-
+- public Http11NioProtocol() {
+- cHandler = new Http11ConnectionHandler( this );
+- setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
+- setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
+- //setServerSoTimeout(Constants.DEFAULT_SERVER_SOCKET_TIMEOUT);
+- setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
+- }
+-
+- /**
+- * The string manager for this package.
+- */
+- protected static StringManager sm =
+- StringManager.getManager(Constants.Package);
+-
+- /** 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 ) {
+- if( log.isTraceEnabled())
+- log.trace(sm.getString("http11protocol.getattribute", key));
+- return attributes.get(key);
+- }
+-
+- public Iterator getAttributeNames() {
+- return attributes.keySet().iterator();
+- }
+-
+- /**
+- * Set a property.
+- */
+- public boolean setProperty(String name, String value) {
+- setAttribute(name, value);
+- if ( name!=null && (name.startsWith("socket.") ||name.startsWith("selectorPool.")) ){
+- return ep.setProperty(name, value);
+- } else {
+- return ep.setProperty(name,value); //make sure we at least try to set all properties
+- }
+- }
+-
+- /**
+- * Get a property
+- */
+- public String getProperty(String name) {
+- return (String)getAttribute(name);
+- }
+-
+- /** The adapter, used to call the connector
+- */
+- public void setAdapter(Adapter adapter) {
+- this.adapter=adapter;
+- }
+-
+- public Adapter getAdapter() {
+- return adapter;
+- }
+-
+-
+- /** Start the protocol
+- */
+- public void init() throws Exception {
+- ep.setName(getName());
+- ep.setHandler(cHandler);
+-
+- //todo, determine if we even need these
+- ep.getSocketProperties().setRxBufSize(Math.max(ep.getSocketProperties().getRxBufSize(),getMaxHttpHeaderSize()));
+- ep.getSocketProperties().setTxBufSize(Math.max(ep.getSocketProperties().getTxBufSize(),getMaxHttpHeaderSize()));
+-
+- try {
+- ep.init();
+- sslImplementation = SSLImplementation.getInstance("org.apache.tomcat.util.net.jsse.JSSEImplementation");
+- } catch (Exception ex) {
+- log.error(sm.getString("http11protocol.endpoint.initerror"), ex);
+- throw ex;
+- }
+- if(log.isInfoEnabled())
+- log.info(sm.getString("http11protocol.init", getName()));
+-
+- }
+-
+- ObjectName tpOname;
+- ObjectName rgOname;
+-
+- public void start() throws Exception {
+- if( this.domain != null ) {
+- try {
+- tpOname=new ObjectName
+- (domain + ":" + "type=ThreadPool,name=" + getName());
+- Registry.getRegistry(null, null)
+- .registerComponent(ep, tpOname, null );
+- } catch (Exception e) {
+- log.error("Can't register threadpool" );
+- }
+- rgOname=new ObjectName
+- (domain + ":type=GlobalRequestProcessor,name=" + getName());
+- Registry.getRegistry(null, null).registerComponent
+- ( cHandler.global, rgOname, null );
+- }
+-
+- try {
+- ep.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 {
+- ep.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 {
+- ep.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()));
+- ep.destroy();
+- if( tpOname!=null )
+- Registry.getRegistry(null, null).unregisterComponent(tpOname);
+- if( rgOname != null )
+- Registry.getRegistry(null, null).unregisterComponent(rgOname);
+- }
+-
+- // -------------------- Properties--------------------
+- protected NioEndpoint ep=new NioEndpoint();
+- protected boolean secure = false;
+-
+- protected Hashtable attributes = new Hashtable();
+-
+- private int maxKeepAliveRequests=100; // as in Apache HTTPD server
+- private int timeout = 300000; // 5 minutes as in Apache HTTPD server
+- private int maxSavePostSize = 4 * 1024;
+- private int maxHttpHeaderSize = 8 * 1024;
+- protected int processorCache = 200; //max number of Http11NioProcessor objects cached
+- private int socketCloseDelay=-1;
+- private boolean disableUploadTimeout = true;
+- private int socketBuffer = 9000;
+-
+- private Adapter adapter;
+- private Http11ConnectionHandler cHandler;
+-
+- /**
+- * Compression value.
+- */
+- private String compression = "off";
+- private String noCompressionUserAgents = null;
+- private String restrictedUserAgents = null;
+- private String compressableMimeTypes = "text/html,text/xml,text/plain";
+- private int compressionMinSize = 2048;
+-
+- private String server;
+-
+- // -------------------- Pool setup --------------------
+-
+- public void setPollerThreadCount(int count) {
+- ep.setPollerThreadCount(count);
+- }
+-
+- public int getPollerThreadCount() {
+- return ep.getPollerThreadCount();
+- }
+-
+- public void setSelectorTimeout(long timeout) {
+- ep.setSelectorTimeout(timeout);
+- }
+-
+- public long getSelectorTimeout() {
+- return ep.getSelectorTimeout();
+- }
+- // *
+- public Executor getExecutor() {
+- return ep.getExecutor();
+- }
+-
+- // *
+- public void setExecutor(Executor executor) {
+- ep.setExecutor(executor);
+- }
+-
+- public void setUseExecutor(boolean useexec) {
+- ep.setUseExecutor(useexec);
+- }
+-
+- public int getMaxThreads() {
+- return ep.getMaxThreads();
+- }
+-
+- public void setMaxThreads( int maxThreads ) {
+- ep.setMaxThreads(maxThreads);
+- setAttribute("maxThreads", "" + maxThreads);
+- }
+-
+- public void setThreadPriority(int threadPriority) {
+- ep.setThreadPriority(threadPriority);
+- setAttribute("threadPriority", "" + threadPriority);
+- }
+-
+- public void setAcceptorThreadPriority(int threadPriority) {
+- ep.setAcceptorThreadPriority(threadPriority);
+- setAttribute("acceptorThreadPriority", "" + threadPriority);
+- }
+-
+- public void setPollerThreadPriority(int threadPriority) {
+- ep.setPollerThreadPriority(threadPriority);
+- setAttribute("pollerThreadPriority", "" + threadPriority);
+- }
+-
+- public int getThreadPriority() {
+- return ep.getThreadPriority();
+- }
+-
+- public int getAcceptorThreadPriority() {
+- return ep.getAcceptorThreadPriority();
+- }
+-
+- public int getPollerThreadPriority() {
+- return ep.getThreadPriority();
+- }
+-
+-
+- public boolean getUseSendfile() {
+- return ep.getUseSendfile();
+- }
+-
+- public void setUseSendfile(boolean useSendfile) {
+- ep.setUseSendfile(useSendfile);
+- }
+-
+-
+- // -------------------- Tcp setup --------------------
+-
+- public int getBacklog() {
+- return ep.getBacklog();
+- }
+-
+- public void setBacklog( int i ) {
+- ep.setBacklog(i);
+- setAttribute("backlog", "" + i);
+- }
+-
+- public int getPort() {
+- return ep.getPort();
+- }
+-
+- public void setPort( int port ) {
+- ep.setPort(port);
+- setAttribute("port", "" + port);
+- }
+-
+- public InetAddress getAddress() {
+- return ep.getAddress();
+- }
+-
+- public void setAddress(InetAddress ia) {
+- ep.setAddress( ia );
+- setAttribute("address", "" + ia);
+- }
+-
+- public String getName() {
+- String encodedAddr = "";
+- if (getAddress() != null) {
+- encodedAddr = "" + getAddress();
+- if (encodedAddr.startsWith("/"))
+- encodedAddr = encodedAddr.substring(1);
+- encodedAddr = URLEncoder.encode(encodedAddr) + "-";
+- }
+- return ("http-" + encodedAddr + ep.getPort());
+- }
+-
+- public boolean getTcpNoDelay() {
+- return ep.getTcpNoDelay();
+- }
+-
+- public void setTcpNoDelay( boolean b ) {
+- ep.setTcpNoDelay( b );
+- setAttribute("tcpNoDelay", "" + b);
+- }
+-
+- public boolean getDisableUploadTimeout() {
+- return disableUploadTimeout;
+- }
+-
+- public void setDisableUploadTimeout(boolean isDisabled) {
+- disableUploadTimeout = isDisabled;
+- }
+-
+- public int getSocketBuffer() {
+- return socketBuffer;
+- }
+-
+- public void setSocketBuffer(int valueI) {
+- socketBuffer = valueI;
+- }
+-
+- public String getCompression() {
+- return compression;
+- }
+-
+- public void setCompression(String valueS) {
+- compression = valueS;
+- setAttribute("compression", valueS);
+- }
+-
+- public int getMaxSavePostSize() {
+- return maxSavePostSize;
+- }
+-
+- public void setMaxSavePostSize(int valueI) {
+- maxSavePostSize = valueI;
+- setAttribute("maxSavePostSize", "" + valueI);
+- }
+-
+- public int getMaxHttpHeaderSize() {
+- return maxHttpHeaderSize;
+- }
+-
+- public void setMaxHttpHeaderSize(int valueI) {
+- maxHttpHeaderSize = valueI;
+- setAttribute("maxHttpHeaderSize", "" + valueI);
+- }
+-
+- public String getRestrictedUserAgents() {
+- return restrictedUserAgents;
+- }
+-
+- public void setRestrictedUserAgents(String valueS) {
+- restrictedUserAgents = valueS;
+- setAttribute("restrictedUserAgents", valueS);
+- }
+-
+- public String getNoCompressionUserAgents() {
+- return noCompressionUserAgents;
+- }
+-
+- public void setNoCompressionUserAgents(String valueS) {
+- noCompressionUserAgents = valueS;
+- setAttribute("noCompressionUserAgents", valueS);
+- }
+-
+- public String getCompressableMimeType() {
+- return compressableMimeTypes;
+- }
+-
+- public void setCompressableMimeType(String valueS) {
+- compressableMimeTypes = valueS;
+- setAttribute("compressableMimeTypes", valueS);
+- }
+-
+- public int getCompressionMinSize() {
+- return compressionMinSize;
+- }
+-
+- public void setCompressionMinSize(int valueI) {
+- compressionMinSize = valueI;
+- setAttribute("compressionMinSize", "" + valueI);
+- }
+-
+- public int getSoLinger() {
+- return ep.getSoLinger();
+- }
+-
+- public void setSoLinger( int i ) {
+- ep.setSoLinger( i );
+- setAttribute("soLinger", "" + i);
+- }
+-
+- public int getSoTimeout() {
+- return ep.getSoTimeout();
+- }
+-
+- public void setSoTimeout( int i ) {
+- ep.setSoTimeout(i);
+- setAttribute("soTimeout", "" + i);
+- }
+-
+- public String getProtocol() {
+- return getProperty("protocol");
+- }
+-
+- public void setProtocol( String k ) {
+- setSecure(true);
+- setAttribute("protocol", k);
+- }
+-
+- public boolean getSecure() {
+- return secure;
+- }
+-
+- public void setSecure( boolean b ) {
+- ep.setSecure(b);
+- secure=b;
+- setAttribute("secure", "" + b);
+- }
+-
+- public int getMaxKeepAliveRequests() {
+- return maxKeepAliveRequests;
+- }
+-
+- /** Set the maximum number of Keep-Alive requests that we will honor.
+- */
+- public void setMaxKeepAliveRequests(int mkar) {
+- maxKeepAliveRequests = mkar;
+- setAttribute("maxKeepAliveRequests", "" + mkar);
+- }
+-
+- /**
+- * Return the Keep-Alive policy for the connection.
+- */
+- public boolean getKeepAlive() {
+- return ((maxKeepAliveRequests != 0) && (maxKeepAliveRequests != 1));
+- }
+-
+- /**
+- * Set the keep-alive policy for this connection.
+- */
+- public void setKeepAlive(boolean keepAlive) {
+- if (!keepAlive) {
+- setMaxKeepAliveRequests(1);
+- }
+- }
+-
+- public int getSocketCloseDelay() {
+- return socketCloseDelay;
+- }
+-
+- public void setSocketCloseDelay( int d ) {
+- socketCloseDelay=d;
+- setAttribute("socketCloseDelay", "" + d);
+- }
+-
+- public void setServer( String server ) {
+- this.server = server;
+- }
+-
+- public String getServer() {
+- return server;
+- }
+-
+- public int getTimeout() {
+- return timeout;
+- }
+-
+- public void setTimeout( int timeouts ) {
+- timeout = timeouts;
+- setAttribute("timeout", "" + timeouts);
+- }
+-
+- public void setProcessorCache(int processorCache) {
+- this.processorCache = processorCache;
+- }
+-
+- public void setOomParachute(int oomParachute) {
+- ep.setOomParachute(oomParachute);
+- setAttribute("oomParachute",oomParachute);
+- }
+-
+- // -------------------- SSL related properties --------------------
+-
+- public String getKeystoreFile() { return ep.getKeystoreFile();}
+- public void setKeystoreFile(String s ) { ep.setKeystoreFile(s);}
+- public void setKeystore(String s) { setKeystoreFile(s);}
+- public String getKeystore(){ return getKeystoreFile();}
+-
+- public String getAlgorithm() { return ep.getAlgorithm();}
+- public void setAlgorithm(String s ) { ep.setAlgorithm(s);}
+-
+- public void setClientauth(String s) {setClientAuth(s);}
+- public String getClientauth(){ return getClientAuth();}
+- public String getClientAuth() { return ep.getClientAuth();}
+- public void setClientAuth(String s ) { ep.setClientAuth(s);}
+-
+- public String getKeystorePass() { return ep.getKeystorePass();}
+- public void setKeystorePass(String s ) { ep.setKeystorePass(s);}
+- public void setKeypass(String s) { setKeystorePass(s);}
+- public String getKeypass() { return getKeystorePass();}
+- public String getKeystoreType() { return ep.getKeystoreType();}
+- public void setKeystoreType(String s ) { ep.setKeystoreType(s);}
+- public String getKeytype() { return getKeystoreType();}
+- public void setKeytype(String s ) { setKeystoreType(s);}
+-
+- public void setTruststoreFile(String f){ep.setTruststoreFile(f);}
+- public String getTruststoreFile(){return ep.getTruststoreFile();}
+- public void setTruststorePass(String p){ep.setTruststorePass(p);}
+- public String getTruststorePass(){return ep.getTruststorePass();}
+- public void setTruststoreType(String t){ep.setTruststoreType(t);}
+- public String getTruststoreType(){ return ep.getTruststoreType();}
+-
+-
+- public String getSslProtocol() { return ep.getSslProtocol();}
+- public void setSslProtocol(String s) { ep.setSslProtocol(s);}
+-
+- public String getCiphers() { return ep.getCiphers();}
+- public void setCiphers(String s) { ep.setCiphers(s);}
+-
+- public boolean getSSLEnabled() { return ep.isSSLEnabled(); }
+- public void setSSLEnabled(boolean SSLEnabled) { ep.setSSLEnabled(SSLEnabled); }
+-
+-
+-
+- // -------------------- Connection handler --------------------
+-
+- static class Http11ConnectionHandler implements Handler {
+-
+- protected Http11NioProtocol proto;
+- protected static int count = 0;
+- protected RequestGroupInfo global = new RequestGroupInfo();
+-
+- protected ConcurrentHashMap<NioChannel, Http11NioProcessor> connections =
+- new ConcurrentHashMap<NioChannel, Http11NioProcessor>();
+- protected ConcurrentLinkedQueue<Http11NioProcessor> recycledProcessors = new ConcurrentLinkedQueue<Http11NioProcessor>() {
+- protected AtomicInteger size = new AtomicInteger(0);
+- public boolean offer(Http11NioProcessor 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) deregister(processor);
+- return result;
+- }
+-
+- public Http11NioProcessor poll() {
+- Http11NioProcessor result = super.poll();
+- if ( result != null ) {
+- size.decrementAndGet();
+- }
+- return result;
+- }
+-
+- public void clear() {
+- Http11NioProcessor next = poll();
+- while ( next != null ) {
+- deregister(next);
+- next = poll();
+- }
+- super.clear();
+- size.set(0);
+- }
+- };
+-
+- Http11ConnectionHandler(Http11NioProtocol proto) {
+- this.proto = proto;
+- }
+-
+- public void releaseCaches() {
+- recycledProcessors.clear();
+- }
+-
+- public SocketState event(NioChannel socket, SocketStatus status) {
+- Http11NioProcessor result = connections.get(socket);
+-
+- SocketState state = SocketState.CLOSED;
+- if (result != null) {
+- if (log.isDebugEnabled()) log.debug("Http11NioProcessor.error="+result.error);
+- // Call the appropriate event
+- try {
+- state = result.event(status);
+- } catch (java.net.SocketException e) {
+- // SocketExceptions are normal
+- Http11NioProtocol.log.debug
+- (sm.getString
+- ("http11protocol.proto.socketexception.debug"), e);
+- } catch (java.io.IOException e) {
+- // IOExceptions are normal
+- Http11NioProtocol.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.
+- Http11NioProtocol.log.error
+- (sm.getString("http11protocol.proto.error"), e);
+- } finally {
+- if (state != SocketState.LONG) {
+- connections.remove(socket);
+- recycledProcessors.offer(result);
+- if (state == SocketState.OPEN) {
+- socket.getPoller().add(socket);
+- }
+- } else {
+- if (log.isDebugEnabled()) log.debug("Keeping processor["+result);
+- socket.getPoller().add(socket);
+- }
+- }
+- }
+- return state;
+- }
+-
+- public SocketState process(NioChannel socket) {
+- Http11NioProcessor processor = null;
+- try {
+- if (processor == null) {
+- processor = recycledProcessors.poll();
+- }
+- if (processor == null) {
+- processor = createProcessor();
+- }
+-
+- if (processor instanceof ActionHook) {
+- ((ActionHook) processor).action(ActionCode.ACTION_START, null);
+- }
+-
+-
+- if (proto.ep.getSecure() && (proto.sslImplementation != null)) {
+- if (socket instanceof SecureNioChannel) {
+- SecureNioChannel ch = (SecureNioChannel)socket;
+- processor.setSslSupport(proto.sslImplementation.getSSLSupport(ch.getSslEngine().getSession()));
+- }else processor.setSslSupport(null);
+- } else {
+- processor.setSslSupport(null);
+- }
+-
+-
+- SocketState state = processor.process(socket);
+- if (state == SocketState.LONG) {
+- // Associate the connection with the processor. The next request
+- // processed by this thread will use either a new or a recycled
+- // processor.
+- if (log.isDebugEnabled()) log.debug("Not recycling ["+processor+"] Comet="+((NioEndpoint.KeyAttachment)socket.getAttachment(false)).getComet());
+- connections.put(socket, processor);
+- socket.getPoller().add(socket);
+- } else {
+- recycledProcessors.offer(processor);
+- }
+- return state;
+-
+- } catch (java.net.SocketException e) {
+- // SocketExceptions are normal
+- Http11NioProtocol.log.debug
+- (sm.getString
+- ("http11protocol.proto.socketexception.debug"), e);
+- } catch (java.io.IOException e) {
+- // IOExceptions are normal
+- Http11NioProtocol.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.
+- Http11NioProtocol.log.error
+- (sm.getString("http11protocol.proto.error"), e);
+- }
+- recycledProcessors.offer(processor);
+- return SocketState.CLOSED;
+- }
+-
+- public Http11NioProcessor createProcessor() {
+- Http11NioProcessor processor = new Http11NioProcessor(
+- proto.ep.getSocketProperties().getRxBufSize(),
+- proto.ep.getSocketProperties().getTxBufSize(),
+- proto.maxHttpHeaderSize,
+- proto.ep);
+- processor.setAdapter(proto.adapter);
+- processor.setMaxKeepAliveRequests(proto.maxKeepAliveRequests);
+- processor.setTimeout(proto.timeout);
+- processor.setDisableUploadTimeout(proto.disableUploadTimeout);
+- processor.setCompression(proto.compression);
+- processor.setCompressionMinSize(proto.compressionMinSize);
+- 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;
+- }
+- AtomicInteger registerCount = new AtomicInteger(0);
+- public void register(Http11NioProcessor processor) {
+- if (proto.getDomain() != null) {
+- synchronized (this) {
+- try {
+- registerCount.addAndGet(1);
+- if (log.isDebugEnabled()) log.debug("Register ["+processor+"] count="+registerCount.get());
+- RequestInfo rp = processor.getRequest().getRequestProcessor();
+- rp.setGlobalProcessor(global);
+- ObjectName rpName = new ObjectName
+- (proto.getDomain() + ":type=RequestProcessor,worker="
+- + proto.getName() + ",name=HttpRequest" + count++);
+- Registry.getRegistry(null, null).registerComponent(rp, rpName, null);
+- rp.setRpName(rpName);
+- } catch (Exception e) {
+- log.warn("Error registering request");
+- }
+- }
+- }
+- }
+-
+- public void deregister(Http11NioProcessor processor) {
+- if (proto.getDomain() != null) {
+- synchronized (this) {
+- try {
+- registerCount.addAndGet(-1);
+- if (log.isDebugEnabled()) log.debug("Deregister ["+processor+"] count="+registerCount.get());
+- RequestInfo rp = processor.getRequest().getRequestProcessor();
+- rp.setGlobalProcessor(null);
+- ObjectName rpName = rp.getRpName();
+- Registry.getRegistry(null, null).unregisterComponent(rpName);
+- rp.setRpName(null);
+- } catch (Exception e) {
+- log.warn("Error unregistering request", e);
+- }
+- }
+- }
+- }
+-
+- }
+-
+-
+-
+- protected static org.apache.juli.logging.Log log
+- = org.apache.juli.logging.LogFactory.getLog(Http11NioProtocol.class);
+-
+- // -------------------- Various implementation classes --------------------
+-
+- protected String domain;
+- protected ObjectName oname;
+- protected MBeanServer mserver;
+-
+- public ObjectName getObjectName() {
+- return oname;
+- }
+-
+- public String getDomain() {
+- return domain;
+- }
+-
+- public int getProcessorCache() {
+- return processorCache;
+- }
+-
+- public int getOomParachute() {
+- return ep.getOomParachute();
+- }
+-
+- 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() {
+- }
+-}
+Index: java/org/apache/coyote/http11/Http11NioProcessor.java
+===================================================================
+--- java/org/apache/coyote/http11/Http11NioProcessor.java (revision 590752)
++++ java/org/apache/coyote/http11/Http11NioProcessor.java (working copy)
+@@ -1,1813 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.coyote.http11;
+-
+-import java.io.IOException;
+-import java.io.InterruptedIOException;
+-import java.net.InetAddress;
+-import java.nio.channels.SelectionKey;
+-import java.util.StringTokenizer;
+-import java.util.regex.Pattern;
+-import java.util.regex.PatternSyntaxException;
+-
+-import org.apache.coyote.ActionCode;
+-import org.apache.coyote.ActionHook;
+-import org.apache.coyote.Adapter;
+-import org.apache.coyote.Request;
+-import org.apache.coyote.RequestInfo;
+-import org.apache.coyote.Response;
+-import org.apache.coyote.http11.filters.BufferedInputFilter;
+-import org.apache.coyote.http11.filters.ChunkedInputFilter;
+-import org.apache.coyote.http11.filters.ChunkedOutputFilter;
+-import org.apache.coyote.http11.filters.GzipOutputFilter;
+-import org.apache.coyote.http11.filters.IdentityInputFilter;
+-import org.apache.coyote.http11.filters.IdentityOutputFilter;
+-import org.apache.coyote.http11.filters.SavedRequestInputFilter;
+-import org.apache.coyote.http11.filters.VoidInputFilter;
+-import org.apache.coyote.http11.filters.VoidOutputFilter;
+-import org.apache.tomcat.util.buf.Ascii;
+-import org.apache.tomcat.util.buf.ByteChunk;
+-import org.apache.tomcat.util.buf.HexUtils;
+-import org.apache.tomcat.util.buf.MessageBytes;
+-import org.apache.tomcat.util.http.FastHttpDateFormat;
+-import org.apache.tomcat.util.http.MimeHeaders;
+-import org.apache.tomcat.util.net.NioChannel;
+-import org.apache.tomcat.util.net.NioEndpoint;
+-import org.apache.tomcat.util.net.SSLSupport;
+-import org.apache.tomcat.util.net.SocketStatus;
+-import org.apache.tomcat.util.net.NioEndpoint.Handler.SocketState;
+-import org.apache.tomcat.util.res.StringManager;
+-import org.apache.tomcat.util.net.NioEndpoint.KeyAttachment;
+-
+-
+-/**
+- * Processes HTTP requests.
+- *
+- * @author Remy Maucherat
+- * @author Filip Hanik
+- */
+-public class Http11NioProcessor implements ActionHook {
+-
+-
+- /**
+- * Logger.
+- */
+- protected static org.apache.juli.logging.Log log
+- = org.apache.juli.logging.LogFactory.getLog(Http11NioProcessor.class);
+-
+- /**
+- * The string manager for this package.
+- */
+- protected static StringManager sm =
+- StringManager.getManager(Constants.Package);
+-
+- /**
+- * SSL information.
+- */
+- protected SSLSupport sslSupport;
+-
+- // ----------------------------------------------------------- Constructors
+-
+-
+- public Http11NioProcessor(int rxBufSize, int txBufSize, int maxHttpHeaderSize, NioEndpoint endpoint) {
+-
+- this.endpoint = endpoint;
+-
+- request = new Request();
+- int readTimeout = endpoint.getSoTimeout();
+- inputBuffer = new InternalNioInputBuffer(request, maxHttpHeaderSize,readTimeout);
+- request.setInputBuffer(inputBuffer);
+-
+- response = new Response();
+- response.setHook(this);
+- outputBuffer = new InternalNioOutputBuffer(response, maxHttpHeaderSize,readTimeout);
+- response.setOutputBuffer(outputBuffer);
+- request.setResponse(response);
+-
+- ssl = endpoint.isSSLEnabled();
+-
+- initializeFilters();
+-
+- // Cause loading of HexUtils
+- int foo = HexUtils.DEC[0];
+-
+- // Cause loading of FastHttpDateFormat
+- FastHttpDateFormat.getCurrentDate();
+-
+- }
+-
+-
+- // ----------------------------------------------------- Instance Variables
+-
+-
+- /**
+- * Associated adapter.
+- */
+- protected Adapter adapter = null;
+-
+-
+- /**
+- * Request object.
+- */
+- protected Request request = null;
+-
+-
+- /**
+- * Response object.
+- */
+- protected Response response = null;
+-
+-
+- /**
+- * Input.
+- */
+- protected InternalNioInputBuffer inputBuffer = null;
+-
+-
+- /**
+- * Output.
+- */
+- protected InternalNioOutputBuffer outputBuffer = null;
+-
+-
+- /**
+- * Error flag.
+- */
+- protected boolean error = false;
+-
+-
+- /**
+- * Keep-alive.
+- */
+- protected boolean keepAlive = true;
+-
+-
+- /**
+- * HTTP/1.1 flag.
+- */
+- protected boolean http11 = true;
+-
+-
+- /**
+- * HTTP/0.9 flag.
+- */
+- protected boolean http09 = false;
+-
+- /**
+- * Sendfile data.
+- */
+- protected NioEndpoint.SendfileData sendfileData = null;
+-
+- /**
+- * Comet used.
+- */
+- protected boolean comet = false;
+-
+- /**
+- * Closed flag, a Comet async thread can
+- * signal for this Nio processor to be closed and recycled instead
+- * of waiting for a timeout.
+- * Closed by HttpServletResponse.getWriter().close()
+- */
+- protected boolean cometClose = false;
+-
+- /**
+- * Content delimitator for the request (if false, the connection will
+- * be closed at the end of the request).
+- */
+- protected boolean contentDelimitation = true;
+-
+-
+- /**
+- * Is there an expectation ?
+- */
+- protected boolean expectation = false;
+-
+-
+- /**
+- * List of restricted user agents.
+- */
+- protected Pattern[] restrictedUserAgents = null;
+-
+-
+- /**
+- * Maximum number of Keep-Alive requests to honor.
+- */
+- protected int maxKeepAliveRequests = -1;
+-
+-
+- /**
+- * SSL enabled ?
+- */
+- protected boolean ssl = false;
+-
+-
+- /**
+- * Socket associated with the current connection.
+- */
+- protected NioChannel socket = null;
+-
+-
+- /**
+- * Remote Address associated with the current connection.
+- */
+- protected String remoteAddr = null;
+-
+-
+- /**
+- * Remote Host associated with the current connection.
+- */
+- protected String remoteHost = null;
+-
+-
+- /**
+- * Local Host associated with the current connection.
+- */
+- protected String localName = null;
+-
+-
+-
+- /**
+- * Local port to which the socket is connected
+- */
+- protected int localPort = -1;
+-
+-
+- /**
+- * Remote port to which the socket is connected
+- */
+- protected int remotePort = -1;
+-
+-
+- /**
+- * The local Host address.
+- */
+- protected String localAddr = null;
+-
+-
+- /**
+- * Maximum timeout on uploads. 5 minutes as in Apache HTTPD server.
+- */
+- protected int timeout = 300000;
+-
+-
+- /**
+- * Flag to disable setting a different time-out on uploads.
+- */
+- protected boolean disableUploadTimeout = false;
+-
+-
+- /**
+- * Allowed compression level.
+- */
+- protected int compressionLevel = 0;
+-
+-
+- /**
+- * Minimum contentsize to make compression.
+- */
+- protected int compressionMinSize = 2048;
+-
+-
+- /**
+- * Socket buffering.
+- */
+- protected int socketBuffer = -1;
+-
+-
+- /**
+- * Max save post size.
+- */
+- protected int maxSavePostSize = 4 * 1024;
+-
+-
+- /**
+- * List of user agents to not use gzip with
+- */
+- protected Pattern noCompressionUserAgents[] = null;
+-
+- /**
+- * List of MIMES which could be gzipped
+- */
+- protected String[] compressableMimeTypes =
+- { "text/html", "text/xml", "text/plain" };
+-
+-
+- /**
+- * Host name (used to avoid useless B2C conversion on the host name).
+- */
+- protected char[] hostNameC = new char[0];
+-
+-
+- /**
+- * Associated endpoint.
+- */
+- protected NioEndpoint endpoint;
+-
+-
+- /**
+- * Allow a customized the server header for the tin-foil hat folks.
+- */
+- protected String server = null;
+-
+-
+- // ------------------------------------------------------------- Properties
+-
+-
+- /**
+- * Return compression level.
+- */
+- public String getCompression() {
+- switch (compressionLevel) {
+- case 0:
+- return "off";
+- case 1:
+- return "on";
+- case 2:
+- return "force";
+- }
+- return "off";
+- }
+-
+-
+- /**
+- * Set compression level.
+- */
+- public void setCompression(String compression) {
+- if (compression.equals("on")) {
+- this.compressionLevel = 1;
+- } else if (compression.equals("force")) {
+- this.compressionLevel = 2;
+- } else if (compression.equals("off")) {
+- this.compressionLevel = 0;
+- } else {
+- try {
+- // Try to parse compression as an int, which would give the
+- // minimum compression size
+- compressionMinSize = Integer.parseInt(compression);
+- this.compressionLevel = 1;
+- } catch (Exception e) {
+- this.compressionLevel = 0;
+- }
+- }
+- }
+-
+- /**
+- * Set Minimum size to trigger compression.
+- */
+- public void setCompressionMinSize(int compressionMinSize) {
+- this.compressionMinSize = compressionMinSize;
+- }
+-
+-
+- /**
+- * Add user-agent for which gzip compression didn't works
+- * The user agent String given will be exactly matched
+- * to the user-agent header submitted by the client.
+- *
+- * @param userAgent user-agent string
+- */
+- public void addNoCompressionUserAgent(String userAgent) {
+- try {
+- Pattern nRule = Pattern.compile(userAgent);
+- noCompressionUserAgents =
+- addREArray(noCompressionUserAgents, nRule);
+- } catch (PatternSyntaxException pse) {
+- log.error(sm.getString("http11processor.regexp.error", userAgent), pse);
+- }
+- }
+-
+-
+- /**
+- * Set no compression user agent list (this method is best when used with
+- * a large number of connectors, where it would be better to have all of
+- * them referenced a single array).
+- */
+- public void setNoCompressionUserAgents(Pattern[] noCompressionUserAgents) {
+- this.noCompressionUserAgents = noCompressionUserAgents;
+- }
+-
+-
+- /**
+- * Set no compression user agent list.
+- * List contains users agents separated by ',' :
+- *
+- * ie: "gorilla,desesplorer,tigrus"
+- */
+- public void setNoCompressionUserAgents(String noCompressionUserAgents) {
+- if (noCompressionUserAgents != null) {
+- StringTokenizer st = new StringTokenizer(noCompressionUserAgents, ",");
+-
+- while (st.hasMoreTokens()) {
+- addNoCompressionUserAgent(st.nextToken().trim());
+- }
+- }
+- }
+-
+- /**
+- * Add a mime-type which will be compressable
+- * The mime-type String will be exactly matched
+- * in the response mime-type header .
+- *
+- * @param mimeType mime-type string
+- */
+- public void addCompressableMimeType(String mimeType) {
+- compressableMimeTypes =
+- addStringArray(compressableMimeTypes, mimeType);
+- }
+-
+-
+- /**
+- * Set compressable mime-type list (this method is best when used with
+- * a large number of connectors, where it would be better to have all of
+- * them referenced a single array).
+- */
+- public void setCompressableMimeTypes(String[] compressableMimeTypes) {
+- this.compressableMimeTypes = compressableMimeTypes;
+- }
+-
+-
+- /**
+- * Set compressable mime-type list
+- * List contains users agents separated by ',' :
+- *
+- * ie: "text/html,text/xml,text/plain"
+- */
+- public void setCompressableMimeTypes(String compressableMimeTypes) {
+- if (compressableMimeTypes != null) {
+- StringTokenizer st = new StringTokenizer(compressableMimeTypes, ",");
+-
+- while (st.hasMoreTokens()) {
+- addCompressableMimeType(st.nextToken().trim());
+- }
+- }
+- }
+-
+-
+- /**
+- * Return the list of restricted user agents.
+- */
+- public String[] findCompressableMimeTypes() {
+- return (compressableMimeTypes);
+- }
+-
+-
+-
+- // --------------------------------------------------------- Public Methods
+-
+-
+- /**
+- * Add input or output filter.
+- *
+- * @param className class name of the filter
+- */
+- protected void addFilter(String className) {
+- try {
+- Class clazz = Class.forName(className);
+- Object obj = clazz.newInstance();
+- if (obj instanceof InputFilter) {
+- inputBuffer.addFilter((InputFilter) obj);
+- } else if (obj instanceof OutputFilter) {
+- outputBuffer.addFilter((OutputFilter) obj);
+- } else {
+- log.warn(sm.getString("http11processor.filter.unknown", className));
+- }
+- } catch (Exception e) {
+- log.error(sm.getString("http11processor.filter.error", className), e);
+- }
+- }
+-
+-
+- /**
+- * General use method
+- *
+- * @param sArray the StringArray
+- * @param value string
+- */
+- private String[] addStringArray(String sArray[], String value) {
+- String[] result = null;
+- if (sArray == null) {
+- result = new String[1];
+- result[0] = value;
+- }
+- else {
+- result = new String[sArray.length + 1];
+- for (int i = 0; i < sArray.length; i++)
+- result[i] = sArray[i];
+- result[sArray.length] = value;
+- }
+- return result;
+- }
+-
+-
+- /**
+- * General use method
+- *
+- * @param rArray the REArray
+- * @param value Obj
+- */
+- private Pattern[] addREArray(Pattern rArray[], Pattern value) {
+- Pattern[] result = null;
+- if (rArray == null) {
+- result = new Pattern[1];
+- result[0] = value;
+- }
+- else {
+- result = new Pattern[rArray.length + 1];
+- for (int i = 0; i < rArray.length; i++)
+- result[i] = rArray[i];
+- result[rArray.length] = value;
+- }
+- return result;
+- }
+-
+-
+- /**
+- * General use method
+- *
+- * @param sArray the StringArray
+- * @param value string
+- */
+- private boolean inStringArray(String sArray[], String value) {
+- for (int i = 0; i < sArray.length; i++) {
+- if (sArray[i].equals(value)) {
+- return true;
+- }
+- }
+- return false;
+- }
+-
+-
+- /**
+- * Checks if any entry in the string array starts with the specified value
+- *
+- * @param sArray the StringArray
+- * @param value string
+- */
+- private boolean startsWithStringArray(String sArray[], String value) {
+- if (value == null)
+- return false;
+- for (int i = 0; i < sArray.length; i++) {
+- if (value.startsWith(sArray[i])) {
+- return true;
+- }
+- }
+- return false;
+- }
+-
+-
+- /**
+- * Add restricted user-agent (which will downgrade the connector
+- * to HTTP/1.0 mode). The user agent String given will be matched
+- * via regexp to the user-agent header submitted by the client.
+- *
+- * @param userAgent user-agent string
+- */
+- public void addRestrictedUserAgent(String userAgent) {
+- try {
+- Pattern nRule = Pattern.compile(userAgent);
+- restrictedUserAgents = addREArray(restrictedUserAgents, nRule);
+- } catch (PatternSyntaxException pse) {
+- log.error(sm.getString("http11processor.regexp.error", userAgent), pse);
+- }
+- }
+-
+-
+- /**
+- * Set restricted user agent list (this method is best when used with
+- * a large number of connectors, where it would be better to have all of
+- * them referenced a single array).
+- */
+- public void setRestrictedUserAgents(Pattern[] restrictedUserAgents) {
+- this.restrictedUserAgents = restrictedUserAgents;
+- }
+-
+-
+- /**
+- * Set restricted user agent list (which will downgrade the connector
+- * to HTTP/1.0 mode). List contains users agents separated by ',' :
+- *
+- * ie: "gorilla,desesplorer,tigrus"
+- */
+- public void setRestrictedUserAgents(String restrictedUserAgents) {
+- if (restrictedUserAgents != null) {
+- StringTokenizer st =
+- new StringTokenizer(restrictedUserAgents, ",");
+- while (st.hasMoreTokens()) {
+- addRestrictedUserAgent(st.nextToken().trim());
+- }
+- }
+- }
+-
+-
+- /**
+- * Return the list of restricted user agents.
+- */
+- public String[] findRestrictedUserAgents() {
+- String[] sarr = new String [restrictedUserAgents.length];
+-
+- for (int i = 0; i < restrictedUserAgents.length; i++)
+- sarr[i] = restrictedUserAgents[i].toString();
+-
+- return (sarr);
+- }
+-
+-
+- /**
+- * Set the maximum number of Keep-Alive requests to honor.
+- * This is to safeguard from DoS attacks. Setting to a negative
+- * value disables the check.
+- */
+- public void setMaxKeepAliveRequests(int mkar) {
+- maxKeepAliveRequests = mkar;
+- }
+-
+-
+- /**
+- * Return the number of Keep-Alive requests that we will honor.
+- */
+- public int getMaxKeepAliveRequests() {
+- return maxKeepAliveRequests;
+- }
+-
+-
+- /**
+- * Set the maximum size of a POST which will be buffered in SSL mode.
+- */
+- public void setMaxSavePostSize(int msps) {
+- maxSavePostSize = msps;
+- }
+-
+-
+- /**
+- * Return the maximum size of a POST which will be buffered in SSL mode.
+- */
+- public int getMaxSavePostSize() {
+- return maxSavePostSize;
+- }
+-
+-
+- /**
+- * Set the flag to control upload time-outs.
+- */
+- public void setDisableUploadTimeout(boolean isDisabled) {
+- disableUploadTimeout = isDisabled;
+- }
+-
+- /**
+- * Get the flag that controls upload time-outs.
+- */
+- public boolean getDisableUploadTimeout() {
+- return disableUploadTimeout;
+- }
+-
+- /**
+- * Set the socket buffer flag.
+- */
+- public void setSocketBuffer(int socketBuffer) {
+- this.socketBuffer = socketBuffer;
+- outputBuffer.setSocketBuffer(socketBuffer);
+- }
+-
+- /**
+- * Get the socket buffer flag.
+- */
+- public int getSocketBuffer() {
+- return socketBuffer;
+- }
+-
+- /**
+- * Set the upload timeout.
+- */
+- public void setTimeout( int timeouts ) {
+- timeout = timeouts ;
+- }
+-
+- /**
+- * Get the upload timeout.
+- */
+- public int getTimeout() {
+- return timeout;
+- }
+-
+-
+- /**
+- * Set the server header name.
+- */
+- public void setServer( String server ) {
+- if (server==null || server.equals("")) {
+- this.server = null;
+- } else {
+- this.server = server;
+- }
+- }
+-
+- /**
+- * Get the server header name.
+- */
+- public String getServer() {
+- return server;
+- }
+-
+-
+- /** Get the request associated with this processor.
+- *
+- * @return The request
+- */
+- public Request getRequest() {
+- return request;
+- }
+-
+- /**
+- * Process pipelined HTTP requests using the specified input and output
+- * streams.
+- *
+- * @throws IOException error during an I/O operation
+- */
+- public SocketState event(SocketStatus status)
+- throws IOException {
+-
+- RequestInfo rp = request.getRequestProcessor();
+-
+- try {
+- rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+- error = !adapter.event(request, response, status);
+- if ( !error ) {
+- NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment)socket.getAttachment(false);
+- if (attach != null) {
+- attach.setComet(comet);
+- if (comet) {
+- Integer comettimeout = (Integer) request.getAttribute("org.apache.tomcat.comet.timeout");
+- if (comettimeout != null) attach.setTimeout(comettimeout.longValue());
+- } else {
+- //reset the timeout
+- attach.setTimeout(endpoint.getSocketProperties().getSoTimeout());
+- }
+-
+- }
+- }
+- } catch (InterruptedIOException e) {
+- error = true;
+- } catch (Throwable t) {
+- log.error(sm.getString("http11processor.request.process"), t);
+- // 500 - Internal Server Error
+- response.setStatus(500);
+- error = true;
+- }
+-
+- rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+-
+- if (error) {
+- recycle();
+- return SocketState.CLOSED;
+- } else if (!comet) {
+- recycle();
+- return SocketState.OPEN;
+- } else {
+- return SocketState.LONG;
+- }
+- }
+-
+- /**
+- * Process pipelined HTTP requests using the specified input and output
+- * streams.
+- *
+- * @throws IOException error during an I/O operation
+- */
+- public SocketState process(NioChannel socket)
+- throws IOException {
+- RequestInfo rp = request.getRequestProcessor();
+- rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
+-
+- // Set the remote address
+- remoteAddr = null;
+- remoteHost = null;
+- localAddr = null;
+- localName = null;
+- remotePort = -1;
+- localPort = -1;
+-
+- // Setting up the socket
+- this.socket = socket;
+- inputBuffer.setSocket(socket);
+- outputBuffer.setSocket(socket);
+- inputBuffer.setSelectorPool(endpoint.getSelectorPool());
+- outputBuffer.setSelectorPool(endpoint.getSelectorPool());
+-
+- // Error flag
+- error = false;
+- keepAlive = true;
+- comet = false;
+-
+-
+- int keepAliveLeft = maxKeepAliveRequests;
+- long soTimeout = endpoint.getSoTimeout();
+-
+- int limit = 0;
+-
+- boolean keptAlive = false;
+- boolean openSocket = false;
+- boolean recycle = true;
+- while (!error && keepAlive && !comet) {
+-
+- // Parsing the request header
+- try {
+- if( !disableUploadTimeout && keptAlive && soTimeout > 0 ) {
+- socket.getIOChannel().socket().setSoTimeout((int)soTimeout);
+- inputBuffer.readTimeout = soTimeout;
+- }
+- if (!inputBuffer.parseRequestLine(keptAlive && (endpoint.getCurrentThreadsBusy() >= limit))) {
+- // This means that no data is available right now
+- // (long keepalive), so that the processor should be recycled
+- // and the method should return true
+- openSocket = true;
+- // Add the socket to the poller
+- socket.getPoller().add(socket);
+- break;
+- }
+- keptAlive = true;
+- if ( !inputBuffer.parseHeaders() ) {
+- openSocket = true;
+- socket.getPoller().add(socket);
+- recycle = false;
+- break;
+- }
+- request.setStartTime(System.currentTimeMillis());
+- if (!disableUploadTimeout) { //only for body, not for request headers
+- socket.getIOChannel().socket().setSoTimeout((int)timeout);
+- inputBuffer.readTimeout = soTimeout;
+- }
+- } catch (IOException e) {
+- error = true;
+- break;
+- } catch (Throwable t) {
+- if (log.isDebugEnabled()) {
+- log.debug(sm.getString("http11processor.header.parse"), t);
+- }
+- // 400 - Bad Request
+- response.setStatus(400);
+- error = true;
+- }
+-
+- // Setting up filters, and parse some request headers
+- rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
+- try {
+- prepareRequest();
+- } catch (Throwable t) {
+- if (log.isDebugEnabled()) {
+- log.debug(sm.getString("http11processor.request.prepare"), t);
+- }
+- // 400 - Internal Server Error
+- response.setStatus(400);
+- error = true;
+- }
+-
+- if (maxKeepAliveRequests > 0 && --keepAliveLeft == 0)
+- keepAlive = false;
+-
+- // Process the request in the adapter
+- if (!error) {
+- try {
+- rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+- adapter.service(request, response);
+- // Handle when the response was committed before a serious
+- // error occurred. Throwing a ServletException should both
+- // set the status to 500 and set the errorException.
+- // If we fail here, then the response is likely already
+- // committed, so we can't try and set headers.
+- if(keepAlive && !error) { // Avoid checking twice.
+- error = response.getErrorException() != null ||
+- statusDropsConnection(response.getStatus());
+- }
+- // Comet support
+- SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
+- if (key != null) {
+- NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment) key.attachment();
+- if (attach != null) {
+- attach.setComet(comet);
+- if (comet) {
+- Integer comettimeout = (Integer) request.getAttribute("org.apache.tomcat.comet.timeout");
+- if (comettimeout != null) attach.setTimeout(comettimeout.longValue());
+- }
+- }
+- }
+- } catch (InterruptedIOException e) {
+- error = true;
+- } catch (Throwable t) {
+- log.error(sm.getString("http11processor.request.process"), t);
+- // 500 - Internal Server Error
+- response.setStatus(500);
+- error = true;
+- }
+- }
+-
+- // Finish the handling of the request
+- if (!comet) {
+- endRequest();
+- }
+-
+- // If there was an error, make sure the request is counted as
+- // and error, and update the statistics counter
+- if (error) {
+- response.setStatus(500);
+- }
+- request.updateCounters();
+-
+- if (!comet) {
+- // Next request
+- inputBuffer.nextRequest();
+- outputBuffer.nextRequest();
+- }
+-
+- // Do sendfile as needed: add socket to sendfile and end
+- if (sendfileData != null && !error) {
+- KeyAttachment ka = (KeyAttachment)socket.getAttachment(false);
+- ka.setSendfileData(sendfileData);
+- sendfileData.keepAlive = keepAlive;
+- SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
+- //do the first write on this thread, might as well
+- openSocket = socket.getPoller().processSendfile(key,ka,true);
+- break;
+- }
+-
+-
+- rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
+-
+- }
+-
+- rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+-
+- if (comet) {
+- if (error) {
+- recycle();
+- return SocketState.CLOSED;
+- } else {
+- return SocketState.LONG;
+- }
+- } else {
+- if ( recycle ) recycle();
+- return (openSocket) ? SocketState.OPEN : SocketState.CLOSED;
+- }
+-
+- }
+-
+-
+- public void endRequest() {
+-
+- // Finish the handling of the request
+- try {
+- inputBuffer.endRequest();
+- } catch (IOException e) {
+- error = true;
+- } catch (Throwable t) {
+- log.error(sm.getString("http11processor.request.finish"), t);
+- // 500 - Internal Server Error
+- response.setStatus(500);
+- error = true;
+- }
+- try {
+- outputBuffer.endRequest();
+- } catch (IOException e) {
+- error = true;
+- } catch (Throwable t) {
+- log.error(sm.getString("http11processor.response.finish"), t);
+- error = true;
+- }
+-
+- }
+-
+-
+- public void recycle() {
+- inputBuffer.recycle();
+- outputBuffer.recycle();
+- this.socket = null;
+- this.cometClose = false;
+- this.comet = false;
+- }
+-
+-
+- // ----------------------------------------------------- ActionHook Methods
+-
+-
+- /**
+- * Send an action to the connector.
+- *
+- * @param actionCode Type of the action
+- * @param param Action parameter
+- */
+- public void action(ActionCode actionCode, Object param) {
+-
+- if (actionCode == ActionCode.ACTION_COMMIT) {
+- // Commit current response
+-
+- if (response.isCommitted())
+- return;
+-
+- // Validate and write response headers
+-
+- try {
+- prepareResponse();
+- outputBuffer.commit();
+- } catch (IOException e) {
+- // Set error flag
+- error = true;
+- }
+-
+- } else if (actionCode == ActionCode.ACTION_ACK) {
+-
+- // Acknowlege request
+-
+- // Send a 100 status back if it makes sense (response not committed
+- // yet, and client specified an expectation for 100-continue)
+-
+- if ((response.isCommitted()) || !expectation)
+- return;
+-
+- inputBuffer.setSwallowInput(true);
+- try {
+- outputBuffer.sendAck();
+- } catch (IOException e) {
+- // Set error flag
+- error = true;
+- }
+-
+- } else if (actionCode == ActionCode.ACTION_CLIENT_FLUSH) {
+-
+- try {
+- outputBuffer.flush();
+- } catch (IOException e) {
+- // Set error flag
+- error = true;
+- response.setErrorException(e);
+- }
+-
+- } else if (actionCode == ActionCode.ACTION_CLOSE) {
+- // Close
+-
+- // End the processing of the current request, and stop any further
+- // transactions with the client
+-
+- comet = false;
+- cometClose = true;
+- SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
+- if ( key != null ) {
+- NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment) key.attachment();
+- if ( attach!=null && attach.getComet()) {
+- //if this is a comet connection
+- //then execute the connection closure at the next selector loop
+- request.getAttributes().remove("org.apache.tomcat.comet.timeout");
+- //attach.setTimeout(5000); //force a cleanup in 5 seconds
+- //attach.setError(true); //this has caused concurrency errors
+- }
+- }
+-
+- try {
+- outputBuffer.endRequest();
+- } catch (IOException e) {
+- // Set error flag
+- error = true;
+- }
+-
+- } else if (actionCode == ActionCode.ACTION_RESET) {
+-
+- // Reset response
+-
+- // Note: This must be called before the response is committed
+-
+- outputBuffer.reset();
+-
+- } else if (actionCode == ActionCode.ACTION_CUSTOM) {
+-
+- // Do nothing
+-
+- } else if (actionCode == ActionCode.ACTION_REQ_HOST_ADDR_ATTRIBUTE) {
+-
+- // Get remote host address
+- if ((remoteAddr == null) && (socket != null)) {
+- InetAddress inetAddr = socket.getIOChannel().socket().getInetAddress();
+- if (inetAddr != null) {
+- remoteAddr = inetAddr.getHostAddress();
+- }
+- }
+- request.remoteAddr().setString(remoteAddr);
+-
+- } else if (actionCode == ActionCode.ACTION_REQ_LOCAL_NAME_ATTRIBUTE) {
+-
+- // Get local host name
+- if ((localName == null) && (socket != null)) {
+- InetAddress inetAddr = socket.getIOChannel().socket().getLocalAddress();
+- if (inetAddr != null) {
+- localName = inetAddr.getHostName();
+- }
+- }
+- request.localName().setString(localName);
+-
+- } else if (actionCode == ActionCode.ACTION_REQ_HOST_ATTRIBUTE) {
+-
+- // Get remote host name
+- if ((remoteHost == null) && (socket != null)) {
+- InetAddress inetAddr = socket.getIOChannel().socket().getInetAddress();
+- if (inetAddr != null) {
+- remoteHost = inetAddr.getHostName();
+- }
+- if(remoteHost == null) {
+- if(remoteAddr != null) {
+- remoteHost = remoteAddr;
+- } else { // all we can do is punt
+- request.remoteHost().recycle();
+- }
+- }
+- }
+- request.remoteHost().setString(remoteHost);
+-
+- } else if (actionCode == ActionCode.ACTION_REQ_LOCAL_ADDR_ATTRIBUTE) {
+-
+- if (localAddr == null)
+- localAddr = socket.getIOChannel().socket().getLocalAddress().getHostAddress();
+-
+- request.localAddr().setString(localAddr);
+-
+- } else if (actionCode == ActionCode.ACTION_REQ_REMOTEPORT_ATTRIBUTE) {
+-
+- if ((remotePort == -1 ) && (socket !=null)) {
+- remotePort = socket.getIOChannel().socket().getPort();
+- }
+- request.setRemotePort(remotePort);
+-
+- } else if (actionCode == ActionCode.ACTION_REQ_LOCALPORT_ATTRIBUTE) {
+-
+- if ((localPort == -1 ) && (socket !=null)) {
+- localPort = socket.getIOChannel().socket().getLocalPort();
+- }
+- request.setLocalPort(localPort);
+-
+- } else if (actionCode == ActionCode.ACTION_REQ_SSL_ATTRIBUTE ) {
+-
+- try {
+- if (sslSupport != null) {
+- Object sslO = sslSupport.getCipherSuite();
+- if (sslO != null)
+- request.setAttribute
+- (SSLSupport.CIPHER_SUITE_KEY, sslO);
+- sslO = sslSupport.getPeerCertificateChain(false);
+- if (sslO != null)
+- request.setAttribute
+- (SSLSupport.CERTIFICATE_KEY, sslO);
+- sslO = sslSupport.getKeySize();
+- if (sslO != null)
+- request.setAttribute
+- (SSLSupport.KEY_SIZE_KEY, sslO);
+- sslO = sslSupport.getSessionId();
+- if (sslO != null)
+- request.setAttribute
+- (SSLSupport.SESSION_ID_KEY, sslO);
+- }
+- } catch (Exception e) {
+- log.warn(sm.getString("http11processor.socket.ssl"), e);
+- }
+-
+- } else if (actionCode == ActionCode.ACTION_REQ_SSL_CERTIFICATE) {
+-
+- if( sslSupport != null) {
+- /*
+- * Consume and buffer the request body, so that it does not
+- * interfere with the client's handshake messages
+- */
+- InputFilter[] inputFilters = inputBuffer.getFilters();
+- ((BufferedInputFilter) inputFilters[Constants.BUFFERED_FILTER])
+- .setLimit(maxSavePostSize);
+- inputBuffer.addActiveFilter
+- (inputFilters[Constants.BUFFERED_FILTER]);
+- try {
+- Object sslO = sslSupport.getPeerCertificateChain(true);
+- if( sslO != null) {
+- request.setAttribute
+- (SSLSupport.CERTIFICATE_KEY, sslO);
+- }
+- } catch (Exception e) {
+- log.warn(sm.getString("http11processor.socket.ssl"), e);
+- }
+- }
+-
+- } else if (actionCode == ActionCode.ACTION_REQ_SET_BODY_REPLAY) {
+- ByteChunk body = (ByteChunk) param;
+-
+- InputFilter savedBody = new SavedRequestInputFilter(body);
+- savedBody.setRequest(request);
+-
+- InternalNioInputBuffer internalBuffer = (InternalNioInputBuffer)
+- request.getInputBuffer();
+- internalBuffer.addActiveFilter(savedBody);
+-
+- } else if (actionCode == ActionCode.ACTION_AVAILABLE) {
+- request.setAvailable(inputBuffer.available());
+- } else if (actionCode == ActionCode.ACTION_COMET_BEGIN) {
+- comet = true;
+- } else if (actionCode == ActionCode.ACTION_COMET_END) {
+- comet = false;
+- }
+-
+- }
+-
+-
+- // ------------------------------------------------------ Connector Methods
+-
+-
+- /**
+- * Set the associated adapter.
+- *
+- * @param adapter the new adapter
+- */
+- public void setAdapter(Adapter adapter) {
+- this.adapter = adapter;
+- }
+-
+- public void setSslSupport(SSLSupport sslSupport) {
+- this.sslSupport = sslSupport;
+- }
+-
+- /**
+- * Get the associated adapter.
+- *
+- * @return the associated adapter
+- */
+- public Adapter getAdapter() {
+- return adapter;
+- }
+-
+- public SSLSupport getSslSupport() {
+- return sslSupport;
+- }
+-
+- // ------------------------------------------------------ Protected Methods
+-
+-
+- /**
+- * After reading the request headers, we have to setup the request filters.
+- */
+- protected void prepareRequest() {
+-
+- http11 = true;
+- http09 = false;
+- contentDelimitation = false;
+- expectation = false;
+- sendfileData = null;
+- if (ssl) {
+- request.scheme().setString("https");
+- }
+- MessageBytes protocolMB = request.protocol();
+- if (protocolMB.equals(Constants.HTTP_11)) {
+- http11 = true;
+- protocolMB.setString(Constants.HTTP_11);
+- } else if (protocolMB.equals(Constants.HTTP_10)) {
+- http11 = false;
+- keepAlive = false;
+- protocolMB.setString(Constants.HTTP_10);
+- } else if (protocolMB.equals("")) {
+- // HTTP/0.9
+- http09 = true;
+- http11 = false;
+- keepAlive = false;
+- } else {
+- // Unsupported protocol
+- http11 = false;
+- error = true;
+- // Send 505; Unsupported HTTP version
+- response.setStatus(505);
+- }
+-
+- MessageBytes methodMB = request.method();
+- if (methodMB.equals(Constants.GET)) {
+- methodMB.setString(Constants.GET);
+- } else if (methodMB.equals(Constants.POST)) {
+- methodMB.setString(Constants.POST);
+- }
+-
+- MimeHeaders headers = request.getMimeHeaders();
+-
+- // Check connection header
+- MessageBytes connectionValueMB = headers.getValue("connection");
+- if (connectionValueMB != null) {
+- ByteChunk connectionValueBC = connectionValueMB.getByteChunk();
+- if (findBytes(connectionValueBC, Constants.CLOSE_BYTES) != -1) {
+- keepAlive = false;
+- } else if (findBytes(connectionValueBC,
+- Constants.KEEPALIVE_BYTES) != -1) {
+- keepAlive = true;
+- }
+- }
+-
+- MessageBytes expectMB = null;
+- if (http11)
+- expectMB = headers.getValue("expect");
+- if ((expectMB != null)
+- && (expectMB.indexOfIgnoreCase("100-continue", 0) != -1)) {
+- inputBuffer.setSwallowInput(false);
+- expectation = true;
+- }
+-
+- // Check user-agent header
+- if ((restrictedUserAgents != null) && ((http11) || (keepAlive))) {
+- MessageBytes userAgentValueMB = headers.getValue("user-agent");
+- // Check in the restricted list, and adjust the http11
+- // and keepAlive flags accordingly
+- if(userAgentValueMB != null) {
+- String userAgentValue = userAgentValueMB.toString();
+- for (int i = 0; i < restrictedUserAgents.length; i++) {
+- if (restrictedUserAgents[i].matcher(userAgentValue).matches()) {
+- http11 = false;
+- keepAlive = false;
+- break;
+- }
+- }
+- }
+- }
+-
+- // Check for a full URI (including protocol://host:port/)
+- ByteChunk uriBC = request.requestURI().getByteChunk();
+- if (uriBC.startsWithIgnoreCase("http", 0)) {
+-
+- int pos = uriBC.indexOf("://", 0, 3, 4);
+- int uriBCStart = uriBC.getStart();
+- int slashPos = -1;
+- if (pos != -1) {
+- byte[] uriB = uriBC.getBytes();
+- slashPos = uriBC.indexOf('/', pos + 3);
+- if (slashPos == -1) {
+- slashPos = uriBC.getLength();
+- // Set URI as "/"
+- request.requestURI().setBytes
+- (uriB, uriBCStart + pos + 1, 1);
+- } else {
+- request.requestURI().setBytes
+- (uriB, uriBCStart + slashPos,
+- uriBC.getLength() - slashPos);
+- }
+- MessageBytes hostMB = headers.setValue("host");
+- hostMB.setBytes(uriB, uriBCStart + pos + 3,
+- slashPos - pos - 3);
+- }
+-
+- }
+-
+- // Input filter setup
+- InputFilter[] inputFilters = inputBuffer.getFilters();
+-
+- // Parse transfer-encoding header
+- MessageBytes transferEncodingValueMB = null;
+- if (http11)
+- transferEncodingValueMB = headers.getValue("transfer-encoding");
+- if (transferEncodingValueMB != null) {
+- String transferEncodingValue = transferEncodingValueMB.toString();
+- // Parse the comma separated list. "identity" codings are ignored
+- int startPos = 0;
+- int commaPos = transferEncodingValue.indexOf(',');
+- String encodingName = null;
+- while (commaPos != -1) {
+- encodingName = transferEncodingValue.substring
+- (startPos, commaPos).toLowerCase().trim();
+- if (!addInputFilter(inputFilters, encodingName)) {
+- // Unsupported transfer encoding
+- error = true;
+- // 501 - Unimplemented
+- response.setStatus(501);
+- }
+- startPos = commaPos + 1;
+- commaPos = transferEncodingValue.indexOf(',', startPos);
+- }
+- encodingName = transferEncodingValue.substring(startPos)
+- .toLowerCase().trim();
+- if (!addInputFilter(inputFilters, encodingName)) {
+- // Unsupported transfer encoding
+- error = true;
+- // 501 - Unimplemented
+- response.setStatus(501);
+- }
+- }
+-
+- // Parse content-length header
+- long contentLength = request.getContentLengthLong();
+- if (contentLength >= 0 && !contentDelimitation) {
+- inputBuffer.addActiveFilter
+- (inputFilters[Constants.IDENTITY_FILTER]);
+- contentDelimitation = true;
+- }
+-
+- MessageBytes valueMB = headers.getValue("host");
+-
+- // Check host header
+- if (http11 && (valueMB == null)) {
+- error = true;
+- // 400 - Bad request
+- response.setStatus(400);
+- }
+-
+- parseHost(valueMB);
+-
+- if (!contentDelimitation) {
+- // If there's no content length
+- // (broken HTTP/1.0 or HTTP/1.1), assume
+- // the client is not broken and didn't send a body
+- inputBuffer.addActiveFilter
+- (inputFilters[Constants.VOID_FILTER]);
+- contentDelimitation = true;
+- }
+-
+- // Advertise sendfile support through a request attribute
+- if (endpoint.getUseSendfile())
+- request.setAttribute("org.apache.tomcat.sendfile.support", Boolean.TRUE);
+- // Advertise comet support through a request attribute
+- request.setAttribute("org.apache.tomcat.comet.support", Boolean.TRUE);
+- // Advertise comet timeout support
+- request.setAttribute("org.apache.tomcat.comet.timeout.support", Boolean.TRUE);
+-
+- }
+-
+-
+- /**
+- * Parse host.
+- */
+- public void parseHost(MessageBytes valueMB) {
+-
+- if (valueMB == null || valueMB.isNull()) {
+- // HTTP/1.0
+- // Default is what the socket tells us. Overriden if a host is
+- // found/parsed
+- request.setServerPort(endpoint.getPort());
+- return;
+- }
+-
+- ByteChunk valueBC = valueMB.getByteChunk();
+- byte[] valueB = valueBC.getBytes();
+- int valueL = valueBC.getLength();
+- int valueS = valueBC.getStart();
+- int colonPos = -1;
+- if (hostNameC.length < valueL) {
+- hostNameC = new char[valueL];
+- }
+-
+- boolean ipv6 = (valueB[valueS] == '[');
+- boolean bracketClosed = false;
+- for (int i = 0; i < valueL; i++) {
+- char b = (char) valueB[i + valueS];
+- hostNameC[i] = b;
+- if (b == ']') {
+- bracketClosed = true;
+- } else if (b == ':') {
+- if (!ipv6 || bracketClosed) {
+- colonPos = i;
+- break;
+- }
+- }
+- }
+-
+- if (colonPos < 0) {
+- if (!ssl) {
+- // 80 - Default HTTP port
+- request.setServerPort(80);
+- } else {
+- // 443 - Default HTTPS port
+- request.setServerPort(443);
+- }
+- request.serverName().setChars(hostNameC, 0, valueL);
+- } else {
+-
+- request.serverName().setChars(hostNameC, 0, colonPos);
+-
+- int port = 0;
+- int mult = 1;
+- for (int i = valueL - 1; i > colonPos; i--) {
+- int charValue = HexUtils.DEC[(int) valueB[i + valueS]];
+- if (charValue == -1) {
+- // Invalid character
+- error = true;
+- // 400 - Bad request
+- response.setStatus(400);
+- break;
+- }
+- port = port + (charValue * mult);
+- mult = 10 * mult;
+- }
+- request.setServerPort(port);
+-
+- }
+-
+- }
+-
+-
+- /**
+- * Check for compression
+- */
+- private boolean isCompressable() {
+-
+- // Nope Compression could works in HTTP 1.0 also
+- // cf: mod_deflate
+-
+- // Compression only since HTTP 1.1
+- // if (! http11)
+- // return false;
+-
+- // Check if browser support gzip encoding
+- MessageBytes acceptEncodingMB =
+- request.getMimeHeaders().getValue("accept-encoding");
+-
+- if ((acceptEncodingMB == null)
+- || (acceptEncodingMB.indexOf("gzip") == -1))
+- return false;
+-
+- // Check if content is not allready gzipped
+- MessageBytes contentEncodingMB =
+- response.getMimeHeaders().getValue("Content-Encoding");
+-
+- if ((contentEncodingMB != null)
+- && (contentEncodingMB.indexOf("gzip") != -1))
+- return false;
+-
+- // If force mode, allways compress (test purposes only)
+- if (compressionLevel == 2)
+- return true;
+-
+- // Check for incompatible Browser
+- if (noCompressionUserAgents != null) {
+- MessageBytes userAgentValueMB =
+- request.getMimeHeaders().getValue("user-agent");
+- if(userAgentValueMB != null) {
+- String userAgentValue = userAgentValueMB.toString();
+-
+- // If one Regexp rule match, disable compression
+- for (int i = 0; i < noCompressionUserAgents.length; i++)
+- if (noCompressionUserAgents[i].matcher(userAgentValue).matches())
+- return false;
+- }
+- }
+-
+- // Check if suffisant len to trig the compression
+- long contentLength = response.getContentLengthLong();
+- if ((contentLength == -1)
+- || (contentLength > compressionMinSize)) {
+- // Check for compatible MIME-TYPE
+- if (compressableMimeTypes != null) {
+- return (startsWithStringArray(compressableMimeTypes,
+- response.getContentType()));
+- }
+- }
+-
+- return false;
+- }
+-
+-
+- /**
+- * When committing the response, we have to validate the set of headers, as
+- * well as setup the response filters.
+- */
+- protected void prepareResponse() throws IOException {
+-
+- boolean entityBody = true;
+- contentDelimitation = false;
+-
+- OutputFilter[] outputFilters = outputBuffer.getFilters();
+-
+- if (http09 == true) {
+- // HTTP/0.9
+- outputBuffer.addActiveFilter
+- (outputFilters[Constants.IDENTITY_FILTER]);
+- return;
+- }
+-
+- int statusCode = response.getStatus();
+- if ((statusCode == 204) || (statusCode == 205)
+- || (statusCode == 304)) {
+- // No entity body
+- outputBuffer.addActiveFilter
+- (outputFilters[Constants.VOID_FILTER]);
+- entityBody = false;
+- contentDelimitation = true;
+- }
+-
+- MessageBytes methodMB = request.method();
+- if (methodMB.equals("HEAD")) {
+- // No entity body
+- outputBuffer.addActiveFilter
+- (outputFilters[Constants.VOID_FILTER]);
+- contentDelimitation = true;
+- }
+-
+- // Sendfile support
+- if (this.endpoint.getUseSendfile()) {
+- String fileName = (String) request.getAttribute("org.apache.tomcat.sendfile.filename");
+- if (fileName != null) {
+- // No entity body sent here
+- outputBuffer.addActiveFilter(outputFilters[Constants.VOID_FILTER]);
+- contentDelimitation = true;
+- sendfileData = new NioEndpoint.SendfileData();
+- sendfileData.fileName = fileName;
+- sendfileData.pos = ((Long) request.getAttribute("org.apache.tomcat.sendfile.start")).longValue();
+- sendfileData.length = ((Long) request.getAttribute("org.apache.tomcat.sendfile.end")).longValue() - sendfileData.pos;
+- }
+- }
+-
+-
+-
+- // Check for compression
+- boolean useCompression = false;
+- if (entityBody && (compressionLevel > 0) && (sendfileData == null)) {
+- useCompression = isCompressable();
+- // Change content-length to -1 to force chunking
+- if (useCompression) {
+- response.setContentLength(-1);
+- }
+- }
+-
+- MimeHeaders headers = response.getMimeHeaders();
+- if (!entityBody) {
+- response.setContentLength(-1);
+- } else {
+- String contentType = response.getContentType();
+- if (contentType != null) {
+- headers.setValue("Content-Type").setString(contentType);
+- }
+- String contentLanguage = response.getContentLanguage();
+- if (contentLanguage != null) {
+- headers.setValue("Content-Language")
+- .setString(contentLanguage);
+- }
+- }
+-
+- long contentLength = response.getContentLengthLong();
+- if (contentLength != -1) {
+- headers.setValue("Content-Length").setLong(contentLength);
+- outputBuffer.addActiveFilter
+- (outputFilters[Constants.IDENTITY_FILTER]);
+- contentDelimitation = true;
+- } else {
+- if (entityBody && http11 && keepAlive) {
+- outputBuffer.addActiveFilter
+- (outputFilters[Constants.CHUNKED_FILTER]);
+- contentDelimitation = true;
+- headers.addValue(Constants.TRANSFERENCODING).setString(Constants.CHUNKED);
+- } else {
+- outputBuffer.addActiveFilter
+- (outputFilters[Constants.IDENTITY_FILTER]);
+- }
+- }
+-
+- if (useCompression) {
+- outputBuffer.addActiveFilter(outputFilters[Constants.GZIP_FILTER]);
+- headers.setValue("Content-Encoding").setString("gzip");
+- // Make Proxies happy via Vary (from mod_deflate)
+- headers.setValue("Vary").setString("Accept-Encoding");
+- }
+-
+- // Add date header
+- headers.setValue("Date").setString(FastHttpDateFormat.getCurrentDate());
+-
+- // FIXME: Add transfer encoding header
+-
+- if ((entityBody) && (!contentDelimitation)) {
+- // Mark as close the connection after the request, and add the
+- // connection: close header
+- keepAlive = false;
+- }
+-
+- // If we know that the request is bad this early, add the
+- // Connection: close header.
+- keepAlive = keepAlive && !statusDropsConnection(statusCode);
+- if (!keepAlive) {
+- headers.addValue(Constants.CONNECTION).setString(Constants.CLOSE);
+- } else if (!http11 && !error) {
+- headers.addValue(Constants.CONNECTION).setString(Constants.KEEPALIVE);
+- }
+-
+- // Build the response header
+- outputBuffer.sendStatus();
+-
+- // Add server header
+- if (server != null) {
+- headers.setValue("Server").setString(server);
+- } else {
+- outputBuffer.write(Constants.SERVER_BYTES);
+- }
+-
+- int size = headers.size();
+- for (int i = 0; i < size; i++) {
+- outputBuffer.sendHeader(headers.getName(i), headers.getValue(i));
+- }
+- outputBuffer.endHeaders();
+-
+- }
+-
+-
+- /**
+- * Initialize standard input and output filters.
+- */
+- protected void initializeFilters() {
+-
+- // Create and add the identity filters.
+- inputBuffer.addFilter(new IdentityInputFilter());
+- outputBuffer.addFilter(new IdentityOutputFilter());
+-
+- // Create and add the chunked filters.
+- inputBuffer.addFilter(new ChunkedInputFilter());
+- outputBuffer.addFilter(new ChunkedOutputFilter());
+-
+- // Create and add the void filters.
+- inputBuffer.addFilter(new VoidInputFilter());
+- outputBuffer.addFilter(new VoidOutputFilter());
+-
+- // Create and add buffered input filter
+- inputBuffer.addFilter(new BufferedInputFilter());
+-
+- // Create and add the chunked filters.
+- //inputBuffer.addFilter(new GzipInputFilter());
+- outputBuffer.addFilter(new GzipOutputFilter());
+-
+- }
+-
+-
+- /**
+- * Add an input filter to the current request.
+- *
+- * @return false if the encoding was not found (which would mean it is
+- * unsupported)
+- */
+- protected boolean addInputFilter(InputFilter[] inputFilters,
+- String encodingName) {
+- if (encodingName.equals("identity")) {
+- // Skip
+- } else if (encodingName.equals("chunked")) {
+- inputBuffer.addActiveFilter
+- (inputFilters[Constants.CHUNKED_FILTER]);
+- contentDelimitation = true;
+- } else {
+- for (int i = 2; i < inputFilters.length; i++) {
+- if (inputFilters[i].getEncodingName()
+- .toString().equals(encodingName)) {
+- inputBuffer.addActiveFilter(inputFilters[i]);
+- return true;
+- }
+- }
+- return false;
+- }
+- return true;
+- }
+-
+-
+- /**
+- * Specialized utility method: find a sequence of lower case bytes inside
+- * a ByteChunk.
+- */
+- protected int findBytes(ByteChunk bc, byte[] b) {
+-
+- byte first = b[0];
+- byte[] buff = bc.getBuffer();
+- int start = bc.getStart();
+- int end = bc.getEnd();
+-
+- // Look for first char
+- int srcEnd = b.length;
+-
+- for (int i = start; i <= (end - srcEnd); i++) {
+- if (Ascii.toLower(buff[i]) != first) continue;
+- // found first char, now look for a match
+- int myPos = i+1;
+- for (int srcPos = 1; srcPos < srcEnd; ) {
+- if (Ascii.toLower(buff[myPos++]) != b[srcPos++])
+- break;
+- if (srcPos == srcEnd) return i - start; // found it
+- }
+- }
+- return -1;
+-
+- }
+-
+- /**
+- * Determine if we must drop the connection because of the HTTP status
+- * code. Use the same list of codes as Apache/httpd.
+- */
+- protected boolean statusDropsConnection(int status) {
+- return status == 400 /* SC_BAD_REQUEST */ ||
+- status == 408 /* SC_REQUEST_TIMEOUT */ ||
+- status == 411 /* SC_LENGTH_REQUIRED */ ||
+- status == 413 /* SC_REQUEST_ENTITY_TOO_LARGE */ ||
+- status == 414 /* SC_REQUEST_URI_TOO_LARGE */ ||
+- status == 500 /* SC_INTERNAL_SERVER_ERROR */ ||
+- status == 503 /* SC_SERVICE_UNAVAILABLE */ ||
+- status == 501 /* SC_NOT_IMPLEMENTED */;
+- }
+-
+-}
+Index: java/org/apache/coyote/http11/InternalNioInputBuffer.java
+===================================================================
+--- java/org/apache/coyote/http11/InternalNioInputBuffer.java (revision 590752)
++++ java/org/apache/coyote/http11/InternalNioInputBuffer.java (working copy)
+@@ -1,908 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-
+-package org.apache.coyote.http11;
+-
+-import java.io.EOFException;
+-import java.io.IOException;
+-import java.nio.channels.Selector;
+-
+-import org.apache.coyote.InputBuffer;
+-import org.apache.coyote.Request;
+-import org.apache.tomcat.util.buf.ByteChunk;
+-import org.apache.tomcat.util.buf.MessageBytes;
+-import org.apache.tomcat.util.http.MimeHeaders;
+-import org.apache.tomcat.util.net.NioChannel;
+-import org.apache.tomcat.util.net.NioSelectorPool;
+-import org.apache.tomcat.util.res.StringManager;
+-
+-/**
+- * Implementation of InputBuffer which provides HTTP request header parsing as
+- * well as transfer decoding.
+- *
+- * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+- * @author Filip Hanik
+- */
+-public class InternalNioInputBuffer implements InputBuffer {
+-
+-
+- // -------------------------------------------------------------- Constants
+-
+- enum HeaderParseStatus {DONE, HAVE_MORE_HEADERS, NEED_MORE_DATA}
+- enum HeaderParsePosition {HEADER_START, HEADER_NAME, HEADER_VALUE, HEADER_MULTI_LINE}
+- // ----------------------------------------------------------- Constructors
+-
+-
+- /**
+- * Alternate constructor.
+- */
+- public InternalNioInputBuffer(Request request, int headerBufferSize,
+- long readTimeout) {
+-
+- this.request = request;
+- headers = request.getMimeHeaders();
+-
+- buf = new byte[headerBufferSize];
+-// if (headerBufferSize < (8 * 1024)) {
+-// bbuf = ByteBuffer.allocateDirect(6 * 1500);
+-// } else {
+-// bbuf = ByteBuffer.allocateDirect((headerBufferSize / 1500 + 1) * 1500);
+-// }
+-
+- inputStreamInputBuffer = new SocketInputBuffer();
+-
+- filterLibrary = new InputFilter[0];
+- activeFilters = new InputFilter[0];
+- lastActiveFilter = -1;
+-
+- parsingHeader = true;
+- parsingRequestLine = true;
+- headerParsePos = HeaderParsePosition.HEADER_START;
+- headerData.recycle();
+- swallowInput = true;
+-
+- if (readTimeout < 0) {
+- this.readTimeout = -1;
+- } else {
+- this.readTimeout = readTimeout;
+- }
+-
+- }
+-
+-
+- // -------------------------------------------------------------- Variables
+-
+-
+- /**
+- * The string manager for this package.
+- */
+- protected static StringManager sm =
+- StringManager.getManager(Constants.Package);
+-
+-
+- // ----------------------------------------------------- Instance Variables
+-
+-
+- /**
+- * Associated Coyote request.
+- */
+- protected Request request;
+-
+-
+- /**
+- * Headers of the associated request.
+- */
+- protected MimeHeaders headers;
+-
+-
+- /**
+- * State.
+- */
+- protected boolean parsingHeader;
+- protected boolean parsingRequestLine;
+- protected HeaderParsePosition headerParsePos;
+-
+-
+- /**
+- * Swallow input ? (in the case of an expectation)
+- */
+- protected boolean swallowInput;
+-
+-
+- /**
+- * Pointer to the current read buffer.
+- */
+- protected byte[] buf;
+-
+-
+- /**
+- * Last valid byte.
+- */
+- protected int lastValid;
+-
+-
+- /**
+- * Position in the buffer.
+- */
+- protected int pos;
+-
+-
+- /**
+- * Pos of the end of the header in the buffer, which is also the
+- * start of the body.
+- */
+- protected int end;
+-
+-
+-
+- /**
+- * Underlying socket.
+- */
+- protected NioChannel socket;
+-
+- /**
+- * Selector pool, for blocking reads and blocking writes
+- */
+- protected NioSelectorPool pool;
+-
+-
+- /**
+- * Underlying input buffer.
+- */
+- protected InputBuffer inputStreamInputBuffer;
+-
+-
+- /**
+- * Filter library.
+- * Note: Filter[0] is always the "chunked" filter.
+- */
+- protected InputFilter[] filterLibrary;
+-
+-
+- /**
+- * Active filters (in order).
+- */
+- protected InputFilter[] activeFilters;
+-
+-
+- /**
+- * Index of the last active filter.
+- */
+- protected int lastActiveFilter;
+-
+-
+- /**
+- * The socket timeout used when reading the first block of the request
+- * header.
+- */
+- protected long readTimeout;
+-
+- // ------------------------------------------------------------- Properties
+-
+-
+- /**
+- * Set the underlying socket.
+- */
+- public void setSocket(NioChannel socket) {
+- this.socket = socket;
+- }
+-
+- /**
+- * Get the underlying socket input stream.
+- */
+- public NioChannel getSocket() {
+- return socket;
+- }
+-
+- public void setSelectorPool(NioSelectorPool pool) {
+- this.pool = pool;
+- }
+-
+- public NioSelectorPool getSelectorPool() {
+- return pool;
+- }
+-
+-
+- /**
+- * Add an input filter to the filter library.
+- */
+- public void addFilter(InputFilter filter) {
+-
+- InputFilter[] newFilterLibrary =
+- new InputFilter[filterLibrary.length + 1];
+- for (int i = 0; i < filterLibrary.length; i++) {
+- newFilterLibrary[i] = filterLibrary[i];
+- }
+- newFilterLibrary[filterLibrary.length] = filter;
+- filterLibrary = newFilterLibrary;
+-
+- activeFilters = new InputFilter[filterLibrary.length];
+-
+- }
+-
+-
+- /**
+- * Get filters.
+- */
+- public InputFilter[] getFilters() {
+-
+- return filterLibrary;
+-
+- }
+-
+-
+- /**
+- * Clear filters.
+- */
+- public void clearFilters() {
+-
+- filterLibrary = new InputFilter[0];
+- lastActiveFilter = -1;
+-
+- }
+-
+-
+- /**
+- * Add an input filter to the filter library.
+- */
+- public void addActiveFilter(InputFilter filter) {
+-
+- if (lastActiveFilter == -1) {
+- filter.setBuffer(inputStreamInputBuffer);
+- } else {
+- for (int i = 0; i <= lastActiveFilter; i++) {
+- if (activeFilters[i] == filter)
+- return;
+- }
+- filter.setBuffer(activeFilters[lastActiveFilter]);
+- }
+-
+- activeFilters[++lastActiveFilter] = filter;
+-
+- filter.setRequest(request);
+-
+- }
+-
+-
+- /**
+- * Set the swallow input flag.
+- */
+- public void setSwallowInput(boolean swallowInput) {
+- this.swallowInput = swallowInput;
+- }
+-
+- // --------------------------------------------------------- Public Methods
+-
+-
+- /**
+- * Recycle the input buffer. This should be called when closing the
+- * connection.
+- */
+- public void recycle() {
+- // Recycle filters
+- for (int i = 0; i <= lastActiveFilter; i++) {
+- activeFilters[i].recycle();
+- }
+-
+- // Recycle Request object
+- request.recycle();
+-
+- socket = null;
+- lastValid = 0;
+- pos = 0;
+- lastActiveFilter = -1;
+- parsingHeader = true;
+- headerParsePos = HeaderParsePosition.HEADER_START;
+- parsingRequestLine = true;
+- headerData.recycle();
+- swallowInput = true;
+-
+- }
+-
+-
+- /**
+- * End processing of current HTTP request.
+- * Note: All bytes of the current request should have been already
+- * consumed. This method only resets all the pointers so that we are ready
+- * to parse the next HTTP request.
+- */
+- public void nextRequest() {
+-
+- // Recycle Request object
+- request.recycle();
+-
+- // Copy leftover bytes to the beginning of the buffer
+- if (lastValid - pos > 0) {
+- int npos = 0;
+- int opos = pos;
+- while (lastValid - opos > opos - npos) {
+- System.arraycopy(buf, opos, buf, npos, opos - npos);
+- npos += pos;
+- opos += pos;
+- }
+- System.arraycopy(buf, opos, buf, npos, lastValid - opos);
+- }
+-
+- // Recycle filters
+- for (int i = 0; i <= lastActiveFilter; i++) {
+- activeFilters[i].recycle();
+- }
+-
+- // Reset pointers
+- lastValid = lastValid - pos;
+- pos = 0;
+- lastActiveFilter = -1;
+- parsingHeader = true;
+- headerParsePos = HeaderParsePosition.HEADER_START;
+- parsingRequestLine = true;
+- headerData.recycle();
+- swallowInput = true;
+-
+- }
+-
+-
+- /**
+- * End request (consumes leftover bytes).
+- *
+- * @throws IOException an undelying I/O error occured
+- */
+- public void endRequest()
+- throws IOException {
+-
+- if (swallowInput && (lastActiveFilter != -1)) {
+- int extraBytes = (int) activeFilters[lastActiveFilter].end();
+- pos = pos - extraBytes;
+- }
+-
+- }
+-
+-
+- /**
+- * Read the request line. This function is meant to be used during the
+- * HTTP request header parsing. Do NOT attempt to read the request body
+- * using it.
+- *
+- * @throws IOException If an exception occurs during the underlying socket
+- * read operations, or if the given buffer is not big enough to accomodate
+- * the whole line.
+- * @return true if data is properly fed; false if no data is available
+- * immediately and thread should be freed
+- */
+- public boolean parseRequestLine(boolean useAvailableData)
+- throws IOException {
+-
+- //check state
+- if ( !parsingRequestLine ) return true;
+-
+- int start = 0;
+-
+- //
+- // Skipping blank lines
+- //
+-
+- byte chr = 0;
+- do {
+-
+- // Read new bytes if needed
+- if (pos >= lastValid) {
+- if (useAvailableData) {
+- return false;
+- }
+- if (readTimeout == -1) {
+- if (!fill(false,true)) //request line parsing
+- throw new EOFException(sm.getString("iib.eof.error"));
+- } else {
+- // Do a simple read with a short timeout
+- if ( !readSocket(true, false) ) return false;
+- }
+- }
+-
+- chr = buf[pos++];
+-
+- } while ((chr == Constants.CR) || (chr == Constants.LF));
+-
+- pos--;
+-
+- // Mark the current buffer position
+- start = pos;
+-
+- if (pos >= lastValid) {
+- if (useAvailableData) {
+- return false;
+- }
+- if (readTimeout == -1) {
+- if (!fill(false,true)) //request line parsing
+- return false;
+- } else {
+- // Do a simple read with a short timeout
+- if ( !readSocket(true, true) ) return false;
+- }
+- }
+-
+- //
+- // Reading the method name
+- // Method name is always US-ASCII
+- //
+-
+- boolean space = false;
+-
+- while (!space) {
+-
+- // Read new bytes if needed
+- if (pos >= lastValid) {
+- if (!fill(true,true)) //request line parsing
+- return false;
+- }
+-
+- if (buf[pos] == Constants.SP) {
+- space = true;
+- request.method().setBytes(buf, start, pos - start);
+- }
+-
+- pos++;
+-
+- }
+-
+- // Mark the current buffer position
+- start = pos;
+- int end = 0;
+- int questionPos = -1;
+-
+- //
+- // Reading the URI
+- //
+-
+- space = false;
+- boolean eol = false;
+-
+- while (!space) {
+-
+- // Read new bytes if needed
+- if (pos >= lastValid) {
+- if (!fill(true,true)) //request line parsing
+- return false;
+- }
+-
+- if (buf[pos] == Constants.SP) {
+- space = true;
+- end = pos;
+- } else if ((buf[pos] == Constants.CR)
+- || (buf[pos] == Constants.LF)) {
+- // HTTP/0.9 style request
+- eol = true;
+- space = true;
+- end = pos;
+- } else if ((buf[pos] == Constants.QUESTION)
+- && (questionPos == -1)) {
+- questionPos = pos;
+- }
+-
+- pos++;
+-
+- }
+-
+- request.unparsedURI().setBytes(buf, start, end - start);
+- if (questionPos >= 0) {
+- request.queryString().setBytes(buf, questionPos + 1,
+- end - questionPos - 1);
+- request.requestURI().setBytes(buf, start, questionPos - start);
+- } else {
+- request.requestURI().setBytes(buf, start, end - start);
+- }
+-
+- // Mark the current buffer position
+- start = pos;
+- end = 0;
+-
+- //
+- // Reading the protocol
+- // Protocol is always US-ASCII
+- //
+-
+- while (!eol) {
+-
+- // Read new bytes if needed
+- if (pos >= lastValid) {
+- if (!fill(true,true)) //reques line parsing
+- return false;
+- }
+-
+- if (buf[pos] == Constants.CR) {
+- end = pos;
+- } else if (buf[pos] == Constants.LF) {
+- if (end == 0)
+- end = pos;
+- eol = true;
+- }
+-
+- pos++;
+-
+- }
+-
+- if ((end - start) > 0) {
+- request.protocol().setBytes(buf, start, end - start);
+- } else {
+- request.protocol().setString("");
+- }
+- parsingRequestLine = false;
+- return true;
+-
+- }
+-
+- private void expand(int newsize) {
+- if ( newsize > buf.length ) {
+- byte[] tmp = new byte[newsize];
+- System.arraycopy(buf,0,tmp,0,buf.length);
+- buf = tmp;
+- tmp = null;
+- }
+- }
+- /**
+- * Perform blocking read with a timeout if desired
+- * @param timeout boolean - if we want to use the timeout data
+- * @param block - true if the system should perform a blocking read, false otherwise
+- * @return boolean - true if data was read, false is no data read, EOFException if EOF is reached
+- * @throws IOException if a socket exception occurs
+- * @throws EOFException if end of stream is reached
+- */
+- private boolean readSocket(boolean timeout, boolean block) throws IOException {
+- int nRead = 0;
+- long rto = timeout?this.readTimeout:-1;
+- socket.getBufHandler().getReadBuffer().clear();
+- if ( block ) {
+- Selector selector = null;
+- try { selector = getSelectorPool().get(); }catch ( IOException x ) {}
+- try {
+- nRead = getSelectorPool().read(socket.getBufHandler().getReadBuffer(),socket,selector,rto);
+- } catch ( EOFException eof ) {
+- nRead = -1;
+- } finally {
+- if ( selector != null ) getSelectorPool().put(selector);
+- }
+- } else {
+- nRead = socket.read(socket.getBufHandler().getReadBuffer());
+- }
+- if (nRead > 0) {
+- socket.getBufHandler().getReadBuffer().flip();
+- socket.getBufHandler().getReadBuffer().limit(nRead);
+- expand(nRead + pos);
+- socket.getBufHandler().getReadBuffer().get(buf, pos, nRead);
+- lastValid = pos + nRead;
+- return true;
+- } else if (nRead == -1) {
+- //return false;
+- throw new EOFException(sm.getString("iib.eof.error"));
+- } else {
+- return false;
+- }
+- }
+-
+- /**
+- * Parse the HTTP headers.
+- */
+- public boolean parseHeaders()
+- throws IOException {
+- HeaderParseStatus status = HeaderParseStatus.HAVE_MORE_HEADERS;
+-
+- do {
+- status = parseHeader();
+- } while ( status == HeaderParseStatus.HAVE_MORE_HEADERS );
+- if (status == HeaderParseStatus.DONE) {
+- parsingHeader = false;
+- end = pos;
+- return true;
+- } else {
+- return false;
+- }
+- }
+-
+-
+- /**
+- * Parse an HTTP header.
+- *
+- * @return false after reading a blank line (which indicates that the
+- * HTTP header parsing is done
+- */
+- public HeaderParseStatus parseHeader()
+- throws IOException {
+-
+- //
+- // Check for blank line
+- //
+-
+- byte chr = 0;
+- while (headerParsePos == HeaderParsePosition.HEADER_START) {
+-
+- // Read new bytes if needed
+- if (pos >= lastValid) {
+- if (!fill(true,true)) {//parse header
+- headerParsePos = HeaderParsePosition.HEADER_START;
+- return HeaderParseStatus.NEED_MORE_DATA;
+- }
+- }
+-
+- chr = buf[pos];
+-
+- if ((chr == Constants.CR) || (chr == Constants.LF)) {
+- if (chr == Constants.LF) {
+- pos++;
+- return HeaderParseStatus.DONE;
+- }
+- } else {
+- break;
+- }
+-
+- pos++;
+-
+- }
+-
+- if ( headerParsePos == HeaderParsePosition.HEADER_START ) {
+- // Mark the current buffer position
+- headerData.start = pos;
+- headerParsePos = HeaderParsePosition.HEADER_NAME;
+- }
+-
+- //
+- // Reading the header name
+- // Header name is always US-ASCII
+- //
+-
+-
+-
+- while (headerParsePos == HeaderParsePosition.HEADER_NAME) {
+-
+- // Read new bytes if needed
+- if (pos >= lastValid) {
+- if (!fill(true,true)) { //parse header
+- return HeaderParseStatus.NEED_MORE_DATA;
+- }
+- }
+-
+- if (buf[pos] == Constants.COLON) {
+- headerParsePos = HeaderParsePosition.HEADER_VALUE;
+- headerData.headerValue = headers.addValue(buf, headerData.start, pos - headerData.start);
+- }
+- chr = buf[pos];
+- if ((chr >= Constants.A) && (chr <= Constants.Z)) {
+- buf[pos] = (byte) (chr - Constants.LC_OFFSET);
+- }
+-
+- pos++;
+- if ( headerParsePos == HeaderParsePosition.HEADER_VALUE ) {
+- // Mark the current buffer position
+- headerData.start = pos;
+- headerData.realPos = pos;
+- }
+- }
+-
+-
+- //
+- // Reading the header value (which can be spanned over multiple lines)
+- //
+-
+- boolean eol = false;
+-
+- while (headerParsePos == HeaderParsePosition.HEADER_VALUE ||
+- headerParsePos == HeaderParsePosition.HEADER_MULTI_LINE) {
+- if ( headerParsePos == HeaderParsePosition.HEADER_VALUE ) {
+-
+- boolean space = true;
+-
+- // Skipping spaces
+- while (space) {
+-
+- // Read new bytes if needed
+- if (pos >= lastValid) {
+- if (!fill(true,true)) {//parse header
+- //HEADER_VALUE, should already be set
+- return HeaderParseStatus.NEED_MORE_DATA;
+- }
+- }
+-
+- if ((buf[pos] == Constants.SP) || (buf[pos] == Constants.HT)) {
+- pos++;
+- } else {
+- space = false;
+- }
+-
+- }
+-
+- headerData.lastSignificantChar = headerData.realPos;
+-
+- // Reading bytes until the end of the line
+- while (!eol) {
+-
+- // Read new bytes if needed
+- if (pos >= lastValid) {
+- if (!fill(true,true)) {//parse header
+- //HEADER_VALUE
+- return HeaderParseStatus.NEED_MORE_DATA;
+- }
+-
+- }
+-
+- if (buf[pos] == Constants.CR) {
+- } else if (buf[pos] == Constants.LF) {
+- eol = true;
+- } else if (buf[pos] == Constants.SP) {
+- buf[headerData.realPos] = buf[pos];
+- headerData.realPos++;
+- } else {
+- buf[headerData.realPos] = buf[pos];
+- headerData.realPos++;
+- headerData.lastSignificantChar = headerData.realPos;
+- }
+-
+- pos++;
+-
+- }
+-
+- headerData.realPos = headerData.lastSignificantChar;
+-
+- // Checking the first character of the new line. If the character
+- // is a LWS, then it's a multiline header
+- headerParsePos = HeaderParsePosition.HEADER_MULTI_LINE;
+- }
+- // Read new bytes if needed
+- if (pos >= lastValid) {
+- if (!fill(true,true)) {//parse header
+-
+- //HEADER_MULTI_LINE
+- return HeaderParseStatus.NEED_MORE_DATA;
+- }
+- }
+-
+- chr = buf[pos];
+- if ( headerParsePos == HeaderParsePosition.HEADER_MULTI_LINE ) {
+- if ( (chr != Constants.SP) && (chr != Constants.HT)) {
+- headerParsePos = HeaderParsePosition.HEADER_START;
+- } else {
+- eol = false;
+- // Copying one extra space in the buffer (since there must
+- // be at least one space inserted between the lines)
+- buf[headerData.realPos] = chr;
+- headerData.realPos++;
+- }
+- }
+- }
+- // Set the header value
+- headerData.headerValue.setBytes(buf, headerData.start, headerData.realPos - headerData.start);
+- headerData.recycle();
+- return HeaderParseStatus.HAVE_MORE_HEADERS;
+- }
+-
+- protected HeaderParseData headerData = new HeaderParseData();
+- public static class HeaderParseData {
+- int start = 0;
+- int realPos = 0;
+- int lastSignificantChar = 0;
+- MessageBytes headerValue = null;
+- public void recycle() {
+- start = 0;
+- realPos = 0;
+- lastSignificantChar = 0;
+- headerValue = null;
+- }
+- }
+-
+-
+- /**
+- * Available bytes (note that due to encoding, this may not correspond )
+- */
+- public int available() {
+- int result = (lastValid - pos);
+- if ((result == 0) && (lastActiveFilter >= 0)) {
+- for (int i = 0; (result == 0) && (i <= lastActiveFilter); i++) {
+- result = activeFilters[i].available();
+- }
+- }
+- return result;
+- }
+-
+-
+- // ---------------------------------------------------- InputBuffer Methods
+-
+-
+- /**
+- * Read some bytes.
+- */
+- public int doRead(ByteChunk chunk, Request req)
+- throws IOException {
+-
+- if (lastActiveFilter == -1)
+- return inputStreamInputBuffer.doRead(chunk, req);
+- else
+- return activeFilters[lastActiveFilter].doRead(chunk,req);
+-
+- }
+-
+-
+- // ------------------------------------------------------ Protected Methods
+-
+- /**
+- * Fill the internal buffer using data from the undelying input stream.
+- *
+- * @return false if at end of stream
+- */
+- protected boolean fill(boolean timeout, boolean block)
+- throws IOException, EOFException {
+-
+- boolean read = false;
+-
+- if (parsingHeader) {
+-
+- if (lastValid == buf.length) {
+- throw new IOException
+- (sm.getString("iib.requestheadertoolarge.error"));
+- }
+-
+- // Do a simple read with a short timeout
+- read = readSocket(timeout,block);
+- } else {
+-
+- if (buf.length - end < 4500) {
+- // In this case, the request header was really large, so we allocate a
+- // brand new one; the old one will get GCed when subsequent requests
+- // clear all references
+- buf = new byte[buf.length];
+- end = 0;
+- }
+- pos = end;
+- lastValid = pos;
+- // Do a simple read with a short timeout
+- read = readSocket(timeout, block);
+- }
+- return read;
+- }
+-
+-
+- // ------------------------------------- InputStreamInputBuffer Inner Class
+-
+-
+- /**
+- * This class is an input buffer which will read its data from an input
+- * stream.
+- */
+- protected class SocketInputBuffer
+- implements InputBuffer {
+-
+-
+- /**
+- * Read bytes into the specified chunk.
+- */
+- public int doRead(ByteChunk chunk, Request req )
+- throws IOException {
+-
+- if (pos >= lastValid) {
+- if (!fill(true,true)) //read body, must be blocking, as the thread is inside the app
+- return -1;
+- }
+-
+- int length = lastValid - pos;
+- chunk.setBytes(buf, pos, length);
+- pos = lastValid;
+-
+- return (length);
+-
+- }
+-
+-
+- }
+-
+-
+-}
+Index: java/org/apache/coyote/http11/InternalNioOutputBuffer.java
+===================================================================
+--- java/org/apache/coyote/http11/InternalNioOutputBuffer.java (revision 590752)
++++ java/org/apache/coyote/http11/InternalNioOutputBuffer.java (working copy)
+@@ -1,810 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.coyote.http11;
+-
+-import java.io.IOException;
+-import java.nio.ByteBuffer;
+-import java.nio.channels.SelectionKey;
+-import java.nio.channels.Selector;
+-
+-import org.apache.coyote.ActionCode;
+-import org.apache.coyote.OutputBuffer;
+-import org.apache.coyote.Response;
+-import org.apache.tomcat.util.buf.ByteChunk;
+-import org.apache.tomcat.util.buf.CharChunk;
+-import org.apache.tomcat.util.buf.MessageBytes;
+-import org.apache.tomcat.util.http.HttpMessages;
+-import org.apache.tomcat.util.http.MimeHeaders;
+-import org.apache.tomcat.util.net.NioChannel;
+-import org.apache.tomcat.util.net.NioEndpoint;
+-import org.apache.tomcat.util.net.NioSelectorPool;
+-import org.apache.tomcat.util.res.StringManager;
+-
+-/**
+- * Output buffer.
+- *
+- * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+- * @author Filip Hanik
+- */
+-public class InternalNioOutputBuffer
+- implements OutputBuffer {
+-
+-
+- // -------------------------------------------------------------- Constants
+-
+-
+- // ----------------------------------------------------------- Constructors
+- int bbufLimit = 0;
+-
+-
+- /**
+- * Default constructor.
+- */
+- public InternalNioOutputBuffer(Response response) {
+- this(response, Constants.DEFAULT_HTTP_HEADER_BUFFER_SIZE, 10000);
+- }
+-
+-
+- /**
+- * Alternate constructor.
+- */
+- public InternalNioOutputBuffer(Response response, int headerBufferSize, long writeTimeout) {
+-
+- this.response = response;
+- headers = response.getMimeHeaders();
+-
+- buf = new byte[headerBufferSize];
+-
+- if (headerBufferSize < (8 * 1024)) {
+- bbufLimit = 6 * 1500;
+- } else {
+- bbufLimit = (headerBufferSize / 1500 + 1) * 1500;
+- }
+- //bbuf = ByteBuffer.allocateDirect(bbufLimit);
+-
+- outputStreamOutputBuffer = new SocketOutputBuffer();
+-
+- filterLibrary = new OutputFilter[0];
+- activeFilters = new OutputFilter[0];
+- lastActiveFilter = -1;
+-
+- committed = false;
+- finished = false;
+-
+- this.writeTimeout = writeTimeout;
+-
+- // Cause loading of HttpMessages
+- HttpMessages.getMessage(200);
+-
+- }
+-
+-
+- // -------------------------------------------------------------- Variables
+-
+-
+- /**
+- * The string manager for this package.
+- */
+- protected static StringManager sm =
+- StringManager.getManager(Constants.Package);
+-
+-
+- // ----------------------------------------------------- Instance Variables
+-
+-
+- /**
+- * Associated Coyote response.
+- */
+- protected Response response;
+-
+-
+- /**
+- * Headers of the associated request.
+- */
+- protected MimeHeaders headers;
+-
+-
+- /**
+- * Committed flag.
+- */
+- protected boolean committed;
+-
+-
+- /**
+- * Finished flag.
+- */
+- protected boolean finished;
+-
+-
+- /**
+- * Pointer to the current write buffer.
+- */
+- protected byte[] buf;
+-
+-
+- /**
+- * Position in the buffer.
+- */
+- protected int pos;
+-
+-
+- /**
+- * Underlying socket.
+- */
+- protected NioChannel socket;
+-
+- /**
+- * Selector pool, for blocking reads and blocking writes
+- */
+- protected NioSelectorPool pool;
+-
+-
+-
+- /**
+- * Underlying output buffer.
+- */
+- protected OutputBuffer outputStreamOutputBuffer;
+-
+-
+- /**
+- * Filter library.
+- * Note: Filter[0] is always the "chunked" filter.
+- */
+- protected OutputFilter[] filterLibrary;
+-
+-
+- /**
+- * Active filter (which is actually the top of the pipeline).
+- */
+- protected OutputFilter[] activeFilters;
+-
+-
+- /**
+- * Index of the last active filter.
+- */
+- protected int lastActiveFilter;
+-
+- /**
+- * Write time out in milliseconds
+- */
+- protected long writeTimeout = -1;
+-
+-
+- // ------------------------------------------------------------- Properties
+-
+-
+- /**
+- * Set the underlying socket.
+- */
+- public void setSocket(NioChannel socket) {
+- this.socket = socket;
+- }
+-
+- public void setWriteTimeout(long writeTimeout) {
+- this.writeTimeout = writeTimeout;
+- }
+-
+- /**
+- * Get the underlying socket input stream.
+- */
+- public NioChannel getSocket() {
+- return socket;
+- }
+-
+- public long getWriteTimeout() {
+- return writeTimeout;
+- }
+-
+- public void setSelectorPool(NioSelectorPool pool) {
+- this.pool = pool;
+- }
+-
+- public NioSelectorPool getSelectorPool() {
+- return pool;
+- }
+- /**
+- * Set the socket buffer size.
+- */
+- public void setSocketBuffer(int socketBufferSize) {
+- // FIXME: Remove
+- }
+-
+-
+- /**
+- * Add an output filter to the filter library.
+- */
+- public void addFilter(OutputFilter filter) {
+-
+- OutputFilter[] newFilterLibrary =
+- new OutputFilter[filterLibrary.length + 1];
+- for (int i = 0; i < filterLibrary.length; i++) {
+- newFilterLibrary[i] = filterLibrary[i];
+- }
+- newFilterLibrary[filterLibrary.length] = filter;
+- filterLibrary = newFilterLibrary;
+-
+- activeFilters = new OutputFilter[filterLibrary.length];
+-
+- }
+-
+-
+- /**
+- * Get filters.
+- */
+- public OutputFilter[] getFilters() {
+-
+- return filterLibrary;
+-
+- }
+-
+-
+- /**
+- * Clear filters.
+- */
+- public void clearFilters() {
+-
+- filterLibrary = new OutputFilter[0];
+- lastActiveFilter = -1;
+-
+- }
+-
+-
+- /**
+- * Add an output filter to the filter library.
+- */
+- public void addActiveFilter(OutputFilter filter) {
+-
+- if (lastActiveFilter == -1) {
+- filter.setBuffer(outputStreamOutputBuffer);
+- } else {
+- for (int i = 0; i <= lastActiveFilter; i++) {
+- if (activeFilters[i] == filter)
+- return;
+- }
+- filter.setBuffer(activeFilters[lastActiveFilter]);
+- }
+-
+- activeFilters[++lastActiveFilter] = filter;
+-
+- filter.setResponse(response);
+-
+- }
+-
+-
+- // --------------------------------------------------------- Public Methods
+-
+-
+- /**
+- * Flush the response.
+- *
+- * @throws IOException an undelying I/O error occured
+- */
+- public void flush()
+- throws IOException {
+-
+- if (!committed) {
+-
+- // Send the connector a request for commit. The connector should
+- // then validate the headers, send them (using sendHeader) and
+- // set the filters accordingly.
+- response.action(ActionCode.ACTION_COMMIT, null);
+-
+- }
+-
+- // Flush the current buffer
+- flushBuffer();
+-
+- }
+-
+-
+- /**
+- * Reset current response.
+- *
+- * @throws IllegalStateException if the response has already been committed
+- */
+- public void reset() {
+-
+- if (committed)
+- throw new IllegalStateException(/*FIXME:Put an error message*/);
+-
+- // Recycle Request object
+- response.recycle();
+-
+- }
+-
+-
+- /**
+- * Recycle the output buffer. This should be called when closing the
+- * connection.
+- */
+- public void recycle() {
+- // Recycle filters
+- for (int i = 0; i <= lastActiveFilter; i++) {
+- activeFilters[i].recycle();
+- }
+-
+- // Recycle Request object
+- response.recycle();
+- socket.getBufHandler().getWriteBuffer().clear();
+-
+- socket = null;
+- pos = 0;
+- lastActiveFilter = -1;
+- committed = false;
+- finished = false;
+-
+- }
+-
+-
+- /**
+- * End processing of current HTTP request.
+- * Note: All bytes of the current request should have been already
+- * consumed. This method only resets all the pointers so that we are ready
+- * to parse the next HTTP request.
+- */
+- public void nextRequest() {
+-
+- // Recycle Request object
+- response.recycle();
+-
+- // Recycle filters
+- for (int i = 0; i <= lastActiveFilter; i++) {
+- activeFilters[i].recycle();
+- }
+-
+- // Reset pointers
+- pos = 0;
+- lastActiveFilter = -1;
+- committed = false;
+- finished = false;
+-
+- }
+-
+-
+- /**
+- * End request.
+- *
+- * @throws IOException an undelying I/O error occured
+- */
+- public void endRequest()
+- throws IOException {
+-
+- if (!committed) {
+-
+- // Send the connector a request for commit. The connector should
+- // then validate the headers, send them (using sendHeader) and
+- // set the filters accordingly.
+- response.action(ActionCode.ACTION_COMMIT, null);
+-
+- }
+-
+- if (finished)
+- return;
+-
+- if (lastActiveFilter != -1)
+- activeFilters[lastActiveFilter].end();
+-
+- flushBuffer();
+-
+- finished = true;
+-
+- }
+-
+-
+- // ------------------------------------------------ HTTP/1.1 Output Methods
+-
+-
+- /**
+- * Send an acknoledgement.
+- */
+- public void sendAck()
+- throws IOException {
+-
+- if (!committed) {
+- //Socket.send(socket, Constants.ACK_BYTES, 0, Constants.ACK_BYTES.length) < 0
+- socket.getBufHandler() .getWriteBuffer().put(Constants.ACK_BYTES,0,Constants.ACK_BYTES.length);
+- writeToSocket(socket.getBufHandler() .getWriteBuffer(),true);
+- }
+-
+- }
+-
+- private synchronized void writeToSocket(ByteBuffer bytebuffer, boolean flip) throws IOException {
+- //int limit = bytebuffer.position();
+- if ( flip ) bytebuffer.flip();
+- int written = 0;
+- Selector selector = null;
+- try {
+- selector = getSelectorPool().get();
+- } catch ( IOException x ) {
+- //ignore
+- }
+- try {
+- written = getSelectorPool().write(bytebuffer, socket, selector, writeTimeout);
+- //make sure we are flushed
+- do {
+- if (socket.flush(true,selector,writeTimeout)) break;
+- }while ( true );
+- }finally {
+- if ( selector != null ) getSelectorPool().put(selector);
+- }
+- socket.getBufHandler().getWriteBuffer().clear();
+- this.total = 0;
+- }
+-
+-
+- /**
+- * Send the response status line.
+- */
+- public void sendStatus() {
+-
+- // Write protocol name
+- write(Constants.HTTP_11_BYTES);
+- buf[pos++] = Constants.SP;
+-
+- // Write status code
+- int status = response.getStatus();
+- switch (status) {
+- case 200:
+- write(Constants._200_BYTES);
+- break;
+- case 400:
+- write(Constants._400_BYTES);
+- break;
+- case 404:
+- write(Constants._404_BYTES);
+- break;
+- default:
+- write(status);
+- }
+-
+- buf[pos++] = Constants.SP;
+-
+- // Write message
+- String message = response.getMessage();
+- if (message == null) {
+- write(HttpMessages.getMessage(status));
+- } else {
+- write(message);
+- }
+-
+- // End the response status line
+- buf[pos++] = Constants.CR;
+- buf[pos++] = Constants.LF;
+-
+- }
+-
+-
+- /**
+- * Send a header.
+- *
+- * @param name Header name
+- * @param value Header value
+- */
+- public void sendHeader(MessageBytes name, MessageBytes value) {
+-
+- write(name);
+- buf[pos++] = Constants.COLON;
+- buf[pos++] = Constants.SP;
+- write(value);
+- buf[pos++] = Constants.CR;
+- buf[pos++] = Constants.LF;
+-
+- }
+-
+-
+- /**
+- * Send a header.
+- *
+- * @param name Header name
+- * @param value Header value
+- */
+- public void sendHeader(ByteChunk name, ByteChunk value) {
+-
+- write(name);
+- buf[pos++] = Constants.COLON;
+- buf[pos++] = Constants.SP;
+- write(value);
+- buf[pos++] = Constants.CR;
+- buf[pos++] = Constants.LF;
+-
+- }
+-
+-
+- /**
+- * Send a header.
+- *
+- * @param name Header name
+- * @param value Header value
+- */
+- public void sendHeader(String name, String value) {
+-
+- write(name);
+- buf[pos++] = Constants.COLON;
+- buf[pos++] = Constants.SP;
+- write(value);
+- buf[pos++] = Constants.CR;
+- buf[pos++] = Constants.LF;
+-
+- }
+-
+-
+- /**
+- * End the header block.
+- */
+- public void endHeaders() {
+-
+- buf[pos++] = Constants.CR;
+- buf[pos++] = Constants.LF;
+-
+- }
+-
+-
+- // --------------------------------------------------- OutputBuffer Methods
+-
+-
+- /**
+- * Write the contents of a byte chunk.
+- *
+- * @param chunk byte chunk
+- * @return number of bytes written
+- * @throws IOException an undelying I/O error occured
+- */
+- public int doWrite(ByteChunk chunk, Response res)
+- throws IOException {
+-
+- if (!committed) {
+-
+- // Send the connector a request for commit. The connector should
+- // then validate the headers, send them (using sendHeaders) and
+- // set the filters accordingly.
+- response.action(ActionCode.ACTION_COMMIT, null);
+-
+- }
+-
+- if (lastActiveFilter == -1)
+- return outputStreamOutputBuffer.doWrite(chunk, res);
+- else
+- return activeFilters[lastActiveFilter].doWrite(chunk, res);
+-
+- }
+-
+-
+- // ------------------------------------------------------ Protected Methods
+-
+-
+- /**
+- * Commit the response.
+- *
+- * @throws IOException an undelying I/O error occured
+- */
+- protected void commit()
+- throws IOException {
+-
+- // The response is now committed
+- committed = true;
+- response.setCommitted(true);
+-
+- if (pos > 0) {
+- // Sending the response header buffer
+- addToBB(buf, 0, pos);
+- }
+-
+- }
+-
+- int total = 0;
+- private synchronized void addToBB(byte[] buf, int offset, int length) throws IOException {
+- while (socket.getBufHandler().getWriteBuffer().remaining() < length) {
+- flushBuffer();
+- }
+- socket.getBufHandler().getWriteBuffer().put(buf, offset, length);
+- total += length;
+- NioEndpoint.KeyAttachment ka = (NioEndpoint.KeyAttachment)socket.getAttachment(false);
+- if ( ka!= null ) ka.access();//prevent timeouts for just doing client writes
+- }
+-
+-
+- /**
+- * This method will write the contents of the specyfied message bytes
+- * buffer to the output stream, without filtering. This method is meant to
+- * be used to write the response header.
+- *
+- * @param mb data to be written
+- */
+- protected void write(MessageBytes mb) {
+-
+- if (mb.getType() == MessageBytes.T_BYTES) {
+- ByteChunk bc = mb.getByteChunk();
+- write(bc);
+- } else if (mb.getType() == MessageBytes.T_CHARS) {
+- CharChunk cc = mb.getCharChunk();
+- write(cc);
+- } else {
+- write(mb.toString());
+- }
+-
+- }
+-
+-
+- /**
+- * This method will write the contents of the specyfied message bytes
+- * buffer to the output stream, without filtering. This method is meant to
+- * be used to write the response header.
+- *
+- * @param bc data to be written
+- */
+- protected void write(ByteChunk bc) {
+-
+- // Writing the byte chunk to the output buffer
+- int length = bc.getLength();
+- System.arraycopy(bc.getBytes(), bc.getStart(), buf, pos, length);
+- pos = pos + length;
+-
+- }
+-
+-
+- /**
+- * This method will write the contents of the specyfied char
+- * buffer to the output stream, without filtering. This method is meant to
+- * be used to write the response header.
+- *
+- * @param cc data to be written
+- */
+- protected void write(CharChunk cc) {
+-
+- int start = cc.getStart();
+- int end = cc.getEnd();
+- char[] cbuf = cc.getBuffer();
+- for (int i = start; i < end; i++) {
+- char c = cbuf[i];
+- // Note: This is clearly incorrect for many strings,
+- // but is the only consistent approach within the current
+- // servlet framework. It must suffice until servlet output
+- // streams properly encode their output.
+- if ((c <= 31) && (c != 9)) {
+- c = ' ';
+- } else if (c == 127) {
+- c = ' ';
+- }
+- buf[pos++] = (byte) c;
+- }
+-
+- }
+-
+-
+- /**
+- * This method will write the contents of the specyfied byte
+- * buffer to the output stream, without filtering. This method is meant to
+- * be used to write the response header.
+- *
+- * @param b data to be written
+- */
+- public void write(byte[] b) {
+-
+- // Writing the byte chunk to the output buffer
+- System.arraycopy(b, 0, buf, pos, b.length);
+- pos = pos + b.length;
+-
+- }
+-
+-
+- /**
+- * This method will write the contents of the specyfied String to the
+- * output stream, without filtering. This method is meant to be used to
+- * write the response header.
+- *
+- * @param s data to be written
+- */
+- protected void write(String s) {
+-
+- if (s == null)
+- return;
+-
+- // From the Tomcat 3.3 HTTP/1.0 connector
+- int len = s.length();
+- for (int i = 0; i < len; i++) {
+- char c = s.charAt (i);
+- // Note: This is clearly incorrect for many strings,
+- // but is the only consistent approach within the current
+- // servlet framework. It must suffice until servlet output
+- // streams properly encode their output.
+- if ((c <= 31) && (c != 9)) {
+- c = ' ';
+- } else if (c == 127) {
+- c = ' ';
+- }
+- buf[pos++] = (byte) c;
+- }
+-
+- }
+-
+-
+- /**
+- * This method will print the specified integer to the output stream,
+- * without filtering. This method is meant to be used to write the
+- * response header.
+- *
+- * @param i data to be written
+- */
+- protected void write(int i) {
+-
+- write(String.valueOf(i));
+-
+- }
+-
+-
+- /**
+- * Callback to write data from the buffer.
+- */
+- protected void flushBuffer()
+- throws IOException {
+-
+- //prevent timeout for async,
+- SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
+- if (key != null) {
+- NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment) key.attachment();
+- attach.access();
+- }
+-
+- //write to the socket, if there is anything to write
+- if (socket.getBufHandler().getWriteBuffer().position() > 0) {
+- writeToSocket(socket.getBufHandler().getWriteBuffer(),true);
+- }
+- }
+-
+-
+- // ----------------------------------- OutputStreamOutputBuffer Inner Class
+-
+-
+- /**
+- * This class is an output buffer which will write data to an output
+- * stream.
+- */
+- protected class SocketOutputBuffer
+- implements OutputBuffer {
+-
+-
+- /**
+- * Write chunk.
+- */
+- public int doWrite(ByteChunk chunk, Response res)
+- throws IOException {
+-
+- int len = chunk.getLength();
+- int start = chunk.getStart();
+- byte[] b = chunk.getBuffer();
+- while (len > 0) {
+- int thisTime = len;
+- if (socket.getBufHandler().getWriteBuffer().position() == socket.getBufHandler().getWriteBuffer().capacity()) {
+- flushBuffer();
+- }
+- if (thisTime > socket.getBufHandler().getWriteBuffer().remaining()) {
+- thisTime = socket.getBufHandler().getWriteBuffer().remaining();
+- }
+- addToBB(b,start,thisTime);
+- len = len - thisTime;
+- start = start + thisTime;
+- }
+- return chunk.getLength();
+-
+- }
+-
+-
+- }
+-
+-
+-}
16 years, 6 months
JBossWeb SVN: r332 - sandbox/tomcat/tomcat6.
by jbossweb-commits@lists.jboss.org
Author: jfrederic.clere(a)jboss.com
Date: 2007-11-01 09:54:20 -0400 (Thu, 01 Nov 2007)
New Revision: 332
Added:
sandbox/tomcat/tomcat6/README
Modified:
sandbox/tomcat/tomcat6/cluster.patch
Log:
Also remove the cluster tests.
Added: sandbox/tomcat/tomcat6/README
===================================================================
--- sandbox/tomcat/tomcat6/README (rev 0)
+++ sandbox/tomcat/tomcat6/README 2007-11-01 13:54:20 UTC (rev 332)
@@ -0,0 +1,3 @@
+patch.build.patch: Arrange build.
+cluster.patch: remove cluster and documentation.
+nio.patch: remove nio and documentation.
Modified: sandbox/tomcat/tomcat6/cluster.patch
===================================================================
--- sandbox/tomcat/tomcat6/cluster.patch 2007-11-01 13:15:22 UTC (rev 331)
+++ sandbox/tomcat/tomcat6/cluster.patch 2007-11-01 13:54:20 UTC (rev 332)
@@ -35184,3 +35184,5271 @@
#base.path=C:/path/to/the/repository
#base.path=/usr/local
+Index: test/org/apache/catalina/tribes/test/interceptors/TestTwoPhaseCommit.java
+===================================================================
+--- test/org/apache/catalina/tribes/test/interceptors/TestTwoPhaseCommit.java (revision 590752)
++++ test/org/apache/catalina/tribes/test/interceptors/TestTwoPhaseCommit.java (working copy)
+@@ -1,41 +0,0 @@
+-/*
+- * 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
+- */
+-
+-package org.apache.catalina.tribes.test.interceptors;
+-
+-import junit.framework.TestCase;
+-
+-/**
+- * <p>Title: </p>
+- *
+- * <p>Description: </p>
+- *
+- * <p>Company: </p>
+- *
+- * @author not attributable
+- * @version 1.0
+- */
+-public class TestTwoPhaseCommit extends TestCase {
+-
+- protected void setUp() throws Exception {
+- super.setUp();
+- }
+-
+- protected void tearDown() throws Exception {
+- super.tearDown();
+- }
+-
+-}
+Index: test/org/apache/catalina/tribes/test/interceptors/TestNonBlockingCoordinator.java
+===================================================================
+--- test/org/apache/catalina/tribes/test/interceptors/TestNonBlockingCoordinator.java (revision 590752)
++++ test/org/apache/catalina/tribes/test/interceptors/TestNonBlockingCoordinator.java (working copy)
+@@ -1,111 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.test.interceptors;
+-
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.group.GroupChannel;
+-import org.apache.catalina.tribes.group.interceptors.NonBlockingCoordinator;
+-import org.apache.catalina.tribes.group.interceptors.TcpFailureDetector;
+-import junit.framework.TestCase;
+-import junit.framework.TestResult;
+-import junit.framework.TestSuite;
+-
+-public class TestNonBlockingCoordinator extends TestCase {
+-
+- GroupChannel[] channels = null;
+- NonBlockingCoordinator[] coordinators = null;
+- int channelCount = 10;
+- Thread[] threads = null;
+- protected void setUp() throws Exception {
+- System.out.println("Setup");
+- super.setUp();
+- channels = new GroupChannel[channelCount];
+- coordinators = new NonBlockingCoordinator[channelCount];
+- threads = new Thread[channelCount];
+- for ( int i=0; i<channelCount; i++ ) {
+- channels[i] = new GroupChannel();
+- coordinators[i] = new NonBlockingCoordinator();
+- channels[i].addInterceptor(coordinators[i]);
+- channels[i].addInterceptor(new TcpFailureDetector());
+- final int j = i;
+- threads[i] = new Thread() {
+- public void run() {
+- try {
+- channels[j].start(Channel.DEFAULT);
+- Thread.sleep(50);
+- } catch (Exception x) {
+- x.printStackTrace();
+- }
+- }
+- };
+- }
+- for ( int i=0; i<channelCount; i++ ) threads[i].start();
+- for ( int i=0; i<channelCount; i++ ) threads[i].join();
+- Thread.sleep(1000);
+- }
+-
+- public void testCoord1() throws Exception {
+- for (int i=1; i<channelCount; i++ )
+- assertEquals("Message count expected to be equal.",channels[i-1].getMembers().length,channels[i].getMembers().length);
+- Member member = coordinators[0].getCoordinator();
+- int cnt = 0;
+- while ( member == null && (cnt++ < 100 ) ) try {Thread.sleep(100); member = coordinators[0].getCoordinator();}catch ( Exception x){}
+- for (int i=0; i<channelCount; i++ ) super.assertEquals(member,coordinators[i].getCoordinator());
+- System.out.println("Coordinator[1] is:"+member);
+-
+- }
+-
+- public void testCoord2() throws Exception {
+- Member member = coordinators[1].getCoordinator();
+- System.out.println("Coordinator[2a] is:" + member);
+- int index = -1;
+- for ( int i=0; i<channelCount; i++ ) {
+- if ( channels[i].getLocalMember(false).equals(member) ) {
+- System.out.println("Shutting down:" + channels[i].getLocalMember(true).toString());
+- channels[i].stop(Channel.DEFAULT);
+- index = i;
+- }
+- }
+- int dead = index;
+- Thread.sleep(1000);
+- if ( index == 0 ) index = 1; else index = 0;
+- System.out.println("Member count:"+channels[index].getMembers().length);
+- member = coordinators[index].getCoordinator();
+- for (int i = 1; i < channelCount; i++) if ( i != dead ) super.assertEquals(member, coordinators[i].getCoordinator());
+- System.out.println("Coordinator[2b] is:" + member);
+- }
+-
+- protected void tearDown() throws Exception {
+- System.out.println("tearDown");
+- super.tearDown();
+- for ( int i=0; i<channelCount; i++ ) {
+- channels[i].stop(Channel.DEFAULT);
+- }
+- }
+-
+- public static void main(String[] args) throws Exception {
+- TestSuite suite = new TestSuite();
+- suite.addTestSuite(TestNonBlockingCoordinator.class);
+- suite.run(new TestResult());
+- }
+-
+-
+-
+-
+-
+-}
+Index: test/org/apache/catalina/tribes/test/interceptors/TestOrderInterceptor.java
+===================================================================
+--- test/org/apache/catalina/tribes/test/interceptors/TestOrderInterceptor.java (revision 590752)
++++ test/org/apache/catalina/tribes/test/interceptors/TestOrderInterceptor.java (working copy)
+@@ -1,186 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.test.interceptors;
+-
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.group.GroupChannel;
+-import org.apache.catalina.tribes.group.interceptors.NonBlockingCoordinator;
+-import org.apache.catalina.tribes.group.interceptors.TcpFailureDetector;
+-import junit.framework.TestCase;
+-import junit.framework.TestResult;
+-import junit.framework.TestSuite;
+-import org.apache.catalina.tribes.ChannelListener;
+-import java.io.Serializable;
+-import org.apache.catalina.tribes.group.interceptors.OrderInterceptor;
+-import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.group.InterceptorPayload;
+-import org.apache.catalina.tribes.ChannelException;
+-import java.util.concurrent.atomic.AtomicInteger;
+-
+-public class TestOrderInterceptor extends TestCase {
+-
+- GroupChannel[] channels = null;
+- OrderInterceptor[] orderitcs = null;
+- MangleOrderInterceptor[] mangleitcs = null;
+- TestListener[] test = null;
+- int channelCount = 2;
+- Thread[] threads = null;
+- protected void setUp() throws Exception {
+- System.out.println("Setup");
+- super.setUp();
+- channels = new GroupChannel[channelCount];
+- orderitcs = new OrderInterceptor[channelCount];
+- mangleitcs = new MangleOrderInterceptor[channelCount];
+- test = new TestListener[channelCount];
+- threads = new Thread[channelCount];
+- for ( int i=0; i<channelCount; i++ ) {
+- channels[i] = new GroupChannel();
+-
+- orderitcs[i] = new OrderInterceptor();
+- mangleitcs[i] = new MangleOrderInterceptor();
+- orderitcs[i].setExpire(Long.MAX_VALUE);
+- channels[i].addInterceptor(orderitcs[i]);
+- channels[i].addInterceptor(mangleitcs[i]);
+- test[i] = new TestListener(i);
+- channels[i].addChannelListener(test[i]);
+- final int j = i;
+- threads[i] = new Thread() {
+- public void run() {
+- try {
+- channels[j].start(Channel.DEFAULT);
+- Thread.sleep(50);
+- } catch (Exception x) {
+- x.printStackTrace();
+- }
+- }
+- };
+- }
+- for ( int i=0; i<channelCount; i++ ) threads[i].start();
+- for ( int i=0; i<channelCount; i++ ) threads[i].join();
+- Thread.sleep(1000);
+- }
+-
+- public void testOrder1() throws Exception {
+- Member[] dest = channels[0].getMembers();
+- final AtomicInteger value = new AtomicInteger(0);
+- for ( int i=0; i<100; i++ ) {
+- channels[0].send(dest,new Integer(value.getAndAdd(1)),0);
+- }
+- Thread.sleep(5000);
+- for ( int i=0; i<test.length; i++ ) {
+- super.assertEquals(false,test[i].fail);
+- }
+- }
+-
+- public void testOrder2() throws Exception {
+- final Member[] dest = channels[0].getMembers();
+- final AtomicInteger value = new AtomicInteger(0);
+- Runnable run = new Runnable() {
+- public void run() {
+- for (int i = 0; i < 100; i++) {
+- try {
+- synchronized (channels[0]) {
+- channels[0].send(dest, new Integer(value.getAndAdd(1)), 0);
+- }
+- }catch ( Exception x ) {
+- x.printStackTrace();
+- assertEquals(true,false);
+- }
+- }
+- }
+- };
+- Thread[] threads = new Thread[5];
+- for (int i=0;i<threads.length;i++) {
+- threads[i] = new Thread(run);
+- }
+- for (int i=0;i<threads.length;i++) {
+- threads[i].start();
+- }
+- for (int i=0;i<threads.length;i++) {
+- threads[i].join();
+- }
+- Thread.sleep(5000);
+- for ( int i=0; i<test.length; i++ ) {
+- super.assertEquals(false,test[i].fail);
+- }
+- }
+-
+-
+- protected void tearDown() throws Exception {
+- System.out.println("tearDown");
+- super.tearDown();
+- for ( int i=0; i<channelCount; i++ ) {
+- channels[i].stop(Channel.DEFAULT);
+- }
+- }
+-
+- public static void main(String[] args) throws Exception {
+- TestSuite suite = new TestSuite();
+- suite.addTestSuite(TestOrderInterceptor.class);
+- suite.run(new TestResult());
+- }
+-
+- public static class TestListener implements ChannelListener {
+- int id = -1;
+- public TestListener(int id) {
+- this.id = id;
+- }
+- int cnt = 0;
+- int total = 0;
+- boolean fail = false;
+- public synchronized void messageReceived(Serializable msg, Member sender) {
+- total++;
+- Integer i = (Integer)msg;
+- if ( i.intValue() != cnt ) fail = true;
+- else cnt++;
+- System.out.println("Listener["+id+"] Message received:"+i+" Count:"+total+" Fail:"+fail);
+-
+- }
+-
+- public boolean accept(Serializable msg, Member sender) {
+- return (msg instanceof Integer);
+- }
+- }
+-
+- public static class MangleOrderInterceptor extends ChannelInterceptorBase {
+- int cnt = 1;
+- ChannelMessage hold = null;
+- Member[] dest = null;
+- public synchronized void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+- if ( hold == null ) {
+- //System.out.println("Skipping message:"+msg);
+- hold = (ChannelMessage)msg.deepclone();
+- dest = new Member[destination.length];
+- System.arraycopy(destination,0,dest,0,dest.length);
+- } else {
+- //System.out.println("Sending message:"+msg);
+- super.sendMessage(destination,msg,payload);
+- //System.out.println("Sending message:"+hold);
+- super.sendMessage(dest,hold,null);
+- hold = null;
+- dest = null;
+- }
+- }
+- }
+-
+-
+-
+-
+-
+-}
+Index: test/org/apache/catalina/tribes/test/TribesTestSuite.java
+===================================================================
+--- test/org/apache/catalina/tribes/test/TribesTestSuite.java (revision 590752)
++++ test/org/apache/catalina/tribes/test/TribesTestSuite.java (working copy)
+@@ -1,41 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.test;
+-
+-import junit.framework.Test;
+-import junit.framework.TestCase;
+-import junit.framework.TestSuite;
+-
+-public class TribesTestSuite
+- extends TestCase {
+-
+- public TribesTestSuite(String s) {
+- super(s);
+- }
+-
+- public static Test suite() {
+- TestSuite suite = new TestSuite();
+- suite.addTestSuite(org.apache.catalina.tribes.test.channel.ChannelStartStop.class);
+- suite.addTestSuite(org.apache.catalina.tribes.test.channel.TestChannelOptionFlag.class);
+- suite.addTestSuite(org.apache.catalina.tribes.test.membership.MemberSerialization.class);
+- suite.addTestSuite(org.apache.catalina.tribes.test.membership.TestMemberArrival.class);
+- suite.addTestSuite(org.apache.catalina.tribes.test.membership.TestTcpFailureDetector.class);
+- suite.addTestSuite(org.apache.catalina.tribes.test.channel.TestDataIntegrity.class);
+- suite.addTestSuite(org.apache.catalina.tribes.test.interceptors.TestOrderInterceptor.class);
+- return suite;
+- }
+-}
+Index: test/org/apache/catalina/tribes/test/TestNioSender.java
+===================================================================
+--- test/org/apache/catalina/tribes/test/TestNioSender.java (revision 590752)
++++ test/org/apache/catalina/tribes/test/TestNioSender.java (working copy)
+@@ -1,120 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.test;
+-
+-import java.io.IOException;
+-import java.nio.channels.SelectionKey;
+-import java.util.Iterator;
+-import java.nio.channels.Selector;
+-import org.apache.catalina.tribes.transport.nio.NioSender;
+-import org.apache.catalina.tribes.membership.MemberImpl;
+-import org.apache.catalina.tribes.io.ChannelData;
+-import org.apache.catalina.tribes.io.XByteBuffer;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.Channel;
+-
+-/**
+- * <p>Title: </p>
+- *
+- * <p>Description: </p>
+- *
+- * <p>Company: </p>
+- *
+- * @author not attributable
+- * @version 1.0
+- */
+-public class TestNioSender {
+- private Selector selector = null;
+- private int counter = 0;
+- MemberImpl mbr;
+- private static int testOptions = Channel.SEND_OPTIONS_DEFAULT;
+- public TestNioSender() {
+-
+- }
+-
+- public synchronized int inc() {
+- return ++counter;
+- }
+-
+- public synchronized ChannelData getMessage(Member mbr) {
+- String msg = new String("Thread-"+Thread.currentThread().getName()+" Message:"+inc());
+- ChannelData data = new ChannelData(true);
+- data.setMessage(new XByteBuffer(msg.getBytes(),false));
+- data.setAddress(mbr);
+-
+- return data;
+- }
+-
+- public void init() throws Exception {
+- selector = Selector.open();
+- mbr = new MemberImpl("localhost",4444,0);
+- NioSender sender = new NioSender();
+- sender.setDestination(mbr);
+- sender.setDirectBuffer(true);
+- sender.setSelector(selector);
+- sender.setMessage(XByteBuffer.createDataPackage(getMessage(mbr)));
+- sender.connect();
+- }
+-
+- public void run() {
+- while (true) {
+-
+- int selectedKeys = 0;
+- try {
+- selectedKeys = selector.select(100);
+- // if ( selectedKeys == 0 ) {
+- // System.out.println("No registered interests. Sleeping for a second.");
+- // Thread.sleep(100);
+- } catch (Exception e) {
+- e.printStackTrace();
+- continue;
+- }
+-
+- if (selectedKeys == 0) {
+- continue;
+- }
+-
+- Iterator it = selector.selectedKeys().iterator();
+- while (it.hasNext()) {
+- SelectionKey sk = (SelectionKey) it.next();
+- it.remove();
+- try {
+- int readyOps = sk.readyOps();
+- sk.interestOps(sk.interestOps() & ~readyOps);
+- NioSender sender = (NioSender) sk.attachment();
+- if ( sender.process(sk, (testOptions&Channel.SEND_OPTIONS_USE_ACK)==Channel.SEND_OPTIONS_USE_ACK) ) {
+- System.out.println("Message completed for handler:"+sender);
+- Thread.currentThread().sleep(2000);
+- sender.reset();
+- sender.setMessage(XByteBuffer.createDataPackage(getMessage(mbr)));
+- }
+-
+-
+- } catch (Throwable t) {
+- t.printStackTrace();
+- return;
+- }
+- }
+- }
+- }
+-
+- public static void main(String[] args) throws Exception {
+- TestNioSender sender = new TestNioSender();
+- sender.init();
+- sender.run();
+- }
+-}
+Index: test/org/apache/catalina/tribes/test/io/TestSenderConnections.java
+===================================================================
+--- test/org/apache/catalina/tribes/test/io/TestSenderConnections.java (revision 590752)
++++ test/org/apache/catalina/tribes/test/io/TestSenderConnections.java (working copy)
+@@ -1,128 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.test.io;
+-
+-import java.util.ArrayList;
+-
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.ManagedChannel;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.MembershipListener;
+-import org.apache.catalina.tribes.group.GroupChannel;
+-import junit.framework.TestCase;
+-import org.apache.catalina.tribes.ChannelListener;
+-import java.io.Serializable;
+-import java.util.Random;
+-import java.util.HashMap;
+-import org.apache.catalina.tribes.transport.ReplicationTransmitter;
+-
+-public class TestSenderConnections extends TestCase {
+- private static int count = 2;
+- private ManagedChannel[] channels = new ManagedChannel[count];
+- private TestMsgListener[] listeners = new TestMsgListener[count];
+-
+- protected void setUp() throws Exception {
+- super.setUp();
+- for (int i = 0; i < channels.length; i++) {
+- channels[i] = new GroupChannel();
+- channels[i].getMembershipService().setPayload( ("Channel-" + (i + 1)).getBytes("ASCII"));
+- listeners[i] = new TestMsgListener( ("Listener-" + (i + 1)));
+- channels[i].addChannelListener(listeners[i]);
+- channels[i].start(Channel.SND_RX_SEQ|Channel.SND_TX_SEQ);
+-
+- }
+- }
+-
+- public void clear() {
+- }
+-
+- public void sendMessages(long delay, long sleep) throws Exception {
+- Member local = channels[0].getLocalMember(true);
+- Member dest = channels[1].getLocalMember(true);
+- int n = 3;
+- System.out.println("Sending " + n + " messages from [" + local.getName() + "] to [" + dest.getName() + "]");
+- for (int i = 0; i < n; i++) {
+- channels[0].send(new Member[] {dest}, new TestMsg(), 0);
+- if ( delay > 0 ) Thread.sleep(delay);
+- }
+- System.out.println("Messages sent. Sleeping for "+(sleep/1000)+" seconds to inspect connections");
+- if ( sleep > 0 ) Thread.sleep(sleep);
+-
+- }
+-
+- public void testConnectionLinger() throws Exception {
+- sendMessages(0,15000);
+- }
+-
+- public void testKeepAliveCount() throws Exception {
+- System.out.println("Setting keep alive count to 0");
+- for (int i = 0; i < channels.length; i++) {
+- ReplicationTransmitter t = (ReplicationTransmitter)channels[0].getChannelSender();
+- t.getTransport().setKeepAliveCount(0);
+- }
+- sendMessages(1000,15000);
+- }
+-
+- public void testKeepAliveTime() throws Exception {
+- System.out.println("Setting keep alive count to 1 second");
+- for (int i = 0; i < channels.length; i++) {
+- ReplicationTransmitter t = (ReplicationTransmitter)channels[0].getChannelSender();
+- t.getTransport().setKeepAliveTime(1000);
+- }
+- sendMessages(2000,15000);
+- }
+-
+- protected void tearDown() throws Exception {
+- for (int i = 0; i < channels.length; i++) {
+- channels[i].stop(Channel.DEFAULT);
+- }
+-
+- }
+-
+- public static class TestMsg implements Serializable {
+- static Random r = new Random(System.currentTimeMillis());
+- HashMap map = new HashMap();
+- public TestMsg() {
+- int size = Math.abs(r.nextInt() % 200);
+- for (int i=0; i<size; i++ ) {
+- int length = Math.abs(r.nextInt() %65000);
+- ArrayList list = new ArrayList(length);
+- map.put(new Integer(i),list);
+- }
+- }
+- }
+-
+- public class TestMsgListener implements ChannelListener {
+- public String name = null;
+- public TestMsgListener(String name) {
+- this.name = name;
+- }
+-
+- public void messageReceived(Serializable msg, Member sender) {
+- System.out.println("["+name+"] Received message:"+msg+" from " + sender.getName());
+- }
+-
+-
+- public boolean accept(Serializable msg, Member sender) {
+- return true;
+- }
+-
+-
+-
+- }
+-
+-}
+Index: test/org/apache/catalina/tribes/test/io/TestSerialization.java
+===================================================================
+--- test/org/apache/catalina/tribes/test/io/TestSerialization.java (revision 590752)
++++ test/org/apache/catalina/tribes/test/io/TestSerialization.java (working copy)
+@@ -1,40 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.test.io;
+-
+-import org.apache.catalina.tribes.io.XByteBuffer;
+-import junit.framework.TestCase;
+-
+-public class TestSerialization extends TestCase {
+- protected void setUp() throws Exception {
+- super.setUp();
+- }
+-
+- public void testEmptyArray() throws Exception {
+-
+- }
+-
+- protected void tearDown() throws Exception {
+- super.tearDown();
+- }
+-
+- public static void main(String[] args) throws Exception {
+- //XByteBuffer.deserialize(new byte[0]);
+- XByteBuffer.deserialize(new byte[] {-84, -19, 0, 5, 115, 114, 0, 17, 106});
+- }
+-
+-}
+Index: test/org/apache/catalina/tribes/test/channel/TestDataIntegrity.java
+===================================================================
+--- test/org/apache/catalina/tribes/test/channel/TestDataIntegrity.java (revision 590752)
++++ test/org/apache/catalina/tribes/test/channel/TestDataIntegrity.java (working copy)
+@@ -1,191 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.test.channel;
+-
+-import junit.framework.TestCase;
+-import java.io.Serializable;
+-import java.util.Random;
+-import java.util.Arrays;
+-import org.apache.catalina.tribes.ChannelListener;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.group.GroupChannel;
+-import org.apache.catalina.tribes.test.channel.TestDataIntegrity.Listener;
+-import org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor;
+-import org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor;
+-
+-/**
+- * <p>Title: </p>
+- *
+- * <p>Description: </p>
+- *
+- * <p>Company: </p>
+- *
+- * @author not attributable
+- * @version 1.0
+- */
+-public class TestDataIntegrity extends TestCase {
+- int msgCount = 500;
+- int threadCount = 20;
+- GroupChannel channel1;
+- GroupChannel channel2;
+- Listener listener1;
+- int threadCounter = 0;
+- protected void setUp() throws Exception {
+- super.setUp();
+- channel1 = new GroupChannel();
+- channel1.addInterceptor(new MessageDispatch15Interceptor());
+- channel2 = new GroupChannel();
+- channel2.addInterceptor(new MessageDispatch15Interceptor());
+- listener1 = new Listener();
+- channel2.addChannelListener(listener1);
+- channel1.start(GroupChannel.DEFAULT);
+- channel2.start(GroupChannel.DEFAULT);
+- }
+-
+- protected void tearDown() throws Exception {
+- super.tearDown();
+- channel1.stop(GroupChannel.DEFAULT);
+- channel2.stop(GroupChannel.DEFAULT);
+- }
+-
+- public void testDataSendNO_ACK() throws Exception {
+- System.err.println("Starting NO_ACK");
+- Thread[] threads = new Thread[threadCount];
+- for (int x=0; x<threads.length; x++ ) {
+- threads[x] = new Thread() {
+- public void run() {
+- try {
+- long start = System.currentTimeMillis();
+- for (int i = 0; i < msgCount; i++) channel1.send(new Member[] {channel2.getLocalMember(false)}, Data.createRandomData(),0);
+- System.out.println("Thread["+this.getName()+"] sent "+msgCount+" messages in "+(System.currentTimeMillis()-start)+" ms.");
+- }catch ( Exception x ) {
+- x.printStackTrace();
+- return;
+- } finally {
+- threadCounter++;
+- }
+- }
+- };
+- }
+- for (int x=0; x<threads.length; x++ ) { threads[x].start();}
+- for (int x=0; x<threads.length; x++ ) { threads[x].join();}
+- //sleep for 50 sec, let the other messages in
+- long start = System.currentTimeMillis();
+- while ( (System.currentTimeMillis()-start)<15000 && msgCount*threadCount!=listener1.count) Thread.sleep(500);
+- System.err.println("Finished NO_ACK ["+listener1.count+"]");
+- assertEquals("Checking success messages.",msgCount*threadCount,listener1.count);
+- }
+-
+- public void testDataSendASYNCM() throws Exception {
+- System.err.println("Starting ASYNC MULTI THREAD");
+- Thread[] threads = new Thread[threadCount];
+- for (int x=0; x<threads.length; x++ ) {
+- threads[x] = new Thread() {
+- public void run() {
+- try {
+- long start = System.currentTimeMillis();
+- for (int i = 0; i < msgCount; i++) channel1.send(new Member[] {channel2.getLocalMember(false)}, Data.createRandomData(),GroupChannel.SEND_OPTIONS_ASYNCHRONOUS);
+- System.out.println("Thread["+this.getName()+"] sent "+msgCount+" messages in "+(System.currentTimeMillis()-start)+" ms.");
+- }catch ( Exception x ) {
+- x.printStackTrace();
+- return;
+- } finally {
+- threadCounter++;
+- }
+- }
+- };
+- }
+- for (int x=0; x<threads.length; x++ ) { threads[x].start();}
+- for (int x=0; x<threads.length; x++ ) { threads[x].join();}
+- //sleep for 50 sec, let the other messages in
+- long start = System.currentTimeMillis();
+- while ( (System.currentTimeMillis()-start)<15000 && msgCount*threadCount!=listener1.count) Thread.sleep(500);
+- System.err.println("Finished ASYNC MULTI THREAD ["+listener1.count+"]");
+- assertEquals("Checking success messages.",msgCount*threadCount,listener1.count);
+- }
+- public void testDataSendASYNC() throws Exception {
+- System.err.println("Starting ASYNC");
+- for (int i=0; i<msgCount; i++) channel1.send(new Member[] {channel2.getLocalMember(false)},Data.createRandomData(),GroupChannel.SEND_OPTIONS_ASYNCHRONOUS);
+- //sleep for 50 sec, let the other messages in
+- long start = System.currentTimeMillis();
+- while ( (System.currentTimeMillis()-start)<5000 && msgCount!=listener1.count) Thread.sleep(500);
+- System.err.println("Finished ASYNC");
+- assertEquals("Checking success messages.",msgCount,listener1.count);
+- }
+-
+- public void testDataSendACK() throws Exception {
+- System.err.println("Starting ACK");
+- for (int i=0; i<msgCount; i++) channel1.send(new Member[] {channel2.getLocalMember(false)},Data.createRandomData(),GroupChannel.SEND_OPTIONS_USE_ACK);
+- Thread.sleep(250);
+- System.err.println("Finished ACK");
+- assertEquals("Checking success messages.",msgCount,listener1.count);
+- }
+-
+- public void testDataSendSYNCACK() throws Exception {
+- System.err.println("Starting SYNC_ACK");
+- for (int i=0; i<msgCount; i++) channel1.send(new Member[] {channel2.getLocalMember(false)},Data.createRandomData(),GroupChannel.SEND_OPTIONS_SYNCHRONIZED_ACK|GroupChannel.SEND_OPTIONS_USE_ACK);
+- Thread.sleep(250);
+- System.err.println("Finished SYNC_ACK");
+- assertEquals("Checking success messages.",msgCount,listener1.count);
+- }
+-
+- public static class Listener implements ChannelListener {
+- long count = 0;
+- public boolean accept(Serializable s, Member m) {
+- return (s instanceof Data);
+- }
+-
+- public void messageReceived(Serializable s, Member m) {
+- Data d = (Data)s;
+- if ( !Data.verify(d) ) {
+- System.err.println("ERROR");
+- } else {
+- count++;
+- if ((count %1000) ==0 ) {
+- System.err.println("SUCCESS:"+count);
+- }
+- }
+- }
+- }
+-
+- public static class Data implements Serializable {
+- public int length;
+- public byte[] data;
+- public byte key;
+- public static Random r = new Random(System.currentTimeMillis());
+- public static Data createRandomData() {
+- int i = r.nextInt();
+- i = ( i % 127 );
+- int length = Math.abs(r.nextInt() % 65555);
+- Data d = new Data();
+- d.length = length;
+- d.key = (byte)i;
+- d.data = new byte[length];
+- Arrays.fill(d.data,d.key);
+- return d;
+- }
+-
+- public static boolean verify(Data d) {
+- boolean result = (d.length == d.data.length);
+- for ( int i=0; result && (i<d.data.length); i++ ) result = result && d.data[i] == d.key;
+- return result;
+- }
+- }
+-
+-
+-
+-}
+Index: test/org/apache/catalina/tribes/test/channel/TestRemoteProcessException.java
+===================================================================
+--- test/org/apache/catalina/tribes/test/channel/TestRemoteProcessException.java (revision 590752)
++++ test/org/apache/catalina/tribes/test/channel/TestRemoteProcessException.java (working copy)
+@@ -1,137 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.test.channel;
+-
+-import junit.framework.TestCase;
+-import java.io.Serializable;
+-import java.util.Random;
+-import java.util.Arrays;
+-import org.apache.catalina.tribes.ChannelListener;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.group.GroupChannel;
+-import java.io.PrintStream;
+-
+-/**
+- * <p>Title: </p>
+- *
+- * <p>Description: </p>
+- *
+- * <p>Company: </p>
+- *
+- * @author not attributable
+- * @version 1.0
+- */
+-public class TestRemoteProcessException extends TestCase {
+- int msgCount = 10000;
+- GroupChannel channel1;
+- GroupChannel channel2;
+- Listener listener1;
+- protected void setUp() throws Exception {
+- super.setUp();
+- channel1 = new GroupChannel();
+- channel2 = new GroupChannel();
+- listener1 = new Listener();
+- channel2.addChannelListener(listener1);
+- channel1.start(GroupChannel.DEFAULT);
+- channel2.start(GroupChannel.DEFAULT);
+- }
+-
+- protected void tearDown() throws Exception {
+- super.tearDown();
+- channel1.stop(GroupChannel.DEFAULT);
+- channel2.stop(GroupChannel.DEFAULT);
+- }
+-
+- public void testDataSendSYNCACK() throws Exception {
+- System.err.println("Starting SYNC_ACK");
+- int errC=0, nerrC=0;
+- for (int i=0; i<msgCount; i++) {
+- boolean error = Data.r.nextBoolean();
+- channel1.send(channel1.getMembers(),Data.createRandomData(error),GroupChannel.SEND_OPTIONS_SYNCHRONIZED_ACK|GroupChannel.SEND_OPTIONS_USE_ACK);
+- if ( error ) errC++; else nerrC++;
+- }
+- System.err.println("Finished SYNC_ACK");
+- assertEquals("Checking failure messages.",errC,listener1.errCnt);
+- assertEquals("Checking success messages.",nerrC,listener1.noErrCnt);
+- assertEquals("Checking all messages.",msgCount,listener1.noErrCnt+listener1.errCnt);
+- System.out.println("Listener 1 stats:");
+- listener1.printStats(System.out);
+- }
+-
+- public static class Listener implements ChannelListener {
+- long noErrCnt = 0;
+- long errCnt = 0;
+- public boolean accept(Serializable s, Member m) {
+- return (s instanceof Data);
+- }
+-
+- public void messageReceived(Serializable s, Member m) {
+- Data d = (Data)s;
+- if ( !Data.verify(d) ) {
+- System.err.println("ERROR");
+- } else {
+- if (d.error) {
+- errCnt++;
+- if ( (errCnt % 100) == 0) {
+- printStats(System.err);
+- }
+- throw new IllegalArgumentException();
+- } else {
+- noErrCnt++;
+- if ( (noErrCnt % 100) == 0) {
+- printStats(System.err);
+- }
+- }
+- }
+- }
+-
+- public void printStats(PrintStream stream) {
+- stream.println("NORMAL:" + noErrCnt);
+- stream.println("FAILURES:" + errCnt);
+- stream.println("TOTAL:" + (errCnt+noErrCnt));
+- }
+- }
+-
+- public static class Data implements Serializable {
+- public int length;
+- public byte[] data;
+- public byte key;
+- public boolean error = false;
+- public static Random r = new Random(System.currentTimeMillis());
+- public static Data createRandomData(boolean error) {
+- int i = r.nextInt();
+- i = ( i % 127 );
+- int length = Math.abs(r.nextInt() % 65555);
+- Data d = new Data();
+- d.length = length;
+- d.key = (byte)i;
+- d.data = new byte[length];
+- Arrays.fill(d.data,d.key);
+- d.error = error;
+- return d;
+- }
+-
+- public static boolean verify(Data d) {
+- boolean result = (d.length == d.data.length);
+- for ( int i=0; result && (i<d.data.length); i++ ) result = result && d.data[i] == d.key;
+- return result;
+- }
+- }
+-
+-
+-
+-}
+Index: test/org/apache/catalina/tribes/test/channel/TestChannelOptionFlag.java
+===================================================================
+--- test/org/apache/catalina/tribes/test/channel/TestChannelOptionFlag.java (revision 590752)
++++ test/org/apache/catalina/tribes/test/channel/TestChannelOptionFlag.java (working copy)
+@@ -1,90 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.test.channel;
+-
+-import junit.framework.*;
+-import org.apache.catalina.tribes.group.*;
+-import org.apache.catalina.tribes.ChannelInterceptor;
+-import org.apache.catalina.tribes.ChannelException;
+-
+-/**
+- * <p>Title: </p>
+- *
+- * <p>Description: </p>
+- *
+- * <p>Company: </p>
+- *
+- * @author not attributable
+- * @version 1.0
+- */
+-public class TestChannelOptionFlag extends TestCase {
+- GroupChannel channel = null;
+- protected void setUp() throws Exception {
+- super.setUp();
+- channel = new GroupChannel();
+- }
+-
+- protected void tearDown() throws Exception {
+- super.tearDown();
+- if ( channel != null ) try {channel.stop(channel.DEFAULT);}catch ( Exception ignore) {}
+- channel = null;
+- }
+-
+-
+- public void testOptionConflict() throws Exception {
+- boolean error = false;
+- channel.setOptionCheck(true);
+- ChannelInterceptor i = new TestInterceptor();
+- i.setOptionFlag(128);
+- channel.addInterceptor(i);
+- i = new TestInterceptor();
+- i.setOptionFlag(128);
+- channel.addInterceptor(i);
+- try {
+- channel.start(channel.DEFAULT);
+- }catch ( ChannelException x ) {
+- if ( x.getMessage().indexOf("option flag conflict") >= 0 ) error = true;
+- }
+- assertEquals(true,error);
+- }
+-
+- public void testOptionNoConflict() throws Exception {
+- boolean error = false;
+- channel.setOptionCheck(true);
+- ChannelInterceptor i = new TestInterceptor();
+- i.setOptionFlag(128);
+- channel.addInterceptor(i);
+- i = new TestInterceptor();
+- i.setOptionFlag(64);
+- channel.addInterceptor(i);
+- i = new TestInterceptor();
+- i.setOptionFlag(256);
+- channel.addInterceptor(i);
+- try {
+- channel.start(channel.DEFAULT);
+- }catch ( ChannelException x ) {
+- if ( x.getMessage().indexOf("option flag conflict") >= 0 ) error = true;
+- }
+- assertEquals(false,error);
+- }
+-
+- public static class TestInterceptor extends ChannelInterceptorBase {
+-
+- }
+-
+-
+-}
+Index: test/org/apache/catalina/tribes/test/channel/ChannelStartStop.java
+===================================================================
+--- test/org/apache/catalina/tribes/test/channel/ChannelStartStop.java (revision 590752)
++++ test/org/apache/catalina/tribes/test/channel/ChannelStartStop.java (working copy)
+@@ -1,126 +0,0 @@
+-/*
+- * 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
+- */
+-package org.apache.catalina.tribes.test.channel;
+-
+-import org.apache.catalina.tribes.group.GroupChannel;
+-import junit.framework.TestCase;
+-import org.apache.catalina.tribes.transport.ReceiverBase;
+-
+-/**
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public class ChannelStartStop extends TestCase {
+- GroupChannel channel = null;
+- protected void setUp() throws Exception {
+- super.setUp();
+- channel = new GroupChannel();
+- }
+-
+- protected void tearDown() throws Exception {
+- super.tearDown();
+- try {channel.stop(channel.DEFAULT);}catch (Exception ignore){}
+- }
+-
+- public void testDoubleFullStart() throws Exception {
+- int count = 0;
+- try {
+- channel.start(channel.DEFAULT);
+- count++;
+- } catch ( Exception x){x.printStackTrace();}
+- try {
+- channel.start(channel.DEFAULT);
+- count++;
+- } catch ( Exception x){x.printStackTrace();}
+- assertEquals(count,2);
+- channel.stop(channel.DEFAULT);
+- }
+-
+- public void testScrap() throws Exception {
+- System.out.println(channel.getChannelReceiver().getClass());
+- ((ReceiverBase)channel.getChannelReceiver()).setMaxThreads(1);
+- }
+-
+-
+- public void testDoublePartialStart() throws Exception {
+- //try to double start the RX
+- int count = 0;
+- try {
+- channel.start(channel.SND_RX_SEQ);
+- channel.start(channel.MBR_RX_SEQ);
+- count++;
+- } catch ( Exception x){x.printStackTrace();}
+- try {
+- channel.start(channel.MBR_RX_SEQ);
+- count++;
+- } catch ( Exception x){/*expected*/}
+- assertEquals(count,1);
+- channel.stop(channel.DEFAULT);
+- //double the membership sender
+- count = 0;
+- try {
+- channel.start(channel.SND_RX_SEQ);
+- channel.start(channel.MBR_TX_SEQ);
+- count++;
+- } catch ( Exception x){x.printStackTrace();}
+- try {
+- channel.start(channel.MBR_TX_SEQ);
+- count++;
+- } catch ( Exception x){/*expected*/}
+- assertEquals(count,1);
+- channel.stop(channel.DEFAULT);
+-
+- count = 0;
+- try {
+- channel.start(channel.SND_RX_SEQ);
+- count++;
+- } catch ( Exception x){x.printStackTrace();}
+- try {
+- channel.start(channel.SND_RX_SEQ);
+- count++;
+- } catch ( Exception x){/*expected*/}
+- assertEquals(count,1);
+- channel.stop(channel.DEFAULT);
+-
+- count = 0;
+- try {
+- channel.start(channel.SND_TX_SEQ);
+- count++;
+- } catch ( Exception x){x.printStackTrace();}
+- try {
+- channel.start(channel.SND_TX_SEQ);
+- count++;
+- } catch ( Exception x){/*expected*/}
+- assertEquals(count,1);
+- channel.stop(channel.DEFAULT);
+- }
+-
+- public void testFalseOption() throws Exception {
+- int flag = 0xFFF0;//should get ignored by the underlying components
+- int count = 0;
+- try {
+- channel.start(flag);
+- count++;
+- } catch ( Exception x){x.printStackTrace();}
+- try {
+- channel.start(flag);
+- count++;
+- } catch ( Exception x){/*expected*/}
+- assertEquals(count,2);
+- channel.stop(channel.DEFAULT);
+- }
+-
+-}
+Index: test/org/apache/catalina/tribes/test/membership/MemberSerialization.java
+===================================================================
+--- test/org/apache/catalina/tribes/test/membership/MemberSerialization.java (revision 590752)
++++ test/org/apache/catalina/tribes/test/membership/MemberSerialization.java (working copy)
+@@ -1,99 +0,0 @@
+-/*
+- * 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
+- */
+-package org.apache.catalina.tribes.test.membership;
+-
+-import junit.framework.TestCase;
+-import org.apache.catalina.tribes.membership.MemberImpl;
+-import java.util.Arrays;
+-
+-/**
+- * <p>Title: </p>
+- *
+- * <p>Description: </p>
+- *
+- * <p>Company: </p>
+- *
+- * @author not attributable
+- * @version 1.0
+- */
+-public class MemberSerialization extends TestCase {
+- MemberImpl m1, m2, p1,p2;
+- byte[] payload = null;
+- protected void setUp() throws Exception {
+- super.setUp();
+- payload = new byte[333];
+- Arrays.fill(payload,(byte)1);
+- m1 = new MemberImpl("localhost",3333,1,payload);
+- m2 = new MemberImpl("localhost",3333,1);
+- payload = new byte[333];
+- Arrays.fill(payload,(byte)2);
+- p1 = new MemberImpl("127.0.0.1",3333,1,payload);
+- p2 = new MemberImpl("localhost",3331,1,payload);
+- m1.setDomain(new byte[] {1,2,3,4,5,6,7,8,9});
+- m2.setDomain(new byte[] {1,2,3,4,5,6,7,8,9});
+- m1.setCommand(new byte[] {1,2,4,5,6,7,8,9});
+- m2.setCommand(new byte[] {1,2,4,5,6,7,8,9});
+- }
+-
+- public void testCompare() throws Exception {
+- assertTrue(m1.equals(m2));
+- assertTrue(m2.equals(m1));
+- assertTrue(p1.equals(m2));
+- assertFalse(m1.equals(p2));
+- assertFalse(m1.equals(p2));
+- assertFalse(m2.equals(p2));
+- assertFalse(p1.equals(p2));
+- }
+-
+- public void testSerializationOne() throws Exception {
+- MemberImpl m = m1;
+- byte[] md1 = m.getData(false,true);
+- byte[] mda1 = m.getData(false,false);
+- assertTrue(Arrays.equals(md1,mda1));
+- assertTrue(md1==mda1);
+- mda1 = m.getData(true,true);
+- MemberImpl ma1 = MemberImpl.getMember(mda1);
+- assertTrue(compareMembers(m,ma1));
+- mda1 = p1.getData(false);
+- assertFalse(Arrays.equals(md1,mda1));
+- ma1 = MemberImpl.getMember(mda1);
+- assertTrue(compareMembers(p1,ma1));
+-
+- md1 = m.getData(true,true);
+- Thread.sleep(50);
+- mda1 = m.getData(true,true);
+- MemberImpl a1 = MemberImpl.getMember(md1);
+- MemberImpl a2 = MemberImpl.getMember(mda1);
+- assertTrue(a1.equals(a2));
+- assertFalse(Arrays.equals(md1,mda1));
+-
+-
+- }
+-
+- public boolean compareMembers(MemberImpl impl1, MemberImpl impl2) {
+- boolean result = true;
+- result = result && Arrays.equals(impl1.getHost(),impl2.getHost());
+- result = result && Arrays.equals(impl1.getPayload(),impl2.getPayload());
+- result = result && Arrays.equals(impl1.getUniqueId(),impl2.getUniqueId());
+- result = result && impl1.getPort() == impl2.getPort();
+- return result;
+- }
+-
+- protected void tearDown() throws Exception {
+- super.tearDown();
+- }
+-
+-}
+Index: test/org/apache/catalina/tribes/test/membership/TestMemberArrival.java
+===================================================================
+--- test/org/apache/catalina/tribes/test/membership/TestMemberArrival.java (revision 590752)
++++ test/org/apache/catalina/tribes/test/membership/TestMemberArrival.java (working copy)
+@@ -1,117 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.test.membership;
+-
+-import java.util.ArrayList;
+-
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.ManagedChannel;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.MembershipListener;
+-import org.apache.catalina.tribes.group.GroupChannel;
+-import junit.framework.TestCase;
+-
+-public class TestMemberArrival
+- extends TestCase {
+- private static int count = 10;
+- private ManagedChannel[] channels = new ManagedChannel[count];
+- private TestMbrListener[] listeners = new TestMbrListener[count];
+-
+- protected void setUp() throws Exception {
+- super.setUp();
+- for (int i = 0; i < channels.length; i++) {
+- channels[i] = new GroupChannel();
+- channels[i].getMembershipService().setPayload( ("Channel-" + (i + 1)).getBytes("ASCII"));
+- listeners[i] = new TestMbrListener( ("Listener-" + (i + 1)));
+- channels[i].addMembershipListener(listeners[i]);
+-
+- }
+- }
+-
+- public void clear() {
+- for (int i = 0; i < channels.length; i++) {
+- listeners[i].members.clear();
+- }
+- }
+-
+- public void testMemberArrival() throws Exception {
+- //purpose of this test is to make sure that we have received all the members
+- //that we can expect before the start method returns
+- Thread[] threads = new Thread[channels.length];
+- for (int i=0; i<channels.length; i++ ) {
+- final Channel channel = channels[i];
+- Thread t = new Thread() {
+- public void run() {
+- try {
+- channel.start(Channel.DEFAULT);
+- }catch ( Exception x ) {
+- throw new RuntimeException(x);
+- }
+- }
+- };
+- threads[i] = t;
+- }
+- for (int i=0; i<threads.length; i++ ) threads[i].start();
+- for (int i=0; i<threads.length; i++ ) threads[i].join();
+- Thread.sleep(2000);
+- System.out.println("All channels started.");
+- for (int i=listeners.length-1; i>=0; i-- ) assertEquals("Checking member arrival length",channels.length-1,listeners[i].members.size());
+- }
+-
+- protected void tearDown() throws Exception {
+-
+- for (int i = 0; i < channels.length; i++) {
+- try {
+- channels[i].stop(Channel.DEFAULT);
+- } catch (Exception ignore) {}
+- }
+- super.tearDown();
+- }
+-
+- public class TestMbrListener
+- implements MembershipListener {
+- public String name = null;
+- public TestMbrListener(String name) {
+- this.name = name;
+- }
+-
+- public ArrayList members = new ArrayList();
+- public void memberAdded(Member member) {
+- if (!members.contains(member)) {
+- members.add(member);
+- try {
+- System.out.println(name + ":member added[" + new String(member.getPayload(), "ASCII") + "; Thread:"+Thread.currentThread().getName()+"]");
+- } catch (Exception x) {
+- System.out.println(name + ":member added[unknown]");
+- }
+- }
+- }
+-
+- public void memberDisappeared(Member member) {
+- if (members.contains(member)) {
+- members.remove(member);
+- try {
+- System.out.println(name + ":member disappeared[" + new String(member.getPayload(), "ASCII") + "; Thread:"+Thread.currentThread().getName()+"]");
+- } catch (Exception x) {
+- System.out.println(name + ":member disappeared[unknown]");
+- }
+- }
+- }
+-
+- }
+-
+-}
+Index: test/org/apache/catalina/tribes/test/membership/TestTcpFailureDetector.java
+===================================================================
+--- test/org/apache/catalina/tribes/test/membership/TestTcpFailureDetector.java (revision 590752)
++++ test/org/apache/catalina/tribes/test/membership/TestTcpFailureDetector.java (working copy)
+@@ -1,165 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.test.membership;
+-
+-import java.util.ArrayList;
+-
+-import org.apache.catalina.tribes.ByteMessage;
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.ManagedChannel;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.MembershipListener;
+-import org.apache.catalina.tribes.group.GroupChannel;
+-import org.apache.catalina.tribes.group.interceptors.TcpFailureDetector;
+-import junit.framework.TestCase;
+-
+-/**
+- * <p>Title: </p>
+- *
+- * <p>Description: </p>
+- *
+- * <p>Company: </p>
+- *
+- * @author not attributable
+- * @version 1.0
+- */
+-public class TestTcpFailureDetector extends TestCase {
+- private TcpFailureDetector tcpFailureDetector1 = null;
+- private TcpFailureDetector tcpFailureDetector2 = null;
+- private ManagedChannel channel1 = null;
+- private ManagedChannel channel2 = null;
+- private TestMbrListener mbrlist1 = null;
+- private TestMbrListener mbrlist2 = null;
+- protected void setUp() throws Exception {
+- super.setUp();
+- channel1 = new GroupChannel();
+- channel2 = new GroupChannel();
+- channel1.getMembershipService().setPayload("Channel-1".getBytes("ASCII"));
+- channel2.getMembershipService().setPayload("Channel-2".getBytes("ASCII"));
+- mbrlist1 = new TestMbrListener("Channel-1");
+- mbrlist2 = new TestMbrListener("Channel-2");
+- tcpFailureDetector1 = new TcpFailureDetector();
+- tcpFailureDetector2 = new TcpFailureDetector();
+- channel1.addInterceptor(tcpFailureDetector1);
+- channel2.addInterceptor(tcpFailureDetector2);
+- channel1.addMembershipListener(mbrlist1);
+- channel2.addMembershipListener(mbrlist2);
+- }
+-
+- public void clear() {
+- mbrlist1.members.clear();
+- mbrlist2.members.clear();
+- }
+-
+- public void testTcpSendFailureMemberDrop() throws Exception {
+- System.out.println("testTcpSendFailureMemberDrop()");
+- clear();
+- channel1.start(channel1.DEFAULT);
+- channel2.start(channel2.DEFAULT);
+- //Thread.sleep(1000);
+- assertEquals("Expecting member count to be equal",mbrlist1.members.size(),mbrlist2.members.size());
+- channel2.stop(channel2.SND_RX_SEQ);
+- ByteMessage msg = new ByteMessage(new byte[1024]);
+- try {
+- channel1.send(channel1.getMembers(), msg, 0);
+- assertEquals("Message send should have failed.",true,false);
+- } catch ( ChannelException x ) {
+-
+- }
+- assertEquals("Expecting member count to not be equal",mbrlist1.members.size()+1,mbrlist2.members.size());
+- channel1.stop(Channel.DEFAULT);
+- channel2.stop(Channel.DEFAULT);
+- }
+-
+- public void testTcpFailureMemberAdd() throws Exception {
+- System.out.println("testTcpFailureMemberAdd()");
+- clear();
+- channel1.start(channel1.DEFAULT);
+- channel2.start(channel2.SND_RX_SEQ);
+- channel2.start(channel2.SND_TX_SEQ);
+- channel2.start(channel2.MBR_RX_SEQ);
+- channel2.stop(channel2.SND_RX_SEQ);
+- channel2.start(channel2.MBR_TX_SEQ);
+- //Thread.sleep(1000);
+- assertEquals("Expecting member count to not be equal",mbrlist1.members.size()+1,mbrlist2.members.size());
+- channel1.stop(Channel.DEFAULT);
+- channel2.stop(Channel.DEFAULT);
+- }
+-
+- public void testTcpMcastFail() throws Exception {
+- System.out.println("testTcpMcastFail()");
+- clear();
+- channel1.start(channel1.DEFAULT);
+- channel2.start(channel2.DEFAULT);
+- //Thread.sleep(1000);
+- assertEquals("Expecting member count to be equal",mbrlist1.members.size(),mbrlist2.members.size());
+- channel2.stop(channel2.MBR_TX_SEQ);
+- ByteMessage msg = new ByteMessage(new byte[1024]);
+- try {
+- Thread.sleep(5000);
+- assertEquals("Expecting member count to be equal",mbrlist1.members.size(),mbrlist2.members.size());
+- channel1.send(channel1.getMembers(), msg, 0);
+- } catch ( ChannelException x ) {
+- assertEquals("Message send should have succeeded.",true,false);
+- }
+- channel1.stop(Channel.DEFAULT);
+- channel2.stop(Channel.DEFAULT);
+- }
+-
+-
+- protected void tearDown() throws Exception {
+- tcpFailureDetector1 = null;
+- tcpFailureDetector2 = null;
+- try { channel1.stop(Channel.DEFAULT);}catch (Exception ignore){}
+- channel1 = null;
+- try { channel2.stop(Channel.DEFAULT);}catch (Exception ignore){}
+- channel2 = null;
+- super.tearDown();
+- }
+-
+- public class TestMbrListener implements MembershipListener {
+- public String name = null;
+- public TestMbrListener(String name) {
+- this.name = name;
+- }
+- public ArrayList members = new ArrayList();
+- public void memberAdded(Member member) {
+- if ( !members.contains(member) ) {
+- members.add(member);
+- try{
+- System.out.println(name + ":member added[" + new String(member.getPayload(), "ASCII") + "]");
+- }catch ( Exception x ) {
+- System.out.println(name + ":member added[unknown]");
+- }
+- }
+- }
+-
+- public void memberDisappeared(Member member) {
+- if ( members.contains(member) ) {
+- members.remove(member);
+- try{
+- System.out.println(name + ":member disappeared[" + new String(member.getPayload(), "ASCII") + "]");
+- }catch ( Exception x ) {
+- System.out.println(name + ":member disappeared[unknown]");
+- }
+- }
+- }
+-
+- }
+-
+-}
+Index: test/org/apache/catalina/tribes/test/membership/TestDomainFilter.java
+===================================================================
+--- test/org/apache/catalina/tribes/test/membership/TestDomainFilter.java (revision 590752)
++++ test/org/apache/catalina/tribes/test/membership/TestDomainFilter.java (working copy)
+@@ -1,120 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.test.membership;
+-
+-import java.util.ArrayList;
+-
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.ManagedChannel;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.MembershipListener;
+-import org.apache.catalina.tribes.group.GroupChannel;
+-import junit.framework.TestCase;
+-import org.apache.catalina.tribes.group.interceptors.DomainFilterInterceptor;
+-import org.apache.catalina.tribes.util.UUIDGenerator;
+-
+-public class TestDomainFilter
+- extends TestCase {
+- private static int count = 10;
+- private ManagedChannel[] channels = new ManagedChannel[count];
+- private TestMbrListener[] listeners = new TestMbrListener[count];
+-
+- protected void setUp() throws Exception {
+- super.setUp();
+- for (int i = 0; i < channels.length; i++) {
+- channels[i] = new GroupChannel();
+- channels[i].getMembershipService().setPayload( ("Channel-" + (i + 1)).getBytes("ASCII"));
+- listeners[i] = new TestMbrListener( ("Listener-" + (i + 1)));
+- channels[i].addMembershipListener(listeners[i]);
+- DomainFilterInterceptor filter = new DomainFilterInterceptor();
+- filter.setDomain(UUIDGenerator.randomUUID(false));
+- channels[i].addInterceptor(filter);
+- }
+- }
+-
+- public void clear() {
+- for (int i = 0; i < channels.length; i++) {
+- listeners[i].members.clear();
+- }
+- }
+-
+- public void testMemberArrival() throws Exception {
+- //purpose of this test is to make sure that we have received all the members
+- //that we can expect before the start method returns
+- Thread[] threads = new Thread[channels.length];
+- for (int i=0; i<channels.length; i++ ) {
+- final Channel channel = channels[i];
+- Thread t = new Thread() {
+- public void run() {
+- try {
+- channel.start(Channel.DEFAULT);
+- }catch ( Exception x ) {
+- throw new RuntimeException(x);
+- }
+- }
+- };
+- threads[i] = t;
+- }
+- for (int i=0; i<threads.length; i++ ) threads[i].start();
+- for (int i=0; i<threads.length; i++ ) threads[i].join();
+- System.out.println("All channels started.");
+- for (int i=listeners.length-1; i>=0; i-- ) assertEquals("Checking member arrival length",0,listeners[i].members.size());
+- }
+-
+- protected void tearDown() throws Exception {
+-
+- for (int i = 0; i < channels.length; i++) {
+- try {
+- channels[i].stop(Channel.DEFAULT);
+- } catch (Exception ignore) {}
+- }
+- super.tearDown();
+- }
+-
+- public class TestMbrListener
+- implements MembershipListener {
+- public String name = null;
+- public TestMbrListener(String name) {
+- this.name = name;
+- }
+-
+- public ArrayList members = new ArrayList();
+- public void memberAdded(Member member) {
+- if (!members.contains(member)) {
+- members.add(member);
+- try {
+- System.out.println(name + ":member added[" + new String(member.getPayload(), "ASCII") + "; Thread:"+Thread.currentThread().getName()+"]");
+- } catch (Exception x) {
+- System.out.println(name + ":member added[unknown]");
+- }
+- }
+- }
+-
+- public void memberDisappeared(Member member) {
+- if (members.contains(member)) {
+- members.remove(member);
+- try {
+- System.out.println(name + ":member disappeared[" + new String(member.getPayload(), "ASCII") + "; Thread:"+Thread.currentThread().getName()+"]");
+- } catch (Exception x) {
+- System.out.println(name + ":member disappeared[unknown]");
+- }
+- }
+- }
+-
+- }
+-
+-}
+Index: test/org/apache/catalina/tribes/test/transport/SocketNioReceive.java
+===================================================================
+--- test/org/apache/catalina/tribes/test/transport/SocketNioReceive.java (revision 590752)
++++ test/org/apache/catalina/tribes/test/transport/SocketNioReceive.java (working copy)
+@@ -1,92 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.test.transport;
+-
+-import java.text.DecimalFormat;
+-
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.MessageListener;
+-import org.apache.catalina.tribes.io.ChannelData;
+-import org.apache.catalina.tribes.io.XByteBuffer;
+-import org.apache.catalina.tribes.membership.MemberImpl;
+-import org.apache.catalina.tribes.transport.nio.NioReceiver;
+-
+-public class SocketNioReceive {
+- static int count = 0;
+- static int accept = 0;
+- static long start = 0;
+- static double mb = 0;
+- static int len = 0;
+- static DecimalFormat df = new DecimalFormat("##.00");
+- static double seconds = 0;
+-
+- protected static Object mutex = new Object();
+- public static void main(String[] args) throws Exception {
+- Member mbr = new MemberImpl("localhost", 9999, 0);
+- ChannelData data = new ChannelData();
+- data.setAddress(mbr);
+- byte[] buf = new byte[8192 * 4];
+- data.setMessage(new XByteBuffer(buf, false));
+- buf = XByteBuffer.createDataPackage(data);
+- len = buf.length;
+- NioReceiver receiver = new NioReceiver();
+- receiver.setPort(9999);
+- receiver.setHost("localhost");
+- MyList list = new MyList();
+- receiver.setMessageListener(list);
+- receiver.start();
+- System.out.println("Listening on 9999");
+- while (true) {
+- try {
+- synchronized (mutex) {
+- mutex.wait(5000);
+- if ( start != 0 ) {
+- System.out.println("Throughput " + df.format(mb / seconds) + " MB/seconds, messages "+count+" accepts "+accept+", total "+mb+" MB.");
+- }
+- }
+- }catch (Throwable x) {
+- x.printStackTrace();
+- }
+- }
+- }
+-
+- public static class MyList implements MessageListener {
+- boolean first = true;
+-
+-
+- public void messageReceived(ChannelMessage msg) {
+- if (first) {
+- first = false;
+- start = System.currentTimeMillis();
+- }
+- mb += ( (double) len) / 1024 / 1024;
+- synchronized (this) {count++;}
+- if ( ( (count) % 10000) == 0) {
+- long time = System.currentTimeMillis();
+- seconds = ( (double) (time - start)) / 1000;
+- System.out.println("Throughput " + df.format(mb / seconds) + " MB/seconds, messages "+count+", total "+mb+" MB.");
+- }
+- }
+-
+- public boolean accept(ChannelMessage msg) {
+- synchronized (this) {accept++;}
+- return true;
+- }
+-
+- }
+-}
+Index: test/org/apache/catalina/tribes/test/transport/SocketNioValidateSend.java
+===================================================================
+--- test/org/apache/catalina/tribes/test/transport/SocketNioValidateSend.java (revision 590752)
++++ test/org/apache/catalina/tribes/test/transport/SocketNioValidateSend.java (working copy)
+@@ -1,106 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.test.transport;
+-
+-import java.io.OutputStream;
+-import java.net.Socket;
+-import java.text.DecimalFormat;
+-import org.apache.catalina.tribes.transport.nio.NioSender;
+-import org.apache.catalina.tribes.membership.MemberImpl;
+-import java.nio.channels.Selector;
+-import org.apache.catalina.tribes.io.XByteBuffer;
+-import org.apache.catalina.tribes.Member;
+-import java.nio.channels.SelectionKey;
+-import java.util.Iterator;
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.io.ChannelData;
+-import java.math.BigDecimal;
+-import java.util.Arrays;
+-
+-public class SocketNioValidateSend {
+-
+- public static void main(String[] args) throws Exception {
+- Selector selector = Selector.open();
+- Member mbr = new MemberImpl("localhost", 9999, 0);
+- byte seq = 0;
+- byte[] buf = new byte[50000];
+- Arrays.fill(buf,seq);
+- int len = buf.length;
+- BigDecimal total = new BigDecimal((double)0);
+- BigDecimal bytes = new BigDecimal((double)len);
+- NioSender sender = new NioSender();
+- sender.setDestination(mbr);
+- sender.setDirectBuffer(true);
+- sender.setSelector(selector);
+- sender.connect();
+- sender.setMessage(buf);
+- System.out.println("Writing to 9999");
+- long start = 0;
+- double mb = 0;
+- boolean first = true;
+- int count = 0;
+-
+- DecimalFormat df = new DecimalFormat("##.00");
+- while (count<100000) {
+- if (first) {
+- first = false;
+- start = System.currentTimeMillis();
+- }
+- sender.setMessage(buf);
+- int selectedKeys = 0;
+- try {
+- selectedKeys = selector.select(0);
+- } catch (Exception e) {
+- e.printStackTrace();
+- continue;
+- }
+-
+- if (selectedKeys == 0) {
+- continue;
+- }
+-
+- Iterator it = selector.selectedKeys().iterator();
+- while (it.hasNext()) {
+- SelectionKey sk = (SelectionKey) it.next();
+- it.remove();
+- try {
+- int readyOps = sk.readyOps();
+- sk.interestOps(sk.interestOps() & ~readyOps);
+- if (sender.process(sk, false)) {
+- total = total.add(bytes);
+- sender.reset();
+- seq++;
+- Arrays.fill(buf,seq);
+- sender.setMessage(buf);
+- mb += ( (double) len) / 1024 / 1024;
+- if ( ( (++count) % 10000) == 0) {
+- long time = System.currentTimeMillis();
+- double seconds = ( (double) (time - start)) / 1000;
+- System.out.println("Throughput " + df.format(mb / seconds) + " MB/seconds, total "+mb+" MB, total "+total+" bytes.");
+- }
+- }
+-
+- } catch (Throwable t) {
+- t.printStackTrace();
+- return;
+- }
+- }
+- }
+- System.out.println("Complete, sleeping 15 seconds");
+- Thread.sleep(15000);
+- }
+-}
+Index: test/org/apache/catalina/tribes/test/transport/SocketSend.java
+===================================================================
+--- test/org/apache/catalina/tribes/test/transport/SocketSend.java (revision 590752)
++++ test/org/apache/catalina/tribes/test/transport/SocketSend.java (working copy)
+@@ -1,69 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.test.transport;
+-
+-import java.io.OutputStream;
+-import java.net.Socket;
+-import java.text.DecimalFormat;
+-import org.apache.catalina.tribes.membership.MemberImpl;
+-import org.apache.catalina.tribes.io.XByteBuffer;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.io.ChannelData;
+-import org.apache.catalina.tribes.Channel;
+-import java.math.BigDecimal;
+-
+-public class SocketSend {
+-
+- public static void main(String[] args) throws Exception {
+-
+-
+- Member mbr = new MemberImpl("localhost", 9999, 0);
+- ChannelData data = new ChannelData();
+- data.setOptions(Channel.SEND_OPTIONS_BYTE_MESSAGE);
+- data.setAddress(mbr);
+- byte[] buf = new byte[8192 * 4];
+- data.setMessage(new XByteBuffer(buf,false));
+- buf = XByteBuffer.createDataPackage(data);
+- int len = buf.length;
+- System.out.println("Message size:"+len+" bytes");
+- BigDecimal total = new BigDecimal((double)0);
+- BigDecimal bytes = new BigDecimal((double)len);
+- Socket socket = new Socket("localhost",9999);
+- System.out.println("Writing to 9999");
+- OutputStream out = socket.getOutputStream();
+- long start = 0;
+- double mb = 0;
+- boolean first = true;
+- int count = 0;
+- DecimalFormat df = new DecimalFormat("##.00");
+- while ( count<1000000 ) {
+- if ( first ) { first = false; start = System.currentTimeMillis();}
+- out.write(buf,0,buf.length);
+- mb += ( (double) buf.length) / 1024 / 1024;
+- total = total.add(bytes);
+- if ( ((++count) % 10000) == 0 ) {
+- long time = System.currentTimeMillis();
+- double seconds = ((double)(time-start))/1000;
+- System.out.println("Throughput "+df.format(mb/seconds)+" MB/seconds messages "+count+", total "+mb+" MB, total "+total+" bytes.");
+- }
+- }
+- out.flush();
+- System.out.println("Complete, sleeping 5 seconds");
+- Thread.sleep(5000);
+-
+- }
+-}
+Index: test/org/apache/catalina/tribes/test/transport/SocketTribesReceive.java
+===================================================================
+--- test/org/apache/catalina/tribes/test/transport/SocketTribesReceive.java (revision 590752)
++++ test/org/apache/catalina/tribes/test/transport/SocketTribesReceive.java (working copy)
+@@ -1,87 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.test.transport;
+-
+-import java.net.ServerSocket;
+-import java.net.Socket;
+-import java.io.InputStream;
+-import java.text.DecimalFormat;
+-import java.math.BigDecimal;
+-import org.apache.catalina.tribes.io.XByteBuffer;
+-
+-public class SocketTribesReceive {
+- static long start = 0;
+- static double mb = 0;
+- //static byte[] buf = new byte[32871];
+- static byte[] buf = new byte[32871];
+- static boolean first = true;
+- static int count = 0;
+- static DecimalFormat df = new DecimalFormat("##.00");
+- static BigDecimal total = new BigDecimal((double)0);
+- static BigDecimal bytes = new BigDecimal((double)32871);
+-
+-
+- public static void main(String[] args) throws Exception {
+- int size = 43800;
+- if (args.length > 0 ) try {size=Integer.parseInt(args[0]);}catch(Exception x){}
+- XByteBuffer xbuf = new XByteBuffer(43800,true);
+- ServerSocket srvSocket = new ServerSocket(9999);
+- System.out.println("Listening on 9999");
+- Socket socket = srvSocket.accept();
+- socket.setReceiveBufferSize(size);
+- InputStream in = socket.getInputStream();
+- Thread t = new Thread() {
+- public void run() {
+- while ( true ) {
+- try {
+- Thread.sleep(1000);
+- printStats(start, mb, count, df, total);
+- }catch ( Exception x ) {}
+- }
+- }
+- };
+- t.setDaemon(true);
+- t.start();
+-
+- while ( true ) {
+- if ( first ) { first = false; start = System.currentTimeMillis();}
+- int len = in.read(buf);
+- if ( len == -1 ) {
+- printStats(start, mb, count, df, total);
+- System.exit(1);
+- }
+- xbuf.append(buf,0,len);
+- if ( bytes.intValue() != len ) bytes = new BigDecimal((double)len);
+- total = total.add(bytes);
+- while ( xbuf.countPackages(true) > 0 ) {
+- xbuf.extractPackage(true);
+- count++;
+- }
+- mb += ( (double) len) / 1024 / 1024;
+- if ( ((count) % 10000) == 0 ) {
+- printStats(start, mb, count, df, total);
+- }
+- }
+-
+- }
+-
+- private static void printStats(long start, double mb, int count, DecimalFormat df, BigDecimal total) {
+- long time = System.currentTimeMillis();
+- double seconds = ((double)(time-start))/1000;
+- System.out.println("Throughput "+df.format(mb/seconds)+" MB/seconds messages "+count+", total "+mb+" MB, total "+total+" bytes.");
+- }
+-}
+\ No newline at end of file
+Index: test/org/apache/catalina/tribes/test/transport/SocketValidateReceive.java
+===================================================================
+--- test/org/apache/catalina/tribes/test/transport/SocketValidateReceive.java (revision 590752)
++++ test/org/apache/catalina/tribes/test/transport/SocketValidateReceive.java (working copy)
+@@ -1,107 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.test.transport;
+-
+-import java.net.ServerSocket;
+-import java.net.Socket;
+-import java.io.InputStream;
+-import java.text.DecimalFormat;
+-import java.math.BigDecimal;
+-
+-public class SocketValidateReceive {
+- static long start = 0;
+- static double mb = 0;
+- static byte[] buf = new byte[8192 * 4];
+- static boolean first = true;
+- static int count = 0;
+- static DecimalFormat df = new DecimalFormat("##.00");
+- static BigDecimal total = new BigDecimal(0);
+- static BigDecimal bytes = new BigDecimal(32871);
+-
+-
+- public static void main(String[] args) throws Exception {
+- int size = 43800;
+- if (args.length > 0 ) try {size=Integer.parseInt(args[0]);}catch(Exception x){}
+-
+- ServerSocket srvSocket = new ServerSocket(9999);
+- System.out.println("Listening on 9999");
+- Socket socket = srvSocket.accept();
+- socket.setReceiveBufferSize(size);
+- InputStream in = socket.getInputStream();
+- MyDataReader reader = new MyDataReader(50000);
+- Thread t = new Thread() {
+- public void run() {
+- while ( true ) {
+- try {
+- Thread.sleep(1000);
+- printStats(start, mb, count, df, total);
+- }catch ( Exception x ) {}
+- }
+- }
+- };
+- t.setDaemon(true);
+- t.start();
+-
+- while ( true ) {
+- if ( first ) { first = false; start = System.currentTimeMillis();}
+- int len = in.read(buf);
+- if ( len == -1 ) {
+- printStats(start, mb, count, df, total);
+- System.exit(1);
+- }
+- count += reader.append(buf,0,len);
+-
+- if ( bytes.intValue() != len ) bytes = new BigDecimal((double)len);
+- total = total.add(bytes);
+- mb += ( (double) len) / 1024 / 1024;
+- if ( ((count) % 10000) == 0 ) {
+- printStats(start, mb, count, df, total);
+- }
+- }
+-
+- }
+-
+- private static void printStats(long start, double mb, int count, DecimalFormat df, BigDecimal total) {
+- long time = System.currentTimeMillis();
+- double seconds = ((double)(time-start))/1000;
+- System.out.println("Throughput "+df.format(mb/seconds)+" MB/seconds messages "+count+", total "+mb+" MB, total "+total+" bytes.");
+- }
+-
+- public static class MyDataReader {
+- byte[] data = new byte[43800];
+- int length = 10;
+- int cur = 0;
+- byte seq = 0;
+- public MyDataReader(int len) {
+- length = len;
+- }
+-
+- public int append(byte[] b, int off, int len) throws Exception {
+- int packages = 0;
+- for ( int i=off; i<len; i++ ) {
+- if ( cur == length ) {
+- cur = 0;
+- seq++;
+- packages++;
+- }
+- if ( b[i] != seq ) throw new Exception("mismatch on seq:"+seq+" and byte nr:"+cur+" count:"+count+" packages:"+packages);
+- cur++;
+- }
+- return packages;
+- }
+- }
+-}
+\ No newline at end of file
+Index: test/org/apache/catalina/tribes/test/transport/SocketNioSend.java
+===================================================================
+--- test/org/apache/catalina/tribes/test/transport/SocketNioSend.java (revision 590752)
++++ test/org/apache/catalina/tribes/test/transport/SocketNioSend.java (working copy)
+@@ -1,107 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.test.transport;
+-
+-import java.io.OutputStream;
+-import java.net.Socket;
+-import java.text.DecimalFormat;
+-import org.apache.catalina.tribes.transport.nio.NioSender;
+-import org.apache.catalina.tribes.membership.MemberImpl;
+-import java.nio.channels.Selector;
+-import org.apache.catalina.tribes.io.XByteBuffer;
+-import org.apache.catalina.tribes.Member;
+-import java.nio.channels.SelectionKey;
+-import java.util.Iterator;
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.io.ChannelData;
+-import java.math.BigDecimal;
+-
+-public class SocketNioSend {
+-
+- public static void main(String[] args) throws Exception {
+- Selector selector = Selector.open();
+- Member mbr = new MemberImpl("localhost", 9999, 0);
+- ChannelData data = new ChannelData();
+- data.setOptions(Channel.SEND_OPTIONS_BYTE_MESSAGE);
+- data.setAddress(mbr);
+- byte[] buf = new byte[8192 * 4];
+- data.setMessage(new XByteBuffer(buf,false));
+- buf = XByteBuffer.createDataPackage(data);
+- int len = buf.length;
+- BigDecimal total = new BigDecimal((double)0);
+- BigDecimal bytes = new BigDecimal((double)len);
+- NioSender sender = new NioSender();
+- sender.setDestination(mbr);
+- sender.setDirectBuffer(true);
+- sender.setSelector(selector);
+- sender.setTxBufSize(1024*1024);
+- sender.connect();
+- sender.setMessage(buf);
+- System.out.println("Writing to 9999");
+- long start = 0;
+- double mb = 0;
+- boolean first = true;
+- int count = 0;
+- DecimalFormat df = new DecimalFormat("##.00");
+- while (count<100000) {
+- if (first) {
+- first = false;
+- start = System.currentTimeMillis();
+- }
+- sender.setMessage(buf);
+- int selectedKeys = 0;
+- try {
+- selectedKeys = selector.select(0);
+- } catch (Exception e) {
+- e.printStackTrace();
+- continue;
+- }
+-
+- if (selectedKeys == 0) {
+- continue;
+- }
+-
+- Iterator it = selector.selectedKeys().iterator();
+- while (it.hasNext()) {
+- SelectionKey sk = (SelectionKey) it.next();
+- it.remove();
+- try {
+- int readyOps = sk.readyOps();
+- sk.interestOps(sk.interestOps() & ~readyOps);
+- if (sender.process(sk, false)) {
+- total = total.add(bytes);
+- sender.reset();
+- sender.setMessage(buf);
+- mb += ( (double) len) / 1024 / 1024;
+- if ( ( (++count) % 10000) == 0) {
+- long time = System.currentTimeMillis();
+- double seconds = ( (double) (time - start)) / 1000;
+- System.out.println("Throughput " + df.format(mb / seconds) + " MB/seconds, total "+mb+" MB, total "+total+" bytes.");
+- }
+- }
+-
+- } catch (Throwable t) {
+- t.printStackTrace();
+- return;
+- }
+- }
+- selector.selectedKeys().clear();
+- }
+- System.out.println("Complete, sleeping 15 seconds");
+- Thread.sleep(15000);
+- }
+-}
+Index: test/org/apache/catalina/tribes/test/transport/SocketReceive.java
+===================================================================
+--- test/org/apache/catalina/tribes/test/transport/SocketReceive.java (revision 590752)
++++ test/org/apache/catalina/tribes/test/transport/SocketReceive.java (working copy)
+@@ -1,78 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.test.transport;
+-
+-import java.net.ServerSocket;
+-import java.net.Socket;
+-import java.io.InputStream;
+-import java.text.DecimalFormat;
+-import java.math.BigDecimal;
+-
+-public class SocketReceive {
+- static long start = 0;
+- static double mb = 0;
+- static byte[] buf = new byte[8192 * 4];
+- static boolean first = true;
+- static int count = 0;
+- static DecimalFormat df = new DecimalFormat("##.00");
+- static BigDecimal total = new BigDecimal(0);
+- static BigDecimal bytes = new BigDecimal(32871);
+-
+-
+- public static void main(String[] args) throws Exception {
+-
+- ServerSocket srvSocket = new ServerSocket(9999);
+- System.out.println("Listening on 9999");
+- Socket socket = srvSocket.accept();
+- socket.setReceiveBufferSize(43800);
+- InputStream in = socket.getInputStream();
+- Thread t = new Thread() {
+- public void run() {
+- while ( true ) {
+- try {
+- Thread.sleep(1000);
+- printStats(start, mb, count, df, total);
+- }catch ( Exception x ) {}
+- }
+- }
+- };
+- t.setDaemon(true);
+- t.start();
+-
+- while ( true ) {
+- if ( first ) { first = false; start = System.currentTimeMillis();}
+- int len = in.read(buf);
+- if ( len == -1 ) {
+- printStats(start, mb, count, df, total);
+- System.exit(1);
+- }
+- if ( bytes.intValue() != len ) bytes = new BigDecimal((double)len);
+- total = total.add(bytes);
+- mb += ( (double) len) / 1024 / 1024;
+- if ( ((++count) % 10000) == 0 ) {
+- printStats(start, mb, count, df, total);
+- }
+- }
+-
+- }
+-
+- private static void printStats(long start, double mb, int count, DecimalFormat df, BigDecimal total) {
+- long time = System.currentTimeMillis();
+- double seconds = ((double)(time-start))/1000;
+- System.out.println("Throughput "+df.format(mb/seconds)+" MB/seconds messages "+count+", total "+mb+" MB, total "+total+" bytes.");
+- }
+-}
+\ No newline at end of file
+Index: test/org/apache/catalina/tribes/demos/LoadTest.java
+===================================================================
+--- test/org/apache/catalina/tribes/demos/LoadTest.java (revision 590752)
++++ test/org/apache/catalina/tribes/demos/LoadTest.java (working copy)
+@@ -1,426 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.demos;
+-
+-import java.io.Serializable;
+-import java.util.Random;
+-
+-import org.apache.catalina.tribes.ByteMessage;
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.ChannelListener;
+-import org.apache.catalina.tribes.ManagedChannel;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.MembershipListener;
+-import org.apache.catalina.tribes.io.XByteBuffer;
+-import org.apache.catalina.tribes.Channel;
+-import java.io.Externalizable;
+-import org.apache.juli.logging.Log;
+-import org.apache.juli.logging.LogFactory;
+-
+-
+-/**
+- * <p>Title: </p>
+- *
+- * <p>Description: </p>
+- *
+- * <p>Company: </p>
+- *
+- * @author not attributable
+- * @version 1.0
+- */
+-public class LoadTest implements MembershipListener,ChannelListener, Runnable {
+- protected static Log log = LogFactory.getLog(LoadTest.class);
+- public static int size = 24000;
+- public static Object mutex = new Object();
+- public boolean doRun = true;
+-
+- public long bytesReceived = 0;
+- public float mBytesReceived = 0;
+- public int messagesReceived = 0;
+- public boolean send = true;
+- public boolean debug = false;
+- public int msgCount = 100;
+- ManagedChannel channel=null;
+- public int statsInterval = 10000;
+- public long pause = 0;
+- public boolean breakonChannelException = false;
+- public boolean async = false;
+- public long receiveStart = 0;
+- public int channelOptions = Channel.SEND_OPTIONS_DEFAULT;
+-
+- static int messageSize = 0;
+-
+- public static long messagesSent = 0;
+- public static long messageStartSendTime = 0;
+- public static long messageEndSendTime = 0;
+- public static int threadCount = 0;
+-
+- public static synchronized void startTest() {
+- threadCount++;
+- if ( messageStartSendTime == 0 ) messageStartSendTime = System.currentTimeMillis();
+- }
+-
+- public static synchronized void endTest() {
+- threadCount--;
+- if ( messageEndSendTime == 0 && threadCount==0 ) messageEndSendTime = System.currentTimeMillis();
+- }
+-
+-
+- public static synchronized long addSendStats(long count) {
+- messagesSent+=count;
+- return 0l;
+- }
+-
+- private static void printSendStats(long counter, int messageSize) {
+- float cnt = (float)counter;
+- float size = (float)messageSize;
+- float time = (float)(System.currentTimeMillis()-messageStartSendTime) / 1000f;
+- log.info("****SEND STATS-"+Thread.currentThread().getName()+"*****"+
+- "\n\tMessage count:"+counter+
+- "\n\tTotal bytes :"+(long)(size*cnt)+
+- "\n\tTotal seconds:"+(time)+
+- "\n\tBytes/second :"+(size*cnt/time)+
+- "\n\tMBytes/second:"+(size*cnt/time/1024f/1024f));
+- }
+-
+-
+-
+- public LoadTest(ManagedChannel channel,
+- boolean send,
+- int msgCount,
+- boolean debug,
+- long pause,
+- int stats,
+- boolean breakOnEx) {
+- this.channel = channel;
+- this.send = send;
+- this.msgCount = msgCount;
+- this.debug = debug;
+- this.pause = pause;
+- this.statsInterval = stats;
+- this.breakonChannelException = breakOnEx;
+- }
+-
+-
+-
+- public void run() {
+-
+- long counter = 0;
+- long total = 0;
+- LoadMessage msg = new LoadMessage();
+- int messageSize = LoadTest.messageSize;
+-
+- try {
+- startTest();
+- while (total < msgCount) {
+- if (channel.getMembers().length == 0 || (!send)) {
+- synchronized (mutex) {
+- try {
+- mutex.wait();
+- } catch (InterruptedException x) {
+- log.info("Thread interrupted from wait");
+- }
+- }
+- } else {
+- try {
+- //msg.setMsgNr((int)++total);
+- counter++;
+- if (debug) {
+- printArray(msg.getMessage());
+- }
+- channel.send(channel.getMembers(), msg, channelOptions);
+- if ( pause > 0 ) {
+- if ( debug) System.out.println("Pausing sender for "+pause+" ms.");
+- Thread.sleep(pause);
+- }
+- } catch (ChannelException x) {
+- if ( debug ) log.error("Unable to send message:"+x.getMessage(),x);
+- log.error("Unable to send message:"+x.getMessage());
+- ChannelException.FaultyMember[] faulty = x.getFaultyMembers();
+- for (int i=0; i<faulty.length; i++ ) log.error("Faulty: "+faulty[i]);
+- --counter;
+- if ( this.breakonChannelException ) throw x;
+- }
+- }
+- if ( (counter % statsInterval) == 0 && (counter > 0)) {
+- //add to the global counter
+- counter = addSendStats(counter);
+- //print from the global counter
+- //printSendStats(LoadTest.messagesSent, LoadTest.messageSize, LoadTest.messageSendTime);
+- printSendStats(LoadTest.messagesSent, LoadTest.messageSize);
+-
+- }
+-
+- }
+- }catch ( Exception x ) {
+- log.error("Captured error while sending:"+x.getMessage());
+- if ( debug ) log.error("",x);
+- printSendStats(LoadTest.messagesSent, LoadTest.messageSize);
+- }
+- endTest();
+- }
+-
+-
+-
+- /**
+- * memberAdded
+- *
+- * @param member Member
+- * @todo Implement this org.apache.catalina.tribes.MembershipListener
+- * method
+- */
+- public void memberAdded(Member member) {
+- log.info("Member added:"+member);
+- synchronized (mutex) {
+- mutex.notifyAll();
+- }
+- }
+-
+- /**
+- * memberDisappeared
+- *
+- * @param member Member
+- * @todo Implement this org.apache.catalina.tribes.MembershipListener
+- * method
+- */
+- public void memberDisappeared(Member member) {
+- log.info("Member disappeared:"+member);
+- }
+-
+- public boolean accept(Serializable msg, Member mbr){
+- return (msg instanceof LoadMessage) || (msg instanceof ByteMessage);
+- }
+-
+- public void messageReceived(Serializable msg, Member mbr){
+- if ( receiveStart == 0 ) receiveStart = System.currentTimeMillis();
+- if ( debug ) {
+- if ( msg instanceof LoadMessage ) {
+- printArray(((LoadMessage)msg).getMessage());
+- }
+- }
+-
+- if ( msg instanceof ByteMessage && !(msg instanceof LoadMessage)) {
+- LoadMessage tmp = new LoadMessage();
+- tmp.setMessage(((ByteMessage)msg).getMessage());
+- msg = tmp;
+- tmp = null;
+- }
+-
+-
+- bytesReceived+=((LoadMessage)msg).getMessage().length;
+- mBytesReceived+=((float)((LoadMessage)msg).getMessage().length)/1024f/1024f;
+- messagesReceived++;
+- if ( (messagesReceived%statsInterval)==0 || (messagesReceived==msgCount)) {
+- float bytes = (float)(((LoadMessage)msg).getMessage().length*messagesReceived);
+- float seconds = ((float)(System.currentTimeMillis()-receiveStart)) / 1000f;
+- log.info("****RECEIVE STATS-"+Thread.currentThread().getName()+"*****"+
+- "\n\tMessage count :"+(long)messagesReceived+
+- "\n\tMessage/sec :"+messagesReceived/seconds+
+- "\n\tTotal bytes :"+(long)bytes+
+- "\n\tTotal mbytes :"+(long)mBytesReceived+
+- "\n\tTime since 1st:"+seconds+" seconds"+
+- "\n\tBytes/second :"+(bytes/seconds)+
+- "\n\tMBytes/second :"+(mBytesReceived/seconds)+"\n");
+-
+- }
+- }
+-
+-
+- public static void printArray(byte[] data) {
+- System.out.print("{");
+- for (int i=0; i<data.length; i++ ) {
+- System.out.print(data[i]);
+- System.out.print(",");
+- }
+- System.out.println("} size:"+data.length);
+- }
+-
+-
+-
+- //public static class LoadMessage implements Serializable {
+- public static class LoadMessage extends ByteMessage implements Serializable {
+-
+- public static byte[] outdata = new byte[size];
+- public static Random r = new Random(System.currentTimeMillis());
+- public static int getMessageSize (LoadMessage msg) {
+- int messageSize = msg.getMessage().length;
+- if ( ((Object)msg) instanceof ByteMessage ) return messageSize;
+- try {
+- messageSize = XByteBuffer.serialize(new LoadMessage()).length;
+- log.info("Average message size:" + messageSize + " bytes");
+- } catch (Exception x) {
+- log.error("Unable to calculate test message size.", x);
+- }
+- return messageSize;
+- }
+- static {
+- r.nextBytes(outdata);
+- }
+-
+- protected byte[] message = getMessage();
+-
+- public LoadMessage() {
+- }
+-
+- public byte[] getMessage() {
+- if ( message == null ) {
+- message = outdata;
+- }
+- return message;
+- }
+-
+- public void setMessage(byte[] data) {
+- this.message = data;
+- }
+- }
+-
+- public static void usage() {
+- System.out.println("Tribes Load tester.");
+- System.out.println("The load tester can be used in sender or received mode or both");
+- System.out.println("Usage:\n\t"+
+- "java LoadTest [options]\n\t"+
+- "Options:\n\t\t"+
+- "[-mode receive|send|both] \n\t\t"+
+- "[-startoptions startflags (default is Channel.DEFAULT) ] \n\t\t"+
+- "[-debug] \n\t\t"+
+- "[-count messagecount] \n\t\t"+
+- "[-stats statinterval] \n\t\t"+
+- "[-pause nrofsecondstopausebetweensends] \n\t\t"+
+- "[-threads numberofsenderthreads] \n\t\t"+
+- "[-size messagesize] \n\t\t"+
+- "[-sendoptions channeloptions] \n\t\t"+
+- "[-break (halts execution on exception)]\n"+
+- "[-shutdown (issues a channel.stop() command after send is completed)]\n"+
+- "\tChannel options:"+
+- ChannelCreator.usage()+"\n\n"+
+- "Example:\n\t"+
+- "java LoadTest -port 4004\n\t"+
+- "java LoadTest -bind 192.168.0.45 -port 4005\n\t"+
+- "java LoadTest -bind 192.168.0.45 -port 4005 -mbind 192.168.0.45 -count 100 -stats 10\n");
+- }
+-
+- public static void main(String[] args) throws Exception {
+- boolean send = true;
+- boolean debug = false;
+- long pause = 0;
+- int count = 1000000;
+- int stats = 10000;
+- boolean breakOnEx = false;
+- int threads = 1;
+- boolean shutdown = false;
+- int startoptions = Channel.DEFAULT;
+- int channelOptions = Channel.SEND_OPTIONS_DEFAULT;
+- if ( args.length == 0 ) {
+- args = new String[] {"-help"};
+- }
+- for (int i = 0; i < args.length; i++) {
+- if ("-threads".equals(args[i])) {
+- threads = Integer.parseInt(args[++i]);
+- } else if ("-count".equals(args[i])) {
+- count = Integer.parseInt(args[++i]);
+- System.out.println("Sending "+count+" messages.");
+- } else if ("-pause".equals(args[i])) {
+- pause = Long.parseLong(args[++i])*1000;
+- } else if ("-break".equals(args[i])) {
+- breakOnEx = true;
+- } else if ("-shutdown".equals(args[i])) {
+- shutdown = true;
+- } else if ("-stats".equals(args[i])) {
+- stats = Integer.parseInt(args[++i]);
+- System.out.println("Stats every "+stats+" message");
+- } else if ("-sendoptions".equals(args[i])) {
+- channelOptions = Integer.parseInt(args[++i]);
+- System.out.println("Setting send options to "+channelOptions);
+- } else if ("-startoptions".equals(args[i])) {
+- startoptions = Integer.parseInt(args[++i]);
+- System.out.println("Setting start options to "+startoptions);
+- } else if ("-size".equals(args[i])) {
+- size = Integer.parseInt(args[++i])-4;
+- System.out.println("Message size will be:"+(size+4)+" bytes");
+- } else if ("-mode".equals(args[i])) {
+- if ( "receive".equals(args[++i]) ) send = false;
+- } else if ("-debug".equals(args[i])) {
+- debug = true;
+- } else if ("-help".equals(args[i]))
+- {
+- usage();
+- System.exit(1);
+- }
+- }
+-
+- ManagedChannel channel = (ManagedChannel)ChannelCreator.createChannel(args);
+-
+- LoadTest test = new LoadTest(channel,send,count,debug,pause,stats,breakOnEx);
+- test.channelOptions = channelOptions;
+- LoadMessage msg = new LoadMessage();
+-
+- messageSize = LoadMessage.getMessageSize(msg);
+- channel.addChannelListener(test);
+- channel.addMembershipListener(test);
+- channel.start(startoptions);
+- Runtime.getRuntime().addShutdownHook(new Shutdown(channel));
+- while ( threads > 1 ) {
+- Thread t = new Thread(test);
+- t.setDaemon(true);
+- t.start();
+- threads--;
+- test = new LoadTest(channel,send,count,debug,pause,stats,breakOnEx);
+- test.channelOptions = channelOptions;
+- }
+- test.run();
+- if ( shutdown && send ) channel.stop(channel.DEFAULT);
+- System.out.println("System test complete, sleeping to let threads finish.");
+- Thread.sleep(60*1000*60);
+- }
+-
+- public static class Shutdown extends Thread {
+- ManagedChannel channel = null;
+- public Shutdown(ManagedChannel channel) {
+- this.channel = channel;
+- }
+-
+- public void run() {
+- System.out.println("Shutting down...");
+- SystemExit exit = new SystemExit(5000);
+- exit.setDaemon(true);
+- exit.start();
+- try {
+- channel.stop(channel.DEFAULT);
+-
+- }catch ( Exception x ) {
+- x.printStackTrace();
+- }
+- System.out.println("Channel stopped.");
+- }
+- }
+- public static class SystemExit extends Thread {
+- private long delay;
+- public SystemExit(long delay) {
+- this.delay = delay;
+- }
+- public void run () {
+- try {
+- Thread.sleep(delay);
+- }catch ( Exception x ) {
+- x.printStackTrace();
+- }
+- System.exit(0);
+-
+- }
+- }
+-
+-}
+\ No newline at end of file
+Index: test/org/apache/catalina/tribes/demos/IntrospectionUtils.java
+===================================================================
+--- test/org/apache/catalina/tribes/demos/IntrospectionUtils.java (revision 590752)
++++ test/org/apache/catalina/tribes/demos/IntrospectionUtils.java (working copy)
+@@ -1,1004 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.demos;
+-
+-import java.io.File;
+-import java.io.FilenameFilter;
+-import java.io.IOException;
+-import java.lang.reflect.InvocationTargetException;
+-import java.lang.reflect.Method;
+-import java.net.InetAddress;
+-import java.net.MalformedURLException;
+-import java.net.URL;
+-import java.net.UnknownHostException;
+-import java.util.Hashtable;
+-import java.util.StringTokenizer;
+-import java.util.Vector;
+-import org.apache.juli.logging.LogFactory;
+-import org.apache.juli.logging.Log;
+-
+-// Depends: JDK1.1
+-
+-/**
+- * Utils for introspection and reflection
+- */
+-public final class IntrospectionUtils {
+-
+-
+- private static Log log= LogFactory.getLog( IntrospectionUtils.class );
+-
+- /**
+- * Call execute() - any ant-like task should work
+- */
+- public static void execute(Object proxy, String method) throws Exception {
+- Method executeM = null;
+- Class c = proxy.getClass();
+- Class params[] = new Class[0];
+- // params[0]=args.getClass();
+- executeM = findMethod(c, method, params);
+- if (executeM == null) {
+- throw new RuntimeException("No execute in " + proxy.getClass());
+- }
+- executeM.invoke(proxy, (Object[]) null);//new Object[] { args });
+- }
+-
+- /**
+- * Call void setAttribute( String ,Object )
+- */
+- public static void setAttribute(Object proxy, String n, Object v)
+- throws Exception {
+- if (proxy instanceof AttributeHolder) {
+- ((AttributeHolder) proxy).setAttribute(n, v);
+- return;
+- }
+-
+- Method executeM = null;
+- Class c = proxy.getClass();
+- Class params[] = new Class[2];
+- params[0] = String.class;
+- params[1] = Object.class;
+- executeM = findMethod(c, "setAttribute", params);
+- if (executeM == null) {
+- if (log.isDebugEnabled())
+- log.debug("No setAttribute in " + proxy.getClass());
+- return;
+- }
+- if (false)
+- if (log.isDebugEnabled())
+- log.debug("Setting " + n + "=" + v + " in " + proxy);
+- executeM.invoke(proxy, new Object[] { n, v });
+- return;
+- }
+-
+- /**
+- * Call void getAttribute( String )
+- */
+- public static Object getAttribute(Object proxy, String n) throws Exception {
+- Method executeM = null;
+- Class c = proxy.getClass();
+- Class params[] = new Class[1];
+- params[0] = String.class;
+- executeM = findMethod(c, "getAttribute", params);
+- if (executeM == null) {
+- if (log.isDebugEnabled())
+- log.debug("No getAttribute in " + proxy.getClass());
+- return null;
+- }
+- return executeM.invoke(proxy, new Object[] { n });
+- }
+-
+- /**
+- * Construct a URLClassLoader. Will compile and work in JDK1.1 too.
+- */
+- public static ClassLoader getURLClassLoader(URL urls[], ClassLoader parent) {
+- try {
+- Class urlCL = Class.forName("java.net.URLClassLoader");
+- Class paramT[] = new Class[2];
+- paramT[0] = urls.getClass();
+- paramT[1] = ClassLoader.class;
+- Method m = findMethod(urlCL, "newInstance", paramT);
+- if (m == null)
+- return null;
+-
+- ClassLoader cl = (ClassLoader) m.invoke(urlCL, new Object[] { urls,
+- parent });
+- return cl;
+- } catch (ClassNotFoundException ex) {
+- // jdk1.1
+- return null;
+- } catch (Exception ex) {
+- ex.printStackTrace();
+- return null;
+- }
+- }
+-
+- public static String guessInstall(String installSysProp,
+- String homeSysProp, String jarName) {
+- return guessInstall(installSysProp, homeSysProp, jarName, null);
+- }
+-
+- /**
+- * Guess a product install/home by analyzing the class path. It works for
+- * product using the pattern: lib/executable.jar or if executable.jar is
+- * included in classpath by a shell script. ( java -jar also works )
+- *
+- * Insures both "install" and "home" System properties are set. If either or
+- * both System properties are unset, "install" and "home" will be set to the
+- * same value. This value will be the other System property that is set, or
+- * the guessed value if neither is set.
+- */
+- public static String guessInstall(String installSysProp,
+- String homeSysProp, String jarName, String classFile) {
+- String install = null;
+- String home = null;
+-
+- if (installSysProp != null)
+- install = System.getProperty(installSysProp);
+-
+- if (homeSysProp != null)
+- home = System.getProperty(homeSysProp);
+-
+- if (install != null) {
+- if (home == null)
+- System.getProperties().put(homeSysProp, install);
+- return install;
+- }
+-
+- // Find the directory where jarName.jar is located
+-
+- String cpath = System.getProperty("java.class.path");
+- String pathSep = System.getProperty("path.separator");
+- StringTokenizer st = new StringTokenizer(cpath, pathSep);
+- while (st.hasMoreTokens()) {
+- String path = st.nextToken();
+- // log( "path " + path );
+- if (path.endsWith(jarName)) {
+- home = path.substring(0, path.length() - jarName.length());
+- try {
+- if ("".equals(home)) {
+- home = new File("./").getCanonicalPath();
+- } else if (home.endsWith(File.separator)) {
+- home = home.substring(0, home.length() - 1);
+- }
+- File f = new File(home);
+- String parentDir = f.getParent();
+- if (parentDir == null)
+- parentDir = home; // unix style
+- File f1 = new File(parentDir);
+- install = f1.getCanonicalPath();
+- if (installSysProp != null)
+- System.getProperties().put(installSysProp, install);
+- if (home == null && homeSysProp != null)
+- System.getProperties().put(homeSysProp, install);
+- return install;
+- } catch (Exception ex) {
+- ex.printStackTrace();
+- }
+- } else {
+- String fname = path + (path.endsWith("/") ? "" : "/")
+- + classFile;
+- if (new File(fname).exists()) {
+- try {
+- File f = new File(path);
+- String parentDir = f.getParent();
+- if (parentDir == null)
+- parentDir = path; // unix style
+- File f1 = new File(parentDir);
+- install = f1.getCanonicalPath();
+- if (installSysProp != null)
+- System.getProperties().put(installSysProp, install);
+- if (home == null && homeSysProp != null)
+- System.getProperties().put(homeSysProp, install);
+- return install;
+- } catch (Exception ex) {
+- ex.printStackTrace();
+- }
+- }
+- }
+- }
+-
+- // if install directory can't be found, use home as the default
+- if (home != null) {
+- System.getProperties().put(installSysProp, home);
+- return home;
+- }
+-
+- return null;
+- }
+-
+- /**
+- * Debug method, display the classpath
+- */
+- public static void displayClassPath(String msg, URL[] cp) {
+- if (log.isDebugEnabled()) {
+- log.debug(msg);
+- for (int i = 0; i < cp.length; i++) {
+- log.debug(cp[i].getFile());
+- }
+- }
+- }
+-
+- public static String PATH_SEPARATOR = System.getProperty("path.separator");
+-
+- /**
+- * Adds classpath entries from a vector of URL's to the "tc_path_add" System
+- * property. This System property lists the classpath entries common to web
+- * applications. This System property is currently used by Jasper when its
+- * JSP servlet compiles the Java file for a JSP.
+- */
+- public static String classPathAdd(URL urls[], String cp) {
+- if (urls == null)
+- return cp;
+-
+- for (int i = 0; i < urls.length; i++) {
+- if (cp != null)
+- cp += PATH_SEPARATOR + urls[i].getFile();
+- else
+- cp = urls[i].getFile();
+- }
+- return cp;
+- }
+-
+- /**
+- * Find a method with the right name If found, call the method ( if param is
+- * int or boolean we'll convert value to the right type before) - that means
+- * you can have setDebug(1).
+- */
+- public static void setProperty(Object o, String name, String value) {
+- if (dbg > 1)
+- d("setProperty(" + o.getClass() + " " + name + "=" + value + ")");
+-
+- String setter = "set" + capitalize(name);
+-
+- try {
+- Method methods[] = findMethods(o.getClass());
+- Method setPropertyMethod = null;
+-
+- // First, the ideal case - a setFoo( String ) method
+- for (int i = 0; i < methods.length; i++) {
+- Class paramT[] = methods[i].getParameterTypes();
+- if (setter.equals(methods[i].getName()) && paramT.length == 1
+- && "java.lang.String".equals(paramT[0].getName())) {
+-
+- methods[i].invoke(o, new Object[] { value });
+- return;
+- }
+- }
+-
+- // Try a setFoo ( int ) or ( boolean )
+- for (int i = 0; i < methods.length; i++) {
+- boolean ok = true;
+- if (setter.equals(methods[i].getName())
+- && methods[i].getParameterTypes().length == 1) {
+-
+- // match - find the type and invoke it
+- Class paramType = methods[i].getParameterTypes()[0];
+- Object params[] = new Object[1];
+-
+- // Try a setFoo ( int )
+- if ("java.lang.Integer".equals(paramType.getName())
+- || "int".equals(paramType.getName())) {
+- try {
+- params[0] = new Integer(value);
+- } catch (NumberFormatException ex) {
+- ok = false;
+- }
+- // Try a setFoo ( long )
+- }else if ("java.lang.Long".equals(paramType.getName())
+- || "long".equals(paramType.getName())) {
+- try {
+- params[0] = new Long(value);
+- } catch (NumberFormatException ex) {
+- ok = false;
+- }
+-
+- // Try a setFoo ( boolean )
+- } else if ("java.lang.Boolean".equals(paramType.getName())
+- || "boolean".equals(paramType.getName())) {
+- params[0] = new Boolean(value);
+-
+- // Try a setFoo ( InetAddress )
+- } else if ("java.net.InetAddress".equals(paramType
+- .getName())) {
+- try {
+- params[0] = InetAddress.getByName(value);
+- } catch (UnknownHostException exc) {
+- d("Unable to resolve host name:" + value);
+- ok = false;
+- }
+-
+- // Unknown type
+- } else {
+- d("Unknown type " + paramType.getName());
+- }
+-
+- if (ok) {
+- methods[i].invoke(o, params);
+- return;
+- }
+- }
+-
+- // save "setProperty" for later
+- if ("setProperty".equals(methods[i].getName())) {
+- setPropertyMethod = methods[i];
+- }
+- }
+-
+- // Ok, no setXXX found, try a setProperty("name", "value")
+- if (setPropertyMethod != null) {
+- Object params[] = new Object[2];
+- params[0] = name;
+- params[1] = value;
+- setPropertyMethod.invoke(o, params);
+- }
+-
+- } catch (IllegalArgumentException ex2) {
+- log.warn("IAE " + o + " " + name + " " + value, ex2);
+- } catch (SecurityException ex1) {
+- if (dbg > 0)
+- d("SecurityException for " + o.getClass() + " " + name + "="
+- + value + ")");
+- if (dbg > 1)
+- ex1.printStackTrace();
+- } catch (IllegalAccessException iae) {
+- if (dbg > 0)
+- d("IllegalAccessException for " + o.getClass() + " " + name
+- + "=" + value + ")");
+- if (dbg > 1)
+- iae.printStackTrace();
+- } catch (InvocationTargetException ie) {
+- if (dbg > 0)
+- d("InvocationTargetException for " + o.getClass() + " " + name
+- + "=" + value + ")");
+- if (dbg > 1)
+- ie.printStackTrace();
+- }
+- }
+-
+- public static Object getProperty(Object o, String name) {
+- String getter = "get" + capitalize(name);
+- String isGetter = "is" + capitalize(name);
+-
+- try {
+- Method methods[] = findMethods(o.getClass());
+- Method getPropertyMethod = null;
+-
+- // First, the ideal case - a getFoo() method
+- for (int i = 0; i < methods.length; i++) {
+- Class paramT[] = methods[i].getParameterTypes();
+- if (getter.equals(methods[i].getName()) && paramT.length == 0) {
+- return methods[i].invoke(o, (Object[]) null);
+- }
+- if (isGetter.equals(methods[i].getName()) && paramT.length == 0) {
+- return methods[i].invoke(o, (Object[]) null);
+- }
+-
+- if ("getProperty".equals(methods[i].getName())) {
+- getPropertyMethod = methods[i];
+- }
+- }
+-
+- // Ok, no setXXX found, try a getProperty("name")
+- if (getPropertyMethod != null) {
+- Object params[] = new Object[1];
+- params[0] = name;
+- return getPropertyMethod.invoke(o, params);
+- }
+-
+- } catch (IllegalArgumentException ex2) {
+- log.warn("IAE " + o + " " + name, ex2);
+- } catch (SecurityException ex1) {
+- if (dbg > 0)
+- d("SecurityException for " + o.getClass() + " " + name + ")");
+- if (dbg > 1)
+- ex1.printStackTrace();
+- } catch (IllegalAccessException iae) {
+- if (dbg > 0)
+- d("IllegalAccessException for " + o.getClass() + " " + name
+- + ")");
+- if (dbg > 1)
+- iae.printStackTrace();
+- } catch (InvocationTargetException ie) {
+- if (dbg > 0)
+- d("InvocationTargetException for " + o.getClass() + " " + name
+- + ")");
+- if (dbg > 1)
+- ie.printStackTrace();
+- }
+- return null;
+- }
+-
+- /**
+- */
+- public static void setProperty(Object o, String name) {
+- String setter = "set" + capitalize(name);
+- try {
+- Method methods[] = findMethods(o.getClass());
+- Method setPropertyMethod = null;
+- // find setFoo() method
+- for (int i = 0; i < methods.length; i++) {
+- Class paramT[] = methods[i].getParameterTypes();
+- if (setter.equals(methods[i].getName()) && paramT.length == 0) {
+- methods[i].invoke(o, new Object[] {});
+- return;
+- }
+- }
+- } catch (Exception ex1) {
+- if (dbg > 0)
+- d("Exception for " + o.getClass() + " " + name);
+- if (dbg > 1)
+- ex1.printStackTrace();
+- }
+- }
+-
+- /**
+- * Replace ${NAME} with the property value
+- *
+- * @deprecated Use the explicit method
+- */
+- public static String replaceProperties(String value, Object getter) {
+- if (getter instanceof Hashtable)
+- return replaceProperties(value, (Hashtable) getter, null);
+-
+- if (getter instanceof PropertySource) {
+- PropertySource src[] = new PropertySource[] { (PropertySource) getter };
+- return replaceProperties(value, null, src);
+- }
+- return value;
+- }
+-
+- /**
+- * Replace ${NAME} with the property value
+- */
+- public static String replaceProperties(String value, Hashtable staticProp,
+- PropertySource dynamicProp[]) {
+- StringBuffer sb = new StringBuffer();
+- int prev = 0;
+- // assert value!=nil
+- int pos;
+- while ((pos = value.indexOf("$", prev)) >= 0) {
+- if (pos > 0) {
+- sb.append(value.substring(prev, pos));
+- }
+- if (pos == (value.length() - 1)) {
+- sb.append('$');
+- prev = pos + 1;
+- } else if (value.charAt(pos + 1) != '{') {
+- sb.append('$');
+- prev = pos + 1; // XXX
+- } else {
+- int endName = value.indexOf('}', pos);
+- if (endName < 0) {
+- sb.append(value.substring(pos));
+- prev = value.length();
+- continue;
+- }
+- String n = value.substring(pos + 2, endName);
+- String v = null;
+- if (staticProp != null) {
+- v = (String) ((Hashtable) staticProp).get(n);
+- }
+- if (v == null && dynamicProp != null) {
+- for (int i = 0; i < dynamicProp.length; i++) {
+- v = dynamicProp[i].getProperty(n);
+- if (v != null) {
+- break;
+- }
+- }
+- }
+- if (v == null)
+- v = "${" + n + "}";
+-
+- sb.append(v);
+- prev = endName + 1;
+- }
+- }
+- if (prev < value.length())
+- sb.append(value.substring(prev));
+- return sb.toString();
+- }
+-
+- /**
+- * Reverse of Introspector.decapitalize
+- */
+- public static String capitalize(String name) {
+- if (name == null || name.length() == 0) {
+- return name;
+- }
+- char chars[] = name.toCharArray();
+- chars[0] = Character.toUpperCase(chars[0]);
+- return new String(chars);
+- }
+-
+- public static String unCapitalize(String name) {
+- if (name == null || name.length() == 0) {
+- return name;
+- }
+- char chars[] = name.toCharArray();
+- chars[0] = Character.toLowerCase(chars[0]);
+- return new String(chars);
+- }
+-
+- // -------------------- Class path tools --------------------
+-
+- /**
+- * Add all the jar files in a dir to the classpath, represented as a Vector
+- * of URLs.
+- */
+- public static void addToClassPath(Vector cpV, String dir) {
+- try {
+- String cpComp[] = getFilesByExt(dir, ".jar");
+- if (cpComp != null) {
+- int jarCount = cpComp.length;
+- for (int i = 0; i < jarCount; i++) {
+- URL url = getURL(dir, cpComp[i]);
+- if (url != null)
+- cpV.addElement(url);
+- }
+- }
+- } catch (Exception ex) {
+- ex.printStackTrace();
+- }
+- }
+-
+- public static void addToolsJar(Vector v) {
+- try {
+- // Add tools.jar in any case
+- File f = new File(System.getProperty("java.home")
+- + "/../lib/tools.jar");
+-
+- if (!f.exists()) {
+- // On some systems java.home gets set to the root of jdk.
+- // That's a bug, but we can work around and be nice.
+- f = new File(System.getProperty("java.home") + "/lib/tools.jar");
+- if (f.exists()) {
+- if (log.isDebugEnabled())
+- log.debug("Detected strange java.home value "
+- + System.getProperty("java.home")
+- + ", it should point to jre");
+- }
+- }
+- URL url = new URL("file", "", f.getAbsolutePath());
+-
+- v.addElement(url);
+- } catch (MalformedURLException ex) {
+- ex.printStackTrace();
+- }
+- }
+-
+- /**
+- * Return all files with a given extension in a dir
+- */
+- public static String[] getFilesByExt(String ld, String ext) {
+- File dir = new File(ld);
+- String[] names = null;
+- final String lext = ext;
+- if (dir.isDirectory()) {
+- names = dir.list(new FilenameFilter() {
+- public boolean accept(File d, String name) {
+- if (name.endsWith(lext)) {
+- return true;
+- }
+- return false;
+- }
+- });
+- }
+- return names;
+- }
+-
+- /**
+- * Construct a file url from a file, using a base dir
+- */
+- public static URL getURL(String base, String file) {
+- try {
+- File baseF = new File(base);
+- File f = new File(baseF, file);
+- String path = f.getCanonicalPath();
+- if (f.isDirectory()) {
+- path += "/";
+- }
+- if (!f.exists())
+- return null;
+- return new URL("file", "", path);
+- } catch (Exception ex) {
+- ex.printStackTrace();
+- return null;
+- }
+- }
+-
+- /**
+- * Add elements from the classpath <i>cp </i> to a Vector <i>jars </i> as
+- * file URLs (We use Vector for JDK 1.1 compat).
+- * <p>
+- *
+- * @param jars The jar list
+- * @param cp a String classpath of directory or jar file elements
+- * separated by path.separator delimiters.
+- * @throws IOException If an I/O error occurs
+- * @throws MalformedURLException Doh ;)
+- */
+- public static void addJarsFromClassPath(Vector jars, String cp)
+- throws IOException, MalformedURLException {
+- String sep = System.getProperty("path.separator");
+- String token;
+- StringTokenizer st;
+- if (cp != null) {
+- st = new StringTokenizer(cp, sep);
+- while (st.hasMoreTokens()) {
+- File f = new File(st.nextToken());
+- String path = f.getCanonicalPath();
+- if (f.isDirectory()) {
+- path += "/";
+- }
+- URL url = new URL("file", "", path);
+- if (!jars.contains(url)) {
+- jars.addElement(url);
+- }
+- }
+- }
+- }
+-
+- /**
+- * Return a URL[] that can be used to construct a class loader
+- */
+- public static URL[] getClassPath(Vector v) {
+- URL[] urls = new URL[v.size()];
+- for (int i = 0; i < v.size(); i++) {
+- urls[i] = (URL) v.elementAt(i);
+- }
+- return urls;
+- }
+-
+- /**
+- * Construct a URL classpath from files in a directory, a cpath property,
+- * and tools.jar.
+- */
+- public static URL[] getClassPath(String dir, String cpath,
+- String cpathProp, boolean addTools) throws IOException,
+- MalformedURLException {
+- Vector jarsV = new Vector();
+- if (dir != null) {
+- // Add dir/classes first, if it exists
+- URL url = getURL(dir, "classes");
+- if (url != null)
+- jarsV.addElement(url);
+- addToClassPath(jarsV, dir);
+- }
+-
+- if (cpath != null)
+- addJarsFromClassPath(jarsV, cpath);
+-
+- if (cpathProp != null) {
+- String cpath1 = System.getProperty(cpathProp);
+- addJarsFromClassPath(jarsV, cpath1);
+- }
+-
+- if (addTools)
+- addToolsJar(jarsV);
+-
+- return getClassPath(jarsV);
+- }
+-
+- // -------------------- Mapping command line params to setters
+-
+- public static boolean processArgs(Object proxy, String args[])
+- throws Exception {
+- String args0[] = null;
+- if (null != findMethod(proxy.getClass(), "getOptions1", new Class[] {})) {
+- args0 = (String[]) callMethod0(proxy, "getOptions1");
+- }
+-
+- if (args0 == null) {
+- //args0=findVoidSetters(proxy.getClass());
+- args0 = findBooleanSetters(proxy.getClass());
+- }
+- Hashtable h = null;
+- if (null != findMethod(proxy.getClass(), "getOptionAliases",
+- new Class[] {})) {
+- h = (Hashtable) callMethod0(proxy, "getOptionAliases");
+- }
+- return processArgs(proxy, args, args0, null, h);
+- }
+-
+- public static boolean processArgs(Object proxy, String args[],
+- String args0[], String args1[], Hashtable aliases) throws Exception {
+- for (int i = 0; i < args.length; i++) {
+- String arg = args[i];
+- if (arg.startsWith("-"))
+- arg = arg.substring(1);
+- if (aliases != null && aliases.get(arg) != null)
+- arg = (String) aliases.get(arg);
+-
+- if (args0 != null) {
+- boolean set = false;
+- for (int j = 0; j < args0.length; j++) {
+- if (args0[j].equalsIgnoreCase(arg)) {
+- setProperty(proxy, args0[j], "true");
+- set = true;
+- break;
+- }
+- }
+- if (set)
+- continue;
+- }
+- if (args1 != null) {
+- for (int j = 0; j < args1.length; j++) {
+- if (args1[j].equalsIgnoreCase(arg)) {
+- i++;
+- if (i >= args.length)
+- return false;
+- setProperty(proxy, arg, args[i]);
+- break;
+- }
+- }
+- } else {
+- // if args1 is not specified,assume all other options have param
+- i++;
+- if (i >= args.length)
+- return false;
+- setProperty(proxy, arg, args[i]);
+- }
+-
+- }
+- return true;
+- }
+-
+- // -------------------- other utils --------------------
+- public static void clear() {
+- objectMethods.clear();
+- }
+-
+- public static String[] findVoidSetters(Class c) {
+- Method m[] = findMethods(c);
+- if (m == null)
+- return null;
+- Vector v = new Vector();
+- for (int i = 0; i < m.length; i++) {
+- if (m[i].getName().startsWith("set")
+- && m[i].getParameterTypes().length == 0) {
+- String arg = m[i].getName().substring(3);
+- v.addElement(unCapitalize(arg));
+- }
+- }
+- String s[] = new String[v.size()];
+- for (int i = 0; i < s.length; i++) {
+- s[i] = (String) v.elementAt(i);
+- }
+- return s;
+- }
+-
+- public static String[] findBooleanSetters(Class c) {
+- Method m[] = findMethods(c);
+- if (m == null)
+- return null;
+- Vector v = new Vector();
+- for (int i = 0; i < m.length; i++) {
+- if (m[i].getName().startsWith("set")
+- && m[i].getParameterTypes().length == 1
+- && "boolean".equalsIgnoreCase(m[i].getParameterTypes()[0]
+- .getName())) {
+- String arg = m[i].getName().substring(3);
+- v.addElement(unCapitalize(arg));
+- }
+- }
+- String s[] = new String[v.size()];
+- for (int i = 0; i < s.length; i++) {
+- s[i] = (String) v.elementAt(i);
+- }
+- return s;
+- }
+-
+- static Hashtable objectMethods = new Hashtable();
+-
+- public static Method[] findMethods(Class c) {
+- Method methods[] = (Method[]) objectMethods.get(c);
+- if (methods != null)
+- return methods;
+-
+- methods = c.getMethods();
+- objectMethods.put(c, methods);
+- return methods;
+- }
+-
+- public static Method findMethod(Class c, String name, Class params[]) {
+- Method methods[] = findMethods(c);
+- if (methods == null)
+- return null;
+- for (int i = 0; i < methods.length; i++) {
+- if (methods[i].getName().equals(name)) {
+- Class methodParams[] = methods[i].getParameterTypes();
+- if (methodParams == null)
+- if (params == null || params.length == 0)
+- return methods[i];
+- if (params == null)
+- if (methodParams == null || methodParams.length == 0)
+- return methods[i];
+- if (params.length != methodParams.length)
+- continue;
+- boolean found = true;
+- for (int j = 0; j < params.length; j++) {
+- if (params[j] != methodParams[j]) {
+- found = false;
+- break;
+- }
+- }
+- if (found)
+- return methods[i];
+- }
+- }
+- return null;
+- }
+-
+- /** Test if the object implements a particular
+- * method
+- */
+- public static boolean hasHook(Object obj, String methodN) {
+- try {
+- Method myMethods[] = findMethods(obj.getClass());
+- for (int i = 0; i < myMethods.length; i++) {
+- if (methodN.equals(myMethods[i].getName())) {
+- // check if it's overriden
+- Class declaring = myMethods[i].getDeclaringClass();
+- Class parentOfDeclaring = declaring.getSuperclass();
+- // this works only if the base class doesn't extend
+- // another class.
+-
+- // if the method is declared in a top level class
+- // like BaseInterceptor parent is Object, otherwise
+- // parent is BaseInterceptor or an intermediate class
+- if (!"java.lang.Object".equals(parentOfDeclaring.getName())) {
+- return true;
+- }
+- }
+- }
+- } catch (Exception ex) {
+- ex.printStackTrace();
+- }
+- return false;
+- }
+-
+- public static void callMain(Class c, String args[]) throws Exception {
+- Class p[] = new Class[1];
+- p[0] = args.getClass();
+- Method m = c.getMethod("main", p);
+- m.invoke(c, new Object[] { args });
+- }
+-
+- public static Object callMethod1(Object target, String methodN,
+- Object param1, String typeParam1, ClassLoader cl) throws Exception {
+- if (target == null || param1 == null) {
+- d("Assert: Illegal params " + target + " " + param1);
+- }
+- if (dbg > 0)
+- d("callMethod1 " + target.getClass().getName() + " "
+- + param1.getClass().getName() + " " + typeParam1);
+-
+- Class params[] = new Class[1];
+- if (typeParam1 == null)
+- params[0] = param1.getClass();
+- else
+- params[0] = cl.loadClass(typeParam1);
+- Method m = findMethod(target.getClass(), methodN, params);
+- if (m == null)
+- throw new NoSuchMethodException(target.getClass().getName() + " "
+- + methodN);
+- return m.invoke(target, new Object[] { param1 });
+- }
+-
+- public static Object callMethod0(Object target, String methodN)
+- throws Exception {
+- if (target == null) {
+- d("Assert: Illegal params " + target);
+- return null;
+- }
+- if (dbg > 0)
+- d("callMethod0 " + target.getClass().getName() + "." + methodN);
+-
+- Class params[] = new Class[0];
+- Method m = findMethod(target.getClass(), methodN, params);
+- if (m == null)
+- throw new NoSuchMethodException(target.getClass().getName() + " "
+- + methodN);
+- return m.invoke(target, emptyArray);
+- }
+-
+- static Object[] emptyArray = new Object[] {};
+-
+- public static Object callMethodN(Object target, String methodN,
+- Object params[], Class typeParams[]) throws Exception {
+- Method m = null;
+- m = findMethod(target.getClass(), methodN, typeParams);
+- if (m == null) {
+- d("Can't find method " + methodN + " in " + target + " CLASS "
+- + target.getClass());
+- return null;
+- }
+- Object o = m.invoke(target, params);
+-
+- if (dbg > 0) {
+- // debug
+- StringBuffer sb = new StringBuffer();
+- sb.append("" + target.getClass().getName() + "." + methodN + "( ");
+- for (int i = 0; i < params.length; i++) {
+- if (i > 0)
+- sb.append(", ");
+- sb.append(params[i]);
+- }
+- sb.append(")");
+- d(sb.toString());
+- }
+- return o;
+- }
+-
+- public static Object convert(String object, Class paramType) {
+- Object result = null;
+- if ("java.lang.String".equals(paramType.getName())) {
+- result = object;
+- } else if ("java.lang.Integer".equals(paramType.getName())
+- || "int".equals(paramType.getName())) {
+- try {
+- result = new Integer(object);
+- } catch (NumberFormatException ex) {
+- }
+- // Try a setFoo ( boolean )
+- } else if ("java.lang.Boolean".equals(paramType.getName())
+- || "boolean".equals(paramType.getName())) {
+- result = new Boolean(object);
+-
+- // Try a setFoo ( InetAddress )
+- } else if ("java.net.InetAddress".equals(paramType
+- .getName())) {
+- try {
+- result = InetAddress.getByName(object);
+- } catch (UnknownHostException exc) {
+- d("Unable to resolve host name:" + object);
+- }
+-
+- // Unknown type
+- } else {
+- d("Unknown type " + paramType.getName());
+- }
+- if (result == null) {
+- throw new IllegalArgumentException("Can't convert argument: " + object);
+- }
+- return result;
+- }
+-
+- // -------------------- Get property --------------------
+- // This provides a layer of abstraction
+-
+- public static interface PropertySource {
+-
+- public String getProperty(String key);
+-
+- }
+-
+- public static interface AttributeHolder {
+-
+- public void setAttribute(String key, Object o);
+-
+- }
+-
+- // debug --------------------
+- static final int dbg = 0;
+-
+- static void d(String s) {
+- if (log.isDebugEnabled())
+- log.debug("IntrospectionUtils: " + s);
+- }
+-}
+Index: test/org/apache/catalina/tribes/demos/MapDemo.java
+===================================================================
+--- test/org/apache/catalina/tribes/demos/MapDemo.java (revision 590752)
++++ test/org/apache/catalina/tribes/demos/MapDemo.java (working copy)
+@@ -1,497 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.demos;
+-
+-import java.io.Serializable;
+-import java.util.Map;
+-
+-import java.awt.ComponentOrientation;
+-import java.awt.Dimension;
+-import java.awt.event.ActionEvent;
+-import java.awt.event.ActionListener;
+-import java.awt.event.MouseAdapter;
+-import java.awt.event.MouseEvent;
+-import javax.swing.BoxLayout;
+-import javax.swing.JButton;
+-import javax.swing.JFrame;
+-import javax.swing.JPanel;
+-import javax.swing.JScrollPane;
+-import javax.swing.JTable;
+-import javax.swing.JTextField;
+-import javax.swing.table.AbstractTableModel;
+-import javax.swing.table.TableModel;
+-
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.ChannelListener;
+-import org.apache.catalina.tribes.ManagedChannel;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.MembershipListener;
+-import org.apache.catalina.tribes.tipis.AbstractReplicatedMap;
+-import org.apache.catalina.tribes.tipis.LazyReplicatedMap;
+-import javax.swing.table.DefaultTableCellRenderer;
+-import java.awt.Color;
+-import java.awt.Component;
+-import javax.swing.table.TableColumn;
+-import org.apache.catalina.tribes.util.UUIDGenerator;
+-import org.apache.catalina.tribes.util.Arrays;
+-import java.util.Set;
+-import java.util.Random;
+-
+-/**
+- * <p>Title: </p>
+- *
+- * <p>Description: </p>
+- *
+- * <p>Company: </p>
+- *
+- * @author not attributable
+- * @version 1.0
+- */
+-public class MapDemo implements ChannelListener, MembershipListener{
+-
+- protected LazyReplicatedMap map;
+- protected SimpleTableDemo table;
+-
+- public MapDemo(Channel channel, String mapName ) {
+- map = new LazyReplicatedMap(null,channel,5000, mapName,null);
+- table = SimpleTableDemo.createAndShowGUI(map,channel.getLocalMember(false).getName());
+- channel.addChannelListener(this);
+- channel.addMembershipListener(this);
+-// for ( int i=0; i<1000; i++ ) {
+-// map.put("MyKey-"+i,"My String Value-"+i);
+-// }
+- this.messageReceived(null,null);
+- }
+-
+- public boolean accept(Serializable msg, Member source) {
+- table.dataModel.getValueAt(-1,-1);
+- return false;
+- }
+-
+- public void messageReceived(Serializable msg, Member source) {
+-
+- }
+-
+- public void memberAdded(Member member) {
+- }
+- public void memberDisappeared(Member member) {
+- table.dataModel.getValueAt(-1,-1);
+- }
+-
+- public static void usage() {
+- System.out.println("Tribes MapDemo.");
+- System.out.println("Usage:\n\t" +
+- "java MapDemo [channel options] mapName\n\t" +
+- "\tChannel options:" +
+- ChannelCreator.usage());
+- }
+-
+- public static void main(String[] args) throws Exception {
+- long start = System.currentTimeMillis();
+- ManagedChannel channel = (ManagedChannel) ChannelCreator.createChannel(args);
+- String mapName = "MapDemo";
+- if ( args.length > 0 && (!args[args.length-1].startsWith("-"))) {
+- mapName = args[args.length-1];
+- }
+- channel.start(channel.DEFAULT);
+- Runtime.getRuntime().addShutdownHook(new Shutdown(channel));
+- MapDemo demo = new MapDemo(channel,mapName);
+-
+- System.out.println("System test complete, time to start="+(System.currentTimeMillis()-start)+" ms. Sleeping to let threads finish.");
+- Thread.sleep(60 * 1000 * 60);
+- }
+-
+- public static class Shutdown
+- extends Thread {
+- ManagedChannel channel = null;
+- public Shutdown(ManagedChannel channel) {
+- this.channel = channel;
+- }
+-
+- public void run() {
+- System.out.println("Shutting down...");
+- SystemExit exit = new SystemExit(5000);
+- exit.setDaemon(true);
+- exit.start();
+- try {
+- channel.stop(channel.DEFAULT);
+-
+- } catch (Exception x) {
+- x.printStackTrace();
+- }
+- System.out.println("Channel stopped.");
+- }
+- }
+-
+- public static class SystemExit
+- extends Thread {
+- private long delay;
+- public SystemExit(long delay) {
+- this.delay = delay;
+- }
+-
+- public void run() {
+- try {
+- Thread.sleep(delay);
+- } catch (Exception x) {
+- x.printStackTrace();
+- }
+- System.exit(0);
+-
+- }
+- }
+-
+- public static class SimpleTableDemo
+- extends JPanel implements ActionListener{
+- private static int WIDTH = 550;
+-
+- private LazyReplicatedMap map;
+- private boolean DEBUG = false;
+- AbstractTableModel dataModel = new AbstractTableModel() {
+-
+-
+- String[] columnNames = {
+- "Rownum",
+- "Key",
+- "Value",
+- "Primary Node",
+- "Backup Node",
+- "isPrimary",
+- "isProxy",
+- "isBackup"};
+-
+- public int getColumnCount() { return columnNames.length; }
+-
+- public int getRowCount() {return map.sizeFull() +1; }
+-
+- public StringBuffer getMemberNames(Member[] members){
+- StringBuffer buf = new StringBuffer();
+- if ( members!=null ) {
+- for (int i=0;i<members.length; i++ ) {
+- buf.append(members[i].getName());
+- buf.append("; ");
+- }
+- }
+- return buf;
+- }
+-
+- public Object getValueAt(int row, int col) {
+- if ( row==-1 ) {
+- update();
+- return "";
+- }
+- if ( row == 0 ) return columnNames[col];
+- Object[] keys = map.keySetFull().toArray();
+- String key = (String)keys [row-1];
+- LazyReplicatedMap.MapEntry entry = map.getInternal(key);
+- switch (col) {
+- case 0: return String.valueOf(row);
+- case 1: return entry.getKey();
+- case 2: return entry.getValue();
+- case 3: return entry.getPrimary()!=null?entry.getPrimary().getName():"null";
+- case 4: return getMemberNames(entry.getBackupNodes());
+- case 5: return new Boolean(entry.isPrimary());
+- case 6: return new Boolean(entry.isProxy());
+- case 7: return new Boolean(entry.isBackup());
+- default: return "";
+- }
+-
+- }
+-
+- public void update() {
+- fireTableDataChanged();
+- }
+- };
+-
+- JTextField txtAddKey = new JTextField(20);
+- JTextField txtAddValue = new JTextField(20);
+- JTextField txtRemoveKey = new JTextField(20);
+- JTextField txtChangeKey = new JTextField(20);
+- JTextField txtChangeValue = new JTextField(20);
+-
+- JTable table = null;
+- public SimpleTableDemo(LazyReplicatedMap map) {
+- super();
+- this.map = map;
+-
+- this.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+-
+- //final JTable table = new JTable(data, columnNames);
+- table = new JTable(dataModel);
+-
+- table.setPreferredScrollableViewportSize(new Dimension(WIDTH, 150));
+- for ( int i=0; i<table.getColumnCount(); i++ ) {
+- TableColumn tm = table.getColumnModel().getColumn(i);
+- tm.setCellRenderer(new ColorRenderer());
+- }
+-
+-
+- if (DEBUG) {
+- table.addMouseListener(new MouseAdapter() {
+- public void mouseClicked(MouseEvent e) {
+- printDebugData(table);
+- }
+- });
+- }
+-
+- //setLayout(new GridLayout(5, 0));
+- setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+-
+- //Create the scroll pane and add the table to it.
+- JScrollPane scrollPane = new JScrollPane(table);
+-
+- //Add the scroll pane to this panel.
+- add(scrollPane);
+-
+- //create a add value button
+- JPanel addpanel = new JPanel();
+- addpanel.setPreferredSize(new Dimension(WIDTH,30));
+- addpanel.add(createButton("Add","add"));
+- addpanel.add(txtAddKey);
+- addpanel.add(txtAddValue);
+- addpanel.setMaximumSize(new Dimension(WIDTH,30));
+- add(addpanel);
+-
+- //create a remove value button
+- JPanel removepanel = new JPanel( );
+- removepanel.setPreferredSize(new Dimension(WIDTH,30));
+- removepanel.add(createButton("Remove","remove"));
+- removepanel.add(txtRemoveKey);
+- removepanel.setMaximumSize(new Dimension(WIDTH,30));
+- add(removepanel);
+-
+- //create a change value button
+- JPanel changepanel = new JPanel( );
+- changepanel.add(createButton("Change","change"));
+- changepanel.add(txtChangeKey);
+- changepanel.add(txtChangeValue);
+- changepanel.setPreferredSize(new Dimension(WIDTH,30));
+- changepanel.setMaximumSize(new Dimension(WIDTH,30));
+- add(changepanel);
+-
+-
+- //create sync button
+- JPanel syncpanel = new JPanel( );
+- syncpanel.add(createButton("Synchronize","sync"));
+- syncpanel.add(createButton("Replicate","replicate"));
+- syncpanel.add(createButton("Random","random"));
+- syncpanel.setPreferredSize(new Dimension(WIDTH,30));
+- syncpanel.setMaximumSize(new Dimension(WIDTH,30));
+- add(syncpanel);
+-
+-
+- }
+-
+- public JButton createButton(String text, String command) {
+- JButton button = new JButton(text);
+- button.setActionCommand(command);
+- button.addActionListener(this);
+- return button;
+- }
+-
+- public void actionPerformed(ActionEvent e) {
+- System.out.println(e.getActionCommand());
+- if ( "add".equals(e.getActionCommand()) ) {
+- System.out.println("Add key:"+txtAddKey.getText()+" value:"+txtAddValue.getText());
+- map.put(txtAddKey.getText(),new StringBuffer(txtAddValue.getText()));
+- }
+- if ( "change".equals(e.getActionCommand()) ) {
+- System.out.println("Change key:"+txtChangeKey.getText()+" value:"+txtChangeValue.getText());
+- StringBuffer buf = (StringBuffer)map.get(txtChangeKey.getText());
+- if ( buf!=null ) {
+- buf.delete(0,buf.length());
+- buf.append(txtChangeValue.getText());
+- map.replicate(txtChangeKey.getText(),true);
+- } else {
+- buf = new StringBuffer();
+- buf.append(txtChangeValue.getText());
+- map.put(txtChangeKey.getText(),buf);
+- }
+- }
+- if ( "remove".equals(e.getActionCommand()) ) {
+- System.out.println("Remove key:"+txtRemoveKey.getText());
+- map.remove(txtRemoveKey.getText());
+- }
+- if ( "sync".equals(e.getActionCommand()) ) {
+- System.out.println("Syncing from another node.");
+- map.transferState();
+- }
+- if ( "random".equals(e.getActionCommand()) ) {
+- Thread t = new Thread() {
+- public void run() {
+- for (int i = 0; i < 5; i++) {
+- String key = random(5,0,0,true,true,null);
+- map.put(key, new StringBuffer(key));
+- dataModel.fireTableDataChanged();
+- table.paint(table.getGraphics());
+- try {
+- Thread.sleep(500);
+- } catch (InterruptedException x) {
+- Thread.currentThread().interrupted();
+- }
+- }
+- }
+- };
+- t.start();
+- }
+-
+- if ( "replicate".equals(e.getActionCommand()) ) {
+- System.out.println("Replicating out to the other nodes.");
+- map.replicate(true);
+- }
+- dataModel.getValueAt(-1,-1);
+- }
+-
+- public static Random random = new Random(System.currentTimeMillis());
+- public static String random(int count, int start, int end, boolean letters, boolean numbers,
+- char[] chars ) {
+- if (count == 0) {
+- return "";
+- } else if (count < 0) {
+- throw new IllegalArgumentException("Requested random string length " + count + " is less than 0.");
+- }
+- if ((start == 0) && (end == 0)) {
+- end = 'z' + 1;
+- start = ' ';
+- if (!letters && !numbers) {
+- start = 0;
+- end = Integer.MAX_VALUE;
+- }
+- }
+-
+- char[] buffer = new char[count];
+- int gap = end - start;
+-
+- while (count-- != 0) {
+- char ch;
+- if (chars == null) {
+- ch = (char) (random.nextInt(gap) + start);
+- } else {
+- ch = chars[random.nextInt(gap) + start];
+- }
+- if ((letters && Character.isLetter(ch))
+- || (numbers && Character.isDigit(ch))
+- || (!letters && !numbers))
+- {
+- if(ch >= 56320 && ch <= 57343) {
+- if(count == 0) {
+- count++;
+- } else {
+- // low surrogate, insert high surrogate after putting it in
+- buffer[count] = ch;
+- count--;
+- buffer[count] = (char) (55296 + random.nextInt(128));
+- }
+- } else if(ch >= 55296 && ch <= 56191) {
+- if(count == 0) {
+- count++;
+- } else {
+- // high surrogate, insert low surrogate before putting it in
+- buffer[count] = (char) (56320 + random.nextInt(128));
+- count--;
+- buffer[count] = ch;
+- }
+- } else if(ch >= 56192 && ch <= 56319) {
+- // private high surrogate, no effing clue, so skip it
+- count++;
+- } else {
+- buffer[count] = ch;
+- }
+- } else {
+- count++;
+- }
+- }
+- return new String(buffer);
+- }
+-
+- private void printDebugData(JTable table) {
+- int numRows = table.getRowCount();
+- int numCols = table.getColumnCount();
+- javax.swing.table.TableModel model = table.getModel();
+-
+- System.out.println("Value of data: ");
+- for (int i = 0; i < numRows; i++) {
+- System.out.print(" row " + i + ":");
+- for (int j = 0; j < numCols; j++) {
+- System.out.print(" " + model.getValueAt(i, j));
+- }
+- System.out.println();
+- }
+- System.out.println("--------------------------");
+- }
+-
+- /**
+- * Create the GUI and show it. For thread safety,
+- * this method should be invoked from the
+- * event-dispatching thread.
+- */
+- public static SimpleTableDemo createAndShowGUI(LazyReplicatedMap map, String title) {
+- //Make sure we have nice window decorations.
+- JFrame.setDefaultLookAndFeelDecorated(true);
+-
+- //Create and set up the window.
+- JFrame frame = new JFrame("SimpleTableDemo - "+title);
+- frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+-
+- //Create and set up the content pane.
+- SimpleTableDemo newContentPane = new SimpleTableDemo(map);
+- newContentPane.setOpaque(true); //content panes must be opaque
+- frame.setContentPane(newContentPane);
+-
+- //Display the window.
+- frame.setSize(450,250);
+- newContentPane.setSize(450,300);
+- frame.pack();
+- frame.setVisible(true);
+- return newContentPane;
+- }
+- }
+-
+- static class ColorRenderer extends DefaultTableCellRenderer {
+-
+- public ColorRenderer() {
+- super();
+- }
+-
+- public Component getTableCellRendererComponent
+- (JTable table, Object value, boolean isSelected,
+- boolean hasFocus, int row, int column) {
+- Component cell = super.getTableCellRendererComponent
+- (table, value, isSelected, hasFocus, row, column);
+- cell.setBackground(Color.WHITE);
+- if ( row > 0 ) {
+- Color color = null;
+- boolean primary = ( (Boolean) table.getValueAt(row, 5)).booleanValue();
+- boolean proxy = ( (Boolean) table.getValueAt(row, 6)).booleanValue();
+- boolean backup = ( (Boolean) table.getValueAt(row, 7)).booleanValue();
+- if (primary) color = Color.GREEN;
+- else if (proxy) color = Color.RED;
+- else if (backup) color = Color.BLUE;
+- if ( color != null ) cell.setBackground(color);
+- }
+-// System.out.println("Row:"+row+" Column:"+column+" Color:"+cell.getBackground());
+-// cell.setBackground(bkgndColor);
+-// cell.setForeground(fgndColor);
+-
+- return cell;
+- }
+-
+-
+- }
+-
+-
+-}
+Index: test/org/apache/catalina/tribes/demos/EchoRpcTest.java
+===================================================================
+--- test/org/apache/catalina/tribes/demos/EchoRpcTest.java (revision 590752)
++++ test/org/apache/catalina/tribes/demos/EchoRpcTest.java (working copy)
+@@ -1,218 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.demos;
+-
+-import java.io.Serializable;
+-
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.group.RpcCallback;
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.ManagedChannel;
+-import org.apache.catalina.tribes.group.RpcChannel;
+-import org.apache.catalina.tribes.group.Response;
+-
+-
+-/**
+- * <p>Title: </p>
+- *
+- * <p>Description: </p>
+- *
+- * <p>Company: </p>
+- *
+- * @author not attributable
+- * @version 1.0
+- */
+-public class EchoRpcTest implements RpcCallback, Runnable {
+-
+- Channel channel;
+- int count;
+- String message;
+- long pause;
+- RpcChannel rpc;
+- int options;
+- long timeout;
+- String name;
+-
+- public EchoRpcTest(Channel channel, String name, int count, String message, long pause, int options, long timeout) {
+- this.channel = channel;
+- this.count = count;
+- this.message = message;
+- this.pause = pause;
+- this.options = options;
+- this.rpc = new RpcChannel(name.getBytes(),channel,this);
+- this.timeout = timeout;
+- this.name = name;
+- }
+-
+- /**
+- * If the reply has already been sent to the requesting thread, the rpc
+- * callback can handle any data that comes in after the fact.
+- *
+- * @param msg Serializable
+- * @param sender Member
+- * @todo Implement this org.apache.catalina.tribes.tipis.RpcCallback
+- * method
+- */
+- public void leftOver(Serializable msg, Member sender) {
+- System.out.println("Received a left over message from ["+sender.getName()+"] with data ["+msg+"]");
+- }
+-
+- /**
+- *
+- * @param msg Serializable
+- * @param sender Member
+- * @return Serializable - null if no reply should be sent
+- * @todo Implement this org.apache.catalina.tribes.tipis.RpcCallback
+- * method
+- */
+- public Serializable replyRequest(Serializable msg, Member sender) {
+- System.out.println("Received a reply request message from ["+sender.getName()+"] with data ["+msg+"]");
+- return "Reply("+name+"):"+msg;
+- }
+-
+- public void run() {
+- long counter = 0;
+- while (counter<count) {
+- String msg = message + " cnt="+(++counter);
+- try {
+- System.out.println("Sending ["+msg+"]");
+- long start = System.currentTimeMillis();
+- Response[] resp = rpc.send(channel.getMembers(),(Serializable)msg,options,Channel.SEND_OPTIONS_DEFAULT,timeout);
+- System.out.println("Send of ["+msg+"] completed. Nr of responses="+resp.length+" Time:"+(System.currentTimeMillis()-start)+" ms.");
+- for ( int i=0; i<resp.length; i++ ) {
+- System.out.println("Received a response message from ["+resp[i].getSource().getName()+"] with data ["+resp[i].getMessage()+"]");
+- }
+- Thread.sleep(pause);
+- }catch(Exception x){}
+- }
+- }
+-
+- public static void usage() {
+- System.out.println("Tribes RPC tester.");
+- System.out.println("Usage:\n\t"+
+- "java EchoRpcTest [options]\n\t"+
+- "Options:\n\t\t"+
+- "[-mode all|first|majority] \n\t\t"+
+- "[-debug] \n\t\t"+
+- "[-count messagecount] \n\t\t"+
+- "[-timeout timeoutinms] \n\t\t"+
+- "[-stats statinterval] \n\t\t"+
+- "[-pause nrofsecondstopausebetweensends] \n\t\t"+
+- "[-message message] \n\t\t"+
+- "[-name rpcname] \n\t\t"+
+- "[-break (halts execution on exception)]\n"+
+- "\tChannel options:"+
+- ChannelCreator.usage()+"\n\n"+
+- "Example:\n\t"+
+- "java EchoRpcTest -port 4004\n\t"+
+- "java EchoRpcTest -bind 192.168.0.45 -port 4005\n\t"+
+- "java EchoRpcTest -bind 192.168.0.45 -port 4005 -mbind 192.168.0.45 -count 100 -stats 10\n");
+- }
+-
+- public static void main(String[] args) throws Exception {
+- boolean send = true;
+- boolean debug = false;
+- long pause = 3000;
+- int count = 1000000;
+- int stats = 10000;
+- String name = "EchoRpcId";
+- boolean breakOnEx = false;
+- int threads = 1;
+- int options = RpcChannel.ALL_REPLY;
+- long timeout = 15000;
+- String message = "EchoRpcMessage";
+- if ( args.length == 0 ) {
+- args = new String[] {"-help"};
+- }
+- for (int i = 0; i < args.length; i++) {
+- if ("-threads".equals(args[i])) {
+- threads = Integer.parseInt(args[++i]);
+- } else if ("-count".equals(args[i])) {
+- count = Integer.parseInt(args[++i]);
+- System.out.println("Sending "+count+" messages.");
+- } else if ("-pause".equals(args[i])) {
+- pause = Long.parseLong(args[++i])*1000;
+- } else if ("-break".equals(args[i])) {
+- breakOnEx = true;
+- } else if ("-stats".equals(args[i])) {
+- stats = Integer.parseInt(args[++i]);
+- System.out.println("Stats every "+stats+" message");
+- } else if ("-timeout".equals(args[i])) {
+- timeout = Long.parseLong(args[++i]);
+- } else if ("-message".equals(args[i])) {
+- message = args[++i];
+- } else if ("-name".equals(args[i])) {
+- name = args[++i];
+- } else if ("-mode".equals(args[i])) {
+- if ( "all".equals(args[++i]) ) options = RpcChannel.ALL_REPLY;
+- else if ( "first".equals(args[i]) ) options = RpcChannel.FIRST_REPLY;
+- else if ( "majority".equals(args[i]) ) options = RpcChannel.MAJORITY_REPLY;
+- } else if ("-debug".equals(args[i])) {
+- debug = true;
+- } else if ("-help".equals(args[i]))
+- {
+- usage();
+- System.exit(1);
+- }
+- }
+-
+-
+- ManagedChannel channel = (ManagedChannel)ChannelCreator.createChannel(args);
+- EchoRpcTest test = new EchoRpcTest(channel,name,count,message,pause,options,timeout);
+- channel.start(channel.DEFAULT);
+- Runtime.getRuntime().addShutdownHook(new Shutdown(channel));
+- test.run();
+-
+- System.out.println("System test complete, sleeping to let threads finish.");
+- Thread.sleep(60*1000*60);
+- }
+-
+- public static class Shutdown extends Thread {
+- ManagedChannel channel = null;
+- public Shutdown(ManagedChannel channel) {
+- this.channel = channel;
+- }
+-
+- public void run() {
+- System.out.println("Shutting down...");
+- SystemExit exit = new SystemExit(5000);
+- exit.setDaemon(true);
+- exit.start();
+- try {
+- channel.stop(channel.DEFAULT);
+-
+- }catch ( Exception x ) {
+- x.printStackTrace();
+- }
+- System.out.println("Channel stopped.");
+- }
+- }
+- public static class SystemExit extends Thread {
+- private long delay;
+- public SystemExit(long delay) {
+- this.delay = delay;
+- }
+- public void run () {
+- try {
+- Thread.sleep(delay);
+- }catch ( Exception x ) {
+- x.printStackTrace();
+- }
+- System.exit(0);
+-
+- }
+- }}
+\ No newline at end of file
+Index: test/org/apache/catalina/tribes/demos/ChannelCreator.java
+===================================================================
+--- test/org/apache/catalina/tribes/demos/ChannelCreator.java (revision 590752)
++++ test/org/apache/catalina/tribes/demos/ChannelCreator.java (working copy)
+@@ -1,254 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.demos;
+-
+-import java.util.Iterator;
+-import java.util.Properties;
+-
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.ManagedChannel;
+-import org.apache.catalina.tribes.group.GroupChannel;
+-import org.apache.catalina.tribes.group.interceptors.FragmentationInterceptor;
+-import org.apache.catalina.tribes.group.interceptors.GzipInterceptor;
+-import org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor;
+-import org.apache.catalina.tribes.group.interceptors.OrderInterceptor;
+-import org.apache.catalina.tribes.membership.McastService;
+-import org.apache.catalina.tribes.transport.MultiPointSender;
+-import org.apache.catalina.tribes.transport.ReceiverBase;
+-import org.apache.catalina.tribes.transport.ReplicationTransmitter;
+-import org.apache.catalina.tribes.group.interceptors.ThroughputInterceptor;
+-import org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor;
+-import org.apache.catalina.tribes.group.interceptors.TcpFailureDetector;
+-import org.apache.catalina.tribes.group.interceptors.DomainFilterInterceptor;
+-import java.util.ArrayList;
+-import org.apache.catalina.tribes.membership.MemberImpl;
+-import org.apache.catalina.tribes.group.interceptors.StaticMembershipInterceptor;
+-import org.apache.catalina.tribes.Member;
+-
+-/**
+- * <p>Title: </p>
+- *
+- * <p>Description: </p>
+- *
+- *
+- * <p>Company: </p>
+- *
+- * @author fhanik
+- * @version 1.0
+- */
+-public class ChannelCreator {
+-
+-
+- public static StringBuffer usage() {
+- StringBuffer buf = new StringBuffer();
+- buf.append("\n\t\t[-bind tcpbindaddress]")
+- .append("\n\t\t[-tcpselto tcpselectortimeout]")
+- .append("\n\t\t[-tcpthreads tcpthreadcount]")
+- .append("\n\t\t[-port tcplistenport]")
+- .append("\n\t\t[-autobind tcpbindtryrange]")
+- .append("\n\t\t[-ackto acktimeout]")
+- .append("\n\t\t[-receiver org.apache.catalina.tribes.transport.nio.NioReceiver|org.apache.catalina.tribes.transport.bio.BioReceiver|]")
+- .append("\n\t\t[-transport org.apache.catalina.tribes.transport.nio.PooledParallelSender|org.apache.catalina.tribes.transport.bio.PooledMultiSender]")
+- .append("\n\t\t[-transport.xxx transport specific property]")
+- .append("\n\t\t[-maddr multicastaddr]")
+- .append("\n\t\t[-mport multicastport]")
+- .append("\n\t\t[-mbind multicastbindaddr]")
+- .append("\n\t\t[-mfreq multicastfrequency]")
+- .append("\n\t\t[-mdrop multicastdroptime]")
+- .append("\n\t\t[-gzip]")
+- .append("\n\t\t[-static hostname:port (-static localhost:9999 -static 127.0.0.1:8888 can be repeated)]")
+- .append("\n\t\t[-order]")
+- .append("\n\t\t[-ordersize maxorderqueuesize]")
+- .append("\n\t\t[-frag]")
+- .append("\n\t\t[-fragsize maxmsgsize]")
+- .append("\n\t\t[-throughput]")
+- .append("\n\t\t[-failuredetect]")
+- .append("\n\t\t[-async]")
+- .append("\n\t\t[-asyncsize maxqueuesizeinkilobytes]");
+- return buf;
+-
+- }
+-
+- public static Channel createChannel(String[] args) throws Exception {
+- String bind = "auto";
+- int port = 4001;
+- String mbind = null;
+- boolean gzip = false;
+- int tcpseltimeout = 5000;
+- int tcpthreadcount = 4;
+- int acktimeout = 15000;
+- String mcastaddr = "228.0.0.5";
+- int mcastport = 45565;
+- long mcastfreq = 500;
+- long mcastdrop = 2000;
+- boolean order = false;
+- int ordersize = Integer.MAX_VALUE;
+- boolean frag = false;
+- int fragsize = 1024;
+- int autoBind = 10;
+- ArrayList staticMembers = new ArrayList();
+- Properties transportProperties = new Properties();
+- String transport = "org.apache.catalina.tribes.transport.nio.PooledParallelSender";
+- String receiver = "org.apache.catalina.tribes.transport.nio.NioReceiver";
+- boolean async = false;
+- int asyncsize = 1024*1024*50; //50MB
+- boolean throughput = false;
+- boolean failuredetect = false;
+-
+- for (int i = 0; i < args.length; i++) {
+- if ("-bind".equals(args[i])) {
+- bind = args[++i];
+- } else if ("-port".equals(args[i])) {
+- port = Integer.parseInt(args[++i]);
+- } else if ("-autobind".equals(args[i])) {
+- autoBind = Integer.parseInt(args[++i]);
+- } else if ("-tcpselto".equals(args[i])) {
+- tcpseltimeout = Integer.parseInt(args[++i]);
+- } else if ("-tcpthreads".equals(args[i])) {
+- tcpthreadcount = Integer.parseInt(args[++i]);
+- } else if ("-gzip".equals(args[i])) {
+- gzip = true;
+- } else if ("-async".equals(args[i])) {
+- async = true;
+- } else if ("-failuredetect".equals(args[i])) {
+- failuredetect = true;
+- } else if ("-asyncsize".equals(args[i])) {
+- asyncsize = Integer.parseInt(args[++i]);
+- System.out.println("Setting MessageDispatchInterceptor.maxQueueSize="+asyncsize);
+- } else if ("-static".equals(args[i])) {
+- String d = args[++i];
+- String h = d.substring(0,d.indexOf(":"));
+- String p = d.substring(h.length()+1);
+- MemberImpl m = new MemberImpl(h,Integer.parseInt(p),2000);
+- staticMembers.add(m);
+- } else if ("-throughput".equals(args[i])) {
+- throughput = true;
+- } else if ("-order".equals(args[i])) {
+- order = true;
+- } else if ("-ordersize".equals(args[i])) {
+- ordersize = Integer.parseInt(args[++i]);
+- System.out.println("Setting OrderInterceptor.maxQueue="+ordersize);
+- } else if ("-frag".equals(args[i])) {
+- frag = true;
+- } else if ("-fragsize".equals(args[i])) {
+- fragsize = Integer.parseInt(args[++i]);
+- System.out.println("Setting FragmentationInterceptor.maxSize="+fragsize);
+- } else if ("-ackto".equals(args[i])) {
+- acktimeout = Integer.parseInt(args[++i]);
+- } else if ("-transport".equals(args[i])) {
+- transport = args[++i];
+- } else if (args[i]!=null && args[i].startsWith("transport.")) {
+- String key = args[i];
+- String val = args[++i];
+- transportProperties.setProperty(key,val);
+- } else if ("-receiver".equals(args[i])) {
+- receiver = args[++i];
+- } else if ("-maddr".equals(args[i])) {
+- mcastaddr = args[++i];
+- } else if ("-mport".equals(args[i])) {
+- mcastport = Integer.parseInt(args[++i]);
+- } else if ("-mfreq".equals(args[i])) {
+- mcastfreq = Long.parseLong(args[++i]);
+- } else if ("-mdrop".equals(args[i])) {
+- mcastdrop = Long.parseLong(args[++i]);
+- } else if ("-mbind".equals(args[i])) {
+- mbind = args[++i];
+- }
+- }
+-
+- System.out.println("Creating receiver class="+receiver);
+- Class cl = Class.forName(receiver,true,ChannelCreator.class.getClassLoader());
+- ReceiverBase rx = (ReceiverBase)cl.newInstance();
+- rx.setTcpListenAddress(bind);
+- rx.setTcpListenPort(port);
+- rx.setTcpSelectorTimeout(tcpseltimeout);
+- rx.setTcpThreadCount(tcpthreadcount);
+- rx.getBind();
+- rx.setRxBufSize(43800);
+- rx.setTxBufSize(25188);
+- rx.setAutoBind(autoBind);
+-
+-
+- ReplicationTransmitter ps = new ReplicationTransmitter();
+- System.out.println("Creating transport class="+transport);
+- MultiPointSender sender = (MultiPointSender)Class.forName(transport,true,ChannelCreator.class.getClassLoader()).newInstance();
+- sender.setTimeout(acktimeout);
+- sender.setMaxRetryAttempts(2);
+- sender.setRxBufSize(43800);
+- sender.setTxBufSize(25188);
+-
+- Iterator i = transportProperties.keySet().iterator();
+- while ( i.hasNext() ) {
+- String key = (String)i.next();
+- IntrospectionUtils.setProperty(sender,key,transportProperties.getProperty(key));
+- }
+- ps.setTransport(sender);
+-
+- McastService service = new McastService();
+- service.setMcastAddr(mcastaddr);
+- if (mbind != null) service.setMcastBindAddress(mbind);
+- service.setMcastFrequency(mcastfreq);
+- service.setMcastDropTime(mcastdrop);
+- service.setMcastPort(mcastport);
+-
+- ManagedChannel channel = new GroupChannel();
+- channel.setChannelReceiver(rx);
+- channel.setChannelSender(ps);
+- channel.setMembershipService(service);
+-
+- if ( throughput ) channel.addInterceptor(new ThroughputInterceptor());
+- if (gzip) channel.addInterceptor(new GzipInterceptor());
+- if ( frag ) {
+- FragmentationInterceptor fi = new FragmentationInterceptor();
+- fi.setMaxSize(fragsize);
+- channel.addInterceptor(fi);
+- }
+- if (order) {
+- OrderInterceptor oi = new OrderInterceptor();
+- oi.setMaxQueue(ordersize);
+- channel.addInterceptor(oi);
+- }
+-
+- if ( async ) {
+- MessageDispatchInterceptor mi = new MessageDispatch15Interceptor();
+- mi.setMaxQueueSize(asyncsize);
+- channel.addInterceptor(mi);
+- System.out.println("Added MessageDispatchInterceptor");
+- }
+-
+- if ( failuredetect ) {
+- TcpFailureDetector tcpfi = new TcpFailureDetector();
+- channel.addInterceptor(tcpfi);
+- }
+- if ( staticMembers.size() > 0 ) {
+- StaticMembershipInterceptor smi = new StaticMembershipInterceptor();
+- for (int x=0; x<staticMembers.size(); x++ ) {
+- smi.addStaticMember((Member)staticMembers.get(x));
+- }
+- channel.addInterceptor(smi);
+- }
+-
+-
+- byte[] domain = new byte[] {1,2,3,4,5,6,7,8,9,0};
+- ((McastService)channel.getMembershipService()).setDomain(domain);
+- DomainFilterInterceptor filter = new DomainFilterInterceptor();
+- filter.setDomain(domain);
+- channel.addInterceptor(filter);
+- return channel;
+- }
+-
+-}
+\ No newline at end of file
+Index: test/org/apache/catalina/tribes/demos/CoordinationDemo.java
+===================================================================
+--- test/org/apache/catalina/tribes/demos/CoordinationDemo.java (revision 590752)
++++ test/org/apache/catalina/tribes/demos/CoordinationDemo.java (working copy)
+@@ -1,364 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.demos;
+-
+-import java.io.BufferedReader;
+-import java.io.IOException;
+-import java.io.InputStreamReader;
+-import java.util.StringTokenizer;
+-
+-import org.apache.catalina.tribes.ChannelInterceptor;
+-import org.apache.catalina.tribes.ChannelInterceptor.InterceptorEvent;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.group.GroupChannel;
+-import org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor;
+-import org.apache.catalina.tribes.group.interceptors.NonBlockingCoordinator;
+-import org.apache.catalina.tribes.group.interceptors.TcpFailureDetector;
+-import org.apache.catalina.tribes.transport.ReceiverBase;
+-import org.apache.catalina.tribes.util.Arrays;
+-
+-
+-
+-public class CoordinationDemo {
+- static int CHANNEL_COUNT = 5;
+- static int SCREEN_WIDTH = 120;
+- static long SLEEP_TIME = 10;
+- static int CLEAR_SCREEN = 30;
+- static boolean MULTI_THREAD = false;
+- static boolean[] VIEW_EVENTS = new boolean[255];
+- StringBuffer statusLine = new StringBuffer();
+- Status[] status = null;
+- BufferedReader reader = null;
+- /**
+- * Construct and show the application.
+- */
+- public CoordinationDemo() {
+- }
+-
+- public void init() {
+- reader = new BufferedReader(new InputStreamReader(System.in));
+- status = new Status[CHANNEL_COUNT];
+- }
+-
+-
+- public void clearScreen() {
+- StringBuffer buf = new StringBuffer(700);
+- for (int i=0; i<CLEAR_SCREEN; i++ ) buf.append("\n");
+- System.out.println(buf);
+- }
+-
+- public void printMenuOptions() {
+- System.out.println("Commands:");
+- System.out.println("\tstart [member id]");
+- System.out.println("\tstop [member id]");
+- System.out.println("\tprint (refresh)");
+- System.out.println("\tquit");
+- System.out.print("Enter command:");
+- }
+-
+- public synchronized void printScreen() {
+- clearScreen();
+- System.out.println(" ###."+getHeader());
+- for ( int i=0; i<status.length; i++ ) {
+- System.out.print(leftfill(String.valueOf(i+1)+".",5," "));
+- if ( status[i] != null ) System.out.print(status[i].getStatusLine());
+- }
+- System.out.println("\n\n");
+- System.out.println("Overall status:"+statusLine);
+- printMenuOptions();
+-
+- }
+-
+- public String getHeader() {
+- //member - 30
+- //running- 10
+- //coord - 30
+- //view-id - 24
+- //view count - 8
+-
+- StringBuffer buf = new StringBuffer();
+- buf.append(leftfill("Member",30," "));
+- buf.append(leftfill("Running",10," "));
+- buf.append(leftfill("Coord",30," "));
+- buf.append(leftfill("View-id(short)",24," "));
+- buf.append(leftfill("Count",8," "));
+- buf.append("\n");
+-
+- buf.append(rightfill("==="+new java.sql.Timestamp(System.currentTimeMillis()).toString(),SCREEN_WIDTH,"="));
+- buf.append("\n");
+- return buf.toString();
+- }
+-
+- public String[] tokenize(String line) {
+- StringTokenizer tz = new StringTokenizer(line," ");
+- String[] result = new String[tz.countTokens()];
+- for (int i=0; i<result.length; i++ ) result[i] = tz.nextToken();
+- return result;
+- }
+-
+- public void waitForInput() throws IOException {
+- for ( int i=0; i<status.length; i++ ) status[i] = new Status(this);
+- printScreen();
+- String l = reader.readLine();
+- String[] args = tokenize(l);
+- while ( args.length >= 1 && (!"quit".equalsIgnoreCase(args[0]))) {
+- if ("start".equalsIgnoreCase(args[0])) {
+- cmdStart(args);
+- } else if ("stop".equalsIgnoreCase(args[0])) {
+- cmdStop(args);
+-
+- }
+- printScreen();
+- l = reader.readLine();
+- args = tokenize(l);
+- }
+- for ( int i=0; i<status.length; i++ ) status[i].stop();
+- }
+-
+- private void cmdStop(String[] args) {
+- if ( args.length == 1 ) {
+- setSystemStatus("System shutting down...");
+- Thread[] t = new Thread[CHANNEL_COUNT];
+- for (int i = 0; i < status.length; i++) {
+- final int j = i;
+- t[j] = new Thread() {
+- public void run() {
+- status[j].stop();
+- }
+- };
+- }
+- for (int i = 0; i < status.length; i++) if (MULTI_THREAD ) t[i].start(); else t[i].run();
+- setSystemStatus("System stopped.");
+- } else {
+- int index = -1;
+- try { index = Integer.parseInt(args[1])-1;}catch ( Exception x ) {setSystemStatus("Invalid index:"+args[1]);}
+- if ( index >= 0 ) {
+- setSystemStatus("Stopping member:"+(index+1));
+- status[index].stop();
+- setSystemStatus("Member stopped:"+(index+1));
+- }
+- }
+- }
+-
+- private void cmdStart(String[] args) {
+- if ( args.length == 1 ) {
+- setSystemStatus("System starting up...");
+- Thread[] t = new Thread[CHANNEL_COUNT];
+- for (int i = 0; i < status.length; i++) {
+- final int j = i;
+- t[j] = new Thread() {
+- public void run() {
+- status[j].start();
+- }
+- };
+- }
+- for (int i = 0; i < status.length; i++) if (MULTI_THREAD ) t[i].start(); else t[i].run();
+- setSystemStatus("System started.");
+- } else {
+- int index = -1;
+- try { index = Integer.parseInt(args[1])-1;}catch ( Exception x ) {setSystemStatus("Invalid index:"+args[1]);}
+- if ( index >= 0 ) {
+- setSystemStatus("Starting member:"+(index+1));
+- status[index].start();
+- setSystemStatus("Member started:"+(index+1));
+- }
+- }
+- }
+-
+- public void setSystemStatus(String status) {
+- statusLine.delete(0,statusLine.length());
+- statusLine.append(status);
+- }
+-
+-
+-
+- public static void setEvents(String events) {
+- java.util.Arrays.fill(VIEW_EVENTS,false);
+- StringTokenizer t = new StringTokenizer(events,",");
+- while (t.hasMoreTokens() ) {
+- int idx = Integer.parseInt(t.nextToken());
+- VIEW_EVENTS[idx] = true;
+- }
+- }
+-
+- public static void run(String[] args,CoordinationDemo demo) throws Exception {
+- usage();
+- java.util.Arrays.fill(VIEW_EVENTS,true);
+-
+- for (int i=0; i<args.length; i++ ) {
+- if ( "-c".equals(args[i]) )
+- CHANNEL_COUNT = Integer.parseInt(args[++i]);
+- else if ( "-t".equals(args[i]) )
+- MULTI_THREAD = Boolean.parseBoolean(args[++i]);
+- else if ( "-s".equals(args[i]) )
+- SLEEP_TIME = Long.parseLong(args[++i]);
+- else if ( "-sc".equals(args[i]) )
+- CLEAR_SCREEN = Integer.parseInt(args[++i]);
+- else if ( "-p".equals(args[i]) )
+- setEvents(args[++i]);
+- else if ( "-h".equals(args[i]) ) System.exit(0);
+- }
+- demo.init();
+- demo.waitForInput();
+- }
+-
+- private static void usage() {
+- System.out.println("Usage:");
+- System.out.println("\tjava org.apache.catalina.tribes.demos.CoordinationDemo -c channel-count(int) -t multi-thread(true|false) -s sleep-time(ms) -sc clear-screen(int) -p view_events_csv(1,2,5,7)");
+- System.out.println("Example:");
+- System.out.println("\tjava o.a.c.t.d.CoordinationDemo -> starts demo single threaded start/stop with 5 channels");
+- System.out.println("\tjava o.a.c.t.d.CoordinationDemo -c 10 -> starts demo single threaded start/stop with 10 channels");
+- System.out.println("\tjava o.a.c.t.d.CoordinationDemo -c 7 -t true -s 1000 -sc 50-> starts demo multi threaded start/stop with 7 channels and 1 second sleep time between events and 50 lines to clear screen");
+- System.out.println("\tjava o.a.c.t.d.CoordinationDemo -t true -p 12 -> starts demo multi threaded start/stop with 5 channels and only prints the EVT_CONF_RX event");
+- System.out.println();
+- }
+- public static void main(String[] args) throws Exception {
+- CoordinationDemo demo = new CoordinationDemo();
+- run(args,demo);
+- }
+-
+- public static String leftfill(String value, int length, String ch) {
+- return fill(value,length,ch,true);
+- }
+-
+- public static String rightfill(String value, int length, String ch) {
+- return fill(value,length,ch,false);
+- }
+-
+- public static String fill(String value, int length, String ch, boolean left) {
+- StringBuffer buf = new StringBuffer();
+- if ( !left ) buf.append(value.trim());
+- for (int i=value.trim().length(); i<length; i++ ) buf.append(ch);
+- if ( left ) buf.append(value.trim());
+- return buf.toString();
+- }
+-
+-
+- public static class Status {
+- public CoordinationDemo parent;
+- public GroupChannel channel;
+- NonBlockingCoordinator interceptor = null;
+- public String status;
+- public Exception error;
+- public String startstatus = "new";
+-
+- public Status(CoordinationDemo parent) {
+- this.parent = parent;
+- }
+-
+- public String getStatusLine() {
+- //member - 30
+- //running- 10
+- //coord - 30
+- //view-id - 24
+- //view count - 8
+- StringBuffer buf = new StringBuffer();
+- String local = "";
+- String coord = "";
+- String viewId = "";
+- String count = "0";
+- if ( channel != null ) {
+- Member lm = channel.getLocalMember(false);
+- local = lm!=null?lm.getName():"";
+- coord = interceptor!=null && interceptor.getCoordinator()!=null?interceptor.getCoordinator().getName():"";
+- viewId = getByteString(interceptor.getViewId()!=null?interceptor.getViewId().getBytes():new byte[0]);
+- count = String.valueOf(interceptor.getView().length);
+- }
+- buf.append(leftfill(local,30," "));
+- buf.append(leftfill(startstatus, 10, " "));
+- buf.append(leftfill(coord, 30, " "));
+- buf.append(leftfill(viewId, 24, " "));
+- buf.append(leftfill(count, 8, " "));
+- buf.append("\n");
+- buf.append("Status:"+status);
+- buf.append("\n");
+- return buf.toString();
+- }
+-
+- public String getByteString(byte[] b) {
+- if ( b == null ) return "{}";
+- return Arrays.toString(b,0,Math.min(b.length,4));
+- }
+-
+- public void start() {
+- try {
+- if ( channel == null ) {
+- channel = createChannel();
+- startstatus = "starting";
+- channel.start(channel.DEFAULT);
+- startstatus = "running";
+- } else {
+- status = "Channel already started.";
+- }
+- } catch ( Exception x ) {
+- synchronized (System.err) {
+- System.err.println("Start failed:");
+- StackTraceElement[] els = x.getStackTrace();
+- for (int i = 0; i < els.length; i++) System.err.println(els[i].toString());
+- }
+- status = "Start failed:"+x.getMessage();
+- error = x;
+- startstatus = "failed";
+- try { channel.stop(GroupChannel.DEFAULT);}catch(Exception ignore){}
+- channel = null;
+- interceptor = null;
+- }
+- }
+-
+- public void stop() {
+- try {
+- if ( channel != null ) {
+- channel.stop(channel.DEFAULT);
+- status = "Channel Stopped";
+- } else {
+- status = "Channel Already Stopped";
+- }
+- }catch ( Exception x ) {
+- synchronized (System.err) {
+- System.err.println("Stop failed:");
+- StackTraceElement[] els = x.getStackTrace();
+- for (int i = 0; i < els.length; i++) System.err.println(els[i].toString());
+- }
+-
+- status = "Stop failed:"+x.getMessage();
+- error = x;
+- }finally {
+- startstatus = "stopped";
+- channel = null;
+- interceptor = null;
+- }
+- }
+-
+- public GroupChannel createChannel() {
+- channel = new GroupChannel();
+- ((ReceiverBase)channel.getChannelReceiver()).setAutoBind(100);
+- interceptor = new NonBlockingCoordinator() {
+- public void fireInterceptorEvent(InterceptorEvent event) {
+- status = event.getEventTypeDesc();
+- int type = event.getEventType();
+- boolean display = VIEW_EVENTS[type];
+- if ( display ) parent.printScreen();
+- try { Thread.sleep(SLEEP_TIME); }catch ( Exception x){}
+- }
+- };
+- channel.addInterceptor(interceptor);
+- channel.addInterceptor(new TcpFailureDetector());
+- channel.addInterceptor(new MessageDispatch15Interceptor());
+- return channel;
+- }
+- }
+-}
+\ No newline at end of file
16 years, 6 months
JBossWeb SVN: r331 - sandbox/tomcat/tomcat6.
by jbossweb-commits@lists.jboss.org
Author: jfrederic.clere(a)jboss.com
Date: 2007-11-01 09:15:22 -0400 (Thu, 01 Nov 2007)
New Revision: 331
Added:
sandbox/tomcat/tomcat6/cluster.patch
Log:
Patch to remove the cluster.
Added: sandbox/tomcat/tomcat6/cluster.patch
===================================================================
--- sandbox/tomcat/tomcat6/cluster.patch (rev 0)
+++ sandbox/tomcat/tomcat6/cluster.patch 2007-11-01 13:15:22 UTC (rev 331)
@@ -0,0 +1,35186 @@
+Index: java/org/apache/catalina/ha/session/BackupManager.java
+===================================================================
+--- java/org/apache/catalina/ha/session/BackupManager.java (revision 590752)
++++ java/org/apache/catalina/ha/session/BackupManager.java (working copy)
+@@ -1,308 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.ha.session;
+-
+-import java.io.ByteArrayInputStream;
+-import java.io.IOException;
+-import java.util.Iterator;
+-
+-import org.apache.catalina.LifecycleException;
+-import org.apache.catalina.Session;
+-import org.apache.catalina.ha.CatalinaCluster;
+-import org.apache.catalina.ha.ClusterManager;
+-import org.apache.catalina.ha.ClusterMessage;
+-import org.apache.catalina.session.StandardManager;
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.io.ReplicationStream;
+-import org.apache.catalina.tribes.tipis.LazyReplicatedMap;
+-import org.apache.catalina.tribes.tipis.AbstractReplicatedMap.MapOwner;
+-import org.apache.catalina.tribes.tipis.AbstractReplicatedMap;
+-
+-/**
+- *@author Filip Hanik
+- *@version 1.0
+- */
+-public class BackupManager extends StandardManager implements ClusterManager, MapOwner
+-{
+- public static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog( BackupManager.class );
+-
+- protected static long DEFAULT_REPL_TIMEOUT = 15000;//15 seconds
+-
+- /** Set to true if we don't want the sessions to expire on shutdown */
+- protected boolean mExpireSessionsOnShutdown = true;
+-
+- /**
+- * The name of this manager
+- */
+- protected String name;
+-
+- /**
+- * A reference to the cluster
+- */
+- protected CatalinaCluster cluster;
+-
+- /**
+- * Should listeners be notified?
+- */
+- private boolean notifyListenersOnReplication;
+- /**
+- *
+- */
+- private int mapSendOptions = Channel.SEND_OPTIONS_SYNCHRONIZED_ACK|Channel.SEND_OPTIONS_USE_ACK;
+-
+- /**
+- * Constructor, just calls super()
+- *
+- */
+- public BackupManager() {
+- super();
+- }
+-
+-
+-//******************************************************************************/
+-// ClusterManager Interface
+-//******************************************************************************/
+-
+- public void messageDataReceived(ClusterMessage msg) {
+- }
+-
+- public boolean doDomainReplication() {
+- return false;
+- }
+-
+- /**
+- * @param sendClusterDomainOnly The sendClusterDomainOnly to set.
+- */
+- public void setDomainReplication(boolean sendClusterDomainOnly) {
+- }
+-
+- /**
+- * @return Returns the defaultMode.
+- */
+- public boolean isDefaultMode() {
+- return false;
+- }
+- /**
+- * @param defaultMode The defaultMode to set.
+- */
+- public void setDefaultMode(boolean defaultMode) {
+- }
+-
+- public void setExpireSessionsOnShutdown(boolean expireSessionsOnShutdown)
+- {
+- mExpireSessionsOnShutdown = expireSessionsOnShutdown;
+- }
+-
+- public void setCluster(CatalinaCluster cluster) {
+- if(log.isDebugEnabled())
+- log.debug("Cluster associated with SimpleTcpReplicationManager");
+- this.cluster = cluster;
+- }
+-
+- public boolean getExpireSessionsOnShutdown()
+- {
+- return mExpireSessionsOnShutdown;
+- }
+-
+-
+- /**
+- * Override persistence since they don't go hand in hand with replication for now.
+- */
+- public void unload() throws IOException {
+- }
+-
+- public ClusterMessage requestCompleted(String sessionId) {
+- if ( !this.started ) return null;
+- LazyReplicatedMap map = (LazyReplicatedMap)sessions;
+- map.replicate(sessionId,false);
+- return null;
+- }
+-
+-
+-//=========================================================================
+-// OVERRIDE THESE METHODS TO IMPLEMENT THE REPLICATION
+-//=========================================================================
+- public void objectMadePrimay(Object key, Object value) {
+- if (value!=null && value instanceof DeltaSession) {
+- DeltaSession session = (DeltaSession)value;
+- synchronized (session) {
+- session.access();
+- session.endAccess();
+- }
+- }
+- }
+-
+- public Session createEmptySession() {
+- return new DeltaSession(this);
+- }
+-
+- public ClassLoader[] getClassLoaders() {
+- return ClusterManagerBase.getClassLoaders(this.container);
+- }
+-
+- /**
+- * Open Stream and use correct ClassLoader (Container) Switch
+- * ThreadClassLoader
+- *
+- * @param data
+- * @return The object input stream
+- * @throws IOException
+- */
+- public ReplicationStream getReplicationStream(byte[] data) throws IOException {
+- return getReplicationStream(data,0,data.length);
+- }
+-
+- public ReplicationStream getReplicationStream(byte[] data, int offset, int length) throws IOException {
+- ByteArrayInputStream fis = new ByteArrayInputStream(data, offset, length);
+- return new ReplicationStream(fis, getClassLoaders());
+- }
+-
+-
+-
+-
+- public String getName() {
+- return this.name;
+- }
+- /**
+- * Prepare for the beginning of active use of the public methods of this
+- * component. This method should be called after <code>configure()</code>,
+- * and before any of the public methods of the component are utilized.<BR>
+- * Starts the cluster communication channel, this will connect with the other nodes
+- * in the cluster, and request the current session state to be transferred to this node.
+- * @exception IllegalStateException if this component has already been
+- * started
+- * @exception LifecycleException if this component detects a fatal error
+- * that prevents this component from being used
+- */
+- public void start() throws LifecycleException {
+- if ( this.started ) return;
+-
+- try {
+- cluster.registerManager(this);
+- CatalinaCluster catclust = (CatalinaCluster)cluster;
+- LazyReplicatedMap map = new LazyReplicatedMap(this,
+- catclust.getChannel(),
+- DEFAULT_REPL_TIMEOUT,
+- getMapName(),
+- getClassLoaders());
+- map.setChannelSendOptions(mapSendOptions);
+- this.sessions = map;
+- super.start();
+- this.started = true;
+- } catch ( Exception x ) {
+- log.error("Unable to start BackupManager",x);
+- throw new LifecycleException("Failed to start BackupManager",x);
+- }
+- }
+-
+- public String getMapName() {
+- CatalinaCluster catclust = (CatalinaCluster)cluster;
+- String name = catclust.getManagerName(getName(),this)+"-"+"map";
+- if ( log.isDebugEnabled() ) log.debug("Backup manager, Setting map name to:"+name);
+- return name;
+- }
+-
+- /**
+- * Gracefully terminate the active use of the public methods of this
+- * component. This method should be the last one called on a given
+- * instance of this component.<BR>
+- * This will disconnect the cluster communication channel and stop the listener thread.
+- * @exception IllegalStateException if this component has not been started
+- * @exception LifecycleException if this component detects a fatal error
+- * that needs to be reported
+- */
+- public void stop() throws LifecycleException
+- {
+-
+- LazyReplicatedMap map = (LazyReplicatedMap)sessions;
+- if ( map!=null ) {
+- map.breakdown();
+- }
+- if ( !this.started ) return;
+- try {
+- } catch ( Exception x ){
+- log.error("Unable to stop BackupManager",x);
+- throw new LifecycleException("Failed to stop BackupManager",x);
+- } finally {
+- super.stop();
+- }
+- cluster.removeManager(this);
+-
+- }
+-
+- public void setDistributable(boolean dist) {
+- this.distributable = dist;
+- }
+-
+- public boolean getDistributable() {
+- return distributable;
+- }
+-
+- public void setName(String name) {
+- this.name = name;
+- }
+- public boolean isNotifyListenersOnReplication() {
+- return notifyListenersOnReplication;
+- }
+- public void setNotifyListenersOnReplication(boolean notifyListenersOnReplication) {
+- this.notifyListenersOnReplication = notifyListenersOnReplication;
+- }
+-
+- public void setMapSendOptions(int mapSendOptions) {
+- this.mapSendOptions = mapSendOptions;
+- }
+-
+- /*
+- * @see org.apache.catalina.ha.ClusterManager#getCluster()
+- */
+- public CatalinaCluster getCluster() {
+- return cluster;
+- }
+-
+- public int getMapSendOptions() {
+- return mapSendOptions;
+- }
+-
+- public String[] getInvalidatedSessions() {
+- return new String[0];
+- }
+-
+- public ClusterManager cloneFromTemplate() {
+- BackupManager result = new BackupManager();
+- result.mExpireSessionsOnShutdown = mExpireSessionsOnShutdown;
+- result.name = "Clone-from-"+name;
+- result.cluster = cluster;
+- result.notifyListenersOnReplication = notifyListenersOnReplication;
+- result.mapSendOptions = mapSendOptions;
+- return result;
+- }
+-
+- public int getActiveSessionsFull() {
+- LazyReplicatedMap map = (LazyReplicatedMap)sessions;
+- return map.sizeFull();
+- }
+-
+- public String listSessionIdsFull() {
+- StringBuffer sb=new StringBuffer();
+- LazyReplicatedMap map = (LazyReplicatedMap)sessions;
+- Iterator keys = map.keySetFull().iterator();
+- while (keys.hasNext()) {
+- sb.append(keys.next()).append(" ");
+- }
+- return sb.toString();
+- }
+-}
+Index: java/org/apache/catalina/ha/session/DeltaRequest.java
+===================================================================
+--- java/org/apache/catalina/ha/session/DeltaRequest.java (revision 590752)
++++ java/org/apache/catalina/ha/session/DeltaRequest.java (working copy)
+@@ -1,387 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-
+-package org.apache.catalina.ha.session;
+-
+-/**
+- * This class is used to track the series of actions that happens when
+- * a request is executed. These actions will then translate into invokations of methods
+- * on the actual session.
+- * This class is NOT thread safe. One DeltaRequest per session
+- * @author <a href="mailto:fhanik@apache.org">Filip Hanik</a>
+- * @version 1.0
+- */
+-
+-import java.io.Externalizable;
+-import java.security.Principal;
+-import java.util.LinkedList;
+-
+-import org.apache.catalina.realm.GenericPrincipal;
+-import org.apache.catalina.util.StringManager;
+-import java.io.ByteArrayOutputStream;
+-import java.io.IOException;
+-import java.io.ObjectOutputStream;
+-
+-
+-public class DeltaRequest implements Externalizable {
+-
+- public static org.apache.juli.logging.Log log =
+- org.apache.juli.logging.LogFactory.getLog( DeltaRequest.class );
+-
+- /**
+- * The string manager for this package.
+- */
+- protected static StringManager sm = StringManager
+- .getManager(Constants.Package);
+-
+- public static final int TYPE_ATTRIBUTE = 0;
+- public static final int TYPE_PRINCIPAL = 1;
+- public static final int TYPE_ISNEW = 2;
+- public static final int TYPE_MAXINTERVAL = 3;
+-
+- public static final int ACTION_SET = 0;
+- public static final int ACTION_REMOVE = 1;
+-
+- public static final String NAME_PRINCIPAL = "__SET__PRINCIPAL__";
+- public static final String NAME_MAXINTERVAL = "__SET__MAXINTERVAL__";
+- public static final String NAME_ISNEW = "__SET__ISNEW__";
+-
+- private String sessionId;
+- private LinkedList actions = new LinkedList();
+- private LinkedList actionPool = new LinkedList();
+-
+- private boolean recordAllActions = false;
+-
+- public DeltaRequest() {
+-
+- }
+-
+- public DeltaRequest(String sessionId, boolean recordAllActions) {
+- this.recordAllActions=recordAllActions;
+- if(sessionId != null)
+- setSessionId(sessionId);
+- }
+-
+-
+- public void setAttribute(String name, Object value) {
+- int action = (value==null)?ACTION_REMOVE:ACTION_SET;
+- addAction(TYPE_ATTRIBUTE,action,name,value);
+- }
+-
+- public void removeAttribute(String name) {
+- int action = ACTION_REMOVE;
+- addAction(TYPE_ATTRIBUTE,action,name,null);
+- }
+-
+- public void setMaxInactiveInterval(int interval) {
+- int action = ACTION_SET;
+- addAction(TYPE_MAXINTERVAL,action,NAME_MAXINTERVAL,new Integer(interval));
+- }
+-
+- /**
+- * convert principal at SerializablePrincipal for backup nodes.
+- * Only support principals from type {@link GenericPrincipal GenericPrincipal}
+- * @param p Session principal
+- * @see GenericPrincipal
+- */
+- public void setPrincipal(Principal p) {
+- int action = (p==null)?ACTION_REMOVE:ACTION_SET;
+- SerializablePrincipal sp = null;
+- if ( p != null ) {
+- if(p instanceof GenericPrincipal) {
+- sp = SerializablePrincipal.createPrincipal((GenericPrincipal)p);
+- if(log.isDebugEnabled())
+- log.debug(sm.getString("deltaRequest.showPrincipal", p.getName() , getSessionId()));
+- } else
+- log.error(sm.getString("deltaRequest.wrongPrincipalClass",p.getClass().getName()));
+- }
+- addAction(TYPE_PRINCIPAL,action,NAME_PRINCIPAL,sp);
+- }
+-
+- public void setNew(boolean n) {
+- int action = ACTION_SET;
+- addAction(TYPE_ISNEW,action,NAME_ISNEW,new Boolean(n));
+- }
+-
+- protected synchronized void addAction(int type,
+- int action,
+- String name,
+- Object value) {
+- AttributeInfo info = null;
+- if ( this.actionPool.size() > 0 ) {
+- try {
+- info = (AttributeInfo) actionPool.removeFirst();
+- }catch ( Exception x ) {
+- log.error("Unable to remove element:",x);
+- info = new AttributeInfo(type, action, name, value);
+- }
+- info.init(type,action,name,value);
+- } else {
+- info = new AttributeInfo(type, action, name, value);
+- }
+- //if we have already done something to this attribute, make sure
+- //we don't send multiple actions across the wire
+- if ( !recordAllActions) {
+- try {
+- actions.remove(info);
+- } catch (java.util.NoSuchElementException x) {
+- //do nothing, we wanted to remove it anyway
+- }
+- }
+- //add the action
+- actions.addLast(info);
+- }
+-
+- public void execute(DeltaSession session) {
+- execute(session,true);
+- }
+-
+- public synchronized void execute(DeltaSession session, boolean notifyListeners) {
+- if ( !this.sessionId.equals( session.getId() ) )
+- throw new java.lang.IllegalArgumentException("Session id mismatch, not executing the delta request");
+- session.access();
+- for ( int i=0; i<actions.size(); i++ ) {
+- AttributeInfo info = (AttributeInfo)actions.get(i);
+- switch ( info.getType() ) {
+- case TYPE_ATTRIBUTE: {
+- if ( info.getAction() == ACTION_SET ) {
+- if ( log.isTraceEnabled() ) log.trace("Session.setAttribute('"+info.getName()+"', '"+info.getValue()+"')");
+- session.setAttribute(info.getName(), info.getValue(),notifyListeners,false);
+- } else {
+- if ( log.isTraceEnabled() ) log.trace("Session.removeAttribute('"+info.getName()+"')");
+- session.removeAttribute(info.getName(),notifyListeners,false);
+- }
+-
+- break;
+- }//case
+- case TYPE_ISNEW: {
+- if ( log.isTraceEnabled() ) log.trace("Session.setNew('"+info.getValue()+"')");
+- session.setNew(((Boolean)info.getValue()).booleanValue(),false);
+- break;
+- }//case
+- case TYPE_MAXINTERVAL: {
+- if ( log.isTraceEnabled() ) log.trace("Session.setMaxInactiveInterval('"+info.getValue()+"')");
+- session.setMaxInactiveInterval(((Integer)info.getValue()).intValue(),false);
+- break;
+- }//case
+- case TYPE_PRINCIPAL: {
+- Principal p = null;
+- if ( info.getAction() == ACTION_SET ) {
+- SerializablePrincipal sp = (SerializablePrincipal)info.getValue();
+- p = (Principal)sp.getPrincipal(session.getManager().getContainer().getRealm());
+- }
+- session.setPrincipal(p,false);
+- break;
+- }//case
+- default : throw new java.lang.IllegalArgumentException("Invalid attribute info type="+info);
+- }//switch
+- }//for
+- session.endAccess();
+- reset();
+- }
+-
+- public synchronized void reset() {
+- while ( actions.size() > 0 ) {
+- try {
+- AttributeInfo info = (AttributeInfo) actions.removeFirst();
+- info.recycle();
+- actionPool.addLast(info);
+- }catch ( Exception x ) {
+- log.error("Unable to remove element",x);
+- }
+- }
+- actions.clear();
+- }
+-
+- public String getSessionId() {
+- return sessionId;
+- }
+- public void setSessionId(String sessionId) {
+- this.sessionId = sessionId;
+- if ( sessionId == null ) {
+- new Exception("Session Id is null for setSessionId").fillInStackTrace().printStackTrace();
+- }
+- }
+- public int getSize() {
+- return actions.size();
+- }
+-
+- public synchronized void clear() {
+- actions.clear();
+- actionPool.clear();
+- }
+-
+- public synchronized void readExternal(java.io.ObjectInput in) throws IOException,ClassNotFoundException {
+- //sessionId - String
+- //recordAll - boolean
+- //size - int
+- //AttributeInfo - in an array
+- reset();
+- sessionId = in.readUTF();
+- recordAllActions = in.readBoolean();
+- int cnt = in.readInt();
+- if (actions == null)
+- actions = new LinkedList();
+- else
+- actions.clear();
+- for (int i = 0; i < cnt; i++) {
+- AttributeInfo info = null;
+- if (this.actionPool.size() > 0) {
+- try {
+- info = (AttributeInfo) actionPool.removeFirst();
+- } catch ( Exception x ) {
+- log.error("Unable to remove element",x);
+- info = new AttributeInfo(-1,-1,null,null);
+- }
+- }
+- else {
+- info = new AttributeInfo(-1,-1,null,null);
+- }
+- info.readExternal(in);
+- actions.addLast(info);
+- }//for
+- }
+-
+-
+-
+- public synchronized void writeExternal(java.io.ObjectOutput out ) throws java.io.IOException {
+- //sessionId - String
+- //recordAll - boolean
+- //size - int
+- //AttributeInfo - in an array
+- out.writeUTF(getSessionId());
+- out.writeBoolean(recordAllActions);
+- out.writeInt(getSize());
+- for ( int i=0; i<getSize(); i++ ) {
+- AttributeInfo info = (AttributeInfo)actions.get(i);
+- info.writeExternal(out);
+- }
+- }
+-
+- /**
+- * serialize DeltaRequest
+- * @see DeltaRequest#writeExternal(java.io.ObjectOutput)
+- *
+- * @param deltaRequest
+- * @return serialized delta request
+- * @throws IOException
+- */
+- protected byte[] serialize() throws IOException {
+- ByteArrayOutputStream bos = new ByteArrayOutputStream();
+- ObjectOutputStream oos = new ObjectOutputStream(bos);
+- writeExternal(oos);
+- oos.flush();
+- oos.close();
+- return bos.toByteArray();
+- }
+-
+- private static class AttributeInfo implements java.io.Externalizable {
+- private String name = null;
+- private Object value = null;
+- private int action;
+- private int type;
+-
+- public AttributeInfo() {}
+-
+- public AttributeInfo(int type,
+- int action,
+- String name,
+- Object value) {
+- super();
+- init(type,action,name,value);
+- }
+-
+- public void init(int type,
+- int action,
+- String name,
+- Object value) {
+- this.name = name;
+- this.value = value;
+- this.action = action;
+- this.type = type;
+- }
+-
+- public int getType() {
+- return type;
+- }
+-
+- public int getAction() {
+- return action;
+- }
+-
+- public Object getValue() {
+- return value;
+- }
+- public int hashCode() {
+- return name.hashCode();
+- }
+-
+- public String getName() {
+- return name;
+- }
+-
+- public void recycle() {
+- name = null;
+- value = null;
+- type=-1;
+- action=-1;
+- }
+-
+- public boolean equals(Object o) {
+- if ( ! (o instanceof AttributeInfo ) ) return false;
+- AttributeInfo other = (AttributeInfo)o;
+- return other.getName().equals(this.getName());
+- }
+-
+- public synchronized void readExternal(java.io.ObjectInput in ) throws IOException,ClassNotFoundException {
+- //type - int
+- //action - int
+- //name - String
+- //hasvalue - boolean
+- //value - object
+- type = in.readInt();
+- action = in.readInt();
+- name = in.readUTF();
+- boolean hasValue = in.readBoolean();
+- if ( hasValue ) value = in.readObject();
+- }
+-
+- public synchronized void writeExternal(java.io.ObjectOutput out) throws IOException {
+- //type - int
+- //action - int
+- //name - String
+- //hasvalue - boolean
+- //value - object
+- out.writeInt(getType());
+- out.writeInt(getAction());
+- out.writeUTF(getName());
+- out.writeBoolean(getValue()!=null);
+- if (getValue()!=null) out.writeObject(getValue());
+- }
+-
+- public String toString() {
+- StringBuffer buf = new StringBuffer("AttributeInfo[type=");
+- buf.append(getType()).append(", action=").append(getAction());
+- buf.append(", name=").append(getName()).append(", value=").append(getValue());
+- buf.append(", addr=").append(super.toString()).append("]");
+- return buf.toString();
+- }
+-
+- }
+-
+-}
+Index: java/org/apache/catalina/ha/session/SessionIDMessage.java
+===================================================================
+--- java/org/apache/catalina/ha/session/SessionIDMessage.java (revision 590752)
++++ java/org/apache/catalina/ha/session/SessionIDMessage.java (working copy)
+@@ -1,130 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.ha.session;
+-
+-import org.apache.catalina.ha.ClusterMessage;
+-import org.apache.catalina.ha.ClusterMessageBase;
+-
+-/**
+- * Session id change cluster message
+- *
+- * @author Peter Rossbach
+- *
+- * @version $Revision$ $Date$
+- */
+-public class SessionIDMessage extends ClusterMessageBase implements ClusterMessage {
+-
+- private int messageNumber;
+-
+- private String orignalSessionID;
+-
+- private String backupSessionID;
+-
+- private String host ;
+- private String contextPath;
+-
+- public String getUniqueId() {
+- StringBuffer result = new StringBuffer(getOrignalSessionID());
+- result.append("#-#");
+- result.append(getHost());
+- result.append("#-#");
+- result.append(getContextPath());
+- result.append("#-#");
+- result.append(getMessageNumber());
+- result.append("#-#");
+- result.append(System.currentTimeMillis());
+- return result.toString();
+- }
+-
+- /**
+- * @return Returns the host.
+- */
+- public String getHost() {
+- return host;
+- }
+-
+- /**
+- * @param host The host to set.
+- */
+- public void setHost(String host) {
+- this.host = host;
+- }
+-
+- /**
+- * @return Returns the contextPath.
+- */
+- public String getContextPath() {
+- return contextPath;
+- }
+- /**
+- * @param contextPath The contextPath to set.
+- */
+- public void setContextPath(String contextPath) {
+- this.contextPath = contextPath;
+- }
+- /**
+- * @return Returns the messageNumber.
+- */
+- public int getMessageNumber() {
+- return messageNumber;
+- }
+-
+- /**
+- * @param messageNumber
+- * The messageNumber to set.
+- */
+- public void setMessageNumber(int messageNumber) {
+- this.messageNumber = messageNumber;
+- }
+-
+-
+- /**
+- * @return Returns the backupSessionID.
+- */
+- public String getBackupSessionID() {
+- return backupSessionID;
+- }
+-
+- /**
+- * @param backupSessionID
+- * The backupSessionID to set.
+- */
+- public void setBackupSessionID(String backupSessionID) {
+- this.backupSessionID = backupSessionID;
+- }
+-
+- /**
+- * @return Returns the orignalSessionID.
+- */
+- public String getOrignalSessionID() {
+- return orignalSessionID;
+- }
+-
+- /**
+- * @param orignalSessionID
+- * The orignalSessionID to set.
+- */
+- public void setOrignalSessionID(String orignalSessionID) {
+- this.orignalSessionID = orignalSessionID;
+- }
+-
+- public String toString() {
+- return "SESSIONID-UPDATE#" + getHost() + "." + getContextPath() + "#" + getOrignalSessionID() + ":" + getBackupSessionID();
+- }
+-
+-}
+-
+Index: java/org/apache/catalina/ha/session/ClusterManagerBase.java
+===================================================================
+--- java/org/apache/catalina/ha/session/ClusterManagerBase.java (revision 590752)
++++ java/org/apache/catalina/ha/session/ClusterManagerBase.java (working copy)
+@@ -1,75 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.ha.session;
+-
+-import org.apache.catalina.ha.ClusterManager;
+-import java.beans.PropertyChangeListener;
+-import org.apache.catalina.Lifecycle;
+-import org.apache.catalina.session.ManagerBase;
+-import org.apache.catalina.Loader;
+-import java.io.ByteArrayInputStream;
+-import java.io.IOException;
+-import org.apache.catalina.tribes.io.ReplicationStream;
+-import org.apache.catalina.Container;
+-
+-/**
+- *
+- * @author Filip Hanik
+- * @version $Revision$ $Date$
+- */
+-
+-public abstract class ClusterManagerBase extends ManagerBase implements Lifecycle, PropertyChangeListener, ClusterManager{
+-
+-
+- public static ClassLoader[] getClassLoaders(Container container) {
+- Loader loader = null;
+- ClassLoader classLoader = null;
+- if (container != null) loader = container.getLoader();
+- if (loader != null) classLoader = loader.getClassLoader();
+- else classLoader = Thread.currentThread().getContextClassLoader();
+- if ( classLoader == Thread.currentThread().getContextClassLoader() ) {
+- return new ClassLoader[] {classLoader};
+- } else {
+- return new ClassLoader[] {classLoader,Thread.currentThread().getContextClassLoader()};
+- }
+- }
+-
+-
+- public ClassLoader[] getClassLoaders() {
+- return getClassLoaders(container);
+- }
+-
+- /**
+- * Open Stream and use correct ClassLoader (Container) Switch
+- * ThreadClassLoader
+- *
+- * @param data
+- * @return The object input stream
+- * @throws IOException
+- */
+- public ReplicationStream getReplicationStream(byte[] data) throws IOException {
+- return getReplicationStream(data,0,data.length);
+- }
+-
+- public ReplicationStream getReplicationStream(byte[] data, int offset, int length) throws IOException {
+- ByteArrayInputStream fis = new ByteArrayInputStream(data, offset, length);
+- return new ReplicationStream(fis, getClassLoaders());
+- }
+-
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/ha/session/SerializablePrincipal.java
+===================================================================
+--- java/org/apache/catalina/ha/session/SerializablePrincipal.java (revision 590752)
++++ java/org/apache/catalina/ha/session/SerializablePrincipal.java (working copy)
+@@ -1,193 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-
+-package org.apache.catalina.ha.session;
+-
+-
+-import java.util.Arrays;
+-import java.util.List;
+-import org.apache.catalina.Realm;
+-
+-
+-/**
+- * Generic implementation of <strong>java.security.Principal</strong> that
+- * is available for use by <code>Realm</code> implementations.
+- * The GenericPrincipal does NOT implement serializable and I didn't want to change that implementation
+- * hence I implemented this one instead.
+- * @author Filip Hanik
+- * @version $Revision$ $Date$
+- */
+-import org.apache.catalina.realm.GenericPrincipal;
+-import java.io.ObjectInput;
+-import java.io.ObjectOutput;
+-public class SerializablePrincipal implements java.io.Serializable {
+-
+-
+- // ----------------------------------------------------------- Constructors
+-
+- public SerializablePrincipal()
+- {
+- super();
+- }
+- /**
+- * Construct a new Principal, associated with the specified Realm, for the
+- * specified username and password.
+- *
+- * @param realm The Realm that owns this Principal
+- * @param name The username of the user represented by this Principal
+- * @param password Credentials used to authenticate this user
+- */
+- public SerializablePrincipal(Realm realm, String name, String password) {
+-
+- this(realm, name, password, null);
+-
+- }
+-
+-
+- /**
+- * Construct a new Principal, associated with the specified Realm, for the
+- * specified username and password, with the specified role names
+- * (as Strings).
+- *
+- * @param realm The Realm that owns this principal
+- * @param name The username of the user represented by this Principal
+- * @param password Credentials used to authenticate this user
+- * @param roles List of roles (must be Strings) possessed by this user
+- */
+- public SerializablePrincipal(Realm realm, String name, String password,
+- List roles) {
+-
+- super();
+- this.realm = realm;
+- this.name = name;
+- this.password = password;
+- if (roles != null) {
+- this.roles = new String[roles.size()];
+- this.roles = (String[]) roles.toArray(this.roles);
+- if (this.roles.length > 0)
+- Arrays.sort(this.roles);
+- }
+-
+- }
+-
+-
+- // ------------------------------------------------------------- Properties
+-
+-
+- /**
+- * The username of the user represented by this Principal.
+- */
+- protected String name = null;
+-
+- public String getName() {
+- return (this.name);
+- }
+-
+-
+- /**
+- * The authentication credentials for the user represented by
+- * this Principal.
+- */
+- protected String password = null;
+-
+- public String getPassword() {
+- return (this.password);
+- }
+-
+-
+- /**
+- * The Realm with which this Principal is associated.
+- */
+- protected transient Realm realm = null;
+-
+- public Realm getRealm() {
+- return (this.realm);
+- }
+-
+- public void setRealm(Realm realm) {
+- this.realm = realm;
+- }
+-
+-
+-
+-
+- /**
+- * The set of roles associated with this user.
+- */
+- protected String roles[] = new String[0];
+-
+- public String[] getRoles() {
+- return (this.roles);
+- }
+-
+-
+- // --------------------------------------------------------- Public Methods
+-
+-
+-
+-
+- /**
+- * Return a String representation of this object, which exposes only
+- * information that should be public.
+- */
+- public String toString() {
+-
+- StringBuffer sb = new StringBuffer("SerializablePrincipal[");
+- sb.append(this.name);
+- sb.append("]");
+- return (sb.toString());
+-
+- }
+-
+- public static SerializablePrincipal createPrincipal(GenericPrincipal principal)
+- {
+- if ( principal==null) return null;
+- return new SerializablePrincipal(principal.getRealm(),
+- principal.getName(),
+- principal.getPassword(),
+- principal.getRoles()!=null?Arrays.asList(principal.getRoles()):null);
+- }
+-
+- public GenericPrincipal getPrincipal( Realm realm )
+- {
+- return new GenericPrincipal(realm,name,password,getRoles()!=null?Arrays.asList(getRoles()):null);
+- }
+-
+- public static GenericPrincipal readPrincipal(ObjectInput in, Realm realm) throws java.io.IOException{
+- String name = in.readUTF();
+- boolean hasPwd = in.readBoolean();
+- String pwd = null;
+- if ( hasPwd ) pwd = in.readUTF();
+- int size = in.readInt();
+- String[] roles = new String[size];
+- for ( int i=0; i<size; i++ ) roles[i] = in.readUTF();
+- return new GenericPrincipal(realm,name,pwd,Arrays.asList(roles));
+- }
+-
+- public static void writePrincipal(GenericPrincipal p, ObjectOutput out) throws java.io.IOException {
+- out.writeUTF(p.getName());
+- out.writeBoolean(p.getPassword()!=null);
+- if ( p.getPassword()!= null ) out.writeUTF(p.getPassword());
+- String[] roles = p.getRoles();
+- if ( roles == null ) roles = new String[0];
+- out.writeInt(roles.length);
+- for ( int i=0; i<roles.length; i++ ) out.writeUTF(roles[i]);
+- }
+-
+-
+-}
+Index: java/org/apache/catalina/ha/session/SessionMessageImpl.java
+===================================================================
+--- java/org/apache/catalina/ha/session/SessionMessageImpl.java (revision 590752)
++++ java/org/apache/catalina/ha/session/SessionMessageImpl.java (working copy)
+@@ -1,159 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.ha.session;
+-
+-
+-import org.apache.catalina.ha.ClusterMessageBase;
+-
+-/**
+- * Session cluster message
+- *
+- * @author Filip Hanik
+- * @author Peter Rossbach
+- *
+- * @version $Revision$ $Date$
+- */
+-public class SessionMessageImpl extends ClusterMessageBase implements SessionMessage, java.io.Serializable {
+-
+- public SessionMessageImpl() {
+- }
+-
+-
+- /*
+-
+- * Private serializable variables to keep the messages state
+- */
+- private int mEvtType = -1;
+- private byte[] mSession;
+- private String mSessionID;
+-
+- private String mContextName;
+- private long serializationTimestamp;
+- private boolean timestampSet = false ;
+- private String uniqueId;
+-
+-
+- private SessionMessageImpl( String contextName,
+- int eventtype,
+- byte[] session,
+- String sessionID)
+- {
+- mEvtType = eventtype;
+- mSession = session;
+- mSessionID = sessionID;
+- mContextName = contextName;
+- uniqueId = sessionID;
+- }
+-
+- /**
+- * Creates a session message. Depending on what event type you want this
+- * message to represent, you populate the different parameters in the constructor<BR>
+- * The following rules apply dependent on what event type argument you use:<BR>
+- * <B>EVT_SESSION_CREATED</B><BR>
+- * The parameters: session, sessionID must be set.<BR>
+- * <B>EVT_SESSION_EXPIRED</B><BR>
+- * The parameters: sessionID must be set.<BR>
+- * <B>EVT_SESSION_ACCESSED</B><BR>
+- * The parameters: sessionID must be set.<BR>
+- * <B>EVT_SESSION_EXPIRED_XXXX</B><BR>
+- * The parameters: sessionID must be set.<BR>
+- * <B>EVT_SESSION_DELTA</B><BR>
+- * Send attribute delta (add,update,remove attribute or principal, ...).<BR>
+- * <B>EVT_ALL_SESSION_DATA</B><BR>
+- * Send complete serializes session list<BR>
+- * <B>EVT_ALL_SESSION_TRANSFERCOMPLETE</B><BR>
+- * send that all session state information are transfered
+- * after GET_ALL_SESSION received from this sender.<BR>
+- * @param contextName - the name of the context (application
+- * @param eventtype - one of the 8 event type defined in this class
+- * @param session - the serialized byte array of the session itself
+- * @param sessionID - the id that identifies this session
+- * @param uniqueID - the id that identifies this message
+- */
+- public SessionMessageImpl( String contextName,
+- int eventtype,
+- byte[] session,
+- String sessionID,
+- String uniqueID)
+- {
+- this(contextName,eventtype,session,sessionID);
+- uniqueId = uniqueID;
+- }
+-
+- /**
+- * returns the event type
+- * @return one of the event types EVT_XXXX
+- */
+- public int getEventType() { return mEvtType; }
+-
+- /**
+- * @return the serialized data for the session
+- */
+- public byte[] getSession() { return mSession;}
+-
+- /**
+- * @return the session ID for the session
+- */
+- public String getSessionID(){ return mSessionID; }
+-
+- /**
+- * set message send time but only the first setting works (one shot)
+- */
+- public void setTimestamp(long time) {
+- synchronized(this) {
+- if(!timestampSet) {
+- serializationTimestamp=time;
+- timestampSet = true ;
+- }
+- }
+- }
+-
+- public long getTimestamp() { return serializationTimestamp;}
+-
+- /**
+- * clear text event type name (for logging purpose only)
+- * @return the event type in a string representating, useful for debugging
+- */
+- public String getEventTypeString()
+- {
+- switch (mEvtType)
+- {
+- case EVT_SESSION_CREATED : return "SESSION-MODIFIED";
+- case EVT_SESSION_EXPIRED : return "SESSION-EXPIRED";
+- case EVT_SESSION_ACCESSED : return "SESSION-ACCESSED";
+- case EVT_GET_ALL_SESSIONS : return "SESSION-GET-ALL";
+- case EVT_SESSION_DELTA : return "SESSION-DELTA";
+- case EVT_ALL_SESSION_DATA : return "ALL-SESSION-DATA";
+- case EVT_ALL_SESSION_TRANSFERCOMPLETE : return "SESSION-STATE-TRANSFERED";
+- default : return "UNKNOWN-EVENT-TYPE";
+- }
+- }
+-
+- public String getContextName() {
+- return mContextName;
+- }
+- public String getUniqueId() {
+- return uniqueId;
+- }
+- public void setUniqueId(String uniqueId) {
+- this.uniqueId = uniqueId;
+- }
+-
+- public String toString() {
+- return getEventTypeString() + "#" + getContextName() + "#" + getSessionID() ;
+- }
+-}
+Index: java/org/apache/catalina/ha/session/Constants.java
+===================================================================
+--- java/org/apache/catalina/ha/session/Constants.java (revision 590752)
++++ java/org/apache/catalina/ha/session/Constants.java (working copy)
+@@ -1,32 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-
+-package org.apache.catalina.ha.session;
+-
+-/**
+- * Manifest constants for the <code>org.apache.catalina.ha.session</code>
+- * package.
+- *
+- * @author Peter Rossbach Pero
+- */
+-
+-public class Constants {
+-
+- public static final String Package = "org.apache.catalina.ha.session";
+-
+-}
+Index: java/org/apache/catalina/ha/session/LocalStrings.properties
+===================================================================
+--- java/org/apache/catalina/ha/session/LocalStrings.properties (revision 590752)
++++ java/org/apache/catalina/ha/session/LocalStrings.properties (working copy)
+@@ -1,111 +0,0 @@
+-# 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.
+-
+-deltaManager.createSession.ise=createSession: Too many active sessions
+-deltaManager.createSession.newSession=Created a DeltaSession with Id [{0}] Total count={1}
+-deltaManager.createMessage.access=Manager [{0}]: create session message [{1}] access.
+-deltaManager.createMessage.accessChangePrimary=Manager [{0}]: create session message [{1}] access to change primary.
+-deltaManager.createMessage.allSessionData=Manager [{0}] send all session data.
+-deltaManager.createMessage.allSessionTransfered=Manager [{0}] send all session data transfered
+-deltaManager.createMessage.delta=Manager [{0}]: create session message [{1}] delta request.
+-deltaManager.createMessage.expire=Manager [{0}]: create session message [{1}] expire.
+-deltaManager.createMessage.unableCreateDeltaRequest=Unable to serialize delta request for sessionid [{0}]
+-deltaManager.dropMessage=Manager [{0}]: Drop message {1} inside GET_ALL_SESSIONS sync phase start date {2} message date {3}
+-deltaManager.foundMasterMember=Found for context [{0}] the replication master member [{1}]
+-deltaManager.loading.cnfe=ClassNotFoundException while loading persisted sessions: {0}
+-deltaManager.loading.existing.session=overload existing session {0}
+-deltaManager.loading.ioe=IOException while loading persisted sessions: {0}
+-deltaManager.loading.withContextClassLoader=Manager [{0}]: Loading the object data with a context class loader.
+-deltaManager.loading.withoutClassLoader=Manager [{0}]: Loading the object data without a context class loader.
+-deltaManager.managerLoad=Exception loading sessions from persistent storage
+-deltaManager.noCluster=Starting... no cluster associated with this context: [{0}]
+-deltaManager.noMasterMember=Starting... with no other member for context [{0}] at domain [{1}]
+-deltaManager.noMembers=Manager [{0}]: skipping state transfer. No members active in cluster group.
+-deltaManager.noSessionState=Manager [{0}]: No session state send at {1} received, timing out after {2} ms.
+-deltaManager.notStarted=Manager has not yet been started
+-deltaManager.sendMessage.newSession=Manager [{0}] send new session ({1})
+-deltaManager.expireSessions=Manager [{0}] expiring sessions upon shutdown
+-deltaManager.receiveMessage.accessed=Manager [{0}]: received session [{1}] accessed.
+-deltaManager.receiveMessage.createNewSession=Manager [{0}]: received session [{1}] created.
+-deltaManager.receiveMessage.delta=Manager [{0}]: received session [{1}] delta.
+-deltaManager.receiveMessage.error=Manager [{0}]: Unable to receive message through TCP channel
+-deltaManager.receiveMessage.eventType=Manager [{0}]: Received SessionMessage of type=({1}) from [{2}]
+-deltaManager.receiveMessage.expired=Manager [{0}]: received session [{1}] expired.
+-deltaManager.receiveMessage.transfercomplete=Manager [{0}] received from node [{1}:{2}] session state transfered.
+-deltaManager.receiveMessage.unloadingAfter=Manager [{0}]: unloading sessions complete
+-deltaManager.receiveMessage.unloadingBegin=Manager [{0}]: start unloading sessions
+-deltaManager.receiveMessage.allSessionDataAfter=Manager [{0}]: session state deserialized
+-deltaManager.receiveMessage.allSessionDataBegin=Manager [{0}]: received session state data
+-deltaManager.receiveMessage.fromWrongDomain=Manager [{0}]: Received wrong SessionMessage of type=({1}) from [{2}] with domain [{3}] (localdomain [{4}]
+-deltaManager.registerCluster=Register manager {0} to cluster element {1} with name {2}
+-deltaManager.sessionReceived=Manager [{0}]; session state send at {1} received in {2} ms.
+-deltaManager.sessionTimeout=Invalid session timeout setting {0}
+-deltaManager.startClustering=Starting clustering manager at {0}
+-deltaManager.stopped=Manager [{0}] is stopping
+-deltaManager.unloading.ioe=IOException while saving persisted sessions: {0}
+-deltaManager.waitForSessionState=Manager [{0}], requesting session state from {1}. This operation will timeout if no session state has been received within 60 seconds.
+-deltaRequest.showPrincipal=Principal [{0}] is set to session {1}
+-deltaRequest.wrongPrincipalClass=DeltaManager only support GenericPrincipal. Your realm used principal class {0}.
+-deltaSession.notifying=Notifying cluster of expiration primary={0} sessionId [{1}]
+-deltaSession.valueBound.ex=Session bound listener throw an exception
+-deltaSession.valueBinding.ex=Session binding listener throw an exception
+-deltaSession.valueUnbound.ex=Session unbound listener throw an exception
+-deltaSession.readSession=readObject() loading session [{0}]
+-deltaSession.readAttribute=session [{0}] loading attribute '{1}' with value '{2}'
+-deltaSession.writeSession=writeObject() storing session [{0}]
+-jvmRoute.cannotFindSession=Can't find session [{0}]
+-jvmRoute.changeSession=Changed session from [{0}] to [{1}]
+-jvmRoute.clusterListener.started=Cluster JvmRouteSessionIDBinderListener started
+-jvmRoute.clusterListener.stopped=Cluster JvmRouteSessionIDBinderListener stopped
+-jvmRoute.configure.warn=Please, setup your JvmRouteBinderValve at host valve, not at context valve!
+-jvmRoute.contextNotFound=Context [{0}] not found at node [{1}]!
+-jvmRoute.failover=Detected a failover with different jvmRoute - orginal route: [{0}] new one: [{1}] at session id [{2}]
+-jvmRoute.foundManager=Found Cluster DeltaManager {0} at {1}
+-jvmRoute.hostNotFound=No host found [{0}]
+-jvmRoute.listener.started=SessionID Binder Listener started
+-jvmRoute.listener.stopped=SessionID Binder Listener stopped
+-jvmRoute.lostSession=Lost Session [{0}] at path [{1}]
+-jvmRoute.missingJvmRouteAttribute=No engine jvmRoute attribute configured!
+-jvmRoute.newSessionCookie=Setting cookie with session id [{0}] name: [{1}] path: [{2}] secure: [{3}]
+-jvmRoute.notFoundManager=Not found Cluster DeltaManager {0} at {1}
+-jvmRoute.receiveMessage.sessionIDChanged=Cluster JvmRouteSessionIDBinderListener received orginal session ID [{0}] set to new id [{1}] for context path [{2}]
+-jvmRoute.run.already=jvmRoute SessionID receiver run already
+-jvmRoute.skipURLSessionIDs=Skip reassign jvm route check, sessionid comes from URL!
+-jvmRoute.turnoverInfo=Turnover Check time {0} msec
+-jvmRoute.valve.alreadyStarted=jvmRoute backup sessionID correction is started
+-jvmRoute.valve.notStarted=jvmRoute backup sessionID correction run already
+-jvmRoute.valve.started=JvmRouteBinderValve started
+-jvmRoute.valve.stopped=JvmRouteBinderValve stopped
+-jvmRoute.set.orignalsessionid=Set Orginal Session id at request attriute {0} value: {1}
+-standardSession.getId.ise=getId: Session already invalidated
+-standardSession.attributeEvent=Session attribute event listener threw exception
+-standardSession.attributeEvent=Session attribute event listener threw exception
+-standardSession.bindingEvent=Session binding event listener threw exception
+-standardSession.invalidate.ise=invalidate: Session already invalidated
+-standardSession.isNew.ise=isNew: Session already invalidated
+-standardSession.getAttribute.ise=getAttribute: Session already invalidated
+-standardSession.getAttributeNames.ise=getAttributeNames: Session already invalidated
+-standardSession.getCreationTime.ise=getCreationTime: Session already invalidated
+-standardSession.getLastAccessedTime.ise=getLastAccessedTime: Session already invalidated
+-standardSession.getId.ise=getId: Session already invalidated
+-standardSession.getMaxInactiveInterval.ise=getMaxInactiveInterval: Session already invalidated
+-standardSession.getValueNames.ise=getValueNames: Session already invalidated
+-standardSession.notSerializable=Cannot serialize session attribute {0} for session {1}
+-standardSession.removeAttribute.ise=removeAttribute: Session already invalidated
+-standardSession.sessionEvent=Session event listener threw exception
+-standardSession.setAttribute.iae=setAttribute: Non-serializable attribute
+-standardSession.setAttribute.ise=setAttribute: Session already invalidated
+-standardSession.setAttribute.namenull=setAttribute: name parameter cannot be null
+-standardSession.sessionCreated=Created Session id = {0}
+Index: java/org/apache/catalina/ha/session/ReplicatedSession.java
+===================================================================
+--- java/org/apache/catalina/ha/session/ReplicatedSession.java (revision 590752)
++++ java/org/apache/catalina/ha/session/ReplicatedSession.java (working copy)
+@@ -1,286 +0,0 @@
+-
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.ha.session;
+-
+-/**
+- * Title: Tomcat Session Replication for Tomcat 4.0 <BR>
+- * Description: A very simple straight forward implementation of
+- * session replication of servers in a cluster.<BR>
+- * This session replication is implemented "live". By live
+- * I mean, when a session attribute is added into a session on Node A
+- * a message is broadcasted to other messages and setAttribute is called on the replicated
+- * sessions.<BR>
+- * A full description of this implementation can be found under
+- * <href="http://www.filip.net/tomcat/">Filip's Tomcat Page</a><BR>
+- *
+- * Copyright: See apache license
+- * @author Filip Hanik
+- * @version $Revision$ $Date$
+- * Description:<BR>
+- * The ReplicatedSession class is a simple extension of the StandardSession class
+- * It overrides a few methods (setAttribute, removeAttribute, expire, access) and has
+- * hooks into the InMemoryReplicationManager to broadcast and receive events from the cluster.<BR>
+- * This class inherits the readObjectData and writeObject data methods from the StandardSession
+- * and does not contain any serializable elements in addition to the inherited ones from the StandardSession
+- *
+- */
+-import org.apache.catalina.Manager;
+-import java.io.IOException;
+-import java.io.ObjectInputStream;
+-import java.io.ObjectOutputStream;
+-import java.security.Principal;
+-
+-public class ReplicatedSession extends org.apache.catalina.session.StandardSession
+-implements org.apache.catalina.ha.ClusterSession{
+-
+- private transient Manager mManager = null;
+- protected boolean isDirty = false;
+- private transient long lastAccessWasDistributed = System.currentTimeMillis();
+- private boolean isPrimarySession=true;
+-
+-
+- public ReplicatedSession(Manager manager) {
+- super(manager);
+- mManager = manager;
+- }
+-
+-
+- public boolean isDirty()
+- {
+- return isDirty;
+- }
+-
+- public void setIsDirty(boolean dirty)
+- {
+- isDirty = dirty;
+- }
+-
+-
+- public void setLastAccessWasDistributed(long time) {
+- lastAccessWasDistributed = time;
+- }
+-
+- public long getLastAccessWasDistributed() {
+- return lastAccessWasDistributed;
+- }
+-
+-
+- public void removeAttribute(String name) {
+- setIsDirty(true);
+- super.removeAttribute(name);
+- }
+-
+- /**
+- * see parent description,
+- * plus we also notify other nodes in the cluster
+- */
+- public void removeAttribute(String name, boolean notify) {
+- setIsDirty(true);
+- super.removeAttribute(name,notify);
+- }
+-
+-
+- /**
+- * Sets an attribute and notifies the other nodes in the cluster
+- */
+- public void setAttribute(String name, Object value)
+- {
+- if ( value == null ) {
+- removeAttribute(name);
+- return;
+- }
+- if (!(value instanceof java.io.Serializable))
+- throw new java.lang.IllegalArgumentException("Value for attribute "+name+" is not serializable.");
+- setIsDirty(true);
+- super.setAttribute(name,value);
+- }
+-
+- public void setMaxInactiveInterval(int interval) {
+- setIsDirty(true);
+- super.setMaxInactiveInterval(interval);
+- }
+-
+-
+- /**
+- * Sets the manager for this session
+- * @param mgr - the servers InMemoryReplicationManager
+- */
+- public void setManager(SimpleTcpReplicationManager mgr)
+- {
+- mManager = mgr;
+- super.setManager(mgr);
+- }
+-
+-
+- /**
+- * Set the authenticated Principal that is associated with this Session.
+- * This provides an <code>Authenticator</code> with a means to cache a
+- * previously authenticated Principal, and avoid potentially expensive
+- * <code>Realm.authenticate()</code> calls on every request.
+- *
+- * @param principal The new Principal, or <code>null</code> if none
+- */
+- public void setPrincipal(Principal principal) {
+- super.setPrincipal(principal);
+- setIsDirty(true);
+- }
+-
+- public void expire() {
+- SimpleTcpReplicationManager mgr =(SimpleTcpReplicationManager)getManager();
+- mgr.sessionInvalidated(getIdInternal());
+- setIsDirty(true);
+- super.expire();
+- }
+-
+- public void invalidate() {
+- SimpleTcpReplicationManager mgr =(SimpleTcpReplicationManager)getManager();
+- mgr.sessionInvalidated(getIdInternal());
+- setIsDirty(true);
+- super.invalidate();
+- }
+-
+-
+- /**
+- * Read a serialized version of the contents of this session object from
+- * the specified object input stream, without requiring that the
+- * StandardSession itself have been serialized.
+- *
+- * @param stream The object input stream to read from
+- *
+- * @exception ClassNotFoundException if an unknown class is specified
+- * @exception IOException if an input/output error occurs
+- */
+- public void readObjectData(ObjectInputStream stream)
+- throws ClassNotFoundException, IOException {
+-
+- super.readObjectData(stream);
+-
+- }
+-
+-
+- /**
+- * Write a serialized version of the contents of this session object to
+- * the specified object output stream, without requiring that the
+- * StandardSession itself have been serialized.
+- *
+- * @param stream The object output stream to write to
+- *
+- * @exception IOException if an input/output error occurs
+- */
+- public void writeObjectData(ObjectOutputStream stream)
+- throws IOException {
+-
+- super.writeObjectData(stream);
+-
+- }
+-
+- public void setId(String id, boolean tellNew) {
+-
+- if ((this.id != null) && (manager != null))
+- manager.remove(this);
+-
+- this.id = id;
+-
+- if (manager != null)
+- manager.add(this);
+- if (tellNew) tellNew();
+- }
+-
+-
+-
+-
+-
+-
+-
+-
+- /**
+- * returns true if this session is the primary session, if that is the
+- * case, the manager can expire it upon timeout.
+- */
+- public boolean isPrimarySession() {
+- return isPrimarySession;
+- }
+-
+- /**
+- * Sets whether this is the primary session or not.
+- * @param primarySession Flag value
+- */
+- public void setPrimarySession(boolean primarySession) {
+- this.isPrimarySession=primarySession;
+- }
+-
+-
+-
+-
+- /**
+- * Implements a log method to log through the manager
+- */
+- protected void log(String message) {
+-
+- if ((mManager != null) && (mManager instanceof SimpleTcpReplicationManager)) {
+- ((SimpleTcpReplicationManager) mManager).log.debug("ReplicatedSession: " + message);
+- } else {
+- System.out.println("ReplicatedSession: " + message);
+- }
+-
+- }
+-
+- protected void log(String message, Throwable x) {
+-
+- if ((mManager != null) && (mManager instanceof SimpleTcpReplicationManager)) {
+- ((SimpleTcpReplicationManager) mManager).log.error("ReplicatedSession: " + message,x);
+- } else {
+- System.out.println("ReplicatedSession: " + message);
+- x.printStackTrace();
+- }
+-
+- }
+-
+- public String toString() {
+- StringBuffer buf = new StringBuffer("ReplicatedSession id=");
+- buf.append(getIdInternal()).append(" ref=").append(super.toString()).append("\n");
+- java.util.Enumeration e = getAttributeNames();
+- while ( e.hasMoreElements() ) {
+- String name = (String)e.nextElement();
+- Object value = getAttribute(name);
+- buf.append("\tname=").append(name).append("; value=").append(value).append("\n");
+- }
+- buf.append("\tLastAccess=").append(getLastAccessedTime()).append("\n");
+- return buf.toString();
+- }
+- public int getAccessCount() {
+- return accessCount.get();
+- }
+- public void setAccessCount(int accessCount) {
+- this.accessCount.set(accessCount);
+- }
+- public long getLastAccessedTime() {
+- return lastAccessedTime;
+- }
+- public void setLastAccessedTime(long lastAccessedTime) {
+- this.lastAccessedTime = lastAccessedTime;
+- }
+- public long getThisAccessedTime() {
+- return thisAccessedTime;
+- }
+- public void setThisAccessedTime(long thisAccessedTime) {
+- this.thisAccessedTime = thisAccessedTime;
+- }
+-
+-}
+Index: java/org/apache/catalina/ha/session/mbeans-descriptors.xml
+===================================================================
+--- java/org/apache/catalina/ha/session/mbeans-descriptors.xml (revision 590752)
++++ java/org/apache/catalina/ha/session/mbeans-descriptors.xml (working copy)
+@@ -1,583 +0,0 @@
+-<?xml version="1.0" encoding="UTF-8"?>
+-<!--
+- 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.
+--->
+-<!DOCTYPE mbeans-descriptors PUBLIC
+-"-//Apache Software Foundation//DTD Model MBeans Configuration File"
+-"http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd">
+-<mbeans-descriptors>
+- <mbean
+- name="JvmRouteBinderValve"
+- description="mod_jk jvmRoute jsessionid cookie backup correction"
+- domain="Catalina"
+- group="Valve"
+- type="org.apache.catalina.ha.session.JvmRouteBinderValve">
+- <attribute
+- name="className"
+- description="Fully qualified class name of the managed object"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute
+- name="info"
+- description="describe version"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute
+- name="enabled"
+- description="enable a jvm Route check"
+- type="boolean"/>
+- <attribute
+- name="numberOfSessions"
+- description="number of jvmRoute session corrections"
+- type="long"
+- writeable="false"/>
+- <attribute
+- name="sessionIdAttribute"
+- description="Name of attribute with sessionid value before turnover a session"
+- type="java.lang.String"/>
+- <operation
+- name="start"
+- description="Stops the Cluster JvmRouteBinderValve"
+- impact="ACTION"
+- returnType="void"/>
+- <operation
+- name="stop"
+- description="Stops the Cluster JvmRouteBinderValve"
+- impact="ACTION"
+- returnType="void"/>
+- </mbean>
+- <mbean
+- name="JvmRouteSessionIDBinderListener"
+- description="Monitors the jvmRoute activity"
+- domain="Catalina"
+- group="Listener"
+- type="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener">
+- <attribute
+- name="info"
+- description="describe version"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute
+- name="numberOfSessions"
+- description="number of jvmRoute session corrections"
+- type="long"
+- writeable="false"/>
+- </mbean>
+- <mbean
+- name="DeltaManager"
+- description="Cluster Manager implementation of the Manager interface"
+- domain="Catalina"
+- group="Manager"
+- type="org.apache.catalina.ha.session.DeltaManager">
+- <attribute
+- name="info"
+- description="describe version"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute
+- name="algorithm"
+- description="The message digest algorithm to be used when generating
+-session identifiers"
+- type="java.lang.String"/>
+- <attribute
+- name="randomFile"
+- description="File source of random - /dev/urandom or a pipe"
+- type="java.lang.String"/>
+- <attribute
+- name="className"
+- description="Fully qualified class name of the managed object"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute
+- name="distributable"
+- description="The distributable flag for Sessions created by this
+-Manager"
+- type="boolean"/>
+- <attribute
+- name="entropy"
+- description="A String initialization parameter used to increase the
+-entropy of the initialization of our random number
+-generator"
+- type="java.lang.String"/>
+- <attribute
+- name="maxActiveSessions"
+- description="The maximum number of active Sessions allowed, or -1
+-for no limit"
+- type="int"/>
+- <attribute
+- name="maxInactiveInterval"
+- description="The default maximum inactive interval for Sessions
+-created by this Manager"
+- type="int"/>
+- <attribute
+- name="processExpiresFrequency"
+- description="The frequency of the manager checks (expiration and passivation)"
+- type="int"/>
+- <attribute
+- name="sessionIdLength"
+- description="The session id length (in bytes) of Sessions
+-created by this Manager"
+- type="int"/>
+- <attribute
+- name="name"
+- description="The descriptive name of this Manager implementation
+-(for logging)"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute
+- name="activeSessions"
+- description="Number of active sessions at this moment"
+- type="int"
+- writeable="false"/>
+- <attribute
+- name="sessionCounter"
+- description="Total number of sessions created by this manager"
+- type="int"/>
+- <attribute
+- name="sessionReplaceCounter"
+- description="Total number of replaced sessions that load from external nodes"
+- type="long"/>
+- <attribute
+- name="maxActive"
+- description="Maximum number of active sessions so far"
+- type="int"/>
+- <attribute
+- name="sessionMaxAliveTime"
+- description="Longest time an expired session had been alive"
+- type="int"/>
+- <attribute
+- name="sessionAverageAliveTime"
+- description="Average time an expired session had been alive"
+- type="int"/>
+- <attribute
+- name="sendClusterDomainOnly"
+- is="true"
+- description="The sendClusterDomainOnly flag send sessions only to members as same cluster domain"
+- type="boolean"/>
+- <attribute
+- name="rejectedSessions"
+- description="Number of sessions we rejected due to maxActive beeing reached"
+- type="int"/>
+- <attribute
+- name="expiredSessions"
+- description="Number of sessions that expired ( doesn't include explicit invalidations )"
+- type="int"/>
+- <attribute
+- name="stateTransferTimeout"
+- description="state transfer timeout in sec"
+- type="int"/>
+- <attribute
+- name="processingTime"
+- description="Time spent doing housekeeping and expiration"
+- type="long"/>
+- <attribute
+- name="duplicates"
+- description="Number of duplicated session ids generated"
+- type="int"/>
+- <attribute
+- name="counterReceive_EVT_GET_ALL_SESSIONS"
+- description="Count receive EVT_GET_ALL_SESSIONS messages"
+- type="long"
+- writeable="false"/>
+- <attribute
+- name="counterReceive_EVT_ALL_SESSION_DATA"
+- description="Count receive EVT_ALL_SESSION_DATA messages"
+- type="long"
+- writeable="false"/>
+- <attribute
+- name="counterReceive_EVT_SESSION_CREATED"
+- description="Count receive EVT_SESSION_CREATED messages"
+- type="long"
+- writeable="false"/>
+- <attribute
+- name="counterReceive_EVT_SESSION_DELTA"
+- description="Count receive EVT_SESSION_DELTA messages"
+- type="long"
+- writeable="false"/>
+- <attribute
+- name="counterReceive_EVT_SESSION_ACCESSED"
+- description="Count receive EVT_SESSION_ACCESSED messages"
+- type="long"
+- writeable="false"/>
+- <attribute
+- name="counterReceive_EVT_SESSION_EXPIRED"
+- description="Count receive EVT_SESSION_EXPIRED messages"
+- type="long"
+- writeable="false"/>
+- <attribute
+- name="counterReceive_EVT_ALL_SESSION_TRANSFERCOMPLETE"
+- description="Count receive EVT_ALL_SESSION_TRANSFERCOMPLETE messages"
+- type="long"
+- writeable="false"/>
+- <attribute
+- name="counterSend_EVT_GET_ALL_SESSIONS"
+- description="Count send EVT_GET_ALL_SESSIONS messages"
+- type="long"
+- writeable="false"/>
+- <attribute
+- name="counterSend_EVT_ALL_SESSION_DATA"
+- description="Count send EVT_ALL_SESSION_DATA messages"
+- type="long"
+- writeable="false"/>
+- <attribute
+- name="counterSend_EVT_SESSION_CREATED"
+- description="Count send EVT_SESSION_CREATED messages"
+- type="long"
+- writeable="false"/>
+- <attribute
+- name="counterSend_EVT_SESSION_DELTA"
+- description="Count send EVT_SESSION_DELTA messages"
+- type="long"
+- writeable="false"/>
+- <attribute
+- name="counterSend_EVT_SESSION_ACCESSED"
+- description="Count send EVT_SESSION_ACCESSED messages"
+- type="long"
+- writeable="false"/>
+- <attribute
+- name="counterSend_EVT_SESSION_EXPIRED"
+- description="Count send EVT_SESSION_EXPIRED messages"
+- type="long"
+- writeable="false"/>
+- <attribute
+- name="counterSend_EVT_ALL_SESSION_TRANSFERCOMPLETE"
+- description="Count send EVT_ALL_SESSION_TRANSFERCOMPLETE messages"
+- type="long"
+- writeable="false"/>
+- <attribute
+- name="counterNoStateTransfered"
+- description="Count the failed session transfers noStateTransfered"
+- type="int"
+- writeable="false"/>
+- <attribute
+- name="receivedQueueSize"
+- description="length of receive queue size when session received from other node"
+- type="int"
+- writeable="false"/>
+- <attribute
+- name="expireSessionsOnShutdown"
+- is="true"
+- description="exipre all sessions cluster wide as one node goes down"
+- type="boolean"/>
+- <attribute
+- name="notifyListenersOnReplication"
+- is="true"
+- description="Send session attribute change events on backup nodes"
+- type="boolean"/>
+- <attribute
+- name="notifySessionListenersOnReplication"
+- is="true"
+- description="Send session start/stop events on backup nodes"
+- type="boolean"/>
+- <attribute
+- name="sendAllSessions"
+- is="true"
+- description="Send all sessions at one big block"
+- type="boolean"/>
+- <attribute
+- name="sendAllSessionsSize"
+- description="session block size when sendAllSessions=false (default=1000)"
+- type="int"/>
+- <attribute
+- name="sendAllSessionsWaitTime"
+- description="wait time between send session block (default 2 sec)"
+- type="int"/>
+- <operation
+- name="listSessionIds"
+- description="Return the list of active session ids"
+- impact="ACTION"
+- returnType="java.lang.String"> </operation>
+- <operation
+- name="getSessionAttribute"
+- description="Return a session attribute"
+- impact="ACTION"
+- returnType="java.lang.String">
+- <parameter
+- name="sessionId"
+- description="Id of the session"
+- type="java.lang.String"/>
+- <parameter
+- name="key"
+- description="key of the attribute"
+- type="java.lang.String"/>
+- </operation>
+- <operation
+- name="expireSession"
+- description="Expire a session"
+- impact="ACTION"
+- returnType="void">
+- <parameter
+- name="sessionId"
+- description="Id of the session"
+- type="java.lang.String"/>
+- </operation>
+- <operation
+- name="getLastAccessedTime"
+- description="Get the last access time"
+- impact="ACTION"
+- returnType="java.lang.String">
+- <parameter
+- name="sessionId"
+- description="Id of the session"
+- type="java.lang.String"/>
+- </operation>
+- <operation name="getCreationTime"
+- description="Get the creation time"
+- impact="ACTION"
+- returnType="java.lang.String">
+- <parameter name="sessionId"
+- description="Id of the session"
+- type="java.lang.String"/>
+- </operation>
+- <operation
+- name="expireAllLocalSessions"
+- description="Exipre all active local sessions and replicate the invalid sessions"
+- impact="ACTION"
+- returnType="void"> </operation>
+- <operation
+- name="processExpires"
+- description="force process to expire sessions"
+- impact="ACTION"
+- returnType="void"> </operation>
+- <operation
+- name="resetStatistics"
+- description="Reset all statistics"
+- impact="ACTION"
+- returnType="void"> </operation>
+- <operation
+- name="getAllClusterSessions"
+- description="send to oldest cluster member that this node need all cluster sessions (resync member)"
+- impact="ACTION"
+- returnType="void"> </operation>
+- </mbean>
+- <mbean
+- name="SimpleTcpReplicationManager"
+- className="org.apache.catalina.mbeans.ClassNameMBean"
+- description="Clustered implementation of the Manager interface"
+- domain="Catalina"
+- group="Manager"
+- type="org.apache.catalina.ha.tcp.SimpleTcpReplicationManager">
+- <attribute
+- name="algorithm"
+- description="The message digest algorithm to be used when generating session identifiers"
+- type="java.lang.String"/>
+- <attribute
+- name="checkInterval"
+- description="The interval (in seconds) between checks for expired sessions"
+- type="int"/>
+- <attribute
+- name="className"
+- description="Fully qualified class name of the managed object"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute
+- name="distributable"
+- description="The distributable flag for Sessions created by this Manager"
+- type="boolean"/>
+- <attribute
+- name="entropy"
+- description="A String initialization parameter used to increase the entropy of the initialization of our random number generator"
+- type="java.lang.String"/>
+- <attribute
+- name="managedResource"
+- description="The managed resource this MBean is associated with"
+- type="java.lang.Object"/>
+- <attribute
+- name="maxActiveSessions"
+- description="The maximum number of active Sessions allowed, or -1 for no limit"
+- type="int"/>
+- <attribute
+- name="maxInactiveInterval"
+- description="The default maximum inactive interval for Sessions created by this Manager"
+- type="int"/>
+- <attribute
+- name="name"
+- description="The descriptive name of this Manager implementation (for logging)"
+- type="java.lang.String"
+- writeable="false"/>
+- </mbean>
+- <mbean
+- name="BackupManager"
+- description="Cluster Manager implementation of the Manager interface"
+- domain="Catalina"
+- group="Manager"
+- type="org.apache.catalina.ha.session.BackupManager">
+- <attribute
+- name="algorithm"
+- description="The message digest algorithm to be used when generating
+-session identifiers"
+- type="java.lang.String"/>
+- <attribute
+- name="randomFile"
+- description="File source of random - /dev/urandom or a pipe"
+- type="java.lang.String"/>
+- <attribute
+- name="className"
+- description="Fully qualified class name of the managed object"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute
+- name="distributable"
+- description="The distributable flag for Sessions created by this
+-Manager"
+- type="boolean"/>
+- <attribute
+- name="entropy"
+- description="A String initialization parameter used to increase the
+-entropy of the initialization of our random number
+-generator"
+- type="java.lang.String"/>
+- <attribute
+- name="maxActiveSessions"
+- description="The maximum number of active Sessions allowed, or -1
+-for no limit"
+- type="int"/>
+- <attribute
+- name="maxInactiveInterval"
+- description="The default maximum inactive interval for Sessions
+-created by this Manager"
+- type="int"/>
+- <attribute
+- name="processExpiresFrequency"
+- description="The frequency of the manager checks (expiration and passivation)"
+- type="int"/>
+- <attribute
+- name="sessionIdLength"
+- description="The session id length (in bytes) of Sessions
+-created by this Manager"
+- type="int"/>
+- <attribute
+- name="name"
+- description="The descriptive name of this Manager implementation
+-(for logging)"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute
+- name="pathname"
+- description="Path name of the disk file in which active sessions"
+- type="java.lang.String"/>
+- <attribute
+- name="activeSessions"
+- description="Number of active primary sessions at this moment"
+- type="int"
+- writeable="false"/>
+- <attribute
+- name="activeSessionsFull"
+- description="Number of active sessions at this moment"
+- type="int"
+- writeable="false"/>
+- <attribute
+- name="sessionCounter"
+- description="Total number of sessions created by this manager"
+- type="int"/>
+- <attribute
+- name="maxActive"
+- description="Maximum number of active sessions so far"
+- type="int"/>
+- <attribute
+- name="sessionMaxAliveTime"
+- description="Longest time an expired session had been alive"
+- type="int"/>
+- <attribute
+- name="sessionAverageAliveTime"
+- description="Average time an expired session had been alive"
+- type="int"/>
+- <attribute
+- name="rejectedSessions"
+- description="Number of sessions we rejected due to maxActive beeing reached"
+- type="int"/>
+- <attribute
+- name="expiredSessions"
+- description="Number of sessions that expired ( doesn't include explicit invalidations )"
+- type="int"/>
+- <attribute
+- name="processingTime"
+- description="Time spent doing housekeeping and expiration"
+- type="long"/>
+- <attribute
+- name="duplicates"
+- description="Number of duplicated session ids generated"
+- type="int"/>
+- <attribute
+- name="expireSessionsOnShutdown"
+- is="true"
+- description="exipre all sessions cluster wide as one node goes down"
+- type="boolean"/>
+- <attribute
+- name="notifyListenersOnReplication"
+- is="true"
+- description="Send session attribute change events on backup nodes"
+- type="boolean"/>
+- <attribute
+- name="mapSendOptions"
+- description="mapSendOptions"
+- type="int"
+- writeable="false"/>
+- <operation
+- name="listSessionIds"
+- description="Return the list of active primary session ids"
+- impact="ACTION"
+- returnType="java.lang.String"> </operation>
+- <operation
+- name="listSessionIdsFull"
+- description="Return the list of active session ids"
+- impact="ACTION"
+- returnType="java.lang.String"> </operation>
+- <operation
+- name="getSessionAttribute"
+- description="Return a session attribute"
+- impact="ACTION"
+- returnType="java.lang.String">
+- <parameter
+- name="sessionId"
+- description="Id of the session"
+- type="java.lang.String"/>
+- <parameter
+- name="key"
+- description="key of the attribute"
+- type="java.lang.String"/>
+- </operation>
+- <operation
+- name="expireSession"
+- description="Expire a session"
+- impact="ACTION"
+- returnType="void">
+- <parameter
+- name="sessionId"
+- description="Id of the session"
+- type="java.lang.String"/>
+- </operation>
+- <operation
+- name="getLastAccessedTime"
+- description="Get the last access time"
+- impact="ACTION"
+- returnType="java.lang.String">
+- <parameter
+- name="sessionId"
+- description="Id of the session"
+- type="java.lang.String"/>
+- </operation>
+- <operation name="getCreationTime"
+- description="Get the creation time"
+- impact="ACTION"
+- returnType="java.lang.String">
+- <parameter name="sessionId"
+- description="Id of the session"
+- type="java.lang.String"/>
+- </operation>
+-
+- </mbean>
+-</mbeans-descriptors>
+Index: java/org/apache/catalina/ha/session/DeltaManager.java
+===================================================================
+--- java/org/apache/catalina/ha/session/DeltaManager.java (revision 590752)
++++ java/org/apache/catalina/ha/session/DeltaManager.java (working copy)
+@@ -1,1522 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.ha.session;
+-
+-import java.beans.PropertyChangeEvent;
+-import java.io.BufferedOutputStream;
+-import java.io.ByteArrayOutputStream;
+-import java.io.IOException;
+-import java.io.ObjectInputStream;
+-import java.io.ObjectOutputStream;
+-import java.util.ArrayList;
+-import java.util.Date;
+-import java.util.Iterator;
+-
+-import org.apache.catalina.Cluster;
+-import org.apache.catalina.Container;
+-import org.apache.catalina.Context;
+-import org.apache.catalina.Engine;
+-import org.apache.catalina.Host;
+-import org.apache.catalina.LifecycleException;
+-import org.apache.catalina.LifecycleListener;
+-import org.apache.catalina.Session;
+-import org.apache.catalina.Valve;
+-import org.apache.catalina.core.StandardContext;
+-import org.apache.catalina.ha.CatalinaCluster;
+-import org.apache.catalina.ha.ClusterMessage;
+-import org.apache.catalina.ha.tcp.ReplicationValve;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.io.ReplicationStream;
+-import org.apache.catalina.util.LifecycleSupport;
+-import org.apache.catalina.util.StringManager;
+-import org.apache.catalina.ha.ClusterManager;
+-
+-/**
+- * The DeltaManager manages replicated sessions by only replicating the deltas
+- * in data. For applications written to handle this, the DeltaManager is the
+- * optimal way of replicating data.
+- *
+- * This code is almost identical to StandardManager with a difference in how it
+- * persists sessions and some modifications to it.
+- *
+- * <b>IMPLEMENTATION NOTE </b>: Correct behavior of session storing and
+- * reloading depends upon external calls to the <code>start()</code> and
+- * <code>stop()</code> methods of this class at the correct times.
+- *
+- * @author Filip Hanik
+- * @author Craig R. McClanahan
+- * @author Jean-Francois Arcand
+- * @author Peter Rossbach
+- * @version $Revision$ $Date$
+- */
+-
+-public class DeltaManager extends ClusterManagerBase{
+-
+- // ---------------------------------------------------- Security Classes
+- public static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(DeltaManager.class);
+-
+- /**
+- * The string manager for this package.
+- */
+- protected static StringManager sm = StringManager.getManager(Constants.Package);
+-
+- // ----------------------------------------------------- Instance Variables
+-
+- /**
+- * The descriptive information about this implementation.
+- */
+- private static final String info = "DeltaManager/2.1";
+-
+- /**
+- * Has this component been started yet?
+- */
+- private boolean started = false;
+-
+- /**
+- * The descriptive name of this Manager implementation (for logging).
+- */
+- protected static String managerName = "DeltaManager";
+- protected String name = null;
+- protected boolean defaultMode = false;
+- private CatalinaCluster cluster = null;
+-
+- /**
+- * cached replication valve cluster container!
+- */
+- private ReplicationValve replicationValve = null ;
+-
+- /**
+- * The lifecycle event support for this component.
+- */
+- protected LifecycleSupport lifecycle = new LifecycleSupport(this);
+-
+- /**
+- * The maximum number of active Sessions allowed, or -1 for no limit.
+- */
+- private int maxActiveSessions = -1;
+- private boolean expireSessionsOnShutdown = false;
+- private boolean notifyListenersOnReplication = true;
+- private boolean notifySessionListenersOnReplication = true;
+- private boolean stateTransfered = false ;
+- private int stateTransferTimeout = 60;
+- private boolean sendAllSessions = true;
+- private boolean sendClusterDomainOnly = true ;
+- private int sendAllSessionsSize = 1000 ;
+-
+- /**
+- * wait time between send session block (default 2 sec)
+- */
+- private int sendAllSessionsWaitTime = 2 * 1000 ;
+- private ArrayList receivedMessageQueue = new ArrayList() ;
+- private boolean receiverQueue = false ;
+- private boolean stateTimestampDrop = true ;
+- private long stateTransferCreateSendTime;
+-
+- // ------------------------------------------------------------------ stats attributes
+-
+- int rejectedSessions = 0;
+- private long sessionReplaceCounter = 0 ;
+- long processingTime = 0;
+- private long counterReceive_EVT_GET_ALL_SESSIONS = 0 ;
+- private long counterSend_EVT_ALL_SESSION_DATA = 0 ;
+- private long counterReceive_EVT_ALL_SESSION_DATA = 0 ;
+- private long counterReceive_EVT_SESSION_CREATED = 0 ;
+- private long counterReceive_EVT_SESSION_EXPIRED = 0;
+- private long counterReceive_EVT_SESSION_ACCESSED = 0 ;
+- private long counterReceive_EVT_SESSION_DELTA = 0;
+- private long counterSend_EVT_GET_ALL_SESSIONS = 0 ;
+- private long counterSend_EVT_SESSION_CREATED = 0;
+- private long counterSend_EVT_SESSION_DELTA = 0 ;
+- private long counterSend_EVT_SESSION_ACCESSED = 0;
+- private long counterSend_EVT_SESSION_EXPIRED = 0;
+- private int counterSend_EVT_ALL_SESSION_TRANSFERCOMPLETE = 0 ;
+- private int counterReceive_EVT_ALL_SESSION_TRANSFERCOMPLETE = 0 ;
+- private int counterNoStateTransfered = 0 ;
+-
+-
+- // ------------------------------------------------------------- Constructor
+- public DeltaManager() {
+- super();
+- }
+-
+- // ------------------------------------------------------------- Properties
+-
+- /**
+- * Return descriptive information about this Manager implementation and the
+- * corresponding version number, in the format
+- * <code><description>/<version></code>.
+- */
+- public String getInfo() {
+- return info;
+- }
+-
+- public void setName(String name) {
+- this.name = name;
+- }
+-
+- /**
+- * Return the descriptive short name of this Manager implementation.
+- */
+- public String getName() {
+- return name;
+- }
+-
+- /**
+- * @return Returns the counterSend_EVT_GET_ALL_SESSIONS.
+- */
+- public long getCounterSend_EVT_GET_ALL_SESSIONS() {
+- return counterSend_EVT_GET_ALL_SESSIONS;
+- }
+-
+- /**
+- * @return Returns the counterSend_EVT_SESSION_ACCESSED.
+- */
+- public long getCounterSend_EVT_SESSION_ACCESSED() {
+- return counterSend_EVT_SESSION_ACCESSED;
+- }
+-
+- /**
+- * @return Returns the counterSend_EVT_SESSION_CREATED.
+- */
+- public long getCounterSend_EVT_SESSION_CREATED() {
+- return counterSend_EVT_SESSION_CREATED;
+- }
+-
+- /**
+- * @return Returns the counterSend_EVT_SESSION_DELTA.
+- */
+- public long getCounterSend_EVT_SESSION_DELTA() {
+- return counterSend_EVT_SESSION_DELTA;
+- }
+-
+- /**
+- * @return Returns the counterSend_EVT_SESSION_EXPIRED.
+- */
+- public long getCounterSend_EVT_SESSION_EXPIRED() {
+- return counterSend_EVT_SESSION_EXPIRED;
+- }
+-
+- /**
+- * @return Returns the counterSend_EVT_ALL_SESSION_DATA.
+- */
+- public long getCounterSend_EVT_ALL_SESSION_DATA() {
+- return counterSend_EVT_ALL_SESSION_DATA;
+- }
+-
+- /**
+- * @return Returns the counterSend_EVT_ALL_SESSION_TRANSFERCOMPLETE.
+- */
+- public int getCounterSend_EVT_ALL_SESSION_TRANSFERCOMPLETE() {
+- return counterSend_EVT_ALL_SESSION_TRANSFERCOMPLETE;
+- }
+-
+- /**
+- * @return Returns the counterReceive_EVT_ALL_SESSION_DATA.
+- */
+- public long getCounterReceive_EVT_ALL_SESSION_DATA() {
+- return counterReceive_EVT_ALL_SESSION_DATA;
+- }
+-
+- /**
+- * @return Returns the counterReceive_EVT_GET_ALL_SESSIONS.
+- */
+- public long getCounterReceive_EVT_GET_ALL_SESSIONS() {
+- return counterReceive_EVT_GET_ALL_SESSIONS;
+- }
+-
+- /**
+- * @return Returns the counterReceive_EVT_SESSION_ACCESSED.
+- */
+- public long getCounterReceive_EVT_SESSION_ACCESSED() {
+- return counterReceive_EVT_SESSION_ACCESSED;
+- }
+-
+- /**
+- * @return Returns the counterReceive_EVT_SESSION_CREATED.
+- */
+- public long getCounterReceive_EVT_SESSION_CREATED() {
+- return counterReceive_EVT_SESSION_CREATED;
+- }
+-
+- /**
+- * @return Returns the counterReceive_EVT_SESSION_DELTA.
+- */
+- public long getCounterReceive_EVT_SESSION_DELTA() {
+- return counterReceive_EVT_SESSION_DELTA;
+- }
+-
+- /**
+- * @return Returns the counterReceive_EVT_SESSION_EXPIRED.
+- */
+- public long getCounterReceive_EVT_SESSION_EXPIRED() {
+- return counterReceive_EVT_SESSION_EXPIRED;
+- }
+-
+-
+- /**
+- * @return Returns the counterReceive_EVT_ALL_SESSION_TRANSFERCOMPLETE.
+- */
+- public int getCounterReceive_EVT_ALL_SESSION_TRANSFERCOMPLETE() {
+- return counterReceive_EVT_ALL_SESSION_TRANSFERCOMPLETE;
+- }
+-
+- /**
+- * @return Returns the processingTime.
+- */
+- public long getProcessingTime() {
+- return processingTime;
+- }
+-
+- /**
+- * @return Returns the sessionReplaceCounter.
+- */
+- public long getSessionReplaceCounter() {
+- return sessionReplaceCounter;
+- }
+-
+- /**
+- * Number of session creations that failed due to maxActiveSessions
+- *
+- * @return The count
+- */
+- public int getRejectedSessions() {
+- return rejectedSessions;
+- }
+-
+- public void setRejectedSessions(int rejectedSessions) {
+- this.rejectedSessions = rejectedSessions;
+- }
+-
+- /**
+- * @return Returns the counterNoStateTransfered.
+- */
+- public int getCounterNoStateTransfered() {
+- return counterNoStateTransfered;
+- }
+-
+- public int getReceivedQueueSize() {
+- return receivedMessageQueue.size() ;
+- }
+-
+- /**
+- * @return Returns the stateTransferTimeout.
+- */
+- public int getStateTransferTimeout() {
+- return stateTransferTimeout;
+- }
+- /**
+- * @param timeoutAllSession The timeout
+- */
+- public void setStateTransferTimeout(int timeoutAllSession) {
+- this.stateTransferTimeout = timeoutAllSession;
+- }
+-
+- /**
+- * is session state transfered complete?
+- *
+- */
+- public boolean getStateTransfered() {
+- return stateTransfered;
+- }
+-
+- /**
+- * set that state ist complete transfered
+- * @param stateTransfered
+- */
+- public void setStateTransfered(boolean stateTransfered) {
+- this.stateTransfered = stateTransfered;
+- }
+-
+- /**
+- * @return Returns the sendAllSessionsWaitTime in msec
+- */
+- public int getSendAllSessionsWaitTime() {
+- return sendAllSessionsWaitTime;
+- }
+-
+- /**
+- * @param sendAllSessionsWaitTime The sendAllSessionsWaitTime to set at msec.
+- */
+- public void setSendAllSessionsWaitTime(int sendAllSessionsWaitTime) {
+- this.sendAllSessionsWaitTime = sendAllSessionsWaitTime;
+- }
+-
+- /**
+- * @return Returns the sendClusterDomainOnly.
+- */
+- public boolean doDomainReplication() {
+- return sendClusterDomainOnly;
+- }
+-
+- /**
+- * @param sendClusterDomainOnly The sendClusterDomainOnly to set.
+- */
+- public void setDomainReplication(boolean sendClusterDomainOnly) {
+- this.sendClusterDomainOnly = sendClusterDomainOnly;
+- }
+-
+- /**
+- * @return Returns the stateTimestampDrop.
+- */
+- public boolean isStateTimestampDrop() {
+- return stateTimestampDrop;
+- }
+-
+- /**
+- * @param isTimestampDrop The new flag value
+- */
+- public void setStateTimestampDrop(boolean isTimestampDrop) {
+- this.stateTimestampDrop = isTimestampDrop;
+- }
+-
+- /**
+- * Return the maximum number of active Sessions allowed, or -1 for no limit.
+- */
+- public int getMaxActiveSessions() {
+- return (this.maxActiveSessions);
+- }
+-
+- /**
+- * Set the maximum number of actives Sessions allowed, or -1 for no limit.
+- *
+- * @param max
+- * The new maximum number of sessions
+- */
+- public void setMaxActiveSessions(int max) {
+- int oldMaxActiveSessions = this.maxActiveSessions;
+- this.maxActiveSessions = max;
+- support.firePropertyChange("maxActiveSessions", new Integer(oldMaxActiveSessions), new Integer(this.maxActiveSessions));
+- }
+-
+- /**
+- *
+- * @return Returns the sendAllSessions.
+- */
+- public boolean isSendAllSessions() {
+- return sendAllSessions;
+- }
+-
+- /**
+- * @param sendAllSessions The sendAllSessions to set.
+- */
+- public void setSendAllSessions(boolean sendAllSessions) {
+- this.sendAllSessions = sendAllSessions;
+- }
+-
+- /**
+- * @return Returns the sendAllSessionsSize.
+- */
+- public int getSendAllSessionsSize() {
+- return sendAllSessionsSize;
+- }
+-
+- /**
+- * @param sendAllSessionsSize The sendAllSessionsSize to set.
+- */
+- public void setSendAllSessionsSize(int sendAllSessionsSize) {
+- this.sendAllSessionsSize = sendAllSessionsSize;
+- }
+-
+- /**
+- * @return Returns the notifySessionListenersOnReplication.
+- */
+- public boolean isNotifySessionListenersOnReplication() {
+- return notifySessionListenersOnReplication;
+- }
+-
+- /**
+- * @param notifyListenersCreateSessionOnReplication The notifySessionListenersOnReplication to set.
+- */
+- public void setNotifySessionListenersOnReplication(boolean notifyListenersCreateSessionOnReplication) {
+- this.notifySessionListenersOnReplication = notifyListenersCreateSessionOnReplication;
+- }
+-
+-
+- public boolean isExpireSessionsOnShutdown() {
+- return expireSessionsOnShutdown;
+- }
+-
+- public void setExpireSessionsOnShutdown(boolean expireSessionsOnShutdown) {
+- this.expireSessionsOnShutdown = expireSessionsOnShutdown;
+- }
+-
+- public boolean isNotifyListenersOnReplication() {
+- return notifyListenersOnReplication;
+- }
+-
+- public void setNotifyListenersOnReplication(boolean notifyListenersOnReplication) {
+- this.notifyListenersOnReplication = notifyListenersOnReplication;
+- }
+-
+-
+- /**
+- * @return Returns the defaultMode.
+- */
+- public boolean isDefaultMode() {
+- return defaultMode;
+- }
+- /**
+- * @param defaultMode The defaultMode to set.
+- */
+- public void setDefaultMode(boolean defaultMode) {
+- this.defaultMode = defaultMode;
+- }
+-
+- public CatalinaCluster getCluster() {
+- return cluster;
+- }
+-
+- public void setCluster(CatalinaCluster cluster) {
+- this.cluster = cluster;
+- }
+-
+- /**
+- * Set the Container with which this Manager has been associated. If it is a
+- * Context (the usual case), listen for changes to the session timeout
+- * property.
+- *
+- * @param container
+- * The associated Container
+- */
+- public void setContainer(Container container) {
+- // De-register from the old Container (if any)
+- if ((this.container != null) && (this.container instanceof Context))
+- ((Context) this.container).removePropertyChangeListener(this);
+-
+- // Default processing provided by our superclass
+- super.setContainer(container);
+-
+- // Register with the new Container (if any)
+- if ((this.container != null) && (this.container instanceof Context)) {
+- setMaxInactiveInterval(((Context) this.container).getSessionTimeout() * 60);
+- ((Context) this.container).addPropertyChangeListener(this);
+- }
+-
+- }
+-
+- // --------------------------------------------------------- Public Methods
+-
+- /**
+- * Construct and return a new session object, based on the default settings
+- * specified by this Manager's properties. The session id will be assigned
+- * by this method, and available via the getId() method of the returned
+- * session. If a new session cannot be created for any reason, return
+- * <code>null</code>.
+- *
+- * @exception IllegalStateException
+- * if a new session cannot be instantiated for any reason
+- *
+- * Construct and return a new session object, based on the default settings
+- * specified by this Manager's properties. The session id will be assigned
+- * by this method, and available via the getId() method of the returned
+- * session. If a new session cannot be created for any reason, return
+- * <code>null</code>.
+- *
+- * @exception IllegalStateException
+- * if a new session cannot be instantiated for any reason
+- */
+- public Session createSession(String sessionId) {
+- return createSession(sessionId, true);
+- }
+-
+- /**
+- * create new session with check maxActiveSessions and send session creation
+- * to other cluster nodes.
+- *
+- * @param distribute
+- * @return The session
+- */
+- public Session createSession(String sessionId, boolean distribute) {
+- if ((maxActiveSessions >= 0) && (sessions.size() >= maxActiveSessions)) {
+- rejectedSessions++;
+- throw new IllegalStateException(sm.getString("deltaManager.createSession.ise"));
+- }
+- DeltaSession session = (DeltaSession) super.createSession(sessionId) ;
+- if (distribute) {
+- sendCreateSession(session.getId(), session);
+- }
+- if (log.isDebugEnabled())
+- log.debug(sm.getString("deltaManager.createSession.newSession",session.getId(), new Integer(sessions.size())));
+- return (session);
+-
+- }
+-
+- /**
+- * Send create session evt to all backup node
+- * @param sessionId
+- * @param session
+- */
+- protected void sendCreateSession(String sessionId, DeltaSession session) {
+- if(cluster.getMembers().length > 0 ) {
+- SessionMessage msg =
+- new SessionMessageImpl(getName(),
+- SessionMessage.EVT_SESSION_CREATED,
+- null,
+- sessionId,
+- sessionId + "-" + System.currentTimeMillis());
+- if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.sendMessage.newSession",name, sessionId));
+- msg.setTimestamp(session.getCreationTime());
+- counterSend_EVT_SESSION_CREATED++;
+- send(msg);
+- }
+- }
+-
+- /**
+- * Send messages to other backup member (domain or all)
+- * @param msg Session message
+- */
+- protected void send(SessionMessage msg) {
+- if(cluster != null) {
+- if(doDomainReplication())
+- cluster.sendClusterDomain(msg);
+- else
+- cluster.send(msg);
+- }
+- }
+-
+- /**
+- * Create DeltaSession
+- * @see org.apache.catalina.Manager#createEmptySession()
+- */
+- public Session createEmptySession() {
+- return getNewDeltaSession() ;
+- }
+-
+- /**
+- * Get new session class to be used in the doLoad() method.
+- */
+- protected DeltaSession getNewDeltaSession() {
+- return new DeltaSession(this);
+- }
+-
+- /**
+- * Load Deltarequest from external node
+- * Load the Class at container classloader
+- * @see DeltaRequest#readExternal(java.io.ObjectInput)
+- * @param session
+- * @param data message data
+- * @return The request
+- * @throws ClassNotFoundException
+- * @throws IOException
+- */
+- protected DeltaRequest deserializeDeltaRequest(DeltaSession session, byte[] data) throws ClassNotFoundException, IOException {
+- ReplicationStream ois = getReplicationStream(data);
+- session.getDeltaRequest().readExternal(ois);
+- ois.close();
+- return session.getDeltaRequest();
+- }
+-
+- /**
+- * serialize DeltaRequest
+- * @see DeltaRequest#writeExternal(java.io.ObjectOutput)
+- *
+- * @param deltaRequest
+- * @return serialized delta request
+- * @throws IOException
+- */
+- protected byte[] serializeDeltaRequest(DeltaRequest deltaRequest) throws IOException {
+- return deltaRequest.serialize();
+- }
+-
+- /**
+- * Load sessions from other cluster node.
+- * FIXME replace currently sessions with same id without notifcation.
+- * FIXME SSO handling is not really correct with the session replacement!
+- * @exception ClassNotFoundException
+- * if a serialized class cannot be found during the reload
+- * @exception IOException
+- * if an input/output error occurs
+- */
+- protected void deserializeSessions(byte[] data) throws ClassNotFoundException,IOException {
+-
+- // Initialize our internal data structures
+- //sessions.clear(); //should not do this
+- // Open an input stream to the specified pathname, if any
+- ClassLoader originalLoader = Thread.currentThread().getContextClassLoader();
+- ObjectInputStream ois = null;
+- // Load the previously unloaded active sessions
+- try {
+- ois = getReplicationStream(data);
+- Integer count = (Integer) ois.readObject();
+- int n = count.intValue();
+- for (int i = 0; i < n; i++) {
+- DeltaSession session = (DeltaSession) createEmptySession();
+- session.readObjectData(ois);
+- session.setManager(this);
+- session.setValid(true);
+- session.setPrimarySession(false);
+- //in case the nodes in the cluster are out of
+- //time synch, this will make sure that we have the
+- //correct timestamp, isValid returns true, cause
+- // accessCount=1
+- session.access();
+- //make sure that the session gets ready to expire if
+- // needed
+- session.setAccessCount(0);
+- session.resetDeltaRequest();
+- // FIXME How inform other session id cache like SingleSignOn
+- // increment sessionCounter to correct stats report
+- if (findSession(session.getIdInternal()) == null ) {
+- sessionCounter++;
+- } else {
+- sessionReplaceCounter++;
+- // FIXME better is to grap this sessions again !
+- if (log.isWarnEnabled()) log.warn(sm.getString("deltaManager.loading.existing.session",session.getIdInternal()));
+- }
+- add(session);
+- }
+- } catch (ClassNotFoundException e) {
+- log.error(sm.getString("deltaManager.loading.cnfe", e), e);
+- throw e;
+- } catch (IOException e) {
+- log.error(sm.getString("deltaManager.loading.ioe", e), e);
+- throw e;
+- } finally {
+- // Close the input stream
+- try {
+- if (ois != null) ois.close();
+- } catch (IOException f) {
+- // ignored
+- }
+- ois = null;
+- if (originalLoader != null) Thread.currentThread().setContextClassLoader(originalLoader);
+- }
+-
+- }
+-
+-
+-
+- /**
+- * Save any currently active sessions in the appropriate persistence
+- * mechanism, if any. If persistence is not supported, this method returns
+- * without doing anything.
+- *
+- * @exception IOException
+- * if an input/output error occurs
+- */
+- protected byte[] serializeSessions(Session[] currentSessions) throws IOException {
+-
+- // Open an output stream to the specified pathname, if any
+- ByteArrayOutputStream fos = null;
+- ObjectOutputStream oos = null;
+-
+- try {
+- fos = new ByteArrayOutputStream();
+- oos = new ObjectOutputStream(new BufferedOutputStream(fos));
+- oos.writeObject(new Integer(currentSessions.length));
+- for(int i=0 ; i < currentSessions.length;i++) {
+- ((DeltaSession)currentSessions[i]).writeObjectData(oos);
+- }
+- // Flush and close the output stream
+- oos.flush();
+- } catch (IOException e) {
+- log.error(sm.getString("deltaManager.unloading.ioe", e), e);
+- throw e;
+- } finally {
+- if (oos != null) {
+- try {
+- oos.close();
+- } catch (IOException f) {
+- ;
+- }
+- oos = null;
+- }
+- }
+- // send object data as byte[]
+- return fos.toByteArray();
+- }
+-
+- // ------------------------------------------------------ Lifecycle Methods
+-
+- /**
+- * Add a lifecycle event listener to this component.
+- *
+- * @param listener
+- * The listener to add
+- */
+- public void addLifecycleListener(LifecycleListener listener) {
+- lifecycle.addLifecycleListener(listener);
+- }
+-
+- /**
+- * Get the lifecycle listeners associated with this lifecycle. If this
+- * Lifecycle has no listeners registered, a zero-length array is returned.
+- */
+- public LifecycleListener[] findLifecycleListeners() {
+- return lifecycle.findLifecycleListeners();
+- }
+-
+- /**
+- * Remove a lifecycle event listener from this component.
+- *
+- * @param listener
+- * The listener to remove
+- */
+- public void removeLifecycleListener(LifecycleListener listener) {
+- lifecycle.removeLifecycleListener(listener);
+- }
+-
+- /**
+- * Prepare for the beginning of active use of the public methods of this
+- * component. This method should be called after <code>configure()</code>,
+- * and before any of the public methods of the component are utilized.
+- *
+- * @exception LifecycleException
+- * if this component detects a fatal error that prevents this
+- * component from being used
+- */
+- public void start() throws LifecycleException {
+- if (!initialized) init();
+-
+- // Validate and update our current component state
+- if (started) {
+- return;
+- }
+- started = true;
+- lifecycle.fireLifecycleEvent(START_EVENT, null);
+-
+- // Force initialization of the random number generator
+- generateSessionId();
+-
+- // Load unloaded sessions, if any
+- try {
+- //the channel is already running
+- Cluster cluster = getCluster() ;
+- // stop remove cluster binding
+- //wow, how many nested levels of if statements can we have ;)
+- if(cluster == null) {
+- Container context = getContainer() ;
+- if(context != null && context instanceof Context) {
+- Container host = context.getParent() ;
+- if(host != null && host instanceof Host) {
+- cluster = host.getCluster();
+- if(cluster != null && cluster instanceof CatalinaCluster) {
+- setCluster((CatalinaCluster) cluster) ;
+- } else {
+- Container engine = host.getParent() ;
+- if(engine != null && engine instanceof Engine) {
+- cluster = engine.getCluster();
+- if(cluster != null && cluster instanceof CatalinaCluster) {
+- setCluster((CatalinaCluster) cluster) ;
+- }
+- } else {
+- cluster = null ;
+- }
+- }
+- }
+- }
+- }
+- if (cluster == null) {
+- log.error(sm.getString("deltaManager.noCluster", getName()));
+- return;
+- } else {
+- if (log.isInfoEnabled()) {
+- String type = "unknown" ;
+- if( cluster.getContainer() instanceof Host){
+- type = "Host" ;
+- } else if( cluster.getContainer() instanceof Engine){
+- type = "Engine" ;
+- }
+- log.info(sm.getString("deltaManager.registerCluster", getName(), type, cluster.getClusterName()));
+- }
+- }
+- if (log.isInfoEnabled()) log.info(sm.getString("deltaManager.startClustering", getName()));
+- //to survice context reloads, as only a stop/start is called, not
+- // createManager
+- cluster.registerManager(this);
+-
+- getAllClusterSessions();
+-
+- } catch (Throwable t) {
+- log.error(sm.getString("deltaManager.managerLoad"), t);
+- }
+- }
+-
+- /**
+- * get from first session master the backup from all clustered sessions
+- * @see #findSessionMasterMember()
+- */
+- public synchronized void getAllClusterSessions() {
+- if (cluster != null && cluster.getMembers().length > 0) {
+- long beforeSendTime = System.currentTimeMillis();
+- Member mbr = findSessionMasterMember();
+- if(mbr == null) { // No domain member found
+- return;
+- }
+- SessionMessage msg = new SessionMessageImpl(this.getName(),SessionMessage.EVT_GET_ALL_SESSIONS, null, "GET-ALL","GET-ALL-" + getName());
+- // set reference time
+- stateTransferCreateSendTime = beforeSendTime ;
+- // request session state
+- counterSend_EVT_GET_ALL_SESSIONS++;
+- stateTransfered = false ;
+- // FIXME This send call block the deploy thread, when sender waitForAck is enabled
+- try {
+- synchronized(receivedMessageQueue) {
+- receiverQueue = true ;
+- }
+- cluster.send(msg, mbr);
+- if (log.isWarnEnabled()) log.warn(sm.getString("deltaManager.waitForSessionState",getName(), mbr));
+- // FIXME At sender ack mode this method check only the state transfer and resend is a problem!
+- waitForSendAllSessions(beforeSendTime);
+- } finally {
+- synchronized(receivedMessageQueue) {
+- for (Iterator iter = receivedMessageQueue.iterator(); iter.hasNext();) {
+- SessionMessage smsg = (SessionMessage) iter.next();
+- if (!stateTimestampDrop) {
+- messageReceived(smsg, smsg.getAddress() != null ? (Member) smsg.getAddress() : null);
+- } else {
+- if (smsg.getEventType() != SessionMessage.EVT_GET_ALL_SESSIONS && smsg.getTimestamp() >= stateTransferCreateSendTime) {
+- // FIXME handle EVT_GET_ALL_SESSIONS later
+- messageReceived(smsg,smsg.getAddress() != null ? (Member) smsg.getAddress() : null);
+- } else {
+- if (log.isWarnEnabled()) {
+- log.warn(sm.getString("deltaManager.dropMessage",getName(), smsg.getEventTypeString(),new Date(stateTransferCreateSendTime), new Date(smsg.getTimestamp())));
+- }
+- }
+- }
+- }
+- receivedMessageQueue.clear();
+- receiverQueue = false ;
+- }
+- }
+- } else {
+- if (log.isInfoEnabled()) log.info(sm.getString("deltaManager.noMembers", getName()));
+- }
+- }
+-
+- /**
+- * Register cross context session at replication valve thread local
+- * @param session cross context session
+- */
+- protected void registerSessionAtReplicationValve(DeltaSession session) {
+- if(replicationValve == null) {
+- if(container instanceof StandardContext && ((StandardContext)container).getCrossContext()) {
+- Cluster cluster = getCluster() ;
+- if(cluster != null && cluster instanceof CatalinaCluster) {
+- Valve[] valves = ((CatalinaCluster)cluster).getValves();
+- if(valves != null && valves.length > 0) {
+- for(int i=0; replicationValve == null && i < valves.length ; i++ ){
+- if(valves[i] instanceof ReplicationValve) replicationValve = (ReplicationValve)valves[i] ;
+- }//for
+-
+- if(replicationValve == null && log.isDebugEnabled()) {
+- log.debug("no ReplicationValve found for CrossContext Support");
+- }//endif
+- }//end if
+- }//endif
+- }//end if
+- }//end if
+- if(replicationValve != null) {
+- replicationValve.registerReplicationSession(session);
+- }
+- }
+-
+- /**
+- * Find the master of the session state
+- * @return master member of sessions
+- */
+- protected Member findSessionMasterMember() {
+- Member mbr = null;
+- Member mbrs[] = cluster.getMembers();
+- if(mbrs.length != 0 ) mbr = mbrs[0];
+- if(mbr == null && log.isWarnEnabled()) log.warn(sm.getString("deltaManager.noMasterMember",getName(), ""));
+- if(mbr != null && log.isDebugEnabled()) log.warn(sm.getString("deltaManager.foundMasterMember",getName(), mbr));
+- return mbr;
+- }
+-
+- /**
+- * Wait that cluster session state is transfer or timeout after 60 Sec
+- * With stateTransferTimeout == -1 wait that backup is transfered (forever mode)
+- */
+- protected void waitForSendAllSessions(long beforeSendTime) {
+- long reqStart = System.currentTimeMillis();
+- long reqNow = reqStart ;
+- boolean isTimeout = false;
+- if(getStateTransferTimeout() > 0) {
+- // wait that state is transfered with timeout check
+- do {
+- try {
+- Thread.sleep(100);
+- } catch (Exception sleep) {
+- //
+- }
+- reqNow = System.currentTimeMillis();
+- isTimeout = ((reqNow - reqStart) > (1000 * getStateTransferTimeout()));
+- } while ((!getStateTransfered()) && (!isTimeout));
+- } else {
+- if(getStateTransferTimeout() == -1) {
+- // wait that state is transfered
+- do {
+- try {
+- Thread.sleep(100);
+- } catch (Exception sleep) {
+- }
+- } while ((!getStateTransfered()));
+- reqNow = System.currentTimeMillis();
+- }
+- }
+- if (isTimeout || (!getStateTransfered())) {
+- counterNoStateTransfered++ ;
+- log.error(sm.getString("deltaManager.noSessionState",getName(),new Date(beforeSendTime),new Long(reqNow - beforeSendTime)));
+- } else {
+- if (log.isInfoEnabled())
+- log.info(sm.getString("deltaManager.sessionReceived",getName(), new Date(beforeSendTime), new Long(reqNow - beforeSendTime)));
+- }
+- }
+-
+- /**
+- * Gracefully terminate the active use of the public methods of this
+- * component. This method should be the last one called on a given instance
+- * of this component.
+- *
+- * @exception LifecycleException
+- * if this component detects a fatal error that needs to be
+- * reported
+- */
+- public void stop() throws LifecycleException {
+-
+- if (log.isDebugEnabled())
+- log.debug(sm.getString("deltaManager.stopped", getName()));
+-
+-
+- // Validate and update our current component state
+- if (!started)
+- throw new LifecycleException(sm.getString("deltaManager.notStarted"));
+- lifecycle.fireLifecycleEvent(STOP_EVENT, null);
+- started = false;
+-
+- // Expire all active sessions
+- if (log.isInfoEnabled()) log.info(sm.getString("deltaManager.expireSessions", getName()));
+- Session sessions[] = findSessions();
+- for (int i = 0; i < sessions.length; i++) {
+- DeltaSession session = (DeltaSession) sessions[i];
+- if (!session.isValid())
+- continue;
+- try {
+- session.expire(true, isExpireSessionsOnShutdown());
+- } catch (Throwable ignore) {
+- ;
+- }
+- }
+-
+- // Require a new random number generator if we are restarted
+- this.random = null;
+- getCluster().removeManager(this);
+- replicationValve = null;
+- if (initialized) {
+- destroy();
+- }
+- }
+-
+- // ----------------------------------------- PropertyChangeListener Methods
+-
+- /**
+- * Process property change events from our associated Context.
+- *
+- * @param event
+- * The property change event that has occurred
+- */
+- public void propertyChange(PropertyChangeEvent event) {
+-
+- // Validate the source of this event
+- if (!(event.getSource() instanceof Context))
+- return;
+- // Process a relevant property change
+- if (event.getPropertyName().equals("sessionTimeout")) {
+- try {
+- setMaxInactiveInterval(((Integer) event.getNewValue()).intValue() * 60);
+- } catch (NumberFormatException e) {
+- log.error(sm.getString("deltaManager.sessionTimeout", event.getNewValue()));
+- }
+- }
+-
+- }
+-
+- // -------------------------------------------------------- Replication
+- // Methods
+-
+- /**
+- * A message was received from another node, this is the callback method to
+- * implement if you are interested in receiving replication messages.
+- *
+- * @param cmsg -
+- * the message received.
+- */
+- public void messageDataReceived(ClusterMessage cmsg) {
+- if (cmsg != null && cmsg instanceof SessionMessage) {
+- SessionMessage msg = (SessionMessage) cmsg;
+- switch (msg.getEventType()) {
+- case SessionMessage.EVT_GET_ALL_SESSIONS:
+- case SessionMessage.EVT_SESSION_CREATED:
+- case SessionMessage.EVT_SESSION_EXPIRED:
+- case SessionMessage.EVT_SESSION_ACCESSED:
+- case SessionMessage.EVT_SESSION_DELTA: {
+- synchronized(receivedMessageQueue) {
+- if(receiverQueue) {
+- receivedMessageQueue.add(msg);
+- return ;
+- }
+- }
+- break;
+- }
+- default: {
+- //we didn't queue, do nothing
+- break;
+- }
+- } //switch
+-
+- messageReceived(msg, msg.getAddress() != null ? (Member) msg.getAddress() : null);
+- }
+- }
+-
+- /**
+- * When the request has been completed, the replication valve will notify
+- * the manager, and the manager will decide whether any replication is
+- * needed or not. If there is a need for replication, the manager will
+- * create a session message and that will be replicated. The cluster
+- * determines where it gets sent.
+- *
+- * @param sessionId -
+- * the sessionId that just completed.
+- * @return a SessionMessage to be sent,
+- */
+- public ClusterMessage requestCompleted(String sessionId) {
+- try {
+- DeltaSession session = (DeltaSession) findSession(sessionId);
+- DeltaRequest deltaRequest = session.getDeltaRequest();
+- SessionMessage msg = null;
+- boolean isDeltaRequest = false ;
+- synchronized(deltaRequest) {
+- isDeltaRequest = deltaRequest.getSize() > 0 ;
+- if (isDeltaRequest) {
+- counterSend_EVT_SESSION_DELTA++;
+- byte[] data = serializeDeltaRequest(deltaRequest);
+- msg = new SessionMessageImpl(getName(),
+- SessionMessage.EVT_SESSION_DELTA,
+- data,
+- sessionId,
+- sessionId + "-" + System.currentTimeMillis());
+- session.resetDeltaRequest();
+- }
+- }
+- if(!isDeltaRequest) {
+- if(!session.isPrimarySession()) {
+- counterSend_EVT_SESSION_ACCESSED++;
+- msg = new SessionMessageImpl(getName(),
+- SessionMessage.EVT_SESSION_ACCESSED,
+- null,
+- sessionId,
+- sessionId + "-" + System.currentTimeMillis());
+- if (log.isDebugEnabled()) {
+- log.debug(sm.getString("deltaManager.createMessage.accessChangePrimary",getName(), sessionId));
+- }
+- }
+- } else { // log only outside synch block!
+- if (log.isDebugEnabled()) {
+- log.debug(sm.getString("deltaManager.createMessage.delta",getName(), sessionId));
+- }
+- }
+- session.setPrimarySession(true);
+- //check to see if we need to send out an access message
+- if ((msg == null)) {
+- long replDelta = System.currentTimeMillis() - session.getLastTimeReplicated();
+- if (replDelta > (getMaxInactiveInterval() * 1000)) {
+- counterSend_EVT_SESSION_ACCESSED++;
+- msg = new SessionMessageImpl(getName(),
+- SessionMessage.EVT_SESSION_ACCESSED,
+- null,
+- sessionId,
+- sessionId + "-" + System.currentTimeMillis());
+- if (log.isDebugEnabled()) {
+- log.debug(sm.getString("deltaManager.createMessage.access", getName(),sessionId));
+- }
+- }
+-
+- }
+-
+- //update last replicated time
+- if (msg != null) session.setLastTimeReplicated(System.currentTimeMillis());
+- return msg;
+- } catch (IOException x) {
+- log.error(sm.getString("deltaManager.createMessage.unableCreateDeltaRequest",sessionId), x);
+- return null;
+- }
+-
+- }
+- /**
+- * Reset manager statistics
+- */
+- public synchronized void resetStatistics() {
+- processingTime = 0 ;
+- expiredSessions = 0 ;
+- rejectedSessions = 0 ;
+- sessionReplaceCounter = 0 ;
+- counterNoStateTransfered = 0 ;
+- maxActive = getActiveSessions() ;
+- sessionCounter = getActiveSessions() ;
+- counterReceive_EVT_ALL_SESSION_DATA = 0;
+- counterReceive_EVT_GET_ALL_SESSIONS = 0;
+- counterReceive_EVT_SESSION_ACCESSED = 0 ;
+- counterReceive_EVT_SESSION_CREATED = 0 ;
+- counterReceive_EVT_SESSION_DELTA = 0 ;
+- counterReceive_EVT_SESSION_EXPIRED = 0 ;
+- counterReceive_EVT_ALL_SESSION_TRANSFERCOMPLETE = 0;
+- counterSend_EVT_ALL_SESSION_DATA = 0;
+- counterSend_EVT_GET_ALL_SESSIONS = 0;
+- counterSend_EVT_SESSION_ACCESSED = 0 ;
+- counterSend_EVT_SESSION_CREATED = 0 ;
+- counterSend_EVT_SESSION_DELTA = 0 ;
+- counterSend_EVT_SESSION_EXPIRED = 0 ;
+- counterSend_EVT_ALL_SESSION_TRANSFERCOMPLETE = 0;
+-
+- }
+-
+- // -------------------------------------------------------- persistence handler
+-
+- public void load() {
+-
+- }
+-
+- public void unload() {
+-
+- }
+-
+- // -------------------------------------------------------- expire
+-
+- /**
+- * send session expired to other cluster nodes
+- *
+- * @param id
+- * session id
+- */
+- protected void sessionExpired(String id) {
+- counterSend_EVT_SESSION_EXPIRED++ ;
+- SessionMessage msg = new SessionMessageImpl(getName(),SessionMessage.EVT_SESSION_EXPIRED, null, id, id+ "-EXPIRED-MSG");
+- if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.createMessage.expire",getName(), id));
+- send(msg);
+- }
+-
+- /**
+- * Exipre all find sessions.
+- */
+- public void expireAllLocalSessions()
+- {
+- long timeNow = System.currentTimeMillis();
+- Session sessions[] = findSessions();
+- int expireDirect = 0 ;
+- int expireIndirect = 0 ;
+-
+- if(log.isDebugEnabled()) log.debug("Start expire all sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length);
+- for (int i = 0; i < sessions.length; i++) {
+- if (sessions[i] instanceof DeltaSession) {
+- DeltaSession session = (DeltaSession) sessions[i];
+- if (session.isPrimarySession()) {
+- if (session.isValid()) {
+- session.expire();
+- expireDirect++;
+- } else {
+- expireIndirect++;
+- }//end if
+- }//end if
+- }//end if
+- }//for
+- long timeEnd = System.currentTimeMillis();
+- if(log.isDebugEnabled()) log.debug("End expire sessions " + getName() + " exipre processingTime " + (timeEnd - timeNow) + " expired direct sessions: " + expireDirect + " expired direct sessions: " + expireIndirect);
+-
+- }
+-
+- /**
+- * When the manager expires session not tied to a request. The cluster will
+- * periodically ask for a list of sessions that should expire and that
+- * should be sent across the wire.
+- *
+- * @return The invalidated sessions array
+- */
+- public String[] getInvalidatedSessions() {
+- return new String[0];
+- }
+-
+- // -------------------------------------------------------- message receive
+-
+- /**
+- * Test that sender and local domain is the same
+- */
+- protected boolean checkSenderDomain(SessionMessage msg,Member sender) {
+- boolean sameDomain= true;
+- if (!sameDomain && log.isWarnEnabled()) {
+- log.warn(sm.getString("deltaManager.receiveMessage.fromWrongDomain",
+- new Object[] {getName(),
+- msg.getEventTypeString(),
+- sender,
+- "",
+- "" }));
+- }
+- return sameDomain ;
+- }
+-
+- /**
+- * This method is called by the received thread when a SessionMessage has
+- * been received from one of the other nodes in the cluster.
+- *
+- * @param msg -
+- * the message received
+- * @param sender -
+- * the sender of the message, this is used if we receive a
+- * EVT_GET_ALL_SESSION message, so that we only reply to the
+- * requesting node
+- */
+- protected void messageReceived(SessionMessage msg, Member sender) {
+- if(doDomainReplication() && !checkSenderDomain(msg,sender)) {
+- return;
+- }
+- ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+- try {
+-
+- ClassLoader[] loaders = getClassLoaders();
+- if ( loaders != null && loaders.length > 0) Thread.currentThread().setContextClassLoader(loaders[0]);
+- if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.eventType",getName(), msg.getEventTypeString(), sender));
+-
+- switch (msg.getEventType()) {
+- case SessionMessage.EVT_GET_ALL_SESSIONS: {
+- handleGET_ALL_SESSIONS(msg,sender);
+- break;
+- }
+- case SessionMessage.EVT_ALL_SESSION_DATA: {
+- handleALL_SESSION_DATA(msg,sender);
+- break;
+- }
+- case SessionMessage.EVT_ALL_SESSION_TRANSFERCOMPLETE: {
+- handleALL_SESSION_TRANSFERCOMPLETE(msg,sender);
+- break;
+- }
+- case SessionMessage.EVT_SESSION_CREATED: {
+- handleSESSION_CREATED(msg,sender);
+- break;
+- }
+- case SessionMessage.EVT_SESSION_EXPIRED: {
+- handleSESSION_EXPIRED(msg,sender);
+- break;
+- }
+- case SessionMessage.EVT_SESSION_ACCESSED: {
+- handleSESSION_ACCESSED(msg,sender);
+- break;
+- }
+- case SessionMessage.EVT_SESSION_DELTA: {
+- handleSESSION_DELTA(msg,sender);
+- break;
+- }
+- default: {
+- //we didn't recognize the message type, do nothing
+- break;
+- }
+- } //switch
+- } catch (Exception x) {
+- log.error(sm.getString("deltaManager.receiveMessage.error",getName()), x);
+- } finally {
+- Thread.currentThread().setContextClassLoader(contextLoader);
+- }
+- }
+-
+- // -------------------------------------------------------- message receiver handler
+-
+-
+- /**
+- * handle receive session state is complete transfered
+- * @param msg
+- * @param sender
+- */
+- protected void handleALL_SESSION_TRANSFERCOMPLETE(SessionMessage msg, Member sender) {
+- counterReceive_EVT_ALL_SESSION_TRANSFERCOMPLETE++ ;
+- if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.transfercomplete",getName(), sender.getHost(), new Integer(sender.getPort())));
+- stateTransferCreateSendTime = msg.getTimestamp() ;
+- stateTransfered = true ;
+- }
+-
+- /**
+- * handle receive session delta
+- * @param msg
+- * @param sender
+- * @throws IOException
+- * @throws ClassNotFoundException
+- */
+- protected void handleSESSION_DELTA(SessionMessage msg, Member sender) throws IOException, ClassNotFoundException {
+- counterReceive_EVT_SESSION_DELTA++;
+- byte[] delta = msg.getSession();
+- DeltaSession session = (DeltaSession) findSession(msg.getSessionID());
+- if (session != null) {
+- if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.delta",getName(), msg.getSessionID()));
+- DeltaRequest dreq = deserializeDeltaRequest(session, delta);
+- dreq.execute(session, notifyListenersOnReplication);
+- session.setPrimarySession(false);
+- }
+- }
+-
+- /**
+- * handle receive session is access at other node ( primary session is now false)
+- * @param msg
+- * @param sender
+- * @throws IOException
+- */
+- protected void handleSESSION_ACCESSED(SessionMessage msg,Member sender) throws IOException {
+- counterReceive_EVT_SESSION_ACCESSED++;
+- DeltaSession session = (DeltaSession) findSession(msg.getSessionID());
+- if (session != null) {
+- if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.accessed",getName(), msg.getSessionID()));
+- session.access();
+- session.setPrimarySession(false);
+- session.endAccess();
+- }
+- }
+-
+- /**
+- * handle receive session is expire at other node ( expire session also here)
+- * @param msg
+- * @param sender
+- * @throws IOException
+- */
+- protected void handleSESSION_EXPIRED(SessionMessage msg,Member sender) throws IOException {
+- counterReceive_EVT_SESSION_EXPIRED++;
+- DeltaSession session = (DeltaSession) findSession(msg.getSessionID());
+- if (session != null) {
+- if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.expired",getName(), msg.getSessionID()));
+- session.expire(notifySessionListenersOnReplication, false);
+- }
+- }
+-
+- /**
+- * handle receive new session is created at other node (create backup - primary false)
+- * @param msg
+- * @param sender
+- */
+- protected void handleSESSION_CREATED(SessionMessage msg,Member sender) {
+- counterReceive_EVT_SESSION_CREATED++;
+- if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.createNewSession",getName(), msg.getSessionID()));
+- DeltaSession session = (DeltaSession) createEmptySession();
+- session.setManager(this);
+- session.setValid(true);
+- session.setPrimarySession(false);
+- session.setCreationTime(msg.getTimestamp());
+- // use container maxInactiveInterval so that session will expire correctly in case of primary transfer
+- session.setMaxInactiveInterval(getMaxInactiveInterval());
+- session.access();
+- if(notifySessionListenersOnReplication)
+- session.setId(msg.getSessionID());
+- else
+- session.setIdInternal(msg.getSessionID());
+- session.resetDeltaRequest();
+- session.endAccess();
+-
+- }
+-
+- /**
+- * handle receive sessions from other not ( restart )
+- * @param msg
+- * @param sender
+- * @throws ClassNotFoundException
+- * @throws IOException
+- */
+- protected void handleALL_SESSION_DATA(SessionMessage msg,Member sender) throws ClassNotFoundException, IOException {
+- counterReceive_EVT_ALL_SESSION_DATA++;
+- if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.allSessionDataBegin",getName()));
+- byte[] data = msg.getSession();
+- deserializeSessions(data);
+- if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.allSessionDataAfter",getName()));
+- //stateTransferred = true;
+- }
+-
+- /**
+- * handle receive that other node want all sessions ( restart )
+- * a) send all sessions with one message
+- * b) send session at blocks
+- * After sending send state is complete transfered
+- * @param msg
+- * @param sender
+- * @throws IOException
+- */
+- protected void handleGET_ALL_SESSIONS(SessionMessage msg, Member sender) throws IOException {
+- counterReceive_EVT_GET_ALL_SESSIONS++;
+- //get a list of all the session from this manager
+- if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.unloadingBegin", getName()));
+- // Write the number of active sessions, followed by the details
+- // get all sessions and serialize without sync
+- Session[] currentSessions = findSessions();
+- long findSessionTimestamp = System.currentTimeMillis() ;
+- if (isSendAllSessions()) {
+- sendSessions(sender, currentSessions, findSessionTimestamp);
+- } else {
+- // send session at blocks
+- int len = currentSessions.length < getSendAllSessionsSize() ? currentSessions.length : getSendAllSessionsSize();
+- Session[] sendSessions = new Session[len];
+- for (int i = 0; i < currentSessions.length; i += getSendAllSessionsSize()) {
+- len = i + getSendAllSessionsSize() > currentSessions.length ? currentSessions.length - i : getSendAllSessionsSize();
+- System.arraycopy(currentSessions, i, sendSessions, 0, len);
+- sendSessions(sender, sendSessions,findSessionTimestamp);
+- if (getSendAllSessionsWaitTime() > 0) {
+- try {
+- Thread.sleep(getSendAllSessionsWaitTime());
+- } catch (Exception sleep) {
+- }
+- }//end if
+- }//for
+- }//end if
+-
+- SessionMessage newmsg = new SessionMessageImpl(name,SessionMessage.EVT_ALL_SESSION_TRANSFERCOMPLETE, null,"SESSION-STATE-TRANSFERED", "SESSION-STATE-TRANSFERED"+ getName());
+- newmsg.setTimestamp(findSessionTimestamp);
+- if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.createMessage.allSessionTransfered",getName()));
+- counterSend_EVT_ALL_SESSION_TRANSFERCOMPLETE++;
+- cluster.send(newmsg, sender);
+- }
+-
+-
+- /**
+- * send a block of session to sender
+- * @param sender
+- * @param currentSessions
+- * @param sendTimestamp
+- * @throws IOException
+- */
+- protected void sendSessions(Member sender, Session[] currentSessions,long sendTimestamp) throws IOException {
+- byte[] data = serializeSessions(currentSessions);
+- if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.receiveMessage.unloadingAfter",getName()));
+- SessionMessage newmsg = new SessionMessageImpl(name,SessionMessage.EVT_ALL_SESSION_DATA, data,"SESSION-STATE", "SESSION-STATE-" + getName());
+- newmsg.setTimestamp(sendTimestamp);
+- if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.createMessage.allSessionData",getName()));
+- counterSend_EVT_ALL_SESSION_DATA++;
+- cluster.send(newmsg, sender);
+- }
+-
+- public ClusterManager cloneFromTemplate() {
+- DeltaManager result = new DeltaManager();
+- result.name = "Clone-from-"+name;
+- result.cluster = cluster;
+- result.replicationValve = replicationValve;
+- result.maxActiveSessions = maxActiveSessions;
+- result.expireSessionsOnShutdown = expireSessionsOnShutdown;
+- result.notifyListenersOnReplication = notifyListenersOnReplication;
+- result.notifySessionListenersOnReplication = notifySessionListenersOnReplication;
+- result.stateTransferTimeout = stateTransferTimeout;
+- result.sendAllSessions = sendAllSessions;
+- result.sendClusterDomainOnly = sendClusterDomainOnly ;
+- result.sendAllSessionsSize = sendAllSessionsSize;
+- result.sendAllSessionsWaitTime = sendAllSessionsWaitTime ;
+- result.receiverQueue = receiverQueue ;
+- result.stateTimestampDrop = stateTimestampDrop ;
+- result.stateTransferCreateSendTime = stateTransferCreateSendTime;
+- return result;
+- }
+-}
+Index: java/org/apache/catalina/ha/session/SimpleTcpReplicationManager.java
+===================================================================
+--- java/org/apache/catalina/ha/session/SimpleTcpReplicationManager.java (revision 590752)
++++ java/org/apache/catalina/ha/session/SimpleTcpReplicationManager.java (working copy)
+@@ -1,685 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.ha.session;
+-
+-import java.io.IOException;
+-
+-import org.apache.catalina.LifecycleException;
+-import org.apache.catalina.Session;
+-import org.apache.catalina.ha.CatalinaCluster;
+-import org.apache.catalina.ha.ClusterManager;
+-import org.apache.catalina.ha.ClusterMessage;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.realm.GenericPrincipal;
+-import org.apache.catalina.session.StandardManager;
+-import org.apache.catalina.tribes.io.ReplicationStream;
+-import java.io.ByteArrayInputStream;
+-import org.apache.catalina.Loader;
+-
+-/**
+- * Title: Tomcat Session Replication for Tomcat 4.0 <BR>
+- * Description: A very simple straight forward implementation of
+- * session replication of servers in a cluster.<BR>
+- * This session replication is implemented "live". By live
+- * I mean, when a session attribute is added into a session on Node A
+- * a message is broadcasted to other messages and setAttribute is called on the
+- * replicated sessions.<BR>
+- * A full description of this implementation can be found under
+- * <href="http://www.filip.net/tomcat/">Filip's Tomcat Page</a><BR>
+- *
+- * Copyright: See apache license
+- * Company: www.filip.net
+- * @author <a href="mailto:mail@filip.net">Filip Hanik</a>
+- * @author Bela Ban (modifications for synchronous replication)
+- * @version 1.0 for TC 4.0
+- * Description: The InMemoryReplicationManager is a session manager that replicated
+- * session information in memory.
+- * <BR><BR>
+- * The InMemoryReplicationManager extends the StandardManager hence it allows for us
+- * to inherit all the basic session management features like expiration, session listeners etc
+- * <BR><BR>
+- * To communicate with other nodes in the cluster, the InMemoryReplicationManager sends out 7 different type of multicast messages
+- * all defined in the SessionMessage class.<BR>
+- * When a session is replicated (not an attribute added/removed) the session is serialized into
+- * a byte array using the StandardSession.readObjectData, StandardSession.writeObjectData methods.
+- */
+-public class SimpleTcpReplicationManager extends StandardManager implements ClusterManager
+-{
+- public static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog( SimpleTcpReplicationManager.class );
+-
+- //the channel configuration
+- protected String mChannelConfig = null;
+-
+- //the group name
+- protected String mGroupName = "TomcatReplication";
+-
+- //somehow start() gets called more than once
+- protected boolean mChannelStarted = false;
+-
+- //log to screen
+- protected boolean mPrintToScreen = true;
+-
+- protected boolean defaultMode = false;
+-
+- protected boolean mManagerRunning = false;
+-
+- /** Use synchronous rather than asynchronous replication. Every session modification (creation, change, removal etc)
+- * will be sent to all members. The call will then wait for max milliseconds, or forever (if timeout is 0) for
+- * all responses.
+- */
+- protected boolean synchronousReplication=true;
+-
+- /** Set to true if we don't want the sessions to expire on shutdown */
+- protected boolean mExpireSessionsOnShutdown = true;
+-
+- protected boolean useDirtyFlag = false;
+-
+- protected String name;
+-
+- protected boolean distributable = true;
+-
+- protected CatalinaCluster cluster;
+-
+- protected java.util.HashMap invalidatedSessions = new java.util.HashMap();
+-
+- /**
+- * Flag to keep track if the state has been transferred or not
+- * Assumes false.
+- */
+- protected boolean stateTransferred = false;
+- private boolean notifyListenersOnReplication;
+- private boolean sendClusterDomainOnly = true ;
+-
+- /**
+- * Constructor, just calls super()
+- *
+- */
+- public SimpleTcpReplicationManager()
+- {
+- super();
+- }
+-
+- public boolean doDomainReplication() {
+- return sendClusterDomainOnly;
+- }
+-
+- /**
+- * @param sendClusterDomainOnly The sendClusterDomainOnly to set.
+- */
+- public void setDomainReplication(boolean sendClusterDomainOnly) {
+- this.sendClusterDomainOnly = sendClusterDomainOnly;
+- }
+-
+- /**
+- * @return Returns the defaultMode.
+- */
+- public boolean isDefaultMode() {
+- return defaultMode;
+- }
+- /**
+- * @param defaultMode The defaultMode to set.
+- */
+- public void setDefaultMode(boolean defaultMode) {
+- this.defaultMode = defaultMode;
+- }
+-
+- public boolean isManagerRunning()
+- {
+- return mManagerRunning;
+- }
+-
+- public void setUseDirtyFlag(boolean usedirtyflag)
+- {
+- this.useDirtyFlag = usedirtyflag;
+- }
+-
+- public void setExpireSessionsOnShutdown(boolean expireSessionsOnShutdown)
+- {
+- mExpireSessionsOnShutdown = expireSessionsOnShutdown;
+- }
+-
+- public void setCluster(CatalinaCluster cluster) {
+- if(log.isDebugEnabled())
+- log.debug("Cluster associated with SimpleTcpReplicationManager");
+- this.cluster = cluster;
+- }
+-
+- public boolean getExpireSessionsOnShutdown()
+- {
+- return mExpireSessionsOnShutdown;
+- }
+-
+- public void setPrintToScreen(boolean printtoscreen)
+- {
+- if(log.isDebugEnabled())
+- log.debug("Setting screen debug to:"+printtoscreen);
+- mPrintToScreen = printtoscreen;
+- }
+-
+- public void setSynchronousReplication(boolean flag)
+- {
+- synchronousReplication=flag;
+- }
+-
+- /**
+- * Override persistence since they don't go hand in hand with replication for now.
+- */
+- public void unload() throws IOException {
+- if ( !getDistributable() ) {
+- super.unload();
+- }
+- }
+-
+- /**
+- * Creates a HTTP session.
+- * Most of the code in here is copied from the StandardManager.
+- * This is not pretty, yeah I know, but it was necessary since the
+- * StandardManager had hard coded the session instantiation to the a
+- * StandardSession, when we actually want to instantiate a ReplicatedSession<BR>
+- * If the call comes from the Tomcat servlet engine, a SessionMessage goes out to the other
+- * nodes in the cluster that this session has been created.
+- * @param notify - if set to true the other nodes in the cluster will be notified.
+- * This flag is needed so that we can create a session before we deserialize
+- * a replicated one
+- *
+- * @see ReplicatedSession
+- */
+- protected Session createSession(String sessionId, boolean notify, boolean setId)
+- {
+-
+- //inherited from the basic manager
+- if ((getMaxActiveSessions() >= 0) &&
+- (sessions.size() >= getMaxActiveSessions()))
+- throw new IllegalStateException(sm.getString("standardManager.createSession.ise"));
+-
+-
+- Session session = new ReplicatedSession(this);
+-
+- // Initialize the properties of the new session and return it
+- session.setNew(true);
+- session.setValid(true);
+- session.setCreationTime(System.currentTimeMillis());
+- session.setMaxInactiveInterval(this.maxInactiveInterval);
+- if(sessionId == null)
+- sessionId = generateSessionId();
+- if ( setId ) session.setId(sessionId);
+- if ( notify && (cluster!=null) ) {
+- ((ReplicatedSession)session).setIsDirty(true);
+- }
+- return (session);
+- }//createSession
+-
+- //=========================================================================
+- // OVERRIDE THESE METHODS TO IMPLEMENT THE REPLICATION
+- //=========================================================================
+-
+- /**
+- * Construct and return a new session object, based on the default
+- * settings specified by this Manager's properties. The session
+- * id will be assigned by this method, and available via the getId()
+- * method of the returned session. If a new session cannot be created
+- * for any reason, return <code>null</code>.
+- *
+- * @exception IllegalStateException if a new session cannot be
+- * instantiated for any reason
+- */
+- public Session createSession(String sessionId)
+- {
+- //create a session and notify the other nodes in the cluster
+- Session session = createSession(sessionId,getDistributable(),true);
+- add(session);
+- return session;
+- }
+-
+- public void sessionInvalidated(String sessionId) {
+- synchronized ( invalidatedSessions ) {
+- invalidatedSessions.put(sessionId, sessionId);
+- }
+- }
+-
+- public String[] getInvalidatedSessions() {
+- synchronized ( invalidatedSessions ) {
+- String[] result = new String[invalidatedSessions.size()];
+- invalidatedSessions.values().toArray(result);
+- return result;
+- }
+-
+- }
+-
+- public ClusterMessage requestCompleted(String sessionId)
+- {
+- if ( !getDistributable() ) {
+- log.warn("Received requestCompleted message, although this context["+
+- getName()+"] is not distributable. Ignoring message");
+- return null;
+- }
+- try
+- {
+- if ( invalidatedSessions.get(sessionId) != null ) {
+- synchronized ( invalidatedSessions ) {
+- invalidatedSessions.remove(sessionId);
+- SessionMessage msg = new SessionMessageImpl(name,
+- SessionMessage.EVT_SESSION_EXPIRED,
+- null,
+- sessionId,
+- sessionId);
+- return msg;
+- }
+- } else {
+- ReplicatedSession session = (ReplicatedSession) findSession(
+- sessionId);
+- if (session != null) {
+- //return immediately if the session is not dirty
+- if (useDirtyFlag && (!session.isDirty())) {
+- //but before we return doing nothing,
+- //see if we should send
+- //an updated last access message so that
+- //sessions across cluster dont expire
+- long interval = session.getMaxInactiveInterval();
+- long lastaccdist = System.currentTimeMillis() -
+- session.getLastAccessWasDistributed();
+- if ( ((interval*1000) / lastaccdist)< 3 ) {
+- SessionMessage accmsg = new SessionMessageImpl(name,
+- SessionMessage.EVT_SESSION_ACCESSED,
+- null,
+- sessionId,
+- sessionId);
+- session.setLastAccessWasDistributed(System.currentTimeMillis());
+- return accmsg;
+- }
+- return null;
+- }
+-
+- session.setIsDirty(false);
+- if (log.isDebugEnabled()) {
+- try {
+- log.debug("Sending session to cluster=" + session);
+- }
+- catch (Exception ignore) {}
+- }
+- SessionMessage msg = new SessionMessageImpl(name,
+- SessionMessage.EVT_SESSION_CREATED,
+- writeSession(session),
+- session.getIdInternal(),
+- session.getIdInternal());
+- return msg;
+- } //end if
+- }//end if
+- }
+- catch (Exception x )
+- {
+- log.error("Unable to replicate session",x);
+- }
+- return null;
+- }
+-
+- /**
+- * Serialize a session into a byte array<BR>
+- * This method simple calls the writeObjectData method on the session
+- * and returns the byte data from that call
+- * @param session - the session to be serialized
+- * @return a byte array containing the session data, null if the serialization failed
+- */
+- protected byte[] writeSession( Session session )
+- {
+- try
+- {
+- java.io.ByteArrayOutputStream session_data = new java.io.ByteArrayOutputStream();
+- java.io.ObjectOutputStream session_out = new java.io.ObjectOutputStream(session_data);
+- session_out.flush();
+- boolean hasPrincipal = session.getPrincipal() != null;
+- session_out.writeBoolean(hasPrincipal);
+- if ( hasPrincipal )
+- {
+- session_out.writeObject(SerializablePrincipal.createPrincipal((GenericPrincipal)session.getPrincipal()));
+- }//end if
+- ((ReplicatedSession)session).writeObjectData(session_out);
+- return session_data.toByteArray();
+-
+- }
+- catch ( Exception x )
+- {
+- log.error("Failed to serialize the session!",x);
+- }
+- return null;
+- }
+-
+- /**
+- * Open Stream and use correct ClassLoader (Container) Switch
+- * ThreadClassLoader
+- *
+- * @param data
+- * @return The object input stream
+- * @throws IOException
+- */
+- public ReplicationStream getReplicationStream(byte[] data) throws IOException {
+- return getReplicationStream(data,0,data.length);
+- }
+-
+- public ReplicationStream getReplicationStream(byte[] data, int offset, int length) throws IOException {
+- ByteArrayInputStream fis =null;
+- ReplicationStream ois = null;
+- Loader loader = null;
+- ClassLoader classLoader = null;
+- //fix to be able to run the DeltaManager
+- //stand alone without a container.
+- //use the Threads context class loader
+- if (container != null)
+- loader = container.getLoader();
+- if (loader != null)
+- classLoader = loader.getClassLoader();
+- else
+- classLoader = Thread.currentThread().getContextClassLoader();
+- //end fix
+- fis = new ByteArrayInputStream(data, offset, length);
+- if ( classLoader == Thread.currentThread().getContextClassLoader() ) {
+- ois = new ReplicationStream(fis, new ClassLoader[] {classLoader});
+- } else {
+- ois = new ReplicationStream(fis, new ClassLoader[] {classLoader,Thread.currentThread().getContextClassLoader()});
+- }
+- return ois;
+- }
+-
+-
+-
+-
+- /**
+- * Reinstantiates a serialized session from the data passed in.
+- * This will first call createSession() so that we get a fresh instance with all
+- * the managers set and all the transient fields validated.
+- * Then it calls Session.readObjectData(byte[]) to deserialize the object
+- * @param data - a byte array containing session data
+- * @return a valid Session object, null if an error occurs
+- *
+- */
+- protected Session readSession( byte[] data, String sessionId )
+- {
+- try
+- {
+- ReplicationStream session_in = getReplicationStream(data);
+-
+- Session session = sessionId!=null?this.findSession(sessionId):null;
+- boolean isNew = (session==null);
+- //clear the old values from the existing session
+- if ( session!=null ) {
+- ReplicatedSession rs = (ReplicatedSession)session;
+- rs.expire(false); //cleans up the previous values, since we are not doing removes
+- session = null;
+- }//end if
+-
+- if (session==null) {
+- session = createSession(null,false, false);
+- sessions.remove(session.getIdInternal());
+- }
+-
+-
+- boolean hasPrincipal = session_in.readBoolean();
+- SerializablePrincipal p = null;
+- if ( hasPrincipal )
+- p = (SerializablePrincipal)session_in.readObject();
+- ((ReplicatedSession)session).readObjectData(session_in);
+- if ( hasPrincipal )
+- session.setPrincipal(p.getPrincipal(getContainer().getRealm()));
+- ((ReplicatedSession)session).setId(sessionId,isNew);
+- ReplicatedSession rsession = (ReplicatedSession)session;
+- rsession.setAccessCount(1);
+- session.setManager(this);
+- session.setValid(true);
+- rsession.setLastAccessedTime(System.currentTimeMillis());
+- rsession.setThisAccessedTime(System.currentTimeMillis());
+- ((ReplicatedSession)session).setAccessCount(0);
+- session.setNew(false);
+- if(log.isTraceEnabled())
+- log.trace("Session loaded id="+sessionId +
+- " actualId="+session.getId()+
+- " exists="+this.sessions.containsKey(sessionId)+
+- " valid="+rsession.isValid());
+- return session;
+-
+- }
+- catch ( Exception x )
+- {
+- log.error("Failed to deserialize the session!",x);
+- }
+- return null;
+- }
+-
+- public String getName() {
+- return this.name;
+- }
+- /**
+- * Prepare for the beginning of active use of the public methods of this
+- * component. This method should be called after <code>configure()</code>,
+- * and before any of the public methods of the component are utilized.<BR>
+- * Starts the cluster communication channel, this will connect with the other nodes
+- * in the cluster, and request the current session state to be transferred to this node.
+- * @exception IllegalStateException if this component has already been
+- * started
+- * @exception LifecycleException if this component detects a fatal error
+- * that prevents this component from being used
+- */
+- public void start() throws LifecycleException {
+- mManagerRunning = true;
+- super.start();
+- try {
+- //the channel is already running
+- if ( mChannelStarted ) return;
+- if(log.isInfoEnabled())
+- log.info("Starting clustering manager...:"+getName());
+- if ( cluster == null ) {
+- log.error("Starting... no cluster associated with this context:"+getName());
+- return;
+- }
+- cluster.registerManager(this);
+-
+- if (cluster.getMembers().length > 0) {
+- Member mbr = cluster.getMembers()[0];
+- SessionMessage msg =
+- new SessionMessageImpl(this.getName(),
+- SessionMessage.EVT_GET_ALL_SESSIONS,
+- null,
+- "GET-ALL",
+- "GET-ALL-"+this.getName());
+- cluster.send(msg, mbr);
+- if(log.isWarnEnabled())
+- log.warn("Manager["+getName()+"], requesting session state from "+mbr+
+- ". This operation will timeout if no session state has been received within "+
+- "60 seconds");
+- long reqStart = System.currentTimeMillis();
+- long reqNow = 0;
+- boolean isTimeout=false;
+- do {
+- try {
+- Thread.sleep(100);
+- }catch ( Exception sleep) {}
+- reqNow = System.currentTimeMillis();
+- isTimeout=((reqNow-reqStart)>(1000*60));
+- } while ( (!isStateTransferred()) && (!isTimeout));
+- if ( isTimeout || (!isStateTransferred()) ) {
+- log.error("Manager["+getName()+"], No session state received, timing out.");
+- }else {
+- if(log.isInfoEnabled())
+- log.info("Manager["+getName()+"], session state received in "+(reqNow-reqStart)+" ms.");
+- }
+- } else {
+- if(log.isInfoEnabled())
+- log.info("Manager["+getName()+"], skipping state transfer. No members active in cluster group.");
+- }//end if
+- mChannelStarted = true;
+- } catch ( Exception x ) {
+- log.error("Unable to start SimpleTcpReplicationManager",x);
+- }
+- }
+-
+- /**
+- * Gracefully terminate the active use of the public methods of this
+- * component. This method should be the last one called on a given
+- * instance of this component.<BR>
+- * This will disconnect the cluster communication channel and stop the listener thread.
+- * @exception IllegalStateException if this component has not been started
+- * @exception LifecycleException if this component detects a fatal error
+- * that needs to be reported
+- */
+- public void stop() throws LifecycleException
+- {
+- mManagerRunning = false;
+- mChannelStarted = false;
+- super.stop();
+- try
+- {
+- this.sessions.clear();
+- cluster.removeManager(this);
+- }
+- catch ( Exception x )
+- {
+- log.error("Unable to stop SimpleTcpReplicationManager",x);
+- }
+- }
+-
+- public void setDistributable(boolean dist) {
+- this.distributable = dist;
+- }
+-
+- public boolean getDistributable() {
+- return distributable;
+- }
+-
+- /**
+- * This method is called by the received thread when a SessionMessage has
+- * been received from one of the other nodes in the cluster.
+- * @param msg - the message received
+- * @param sender - the sender of the message, this is used if we receive a
+- * EVT_GET_ALL_SESSION message, so that we only reply to
+- * the requesting node
+- */
+- protected void messageReceived( SessionMessage msg, Member sender ) {
+- try {
+- if(log.isInfoEnabled()) {
+- log.debug("Received SessionMessage of type="+msg.getEventTypeString());
+- log.debug("Received SessionMessage sender="+sender);
+- }
+- switch ( msg.getEventType() ) {
+- case SessionMessage.EVT_GET_ALL_SESSIONS: {
+- //get a list of all the session from this manager
+- Object[] sessions = findSessions();
+- java.io.ByteArrayOutputStream bout = new java.io.ByteArrayOutputStream();
+- java.io.ObjectOutputStream oout = new java.io.ObjectOutputStream(bout);
+- oout.writeInt(sessions.length);
+- for (int i=0; i<sessions.length; i++){
+- ReplicatedSession ses = (ReplicatedSession)sessions[i];
+- oout.writeUTF(ses.getIdInternal());
+- byte[] data = writeSession(ses);
+- oout.writeObject(data);
+- }//for
+- //don't send a message if we don't have to
+- oout.flush();
+- oout.close();
+- byte[] data = bout.toByteArray();
+- SessionMessage newmsg = new SessionMessageImpl(name,
+- SessionMessage.EVT_ALL_SESSION_DATA,
+- data, "SESSION-STATE","SESSION-STATE-"+getName());
+- cluster.send(newmsg, sender);
+- break;
+- }
+- case SessionMessage.EVT_ALL_SESSION_DATA: {
+- java.io.ByteArrayInputStream bin =
+- new java.io.ByteArrayInputStream(msg.getSession());
+- java.io.ObjectInputStream oin = new java.io.ObjectInputStream(bin);
+- int size = oin.readInt();
+- for ( int i=0; i<size; i++) {
+- String id = oin.readUTF();
+- byte[] data = (byte[])oin.readObject();
+- Session session = readSession(data,id);
+- }//for
+- stateTransferred=true;
+- break;
+- }
+- case SessionMessage.EVT_SESSION_CREATED: {
+- Session session = this.readSession(msg.getSession(),msg.getSessionID());
+- if ( log.isDebugEnabled() ) {
+- log.debug("Received replicated session=" + session +
+- " isValid=" + session.isValid());
+- }
+- break;
+- }
+- case SessionMessage.EVT_SESSION_EXPIRED: {
+- Session session = findSession(msg.getSessionID());
+- if ( session != null ) {
+- session.expire();
+- this.remove(session);
+- }//end if
+- break;
+- }
+- case SessionMessage.EVT_SESSION_ACCESSED :{
+- Session session = findSession(msg.getSessionID());
+- if ( session != null ) {
+- session.access();
+- session.endAccess();
+- }
+- break;
+- }
+- default: {
+- //we didn't recognize the message type, do nothing
+- break;
+- }
+- }//switch
+- }
+- catch ( Exception x )
+- {
+- log.error("Unable to receive message through TCP channel",x);
+- }
+- }
+-
+- public void messageDataReceived(ClusterMessage cmsg) {
+- try {
+- if ( cmsg instanceof SessionMessage ) {
+- SessionMessage msg = (SessionMessage)cmsg;
+- messageReceived(msg,
+- msg.getAddress() != null ? (Member) msg.getAddress() : null);
+- }
+- } catch(Throwable ex){
+- log.error("InMemoryReplicationManager.messageDataReceived()", ex);
+- }//catch
+- }
+-
+- public boolean isStateTransferred() {
+- return stateTransferred;
+- }
+-
+- public void setName(String name) {
+- this.name = name;
+- }
+- public boolean isNotifyListenersOnReplication() {
+- return notifyListenersOnReplication;
+- }
+- public void setNotifyListenersOnReplication(boolean notifyListenersOnReplication) {
+- this.notifyListenersOnReplication = notifyListenersOnReplication;
+- }
+-
+-
+- /*
+- * @see org.apache.catalina.ha.ClusterManager#getCluster()
+- */
+- public CatalinaCluster getCluster() {
+- return cluster;
+- }
+-
+- public ClusterManager cloneFromTemplate() {
+- throw new UnsupportedOperationException();
+- }
+-
+-}
+Index: java/org/apache/catalina/ha/session/JvmRouteSessionIDBinderListener.java
+===================================================================
+--- java/org/apache/catalina/ha/session/JvmRouteSessionIDBinderListener.java (revision 590752)
++++ java/org/apache/catalina/ha/session/JvmRouteSessionIDBinderListener.java (working copy)
+@@ -1,167 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.ha.session;
+-
+-import java.io.IOException;
+-
+-import org.apache.catalina.Container;
+-import org.apache.catalina.Context;
+-import org.apache.catalina.Engine;
+-import org.apache.catalina.LifecycleException;
+-import org.apache.catalina.Session;
+-import org.apache.catalina.ha.ClusterMessage;
+-import org.apache.catalina.core.StandardEngine;
+-import org.apache.catalina.ha.*;
+-
+-/**
+- * Receive SessionID cluster change from other backup node after primary session
+- * node is failed.
+- *
+- * @author Peter Rossbach
+- * @version $Revision$ $Date$
+- */
+-public class JvmRouteSessionIDBinderListener extends ClusterListener {
+-
+- /**
+- * The descriptive information about this implementation.
+- */
+- protected static final String info = "org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener/1.1";
+-
+- //--Instance Variables--------------------------------------
+-
+-
+- protected boolean started = false;
+-
+- /**
+- * number of session that goes to this cluster node
+- */
+- private long numberOfSessions = 0;
+-
+- //--Constructor---------------------------------------------
+-
+- public JvmRouteSessionIDBinderListener() {
+- }
+-
+- //--Logic---------------------------------------------------
+-
+- /**
+- * Return descriptive information about this implementation.
+- */
+- public String getInfo() {
+-
+- return (info);
+-
+- }
+-
+- /**
+- * @return Returns the numberOfSessions.
+- */
+- public long getNumberOfSessions() {
+- return numberOfSessions;
+- }
+-
+- /**
+- * Add this Mover as Cluster Listener ( receiver)
+- *
+- * @throws LifecycleException
+- */
+- public void start() throws LifecycleException {
+- if (started)
+- return;
+- getCluster().addClusterListener(this);
+- started = true;
+- if (log.isInfoEnabled())
+- log.info(sm.getString("jvmRoute.clusterListener.started"));
+- }
+-
+- /**
+- * Remove this from Cluster Listener
+- *
+- * @throws LifecycleException
+- */
+- public void stop() throws LifecycleException {
+- started = false;
+- getCluster().removeClusterListener(this);
+- if (log.isInfoEnabled())
+- log.info(sm.getString("jvmRoute.clusterListener.stopped"));
+- }
+-
+- /**
+- * Callback from the cluster, when a message is received, The cluster will
+- * broadcast it invoking the messageReceived on the receiver.
+- *
+- * @param msg
+- * ClusterMessage - the message received from the cluster
+- */
+- public void messageReceived(ClusterMessage msg) {
+- if (msg instanceof SessionIDMessage && msg != null) {
+- SessionIDMessage sessionmsg = (SessionIDMessage) msg;
+- if (log.isDebugEnabled())
+- log.debug(sm.getString(
+- "jvmRoute.receiveMessage.sessionIDChanged", sessionmsg
+- .getOrignalSessionID(), sessionmsg
+- .getBackupSessionID(), sessionmsg
+- .getContextPath()));
+- Container container = getCluster().getContainer();
+- Container host = null ;
+- if(container instanceof Engine) {
+- host = container.findChild(sessionmsg.getHost());
+- } else {
+- host = container ;
+- }
+- if (host != null) {
+- Context context = (Context) host.findChild(sessionmsg
+- .getContextPath());
+- if (context != null) {
+- try {
+- Session session = context.getManager().findSession(
+- sessionmsg.getOrignalSessionID());
+- if (session != null) {
+- session.setId(sessionmsg.getBackupSessionID());
+- } else if (log.isInfoEnabled())
+- log.info(sm.getString("jvmRoute.lostSession",
+- sessionmsg.getOrignalSessionID(),
+- sessionmsg.getContextPath()));
+- } catch (IOException e) {
+- log.error(e);
+- }
+-
+- } else if (log.isErrorEnabled())
+- log.error(sm.getString("jvmRoute.contextNotFound",
+- sessionmsg.getContextPath(), ((StandardEngine) host
+- .getParent()).getJvmRoute()));
+- } else if (log.isErrorEnabled())
+- log.error(sm.getString("jvmRoute.hostNotFound", sessionmsg.getContextPath()));
+- }
+- return;
+- }
+-
+- /**
+- * Accept only SessionIDMessages
+- *
+- * @param msg
+- * ClusterMessage
+- * @return boolean - returns true to indicate that messageReceived should be
+- * invoked. If false is returned, the messageReceived method will
+- * not be invoked.
+- */
+- public boolean accept(ClusterMessage msg) {
+- return (msg instanceof SessionIDMessage);
+- }
+-}
+-
+Index: java/org/apache/catalina/ha/session/SessionMessage.java
+===================================================================
+--- java/org/apache/catalina/ha/session/SessionMessage.java (revision 590752)
++++ java/org/apache/catalina/ha/session/SessionMessage.java (working copy)
+@@ -1,104 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.ha.session;
+-import org.apache.catalina.ha.ClusterMessage;
+-
+-/**
+- *
+- * <B>Class Description:</B><BR>
+- * The SessionMessage class is a class that is used when a session has been
+- * created, modified, expired in a Tomcat cluster node.<BR>
+- *
+- * The following events are currently available:
+- * <ul>
+- * <li><pre>public static final int EVT_SESSION_CREATED</pre><li>
+- * <li><pre>public static final int EVT_SESSION_ACCESSED</pre><li>
+- * <li><pre>public static final int EVT_ATTRIBUTE_ADDED</pre><li>
+- * <li><pre>public static final int EVT_ATTRIBUTE_REMOVED</pre><li>
+- * <li><pre>public static final int EVT_SESSION_EXPIRED_WONOTIFY</pre><li>
+- * <li><pre>public static final int EVT_SESSION_EXPIRED_WNOTIFY</pre><li>
+- * <li><pre>public static final int EVT_GET_ALL_SESSIONS</pre><li>
+- * <li><pre>public static final int EVT_SET_USER_PRINCIPAL</pre><li>
+- * <li><pre>public static final int EVT_SET_SESSION_NOTE</pre><li>
+- * <li><pre>public static final int EVT_REMOVE_SESSION_NOTE</pre><li>
+- * </ul>
+- *
+- */
+-
+-public interface SessionMessage extends ClusterMessage, java.io.Serializable
+-{
+-
+- /**
+- * Event type used when a session has been created on a node
+- */
+- public static final int EVT_SESSION_CREATED = 1;
+- /**
+- * Event type used when a session has expired
+- */
+- public static final int EVT_SESSION_EXPIRED = 2;
+-
+- /**
+- * Event type used when a session has been accessed (ie, last access time
+- * has been updated. This is used so that the replicated sessions will not expire
+- * on the network
+- */
+- public static final int EVT_SESSION_ACCESSED = 3;
+- /**
+- * Event type used when a server comes online for the first time.
+- * The first thing the newly started server wants to do is to grab the
+- * all the sessions from one of the nodes and keep the same state in there
+- */
+- public static final int EVT_GET_ALL_SESSIONS = 4;
+- /**
+- * Event type used when an attribute has been added to a session,
+- * the attribute will be sent to all the other nodes in the cluster
+- */
+- public static final int EVT_SESSION_DELTA = 13;
+-
+- /**
+- * When a session state is transferred, this is the event.
+- */
+- public static final int EVT_ALL_SESSION_DATA = 12;
+-
+- /**
+- * When a session state is complete transferred, this is the event.
+- */
+- public static final int EVT_ALL_SESSION_TRANSFERCOMPLETE = 14;
+-
+-
+-
+- public String getContextName();
+-
+- public String getEventTypeString();
+-
+- /**
+- * returns the event type
+- * @return one of the event types EVT_XXXX
+- */
+- public int getEventType();
+- /**
+- * @return the serialized data for the session
+- */
+- public byte[] getSession();
+- /**
+- * @return the session ID for the session
+- */
+- public String getSessionID();
+-
+-
+-
+-}//SessionMessage
+Index: java/org/apache/catalina/ha/session/ClusterSessionListener.java
+===================================================================
+--- java/org/apache/catalina/ha/session/ClusterSessionListener.java (revision 590752)
++++ java/org/apache/catalina/ha/session/ClusterSessionListener.java (working copy)
+@@ -1,108 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.ha.session;
+-
+-import java.util.Map;
+-
+-import org.apache.catalina.ha.ClusterManager;
+-import org.apache.catalina.ha.ClusterMessage;
+-import org.apache.catalina.ha.*;
+-
+-/**
+- * Receive replicated SessionMessage form other cluster node.
+- * @author Filip Hanik
+- * @author Peter Rossbach
+- * @version $Revision$ $Date$
+- */
+-public class ClusterSessionListener extends ClusterListener {
+-
+- /**
+- * The descriptive information about this implementation.
+- */
+- protected static final String info = "org.apache.catalina.session.ClusterSessionListener/1.1";
+-
+- //--Constructor---------------------------------------------
+-
+- public ClusterSessionListener() {
+- }
+-
+- //--Logic---------------------------------------------------
+-
+- /**
+- * Return descriptive information about this implementation.
+- */
+- public String getInfo() {
+-
+- return (info);
+-
+- }
+-
+- /**
+- * Callback from the cluster, when a message is received, The cluster will
+- * broadcast it invoking the messageReceived on the receiver.
+- *
+- * @param myobj
+- * ClusterMessage - the message received from the cluster
+- */
+- public void messageReceived(ClusterMessage myobj) {
+- if (myobj != null && myobj instanceof SessionMessage) {
+- SessionMessage msg = (SessionMessage) myobj;
+- String ctxname = msg.getContextName();
+- //check if the message is a EVT_GET_ALL_SESSIONS,
+- //if so, wait until we are fully started up
+- Map managers = cluster.getManagers() ;
+- if (ctxname == null) {
+- java.util.Iterator i = managers.keySet().iterator();
+- while (i.hasNext()) {
+- String key = (String) i.next();
+- ClusterManager mgr = (ClusterManager) managers.get(key);
+- if (mgr != null)
+- mgr.messageDataReceived(msg);
+- else {
+- //this happens a lot before the system has started
+- // up
+- if (log.isDebugEnabled())
+- log.debug("Context manager doesn't exist:"
+- + key);
+- }
+- }
+- } else {
+- ClusterManager mgr = (ClusterManager) managers.get(ctxname);
+- if (mgr != null)
+- mgr.messageDataReceived(msg);
+- else if (log.isWarnEnabled())
+- log.warn("Context manager doesn't exist:" + ctxname);
+- }
+- }
+- return;
+- }
+-
+- /**
+- * Accept only SessionMessage
+- *
+- * @param msg
+- * ClusterMessage
+- * @return boolean - returns true to indicate that messageReceived should be
+- * invoked. If false is returned, the messageReceived method will
+- * not be invoked.
+- */
+- public boolean accept(ClusterMessage msg) {
+- return (msg instanceof SessionMessage);
+- }
+-}
+-
+Index: java/org/apache/catalina/ha/session/JvmRouteBinderValve.java
+===================================================================
+--- java/org/apache/catalina/ha/session/JvmRouteBinderValve.java (revision 590752)
++++ java/org/apache/catalina/ha/session/JvmRouteBinderValve.java (working copy)
+@@ -1,544 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.ha.session;
+-
+-import java.io.IOException;
+-
+-import javax.servlet.ServletException;
+-import javax.servlet.http.Cookie;
+-
+-import org.apache.catalina.Container;
+-import org.apache.catalina.Context;
+-import org.apache.catalina.Engine;
+-import org.apache.catalina.Globals;
+-import org.apache.catalina.Host;
+-import org.apache.catalina.Lifecycle;
+-import org.apache.catalina.LifecycleException;
+-import org.apache.catalina.LifecycleListener;
+-import org.apache.catalina.Manager;
+-import org.apache.catalina.Session;
+-import org.apache.catalina.ha.CatalinaCluster;
+-import org.apache.catalina.ha.ClusterManager;
+-import org.apache.catalina.ha.ClusterMessage;
+-import org.apache.catalina.ha.ClusterValve;
+-import org.apache.catalina.connector.Request;
+-import org.apache.catalina.connector.Response;
+-import org.apache.catalina.session.ManagerBase;
+-import org.apache.catalina.util.LifecycleSupport;
+-import org.apache.catalina.util.StringManager;
+-import org.apache.catalina.valves.ValveBase;
+-
+-/**
+- * Valve to handle Tomcat jvmRoute takeover using mod_jk module after node
+- * failure. After a node crashed the next request going to other cluster node.
+- * Now the answering from apache is slower ( make some error handshaking. Very
+- * bad with apache at my windows.). We rewrite now the cookie jsessionid
+- * information to the backup cluster node. After the next response all client
+- * request goes direct to the backup node. The change sessionid send also to all
+- * other cluster nodes. Well, now the session stickyness work directly to the
+- * backup node and traffic don't go back too restarted cluster nodes!
+- *
+- * At all cluster node you must configure the as ClusterListener since 5.5.10
+- * {@link org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener JvmRouteSessionIDBinderListener}
+- * or before with
+- * org.apache.catalina.ha.session.JvmRouteSessionIDBinderListenerLifecycle.
+- *
+- * Add this Valve to your host definition at conf/server.xml .
+- *
+- * Since 5.5.10 as direct cluster valve:<br/>
+- * <pre>
+- * <Cluster>
+- * <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve" />
+- * </Cluster>
+- * </pre>
+- * <br />
+- * Before 5.5.10 as Host element:<br/>
+- * <pre>
+- * <Hostr>
+- * <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve" />
+- * </Hostr>
+- * </pre>
+- *
+- * Trick:<br/>
+- * You can enable this mod_jk turnover mode via JMX before you drop a node to all backup nodes!
+- * Set enable true on all JvmRouteBinderValve backups, disable worker at mod_jk
+- * and then drop node and restart it! Then enable mod_jk Worker and disable JvmRouteBinderValves again.
+- * This use case means that only requested session are migrated.
+- *
+- * @author Peter Rossbach
+- * @version $Revision$ $Date$
+- */
+-public class JvmRouteBinderValve extends ValveBase implements ClusterValve, Lifecycle {
+-
+- /*--Static Variables----------------------------------------*/
+- public static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory
+- .getLog(JvmRouteBinderValve.class);
+-
+- /**
+- * The descriptive information about this implementation.
+- */
+- protected static final String info = "org.apache.catalina.ha.session.JvmRouteBinderValve/1.2";
+-
+- /*--Instance Variables--------------------------------------*/
+-
+- /**
+- * the cluster
+- */
+- protected CatalinaCluster cluster;
+-
+- /**
+- * The string manager for this package.
+- */
+- protected StringManager sm = StringManager.getManager(Constants.Package);
+-
+- /**
+- * Has this component been started yet?
+- */
+- protected boolean started = false;
+-
+- /**
+- * enabled this component
+- */
+- protected boolean enabled = true;
+-
+- /**
+- * number of session that no at this tomcat instanz hosted
+- */
+- protected long numberOfSessions = 0;
+-
+- protected String sessionIdAttribute = "org.apache.catalina.ha.session.JvmRouteOrignalSessionID";
+-
+- /**
+- * The lifecycle event support for this component.
+- */
+- protected LifecycleSupport lifecycle = new LifecycleSupport(this);
+-
+- /*--Logic---------------------------------------------------*/
+-
+- /**
+- * Return descriptive information about this implementation.
+- */
+- public String getInfo() {
+-
+- return (info);
+-
+- }
+-
+- /**
+- * set session id attribute to failed node for request.
+- *
+- * @return Returns the sessionIdAttribute.
+- */
+- public String getSessionIdAttribute() {
+- return sessionIdAttribute;
+- }
+-
+- /**
+- * get name of failed reqeust session attribute
+- *
+- * @param sessionIdAttribute
+- * The sessionIdAttribute to set.
+- */
+- public void setSessionIdAttribute(String sessionIdAttribute) {
+- this.sessionIdAttribute = sessionIdAttribute;
+- }
+-
+- /**
+- * @return Returns the number of migrated sessions.
+- */
+- public long getNumberOfSessions() {
+- return numberOfSessions;
+- }
+-
+- /**
+- * @return Returns the enabled.
+- */
+- public boolean getEnabled() {
+- return enabled;
+- }
+-
+- /**
+- * @param enabled
+- * The enabled to set.
+- */
+- public void setEnabled(boolean enabled) {
+- this.enabled = enabled;
+- }
+-
+- /**
+- * Detect possible the JVMRoute change at cluster backup node..
+- *
+- * @param request
+- * tomcat request being processed
+- * @param response
+- * tomcat response being processed
+- * @exception IOException
+- * if an input/output error has occurred
+- * @exception ServletException
+- * if a servlet error has occurred
+- */
+- public void invoke(Request request, Response response) throws IOException,
+- ServletException {
+-
+- if (getEnabled()
+- && getCluster() != null
+- && request.getContext() != null
+- && request.getContext().getDistributable() ) {
+- // valve cluster can access manager - other cluster handle turnover
+- // at host level - hopefully!
+- Manager manager = request.getContext().getManager();
+- if (manager != null && manager instanceof ClusterManager
+- && getCluster().getManager(((ClusterManager)manager).getName()) != null)
+- handlePossibleTurnover(request, response);
+- }
+- // Pass this request on to the next valve in our pipeline
+- getNext().invoke(request, response);
+- }
+-
+- /**
+- * handle possible session turn over.
+- *
+- * @see JvmRouteBinderValve#handleJvmRoute(Request, Response, String, String)
+- * @param request current request
+- * @param response current response
+- */
+- protected void handlePossibleTurnover(Request request, Response response) {
+- Session session = request.getSessionInternal(false);
+- if (session != null) {
+- long t1 = System.currentTimeMillis();
+- String jvmRoute = getLocalJvmRoute(request);
+- if (jvmRoute == null) {
+- if (log.isDebugEnabled())
+- log.debug(sm.getString("jvmRoute.missingJvmRouteAttribute"));
+- return;
+- }
+- handleJvmRoute( request, response,session.getIdInternal(), jvmRoute);
+- if (log.isDebugEnabled()) {
+- long t2 = System.currentTimeMillis();
+- long time = t2 - t1;
+- log.debug(sm.getString("jvmRoute.turnoverInfo", new Long(time)));
+- }
+- }
+- }
+-
+- /**
+- * get jvmroute from engine
+- *
+- * @param request current request
+- * @return return jvmRoute from ManagerBase or null
+- */
+- protected String getLocalJvmRoute(Request request) {
+- Manager manager = getManager(request);
+- if(manager instanceof ManagerBase)
+- return ((ManagerBase) manager).getJvmRoute();
+- return null ;
+- }
+-
+- /**
+- * get Cluster DeltaManager
+- *
+- * @param request current request
+- * @return manager or null
+- */
+- protected Manager getManager(Request request) {
+- Manager manager = request.getContext().getManager();
+- if (log.isDebugEnabled()) {
+- if(manager != null)
+- log.debug(sm.getString("jvmRoute.foundManager", manager, request.getContext().getName()));
+- else
+- log.debug(sm.getString("jvmRoute.notFoundManager", manager, request.getContext().getName()));
+- }
+- return manager;
+- }
+-
+- /**
+- * @return Returns the cluster.
+- */
+- public CatalinaCluster getCluster() {
+- return cluster;
+- }
+-
+- /**
+- * @param cluster The cluster to set.
+- */
+- public void setCluster(CatalinaCluster cluster) {
+- this.cluster = cluster;
+- }
+-
+- /**
+- * Handle jvmRoute stickyness after tomcat instance failed. After this
+- * correction a new Cookie send to client with new jvmRoute and the
+- * SessionID change propage to the other cluster nodes.
+- *
+- * @param request current request
+- * @param response
+- * Tomcat Response
+- * @param sessionId
+- * request SessionID from Cookie
+- * @param localJvmRoute
+- * local jvmRoute
+- */
+- protected void handleJvmRoute(
+- Request request, Response response,String sessionId, String localJvmRoute) {
+- // get requested jvmRoute.
+- String requestJvmRoute = null;
+- int index = sessionId.indexOf(".");
+- if (index > 0) {
+- requestJvmRoute = sessionId
+- .substring(index + 1, sessionId.length());
+- }
+- if (requestJvmRoute != null && !requestJvmRoute.equals(localJvmRoute)) {
+- if (log.isDebugEnabled()) {
+- log.debug(sm.getString("jvmRoute.failover", requestJvmRoute,
+- localJvmRoute, sessionId));
+- }
+- // OK - turnover the session ?
+- String newSessionID = sessionId.substring(0, index) + "."
+- + localJvmRoute;
+- Session catalinaSession = null;
+- try {
+- catalinaSession = getManager(request).findSession(sessionId);
+- } catch (IOException e) {
+- // Hups!
+- }
+- if (catalinaSession != null) {
+- changeSessionID(request, response, sessionId, newSessionID,
+- catalinaSession);
+- numberOfSessions++;
+- } else {
+- if (log.isDebugEnabled()) {
+- log.debug(sm.getString("jvmRoute.cannotFindSession",
+- sessionId));
+- }
+- }
+- }
+- }
+-
+- /**
+- * change session id and send to all cluster nodes
+- *
+- * @param request current request
+- * @param response current response
+- * @param sessionId
+- * original session id
+- * @param newSessionID
+- * new session id for node migration
+- * @param catalinaSession
+- * current session with original session id
+- */
+- protected void changeSessionID(Request request,
+- Response response, String sessionId, String newSessionID, Session catalinaSession) {
+- lifecycle.fireLifecycleEvent("Before session migration",
+- catalinaSession);
+- request.setRequestedSessionId(newSessionID);
+- catalinaSession.setId(newSessionID);
+- if (catalinaSession instanceof DeltaSession)
+- ((DeltaSession) catalinaSession).resetDeltaRequest();
+- if(request.isRequestedSessionIdFromCookie()) setNewSessionCookie(request, response,newSessionID);
+- // set orginal sessionid at request, to allow application detect the
+- // change
+- if (sessionIdAttribute != null && !"".equals(sessionIdAttribute)) {
+- if (log.isDebugEnabled()) {
+- log.debug(sm.getString("jvmRoute.set.orignalsessionid",sessionIdAttribute,sessionId));
+- }
+- request.setAttribute(sessionIdAttribute, sessionId);
+- }
+- // now sending the change to all other clusternode!
+- ClusterManager manager = (ClusterManager)catalinaSession.getManager();
+- sendSessionIDClusterBackup(manager,request,sessionId, newSessionID);
+- lifecycle
+- .fireLifecycleEvent("After session migration", catalinaSession);
+- if (log.isDebugEnabled()) {
+- log.debug(sm.getString("jvmRoute.changeSession", sessionId,
+- newSessionID));
+- }
+- }
+-
+- /**
+- * Send the changed Sessionid to all clusternodes.
+- *
+- * @see JvmRouteSessionIDBinderListener#messageReceived(ClusterMessage)
+- * @param manager
+- * ClusterManager
+- * @param sessionId
+- * current failed sessionid
+- * @param newSessionID
+- * new session id, bind to the new cluster node
+- */
+- protected void sendSessionIDClusterBackup(ClusterManager manager,Request request,String sessionId,
+- String newSessionID) {
+- SessionIDMessage msg = new SessionIDMessage();
+- msg.setOrignalSessionID(sessionId);
+- msg.setBackupSessionID(newSessionID);
+- Context context = request.getContext();
+- msg.setContextPath(context.getPath());
+- msg.setHost(context.getParent().getName());
+- if(manager.doDomainReplication())
+- cluster.sendClusterDomain(msg);
+- else
+- cluster.send(msg);
+- }
+-
+- /**
+- * Sets a new cookie for the given session id and response and see
+- * {@link org.apache.catalina.connector.Request#configureSessionCookie(javax.servlet.http.Cookie)}
+- *
+- * @param request current request
+- * @param response Tomcat Response
+- * @param sessionId The session id
+- */
+- protected void setNewSessionCookie(Request request,
+- Response response, String sessionId) {
+- if (response != null) {
+- Context context = request.getContext();
+- if (context.getCookies()) {
+- // set a new session cookie
+- Cookie newCookie = new Cookie(Globals.SESSION_COOKIE_NAME,
+- sessionId);
+- newCookie.setMaxAge(-1);
+- String contextPath = null;
+- if (!response.getConnector().getEmptySessionPath()
+- && (context != null)) {
+- contextPath = context.getEncodedPath();
+- }
+- if ((contextPath != null) && (contextPath.length() > 0)) {
+- newCookie.setPath(contextPath);
+- } else {
+- newCookie.setPath("/");
+- }
+- if (request.isSecure()) {
+- newCookie.setSecure(true);
+- }
+- if (log.isDebugEnabled()) {
+- log.debug(sm.getString("jvmRoute.newSessionCookie",
+- sessionId, Globals.SESSION_COOKIE_NAME, newCookie
+- .getPath(), new Boolean(newCookie
+- .getSecure())));
+- }
+- response.addCookie(newCookie);
+- }
+- }
+- }
+-
+- // ------------------------------------------------------ Lifecycle Methods
+-
+- /**
+- * Add a lifecycle event listener to this component.
+- *
+- * @param listener
+- * The listener to add
+- */
+- public void addLifecycleListener(LifecycleListener listener) {
+-
+- lifecycle.addLifecycleListener(listener);
+-
+- }
+-
+- /**
+- * Get the lifecycle listeners associated with this lifecycle. If this
+- * Lifecycle has no listeners registered, a zero-length array is returned.
+- */
+- public LifecycleListener[] findLifecycleListeners() {
+-
+- return lifecycle.findLifecycleListeners();
+-
+- }
+-
+- /**
+- * Remove a lifecycle event listener from this component.
+- *
+- * @param listener
+- * The listener to add
+- */
+- public void removeLifecycleListener(LifecycleListener listener) {
+-
+- lifecycle.removeLifecycleListener(listener);
+-
+- }
+-
+- /**
+- * Prepare for the beginning of active use of the public methods of this
+- * component. This method should be called after <code>configure()</code>,
+- * and before any of the public methods of the component are utilized.
+- *
+- * @exception LifecycleException
+- * if this component detects a fatal error that prevents this
+- * component from being used
+- */
+- public void start() throws LifecycleException {
+-
+- // Validate and update our current component state
+- if (started)
+- throw new LifecycleException(sm
+- .getString("jvmRoute.valve.alreadyStarted"));
+- lifecycle.fireLifecycleEvent(START_EVENT, null);
+- started = true;
+- if (cluster == null) {
+- Container hostContainer = getContainer();
+- // compatibility with JvmRouteBinderValve version 1.1
+- // ( setup at context.xml or context.xml.default )
+- if (!(hostContainer instanceof Host)) {
+- if (log.isWarnEnabled())
+- log.warn(sm.getString("jvmRoute.configure.warn"));
+- hostContainer = hostContainer.getParent();
+- }
+- if (hostContainer instanceof Host
+- && ((Host) hostContainer).getCluster() != null) {
+- cluster = (CatalinaCluster) ((Host) hostContainer).getCluster();
+- } else {
+- Container engine = hostContainer.getParent() ;
+- if (engine instanceof Engine
+- && ((Engine) engine).getCluster() != null) {
+- cluster = (CatalinaCluster) ((Engine) engine).getCluster();
+- }
+- }
+- }
+- if (cluster == null) {
+- throw new RuntimeException("No clustering support at container "
+- + container.getName());
+- }
+-
+- if (log.isInfoEnabled())
+- log.info(sm.getString("jvmRoute.valve.started"));
+-
+- }
+-
+- /**
+- * Gracefully terminate the active use of the public methods of this
+- * component. This method should be the last one called on a given instance
+- * of this component.
+- *
+- * @exception LifecycleException
+- * if this component detects a fatal error that needs to be
+- * reported
+- */
+- public void stop() throws LifecycleException {
+-
+- // Validate and update our current component state
+- if (!started)
+- throw new LifecycleException(sm
+- .getString("jvmRoute.valve.notStarted"));
+- lifecycle.fireLifecycleEvent(STOP_EVENT, null);
+- started = false;
+- cluster = null;
+- numberOfSessions = 0;
+- if (log.isInfoEnabled())
+- log.info(sm.getString("jvmRoute.valve.stopped"));
+-
+- }
+-
+-}
+Index: java/org/apache/catalina/ha/session/DeltaSession.java
+===================================================================
+--- java/org/apache/catalina/ha/session/DeltaSession.java (revision 590752)
++++ java/org/apache/catalina/ha/session/DeltaSession.java (working copy)
+@@ -1,755 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.ha.session;
+-
+-import java.io.Externalizable;
+-import java.io.IOException;
+-import java.io.NotSerializableException;
+-import java.io.ObjectInput;
+-import java.io.ObjectOutput;
+-import java.io.Serializable;
+-import java.security.Principal;
+-import java.util.ArrayList;
+-import java.util.Enumeration;
+-import java.util.HashMap;
+-import java.util.Hashtable;
+-import java.util.concurrent.locks.Lock;
+-import java.util.concurrent.locks.ReentrantReadWriteLock;
+-import javax.servlet.http.HttpSession;
+-import javax.servlet.http.HttpSessionContext;
+-
+-import org.apache.catalina.Manager;
+-import org.apache.catalina.ha.ClusterManager;
+-import org.apache.catalina.ha.ClusterSession;
+-import org.apache.catalina.realm.GenericPrincipal;
+-import org.apache.catalina.session.StandardSession;
+-import org.apache.catalina.tribes.io.ReplicationStream;
+-import org.apache.catalina.tribes.tipis.ReplicatedMapEntry;
+-import org.apache.catalina.util.Enumerator;
+-import org.apache.catalina.util.StringManager;
+-import org.apache.catalina.session.StandardManager;
+-import org.apache.catalina.session.ManagerBase;
+-import java.util.concurrent.atomic.AtomicInteger;
+-
+-/**
+- *
+- * Similar to the StandardSession except that this session will keep
+- * track of deltas during a request.
+- *
+- * @author Filip Hanik
+- * @version $Revision$ $Date$
+- */
+-
+-public class DeltaSession extends StandardSession implements Externalizable,ClusterSession,ReplicatedMapEntry {
+-
+- public static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(DeltaSession.class);
+-
+- /**
+- * The string manager for this package.
+- */
+- protected static StringManager sm = StringManager.getManager(Constants.Package);
+-
+- // ----------------------------------------------------- Instance Variables
+-
+- /**
+- * only the primary session will expire, or be able to expire due to
+- * inactivity. This is set to false as soon as I receive this session over
+- * the wire in a session message. That means that someone else has made a
+- * request on another server.
+- */
+- private transient boolean isPrimarySession = true;
+-
+- /**
+- * The delta request contains all the action info
+- *
+- */
+- private transient DeltaRequest deltaRequest = null;
+-
+- /**
+- * Last time the session was replicatd, used for distributed expiring of
+- * session
+- */
+- private transient long lastTimeReplicated = System.currentTimeMillis();
+-
+-
+- protected Lock diffLock = new ReentrantReadWriteLock().writeLock();
+-
+- private long version;
+-
+- // ----------------------------------------------------------- Constructors
+-
+- /**
+- * Construct a new Session associated with the specified Manager.
+- *
+- * @param manager
+- * The manager with which this Session is associated
+- */
+- public DeltaSession() {
+- this(null);
+- }
+-
+- public DeltaSession(Manager manager) {
+- super(manager);
+- this.resetDeltaRequest();
+- }
+-
+- // ----------------------------------------------------- ReplicatedMapEntry
+-
+- /**
+- * Has the object changed since last replication
+- * and is not in a locked state
+- * @return boolean
+- */
+- public boolean isDirty() {
+- return getDeltaRequest().getSize()>0;
+- }
+-
+- /**
+- * If this returns true, the map will extract the diff using getDiff()
+- * Otherwise it will serialize the entire object.
+- * @return boolean
+- */
+- public boolean isDiffable() {
+- return true;
+- }
+-
+- /**
+- * Returns a diff and sets the dirty map to false
+- * @return byte[]
+- * @throws IOException
+- */
+- public byte[] getDiff() throws IOException {
+- return getDeltaRequest().serialize();
+- }
+-
+- public ClassLoader[] getClassLoaders() {
+- if ( manager instanceof BackupManager ) return ((BackupManager)manager).getClassLoaders();
+- else if ( manager instanceof ClusterManagerBase ) return ((ClusterManagerBase)manager).getClassLoaders();
+- else if ( manager instanceof StandardManager ) {
+- StandardManager sm = (StandardManager)manager;
+- return ClusterManagerBase.getClassLoaders(sm.getContainer());
+- } else if ( manager instanceof ManagerBase ) {
+- ManagerBase mb = (ManagerBase)manager;
+- return ClusterManagerBase.getClassLoaders(mb.getContainer());
+- }//end if
+- return null;
+- }
+-
+- /**
+- * Applies a diff to an existing object.
+- * @param diff byte[]
+- * @param offset int
+- * @param length int
+- * @throws IOException
+- */
+- public void applyDiff(byte[] diff, int offset, int length) throws IOException, ClassNotFoundException {
+- ReplicationStream stream = ((ClusterManager)getManager()).getReplicationStream(diff,offset,length);
+- getDeltaRequest().readExternal(stream);
+- ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+- try {
+- ClassLoader[] loaders = getClassLoaders();
+- if ( loaders != null && loaders.length >0 ) Thread.currentThread().setContextClassLoader(loaders[0]);
+- getDeltaRequest().execute(this);
+- }finally {
+- Thread.currentThread().setContextClassLoader(contextLoader);
+- }
+- }
+-
+- /**
+- * Resets the current diff state and resets the dirty flag
+- */
+- public void resetDiff() {
+- resetDeltaRequest();
+- }
+-
+- /**
+- * Lock during serialization
+- */
+- public void lock() {
+- diffLock.lock();
+- }
+-
+- /**
+- * Unlock after serialization
+- */
+- public void unlock() {
+- diffLock.unlock();
+- }
+-
+- public void setOwner(Object owner) {
+- if ( owner instanceof ClusterManager && getManager()==null) {
+- ClusterManager cm = (ClusterManager)owner;
+- this.setManager(cm);
+- this.setValid(true);
+- this.setPrimarySession(false);
+- this.access();
+- this.resetDeltaRequest();
+- this.endAccess();
+- }
+- }
+- // ----------------------------------------------------- Session Properties
+-
+- /**
+- * returns true if this session is the primary session, if that is the case,
+- * the manager can expire it upon timeout.
+- */
+- public boolean isPrimarySession() {
+- return isPrimarySession;
+- }
+-
+- /**
+- * Sets whether this is the primary session or not.
+- *
+- * @param primarySession
+- * Flag value
+- */
+- public void setPrimarySession(boolean primarySession) {
+- this.isPrimarySession = primarySession;
+- }
+-
+- /**
+- * Set the session identifier for this session without notify listeners.
+- *
+- * @param id
+- * The new session identifier
+- */
+- public void setIdInternal(String id) {
+- this.id = id;
+- resetDeltaRequest();
+- }
+-
+- /**
+- * Set the session identifier for this session.
+- *
+- * @param id
+- * The new session identifier
+- */
+- public void setId(String id) {
+- super.setId(id);
+- resetDeltaRequest();
+- }
+-
+-
+-
+- /**
+- * Return the last client access time without invalidation check
+- * @see #getLastAccessedTime().
+- */
+- public long getLastAccessedTimeInternal() {
+- return (this.lastAccessedTime);
+- }
+-
+-
+-
+- public void setMaxInactiveInterval(int interval) {
+- this.setMaxInactiveInterval(interval,true);
+- }
+- public void setMaxInactiveInterval(int interval, boolean addDeltaRequest) {
+- super.maxInactiveInterval = interval;
+- if (isValid && interval == 0) {
+- expire();
+- } else {
+- if (addDeltaRequest && (deltaRequest != null))
+- deltaRequest.setMaxInactiveInterval(interval);
+- }
+- }
+-
+- /**
+- * Set the <code>isNew</code> flag for this session.
+- *
+- * @param isNew
+- * The new value for the <code>isNew</code> flag
+- */
+- public void setNew(boolean isNew) {
+- setNew(isNew, true);
+- }
+-
+- public void setNew(boolean isNew, boolean addDeltaRequest) {
+- super.setNew(isNew);
+- if (addDeltaRequest && (deltaRequest != null))
+- deltaRequest.setNew(isNew);
+- }
+-
+- /**
+- * Set the authenticated Principal that is associated with this Session.
+- * This provides an <code>Authenticator</code> with a means to cache a
+- * previously authenticated Principal, and avoid potentially expensive
+- * <code>Realm.authenticate()</code> calls on every request.
+- *
+- * @param principal
+- * The new Principal, or <code>null</code> if none
+- */
+- public void setPrincipal(Principal principal) {
+- setPrincipal(principal, true);
+- }
+-
+- public void setPrincipal(Principal principal, boolean addDeltaRequest) {
+- try {
+- lock();
+- super.setPrincipal(principal);
+- if (addDeltaRequest && (deltaRequest != null))
+- deltaRequest.setPrincipal(principal);
+- } finally {
+- unlock();
+- }
+- }
+-
+- /**
+- * Return the <code>isValid</code> flag for this session.
+- */
+- public boolean isValid() {
+- if (this.expiring) {
+- return true;
+- }
+- if (!this.isValid) {
+- return false;
+- }
+- if (ACTIVITY_CHECK && accessCount.get() > 0) {
+- return true;
+- }
+- if (maxInactiveInterval >= 0) {
+- long timeNow = System.currentTimeMillis();
+- int timeIdle = (int) ( (timeNow - thisAccessedTime) / 1000L);
+- if (isPrimarySession()) {
+- if (timeIdle >= maxInactiveInterval) {
+- expire(true);
+- }
+- } else {
+- if (timeIdle >= (2 * maxInactiveInterval)) {
+- //if the session has been idle twice as long as allowed,
+- //the primary session has probably crashed, and no other
+- //requests are coming in. that is why we do this. otherwise
+- //we would have a memory leak
+- expire(true, false);
+- }
+- }
+- }
+- return (this.isValid);
+- }
+-
+- // ------------------------------------------------- Session Public Methods
+-
+- /**
+- * Perform the internal processing required to invalidate this session,
+- * without triggering an exception if the session has already expired.
+- *
+- * @param notify
+- * Should we notify listeners about the demise of this session?
+- */
+- public void expire(boolean notify) {
+- expire(notify, true);
+- }
+-
+- public void expire(boolean notify, boolean notifyCluster) {
+- String expiredId = getIdInternal();
+- super.expire(notify);
+-
+- if (notifyCluster) {
+- if (log.isDebugEnabled())
+- log.debug(sm.getString("deltaSession.notifying",
+- ((ClusterManager)manager).getName(),
+- new Boolean(isPrimarySession()),
+- expiredId));
+- if ( manager instanceof DeltaManager ) {
+- ( (DeltaManager) manager).sessionExpired(expiredId);
+- }
+- }
+- }
+-
+- /**
+- * Release all object references, and initialize instance variables, in
+- * preparation for reuse of this object.
+- */
+- public void recycle() {
+- super.recycle();
+- deltaRequest.clear();
+- }
+-
+-
+- /**
+- * Return a string representation of this object.
+- */
+- public String toString() {
+- StringBuffer sb = new StringBuffer();
+- sb.append("DeltaSession[");
+- sb.append(id);
+- sb.append("]");
+- return (sb.toString());
+- }
+-
+- // ------------------------------------------------ Session Package Methods
+-
+- public synchronized void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {
+- readObjectData(in);
+- }
+-
+-
+- /**
+- * Read a serialized version of the contents of this session object from the
+- * specified object input stream, without requiring that the StandardSession
+- * itself have been serialized.
+- *
+- * @param stream
+- * The object input stream to read from
+- *
+- * @exception ClassNotFoundException
+- * if an unknown class is specified
+- * @exception IOException
+- * if an input/output error occurs
+- */
+- public void readObjectData(ObjectInput stream) throws ClassNotFoundException, IOException {
+- readObject(stream);
+- }
+-
+- /**
+- * Write a serialized version of the contents of this session object to the
+- * specified object output stream, without requiring that the
+- * StandardSession itself have been serialized.
+- *
+- * @param stream
+- * The object output stream to write to
+- *
+- * @exception IOException
+- * if an input/output error occurs
+- */
+- public void writeObjectData(ObjectOutput stream) throws IOException {
+- writeObject(stream);
+- }
+-
+- public void resetDeltaRequest() {
+- if (deltaRequest == null) {
+- deltaRequest = new DeltaRequest(getIdInternal(), false);
+- } else {
+- deltaRequest.reset();
+- deltaRequest.setSessionId(getIdInternal());
+- }
+- }
+-
+- public DeltaRequest getDeltaRequest() {
+- if (deltaRequest == null) resetDeltaRequest();
+- return deltaRequest;
+- }
+-
+- // ------------------------------------------------- HttpSession Properties
+-
+- // ----------------------------------------------HttpSession Public Methods
+-
+-
+-
+- /**
+- * Remove the object bound with the specified name from this session. If the
+- * session does not have an object bound with this name, this method does
+- * nothing.
+- * <p>
+- * After this method executes, and if the object implements
+- * <code>HttpSessionBindingListener</code>, the container calls
+- * <code>valueUnbound()</code> on the object.
+- *
+- * @param name
+- * Name of the object to remove from this session.
+- * @param notify
+- * Should we notify interested listeners that this attribute is
+- * being removed?
+- *
+- * @exception IllegalStateException
+- * if this method is called on an invalidated session
+- */
+- public void removeAttribute(String name, boolean notify) {
+- removeAttribute(name, notify, true);
+- }
+-
+- public void removeAttribute(String name, boolean notify,boolean addDeltaRequest) {
+- // Validate our current state
+- if (!isValid()) throw new IllegalStateException(sm.getString("standardSession.removeAttribute.ise"));
+- removeAttributeInternal(name, notify, addDeltaRequest);
+- }
+-
+- /**
+- * Bind an object to this session, using the specified name. If an object of
+- * the same name is already bound to this session, the object is replaced.
+- * <p>
+- * After this method executes, and if the object implements
+- * <code>HttpSessionBindingListener</code>, the container calls
+- * <code>valueBound()</code> on the object.
+- *
+- * @param name
+- * Name to which the object is bound, cannot be null
+- * @param value
+- * Object to be bound, cannot be null
+- *
+- * @exception IllegalArgumentException
+- * if an attempt is made to add a non-serializable object in
+- * an environment marked distributable.
+- * @exception IllegalStateException
+- * if this method is called on an invalidated session
+- */
+- public void setAttribute(String name, Object value) {
+- setAttribute(name, value, true, true);
+- }
+-
+- public void setAttribute(String name, Object value, boolean notify,boolean addDeltaRequest) {
+-
+- // Name cannot be null
+- if (name == null) throw new IllegalArgumentException(sm.getString("standardSession.setAttribute.namenull"));
+-
+- // Null value is the same as removeAttribute()
+- if (value == null) {
+- removeAttribute(name);
+- return;
+- }
+-
+- try {
+- lock();
+- super.setAttribute(name,value, notify);
+- if (addDeltaRequest && (deltaRequest != null)) deltaRequest.setAttribute(name, value);
+- } finally {
+- unlock();
+- }
+- }
+-
+- // -------------------------------------------- HttpSession Private Methods
+-
+- /**
+- * Read a serialized version of this session object from the specified
+- * object input stream.
+- * <p>
+- * <b>IMPLEMENTATION NOTE </b>: The reference to the owning Manager is not
+- * restored by this method, and must be set explicitly.
+- *
+- * @param stream
+- * The input stream to read from
+- *
+- * @exception ClassNotFoundException
+- * if an unknown class is specified
+- * @exception IOException
+- * if an input/output error occurs
+- */
+- private void readObject(ObjectInput stream) throws ClassNotFoundException, IOException {
+-
+- // Deserialize the scalar instance variables (except Manager)
+- authType = null; // Transient only
+- creationTime = ( (Long) stream.readObject()).longValue();
+- lastAccessedTime = ( (Long) stream.readObject()).longValue();
+- maxInactiveInterval = ( (Integer) stream.readObject()).intValue();
+- isNew = ( (Boolean) stream.readObject()).booleanValue();
+- isValid = ( (Boolean) stream.readObject()).booleanValue();
+- thisAccessedTime = ( (Long) stream.readObject()).longValue();
+- version = ( (Long) stream.readObject()).longValue();
+- boolean hasPrincipal = stream.readBoolean();
+- principal = null;
+- if (hasPrincipal) {
+- principal = SerializablePrincipal.readPrincipal(stream,getManager().getContainer().getRealm());
+- }
+-
+- // setId((String) stream.readObject());
+- id = (String) stream.readObject();
+- if (log.isDebugEnabled()) log.debug(sm.getString("deltaSession.readSession", id));
+-
+- // Deserialize the attribute count and attribute values
+- if (attributes == null) attributes = new Hashtable();
+- int n = ( (Integer) stream.readObject()).intValue();
+- boolean isValidSave = isValid;
+- isValid = true;
+- for (int i = 0; i < n; i++) {
+- String name = (String) stream.readObject();
+- Object value = (Object) stream.readObject();
+- if ( (value instanceof String) && (value.equals(NOT_SERIALIZED)))
+- continue;
+- attributes.put(name, value);
+- }
+- isValid = isValidSave;
+-
+- if (listeners == null) {
+- listeners = new ArrayList();
+- }
+-
+- if (notes == null) {
+- notes = new Hashtable();
+- }
+- activate();
+- }
+-
+- public synchronized void writeExternal(ObjectOutput out ) throws java.io.IOException {
+- writeObject(out);
+- }
+-
+-
+- /**
+- * Write a serialized version of this session object to the specified object
+- * output stream.
+- * <p>
+- * <b>IMPLEMENTATION NOTE </b>: The owning Manager will not be stored in the
+- * serialized representation of this Session. After calling
+- * <code>readObject()</code>, you must set the associated Manager
+- * explicitly.
+- * <p>
+- * <b>IMPLEMENTATION NOTE </b>: Any attribute that is not Serializable will
+- * be unbound from the session, with appropriate actions if it implements
+- * HttpSessionBindingListener. If you do not want any such attributes, be
+- * sure the <code>distributable</code> property of the associated Manager
+- * is set to <code>true</code>.
+- *
+- * @param stream
+- * The output stream to write to
+- *
+- * @exception IOException
+- * if an input/output error occurs
+- */
+- private void writeObject(ObjectOutput stream) throws IOException {
+- // Write the scalar instance variables (except Manager)
+- stream.writeObject(new Long(creationTime));
+- stream.writeObject(new Long(lastAccessedTime));
+- stream.writeObject(new Integer(maxInactiveInterval));
+- stream.writeObject(new Boolean(isNew));
+- stream.writeObject(new Boolean(isValid));
+- stream.writeObject(new Long(thisAccessedTime));
+- stream.writeObject(new Long(version));
+- stream.writeBoolean(getPrincipal() != null);
+- if (getPrincipal() != null) {
+- SerializablePrincipal.writePrincipal((GenericPrincipal) principal,stream);
+- }
+-
+- stream.writeObject(id);
+- if (log.isDebugEnabled()) log.debug(sm.getString("deltaSession.writeSession", id));
+-
+- // Accumulate the names of serializable and non-serializable attributes
+- String keys[] = keys();
+- ArrayList saveNames = new ArrayList();
+- ArrayList saveValues = new ArrayList();
+- for (int i = 0; i < keys.length; i++) {
+- Object value = null;
+- value = attributes.get(keys[i]);
+- if (value == null)
+- continue;
+- else if (value instanceof Serializable) {
+- saveNames.add(keys[i]);
+- saveValues.add(value);
+- }
+- }
+-
+- // Serialize the attribute count and the Serializable attributes
+- int n = saveNames.size();
+- stream.writeObject(new Integer(n));
+- for (int i = 0; i < n; i++) {
+- stream.writeObject( (String) saveNames.get(i));
+- try {
+- stream.writeObject(saveValues.get(i));
+- } catch (NotSerializableException e) {
+- log.error(sm.getString("standardSession.notSerializable",saveNames.get(i), id), e);
+- stream.writeObject(NOT_SERIALIZED);
+- log.error(" storing attribute '" + saveNames.get(i)+ "' with value NOT_SERIALIZED");
+- }
+- }
+-
+- }
+-
+- // -------------------------------------------------------- Private Methods
+-
+-
+-
+- /**
+- * Return the value of an attribute without a check for validity.
+- */
+- protected Object getAttributeInternal(String name) {
+- return (attributes.get(name));
+- }
+-
+- protected void removeAttributeInternal(String name, boolean notify,
+- boolean addDeltaRequest) {
+- try {
+- lock();
+- // Remove this attribute from our collection
+- Object value = attributes.get(name);
+- if (value == null) return;
+-
+- super.removeAttributeInternal(name,notify);
+- if (addDeltaRequest && (deltaRequest != null)) deltaRequest.removeAttribute(name);
+-
+- }finally {
+- unlock();
+- }
+- }
+-
+- protected long getLastTimeReplicated() {
+- return lastTimeReplicated;
+- }
+-
+- public long getVersion() {
+- return version;
+- }
+-
+- protected void setLastTimeReplicated(long lastTimeReplicated) {
+- this.lastTimeReplicated = lastTimeReplicated;
+- }
+-
+- public void setVersion(long version) {
+- this.version = version;
+- }
+-
+- protected void setAccessCount(int count) {
+- if ( accessCount == null && ACTIVITY_CHECK ) accessCount = new AtomicInteger();
+- if ( accessCount != null ) super.accessCount.set(count);
+- }
+-}
+-
+-// -------------------------------------------------------------- Private Class
+-
+-/**
+- * This class is a dummy implementation of the <code>HttpSessionContext</code>
+- * interface, to conform to the requirement that such an object be returned when
+- * <code>HttpSession.getSessionContext()</code> is called.
+- *
+- * @author Craig R. McClanahan
+- *
+- * @deprecated As of Java Servlet API 2.1 with no replacement. The interface
+- * will be removed in a future version of this API.
+- */
+-
+-final class StandardSessionContext
+- implements HttpSessionContext {
+-
+- private HashMap dummy = new HashMap();
+-
+- /**
+- * Return the session identifiers of all sessions defined within this
+- * context.
+- *
+- * @deprecated As of Java Servlet API 2.1 with no replacement. This method
+- * must return an empty <code>Enumeration</code> and will be
+- * removed in a future version of the API.
+- */
+- public Enumeration getIds() {
+- return (new Enumerator(dummy));
+- }
+-
+- /**
+- * Return the <code>HttpSession</code> associated with the specified
+- * session identifier.
+- *
+- * @param id
+- * Session identifier for which to look up a session
+- *
+- * @deprecated As of Java Servlet API 2.1 with no replacement. This method
+- * must return null and will be removed in a future version of
+- * the API.
+- */
+- public HttpSession getSession(String id) {
+- return (null);
+- }
+-
+-}
+Index: java/org/apache/catalina/ha/context/ReplicatedContext.java
+===================================================================
+--- java/org/apache/catalina/ha/context/ReplicatedContext.java (revision 590752)
++++ java/org/apache/catalina/ha/context/ReplicatedContext.java (working copy)
+@@ -1,204 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.ha.context;
+-
+-import org.apache.catalina.core.StandardContext;
+-import org.apache.catalina.LifecycleException;
+-import org.apache.catalina.ha.CatalinaCluster;
+-import org.apache.catalina.tribes.tipis.ReplicatedMap;
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.Loader;
+-import org.apache.catalina.core.ApplicationContext;
+-import org.apache.catalina.Globals;
+-import javax.servlet.ServletContext;
+-import java.util.AbstractMap;
+-import org.apache.catalina.tribes.tipis.AbstractReplicatedMap;
+-import java.util.ArrayList;
+-import java.util.Iterator;
+-import javax.servlet.ServletContextAttributeListener;
+-import javax.servlet.ServletContextAttributeEvent;
+-import org.apache.catalina.LifecycleEvent;
+-import org.apache.catalina.LifecycleListener;
+-import java.util.Enumeration;
+-import java.util.concurrent.ConcurrentHashMap;
+-import org.apache.catalina.util.Enumerator;
+-import org.apache.catalina.tribes.tipis.AbstractReplicatedMap.MapOwner;
+-import org.apache.catalina.ha.session.DeltaSession;
+-
+-/**
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public class ReplicatedContext extends StandardContext implements LifecycleListener,MapOwner {
+- private int mapSendOptions = Channel.SEND_OPTIONS_DEFAULT;
+- public static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog( ReplicatedContext.class );
+- protected boolean startComplete = false;
+- protected static long DEFAULT_REPL_TIMEOUT = 15000;//15 seconds
+-
+- public void lifecycleEvent(LifecycleEvent event) {
+- if ( event.getType() == AFTER_START_EVENT )
+- startComplete = true;
+- }
+-
+- public synchronized void start() throws LifecycleException {
+- if ( this.started ) return;
+- super.addLifecycleListener(this);
+- try {
+- CatalinaCluster catclust = (CatalinaCluster)this.getCluster();
+- if (this.context == null) this.context = new ReplApplContext(this.getBasePath(), this);
+- if ( catclust != null ) {
+- ReplicatedMap map = new ReplicatedMap(this,catclust.getChannel(),DEFAULT_REPL_TIMEOUT,
+- getName(),getClassLoaders());
+- map.setChannelSendOptions(mapSendOptions);
+- ((ReplApplContext)this.context).setAttributeMap(map);
+- if (getAltDDName() != null) context.setAttribute(Globals.ALT_DD_ATTR, getAltDDName());
+- }
+- super.start();
+- } catch ( Exception x ) {
+- log.error("Unable to start ReplicatedContext",x);
+- throw new LifecycleException("Failed to start ReplicatedContext",x);
+- }
+- }
+-
+- public synchronized void stop() throws LifecycleException
+- {
+- ReplicatedMap map = (ReplicatedMap)((ReplApplContext)this.context).getAttributeMap();
+- if ( map!=null ) {
+- map.breakdown();
+- }
+- if ( !this.started ) return;
+- try {
+- super.lifecycle.removeLifecycleListener(this);
+- } catch ( Exception x ){
+- log.error("Unable to stop ReplicatedContext",x);
+- throw new LifecycleException("Failed to stop ReplicatedContext",x);
+- } finally {
+- this.startComplete = false;
+- super.stop();
+- }
+-
+-
+- }
+-
+-
+- public void setMapSendOptions(int mapSendOptions) {
+- this.mapSendOptions = mapSendOptions;
+- }
+-
+- public int getMapSendOptions() {
+- return mapSendOptions;
+- }
+-
+- public ClassLoader[] getClassLoaders() {
+- Loader loader = null;
+- ClassLoader classLoader = null;
+- loader = this.getLoader();
+- if (loader != null) classLoader = loader.getClassLoader();
+- if ( classLoader == null ) classLoader = Thread.currentThread().getContextClassLoader();
+- if ( classLoader == Thread.currentThread().getContextClassLoader() ) {
+- return new ClassLoader[] {classLoader};
+- } else {
+- return new ClassLoader[] {classLoader,Thread.currentThread().getContextClassLoader()};
+- }
+- }
+-
+- public ServletContext getServletContext() {
+- if (context == null) {
+- context = new ReplApplContext(getBasePath(), this);
+- if (getAltDDName() != null)
+- context.setAttribute(Globals.ALT_DD_ATTR,getAltDDName());
+- }
+-
+- return ((ReplApplContext)context).getFacade();
+-
+- }
+-
+-
+- protected static class ReplApplContext extends ApplicationContext {
+- protected ConcurrentHashMap tomcatAttributes = new ConcurrentHashMap();
+-
+- public ReplApplContext(String basePath, ReplicatedContext context) {
+- super(basePath,context);
+- }
+-
+- protected ReplicatedContext getParent() {
+- return (ReplicatedContext)getContext();
+- }
+-
+- protected ServletContext getFacade() {
+- return super.getFacade();
+- }
+-
+- public AbstractMap getAttributeMap() {
+- return (AbstractMap)this.attributes;
+- }
+- public void setAttributeMap(AbstractMap map) {
+- this.attributes = map;
+- }
+-
+- public void removeAttribute(String name) {
+- tomcatAttributes.remove(name);
+- //do nothing
+- super.removeAttribute(name);
+- }
+-
+- public void setAttribute(String name, Object value) {
+- if ( (!getParent().startComplete) || "org.apache.jasper.runtime.JspApplicationContextImpl".equals(name) ){
+- tomcatAttributes.put(name,value);
+- } else
+- super.setAttribute(name,value);
+- }
+-
+- public Object getAttribute(String name) {
+- if (tomcatAttributes.containsKey(name) )
+- return tomcatAttributes.get(name);
+- else
+- return super.getAttribute(name);
+- }
+-
+- public Enumeration getAttributeNames() {
+- return new MultiEnumeration(new Enumeration[] {super.getAttributeNames(),new Enumerator(tomcatAttributes.keySet(), true)});
+- }
+-
+- }
+-
+- protected static class MultiEnumeration implements Enumeration {
+- Enumeration[] e=null;
+- public MultiEnumeration(Enumeration[] lists) {
+- e = lists;
+- }
+- public boolean hasMoreElements() {
+- for ( int i=0; i<e.length; i++ ) {
+- if ( e[i].hasMoreElements() ) return true;
+- }
+- return false;
+- }
+- public Object nextElement() {
+- for ( int i=0; i<e.length; i++ ) {
+- if ( e[i].hasMoreElements() ) return e[i].nextElement();
+- }
+- return null;
+-
+- }
+- }
+-
+- public void objectMadePrimay(Object key, Object value) {
+- //noop
+- }
+-
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/ha/tcp/LocalStrings.properties
+===================================================================
+--- java/org/apache/catalina/ha/tcp/LocalStrings.properties (revision 590752)
++++ java/org/apache/catalina/ha/tcp/LocalStrings.properties (working copy)
+@@ -1,87 +0,0 @@
+-# 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.
+-
+-AsyncSocketSender.create.thread=Create sender [{0}:{1,number,integer}] queue thread to tcp background replication
+-AsyncSocketSender.queue.message=Queue message to [{0}:{1,number,integer}] id=[{2}] size={3}
+-AsyncSocketSender.send.error=Unable to asynchronously send session with id=[{0}] - message will be ignored.
+-AsyncSocketSender.queue.empty=Queue in sender [{0}:{1,number,integer}] returned null element!
+-cluster.mbean.register.already=MBean {0} already registered!
+-FastAsyncSocketSender.setThreadPriority=[{0}:{1,number,integer}] set priority to {2}
+-FastAsyncSocketSender.min.exception=[{0}:{1,number,integer}] new priority {2} < MIN_PRIORITY
+-FastAsyncSocketSender.max.exception=[{0}:{1,number,integer}] new priority {2} > MAX_PRIORITY
+-IDataSender.ack.eof=EOF reached at local port [{0}:{1,number,integer}]
+-IDataSender.ack.receive=Got ACK at local port [{0}:{1,number,integer}]
+-IDataSender.ack.missing=Unable to read acknowledgement from [{0}:{1,number,integer}] in {2,number,integer} ms. Disconnecting socket, and trying again.
+-IDataSender.ack.read=Read wait ack char '{2}' [{0}:{1,number,integer}]
+-IDataSender.ack.start=Waiting for ACK message [{0}:{1,number,integer}]
+-IDataSender.ack.wrong=Missing correct ACK after 10 bytes read at local port [{0}:{1,number,integer}]
+-IDataSender.closeSocket=Sender close socket to [{0}:{1,number,integer}] (close count {2,number,integer})
+-IDataSender.connect=Sender connect to [{0}:{1,number,integer}] (connect count {2,number,integer})
+-IDataSender.create=Create sender [{0}:{1,number,integer}]
+-IDataSender.disconnect=Sender disconnect from [{0}:{1,number,integer}] (disconnect count {2,number,integer})
+-IDataSender.message.disconnect=Message transfered: Sender can't disconnect from [{0}:{1,number,integer}]
+-IDataSender.message.create=Message transfered: Sender can't create current socket [{0}:{1,number,integer}]
+-IDataSender.openSocket=Sender open socket to [{0}:{1,number,integer}] (open count {2,number,integer})
+-IDataSender.openSocket.failure=Open sender socket [{0}:{1,number,integer}] failure! (open failure count {2,number,integer})
+-IDataSender.send.again=Send data again to [{0}:{1,number,integer}]
+-IDataSender.send.crash=Send message crashed [{0}:{1,number,integer}] type=[{2}], id=[{3}]
+-IDataSender.send.message=Send message to [{0}:{1,number,integer}] id=[{2}] size={3,number,integer}
+-IDataSender.send.lost=Message lost: [{0}:{1,number,integer}] type=[{2}], id=[{3}]
+-IDataSender.senderModes.Configured=Configured a data replication sender for mode {0}
+-IDataSender.senderModes.Instantiate=Can't instantiate a data replication sender of class {0}
+-IDataSender.senderModes.Missing=Can't configure a data replication sender for mode {0}
+-IDataSender.senderModes.Resources=Can't load data replication sender mapping list
+-IDataSender.stats=Send stats from [{0}:{1,number,integer}], Nr of bytes sent={2,number,integer} over {3} = {4,number,integer} bytes/request, processing time {5,number,integer} msec, avg processing time {6,number,integer} msec
+-PoolSocketSender.senderQueue.sender.failed=PoolSocketSender create new sender to [{0}:{1,number,integer}] failed
+-PoolSocketSender.noMoreSender=No socket sender available for client [{0}:{1,number,integer}] did it disappeared?
+-ReplicationTransmitter.getProperty=get property {0}
+-ReplicationTransmitter.setProperty=set property {0}: {1} old value {2}
+-ReplicationTransmitter.started=Start ClusterSender at cluster {0} with name {1}
+-ReplicationTransmitter.stopped=Stopped ClusterSender at cluster {0} with name {1}
+-ReplicationValve.crossContext.add=add Cross Context session replication container to replicationValve threadlocal
+-ReplicationValve.crossContext.registerSession=register Cross context session id={0} from context {1}
+-ReplicationValve.crossContext.remove=remove Cross Context session replication container from replicationValve threadlocal
+-ReplicationValve.crossContext.sendDelta=send Cross Context session delta from context {0}.
+-ReplicationValve.filter.loading=Loading request filters={0}
+-ReplicationValve.filter.token=Request filter={0}
+-ReplicationValve.filter.token.failure=Unable to compile filter={0}
+-ReplicationValve.invoke.uri=Invoking replication request on {0}
+-ReplicationValve.nocluster=No cluster configured for this request.
+-ReplicationValve.resetDeltaRequest=Cluster is standalone: reset Session Request Delta at context {0}
+-ReplicationValve.send.failure=Unable to perform replication request.
+-ReplicationValve.send.invalid.failure=Unable to send session [id={0}] invalid message over cluster.
+-ReplicationValve.session.found=Context {0}: Found session {1} but it isn't a ClusterSession.
+-ReplicationValve.session.indicator=Context {0}: Primarity of session {0} in request attribute {1} is {2}.
+-ReplicationValve.session.invalid=Context {0}: Requested session {1} is invalid, removed or not replicated at this node.
+-ReplicationValve.stats=Average request time= {0} ms for Cluster overhead time={1} ms for {2} requests {3} filter requests {4} send requests {5} cross context requests (Request={6} ms Cluster={7} ms).
+-SimpleTcpCluster.event.log=Cluster receive listener event {0} with data {1}
+-SimpleTcpCluster.getProperty=get property {0}
+-SimpleTcpCluster.setProperty=set property {0}: {1} old value {2}
+-SimpleTcpCluster.default.addClusterListener=Add Default ClusterListener at cluster {0}
+-SimpleTcpCluster.default.addClusterValves=Add Default ClusterValves at cluster {0}
+-SimpleTcpCluster.default.addClusterReceiver=Add Default ClusterReceiver at cluster {0}
+-SimpleTcpCluster.default.addClusterSender=Add Default ClusterSender at cluster {0}
+-SimpleTcpCluster.default.addMembershipService=Add Default Membership Service at cluster {0}
+-SimpleTcpCluster.log.receive=RECEIVE {0,date}:{0,time} {1,number} {2}:{3,number,integer} {4} {5}
+-SimpleTcpCluster.log.send=SEND {0,date}:{0,time} {1,number} {2}:{3,number,integer} {4}
+-SimpleTcpCluster.log.send.all=SEND {0,date}:{0,time} {1,number} - {2}
+-SocketReplictionListener.allreadyExists=ServerSocket [{0}:{1}] allready started!
+-SocketReplictionListener.accept.failure=ServerSocket [{0}:{1}] - Exception to start thread or accept server socket
+-SocketReplictionListener.open=Open Socket at [{0}:{1}]
+-SocketReplictionListener.openclose.failure=ServerSocket [{0}:{1}] - Exception to open or close server socket
+-SocketReplictionListener.portbusy=Port busy at [{0}:{i}] - reason [{2}]
+-SocketReplictionListener.serverSocket.notExists=Fatal error: Receiver socket not bound address={0} port={1} maxport={2}
+-SocketReplictionListener.timeout=Receiver ServerSocket no started [{0}:{1}] - reason: timeout={2} or listen={3}
+-SocketReplictionListener.unlockSocket.failure=UnLocksocket failure at ServerSocket [{0:{1}]
+Index: java/org/apache/catalina/ha/tcp/SimpleTcpCluster.java
+===================================================================
+--- java/org/apache/catalina/ha/tcp/SimpleTcpCluster.java (revision 590752)
++++ java/org/apache/catalina/ha/tcp/SimpleTcpCluster.java (working copy)
+@@ -1,941 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.ha.tcp;
+-
+-import java.beans.PropertyChangeSupport;
+-import java.io.Serializable;
+-import java.util.ArrayList;
+-import java.util.HashMap;
+-import java.util.Iterator;
+-import java.util.List;
+-import java.util.Map;
+-
+-import org.apache.catalina.Container;
+-import org.apache.catalina.Context;
+-import org.apache.catalina.Engine;
+-import org.apache.catalina.Host;
+-import org.apache.catalina.Lifecycle;
+-import org.apache.catalina.LifecycleEvent;
+-import org.apache.catalina.LifecycleException;
+-import org.apache.catalina.LifecycleListener;
+-import org.apache.catalina.Manager;
+-import org.apache.catalina.Valve;
+-import org.apache.catalina.ha.CatalinaCluster;
+-import org.apache.catalina.ha.ClusterListener;
+-import org.apache.catalina.ha.ClusterManager;
+-import org.apache.catalina.ha.ClusterMessage;
+-import org.apache.catalina.ha.ClusterValve;
+-import org.apache.catalina.ha.session.DeltaManager;
+-import org.apache.catalina.ha.util.IDynamicProperty;
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.ChannelListener;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.MembershipListener;
+-import org.apache.catalina.tribes.group.GroupChannel;
+-import org.apache.catalina.util.LifecycleSupport;
+-import org.apache.catalina.util.StringManager;
+-import org.apache.juli.logging.Log;
+-import org.apache.juli.logging.LogFactory;
+-import org.apache.tomcat.util.IntrospectionUtils;
+-import org.apache.catalina.ha.session.ClusterSessionListener;
+-import org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor;
+-import org.apache.catalina.tribes.group.interceptors.TcpFailureDetector;
+-import org.apache.catalina.ha.session.JvmRouteBinderValve;
+-import org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener;
+-
+-/**
+- * A <b>Cluster </b> implementation using simple multicast. Responsible for
+- * setting up a cluster and provides callers with a valid multicast
+- * receiver/sender.
+- *
+- * FIXME remove install/remove/start/stop context dummys
+- * FIXME wrote testcases
+- *
+- * @author Filip Hanik
+- * @author Remy Maucherat
+- * @author Peter Rossbach
+- * @version $Revision$, $Date$
+- */
+-public class SimpleTcpCluster
+- implements CatalinaCluster, Lifecycle, LifecycleListener, IDynamicProperty,
+- MembershipListener, ChannelListener{
+-
+- public static Log log = LogFactory.getLog(SimpleTcpCluster.class);
+-
+- // ----------------------------------------------------- Instance Variables
+-
+- /**
+- * Descriptive information about this component implementation.
+- */
+- protected static final String info = "SimpleTcpCluster/2.2";
+-
+- public static final String BEFORE_MEMBERREGISTER_EVENT = "before_member_register";
+-
+- public static final String AFTER_MEMBERREGISTER_EVENT = "after_member_register";
+-
+- public static final String BEFORE_MANAGERREGISTER_EVENT = "before_manager_register";
+-
+- public static final String AFTER_MANAGERREGISTER_EVENT = "after_manager_register";
+-
+- public static final String BEFORE_MANAGERUNREGISTER_EVENT = "before_manager_unregister";
+-
+- public static final String AFTER_MANAGERUNREGISTER_EVENT = "after_manager_unregister";
+-
+- public static final String BEFORE_MEMBERUNREGISTER_EVENT = "before_member_unregister";
+-
+- public static final String AFTER_MEMBERUNREGISTER_EVENT = "after_member_unregister";
+-
+- public static final String SEND_MESSAGE_FAILURE_EVENT = "send_message_failure";
+-
+- public static final String RECEIVE_MESSAGE_FAILURE_EVENT = "receive_message_failure";
+-
+- /**
+- * Group channel.
+- */
+- protected Channel channel = new GroupChannel();
+-
+-
+- /**
+- * Name for logging purpose
+- */
+- protected String clusterImpName = "SimpleTcpCluster";
+-
+- /**
+- * The string manager for this package.
+- */
+- protected StringManager sm = StringManager.getManager(Constants.Package);
+-
+- /**
+- * The cluster name to join
+- */
+- protected String clusterName ;
+-
+- /**
+- * call Channel.heartbeat() at container background thread
+- * @see org.apache.catalina.tribes.group.GroupChannel#heartbeat()
+- */
+- protected boolean heartbeatBackgroundEnabled =false ;
+-
+- /**
+- * The Container associated with this Cluster.
+- */
+- protected Container container = null;
+-
+- /**
+- * The lifecycle event support for this component.
+- */
+- protected LifecycleSupport lifecycle = new LifecycleSupport(this);
+-
+- /**
+- * Has this component been started?
+- */
+- protected boolean started = false;
+-
+- /**
+- * The property change support for this component.
+- */
+- protected PropertyChangeSupport support = new PropertyChangeSupport(this);
+-
+- /**
+- * The context name <->manager association for distributed contexts.
+- */
+- protected Map managers = new HashMap();
+-
+- protected ClusterManager managerTemplate = new DeltaManager();
+-
+- private List valves = new ArrayList();
+-
+- private org.apache.catalina.ha.ClusterDeployer clusterDeployer;
+-
+- /**
+- * Listeners of messages
+- */
+- protected List clusterListeners = new ArrayList();
+-
+- /**
+- * Comment for <code>notifyLifecycleListenerOnFailure</code>
+- */
+- private boolean notifyLifecycleListenerOnFailure = false;
+-
+- /**
+- * dynamic sender <code>properties</code>
+- */
+- private Map properties = new HashMap();
+-
+- private int channelSendOptions = Channel.SEND_OPTIONS_ASYNCHRONOUS;
+-
+- // ------------------------------------------------------------- Properties
+-
+- public SimpleTcpCluster() {
+- }
+-
+- /**
+- * Return descriptive information about this Cluster implementation and the
+- * corresponding version number, in the format
+- * <code><description>/<version></code>.
+- */
+- public String getInfo() {
+- return (info);
+- }
+-
+- /**
+- * Return heartbeat enable flag (default false)
+- * @return the heartbeatBackgroundEnabled
+- */
+- public boolean isHeartbeatBackgroundEnabled() {
+- return heartbeatBackgroundEnabled;
+- }
+-
+- /**
+- * enabled that container backgroundThread call heartbeat at channel
+- * @param heartbeatBackgroundEnabled the heartbeatBackgroundEnabled to set
+- */
+- public void setHeartbeatBackgroundEnabled(boolean heartbeatBackgroundEnabled) {
+- this.heartbeatBackgroundEnabled = heartbeatBackgroundEnabled;
+- }
+-
+- /**
+- * Set the name of the cluster to join, if no cluster with this name is
+- * present create one.
+- *
+- * @param clusterName
+- * The clustername to join
+- */
+- public void setClusterName(String clusterName) {
+- this.clusterName = clusterName;
+- }
+-
+- /**
+- * Return the name of the cluster that this Server is currently configured
+- * to operate within.
+- *
+- * @return The name of the cluster associated with this server
+- */
+- public String getClusterName() {
+- if(clusterName == null && container != null)
+- return container.getName() ;
+- return clusterName;
+- }
+-
+- /**
+- * Set the Container associated with our Cluster
+- *
+- * @param container
+- * The Container to use
+- */
+- public void setContainer(Container container) {
+- Container oldContainer = this.container;
+- this.container = container;
+- support.firePropertyChange("container", oldContainer, this.container);
+- }
+-
+- /**
+- * Get the Container associated with our Cluster
+- *
+- * @return The Container associated with our Cluster
+- */
+- public Container getContainer() {
+- return (this.container);
+- }
+-
+- /**
+- * @return Returns the notifyLifecycleListenerOnFailure.
+- */
+- public boolean isNotifyLifecycleListenerOnFailure() {
+- return notifyLifecycleListenerOnFailure;
+- }
+-
+- /**
+- * @param notifyListenerOnFailure
+- * The notifyLifecycleListenerOnFailure to set.
+- */
+- public void setNotifyLifecycleListenerOnFailure(
+- boolean notifyListenerOnFailure) {
+- boolean oldNotifyListenerOnFailure = this.notifyLifecycleListenerOnFailure;
+- this.notifyLifecycleListenerOnFailure = notifyListenerOnFailure;
+- support.firePropertyChange("notifyLifecycleListenerOnFailure",
+- oldNotifyListenerOnFailure,
+- this.notifyLifecycleListenerOnFailure);
+- }
+-
+- /**
+- * @deprecated use getManagerTemplate().getClass().getName() instead.
+- * @return String
+- */
+- public String getManagerClassName() {
+- return managerTemplate.getClass().getName();
+- }
+-
+- /**
+- * @deprecated use nested <Manager> element inside the cluster config instead.
+- * @param managerClassName String
+- */
+- public void setManagerClassName(String managerClassName) {
+- log.warn("setManagerClassName is deprecated, use nested <Manager> element inside the <Cluster> element instead, this request will be ignored.");
+- }
+-
+- /**
+- * Add cluster valve
+- * Cluster Valves are only add to container when cluster is started!
+- * @param valve The new cluster Valve.
+- */
+- public void addValve(Valve valve) {
+- if (valve instanceof ClusterValve && (!valves.contains(valve)))
+- valves.add(valve);
+- }
+-
+- /**
+- * get all cluster valves
+- * @return current cluster valves
+- */
+- public Valve[] getValves() {
+- return (Valve[]) valves.toArray(new Valve[valves.size()]);
+- }
+-
+- /**
+- * Get the cluster listeners associated with this cluster. If this Array has
+- * no listeners registered, a zero-length array is returned.
+- */
+- public ClusterListener[] findClusterListeners() {
+- if (clusterListeners.size() > 0) {
+- ClusterListener[] listener = new ClusterListener[clusterListeners.size()];
+- clusterListeners.toArray(listener);
+- return listener;
+- } else
+- return new ClusterListener[0];
+-
+- }
+-
+- /**
+- * add cluster message listener and register cluster to this listener
+- *
+- * @see org.apache.catalina.ha.CatalinaCluster#addClusterListener(org.apache.catalina.ha.MessageListener)
+- */
+- public void addClusterListener(ClusterListener listener) {
+- if (listener != null && !clusterListeners.contains(listener)) {
+- clusterListeners.add(listener);
+- listener.setCluster(this);
+- }
+- }
+-
+- /**
+- * remove message listener and deregister Cluster from listener
+- *
+- * @see org.apache.catalina.ha.CatalinaCluster#removeClusterListener(org.apache.catalina.ha.MessageListener)
+- */
+- public void removeClusterListener(ClusterListener listener) {
+- if (listener != null) {
+- clusterListeners.remove(listener);
+- listener.setCluster(null);
+- }
+- }
+-
+- /**
+- * get current Deployer
+- */
+- public org.apache.catalina.ha.ClusterDeployer getClusterDeployer() {
+- return clusterDeployer;
+- }
+-
+- /**
+- * set a new Deployer, must be set before cluster started!
+- */
+- public void setClusterDeployer(
+- org.apache.catalina.ha.ClusterDeployer clusterDeployer) {
+- this.clusterDeployer = clusterDeployer;
+- }
+-
+- public void setChannel(Channel channel) {
+- this.channel = channel;
+- }
+-
+- public void setManagerTemplate(ClusterManager managerTemplate) {
+- this.managerTemplate = managerTemplate;
+- }
+-
+- public void setChannelSendOptions(int channelSendOptions) {
+- this.channelSendOptions = channelSendOptions;
+- }
+-
+- /**
+- * has members
+- */
+- protected boolean hasMembers = false;
+- public boolean hasMembers() {
+- return hasMembers;
+- }
+-
+- /**
+- * Get all current cluster members
+- * @return all members or empty array
+- */
+- public Member[] getMembers() {
+- return channel.getMembers();
+- }
+-
+- /**
+- * Return the member that represents this node.
+- *
+- * @return Member
+- */
+- public Member getLocalMember() {
+- return channel.getLocalMember(true);
+- }
+-
+- // ------------------------------------------------------------- dynamic
+- // manager property handling
+-
+- /**
+- * JMX hack to direct use at jconsole
+- *
+- * @param name
+- * @param value
+- */
+- public boolean setProperty(String name, String value) {
+- return setProperty(name, (Object) value);
+- }
+-
+- /**
+- * set config attributes with reflect and propagate to all managers
+- *
+- * @param name
+- * @param value
+- */
+- public boolean setProperty(String name, Object value) {
+- properties.put(name, value);
+- return false;
+- }
+-
+- /**
+- * get current config
+- *
+- * @param key
+- * @return The property
+- */
+- public Object getProperty(String key) {
+- if (log.isTraceEnabled())
+- log.trace(sm.getString("SimpleTcpCluster.getProperty", key));
+- return properties.get(key);
+- }
+-
+- /**
+- * Get all properties keys
+- *
+- * @return An iterator over the property names.
+- */
+- public Iterator getPropertyNames() {
+- return properties.keySet().iterator();
+- }
+-
+- /**
+- * remove a configured property.
+- *
+- * @param key
+- */
+- public void removeProperty(String key) {
+- properties.remove(key);
+- }
+-
+- /**
+- * transfer properties from cluster configuration to subelement bean.
+- * @param prefix
+- * @param bean
+- */
+- protected void transferProperty(String prefix, Object bean) {
+- if (prefix != null) {
+- for (Iterator iter = getPropertyNames(); iter.hasNext();) {
+- String pkey = (String) iter.next();
+- if (pkey.startsWith(prefix)) {
+- String key = pkey.substring(prefix.length() + 1);
+- Object value = getProperty(pkey);
+- IntrospectionUtils.setProperty(bean, key, value.toString());
+- }
+- }
+- }
+- }
+-
+- // --------------------------------------------------------- Public Methods
+-
+- /**
+- * @return Returns the managers.
+- */
+- public Map getManagers() {
+- return managers;
+- }
+-
+- public Channel getChannel() {
+- return channel;
+- }
+-
+- public ClusterManager getManagerTemplate() {
+- return managerTemplate;
+- }
+-
+- public int getChannelSendOptions() {
+- return channelSendOptions;
+- }
+-
+- /**
+- * Create new Manager without add to cluster (comes with start the manager)
+- *
+- * @param name
+- * Context Name of this manager
+- * @see org.apache.catalina.Cluster#createManager(java.lang.String)
+- * @see #addManager(String, Manager)
+- * @see DeltaManager#start()
+- */
+- public synchronized Manager createManager(String name) {
+- if (log.isDebugEnabled()) log.debug("Creating ClusterManager for context " + name + " using class " + getManagerClassName());
+- Manager manager = null;
+- try {
+- manager = managerTemplate.cloneFromTemplate();
+- ((ClusterManager)manager).setName(name);
+- } catch (Exception x) {
+- log.error("Unable to clone cluster manager, defaulting to org.apache.catalina.ha.session.DeltaManager", x);
+- manager = new org.apache.catalina.ha.session.DeltaManager();
+- } finally {
+- if ( manager != null && (manager instanceof ClusterManager)) ((ClusterManager)manager).setCluster(this);
+- }
+- return manager;
+- }
+-
+- public void registerManager(Manager manager) {
+-
+- if (! (manager instanceof ClusterManager)) {
+- log.warn("Manager [ " + manager + "] does not implement ClusterManager, addition to cluster has been aborted.");
+- return;
+- }
+- ClusterManager cmanager = (ClusterManager) manager ;
+- cmanager.setDistributable(true);
+- // Notify our interested LifecycleListeners
+- lifecycle.fireLifecycleEvent(BEFORE_MANAGERREGISTER_EVENT, manager);
+- String clusterName = getManagerName(cmanager.getName(), manager);
+- cmanager.setName(clusterName);
+- cmanager.setCluster(this);
+- cmanager.setDefaultMode(false);
+-
+- managers.put(clusterName, manager);
+- // Notify our interested LifecycleListeners
+- lifecycle.fireLifecycleEvent(AFTER_MANAGERREGISTER_EVENT, manager);
+- }
+-
+- /**
+- * remove an application form cluster replication bus
+- *
+- * @see org.apache.catalina.ha.CatalinaCluster#removeManager(java.lang.String,Manager)
+- */
+- public void removeManager(Manager manager) {
+- if (manager != null && manager instanceof ClusterManager ) {
+- ClusterManager cmgr = (ClusterManager) manager;
+- // Notify our interested LifecycleListeners
+- lifecycle.fireLifecycleEvent(BEFORE_MANAGERUNREGISTER_EVENT,manager);
+- managers.remove(getManagerName(cmgr.getName(),manager));
+- cmgr.setCluster(null);
+- // Notify our interested LifecycleListeners
+- lifecycle.fireLifecycleEvent(AFTER_MANAGERUNREGISTER_EVENT, manager);
+- }
+- }
+-
+- /**
+- * @param name
+- * @param manager
+- * @return
+- */
+- public String getManagerName(String name, Manager manager) {
+- String clusterName = name ;
+- if ( clusterName == null ) clusterName = manager.getContainer().getName();
+- if(getContainer() instanceof Engine) {
+- Container context = manager.getContainer() ;
+- if(context != null && context instanceof Context) {
+- Container host = ((Context)context).getParent();
+- if(host != null && host instanceof Host && clusterName!=null && !(clusterName.indexOf("#")>=0))
+- clusterName = host.getName() +"#" + clusterName ;
+- }
+- }
+- return clusterName;
+- }
+-
+- /*
+- * Get Manager
+- *
+- * @see org.apache.catalina.ha.CatalinaCluster#getManager(java.lang.String)
+- */
+- public Manager getManager(String name) {
+- return (Manager) managers.get(name);
+- }
+-
+- // ------------------------------------------------------ Lifecycle Methods
+-
+- /**
+- * Execute a periodic task, such as reloading, etc. This method will be
+- * invoked inside the classloading context of this container. Unexpected
+- * throwables will be caught and logged.
+- * @see org.apache.catalina.ha.deploy.FarmWarDeployer#backgroundProcess()
+- * @see org.apache.catalina.tribes.group.GroupChannel#heartbeat()
+- * @see org.apache.catalina.tribes.group.GroupChannel.HeartbeatThread#run()
+- *
+- */
+- public void backgroundProcess() {
+- if (clusterDeployer != null) clusterDeployer.backgroundProcess();
+-
+- //send a heartbeat through the channel
+- if ( isHeartbeatBackgroundEnabled() && channel !=null ) channel.heartbeat();
+- }
+-
+- /**
+- * Add a lifecycle event listener to this component.
+- *
+- * @param listener
+- * The listener to add
+- */
+- public void addLifecycleListener(LifecycleListener listener) {
+- lifecycle.addLifecycleListener(listener);
+- }
+-
+- /**
+- * Get the lifecycle listeners associated with this lifecycle. If this
+- * Lifecycle has no listeners registered, a zero-length array is returned.
+- */
+- public LifecycleListener[] findLifecycleListeners() {
+-
+- return lifecycle.findLifecycleListeners();
+-
+- }
+-
+- /**
+- * Remove a lifecycle event listener from this component.
+- *
+- * @param listener
+- * The listener to remove
+- */
+- public void removeLifecycleListener(LifecycleListener listener) {
+- lifecycle.removeLifecycleListener(listener);
+- }
+-
+- /**
+- * Use as base to handle start/stop/periodic Events from host. Currently
+- * only log the messages as trace level.
+- *
+- * @see org.apache.catalina.LifecycleListener#lifecycleEvent(org.apache.catalina.LifecycleEvent)
+- */
+- public void lifecycleEvent(LifecycleEvent lifecycleEvent) {
+- if (log.isTraceEnabled())
+- log.trace(sm.getString("SimpleTcpCluster.event.log", lifecycleEvent.getType(), lifecycleEvent.getData()));
+- }
+-
+- // ------------------------------------------------------ public
+-
+- /**
+- * Prepare for the beginning of active use of the public methods of this
+- * component. This method should be called after <code>configure()</code>,
+- * and before any of the public methods of the component are utilized. <BR>
+- * Starts the cluster communication channel, this will connect with the
+- * other nodes in the cluster, and request the current session state to be
+- * transferred to this node.
+- *
+- * @exception IllegalStateException
+- * if this component has already been started
+- * @exception LifecycleException
+- * if this component detects a fatal error that prevents this
+- * component from being used
+- */
+- public void start() throws LifecycleException {
+- if (started)
+- throw new LifecycleException(sm.getString("cluster.alreadyStarted"));
+- if (log.isInfoEnabled()) log.info("Cluster is about to start");
+-
+- // Notify our interested LifecycleListeners
+- lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, this);
+- try {
+- checkDefaults();
+- registerClusterValve();
+- channel.addMembershipListener(this);
+- channel.addChannelListener(this);
+- channel.start(channel.DEFAULT);
+- if (clusterDeployer != null) clusterDeployer.start();
+- this.started = true;
+- // Notify our interested LifecycleListeners
+- lifecycle.fireLifecycleEvent(AFTER_START_EVENT, this);
+- } catch (Exception x) {
+- log.error("Unable to start cluster.", x);
+- throw new LifecycleException(x);
+- }
+- }
+-
+- protected void checkDefaults() {
+- if ( clusterListeners.size() == 0 ) {
+- addClusterListener(new JvmRouteSessionIDBinderListener());
+- addClusterListener(new ClusterSessionListener());
+- }
+- if ( valves.size() == 0 ) {
+- addValve(new JvmRouteBinderValve());
+- addValve(new ReplicationValve());
+- }
+- if ( clusterDeployer != null ) clusterDeployer.setCluster(this);
+- if ( channel == null ) channel = new GroupChannel();
+- if ( channel instanceof GroupChannel && !((GroupChannel)channel).getInterceptors().hasNext()) {
+- channel.addInterceptor(new MessageDispatch15Interceptor());
+- channel.addInterceptor(new TcpFailureDetector());
+- }
+- }
+-
+- /**
+- * register all cluster valve to host or engine
+- * @throws Exception
+- * @throws ClassNotFoundException
+- */
+- protected void registerClusterValve() throws Exception {
+- if(container != null ) {
+- for (Iterator iter = valves.iterator(); iter.hasNext();) {
+- ClusterValve valve = (ClusterValve) iter.next();
+- if (log.isDebugEnabled())
+- log.debug("Invoking addValve on " + getContainer()
+- + " with class=" + valve.getClass().getName());
+- if (valve != null) {
+- IntrospectionUtils.callMethodN(getContainer(), "addValve",
+- new Object[] { valve },
+- new Class[] { org.apache.catalina.Valve.class });
+-
+- }
+- valve.setCluster(this);
+- }
+- }
+- }
+-
+- /**
+- * unregister all cluster valve to host or engine
+- * @throws Exception
+- * @throws ClassNotFoundException
+- */
+- protected void unregisterClusterValve() throws Exception {
+- for (Iterator iter = valves.iterator(); iter.hasNext();) {
+- ClusterValve valve = (ClusterValve) iter.next();
+- if (log.isDebugEnabled())
+- log.debug("Invoking removeValve on " + getContainer()
+- + " with class=" + valve.getClass().getName());
+- if (valve != null) {
+- IntrospectionUtils.callMethodN(getContainer(), "removeValve",
+- new Object[] { valve }, new Class[] { org.apache.catalina.Valve.class });
+- }
+- valve.setCluster(this);
+- }
+- }
+-
+- /**
+- * Gracefully terminate the active cluster component.<br/>
+- * This will disconnect the cluster communication channel, stop the
+- * listener and deregister the valves from host or engine.<br/><br/>
+- * <b>Note:</b><br/>The sub elements receiver, sender, membership,
+- * listener or valves are not removed. You can easily start the cluster again.
+- *
+- * @exception IllegalStateException
+- * if this component has not been started
+- * @exception LifecycleException
+- * if this component detects a fatal error that needs to be
+- * reported
+- */
+- public void stop() throws LifecycleException {
+-
+- if (!started)
+- throw new IllegalStateException(sm.getString("cluster.notStarted"));
+- // Notify our interested LifecycleListeners
+- lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, this);
+-
+- if (clusterDeployer != null) clusterDeployer.stop();
+- this.managers.clear();
+- try {
+- if ( clusterDeployer != null ) clusterDeployer.setCluster(null);
+- channel.stop(Channel.DEFAULT);
+- channel.removeChannelListener(this);
+- channel.removeMembershipListener(this);
+- this.unregisterClusterValve();
+- } catch (Exception x) {
+- log.error("Unable to stop cluster valve.", x);
+- }
+- started = false;
+- // Notify our interested LifecycleListeners
+- lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, this);
+- }
+-
+-
+-
+-
+- /**
+- * send message to all cluster members
+- * @param msg message to transfer
+- *
+- * @see org.apache.catalina.ha.CatalinaCluster#send(org.apache.catalina.ha.ClusterMessage)
+- */
+- public void send(ClusterMessage msg) {
+- send(msg, null);
+- }
+-
+- /**
+- * send message to all cluster members same cluster domain
+- *
+- * @see org.apache.catalina.ha.CatalinaCluster#send(org.apache.catalina.ha.ClusterMessage)
+- */
+- public void sendClusterDomain(ClusterMessage msg) {
+- send(msg,null);
+- }
+-
+-
+- /**
+- * send a cluster message to one member
+- *
+- * @param msg message to transfer
+- * @param dest Receiver member
+- * @see org.apache.catalina.ha.CatalinaCluster#send(org.apache.catalina.ha.ClusterMessage,
+- * org.apache.catalina.ha.Member)
+- */
+- public void send(ClusterMessage msg, Member dest) {
+- try {
+- msg.setAddress(getLocalMember());
+- if (dest != null) {
+- if (!getLocalMember().equals(dest)) {
+- channel.send(new Member[] {dest}, msg,channelSendOptions);
+- } else
+- log.error("Unable to send message to local member " + msg);
+- } else {
+- if (channel.getMembers().length>0)
+- channel.send(channel.getMembers(),msg,channelSendOptions);
+- else if (log.isDebugEnabled())
+- log.debug("No members in cluster, ignoring message:"+msg);
+- }
+- } catch (Exception x) {
+- log.error("Unable to send message through cluster sender.", x);
+- }
+- }
+-
+- /**
+- * New cluster member is registered
+- *
+- * @see org.apache.catalina.ha.MembershipListener#memberAdded(org.apache.catalina.ha.Member)
+- */
+- public void memberAdded(Member member) {
+- try {
+- hasMembers = channel.hasMembers();
+- if (log.isInfoEnabled()) log.info("Replication member added:" + member);
+- // Notify our interested LifecycleListeners
+- lifecycle.fireLifecycleEvent(BEFORE_MEMBERREGISTER_EVENT, member);
+- // Notify our interested LifecycleListeners
+- lifecycle.fireLifecycleEvent(AFTER_MEMBERREGISTER_EVENT, member);
+- } catch (Exception x) {
+- log.error("Unable to connect to replication system.", x);
+- }
+-
+- }
+-
+- /**
+- * Cluster member is gone
+- *
+- * @see org.apache.catalina.ha.MembershipListener#memberDisappeared(org.apache.catalina.ha.Member)
+- */
+- public void memberDisappeared(Member member) {
+- try {
+- hasMembers = channel.hasMembers();
+- if (log.isInfoEnabled()) log.info("Received member disappeared:" + member);
+- // Notify our interested LifecycleListeners
+- lifecycle.fireLifecycleEvent(BEFORE_MEMBERUNREGISTER_EVENT, member);
+- // Notify our interested LifecycleListeners
+- lifecycle.fireLifecycleEvent(AFTER_MEMBERUNREGISTER_EVENT, member);
+- } catch (Exception x) {
+- log.error("Unable remove cluster node from replication system.", x);
+- }
+- }
+-
+- // --------------------------------------------------------- receiver
+- // messages
+-
+- /**
+- * notify all listeners from receiving a new message is not ClusterMessage
+- * emitt Failure Event to LifecylceListener
+- *
+- * @param message
+- * receveived Message
+- */
+- public boolean accept(Serializable msg, Member sender) {
+- return (msg instanceof ClusterMessage);
+- }
+-
+-
+- public void messageReceived(Serializable message, Member sender) {
+- ClusterMessage fwd = (ClusterMessage)message;
+- fwd.setAddress(sender);
+- messageReceived(fwd);
+- }
+-
+- public void messageReceived(ClusterMessage message) {
+-
+- long start = 0;
+- if (log.isDebugEnabled() && message != null)
+- log.debug("Assuming clocks are synched: Replication for "
+- + message.getUniqueId() + " took="
+- + (System.currentTimeMillis() - (message).getTimestamp())
+- + " ms.");
+-
+- //invoke all the listeners
+- boolean accepted = false;
+- if (message != null) {
+- for (Iterator iter = clusterListeners.iterator(); iter.hasNext();) {
+- ClusterListener listener = (ClusterListener) iter.next();
+- if (listener.accept(message)) {
+- accepted = true;
+- listener.messageReceived(message);
+- }
+- }
+- }
+- if (!accepted && log.isDebugEnabled()) {
+- if (notifyLifecycleListenerOnFailure) {
+- Member dest = message.getAddress();
+- // Notify our interested LifecycleListeners
+- lifecycle.fireLifecycleEvent(RECEIVE_MESSAGE_FAILURE_EVENT,
+- new SendMessageData(message, dest, null));
+- }
+- log.debug("Message " + message.toString() + " from type "
+- + message.getClass().getName()
+- + " transfered but no listener registered");
+- }
+- return;
+- }
+-
+- // --------------------------------------------------------- Logger
+-
+- public Log getLogger() {
+- return log;
+- }
+-
+-
+-
+-
+- // ------------------------------------------------------------- deprecated
+-
+- /**
+- *
+- * @see org.apache.catalina.Cluster#setProtocol(java.lang.String)
+- */
+- public void setProtocol(String protocol) {
+- }
+-
+- /**
+- * @see org.apache.catalina.Cluster#getProtocol()
+- */
+- public String getProtocol() {
+- return null;
+- }
+-}
+Index: java/org/apache/catalina/ha/tcp/mbeans-descriptors.xml
+===================================================================
+--- java/org/apache/catalina/ha/tcp/mbeans-descriptors.xml (revision 590752)
++++ java/org/apache/catalina/ha/tcp/mbeans-descriptors.xml (working copy)
+@@ -1,1054 +0,0 @@
+-<?xml version="1.0" encoding="UTF-8"?>
+-<!--
+- 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.
+--->
+-<!DOCTYPE mbeans-descriptors PUBLIC
+- "-//Apache Software Foundation//DTD Model MBeans Configuration File"
+- "http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd">
+-<mbeans-descriptors>
+-
+- <mbean name="SimpleTcpCluster"
+- description="Tcp Cluster implementation"
+- domain="Catalina"
+- group="Cluster"
+- type="org.apache.catalina.ha.tcp.SimpleTcpCluster">
+- <attribute name="info"
+- description="Class version info"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute name="notifyLifecycleListenerOnFailure"
+- description="notify lifecycleListener from message transfer failure"
+- is="true"
+- type="boolean"/>
+- <attribute name="heartbeatBackgroundEnabled"
+- description="enable that container background thread call channel heartbeat, default is that channel mangage heartbeat itself."
+- is="true"
+- type="boolean"/>
+- <attribute name="clusterName"
+- description="name of cluster"
+- type="java.lang.String"/>
+- <attribute name="managerClassName"
+- description="session mananager classname"
+- type="java.lang.String"/>
+- <attribute name="clusterLogName"
+- description="Name of cluster transfer log device"
+- type="java.lang.String"/>
+- <attribute name="doClusterLog"
+- is="true"
+- description="enable cluster log transfer logging"
+- type="boolean"/>
+- <operation name="setProperty"
+- description="set a property to all cluster managers (with prefix 'manager.')"
+- impact="ACTION"
+- returnType="void">
+- <parameter name="key"
+- description="Property name"
+- type="java.lang.String"/>
+- <parameter name="value"
+- description="Property value"
+- type="java.lang.String"/>
+- </operation>
+-
+- <operation name="send"
+- description="send message to all cluster members"
+- impact="ACTION"
+- returnType="void">
+- <parameter name="message"
+- description="replication message"
+- type="org.apache.catalina.ha.ClusterMessage"/>
+- </operation>
+-
+- <operation name="sendClusterDomain"
+- description="send message to all cluster members with same domain"
+- impact="ACTION"
+- returnType="void">
+- <parameter name="message"
+- description="replication message"
+- type="org.apache.catalina.ha.ClusterMessage"/>
+- </operation>
+-
+- <operation name="start"
+- description="Start the cluster"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- <operation name="stop"
+- description="Stop the cluster"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- </mbean>
+-
+- <mbean name="ClusterReceiverBase"
+- description="Tcp Cluster ReplicationListener implementation"
+- domain="Catalina"
+- group="Cluster"
+- type="org.apache.catalina.ha.tcp.ClusterReceiverBase">
+- <attribute name="info"
+- description="Class version info"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute name="tcpListenAddress"
+- description="tcp listener address"
+- type="java.lang.String"/>
+- <attribute name="tcpListenPort"
+- description="tcp listener port"
+- type="int"/>
+- <attribute name="tcpThreadCount"
+- description="number of tcp listener worker threads"
+- type="int"/>
+- <attribute name="tcpSelectorTimeout"
+- description="tcp listener Selector timeout"
+- type="long"/>
+- <attribute name="nrOfMsgsReceived"
+- description="number of messages received from other nodes"
+- type="long"
+- writeable="false"/>
+- <attribute name="receivedTime"
+- description="total time message send time"
+- type="long"
+- writeable="false"/>
+- <attribute name="receivedProcessingTime"
+- description="received processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="minReceivedProcessingTime"
+- description="minimal received processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="avgReceivedProcessingTime"
+- description="received processing time / nrOfRequests"
+- type="double"
+- writeable="false"/>
+- <attribute name="maxReceivedProcessingTime"
+- description="maximal received processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="doReceivedProcessingStats"
+- description="create received processing time stats"
+- is="true"
+- type="boolean" />
+- <attribute name="avgTotalReceivedBytes"
+- description="received totalReceivedBytes / nrOfMsgsReceived"
+- type="long"
+- writeable="false"/>
+- <attribute name="totalReceivedBytes"
+- description="number of bytes received"
+- type="long"
+- writeable="false"/>
+- <attribute name="sendAck"
+- description="send ack after data received"
+- is="true"
+- type="boolean"
+- writeable="false" />
+- <attribute name="compress"
+- description="data received compressed"
+- is="true"
+- type="boolean"
+- writeable="false" />
+- <attribute name="doListen"
+- description="is port really started"
+- is="true"
+- type="boolean"
+- writeable="false" />
+-
+- <operation name="resetStatistics"
+- description="Reset all statistics"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- <operation name="start"
+- description="Start the cluster"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- <operation name="stop"
+- description="Stop the cluster"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- </mbean>
+-
+- <mbean name="SocketReplicationListener"
+- description="Tcp Cluster SocketReplicationListener implementation"
+- domain="Catalina"
+- group="Cluster"
+- type="org.apache.catalina.ha.tcp.SocketReplicationListener">
+- <attribute name="info"
+- description="Class version info"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute name="tcpListenAddress"
+- description="tcp listener address"
+- type="java.lang.String"/>
+- <attribute name="tcpListenPort"
+- description="tcp listener port"
+- type="int"/>
+- <attribute name="tcpListenMaxPort"
+- description="max tcp listen used port"
+- type="int"/>
+- <attribute name="tcpListenTimeout"
+- description="max tcp listen timeout (sec) wait for ServerSocket start"
+- type="int"/>
+- <attribute name="nrOfMsgsReceived"
+- description="number of messages received from other nodes"
+- type="long"
+- writeable="false"/>
+- <attribute name="receivedTime"
+- description="total time message send time"
+- type="long"
+- writeable="false"/>
+- <attribute name="receivedProcessingTime"
+- description="received processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="minReceivedProcessingTime"
+- description="minimal received processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="avgReceivedProcessingTime"
+- description="received processing time / nrOfRequests"
+- type="double"
+- writeable="false"/>
+- <attribute name="maxReceivedProcessingTime"
+- description="maximal received processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="doReceivedProcessingStats"
+- description="create received processing time stats"
+- is="true"
+- type="boolean" />
+- <attribute name="avgTotalReceivedBytes"
+- description="received totalReceivedBytes / nrOfMsgsReceived"
+- type="long"
+- writeable="false"/>
+- <attribute name="totalReceivedBytes"
+- description="number of bytes received"
+- type="long"
+- writeable="false"/>
+- <attribute name="sendAck"
+- description="send ack after data received"
+- is="true"
+- type="boolean"
+- writeable="false" />
+- <attribute name="compress"
+- description="data received compressed"
+- is="true"
+- type="boolean"
+- writeable="false" />
+- <attribute name="doListen"
+- description="is port really started"
+- is="true"
+- type="boolean"
+- writeable="false" />
+-
+- <operation name="resetStatistics"
+- description="Reset all statistics"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- <operation name="start"
+- description="Start the cluster"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- <operation name="stop"
+- description="Stop the cluster"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- </mbean>
+-
+- <mbean name="ReplicationTransmitter"
+- description="Tcp replication transmitter"
+- domain="Catalina"
+- group="ClusterSender"
+- type="org.apache.catalina.ha.tcp.ReplicationTransmitter">
+- <attribute name="info"
+- description="Class version info"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute name="replicationMode"
+- description="replication mode (synchnous,pooled.asynchnous,fastasyncqueue)"
+- type="java.lang.String"/>
+- <attribute name="ackTimeout"
+- description="acknowledge timeout"
+- type="long"/>
+- <attribute name="autoConnect"
+- description="is sender disabled, fork a new socket"
+- is="true"
+- type="boolean" />
+- <attribute name="waitForAck"
+- description="Wait for ack after data send"
+- is="true"
+- type="boolean"
+- writeable="false" />
+- <attribute name="processingTime"
+- description="sending processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="minProcessingTime"
+- description="minimal sending processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="avgProcessingTime"
+- description="processing time / nrOfRequests"
+- type="double"
+- writeable="false"/>
+- <attribute name="maxProcessingTime"
+- description="maximal sending processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="doTransmitterProcessingStats"
+- description="create processing time stats"
+- is="true"
+- type="boolean" />
+- <attribute name="nrOfRequests"
+- description="number of send messages to other members"
+- type="long"
+- writeable="false"/>
+- <attribute name="totalBytes"
+- description="number of bytes transfered"
+- type="long"
+- writeable="false"/>
+- <attribute name="failureCounter"
+- description="number of wrong transfers"
+- type="long"
+- writeable="false"/>
+- <attribute name="senderObjectNames"
+- description="get all sender object names"
+- type="[Ljavax.management.ObjectName;"
+- writeable="false"/>
+- <operation name="start"
+- description="Start the cluster"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+- <operation name="stop"
+- description="Stop the cluster"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+- <operation name="resetStatistics"
+- description="Reset all statistics"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+- <operation name="checkKeepAlive"
+- description="Check all sender connection for close socket (keepalive)"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+- </mbean>
+-
+- <mbean name="AsyncSocketSender"
+- description="Async Cluster Sender"
+- domain="Catalina"
+- group="IDataSender"
+- type="org.apache.catalina.ha.tcp.AsyncSocketSender">
+- <attribute name="info"
+- description="Class version info"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute name="address"
+- description="sender ip address"
+- type="java.net.InetAddress"
+- writeable="false"/>
+- <attribute name="port"
+- description="sender port"
+- type="int"
+- writeable="false" />
+- <attribute name="suspect"
+- description="Socket is gone"
+- type="boolean"/>
+- <attribute name="waitForAck"
+- description="Wait for ack after data send"
+- is="true"
+- type="boolean"
+- writeable="false"/>
+- <attribute name="ackTimeout"
+- description="acknowledge timeout"
+- type="long"/>
+- <attribute name="avgMessageSize"
+- writeable="false"
+- description="avg message size (totalbytes/nrOfRequests"
+- type="long"/>
+- <attribute name="queueSize"
+- writeable="false"
+- description="queue size"
+- type="int"/>
+- <attribute name="queuedNrOfBytes"
+- writeable="false"
+- description="number of bytes over all queued messages"
+- type="long"/>
+- <attribute name="messageTransferStarted"
+- description="message is in transfer"
+- type="boolean"
+- is="true"
+- writeable="false"/>
+- <attribute name="keepAliveTimeout"
+- description="active socket keep alive timeout"
+- type="long"/>
+- <attribute name="keepAliveMaxRequestCount"
+- description="max request over this socket"
+- type="int"/>
+- <attribute name="keepAliveCount"
+- description="keep Alive request count"
+- type="int"
+- writeable="false"/>
+- <attribute name="keepAliveConnectTime"
+- description="Connect time for keep alive"
+- type="long"
+- writeable="false"/>
+- <attribute name="resend"
+- description="after send failure make a resend"
+- is="true"
+- type="boolean" />
+- <attribute name="connected"
+- is="true"
+- description="socket connected"
+- type="boolean"
+- writeable="false"/>
+- <attribute name="nrOfRequests"
+- description="number of send messages to other members"
+- type="long"
+- writeable="false"/>
+- <attribute name="totalBytes"
+- description="number of bytes transfered"
+- type="long"
+- writeable="false"/>
+- <attribute name="processingTime"
+- description="sending processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="minProcessingTime"
+- description="minimal sending processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="avgProcessingTime"
+- description="processing time / nrOfRequests"
+- type="double"
+- writeable="false"/>
+- <attribute name="maxProcessingTime"
+- description="maximal sending processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="doProcessingStats"
+- description="create processing time stats"
+- is="true"
+- type="boolean" />
+- <attribute name="waitAckTime"
+- description="sending waitAck time"
+- type="long"
+- writeable="false"/>
+- <attribute name="minWaitAckTime"
+- description="minimal sending waitAck time"
+- type="long"
+- writeable="false"/>
+- <attribute name="avgWaitAckTime"
+- description="waitAck time / nrOfRequests"
+- type="double"
+- writeable="false"/>
+- <attribute name="maxWaitAckTime"
+- description="maximal sending waitAck time"
+- type="long"
+- writeable="false"/>
+- <attribute name="doWaitAckStats"
+- description="create waitAck time stats"
+- is="true"
+- type="boolean" />
+- <attribute name="connectCounter"
+- description="counts connects"
+- type="long"
+- writeable="false"/>
+- <attribute name="disconnectCounter"
+- description="counts disconnects"
+- type="long"
+- writeable="false"/>
+- <attribute name="socketOpenCounter"
+- description="counts open socket (KeepAlive and connects)"
+- type="long"
+- writeable="false"/>
+- <attribute name="socketOpenFailureCounter"
+- description="counts open socket failures"
+- type="long"
+- writeable="false"/>
+- <attribute name="socketCloseCounter"
+- description="counts closed socket (KeepAlive and disconnects)"
+- type="long"
+- writeable="false"/>
+- <attribute name="missingAckCounter"
+- description="counts missing ack"
+- type="long"
+- writeable="false"/>
+- <attribute name="dataResendCounter"
+- description="counts data resends"
+- type="long"
+- writeable="false"/>
+- <attribute name="dataFailureCounter"
+- description="counts data send failures"
+- type="long"
+- writeable="false"/>
+- <attribute name="inQueueCounter"
+- description="counts all queued messages"
+- type="long"
+- writeable="false"/>
+- <attribute name="outQueueCounter"
+- description="counts all successfully sended messages"
+- type="long"
+- writeable="false"/>
+- <operation name="connect"
+- description="connect to other replication node"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+- <operation name="disconnect"
+- description="disconnect to other replication node"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+- <operation name="checkKeepAlive"
+- description="Check connection for close socket"
+- impact="ACTION"
+- returnType="boolean">
+- </operation>
+- <operation name="resetStatistics"
+- description="Reset all statistics"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- </mbean>
+-
+- <mbean name="FastAsyncSocketSender"
+- description="Fast Async Cluster Sender"
+- domain="Catalina"
+- group="IDataSender"
+- type="org.apache.catalina.ha.tcp.FastAsyncSocketSender">
+- <attribute name="info"
+- description="Class version info"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute name="threadPriority"
+- description="change queue thread priority"
+- type="int"/>
+- <attribute name="address"
+- description="sender ip address"
+- type="java.net.InetAddress"
+- writeable="false"/>
+- <attribute name="port"
+- description="sender port"
+- type="int"
+- writeable="false" />
+- <attribute name="suspect"
+- description="Socket is gone"
+- type="boolean"/>
+- <attribute name="waitForAck"
+- description="Wait for ack after data send"
+- is="true"
+- type="boolean"
+- writeable="false"/>
+- <attribute name="ackTimeout"
+- description="acknowledge timeout"
+- type="long"/>
+- <attribute name="avgMessageSize"
+- writeable="false"
+- description="avg message size (totalbytes/nrOfRequests"
+- type="long" />
+- <attribute name="queueSize"
+- writeable="false"
+- description="queue size"
+- type="int"/>
+- <attribute name="queuedNrOfBytes"
+- writeable="false"
+- description="number of bytes over all queued messages"
+- type="long"/>
+- <attribute name="messageTransferStarted"
+- description="message is in transfer"
+- type="boolean"
+- is="true"
+- writeable="false"/>
+- <attribute name="keepAliveTimeout"
+- description="active socket keep alive timeout"
+- type="long"/>
+- <attribute name="keepAliveMaxRequestCount"
+- description="max request over this socket"
+- type="int"/>
+- <attribute name="queueAddWaitTimeout"
+- description="add wait timeout (default 10000 msec)"
+- type="long"/>
+- <attribute name="queueRemoveWaitTimeout"
+- description="remove wait timeout (default 30000 msec)"
+- type="long"/>
+- <attribute name="maxQueueLength"
+- description="max queue length"
+- type="int"/>
+- <attribute name="queueTimeWait"
+- description="remember queue wait times"
+- is="true"
+- type="boolean"/>
+- <attribute name="queueCheckLock"
+- description="check to lost locks"
+- is="true"
+- type="boolean"/>
+- <attribute name="queueDoStats"
+- description="activated queue stats"
+- is="true"
+- type="boolean"/>
+- <attribute name="keepAliveCount"
+- description="keep Alive request count"
+- type="int"
+- writeable="false"/>
+- <attribute name="keepAliveConnectTime"
+- description="Connect time for keep alive"
+- type="long"
+- writeable="false"/>
+- <attribute name="resend"
+- description="after send failure make a resend"
+- is="true"
+- type="boolean" />
+- <attribute name="connected"
+- is="true"
+- description="socket connected"
+- type="boolean"
+- writeable="false"/>
+- <attribute name="nrOfRequests"
+- description="number of send messages to other members"
+- type="long"
+- writeable="false"/>
+- <attribute name="totalBytes"
+- description="number of bytes transfered"
+- type="long"
+- writeable="false"/>
+- <attribute name="processingTime"
+- description="sending processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="minProcessingTime"
+- description="minimal sending processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="avgProcessingTime"
+- description="processing time / nrOfRequests"
+- type="double"
+- writeable="false"/>
+- <attribute name="maxProcessingTime"
+- description="maximal sending processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="doProcessingStats"
+- description="create Processing time stats"
+- is="true"
+- type="boolean" />
+- <attribute name="waitAckTime"
+- description="sending waitAck time"
+- type="long"
+- writeable="false"/>
+- <attribute name="minWaitAckTime"
+- description="minimal sending waitAck time"
+- type="long"
+- writeable="false"/>
+- <attribute name="avgWaitAckTime"
+- description="waitAck time / nrOfRequests"
+- type="double"
+- writeable="false"/>
+- <attribute name="maxWaitAckTime"
+- description="maximal sending waitAck time"
+- type="long"
+- writeable="false"/>
+- <attribute name="doWaitAckStats"
+- description="create waitAck time stats"
+- is="true"
+- type="boolean" />
+- <attribute name="connectCounter"
+- description="counts connects"
+- type="long"
+- writeable="false"/>
+- <attribute name="disconnectCounter"
+- description="counts disconnects"
+- type="long"
+- writeable="false"/>
+- <attribute name="socketOpenCounter"
+- description="counts open socket (KeepAlive and connects)"
+- type="long"
+- writeable="false"/>
+- <attribute name="socketOpenFailureCounter"
+- description="counts open socket failures"
+- type="long"
+- writeable="false"/>
+- <attribute name="socketCloseCounter"
+- description="counts closed socket (KeepAlive and disconnects)"
+- type="long"
+- writeable="false"/>
+- <attribute name="missingAckCounter"
+- description="counts missing ack"
+- type="long"
+- writeable="false"/>
+- <attribute name="dataResendCounter"
+- description="counts data resends"
+- type="long"
+- writeable="false"/>
+- <attribute name="dataFailureCounter"
+- description="counts data send failures"
+- type="long"
+- writeable="false"/>
+- <attribute name="inQueueCounter"
+- description="counts all queued messages"
+- type="long"
+- writeable="false"/>
+- <attribute name="outQueueCounter"
+- description="counts all successfully sended messages"
+- type="long"
+- writeable="false"/>
+- <attribute name="queueAddWaitTime"
+- description="queue add wait time (tomcat thread waits)"
+- type="long"
+- writeable="false"/>
+- <attribute name="queueRemoveWaitTime"
+- description="queue remove wait time (queue thread waits)"
+- type="long"
+- writeable="false"/>
+- <operation name="connect"
+- description="connect to other replication node"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+- <operation name="disconnect"
+- description="disconnect to other replication node"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+- <operation name="checkKeepAlive"
+- description="Check connection for close socket"
+- impact="ACTION"
+- returnType="boolean">
+- </operation>
+- <operation name="resetStatistics"
+- description="Reset all statistics"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- </mbean>
+-
+- <mbean name="PooledSocketSender"
+- description="Pooled Cluster Sender"
+- domain="Catalina"
+- group="IDataSender"
+- type="org.apache.catalina.ha.tcp.PooledSocketSender">
+- <attribute name="address"
+- description="sender ip address"
+- type="java.net.InetAddress"
+- writeable="false"/>
+- <attribute name="port"
+- description="sender port"
+- type="int"
+- writeable="false" />
+- <attribute name="suspect"
+- description="Socket is gone"
+- type="boolean"/>
+- <attribute name="ackTimeout"
+- description="acknowledge timeout"
+- type="long"/>
+- <attribute name="waitForAck"
+- description="Wait for ack after data send"
+- is="true"
+- type="boolean"
+- writeable="false" />
+- <attribute name="maxPoolSocketLimit"
+- description="Max parallel sockets"
+- type="int"/>
+- <attribute name="keepAliveTimeout"
+- description="active socket keep alive timeout"
+- type="long"/>
+- <attribute name="keepAliveMaxRequestCount"
+- description="max request over this socket"
+- type="int"/>
+- <attribute name="resend"
+- description="after send failure make a resend"
+- is="true"
+- type="boolean" />
+- <attribute name="connected"
+- is="true"
+- description="socket connected"
+- type="boolean"
+- writeable="false"/>
+- <attribute name="avgMessageSize"
+- writeable="false"
+- description="avg message size (totalbytes/nrOfRequests"
+- type="long"/>
+- <attribute name="nrOfRequests"
+- description="number of send messages to other members"
+- type="long"
+- writeable="false"/>
+- <attribute name="totalBytes"
+- description="number of bytes transfered"
+- type="long"
+- writeable="false"/>
+- <attribute name="connectCounter"
+- description="counts connects"
+- type="long"
+- writeable="false"/>
+- <attribute name="disconnectCounter"
+- description="counts disconnects"
+- type="long"
+- writeable="false"/>
+- <operation name="connect"
+- description="start Queue to connect to ohter replication node"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+- <operation name="disconnect"
+- description="stop Queue to other replication node"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+- <operation name="resetStatistics"
+- description="Reset all statistics"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- </mbean>
+-
+- <mbean name="SocketSender"
+- description="Sync Cluster Sender"
+- domain="Catalina"
+- group="IDataSender"
+- type="org.apache.catalina.ha.tcp.SocketSender">
+- <attribute name="address"
+- description="sender ip address"
+- type="java.net.InetAddress"
+- writeable="false"/>
+- <attribute name="port"
+- description="sender port"
+- type="int"
+- writeable="false" />
+- <attribute name="suspect"
+- description="Socket is gone"
+- type="boolean"/>
+- <attribute name="ackTimeout"
+- description="acknowledge timeout"
+- type="long"/>
+- <attribute name="waitForAck"
+- description="Wait for ack after data send"
+- is="true"
+- type="boolean"
+- writeable="false" />
+- <attribute name="keepAliveTimeout"
+- description="active socket keep alive timeout"
+- type="long"/>
+- <attribute name="keepAliveMaxRequestCount"
+- description="max request over this socket"
+- type="int"/>
+- <attribute name="messageTransferStarted"
+- description="message is in transfer"
+- type="boolean"
+- is="true"
+- writeable="false"/>
+- <attribute name="keepAliveCount"
+- description="keep Alive request count"
+- type="int"
+- writeable="false"/>
+- <attribute name="keepAliveConnectTime"
+- description="Connect time for keep alive"
+- type="long"
+- writeable="false"/>
+- <attribute name="resend"
+- description="after send failure make a resend"
+- is="true"
+- type="boolean" />
+- <attribute name="connected"
+- is="true"
+- description="socket connected"
+- type="boolean"
+- writeable="false"/>
+- <attribute name="avgMessageSize"
+- writeable="false"
+- description="avg message size (totalbytes/nrOfRequests"
+- type="long"/>
+- <attribute name="nrOfRequests"
+- description="number of send messages to other members"
+- type="long"
+- writeable="false"/>
+- <attribute name="totalBytes"
+- description="number of bytes transfered"
+- type="long"
+- writeable="false"/>
+- <attribute name="processingTime"
+- description="sending processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="minProcessingTime"
+- description="minimal sending processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="avgProcessingTime"
+- description="processing time / nrOfRequests"
+- type="double"
+- writeable="false"/>
+- <attribute name="maxProcessingTime"
+- description="maximal sending processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="doProcessingStats"
+- description="create Processing time stats"
+- is="true"
+- type="boolean" />
+- <attribute name="waitAckTime"
+- description="sending waitAck time"
+- type="long"
+- writeable="false"/>
+- <attribute name="minWaitAckTime"
+- description="minimal sending waitAck time"
+- type="long"
+- writeable="false"/>
+- <attribute name="avgWaitAckTime"
+- description="waitAck time / nrOfRequests"
+- type="double"
+- writeable="false"/>
+- <attribute name="maxWaitAckTime"
+- description="maximal sending waitAck time"
+- type="long"
+- writeable="false"/>
+- <attribute name="doWaitAckStats"
+- description="create waitAck time stats"
+- is="true"
+- type="boolean" />
+- <attribute name="connectCounter"
+- description="counts connects"
+- type="long"
+- writeable="false"/>
+- <attribute name="disconnectCounter"
+- description="counts disconnects"
+- type="long"
+- writeable="false"/>
+- <attribute name="socketCloseCounter"
+- description="counts closed socket (KeepAlive and disconnects)"
+- type="long"
+- writeable="false"/>
+- <attribute name="socketOpenFailureCounter"
+- description="counts open socket failures"
+- type="long"
+- writeable="false"/>
+- <attribute name="socketOpenCounter"
+- description="counts open socket (KeepAlive and connects)"
+- type="long"
+- writeable="false"/>
+- <attribute name="missingAckCounter"
+- description="counts missing ack"
+- type="long"
+- writeable="false"/>
+- <attribute name="dataResendCounter"
+- description="counts data resends"
+- type="long"
+- writeable="false"/>
+- <attribute name="dataFailureCounter"
+- description="counts data send failures"
+- type="long"
+- writeable="false"/>
+- <operation name="connect"
+- description="connect to other replication node"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+- <operation name="disconnect"
+- description="disconnect to other replication node"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+- <operation name="checkKeepAlive"
+- description="Check connection for close socket"
+- impact="ACTION"
+- returnType="boolean">
+- </operation>
+- <operation name="resetStatistics"
+- description="Reset all statistics"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- </mbean>
+-
+- <mbean name="ReplicationValve"
+- description="Valve for simple tcp replication"
+- domain="Catalina"
+- group="Valve"
+- type="org.apache.catalina.ha.tcp.ReplicationValve">
+- <attribute name="info"
+- description="Class version info"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute name="filter"
+- description="resource filter to disable session replication check"
+- type="java.lang.String"/>
+- <attribute name="primaryIndicator"
+- is="true"
+- description="set indicator that request processing is at primary session node"
+- type="boolean"/>
+- <attribute name="primaryIndicatorName"
+- description="Request attribute name to indicate that request processing is at primary session node"
+- type="java.lang.String"/>
+- <attribute name="doProcessingStats"
+- is="true"
+- description="active statistics counting"
+- type="boolean"/>
+- <attribute name="nrOfRequests"
+- description="number of replicated requests"
+- type="long"
+- writeable="false"/>
+- <attribute name="nrOfFilterRequests"
+- description="number of filtered requests"
+- type="long"
+- writeable="false"/>
+- <attribute name="nrOfSendRequests"
+- description="number of send requests"
+- type="long"
+- writeable="false"/>
+- <attribute name="nrOfCrossContextSendRequests"
+- description="number of send cross context session requests"
+- type="long"
+- writeable="false"/>
+- <attribute name="totalRequestTime"
+- description="total replicated request time"
+- type="long"
+- writeable="false"/>
+- <attribute name="totalSendTime"
+- description="total replicated send time"
+- type="long"
+- writeable="false"/>
+- <attribute name="lastSendTime"
+- description="last replicated request time"
+- type="long"
+- writeable="false"/>
+- <operation name="resetStatistics"
+- description="Reset all statistics"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- </mbean>
+-
+-
+-</mbeans-descriptors>
+Index: java/org/apache/catalina/ha/tcp/ReplicationValve.java
+===================================================================
+--- java/org/apache/catalina/ha/tcp/ReplicationValve.java (revision 590752)
++++ java/org/apache/catalina/ha/tcp/ReplicationValve.java (working copy)
+@@ -1,658 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.ha.tcp;
+-
+-import java.io.IOException;
+-import java.util.StringTokenizer;
+-import java.util.regex.Pattern;
+-import java.util.ArrayList;
+-import java.util.List;
+-import java.util.Iterator;
+-import javax.servlet.ServletException;
+-
+-import org.apache.catalina.Manager;
+-import org.apache.catalina.Session;
+-import org.apache.catalina.Context;
+-import org.apache.catalina.core.StandardContext;
+-import org.apache.catalina.ha.CatalinaCluster;
+-import org.apache.catalina.ha.ClusterManager;
+-import org.apache.catalina.ha.ClusterMessage;
+-import org.apache.catalina.ha.ClusterSession;
+-import org.apache.catalina.ha.ClusterValve;
+-import org.apache.catalina.ha.session.DeltaManager;
+-import org.apache.catalina.ha.session.DeltaSession;
+-import org.apache.catalina.connector.Request;
+-import org.apache.catalina.connector.Response;
+-import org.apache.catalina.util.StringManager;
+-import org.apache.catalina.valves.ValveBase;
+-
+-/**
+- * <p>Implementation of a Valve that logs interesting contents from the
+- * specified Request (before processing) and the corresponding Response
+- * (after processing). It is especially useful in debugging problems
+- * related to headers and cookies.</p>
+- *
+- * <p>This Valve may be attached to any Container, depending on the granularity
+- * of the logging you wish to perform.</p>
+- *
+- * <p>primaryIndicator=true, then the request attribute <i>org.apache.catalina.ha.tcp.isPrimarySession.</i>
+- * is set true, when request processing is at sessions primary node.
+- * </p>
+- *
+- * @author Craig R. McClanahan
+- * @author Filip Hanik
+- * @author Peter Rossbach
+- * @version $Revision$ $Date$
+- */
+-
+-public class ReplicationValve
+- extends ValveBase implements ClusterValve {
+-
+- private static org.apache.juli.logging.Log log =
+- org.apache.juli.logging.LogFactory.getLog( ReplicationValve.class );
+-
+- // ----------------------------------------------------- Instance Variables
+-
+- /**
+- * The descriptive information related to this implementation.
+- */
+- private static final String info =
+- "org.apache.catalina.ha.tcp.ReplicationValve/2.0";
+-
+-
+- /**
+- * The StringManager for this package.
+- */
+- protected static StringManager sm =
+- StringManager.getManager(Constants.Package);
+-
+- private CatalinaCluster cluster = null ;
+-
+- /**
+- * holds file endings to not call for like images and others
+- */
+- protected java.util.regex.Pattern[] reqFilters = new java.util.regex.Pattern[0];
+-
+- /**
+- * Orginal filter
+- */
+- protected String filter ;
+-
+- /**
+- * crossContext session container
+- */
+- protected ThreadLocal crossContextSessions = new ThreadLocal() ;
+-
+- /**
+- * doProcessingStats (default = off)
+- */
+- protected boolean doProcessingStats = false;
+-
+- protected long totalRequestTime = 0;
+- protected long totalSendTime = 0;
+- protected long nrOfRequests = 0;
+- protected long lastSendTime = 0;
+- protected long nrOfFilterRequests = 0;
+- protected long nrOfSendRequests = 0;
+- protected long nrOfCrossContextSendRequests = 0;
+-
+- /**
+- * must primary change indicator set
+- */
+- protected boolean primaryIndicator = false ;
+-
+- /**
+- * Name of primary change indicator as request attribute
+- */
+- protected String primaryIndicatorName = "org.apache.catalina.ha.tcp.isPrimarySession";
+-
+- // ------------------------------------------------------------- Properties
+-
+- public ReplicationValve() {
+- }
+-
+- /**
+- * Return descriptive information about this Valve implementation.
+- */
+- public String getInfo() {
+-
+- return (info);
+-
+- }
+-
+- /**
+- * @return Returns the cluster.
+- */
+- public CatalinaCluster getCluster() {
+- return cluster;
+- }
+-
+- /**
+- * @param cluster The cluster to set.
+- */
+- public void setCluster(CatalinaCluster cluster) {
+- this.cluster = cluster;
+- }
+-
+- /**
+- * @return Returns the filter
+- */
+- public String getFilter() {
+- return filter ;
+- }
+-
+- /**
+- * compile filter string to regular expressions
+- * @see Pattern#compile(java.lang.String)
+- * @param filter
+- * The filter to set.
+- */
+- public void setFilter(String filter) {
+- if (log.isDebugEnabled())
+- log.debug(sm.getString("ReplicationValve.filter.loading", filter));
+- this.filter = filter;
+- StringTokenizer t = new StringTokenizer(filter, ";");
+- this.reqFilters = new Pattern[t.countTokens()];
+- int i = 0;
+- while (t.hasMoreTokens()) {
+- String s = t.nextToken();
+- if (log.isTraceEnabled())
+- log.trace(sm.getString("ReplicationValve.filter.token", s));
+- try {
+- reqFilters[i++] = Pattern.compile(s);
+- } catch (Exception x) {
+- log.error(sm.getString("ReplicationValve.filter.token.failure",
+- s), x);
+- }
+- }
+- }
+-
+- /**
+- * @return Returns the primaryIndicator.
+- */
+- public boolean isPrimaryIndicator() {
+- return primaryIndicator;
+- }
+-
+- /**
+- * @param primaryIndicator The primaryIndicator to set.
+- */
+- public void setPrimaryIndicator(boolean primaryIndicator) {
+- this.primaryIndicator = primaryIndicator;
+- }
+-
+- /**
+- * @return Returns the primaryIndicatorName.
+- */
+- public String getPrimaryIndicatorName() {
+- return primaryIndicatorName;
+- }
+-
+- /**
+- * @param primaryIndicatorName The primaryIndicatorName to set.
+- */
+- public void setPrimaryIndicatorName(String primaryIndicatorName) {
+- this.primaryIndicatorName = primaryIndicatorName;
+- }
+-
+- /**
+- * Calc processing stats
+- */
+- public boolean doStatistics() {
+- return doProcessingStats;
+- }
+-
+- /**
+- * Set Calc processing stats
+- * @see #resetStatistics()
+- */
+- public void setStatistics(boolean doProcessingStats) {
+- this.doProcessingStats = doProcessingStats;
+- }
+-
+- /**
+- * @return Returns the lastSendTime.
+- */
+- public long getLastSendTime() {
+- return lastSendTime;
+- }
+-
+- /**
+- * @return Returns the nrOfRequests.
+- */
+- public long getNrOfRequests() {
+- return nrOfRequests;
+- }
+-
+- /**
+- * @return Returns the nrOfFilterRequests.
+- */
+- public long getNrOfFilterRequests() {
+- return nrOfFilterRequests;
+- }
+-
+- /**
+- * @return Returns the nrOfCrossContextSendRequests.
+- */
+- public long getNrOfCrossContextSendRequests() {
+- return nrOfCrossContextSendRequests;
+- }
+-
+- /**
+- * @return Returns the nrOfSendRequests.
+- */
+- public long getNrOfSendRequests() {
+- return nrOfSendRequests;
+- }
+-
+- /**
+- * @return Returns the totalRequestTime.
+- */
+- public long getTotalRequestTime() {
+- return totalRequestTime;
+- }
+-
+- /**
+- * @return Returns the totalSendTime.
+- */
+- public long getTotalSendTime() {
+- return totalSendTime;
+- }
+-
+- /**
+- * @return Returns the reqFilters.
+- */
+- protected java.util.regex.Pattern[] getReqFilters() {
+- return reqFilters;
+- }
+-
+- /**
+- * @param reqFilters The reqFilters to set.
+- */
+- protected void setReqFilters(java.util.regex.Pattern[] reqFilters) {
+- this.reqFilters = reqFilters;
+- }
+-
+-
+- // --------------------------------------------------------- Public Methods
+-
+- /**
+- * Register all cross context sessions inside endAccess.
+- * Use a list with contains check, that the Portlet API can include a lot of fragments from same or
+- * different applications with session changes.
+- *
+- * @param session cross context session
+- */
+- public void registerReplicationSession(DeltaSession session) {
+- List sessions = (List)crossContextSessions.get();
+- if(sessions != null) {
+- if(!sessions.contains(session)) {
+- if(log.isDebugEnabled())
+- log.debug(sm.getString("ReplicationValve.crossContext.registerSession",
+- session.getIdInternal(),
+- session.getManager().getContainer().getName()));
+- sessions.add(session);
+- }
+- }
+- }
+-
+- /**
+- * Log the interesting request parameters, invoke the next Valve in the
+- * sequence, and log the interesting response parameters.
+- *
+- * @param request The servlet request to be processed
+- * @param response The servlet response to be created
+- *
+- * @exception IOException if an input/output error occurs
+- * @exception ServletException if a servlet error occurs
+- */
+- public void invoke(Request request, Response response)
+- throws IOException, ServletException
+- {
+- long totalstart = 0;
+-
+- //this happens before the request
+- if(doStatistics()) {
+- totalstart = System.currentTimeMillis();
+- }
+- if (primaryIndicator) {
+- createPrimaryIndicator(request) ;
+- }
+- Context context = request.getContext();
+- boolean isCrossContext = context != null
+- && context instanceof StandardContext
+- && ((StandardContext) context).getCrossContext();
+- try {
+- if(isCrossContext) {
+- if(log.isDebugEnabled())
+- log.debug(sm.getString("ReplicationValve.crossContext.add"));
+- //FIXME add Pool of Arraylists
+- crossContextSessions.set(new ArrayList());
+- }
+- getNext().invoke(request, response);
+- Manager manager = request.getContext().getManager();
+- if (manager != null && manager instanceof ClusterManager) {
+- ClusterManager clusterManager = (ClusterManager) manager;
+- CatalinaCluster containerCluster = (CatalinaCluster) getContainer().getCluster();
+- if (containerCluster == null) {
+- if (log.isWarnEnabled())
+- log.warn(sm.getString("ReplicationValve.nocluster"));
+- return;
+- }
+- // valve cluster can access manager - other cluster handle replication
+- // at host level - hopefully!
+- if(containerCluster.getManager(clusterManager.getName()) == null)
+- return ;
+- if(containerCluster.hasMembers()) {
+- sendReplicationMessage(request, totalstart, isCrossContext, clusterManager, containerCluster);
+- } else {
+- resetReplicationRequest(request,isCrossContext);
+- }
+- }
+- } finally {
+- // Array must be remove: Current master request send endAccess at recycle.
+- // Don't register this request session again!
+- if(isCrossContext) {
+- if(log.isDebugEnabled())
+- log.debug(sm.getString("ReplicationValve.crossContext.remove"));
+- // crossContextSessions.remove() only exist at Java 5
+- // register ArrayList at a pool
+- crossContextSessions.set(null);
+- }
+- }
+- }
+-
+-
+- /**
+- * reset the active statitics
+- */
+- public void resetStatistics() {
+- totalRequestTime = 0 ;
+- totalSendTime = 0 ;
+- lastSendTime = 0 ;
+- nrOfFilterRequests = 0 ;
+- nrOfRequests = 0 ;
+- nrOfSendRequests = 0;
+- nrOfCrossContextSendRequests = 0;
+- }
+-
+- /**
+- * Return a String rendering of this object.
+- */
+- public String toString() {
+-
+- StringBuffer sb = new StringBuffer("ReplicationValve[");
+- if (container != null)
+- sb.append(container.getName());
+- sb.append("]");
+- return (sb.toString());
+-
+- }
+-
+- // --------------------------------------------------------- Protected Methods
+-
+- /**
+- * @param request
+- * @param totalstart
+- * @param isCrossContext
+- * @param clusterManager
+- * @param containerCluster
+- */
+- protected void sendReplicationMessage(Request request, long totalstart, boolean isCrossContext, ClusterManager clusterManager, CatalinaCluster containerCluster) {
+- //this happens after the request
+- long start = 0;
+- if(doStatistics()) {
+- start = System.currentTimeMillis();
+- }
+- try {
+- // send invalid sessions
+- // DeltaManager returns String[0]
+- if (!(clusterManager instanceof DeltaManager))
+- sendInvalidSessions(clusterManager, containerCluster);
+- // send replication
+- sendSessionReplicationMessage(request, clusterManager, containerCluster);
+- if(isCrossContext)
+- sendCrossContextSession(containerCluster);
+- } catch (Exception x) {
+- // FIXME we have a lot of sends, but the trouble with one node stops the correct replication to other nodes!
+- log.error(sm.getString("ReplicationValve.send.failure"), x);
+- } finally {
+- // FIXME this stats update are not cheap!!
+- if(doStatistics()) {
+- updateStats(totalstart,start);
+- }
+- }
+- }
+-
+- /**
+- * Send all changed cross context sessions to backups
+- * @param containerCluster
+- */
+- protected void sendCrossContextSession(CatalinaCluster containerCluster) {
+- Object sessions = crossContextSessions.get();
+- if(sessions != null && sessions instanceof List
+- && ((List)sessions).size() >0) {
+- for(Iterator iter = ((List)sessions).iterator(); iter.hasNext() ;) {
+- Session session = (Session)iter.next();
+- if(log.isDebugEnabled())
+- log.debug(sm.getString("ReplicationValve.crossContext.sendDelta",
+- session.getManager().getContainer().getName() ));
+- sendMessage(session,(ClusterManager)session.getManager(),containerCluster);
+- if(doStatistics()) {
+- nrOfCrossContextSendRequests++;
+- }
+- }
+- }
+- }
+-
+- /**
+- * Fix memory leak for long sessions with many changes, when no backup member exists!
+- * @param request current request after responce is generated
+- * @param isCrossContext check crosscontext threadlocal
+- */
+- protected void resetReplicationRequest(Request request, boolean isCrossContext) {
+- Session contextSession = request.getSessionInternal(false);
+- if(contextSession != null & contextSession instanceof DeltaSession){
+- resetDeltaRequest(contextSession);
+- ((DeltaSession)contextSession).setPrimarySession(true);
+- }
+- if(isCrossContext) {
+- Object sessions = crossContextSessions.get();
+- if(sessions != null && sessions instanceof List
+- && ((List)sessions).size() >0) {
+- Iterator iter = ((List)sessions).iterator();
+- for(; iter.hasNext() ;) {
+- Session session = (Session)iter.next();
+- resetDeltaRequest(session);
+- if(session instanceof DeltaSession)
+- ((DeltaSession)contextSession).setPrimarySession(true);
+-
+- }
+- }
+- }
+- }
+-
+- /**
+- * Reset DeltaRequest from session
+- * @param session HttpSession from current request or cross context session
+- */
+- protected void resetDeltaRequest(Session session) {
+- if(log.isDebugEnabled()) {
+- log.debug(sm.getString("ReplicationValve.resetDeltaRequest" ,
+- session.getManager().getContainer().getName() ));
+- }
+- ((DeltaSession)session).resetDeltaRequest();
+- }
+-
+- /**
+- * Send Cluster Replication Request
+- * @param request current request
+- * @param manager session manager
+- * @param cluster replication cluster
+- */
+- protected void sendSessionReplicationMessage(Request request,
+- ClusterManager manager, CatalinaCluster cluster) {
+- Session session = request.getSessionInternal(false);
+- if (session != null) {
+- String uri = request.getDecodedRequestURI();
+- // request without session change
+- if (!isRequestWithoutSessionChange(uri)) {
+- if (log.isDebugEnabled())
+- log.debug(sm.getString("ReplicationValve.invoke.uri", uri));
+- sendMessage(session,manager,cluster);
+- } else
+- if(doStatistics())
+- nrOfFilterRequests++;
+- }
+-
+- }
+-
+- /**
+- * Send message delta message from request session
+- * @param request current request
+- * @param manager session manager
+- * @param cluster replication cluster
+- */
+- protected void sendMessage(Session session,
+- ClusterManager manager, CatalinaCluster cluster) {
+- String id = session.getIdInternal();
+- if (id != null) {
+- send(manager, cluster, id);
+- }
+- }
+-
+- /**
+- * send manager requestCompleted message to cluster
+- * @param manager SessionManager
+- * @param cluster replication cluster
+- * @param sessionId sessionid from the manager
+- * @see DeltaManager#requestCompleted(String)
+- * @see SimpleTcpCluster#send(ClusterMessage)
+- */
+- protected void send(ClusterManager manager, CatalinaCluster cluster, String sessionId) {
+- ClusterMessage msg = manager.requestCompleted(sessionId);
+- if (msg != null) {
+- if(manager.doDomainReplication()) {
+- cluster.sendClusterDomain(msg);
+- } else {
+- cluster.send(msg);
+- }
+- if(doStatistics())
+- nrOfSendRequests++;
+- }
+- }
+-
+- /**
+- * check for session invalidations
+- * @param manager
+- * @param cluster
+- */
+- protected void sendInvalidSessions(ClusterManager manager, CatalinaCluster cluster) {
+- String[] invalidIds=manager.getInvalidatedSessions();
+- if ( invalidIds.length > 0 ) {
+- for ( int i=0;i<invalidIds.length; i++ ) {
+- try {
+- send(manager,cluster,invalidIds[i]);
+- } catch ( Exception x ) {
+- log.error(sm.getString("ReplicationValve.send.invalid.failure",invalidIds[i]),x);
+- }
+- }
+- }
+- }
+-
+- /**
+- * is request without possible session change
+- * @param uri The request uri
+- * @return True if no session change
+- */
+- protected boolean isRequestWithoutSessionChange(String uri) {
+-
+- boolean filterfound = false;
+-
+- for (int i = 0; (i < reqFilters.length) && (!filterfound); i++) {
+- java.util.regex.Matcher matcher = reqFilters[i].matcher(uri);
+- filterfound = matcher.matches();
+- }
+- return filterfound;
+- }
+-
+- /**
+- * protocol cluster replications stats
+- * @param requestTime
+- * @param clusterTime
+- */
+- protected void updateStats(long requestTime, long clusterTime) {
+- synchronized(this) {
+- lastSendTime=System.currentTimeMillis();
+- totalSendTime+=lastSendTime - clusterTime;
+- totalRequestTime+=lastSendTime - requestTime;
+- nrOfRequests++;
+- }
+- if(log.isInfoEnabled()) {
+- if ( (nrOfRequests % 100) == 0 ) {
+- log.info(sm.getString("ReplicationValve.stats",
+- new Object[]{
+- new Long(totalRequestTime/nrOfRequests),
+- new Long(totalSendTime/nrOfRequests),
+- new Long(nrOfRequests),
+- new Long(nrOfSendRequests),
+- new Long(nrOfCrossContextSendRequests),
+- new Long(nrOfFilterRequests),
+- new Long(totalRequestTime),
+- new Long(totalSendTime)}));
+- }
+- }
+- }
+-
+-
+- /**
+- * Mark Request that processed at primary node with attribute
+- * primaryIndicatorName
+- *
+- * @param request
+- * @throws IOException
+- */
+- protected void createPrimaryIndicator(Request request) throws IOException {
+- String id = request.getRequestedSessionId();
+- if ((id != null) && (id.length() > 0)) {
+- Manager manager = request.getContext().getManager();
+- Session session = manager.findSession(id);
+- if (session instanceof ClusterSession) {
+- ClusterSession cses = (ClusterSession) session;
+- if (cses != null) {
+- if (log.isDebugEnabled())
+- log.debug(sm.getString(
+- "ReplicationValve.session.indicator", request.getContext().getName(),id,
+- primaryIndicatorName, cses.isPrimarySession()));
+- request.setAttribute(primaryIndicatorName, cses.isPrimarySession()?Boolean.TRUE:Boolean.FALSE);
+- }
+- } else {
+- if (log.isDebugEnabled()) {
+- if (session != null) {
+- log.debug(sm.getString(
+- "ReplicationValve.session.found", request.getContext().getName(),id));
+- } else {
+- log.debug(sm.getString(
+- "ReplicationValve.session.invalid", request.getContext().getName(),id));
+- }
+- }
+- }
+- }
+- }
+-
+-}
+Index: java/org/apache/catalina/ha/tcp/SendMessageData.java
+===================================================================
+--- java/org/apache/catalina/ha/tcp/SendMessageData.java (revision 590752)
++++ java/org/apache/catalina/ha/tcp/SendMessageData.java (working copy)
+@@ -1,82 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.ha.tcp;
+-
+-import org.apache.catalina.tribes.Member;
+-
+-/**
+- * @author Peter Rossbach
+- * @version $Revision$ $Date$
+- */
+-public class SendMessageData {
+-
+- private Object message ;
+- private Member destination ;
+- private Exception exception ;
+-
+-
+- /**
+- * @param message
+- * @param destination
+- * @param exception
+- */
+- public SendMessageData(Object message, Member destination,
+- Exception exception) {
+- super();
+- this.message = message;
+- this.destination = destination;
+- this.exception = exception;
+- }
+-
+- /**
+- * @return Returns the destination.
+- */
+- public Member getDestination() {
+- return destination;
+- }
+- /**
+- * @param destination The destination to set.
+- */
+- public void setDestination(Member destination) {
+- this.destination = destination;
+- }
+- /**
+- * @return Returns the exception.
+- */
+- public Exception getException() {
+- return exception;
+- }
+- /**
+- * @param exception The exception to set.
+- */
+- public void setException(Exception exception) {
+- this.exception = exception;
+- }
+- /**
+- * @return Returns the message.
+- */
+- public Object getMessage() {
+- return message;
+- }
+- /**
+- * @param message The message to set.
+- */
+- public void setMessage(Object message) {
+- this.message = message;
+- }
+-}
+Index: java/org/apache/catalina/ha/tcp/Constants.java
+===================================================================
+--- java/org/apache/catalina/ha/tcp/Constants.java (revision 590752)
++++ java/org/apache/catalina/ha/tcp/Constants.java (working copy)
+@@ -1,33 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-
+-package org.apache.catalina.ha.tcp;
+-
+-/**
+- * Manifest constants for the <code>org.apache.catalina.ha.tcp</code>
+- * package.
+- *
+- * @author Peter Rossbach
+- * @version $Revision$ $Date$
+- */
+-
+-public class Constants {
+-
+- public static final String Package = "org.apache.catalina.ha.tcp";
+-
+-}
+Index: java/org/apache/catalina/ha/ClusterManager.java
+===================================================================
+--- java/org/apache/catalina/ha/ClusterManager.java (revision 590752)
++++ java/org/apache/catalina/ha/ClusterManager.java (working copy)
+@@ -1,112 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.ha;
+-
+-
+-import org.apache.catalina.Manager;
+-import java.io.IOException;
+-import org.apache.catalina.tribes.io.ReplicationStream;
+-
+-
+-/**
+- * The common interface used by all cluster manager.
+- * This is so that we can have a more pluggable way
+- * of swapping session managers for different algorithms.
+- *
+- * @author Filip Hanik
+- * @author Peter Rossbach
+- */
+-public interface ClusterManager extends Manager {
+-
+- /**
+- * A message was received from another node, this
+- * is the callback method to implement if you are interested in
+- * receiving replication messages.
+- * @param msg - the message received.
+- */
+- public void messageDataReceived(ClusterMessage msg);
+-
+- /**
+- * When the request has been completed, the replication valve
+- * will notify the manager, and the manager will decide whether
+- * any replication is needed or not.
+- * If there is a need for replication, the manager will
+- * create a session message and that will be replicated.
+- * The cluster determines where it gets sent.
+- * @param sessionId - the sessionId that just completed.
+- * @return a SessionMessage to be sent.
+- */
+- public ClusterMessage requestCompleted(String sessionId);
+-
+- /**
+- * When the manager expires session not tied to a request.
+- * The cluster will periodically ask for a list of sessions
+- * that should expire and that should be sent across the wire.
+- * @return String[] The invalidated sessions
+- */
+- public String[] getInvalidatedSessions();
+-
+- /**
+- * Return the name of the manager, at host /context name and at engine hostname+/context.
+- * @return String
+- * @since 5.5.10
+- */
+- public String getName();
+-
+- /**
+- * Set the name of the manager, at host /context name and at engine hostname+/context
+- * @param name
+- * @since 5.5.10
+- */
+- public void setName(String name);
+-
+- public CatalinaCluster getCluster();
+-
+- public void setCluster(CatalinaCluster cluster);
+-
+- /**
+- * @return Manager send only to same cluster domain.
+- * @since 5.5.10
+- */
+- public boolean doDomainReplication();
+-
+- /**
+- * @param sendClusterDomainOnly Flag value.
+- * @since 5.5.10
+- */
+- public void setDomainReplication(boolean domainReplication);
+-
+- /**
+- * @param mode The mode
+- * @since 5.5.10
+- */
+- public void setDefaultMode(boolean mode);
+-
+- /**
+- * @since 5.5.10
+- */
+- public boolean isDefaultMode();
+-
+- public ReplicationStream getReplicationStream(byte[] data) throws IOException;
+-
+- public ReplicationStream getReplicationStream(byte[] data, int offset, int length) throws IOException;
+-
+- public boolean isNotifyListenersOnReplication();
+-
+- public ClusterManager cloneFromTemplate();
+-}
+Index: java/org/apache/catalina/ha/Constants.java
+===================================================================
+--- java/org/apache/catalina/ha/Constants.java (revision 590752)
++++ java/org/apache/catalina/ha/Constants.java (working copy)
+@@ -1,31 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-
+-package org.apache.catalina.ha;
+-
+-/**
+- * Manifest constants for the <code>org.apache.catalina.ha</code>
+- * package.
+- *
+- * @author Bip Thelin
+- * @version $Revision$, $Date$
+- */
+-
+-public final class Constants {
+- public static final String Package = "org.apache.catalina.ha";
+-}
+Index: java/org/apache/catalina/ha/deploy/FarmWarDeployer.java
+===================================================================
+--- java/org/apache/catalina/ha/deploy/FarmWarDeployer.java (revision 590752)
++++ java/org/apache/catalina/ha/deploy/FarmWarDeployer.java (working copy)
+@@ -1,750 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.ha.deploy;
+-
+-import java.io.File;
+-import java.io.IOException;
+-import java.net.URL;
+-import java.util.HashMap;
+-import javax.management.MBeanServer;
+-import javax.management.ObjectName;
+-
+-import org.apache.catalina.Context;
+-import org.apache.catalina.Engine;
+-import org.apache.catalina.Host;
+-import org.apache.catalina.Lifecycle;
+-import org.apache.catalina.LifecycleException;
+-import org.apache.catalina.ha.CatalinaCluster;
+-import org.apache.catalina.ha.ClusterDeployer;
+-import org.apache.catalina.ha.ClusterListener;
+-import org.apache.catalina.ha.ClusterMessage;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.tomcat.util.modeler.Registry;
+-
+-
+-/**
+- * <p>
+- * A farm war deployer is a class that is able to deploy/undeploy web
+- * applications in WAR form within the cluster.
+- * </p>
+- * Any host can act as the admin, and will have three directories
+- * <ul>
+- * <li>deployDir - the directory where we watch for changes</li>
+- * <li>applicationDir - the directory where we install applications</li>
+- * <li>tempDir - a temporaryDirectory to store binary data when downloading a
+- * war from the cluster</li>
+- * </ul>
+- * Currently we only support deployment of WAR files since they are easier to
+- * send across the wire.
+- *
+- * @author Filip Hanik
+- * @author Peter Rossbach
+- * @version $Revision$
+- */
+-public class FarmWarDeployer extends ClusterListener implements ClusterDeployer, FileChangeListener {
+- /*--Static Variables----------------------------------------*/
+- public static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory
+- .getLog(FarmWarDeployer.class);
+- /**
+- * The descriptive information about this implementation.
+- */
+- private static final String info = "FarmWarDeployer/1.2";
+-
+- /*--Instance Variables--------------------------------------*/
+- protected CatalinaCluster cluster = null;
+-
+- protected boolean started = false; //default 5 seconds
+-
+- protected HashMap fileFactories = new HashMap();
+-
+- protected String deployDir;
+-
+- protected String tempDir;
+-
+- protected String watchDir;
+-
+- protected boolean watchEnabled = false;
+-
+- protected WarWatcher watcher = null;
+-
+- /**
+- * Iteration count for background processing.
+- */
+- private int count = 0;
+-
+- /**
+- * Frequency of the Farm watchDir check. Cluster wide deployment will be
+- * done once for the specified amount of backgrondProcess calls (ie, the
+- * lower the amount, the most often the checks will occur).
+- */
+- protected int processDeployFrequency = 2;
+-
+- /**
+- * Path where context descriptors should be deployed.
+- */
+- protected File configBase = null;
+-
+- /**
+- * The associated host.
+- */
+- protected Host host = null;
+-
+- /**
+- * The host appBase.
+- */
+- protected File appBase = null;
+-
+- /**
+- * MBean server.
+- */
+- protected MBeanServer mBeanServer = null;
+-
+- /**
+- * The associated deployer ObjectName.
+- */
+- protected ObjectName oname = null;
+-
+- /*--Constructor---------------------------------------------*/
+- public FarmWarDeployer() {
+- }
+-
+- /**
+- * Return descriptive information about this deployer implementation and the
+- * corresponding version number, in the format
+- * <code><description>/<version></code>.
+- */
+- public String getInfo() {
+-
+- return (info);
+-
+- }
+-
+- /*--Logic---------------------------------------------------*/
+- public void start() throws Exception {
+- if (started)
+- return;
+- getCluster().addClusterListener(this);
+- if (watchEnabled) {
+- watcher = new WarWatcher(this, new File(getWatchDir()));
+- if (log.isInfoEnabled())
+- log.info("Cluster deployment is watching " + getWatchDir()
+- + " for changes.");
+- }
+-
+- // Check to correct engine and host setup
+- Object parent = getCluster().getContainer();
+- Engine engine = null;
+- String hostname = null;
+- if ( parent instanceof Host ) {
+- host = (Host) parent;
+- engine = (Engine) host.getParent();
+- hostname = host.getName();
+- }else {
+- engine = (Engine)parent;
+- hostname = engine.getDefaultHost();
+- }
+- try {
+- oname = new ObjectName(engine.getName() + ":type=Deployer,host="
+- + hostname);
+- } catch (Exception e) {
+- log.error("Can't construct MBean object name" + e);
+- }
+- configBase = new File(System.getProperty("catalina.base"), "conf");
+- if (engine != null) {
+- configBase = new File(configBase, engine.getName());
+- } else if (host != null) {
+- configBase = new File(configBase, host.getName());
+- }
+-
+- // Retrieve the MBean server
+- mBeanServer = Registry.getRegistry(null, null).getMBeanServer();
+-
+- started = true;
+- count = 0;
+- if (log.isInfoEnabled())
+- log.info("Cluster FarmWarDeployer started.");
+- }
+-
+- /*
+- * stop cluster wide deployments
+- *
+- * @see org.apache.catalina.ha.ClusterDeployer#stop()
+- */
+- public void stop() throws LifecycleException {
+- started = false;
+- getCluster().removeClusterListener(this);
+- count = 0;
+- if (watcher != null) {
+- watcher.clear();
+- watcher = null;
+-
+- }
+- if (log.isInfoEnabled())
+- log.info("Cluster FarmWarDeployer stopped.");
+- }
+-
+- public void cleanDeployDir() {
+- throw new java.lang.UnsupportedOperationException(
+- "Method cleanDeployDir() not yet implemented.");
+- }
+-
+- /**
+- * Callback from the cluster, when a message is received, The cluster will
+- * broadcast it invoking the messageReceived on the receiver.
+- *
+- * @param msg
+- * ClusterMessage - the message received from the cluster
+- */
+- public void messageReceived(ClusterMessage msg) {
+- try {
+- if (msg instanceof FileMessage && msg != null) {
+- FileMessage fmsg = (FileMessage) msg;
+- if (log.isDebugEnabled())
+- log.debug("receive cluster deployment [ path: "
+- + fmsg.getContextPath() + " war: "
+- + fmsg.getFileName() + " ]");
+- FileMessageFactory factory = getFactory(fmsg);
+- // TODO correct second try after app is in service!
+- if (factory.writeMessage(fmsg)) {
+- //last message received war file is completed
+- String name = factory.getFile().getName();
+- if (!name.endsWith(".war"))
+- name = name + ".war";
+- File deployable = new File(getDeployDir(), name);
+- try {
+- String path = fmsg.getContextPath();
+- if (!isServiced(path)) {
+- addServiced(path);
+- try {
+- remove(path);
+- factory.getFile().renameTo(deployable);
+- check(path);
+- } finally {
+- removeServiced(path);
+- }
+- if (log.isDebugEnabled())
+- log.debug("deployment from " + path
+- + " finished.");
+- } else
+- log.error("Application " + path
+- + " in used. touch war file " + name
+- + " again!");
+- } catch (Exception ex) {
+- log.error(ex);
+- } finally {
+- removeFactory(fmsg);
+- }
+- }
+- } else if (msg instanceof UndeployMessage && msg != null) {
+- try {
+- UndeployMessage umsg = (UndeployMessage) msg;
+- String path = umsg.getContextPath();
+- if (log.isDebugEnabled())
+- log.debug("receive cluster undeployment from " + path);
+- if (!isServiced(path)) {
+- addServiced(path);
+- try {
+- remove(path);
+- } finally {
+- removeServiced(path);
+- }
+- if (log.isDebugEnabled())
+- log.debug("undeployment from " + path
+- + " finished.");
+- } else
+- log.error("Application "
+- + path
+- + " in used. Sorry not remove from backup cluster nodes!");
+- } catch (Exception ex) {
+- log.error(ex);
+- }
+- }
+- } catch (java.io.IOException x) {
+- log.error("Unable to read farm deploy file message.", x);
+- }
+- }
+-
+- /**
+- * create factory for all transported war files
+- *
+- * @param msg
+- * @return Factory for all app message (war files)
+- * @throws java.io.FileNotFoundException
+- * @throws java.io.IOException
+- */
+- public synchronized FileMessageFactory getFactory(FileMessage msg)
+- throws java.io.FileNotFoundException, java.io.IOException {
+- File tmpFile = new File(msg.getFileName());
+- File writeToFile = new File(getTempDir(), tmpFile.getName());
+- FileMessageFactory factory = (FileMessageFactory) fileFactories.get(msg
+- .getFileName());
+- if (factory == null) {
+- factory = FileMessageFactory.getInstance(writeToFile, true);
+- fileFactories.put(msg.getFileName(), factory);
+- }
+- return factory;
+- }
+-
+- /**
+- * Remove file (war) from messages)
+- *
+- * @param msg
+- */
+- public void removeFactory(FileMessage msg) {
+- fileFactories.remove(msg.getFileName());
+- }
+-
+- /**
+- * Before the cluster invokes messageReceived the cluster will ask the
+- * receiver to accept or decline the message, In the future, when messages
+- * get big, the accept method will only take a message header
+- *
+- * @param msg
+- * ClusterMessage
+- * @return boolean - returns true to indicate that messageReceived should be
+- * invoked. If false is returned, the messageReceived method will
+- * not be invoked.
+- */
+- public boolean accept(ClusterMessage msg) {
+- return (msg instanceof FileMessage) || (msg instanceof UndeployMessage);
+- }
+-
+- /**
+- * Install a new web application, whose web application archive is at the
+- * specified URL, into this container and all the other members of the
+- * cluster with the specified context path. A context path of "" (the empty
+- * string) should be used for the root application for this container.
+- * Otherwise, the context path must start with a slash.
+- * <p>
+- * If this application is successfully installed locally, a ContainerEvent
+- * of type <code>INSTALL_EVENT</code> will be sent to all registered
+- * listeners, with the newly created <code>Context</code> as an argument.
+- *
+- * @param contextPath
+- * The context path to which this application should be installed
+- * (must be unique)
+- * @param war
+- * A URL of type "jar:" that points to a WAR file, or type
+- * "file:" that points to an unpacked directory structure
+- * containing the web application to be installed
+- *
+- * @exception IllegalArgumentException
+- * if the specified context path is malformed (it must be ""
+- * or start with a slash)
+- * @exception IllegalStateException
+- * if the specified context path is already attached to an
+- * existing web application
+- * @exception IOException
+- * if an input/output error was encountered during
+- * installation
+- */
+- public void install(String contextPath, URL war) throws IOException {
+- Member[] members = getCluster().getMembers();
+- Member localMember = getCluster().getLocalMember();
+- FileMessageFactory factory = FileMessageFactory.getInstance(new File(
+- war.getFile()), false);
+- FileMessage msg = new FileMessage(localMember, war.getFile(),
+- contextPath);
+- if(log.isDebugEnabled())
+- log.debug("Send cluster war deployment [ path:"
+- + contextPath + " war: " + war + " ] started.");
+- msg = factory.readMessage(msg);
+- while (msg != null) {
+- for (int i = 0; i < members.length; i++) {
+- if (log.isDebugEnabled())
+- log.debug("Send cluster war fragment [ path: "
+- + contextPath + " war: " + war + " to: " + members[i] + " ]");
+- getCluster().send(msg, members[i]);
+- }
+- msg = factory.readMessage(msg);
+- }
+- if(log.isDebugEnabled())
+- log.debug("Send cluster war deployment [ path: "
+- + contextPath + " war: " + war + " ] finished.");
+- }
+-
+- /**
+- * Remove an existing web application, attached to the specified context
+- * path. If this application is successfully removed, a ContainerEvent of
+- * type <code>REMOVE_EVENT</code> will be sent to all registered
+- * listeners, with the removed <code>Context</code> as an argument.
+- * Deletes the web application war file and/or directory if they exist in
+- * the Host's appBase.
+- *
+- * @param contextPath
+- * The context path of the application to be removed
+- * @param undeploy
+- * boolean flag to remove web application from server
+- *
+- * @exception IllegalArgumentException
+- * if the specified context path is malformed (it must be ""
+- * or start with a slash)
+- * @exception IllegalArgumentException
+- * if the specified context path does not identify a
+- * currently installed web application
+- * @exception IOException
+- * if an input/output error occurs during removal
+- */
+- public void remove(String contextPath, boolean undeploy) throws IOException {
+- if (log.isInfoEnabled())
+- log.info("Cluster wide remove of web app " + contextPath);
+- Member localMember = getCluster().getLocalMember();
+- UndeployMessage msg = new UndeployMessage(localMember, System
+- .currentTimeMillis(), "Undeploy:" + contextPath + ":"
+- + System.currentTimeMillis(), contextPath, undeploy);
+- if (log.isDebugEnabled())
+- log.debug("Send cluster wide undeployment from "
+- + contextPath );
+- cluster.send(msg);
+- // remove locally
+- if (undeploy) {
+- try {
+- if (!isServiced(contextPath)) {
+- addServiced(contextPath);
+- try {
+- remove(contextPath);
+- } finally {
+- removeServiced(contextPath);
+- }
+- } else
+- log.error("Local remove from " + contextPath
+- + "failed, other manager has app in service!");
+-
+- } catch (Exception ex) {
+- log.error("local remove from " + contextPath + " failed", ex);
+- }
+- }
+-
+- }
+-
+- /*
+- * Modifcation from watchDir war detected!
+- *
+- * @see org.apache.catalina.ha.deploy.FileChangeListener#fileModified(java.io.File)
+- */
+- public void fileModified(File newWar) {
+- try {
+- File deployWar = new File(getDeployDir(), newWar.getName());
+- copy(newWar, deployWar);
+- String contextName = getContextName(deployWar);
+- if (log.isInfoEnabled())
+- log.info("Installing webapp[" + contextName + "] from "
+- + deployWar.getAbsolutePath());
+- try {
+- remove(contextName, false);
+- } catch (Exception x) {
+- log.error("No removal", x);
+- }
+- install(contextName, deployWar.toURL());
+- } catch (Exception x) {
+- log.error("Unable to install WAR file", x);
+- }
+- }
+-
+- /*
+- * War remvoe from watchDir
+- *
+- * @see org.apache.catalina.ha.deploy.FileChangeListener#fileRemoved(java.io.File)
+- */
+- public void fileRemoved(File removeWar) {
+- try {
+- String contextName = getContextName(removeWar);
+- if (log.isInfoEnabled())
+- log.info("Removing webapp[" + contextName + "]");
+- remove(contextName, true);
+- } catch (Exception x) {
+- log.error("Unable to remove WAR file", x);
+- }
+- }
+-
+- /**
+- * Create a context path from war
+- * @param war War filename
+- * @return '/filename' or if war name is ROOT.war context name is empty string ''
+- */
+- protected String getContextName(File war) {
+- String contextName = "/"
+- + war.getName().substring(0,
+- war.getName().lastIndexOf(".war"));
+- if("/ROOT".equals(contextName))
+- contextName= "" ;
+- return contextName ;
+- }
+-
+- /**
+- * Given a context path, get the config file name.
+- */
+- protected String getConfigFile(String path) {
+- String basename = null;
+- if (path.equals("")) {
+- basename = "ROOT";
+- } else {
+- basename = path.substring(1).replace('/', '#');
+- }
+- return (basename);
+- }
+-
+- /**
+- * Given a context path, get the config file name.
+- */
+- protected String getDocBase(String path) {
+- String basename = null;
+- if (path.equals("")) {
+- basename = "ROOT";
+- } else {
+- basename = path.substring(1);
+- }
+- return (basename);
+- }
+-
+- /**
+- * Return a File object representing the "application root" directory for
+- * our associated Host.
+- */
+- protected File getAppBase() {
+-
+- if (appBase != null) {
+- return appBase;
+- }
+-
+- File file = new File(host.getAppBase());
+- if (!file.isAbsolute())
+- file = new File(System.getProperty("catalina.base"), host
+- .getAppBase());
+- try {
+- appBase = file.getCanonicalFile();
+- } catch (IOException e) {
+- appBase = file;
+- }
+- return (appBase);
+-
+- }
+-
+- /**
+- * Invoke the remove method on the deployer.
+- */
+- protected void remove(String path) throws Exception {
+- // TODO Handle remove also work dir content !
+- // Stop the context first to be nicer
+- Context context = (Context) host.findChild(path);
+- if (context != null) {
+- if(log.isDebugEnabled())
+- log.debug("Undeploy local context " +path );
+- ((Lifecycle) context).stop();
+- File war = new File(getAppBase(), getDocBase(path) + ".war");
+- File dir = new File(getAppBase(), getDocBase(path));
+- File xml = new File(configBase, getConfigFile(path) + ".xml");
+- if (war.exists()) {
+- war.delete();
+- } else if (dir.exists()) {
+- undeployDir(dir);
+- } else {
+- xml.delete();
+- }
+- // Perform new deployment and remove internal HostConfig state
+- check(path);
+- }
+-
+- }
+-
+- /**
+- * Delete the specified directory, including all of its contents and
+- * subdirectories recursively.
+- *
+- * @param dir
+- * File object representing the directory to be deleted
+- */
+- protected void undeployDir(File dir) {
+-
+- String files[] = dir.list();
+- if (files == null) {
+- files = new String[0];
+- }
+- for (int i = 0; i < files.length; i++) {
+- File file = new File(dir, files[i]);
+- if (file.isDirectory()) {
+- undeployDir(file);
+- } else {
+- file.delete();
+- }
+- }
+- dir.delete();
+-
+- }
+-
+- /*
+- * Call watcher to check for deploy changes
+- *
+- * @see org.apache.catalina.ha.ClusterDeployer#backgroundProcess()
+- */
+- public void backgroundProcess() {
+- if (started) {
+- count = (count + 1) % processDeployFrequency;
+- if (count == 0 && watchEnabled) {
+- watcher.check();
+- }
+- }
+-
+- }
+-
+- /*--Deployer Operations ------------------------------------*/
+-
+- /**
+- * Invoke the check method on the deployer.
+- */
+- protected void check(String name) throws Exception {
+- String[] params = { name };
+- String[] signature = { "java.lang.String" };
+- mBeanServer.invoke(oname, "check", params, signature);
+- }
+-
+- /**
+- * Invoke the check method on the deployer.
+- */
+- protected boolean isServiced(String name) throws Exception {
+- String[] params = { name };
+- String[] signature = { "java.lang.String" };
+- Boolean result = (Boolean) mBeanServer.invoke(oname, "isServiced",
+- params, signature);
+- return result.booleanValue();
+- }
+-
+- /**
+- * Invoke the check method on the deployer.
+- */
+- protected void addServiced(String name) throws Exception {
+- String[] params = { name };
+- String[] signature = { "java.lang.String" };
+- mBeanServer.invoke(oname, "addServiced", params, signature);
+- }
+-
+- /**
+- * Invoke the check method on the deployer.
+- */
+- protected void removeServiced(String name) throws Exception {
+- String[] params = { name };
+- String[] signature = { "java.lang.String" };
+- mBeanServer.invoke(oname, "removeServiced", params, signature);
+- }
+-
+- /*--Instance Getters/Setters--------------------------------*/
+- public CatalinaCluster getCluster() {
+- return cluster;
+- }
+-
+- public void setCluster(CatalinaCluster cluster) {
+- this.cluster = cluster;
+- }
+-
+- public boolean equals(Object listener) {
+- return super.equals(listener);
+- }
+-
+- public int hashCode() {
+- return super.hashCode();
+- }
+-
+- public String getDeployDir() {
+- return deployDir;
+- }
+-
+- public void setDeployDir(String deployDir) {
+- this.deployDir = deployDir;
+- }
+-
+- public String getTempDir() {
+- return tempDir;
+- }
+-
+- public void setTempDir(String tempDir) {
+- this.tempDir = tempDir;
+- }
+-
+- public String getWatchDir() {
+- return watchDir;
+- }
+-
+- public void setWatchDir(String watchDir) {
+- this.watchDir = watchDir;
+- }
+-
+- public boolean isWatchEnabled() {
+- return watchEnabled;
+- }
+-
+- public boolean getWatchEnabled() {
+- return watchEnabled;
+- }
+-
+- public void setWatchEnabled(boolean watchEnabled) {
+- this.watchEnabled = watchEnabled;
+- }
+-
+- /**
+- * Return the frequency of watcher checks.
+- */
+- public int getProcessDeployFrequency() {
+-
+- return (this.processDeployFrequency);
+-
+- }
+-
+- /**
+- * Set the watcher checks frequency.
+- *
+- * @param processExpiresFrequency
+- * the new manager checks frequency
+- */
+- public void setProcessDeployFrequency(int processExpiresFrequency) {
+-
+- if (processExpiresFrequency <= 0) {
+- return;
+- }
+- this.processDeployFrequency = processExpiresFrequency;
+- }
+-
+- /**
+- * Copy a file to the specified temp directory.
+- * @param from copy from temp
+- * @param to to host appBase directory
+- * @return true, copy successful
+- */
+- protected boolean copy(File from, File to) {
+- try {
+- if (!to.exists())
+- to.createNewFile();
+- java.io.FileInputStream is = new java.io.FileInputStream(from);
+- java.io.FileOutputStream os = new java.io.FileOutputStream(to,
+- false);
+- byte[] buf = new byte[4096];
+- while (true) {
+- int len = is.read(buf);
+- if (len < 0)
+- break;
+- os.write(buf, 0, len);
+- }
+- is.close();
+- os.close();
+- } catch (IOException e) {
+- log.error("Unable to copy file from:" + from + " to:" + to, e);
+- return false;
+- }
+- return true;
+- }
+-
+-}
+Index: java/org/apache/catalina/ha/deploy/mbeans-descriptors.xml
+===================================================================
+--- java/org/apache/catalina/ha/deploy/mbeans-descriptors.xml (revision 590752)
++++ java/org/apache/catalina/ha/deploy/mbeans-descriptors.xml (working copy)
+@@ -1,27 +0,0 @@
+-<?xml version="1.0"?>
+-<!--
+- 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.
+--->
+-<mbeans-descriptors>
+- <mbean
+- name="FarmWarDeployer"
+- className="org.apache.catalina.mbeans.ClassNameMBean"
+- description="Farm Deployer - Broken"
+- domain="Catalina"
+- group="Cluster"
+- type="org.apache.catalina.ha.deploy.FarmWarDeployer">
+- </mbean>
+-</mbeans-descriptors>
+Index: java/org/apache/catalina/ha/deploy/FileMessage.java
+===================================================================
+--- java/org/apache/catalina/ha/deploy/FileMessage.java (revision 590752)
++++ java/org/apache/catalina/ha/deploy/FileMessage.java (working copy)
+@@ -1,113 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.ha.deploy;
+-
+-import java.io.Serializable;
+-
+-import org.apache.catalina.ha.ClusterMessage;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.ha.ClusterMessageBase;
+-
+-/**
+- * Contains the data for a file being transferred over TCP, this is
+- * essentially a fragment of a file, read and written by the FileMessageFactory
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-
+-public class FileMessage extends ClusterMessageBase implements ClusterMessage, Serializable {
+- private int messageNumber;
+- private byte[] data;
+- private int dataLength;
+-
+- private long totalLength;
+- private long totalNrOfMsgs;
+- private String fileName;
+- private String contextPath;
+-
+- public FileMessage(Member source,
+- String fileName,
+- String contextPath) {
+- this.address=source;
+- this.fileName=fileName;
+- this.contextPath=contextPath;
+- }
+-
+- /*
+- public void writeExternal(ObjectOutput out) throws IOException {
+-
+- }
+-
+- public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+-
+- }
+- */
+-
+- public int getMessageNumber() {
+- return messageNumber;
+- }
+- public void setMessageNumber(int messageNumber) {
+- this.messageNumber = messageNumber;
+- }
+- public long getTotalNrOfMsgs() {
+- return totalNrOfMsgs;
+- }
+- public void setTotalNrOfMsgs(long totalNrOfMsgs) {
+- this.totalNrOfMsgs = totalNrOfMsgs;
+- }
+- public byte[] getData() {
+- return data;
+- }
+- public void setData(byte[] data, int length) {
+- this.data = data;
+- this.dataLength = length;
+- }
+- public int getDataLength() {
+- return dataLength;
+- }
+- public void setDataLength(int dataLength) {
+- this.dataLength = dataLength;
+- }
+- public long getTotalLength() {
+- return totalLength;
+- }
+- public void setTotalLength(long totalLength) {
+- this.totalLength = totalLength;
+- }
+-
+- public String getUniqueId() {
+- StringBuffer result = new StringBuffer(getFileName());
+- result.append("#-#");
+- result.append(getMessageNumber());
+- result.append("#-#");
+- result.append(System.currentTimeMillis());
+- return result.toString();
+- }
+-
+-
+- public String getFileName() {
+- return fileName;
+- }
+- public void setFileName(String fileName) {
+- this.fileName = fileName;
+- }
+- public String getContextPath() {
+- return contextPath;
+- }
+-
+-}
+Index: java/org/apache/catalina/ha/deploy/UndeployMessage.java
+===================================================================
+--- java/org/apache/catalina/ha/deploy/UndeployMessage.java (revision 590752)
++++ java/org/apache/catalina/ha/deploy/UndeployMessage.java (working copy)
+@@ -1,114 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.ha.deploy;
+-
+-import org.apache.catalina.ha.ClusterMessage;
+-import org.apache.catalina.tribes.Member;
+-import java.io.Serializable;
+-public class UndeployMessage implements ClusterMessage,Serializable {
+- private Member address;
+- private long timestamp;
+- private String uniqueId;
+- private String contextPath;
+- private boolean undeploy;
+- private int resend = 0;
+- private int compress = 0;
+-
+- public UndeployMessage() {} //for serialization
+- public UndeployMessage(Member address,
+- long timestamp,
+- String uniqueId,
+- String contextPath,
+- boolean undeploy) {
+- this.address = address;
+- this.timestamp= timestamp;
+- this.undeploy = undeploy;
+- this.uniqueId = uniqueId;
+- this.undeploy = undeploy;
+- this.contextPath = contextPath;
+- }
+-
+- public Member getAddress() {
+- return address;
+- }
+-
+- public void setAddress(Member address) {
+- this.address = address;
+- }
+-
+- public long getTimestamp() {
+- return timestamp;
+- }
+-
+- public void setTimestamp(long timestamp) {
+- this.timestamp = timestamp;
+- }
+-
+- public String getUniqueId() {
+- return uniqueId;
+- }
+-
+- public void setUniqueId(String uniqueId) {
+- this.uniqueId = uniqueId;
+- }
+-
+- public String getContextPath() {
+- return contextPath;
+- }
+-
+- public void setContextPath(String contextPath) {
+- this.contextPath = contextPath;
+- }
+-
+- public boolean getUndeploy() {
+- return undeploy;
+- }
+-
+- public void setUndeploy(boolean undeploy) {
+- this.undeploy = undeploy;
+- }
+- /**
+- * @return Returns the compress.
+- * @since 5.5.10
+- */
+- public int getCompress() {
+- return compress;
+- }
+- /**
+- * @param compress The compress to set.
+- * @since 5.5.10
+- */
+- public void setCompress(int compress) {
+- this.compress = compress;
+- }
+- /**
+- * @return Returns the resend.
+- * @since 5.5.10
+- */
+- public int getResend() {
+- return resend;
+- }
+- /**
+- * @param resend The resend to set.
+- * @since 5.5.10
+- */
+- public void setResend(int resend) {
+- this.resend = resend;
+- }
+-
+-}
+Index: java/org/apache/catalina/ha/deploy/WarWatcher.java
+===================================================================
+--- java/org/apache/catalina/ha/deploy/WarWatcher.java (revision 590752)
++++ java/org/apache/catalina/ha/deploy/WarWatcher.java (working copy)
+@@ -1,239 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.ha.deploy;
+-
+-import java.io.File;
+-import java.util.HashMap;
+-import java.util.Map;
+-import java.util.Iterator;
+-
+-/**
+- * <p>
+- * The <b>WarWatcher </b> watches the deployDir for changes made to the
+- * directory (adding new WAR files->deploy or remove WAR files->undeploy) And
+- * notifies a listener of the changes made
+- * </p>
+- *
+- * @author Filip Hanik
+- * @author Peter Rossbach
+- * @version 1.1
+- */
+-
+-public class WarWatcher {
+-
+- /*--Static Variables----------------------------------------*/
+- public static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory
+- .getLog(WarWatcher.class);
+-
+- /*--Instance Variables--------------------------------------*/
+- /**
+- * Directory to watch for war files
+- */
+- protected File watchDir = null;
+-
+- /**
+- * Parent to be notified of changes
+- */
+- protected FileChangeListener listener = null;
+-
+- /**
+- * Currently deployed files
+- */
+- protected Map currentStatus = new HashMap();
+-
+- /*--Constructor---------------------------------------------*/
+-
+- public WarWatcher() {
+- }
+-
+- public WarWatcher(FileChangeListener listener, File watchDir) {
+- this.listener = listener;
+- this.watchDir = watchDir;
+- }
+-
+- /*--Logic---------------------------------------------------*/
+-
+- /**
+- * check for modification and send notifcation to listener
+- */
+- public void check() {
+- if (log.isInfoEnabled())
+- log.info("check cluster wars at " + watchDir);
+- File[] list = watchDir.listFiles(new WarFilter());
+- if (list == null)
+- list = new File[0];
+- //first make sure all the files are listed in our current status
+- for (int i = 0; i < list.length; i++) {
+- addWarInfo(list[i]);
+- }
+-
+- //check all the status codes and update the FarmDeployer
+- for (Iterator i = currentStatus.entrySet().iterator(); i.hasNext();) {
+- Map.Entry entry = (Map.Entry) i.next();
+- WarInfo info = (WarInfo) entry.getValue();
+- int check = info.check();
+- if (check == 1) {
+- listener.fileModified(info.getWar());
+- } else if (check == -1) {
+- listener.fileRemoved(info.getWar());
+- //no need to keep in memory
+- currentStatus.remove(info.getWar());
+- }
+- }
+-
+- }
+-
+- /**
+- * add cluster war to the watcher state
+- * @param warfile
+- */
+- protected void addWarInfo(File warfile) {
+- WarInfo info = (WarInfo) currentStatus.get(warfile.getAbsolutePath());
+- if (info == null) {
+- info = new WarInfo(warfile);
+- info.setLastState(-1); //assume file is non existent
+- currentStatus.put(warfile.getAbsolutePath(), info);
+- }
+- }
+-
+- /**
+- * clear watcher state
+- */
+- public void clear() {
+- currentStatus.clear();
+- }
+-
+- /**
+- * @return Returns the watchDir.
+- */
+- public File getWatchDir() {
+- return watchDir;
+- }
+-
+- /**
+- * @param watchDir
+- * The watchDir to set.
+- */
+- public void setWatchDir(File watchDir) {
+- this.watchDir = watchDir;
+- }
+-
+- /**
+- * @return Returns the listener.
+- */
+- public FileChangeListener getListener() {
+- return listener;
+- }
+-
+- /**
+- * @param listener
+- * The listener to set.
+- */
+- public void setListener(FileChangeListener listener) {
+- this.listener = listener;
+- }
+-
+- /*--Inner classes-------------------------------------------*/
+-
+- /**
+- * File name filter for war files
+- */
+- protected class WarFilter implements java.io.FilenameFilter {
+- public boolean accept(File path, String name) {
+- if (name == null)
+- return false;
+- return name.endsWith(".war");
+- }
+- }
+-
+- /**
+- * File information on existing WAR files
+- */
+- protected class WarInfo {
+- protected File war = null;
+-
+- protected long lastChecked = 0;
+-
+- protected long lastState = 0;
+-
+- public WarInfo(File war) {
+- this.war = war;
+- this.lastChecked = war.lastModified();
+- if (!war.exists())
+- lastState = -1;
+- }
+-
+- public boolean modified() {
+- return war.exists() && war.lastModified() > lastChecked;
+- }
+-
+- public boolean exists() {
+- return war.exists();
+- }
+-
+- /**
+- * Returns 1 if the file has been added/modified, 0 if the file is
+- * unchanged and -1 if the file has been removed
+- *
+- * @return int 1=file added; 0=unchanged; -1=file removed
+- */
+- public int check() {
+- //file unchanged by default
+- int result = 0;
+-
+- if (modified()) {
+- //file has changed - timestamp
+- result = 1;
+- lastState = result;
+- } else if ((!exists()) && (!(lastState == -1))) {
+- //file was removed
+- result = -1;
+- lastState = result;
+- } else if ((lastState == -1) && exists()) {
+- //file was added
+- result = 1;
+- lastState = result;
+- }
+- this.lastChecked = System.currentTimeMillis();
+- return result;
+- }
+-
+- public File getWar() {
+- return war;
+- }
+-
+- public int hashCode() {
+- return war.getAbsolutePath().hashCode();
+- }
+-
+- public boolean equals(Object other) {
+- if (other instanceof WarInfo) {
+- WarInfo wo = (WarInfo) other;
+- return wo.getWar().equals(getWar());
+- } else {
+- return false;
+- }
+- }
+-
+- protected void setLastState(int lastState) {
+- this.lastState = lastState;
+- }
+-
+- }
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/ha/deploy/FileChangeListener.java
+===================================================================
+--- java/org/apache/catalina/ha/deploy/FileChangeListener.java (revision 590752)
++++ java/org/apache/catalina/ha/deploy/FileChangeListener.java (working copy)
+@@ -1,24 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.ha.deploy;
+-import java.io.File;
+-
+-public interface FileChangeListener {
+- public void fileModified(File f);
+- public void fileRemoved(File f);
+-}
+Index: java/org/apache/catalina/ha/deploy/FileMessageFactory.java
+===================================================================
+--- java/org/apache/catalina/ha/deploy/FileMessageFactory.java (revision 590752)
++++ java/org/apache/catalina/ha/deploy/FileMessageFactory.java (working copy)
+@@ -1,312 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.ha.deploy;
+-
+-import java.io.File;
+-import java.io.IOException;
+-import java.io.FileInputStream;
+-import java.io.FileOutputStream;
+-import java.io.FileNotFoundException;
+-
+-/**
+- * This factory is used to read files and write files by splitting them up into
+- * smaller messages. So that entire files don't have to be read into memory.
+- * <BR>
+- * The factory can be used as a reader or writer but not both at the same time.
+- * When done reading or writing the factory will close the input or output
+- * streams and mark the factory as closed. It is not possible to use it after
+- * that. <BR>
+- * To force a cleanup, call cleanup() from the calling object. <BR>
+- * This class is not thread safe.
+- *
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public class FileMessageFactory {
+- /*--Static Variables----------------------------------------*/
+- public static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory
+- .getLog(FileMessageFactory.class);
+-
+- /**
+- * The number of bytes that we read from file
+- */
+- public static final int READ_SIZE = 1024 * 10; //10kb
+-
+- /**
+- * The file that we are reading/writing
+- */
+- protected File file = null;
+-
+- /**
+- * True means that we are writing with this factory. False means that we are
+- * reading with this factory
+- */
+- protected boolean openForWrite;
+-
+- /**
+- * Once the factory is used, it can not be reused.
+- */
+- protected boolean closed = false;
+-
+- /**
+- * When openForWrite=false, the input stream is held by this variable
+- */
+- protected FileInputStream in;
+-
+- /**
+- * When openForWrite=true, the output stream is held by this variable
+- */
+- protected FileOutputStream out;
+-
+- /**
+- * The number of messages we have read or written
+- */
+- protected int nrOfMessagesProcessed = 0;
+-
+- /**
+- * The total size of the file
+- */
+- protected long size = 0;
+-
+- /**
+- * The total number of packets that we split this file into
+- */
+- protected long totalNrOfMessages = 0;
+-
+- /**
+- * The bytes that we hold the data in, not thread safe.
+- */
+- protected byte[] data = new byte[READ_SIZE];
+-
+- /**
+- * Private constructor, either instantiates a factory to read or write. <BR>
+- * When openForWrite==true, then a the file, f, will be created and an
+- * output stream is opened to write to it. <BR>
+- * When openForWrite==false, an input stream is opened, the file has to
+- * exist.
+- *
+- * @param f
+- * File - the file to be read/written
+- * @param openForWrite
+- * boolean - true means we are writing to the file, false means
+- * we are reading from the file
+- * @throws FileNotFoundException -
+- * if the file to be read doesn't exist
+- * @throws IOException -
+- * if the system fails to open input/output streams to the file
+- * or if it fails to create the file to be written to.
+- */
+- private FileMessageFactory(File f, boolean openForWrite)
+- throws FileNotFoundException, IOException {
+- this.file = f;
+- this.openForWrite = openForWrite;
+- if (log.isDebugEnabled())
+- log.debug("open file " + f + " write " + openForWrite);
+- if (openForWrite) {
+- if (!file.exists())
+- file.createNewFile();
+- out = new FileOutputStream(f);
+- } else {
+- size = file.length();
+- totalNrOfMessages = (size / READ_SIZE) + 1;
+- in = new FileInputStream(f);
+- }//end if
+-
+- }
+-
+- /**
+- * Creates a factory to read or write from a file. When opening for read,
+- * the readMessage can be invoked, and when opening for write the
+- * writeMessage can be invoked.
+- *
+- * @param f
+- * File - the file to be read or written
+- * @param openForWrite
+- * boolean - true, means we are writing to the file, false means
+- * we are reading from it
+- * @throws FileNotFoundException -
+- * if the file to be read doesn't exist
+- * @throws IOException -
+- * if it fails to create the file that is to be written
+- * @return FileMessageFactory
+- */
+- public static FileMessageFactory getInstance(File f, boolean openForWrite)
+- throws FileNotFoundException, IOException {
+- return new FileMessageFactory(f, openForWrite);
+- }
+-
+- /**
+- * Reads file data into the file message and sets the size, totalLength,
+- * totalNrOfMsgs and the message number <BR>
+- * If EOF is reached, the factory returns null, and closes itself, otherwise
+- * the same message is returned as was passed in. This makes sure that not
+- * more memory is ever used. To remember, neither the file message or the
+- * factory are thread safe. dont hand off the message to one thread and read
+- * the same with another.
+- *
+- * @param f
+- * FileMessage - the message to be populated with file data
+- * @throws IllegalArgumentException -
+- * if the factory is for writing or is closed
+- * @throws IOException -
+- * if a file read exception occurs
+- * @return FileMessage - returns the same message passed in as a parameter,
+- * or null if EOF
+- */
+- public FileMessage readMessage(FileMessage f)
+- throws IllegalArgumentException, IOException {
+- checkState(false);
+- int length = in.read(data);
+- if (length == -1) {
+- cleanup();
+- return null;
+- } else {
+- f.setData(data, length);
+- f.setTotalLength(size);
+- f.setTotalNrOfMsgs(totalNrOfMessages);
+- f.setMessageNumber(++nrOfMessagesProcessed);
+- return f;
+- }//end if
+- }
+-
+- /**
+- * Writes a message to file. If (msg.getMessageNumber() ==
+- * msg.getTotalNrOfMsgs()) the output stream will be closed after writing.
+- *
+- * @param msg
+- * FileMessage - message containing data to be written
+- * @throws IllegalArgumentException -
+- * if the factory is opened for read or closed
+- * @throws IOException -
+- * if a file write error occurs
+- * @return returns true if the file is complete and outputstream is closed,
+- * false otherwise.
+- */
+- public boolean writeMessage(FileMessage msg)
+- throws IllegalArgumentException, IOException {
+- if (!openForWrite)
+- throw new IllegalArgumentException(
+- "Can't write message, this factory is reading.");
+- if (log.isDebugEnabled())
+- log.debug("Message " + msg + " data " + msg.getData()
+- + " data length " + msg.getDataLength() + " out " + out);
+- if (out != null) {
+- out.write(msg.getData(), 0, msg.getDataLength());
+- nrOfMessagesProcessed++;
+- out.flush();
+- if (msg.getMessageNumber() == msg.getTotalNrOfMsgs()) {
+- out.close();
+- cleanup();
+- return true;
+- }//end if
+- } else {
+- if (log.isWarnEnabled())
+- log.warn("Receive Message again -- Sender ActTimeout to short [ path: "
+- + msg.getContextPath()
+- + " war: "
+- + msg.getFileName()
+- + " data: "
+- + msg.getData()
+- + " data length: " + msg.getDataLength() + " ]");
+- }
+- return false;
+- }//writeMessage
+-
+- /**
+- * Closes the factory, its streams and sets all its references to null
+- */
+- public void cleanup() {
+- if (in != null)
+- try {
+- in.close();
+- } catch (Exception ignore) {
+- }
+- if (out != null)
+- try {
+- out.close();
+- } catch (Exception ignore) {
+- }
+- in = null;
+- out = null;
+- size = 0;
+- closed = true;
+- data = null;
+- nrOfMessagesProcessed = 0;
+- totalNrOfMessages = 0;
+- }
+-
+- /**
+- * Check to make sure the factory is able to perform the function it is
+- * asked to do. Invoked by readMessage/writeMessage before those methods
+- * proceed.
+- *
+- * @param openForWrite
+- * boolean
+- * @throws IllegalArgumentException
+- */
+- protected void checkState(boolean openForWrite)
+- throws IllegalArgumentException {
+- if (this.openForWrite != openForWrite) {
+- cleanup();
+- if (openForWrite)
+- throw new IllegalArgumentException(
+- "Can't write message, this factory is reading.");
+- else
+- throw new IllegalArgumentException(
+- "Can't read message, this factory is writing.");
+- }
+- if (this.closed) {
+- cleanup();
+- throw new IllegalArgumentException("Factory has been closed.");
+- }
+- }
+-
+- /**
+- * Example usage.
+- *
+- * @param args
+- * String[], args[0] - read from filename, args[1] write to
+- * filename
+- * @throws Exception
+- */
+- public static void main(String[] args) throws Exception {
+-
+- System.out
+- .println("Usage: FileMessageFactory fileToBeRead fileToBeWritten");
+- System.out
+- .println("Usage: This will make a copy of the file on the local file system");
+- FileMessageFactory read = getInstance(new File(args[0]), false);
+- FileMessageFactory write = getInstance(new File(args[1]), true);
+- FileMessage msg = new FileMessage(null, args[0], args[0]);
+- msg = read.readMessage(msg);
+- System.out.println("Expecting to write " + msg.getTotalNrOfMsgs()
+- + " messages.");
+- int cnt = 0;
+- while (msg != null) {
+- write.writeMessage(msg);
+- cnt++;
+- msg = read.readMessage(msg);
+- }//while
+- System.out.println("Actually wrote " + cnt + " messages.");
+- }///main
+-
+- public File getFile() {
+- return file;
+- }
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/ha/CatalinaCluster.java
+===================================================================
+--- java/org/apache/catalina/ha/CatalinaCluster.java (revision 590752)
++++ java/org/apache/catalina/ha/CatalinaCluster.java (working copy)
+@@ -1,130 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.ha;
+-
+-import java.util.Map;
+-
+-import org.apache.catalina.Cluster;
+-import org.apache.catalina.LifecycleException;
+-import org.apache.catalina.Manager;
+-import org.apache.catalina.Valve;
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.juli.logging.Log;
+-
+-
+-
+-/**
+- * A <b>CatalinaCluster</b> interface allows to plug in and out the
+- * different cluster implementations
+- *
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-
+-public interface CatalinaCluster extends Cluster {
+- // ----------------------------------------------------- Instance Variables
+-
+- /**
+- * Descriptive information about this component implementation.
+- */
+- public String info = "CatalinaCluster/2.0";
+-
+- /**
+- * Start the cluster, the owning container will invoke this
+- * @throws Exception - if failure to start cluster
+- */
+- public void start() throws Exception;
+-
+- /**
+- * Stops the cluster, the owning container will invoke this
+- * @throws LifecycleException
+- */
+- public void stop() throws LifecycleException;
+-
+- /**
+- * Returns the associates logger with this cluster.
+- *
+- * @return Log
+- */
+- public Log getLogger();
+-
+- /**
+- * Sends a message to all the members in the cluster
+- * @param msg ClusterMessage
+- */
+- public void send(ClusterMessage msg);
+-
+- /**
+- * Sends a message to a specific member in the cluster.
+- *
+- * @param msg ClusterMessage
+- * @param dest Member
+- */
+- public void send(ClusterMessage msg, Member dest);
+-
+- /**
+- * Sends a message to a all members at local cluster domain
+- *
+- * @param msg ClusterMessage
+- */
+- public void sendClusterDomain(ClusterMessage msg);
+-
+- /**
+- * Returns that cluster has members.
+- */
+- public boolean hasMembers();
+-
+- /**
+- * Returns all the members currently participating in the cluster.
+- *
+- * @return Member[]
+- */
+- public Member[] getMembers();
+-
+- /**
+- * Return the member that represents this node.
+- *
+- * @return Member
+- */
+- public Member getLocalMember();
+-
+- public void addValve(Valve valve);
+-
+- public void addClusterListener(ClusterListener listener);
+-
+- public void removeClusterListener(ClusterListener listener);
+-
+- public void setClusterDeployer(ClusterDeployer deployer);
+-
+- public ClusterDeployer getClusterDeployer();
+-
+- /**
+- * @return The map of managers
+- */
+- public Map getManagers();
+-
+- public Manager getManager(String name);
+- public String getManagerName(String name, Manager manager);
+- public Valve[] getValves();
+-
+- public void setChannel(Channel channel);
+- public Channel getChannel();
+-
+-
+-}
+Index: java/org/apache/catalina/ha/package.html
+===================================================================
+--- java/org/apache/catalina/ha/package.html (revision 590752)
++++ java/org/apache/catalina/ha/package.html (working copy)
+@@ -1,23 +0,0 @@
+-<!--
+- 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.
+--->
+-<body>
+-
+-<p>This package contains code for Clustering, the base class
+-of a Cluster is <code>org.apache.catalina.Cluster</code> implementations
+-of this class is done when implementing a new Cluster protocol</p>
+-
+-</body>
+Index: java/org/apache/catalina/ha/ClusterValve.java
+===================================================================
+--- java/org/apache/catalina/ha/ClusterValve.java (revision 590752)
++++ java/org/apache/catalina/ha/ClusterValve.java (working copy)
+@@ -1,40 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.ha;
+-
+-import org.apache.catalina.Valve;
+-
+-/**
+- * Cluster valves are a simple extension to the Tomcat valve architecture
+- * with a small addition of being able to reference the cluster component in the container it sits in.
+- * @author Filip Hanik
+- * @author Peter Rossbach
+- * @version $Revision$, $Date$
+- */
+-public interface ClusterValve extends Valve{
+- /**
+- * Returns the cluster the cluster deployer is associated with
+- * @return CatalinaCluster
+- */
+- public CatalinaCluster getCluster();
+-
+- /**
+- * Associates the cluster deployer with a cluster
+- * @param cluster CatalinaCluster
+- */
+- public void setCluster(CatalinaCluster cluster);
+-}
+Index: java/org/apache/catalina/ha/LocalStrings.properties
+===================================================================
+--- java/org/apache/catalina/ha/LocalStrings.properties (revision 590752)
++++ java/org/apache/catalina/ha/LocalStrings.properties (working copy)
+@@ -1,16 +0,0 @@
+-# 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.
+-
+-cluster.mbean.register.already=MBean {0} already registered!
+Index: java/org/apache/catalina/ha/ClusterMessageBase.java
+===================================================================
+--- java/org/apache/catalina/ha/ClusterMessageBase.java (revision 590752)
++++ java/org/apache/catalina/ha/ClusterMessageBase.java (working copy)
+@@ -1,76 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.ha;
+-
+-import org.apache.catalina.tribes.Member;
+-
+-
+-/**
+- * <p>Title: </p>
+- *
+- * <p>Description: </p>
+- *
+- *
+- * <p>Company: </p>
+- *
+- * @author not attributable
+- * @version 1.0
+- */
+-public class ClusterMessageBase implements ClusterMessage {
+-
+- protected transient Member address;
+- private String uniqueId;
+- private long timestamp;
+- public ClusterMessageBase() {
+- }
+-
+- /**
+- * getAddress
+- *
+- * @return Member
+- * @todo Implement this org.apache.catalina.ha.ClusterMessage method
+- */
+- public Member getAddress() {
+- return address;
+- }
+-
+- public String getUniqueId() {
+- return uniqueId;
+- }
+-
+- public long getTimestamp() {
+- return timestamp;
+- }
+-
+- /**
+- * setAddress
+- *
+- * @param member Member
+- * @todo Implement this org.apache.catalina.ha.ClusterMessage method
+- */
+- public void setAddress(Member member) {
+- this.address = member;
+- }
+-
+- public void setUniqueId(String uniqueId) {
+- this.uniqueId = uniqueId;
+- }
+-
+- public void setTimestamp(long timestamp) {
+- this.timestamp = timestamp;
+- }
+-}
+Index: java/org/apache/catalina/ha/mbeans-descriptors.xml
+===================================================================
+--- java/org/apache/catalina/ha/mbeans-descriptors.xml (revision 590752)
++++ java/org/apache/catalina/ha/mbeans-descriptors.xml (working copy)
+@@ -1,106 +0,0 @@
+-<?xml version="1.0"?>
+-<!--
+- 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.
+--->
+-<mbeans-descriptors>
+-
+- <mbean name="SimpleTcpCluster"
+- className="org.apache.catalina.mbeans.ClassNameMBean"
+- description="Tcp Cluster implementation"
+- domain="Catalina"
+- group="Cluster"
+- type="org.apache.catalina.ha.tcp.SimpleTcpCluster">
+-
+- </mbean>
+-
+-
+- <mbean name="SimpleTcpReplicationManager"
+- className="org.apache.catalina.mbeans.ClassNameMBean"
+- description="Clustered implementation of the Manager interface"
+- domain="Catalina"
+- group="Manager"
+- type="org.apache.catalina.ha.tcp.SimpleTcpReplicationManager">
+-
+- <attribute name="algorithm"
+- description="The message digest algorithm to be used when generating
+- session identifiers"
+- type="java.lang.String"/>
+-
+- <attribute name="checkInterval"
+- description="The interval (in seconds) between checks for expired
+- sessions"
+- type="int"/>
+-
+- <attribute name="className"
+- description="Fully qualified class name of the managed object"
+- type="java.lang.String"
+- writeable="false"/>
+-
+- <attribute name="distributable"
+- description="The distributable flag for Sessions created by this
+- Manager"
+- type="boolean"/>
+-
+- <attribute name="entropy"
+- description="A String initialization parameter used to increase the
+- entropy of the initialization of our random number
+- generator"
+- type="java.lang.String"/>
+-
+- <attribute name="managedResource"
+- description="The managed resource this MBean is associated with"
+- type="java.lang.Object"/>
+-
+- <attribute name="maxActiveSessions"
+- description="The maximum number of active Sessions allowed, or -1
+- for no limit"
+- type="int"/>
+-
+- <attribute name="maxInactiveInterval"
+- description="The default maximum inactive interval for Sessions
+- created by this Manager"
+- type="int"/>
+-
+- <attribute name="name"
+- description="The descriptive name of this Manager implementation
+- (for logging)"
+- type="java.lang.String"
+- writeable="false"/>
+-
+- </mbean>
+-
+-
+-
+-<mbean name="ReplicationValve"
+- className="org.apache.catalina.mbeans.ClassNameMBean"
+- description="Valve for simple tcp replication"
+- domain="Catalina"
+- group="Valve"
+- type="org.apache.catalina.ha.tcp.ReplicationValve">
+-
+- <attribute name="className"
+- description="Fully qualified class name of the managed object"
+- type="java.lang.String"
+- writeable="false"/>
+-
+- <attribute name="debug"
+- description="The debugging detail level for this component"
+- type="int"/>
+-
+- </mbean>
+-
+-
+-</mbeans-descriptors>
+Index: java/org/apache/catalina/ha/ClusterSession.java
+===================================================================
+--- java/org/apache/catalina/ha/ClusterSession.java (revision 590752)
++++ java/org/apache/catalina/ha/ClusterSession.java (working copy)
+@@ -1,40 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-
+-package org.apache.catalina.ha;
+-
+-import org.apache.catalina.Session;
+-import javax.servlet.http.HttpSession;
+-
+-public interface ClusterSession extends Session, HttpSession {
+- /**
+- * returns true if this session is the primary session, if that is the
+- * case, the manager can expire it upon timeout.
+- * @return True if this session is primary
+- */
+- public boolean isPrimarySession();
+-
+- /**
+- * Sets whether this is the primary session or not.
+- * @param primarySession Flag value
+- */
+- public void setPrimarySession(boolean primarySession);
+-
+-
+-
+-}
+Index: java/org/apache/catalina/ha/ClusterRuleSet.java
+===================================================================
+--- java/org/apache/catalina/ha/ClusterRuleSet.java (revision 590752)
++++ java/org/apache/catalina/ha/ClusterRuleSet.java (working copy)
+@@ -1,195 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-
+-package org.apache.catalina.ha;
+-
+-
+-import org.apache.tomcat.util.digester.Digester;
+-import org.apache.tomcat.util.digester.RuleSetBase;
+-
+-
+-/**
+- * <p><strong>RuleSet</strong> for processing the contents of a
+- * Cluster definition element. </p>
+- *
+- * @author Filip Hanik
+- * @author Peter Rossbach
+- * @version $Revision$ $Date$
+- */
+-
+-public class ClusterRuleSet extends RuleSetBase {
+-
+-
+- // ----------------------------------------------------- Instance Variables
+-
+-
+- /**
+- * The matching pattern prefix to use for recognizing our elements.
+- */
+- protected String prefix = null;
+-
+-
+- // ------------------------------------------------------------ Constructor
+-
+-
+- /**
+- * Construct an instance of this <code>RuleSet</code> with the default
+- * matching pattern prefix.
+- */
+- public ClusterRuleSet() {
+-
+- this("");
+-
+- }
+-
+-
+- /**
+- * Construct an instance of this <code>RuleSet</code> with the specified
+- * matching pattern prefix.
+- *
+- * @param prefix Prefix for matching pattern rules (including the
+- * trailing slash character)
+- */
+- public ClusterRuleSet(String prefix) {
+- super();
+- this.namespaceURI = null;
+- this.prefix = prefix;
+- }
+-
+-
+- // --------------------------------------------------------- Public Methods
+-
+-
+- /**
+- * <p>Add the set of Rule instances defined in this RuleSet to the
+- * specified <code>Digester</code> instance, associating them with
+- * our namespace URI (if any). This method should only be called
+- * by a Digester instance.</p>
+- *
+- * @param digester Digester instance to which the new Rule instances
+- * should be added.
+- */
+- public void addRuleInstances(Digester digester) {
+- //Cluster configuration start
+- digester.addObjectCreate(prefix + "Manager",
+- null, // MUST be specified in the element
+- "className");
+- digester.addSetProperties(prefix + "Manager");
+- digester.addSetNext(prefix + "Manager",
+- "setManagerTemplate",
+- "org.apache.catalina.ha.ClusterManager");
+-
+-
+-
+- digester.addObjectCreate(prefix + "Channel",
+- null, // MUST be specified in the element
+- "className");
+- digester.addSetProperties(prefix + "Channel");
+- digester.addSetNext(prefix + "Channel",
+- "setChannel",
+- "org.apache.catalina.tribes.Channel");
+-
+-
+- String channelPrefix = prefix + "Channel/";
+- { //channel properties
+- digester.addObjectCreate(channelPrefix + "Membership",
+- null, // MUST be specified in the element
+- "className");
+- digester.addSetProperties(channelPrefix + "Membership");
+- digester.addSetNext(channelPrefix + "Membership",
+- "setMembershipService",
+- "org.apache.catalina.tribes.MembershipService");
+-
+- digester.addObjectCreate(channelPrefix + "Sender",
+- null, // MUST be specified in the element
+- "className");
+- digester.addSetProperties(channelPrefix + "Sender");
+- digester.addSetNext(channelPrefix + "Sender",
+- "setChannelSender",
+- "org.apache.catalina.tribes.ChannelSender");
+-
+- digester.addObjectCreate(channelPrefix + "Sender/Transport",
+- null, // MUST be specified in the element
+- "className");
+- digester.addSetProperties(channelPrefix + "Sender/Transport");
+- digester.addSetNext(channelPrefix + "Sender/Transport",
+- "setTransport",
+- "org.apache.catalina.tribes.transport.MultiPointSender");
+-
+-
+- digester.addObjectCreate(channelPrefix + "Receiver",
+- null, // MUST be specified in the element
+- "className");
+- digester.addSetProperties(channelPrefix + "Receiver");
+- digester.addSetNext(channelPrefix + "Receiver",
+- "setChannelReceiver",
+- "org.apache.catalina.tribes.ChannelReceiver");
+-
+- digester.addObjectCreate(channelPrefix + "Interceptor",
+- null, // MUST be specified in the element
+- "className");
+- digester.addSetProperties(channelPrefix + "Interceptor");
+- digester.addSetNext(channelPrefix + "Interceptor",
+- "addInterceptor",
+- "org.apache.catalina.tribes.ChannelInterceptor");
+-
+-
+- digester.addObjectCreate(channelPrefix + "Interceptor/Member",
+- null, // MUST be specified in the element
+- "className");
+- digester.addSetProperties(channelPrefix + "Interceptor/Member");
+- digester.addSetNext(channelPrefix + "Interceptor/Member",
+- "addStaticMember",
+- "org.apache.catalina.tribes.Member");
+- }
+-
+- digester.addObjectCreate(prefix + "Valve",
+- null, // MUST be specified in the element
+- "className");
+- digester.addSetProperties(prefix + "Valve");
+- digester.addSetNext(prefix + "Valve",
+- "addValve",
+- "org.apache.catalina.Valve");
+-
+- digester.addObjectCreate(prefix + "Deployer",
+- null, // MUST be specified in the element
+- "className");
+- digester.addSetProperties(prefix + "Deployer");
+- digester.addSetNext(prefix + "Deployer",
+- "setClusterDeployer",
+- "org.apache.catalina.ha.ClusterDeployer");
+-
+- digester.addObjectCreate(prefix + "Listener",
+- null, // MUST be specified in the element
+- "className");
+- digester.addSetProperties(prefix + "Listener");
+- digester.addSetNext(prefix + "Listener",
+- "addLifecycleListener",
+- "org.apache.catalina.LifecycleListener");
+-
+- digester.addObjectCreate(prefix + "ClusterListener",
+- null, // MUST be specified in the element
+- "className");
+- digester.addSetProperties(prefix + "ClusterListener");
+- digester.addSetNext(prefix + "ClusterListener",
+- "addClusterListener",
+- "org.apache.catalina.ha.ClusterListener");
+- //Cluster configuration end
+- }
+-
+-}
+Index: java/org/apache/catalina/ha/ClusterDeployer.java
+===================================================================
+--- java/org/apache/catalina/ha/ClusterDeployer.java (revision 590752)
++++ java/org/apache/catalina/ha/ClusterDeployer.java (working copy)
+@@ -1,121 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.ha;
+-
+-/**
+- * A <b>ClusterDeployer</b> interface allows to plug in and out the
+- * different deployment implementations
+- *
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-import org.apache.catalina.LifecycleException;
+-import java.io.IOException;
+-import java.net.URL;
+-import org.apache.catalina.tribes.ChannelListener;
+-
+-public interface ClusterDeployer extends ChannelListener {
+- /**
+- * Descriptive information about this component implementation.
+- */
+- public String info = "ClusterDeployer/1.0";
+- /**
+- * Start the cluster deployer, the owning container will invoke this
+- * @throws Exception - if failure to start cluster
+- */
+- public void start() throws Exception;
+-
+- /**
+- * Stops the cluster deployer, the owning container will invoke this
+- * @throws LifecycleException
+- */
+- public void stop() throws LifecycleException;
+-
+- /**
+- * Sets the deployer for this cluster deployer to use.
+- * @param deployer Deployer
+- */
+- // FIXME
+- //public void setDeployer(Deployer deployer);
+-
+- /**
+- * Install a new web application, whose web application archive is at the
+- * specified URL, into this container and all the other
+- * members of the cluster with the specified context path.
+- * A context path of "" (the empty string) should be used for the root
+- * application for this container. Otherwise, the context path must
+- * start with a slash.
+- * <p>
+- * If this application is successfully installed locally,
+- * a ContainerEvent of type
+- * <code>INSTALL_EVENT</code> will be sent to all registered listeners,
+- * with the newly created <code>Context</code> as an argument.
+- *
+- * @param contextPath The context path to which this application should
+- * be installed (must be unique)
+- * @param war A URL of type "jar:" that points to a WAR file, or type
+- * "file:" that points to an unpacked directory structure containing
+- * the web application to be installed
+- *
+- * @exception IllegalArgumentException if the specified context path
+- * is malformed (it must be "" or start with a slash)
+- * @exception IllegalStateException if the specified context path
+- * is already attached to an existing web application
+- * @exception IOException if an input/output error was encountered
+- * during installation
+- */
+- public void install(String contextPath, URL war) throws IOException;
+-
+- /**
+- * Remove an existing web application, attached to the specified context
+- * path. If this application is successfully removed, a
+- * ContainerEvent of type <code>REMOVE_EVENT</code> will be sent to all
+- * registered listeners, with the removed <code>Context</code> as
+- * an argument. Deletes the web application war file and/or directory
+- * if they exist in the Host's appBase.
+- *
+- * @param contextPath The context path of the application to be removed
+- * @param undeploy boolean flag to remove web application from server
+- *
+- * @exception IllegalArgumentException if the specified context path
+- * is malformed (it must be "" or start with a slash)
+- * @exception IllegalArgumentException if the specified context path does
+- * not identify a currently installed web application
+- * @exception IOException if an input/output error occurs during
+- * removal
+- */
+- public void remove(String contextPath, boolean undeploy) throws IOException;
+-
+- /**
+- * call from container Background Process
+- */
+- public void backgroundProcess();
+-
+- /**
+- * Returns the cluster the cluster deployer is associated with
+- * @return CatalinaCluster
+- */
+- public CatalinaCluster getCluster();
+-
+- /**
+- * Associates the cluster deployer with a cluster
+- * @param cluster CatalinaCluster
+- */
+- public void setCluster(CatalinaCluster cluster);
+-
+-}
+Index: java/org/apache/catalina/ha/ClusterMessage.java
+===================================================================
+--- java/org/apache/catalina/ha/ClusterMessage.java (revision 590752)
++++ java/org/apache/catalina/ha/ClusterMessage.java (working copy)
+@@ -1,34 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.ha;
+-
+-import java.io.Serializable;
+-import org.apache.catalina.tribes.Member;
+-
+-
+-/**
+- * @author Filip Hanik
+- *
+- */
+-public interface ClusterMessage extends Serializable {
+- public Member getAddress();
+- public void setAddress(Member member);
+- public String getUniqueId();
+- public void setUniqueId(String id);
+- public long getTimestamp();
+- public void setTimestamp(long timestamp);
+-}
+Index: java/org/apache/catalina/ha/ClusterListener.java
+===================================================================
+--- java/org/apache/catalina/ha/ClusterListener.java (revision 590752)
++++ java/org/apache/catalina/ha/ClusterListener.java (working copy)
+@@ -1,114 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.ha;
+-
+-
+-
+-
+-import java.io.Serializable;
+-
+-import org.apache.catalina.tribes.ChannelListener;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.util.StringManager;
+-
+-
+-/**
+- * Receive SessionID cluster change from other backup node after primary session
+- * node is failed.
+- *
+- * @author Peter Rossbach
+- * @author Filip Hanik
+- * @version $Revision$ $Date$
+- */
+-public abstract class ClusterListener implements ChannelListener {
+-
+- public static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(ClusterListener.class);
+-
+-
+- //--Instance Variables--------------------------------------
+-
+- /**
+- * The string manager for this package.
+- */
+- protected StringManager sm = StringManager.getManager(Constants.Package);
+-
+- protected CatalinaCluster cluster = null;
+-
+- //--Constructor---------------------------------------------
+-
+- public ClusterListener() {
+- }
+-
+- //--Instance Getters/Setters--------------------------------
+-
+- public CatalinaCluster getCluster() {
+- return cluster;
+- }
+-
+- public void setCluster(CatalinaCluster cluster) {
+- if (log.isDebugEnabled()) {
+- if (cluster != null)
+- log.debug("add ClusterListener " + this.toString() + " to cluster" + cluster);
+- else
+- log.debug("remove ClusterListener " + this.toString() + " from cluster");
+- }
+- this.cluster = cluster;
+- }
+-
+- public boolean equals(Object listener) {
+- return super.equals(listener);
+- }
+-
+- public int hashCode() {
+- return super.hashCode();
+- }
+-
+- //--Logic---------------------------------------------------
+-
+- public final void messageReceived(Serializable msg, Member member) {
+- if ( msg instanceof ClusterMessage ) messageReceived((ClusterMessage)msg);
+- }
+- public final boolean accept(Serializable msg, Member member) {
+- if ( msg instanceof ClusterMessage ) return true;
+- return false;
+- }
+-
+-
+-
+- /**
+- * Callback from the cluster, when a message is received, The cluster will
+- * broadcast it invoking the messageReceived on the receiver.
+- *
+- * @param msg
+- * ClusterMessage - the message received from the cluster
+- */
+- public abstract void messageReceived(ClusterMessage msg) ;
+-
+-
+- /**
+- * Accept only SessionIDMessages
+- *
+- * @param msg
+- * ClusterMessage
+- * @return boolean - returns true to indicate that messageReceived should be
+- * invoked. If false is returned, the messageReceived method will
+- * not be invoked.
+- */
+- public abstract boolean accept(ClusterMessage msg) ;
+-
+-}
+Index: java/org/apache/catalina/ha/authenticator/ClusterSingleSignOn.java
+===================================================================
+--- java/org/apache/catalina/ha/authenticator/ClusterSingleSignOn.java (revision 590752)
++++ java/org/apache/catalina/ha/authenticator/ClusterSingleSignOn.java (working copy)
+@@ -1,447 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-
+-package org.apache.catalina.ha.authenticator;
+-
+-
+-import java.security.Principal;
+-
+-import org.apache.catalina.Container;
+-import org.apache.catalina.Cluster;
+-import org.apache.catalina.Engine;
+-import org.apache.catalina.Host;
+-import org.apache.catalina.LifecycleException;
+-import org.apache.catalina.Manager;
+-import org.apache.catalina.Session;
+-import org.apache.catalina.authenticator.SingleSignOn;
+-import org.apache.catalina.ha.CatalinaCluster;
+-import org.apache.catalina.ha.ClusterManager;
+-
+-
+-
+-/**
+- * A <strong>Valve</strong> that supports a "single sign on" user experience on
+- * each nodes of a cluster, where the security identity of a user who successfully
+- * authenticates to one web application is propogated to other web applications and
+- * to other nodes cluster in the same security domain. For successful use, the following
+- * requirements must be met:
+- * <ul>
+- * <li>This Valve must be configured on the Container that represents a
+- * virtual host (typically an implementation of <code>Host</code>).</li>
+- * <li>The <code>Realm</code> that contains the shared user and role
+- * information must be configured on the same Container (or a higher
+- * one), and not overridden at the web application level.</li>
+- * <li>The web applications themselves must use one of the standard
+- * Authenticators found in the
+- * <code>org.apache.catalina.authenticator</code> package.</li>
+- * </ul>
+- *
+- * @author Fabien Carrion
+- */
+-
+-public class ClusterSingleSignOn
+- extends SingleSignOn {
+-
+-
+- // ----------------------------------------------------- Instance Variables
+-
+-
+- /**
+- * Descriptive information about this Valve implementation.
+- */
+- protected static String info =
+- "org.apache.catalina.cluster.authenticator.ClusterSingleSignOn";
+-
+- protected int messageNumber = 0;
+-
+- private ClusterSingleSignOnListener clusterSSOListener = null;
+-
+-
+- // ------------------------------------------------------------- Properties
+-
+- private CatalinaCluster cluster = null;
+-
+-
+-
+- /**
+- * Return descriptive information about this Valve implementation.
+- */
+- public String getInfo() {
+-
+- return (info);
+-
+- }
+-
+- public CatalinaCluster getCluster() {
+-
+- return cluster;
+-
+- }
+-
+- public void setCluster(CatalinaCluster cluster) {
+-
+- this.cluster = cluster;
+-
+- }
+-
+-
+- // ------------------------------------------------------ Lifecycle Methods
+-
+-
+- /**
+- * Prepare for the beginning of active use of the public methods of this
+- * component. This method should be called after <code>configure()</code>,
+- * and before any of the public methods of the component are utilized.
+- *
+- * @exception LifecycleException if this component detects a fatal error
+- * that prevents this component from being used
+- */
+- public void start() throws LifecycleException {
+-
+- super.start();
+-
+- clusterSSOListener = new ClusterSingleSignOnListener();
+- clusterSSOListener.setClusterSSO(this);
+-
+- // Load the cluster component, if any
+- try {
+- //the channel is already running
+- Cluster cluster = getCluster();
+- // stop remove cluster binding
+- if(cluster == null) {
+- Container host = getContainer();
+- if(host != null && host instanceof Host) {
+- cluster = host.getCluster();
+- if(cluster != null && cluster instanceof CatalinaCluster) {
+- setCluster((CatalinaCluster) cluster);
+- getCluster().addClusterListener(clusterSSOListener);
+- } else {
+- Container engine = host.getParent();
+- if(engine != null && engine instanceof Engine) {
+- cluster = engine.getCluster();
+- if(cluster != null && cluster instanceof CatalinaCluster) {
+- setCluster((CatalinaCluster) cluster);
+- getCluster().addClusterListener(clusterSSOListener);
+- }
+- } else {
+- cluster = null;
+- }
+- }
+- }
+- }
+- if (cluster == null) {
+- throw new LifecycleException
+- ("There is no Cluster for ClusterSingleSignOn");
+- }
+-
+- } catch (Throwable t) {
+- throw new LifecycleException
+- ("ClusterSingleSignOn exception during clusterLoad " + t);
+- }
+-
+- }
+-
+-
+- /**
+- * Gracefully terminate the active use of the public methods of this
+- * component. This method should be the last one called on a given
+- * instance of this component.
+- *
+- * @exception LifecycleException if this component detects a fatal error
+- * that needs to be reported
+- */
+- public void stop() throws LifecycleException {
+-
+- super.stop();
+-
+- if (getCluster() != null && getCluster() instanceof CatalinaCluster) {
+- getCluster().removeClusterListener(clusterSSOListener);
+- }
+-
+- }
+-
+-
+- // --------------------------------------------------------- Public Methods
+-
+-
+- /**
+- * Return a String rendering of this object.
+- */
+- public String toString() {
+-
+- StringBuffer sb = new StringBuffer("ClusterSingleSignOn[");
+- if (container == null )
+- sb.append("Container is null");
+- else
+- sb.append(container.getName());
+- sb.append("]");
+- return (sb.toString());
+-
+- }
+-
+-
+- // ------------------------------------------------------ Protected Methods
+-
+-
+- /**
+- * Notify the cluster of the addition of a Session to
+- * an SSO session and associate the specified single
+- * sign on identifier with the specified Session on the
+- * local node.
+- *
+- * @param ssoId Single sign on identifier
+- * @param session Session to be associated
+- */
+- protected void associate(String ssoId, Session session) {
+-
+- if (cluster != null) {
+- messageNumber++;
+- SingleSignOnMessage msg =
+- new SingleSignOnMessage(cluster.getLocalMember(),
+- ssoId, session.getId());
+- Manager mgr = session.getManager();
+- if ((mgr != null) && (mgr instanceof ClusterManager))
+- msg.setContextName(((ClusterManager) mgr).getName());
+-
+- msg.setAction(SingleSignOnMessage.ADD_SESSION);
+-
+- cluster.sendClusterDomain(msg);
+-
+- if (containerLog.isDebugEnabled())
+- containerLog.debug("SingleSignOnMessage Send with action "
+- + msg.getAction());
+- }
+-
+- associateLocal(ssoId, session);
+-
+- }
+-
+- protected void associateLocal(String ssoId, Session session) {
+-
+- super.associate(ssoId, session);
+-
+- }
+-
+- /**
+- * Notify the cluster of the removal of a Session from an
+- * SSO session and deregister the specified session. If it is the last
+- * session, then also get rid of the single sign on identifier on the
+- * local node.
+- *
+- * @param ssoId Single sign on identifier
+- * @param session Session to be deregistered
+- */
+- protected void deregister(String ssoId, Session session) {
+-
+- if (cluster != null) {
+- messageNumber++;
+- SingleSignOnMessage msg =
+- new SingleSignOnMessage(cluster.getLocalMember(),
+- ssoId, session.getId());
+- Manager mgr = session.getManager();
+- if ((mgr != null) && (mgr instanceof ClusterManager))
+- msg.setContextName(((ClusterManager) mgr).getName());
+-
+- msg.setAction(SingleSignOnMessage.DEREGISTER_SESSION);
+-
+- cluster.sendClusterDomain(msg);
+- if (containerLog.isDebugEnabled())
+- containerLog.debug("SingleSignOnMessage Send with action "
+- + msg.getAction());
+- }
+-
+- deregisterLocal(ssoId, session);
+-
+- }
+-
+- protected void deregisterLocal(String ssoId, Session session) {
+-
+- super.deregister(ssoId, session);
+-
+- }
+-
+- /**
+- * Notifies the cluster that a single sign on session
+- * has been terminated due to a user logout, deregister
+- * the specified single sign on identifier, and invalidate
+- * any associated sessions on the local node.
+- *
+- * @param ssoId Single sign on identifier to deregister
+- */
+- protected void deregister(String ssoId) {
+-
+- if (cluster != null) {
+- messageNumber++;
+- SingleSignOnMessage msg =
+- new SingleSignOnMessage(cluster.getLocalMember(),
+- ssoId, null);
+- msg.setAction(SingleSignOnMessage.LOGOUT_SESSION);
+-
+- cluster.sendClusterDomain(msg);
+- if (containerLog.isDebugEnabled())
+- containerLog.debug("SingleSignOnMessage Send with action "
+- + msg.getAction());
+- }
+-
+- deregisterLocal(ssoId);
+-
+- }
+-
+- protected void deregisterLocal(String ssoId) {
+-
+- super.deregister(ssoId);
+-
+- }
+-
+- /**
+- * Notifies the cluster of the creation of a new SSO entry
+- * and register the specified Principal as being associated
+- * with the specified value for the single sign on identifier.
+- *
+- * @param ssoId Single sign on identifier to register
+- * @param principal Associated user principal that is identified
+- * @param authType Authentication type used to authenticate this
+- * user principal
+- * @param username Username used to authenticate this user
+- * @param password Password used to authenticate this user
+- */
+- protected void register(String ssoId, Principal principal, String authType,
+- String username, String password) {
+-
+- if (cluster != null) {
+- messageNumber++;
+- SingleSignOnMessage msg =
+- new SingleSignOnMessage(cluster.getLocalMember(),
+- ssoId, null);
+- msg.setAction(SingleSignOnMessage.REGISTER_SESSION);
+- msg.setAuthType(authType);
+- msg.setUsername(username);
+- msg.setPassword(password);
+-
+- cluster.sendClusterDomain(msg);
+- if (containerLog.isDebugEnabled())
+- containerLog.debug("SingleSignOnMessage Send with action "
+- + msg.getAction());
+- }
+-
+- registerLocal(ssoId, principal, authType, username, password);
+-
+- }
+-
+- protected void registerLocal(String ssoId, Principal principal, String authType,
+- String username, String password) {
+-
+- super.register(ssoId, principal, authType, username, password);
+-
+- }
+-
+-
+- /**
+- * Notifies the cluster of an update of the security credentials
+- * associated with an SSO session. Updates any <code>SingleSignOnEntry</code>
+- * found under key <code>ssoId</code> with the given authentication data.
+- * <p>
+- * The purpose of this method is to allow an SSO entry that was
+- * established without a username/password combination (i.e. established
+- * following DIGEST or CLIENT-CERT authentication) to be updated with
+- * a username and password if one becomes available through a subsequent
+- * BASIC or FORM authentication. The SSO entry will then be usable for
+- * reauthentication.
+- * <p>
+- * <b>NOTE:</b> Only updates the SSO entry if a call to
+- * <code>SingleSignOnEntry.getCanReauthenticate()</code> returns
+- * <code>false</code>; otherwise, it is assumed that the SSO entry already
+- * has sufficient information to allow reauthentication and that no update
+- * is needed.
+- *
+- * @param ssoId identifier of Single sign to be updated
+- * @param principal the <code>Principal</code> returned by the latest
+- * call to <code>Realm.authenticate</code>.
+- * @param authType the type of authenticator used (BASIC, CLIENT-CERT,
+- * DIGEST or FORM)
+- * @param username the username (if any) used for the authentication
+- * @param password the password (if any) used for the authentication
+- */
+- protected void update(String ssoId, Principal principal, String authType,
+- String username, String password) {
+-
+- if (cluster != null) {
+- messageNumber++;
+- SingleSignOnMessage msg =
+- new SingleSignOnMessage(cluster.getLocalMember(),
+- ssoId, null);
+- msg.setAction(SingleSignOnMessage.UPDATE_SESSION);
+- msg.setAuthType(authType);
+- msg.setUsername(username);
+- msg.setPassword(password);
+-
+- cluster.sendClusterDomain(msg);
+- if (containerLog.isDebugEnabled())
+- containerLog.debug("SingleSignOnMessage Send with action "
+- + msg.getAction());
+- }
+-
+- updateLocal(ssoId, principal, authType, username, password);
+-
+- }
+-
+- protected void updateLocal(String ssoId, Principal principal, String authType,
+- String username, String password) {
+-
+- super.update(ssoId, principal, authType, username, password);
+-
+- }
+-
+-
+- /**
+- * Remove a single Session from a SingleSignOn and notify the cluster
+- * of the removal. Called when a session is timed out and no longer active.
+- *
+- * @param ssoId Single sign on identifier from which to remove the session.
+- * @param session the session to be removed.
+- */
+- protected void removeSession(String ssoId, Session session) {
+-
+- if (cluster != null) {
+- messageNumber++;
+- SingleSignOnMessage msg =
+- new SingleSignOnMessage(cluster.getLocalMember(),
+- ssoId, session.getId());
+-
+- Manager mgr = session.getManager();
+- if ((mgr != null) && (mgr instanceof ClusterManager))
+- msg.setContextName(((ClusterManager) mgr).getName());
+-
+- msg.setAction(SingleSignOnMessage.REMOVE_SESSION);
+-
+- cluster.sendClusterDomain(msg);
+- if (containerLog.isDebugEnabled())
+- containerLog.debug("SingleSignOnMessage Send with action "
+- + msg.getAction());
+- }
+-
+- removeSessionLocal(ssoId, session);
+- }
+-
+- protected void removeSessionLocal(String ssoId, Session session) {
+-
+- super.removeSession(ssoId, session);
+-
+- }
+-
+-}
+Index: java/org/apache/catalina/ha/authenticator/mbeans-descriptors.xml
+===================================================================
+--- java/org/apache/catalina/ha/authenticator/mbeans-descriptors.xml (revision 590752)
++++ java/org/apache/catalina/ha/authenticator/mbeans-descriptors.xml (working copy)
+@@ -1,41 +0,0 @@
+-<?xml version="1.0"?>
+-<!--
+- 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.
+--->
+-<mbeans-descriptors>
+-
+- <mbean name="ClusterSingleSignOn"
+- description="A Valve that supports a 'single signon' user experience on a whole cluster"
+- domain="Catalina"
+- group="Valve"
+- type="org.apache.catalina.cluster.authenticator.ClusterSingleSignOn">
+-
+- <attribute name="className"
+- description="Fully qualified class name of the managed object"
+- type="java.lang.String"
+- writeable="false"/>
+-
+- <attribute name="requireReauthentication"
+- description="Should we attempt to reauthenticate each request against the security Realm?"
+- type="boolean"/>
+-
+- <attribute name="cookieDomain"
+- description="(Optiona) Domain to be used by sso cookies"
+- type="java.lang.String" />
+-
+- </mbean>
+-
+-</mbeans-descriptors>
+Index: java/org/apache/catalina/ha/authenticator/SingleSignOnMessage.java
+===================================================================
+--- java/org/apache/catalina/ha/authenticator/SingleSignOnMessage.java (revision 590752)
++++ java/org/apache/catalina/ha/authenticator/SingleSignOnMessage.java (working copy)
+@@ -1,188 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.ha.authenticator;
+-
+-import java.io.Serializable;
+-
+-import org.apache.catalina.ha.ClusterMessage;
+-import org.apache.catalina.tribes.Member;
+-
+-/**
+- * Contains the SingleSignOn data, read and written by the ClusterSingleSignOn
+- * @author Fabien Carrion
+- */
+-
+-public class SingleSignOnMessage implements ClusterMessage, Serializable {
+-
+- public static final int ADD_SESSION = 1;
+- public static final int DEREGISTER_SESSION = 2;
+- public static final int LOGOUT_SESSION = 3;
+- public static final int REGISTER_SESSION = 4;
+- public static final int UPDATE_SESSION = 5;
+- public static final int REMOVE_SESSION = 6;
+-
+- private int action = -1;
+- private String ssoId = null;
+- private String ctxname = null;
+- private String sessionId = null;
+- private String authType = null;
+- private String password = null;
+- private String username = null;
+-
+- private Member address = null;
+- private long timestamp = 0;
+- private String uniqueId = null;
+-
+- public SingleSignOnMessage(Member source,
+- String ssoId,
+- String sessionId) {
+- this.address = source;
+- this.ssoId = ssoId;
+- this.sessionId = sessionId;
+- }
+-
+- /**
+- * Get the address that this message originated from. This would be set
+- * if the message was being relayed from a host other than the one
+- * that originally sent it.
+- */
+- public Member getAddress() {
+- return address;
+- }
+-
+- /**
+- * Called by the cluster before sending it to the other
+- * nodes.
+- *
+- * @param member Member
+- */
+- public void setAddress(Member member) {
+- this.address = member;
+- }
+-
+- /**
+- * Timestamp message.
+- *
+- * @return long
+- */
+- public long getTimestamp() {
+- return timestamp;
+- }
+-
+- /**
+- * Called by the cluster before sending out
+- * the message.
+- *
+- * @param timestamp The timestamp
+- */
+- public void setTimestamp(long timestamp) {
+- this.timestamp = timestamp;
+- }
+-
+- /**
+- * Each message must have a unique ID, in case of using async replication,
+- * and a smart queue, this id is used to replace messages not yet sent.
+- *
+- * @return String
+- */
+- public String getUniqueId() {
+- if (this.uniqueId != null)
+- return this.uniqueId;
+- StringBuffer result = new StringBuffer(getSsoId());
+- result.append("#-#");
+- result.append(System.currentTimeMillis());
+- return result.toString();
+- }
+-
+- public void setUniqueId(String uniqueId) {
+- this.uniqueId = uniqueId;
+- }
+-
+- public int getAction() {
+- return action;
+- }
+-
+- public void setAction(int action) {
+- this.action = action;
+- }
+-
+- public String getSsoId() {
+- return ssoId;
+- }
+-
+- public void setSsoId(String ssoId) {
+- this.ssoId = ssoId;
+- }
+-
+- public String getContextName() {
+- return ctxname;
+- }
+-
+- public void setContextName(String ctxname) {
+- this.ctxname = ctxname;
+- }
+-
+- public String getSessionId() {
+- return sessionId;
+- }
+-
+- public void setSessionId(String sessionId) {
+- this.sessionId = sessionId;
+- }
+-
+- public String getAuthType() {
+- return authType;
+- }
+-
+- public void setAuthType(String authType) {
+- this.authType = authType;
+- }
+-
+- public String getPassword() {
+- return password;
+- }
+-
+- public void setPassword(String password) {
+- this.password = password;
+- }
+-
+- public String getUsername() {
+- return username;
+- }
+-
+- public void setUsername(String username) {
+- this.username = username;
+- }
+-
+-
+- // --------------------------------------------------------- Public Methods
+-
+- /**
+- * Return a String rendering of this object.
+- */
+- public String toString() {
+-
+- StringBuffer sb = new StringBuffer("SingleSignOnMessage[action=");
+- sb.append(getAction()).append(", ssoId=").append(getSsoId());
+- sb.append(", sessionId=").append(getSessionId()).append(", username=");
+- sb.append(getUsername()).append("]");
+- return (sb.toString());
+-
+- }
+-
+-}
+Index: java/org/apache/catalina/ha/authenticator/ClusterSingleSignOnListener.java
+===================================================================
+--- java/org/apache/catalina/ha/authenticator/ClusterSingleSignOnListener.java (revision 590752)
++++ java/org/apache/catalina/ha/authenticator/ClusterSingleSignOnListener.java (working copy)
+@@ -1,180 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.ha.authenticator;
+-
+-import java.util.Map;
+-import java.io.IOException;
+-
+-import org.apache.catalina.Session;
+-import org.apache.catalina.ha.ClusterManager;
+-import org.apache.catalina.ha.ClusterMessage;
+-import org.apache.catalina.ha.ClusterListener;
+-
+-/**
+- * Receive replicated SingleSignOnMessage form other cluster node.
+- *
+- * @author Fabien Carrion
+- */
+-public class ClusterSingleSignOnListener extends ClusterListener {
+-
+- /**
+- * The descriptive information about this implementation.
+- */
+- protected static final String info = "org.apache.catalina.session.ClusterSingleSignOnListener/1.0";
+-
+- // ------------------------------------------------------------- Properties
+-
+- private ClusterSingleSignOn clusterSSO = null;
+-
+-
+- //--Constructor---------------------------------------------
+-
+- public ClusterSingleSignOnListener() {
+- }
+-
+- //--Logic---------------------------------------------------
+-
+- /**
+- * Return descriptive information about this implementation.
+- */
+- public String getInfo() {
+-
+- return (info);
+-
+- }
+-
+- public ClusterSingleSignOn getClusterSSO() {
+-
+- return clusterSSO;
+-
+- }
+-
+- public void setClusterSSO(ClusterSingleSignOn clusterSSO) {
+-
+- this.clusterSSO = clusterSSO;
+-
+- }
+-
+-
+- /**
+- * Callback from the cluster, when a message is received, The cluster will
+- * broadcast it invoking the messageReceived on the receiver.
+- *
+- * @param myobj
+- * ClusterMessage - the message received from the cluster
+- */
+- public void messageReceived(ClusterMessage myobj) {
+- if (myobj != null && myobj instanceof SingleSignOnMessage) {
+- SingleSignOnMessage msg = (SingleSignOnMessage) myobj;
+- int action = msg.getAction();
+- Session session = null;
+-
+- if (log.isDebugEnabled())
+- log.debug("SingleSignOnMessage Received with action "
+- + msg.getAction());
+-
+- switch(action) {
+- case SingleSignOnMessage.ADD_SESSION:
+- session = getSession(msg.getSessionId(),
+- msg.getContextName());
+- if (session != null)
+- clusterSSO.associateLocal(msg.getSsoId(), session);
+- break;
+- case SingleSignOnMessage.DEREGISTER_SESSION:
+- session = getSession(msg.getSessionId(),
+- msg.getContextName());
+- if (session != null)
+- clusterSSO.deregisterLocal(msg.getSsoId(), session);
+- break;
+- case SingleSignOnMessage.LOGOUT_SESSION:
+- clusterSSO.deregisterLocal(msg.getSsoId());
+- break;
+- case SingleSignOnMessage.REGISTER_SESSION:
+- clusterSSO.registerLocal(msg.getSsoId(), null, msg.getAuthType(),
+- msg.getUsername(), msg.getPassword());
+- break;
+- case SingleSignOnMessage.UPDATE_SESSION:
+- clusterSSO.updateLocal(msg.getSsoId(), null, msg.getAuthType(),
+- msg.getUsername(), msg.getPassword());
+- break;
+- case SingleSignOnMessage.REMOVE_SESSION:
+- session = getSession(msg.getSessionId(),
+- msg.getContextName());
+- if (session != null)
+- clusterSSO.removeSessionLocal(msg.getSsoId(), session);
+- break;
+- }
+- }
+- }
+-
+- /**
+- * Accept only SingleSignOnMessage
+- *
+- * @param msg
+- * ClusterMessage
+- * @return boolean - returns true to indicate that messageReceived should be
+- * invoked. If false is returned, the messageReceived method will
+- * not be invoked.
+- */
+- public boolean accept(ClusterMessage msg) {
+- return (msg instanceof SingleSignOnMessage);
+- }
+-
+-
+- private Session getSession(String sessionId, String ctxname) {
+-
+- Map managers = clusterSSO.getCluster().getManagers() ;
+- Session session = null;
+-
+- if (ctxname == null) {
+- java.util.Iterator i = managers.keySet().iterator();
+- while (i.hasNext()) {
+- String key = (String) i.next();
+- ClusterManager mgr = (ClusterManager) managers.get(key);
+- if (mgr != null) {
+- try {
+- session = mgr.findSession(sessionId);
+- } catch (IOException io) {
+- log.error("Session doesn't exist:" + io);
+- }
+- return session;
+- } else {
+- //this happens a lot before the system has started
+- // up
+- if (log.isDebugEnabled())
+- log.debug("Context manager doesn't exist:"
+- + key);
+- }
+- }
+- } else {
+- ClusterManager mgr = (ClusterManager) managers.get(ctxname);
+- if (mgr != null) {
+- try {
+- session = mgr.findSession(sessionId);
+- } catch (IOException io) {
+- log.error("Session doesn't exist:" + io);
+- }
+- return session;
+- } else if (log.isErrorEnabled())
+- log.error("Context manager doesn't exist:" + ctxname);
+- }
+-
+- return null;
+- }
+-}
+-
+Index: java/org/apache/catalina/ha/util/IDynamicProperty.java
+===================================================================
+--- java/org/apache/catalina/ha/util/IDynamicProperty.java (revision 590752)
++++ java/org/apache/catalina/ha/util/IDynamicProperty.java (working copy)
+@@ -1,58 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.ha.util;
+-
+-import java.util.Iterator;
+-
+-/**
+- * @author Peter Rossbach
+- * @version $Revision$, $Date$
+- */
+-
+-public interface IDynamicProperty {
+-
+- /**
+- * set config attributes with reflect
+- *
+- * @param name
+- * @param value
+- */
+- public boolean setProperty(String name, Object value) ;
+-
+- /**
+- * get current config
+- *
+- * @param key
+- * @return The property
+- */
+- public Object getProperty(String key) ;
+- /**
+- * Get all properties keys
+- *
+- * @return An iterator over the property names
+- */
+- public Iterator getPropertyNames() ;
+-
+- /**
+- * remove a configured property.
+- *
+- * @param key
+- */
+- public void removeProperty(String key) ;
+-
+-}
+Index: java/org/apache/catalina/tribes/Heartbeat.java
+===================================================================
+--- java/org/apache/catalina/tribes/Heartbeat.java (revision 590752)
++++ java/org/apache/catalina/tribes/Heartbeat.java (working copy)
+@@ -1,34 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes;
+-
+-/**
+- * Can be implemented by the ChannelListener and Membership listeners to receive heartbeat
+- * notifications from the Channel
+- * @author Filip Hanik
+- * @version 1.0
+- * @see Channel
+- * @see Channel#heartbeat()
+- */
+-public interface Heartbeat {
+-
+- /**
+- * Heartbeat invokation for resources cleanup etc
+- */
+- public void heartbeat();
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/MembershipListener.java
+===================================================================
+--- java/org/apache/catalina/tribes/MembershipListener.java (revision 590752)
++++ java/org/apache/catalina/tribes/MembershipListener.java (working copy)
+@@ -1,45 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes;
+-
+-/**
+- * The MembershipListener interface is used as a callback to the
+- * membership service. It has two methods that will notify the listener
+- * when a member has joined the group and when a member has disappeared (crashed)
+- *
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-
+-
+-public interface MembershipListener {
+- /**
+- * A member was added to the group
+- * @param member Member - the member that was added
+- */
+- public void memberAdded(Member member);
+-
+- /**
+- * A member was removed from the group<br>
+- * If the member left voluntarily, the Member.getCommand will contain the Member.SHUTDOWN_PAYLOAD data
+- * @param member Member
+- * @see Member#SHUTDOWN_PAYLOAD
+- */
+- public void memberDisappeared(Member member);
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/Member.java
+===================================================================
+--- java/org/apache/catalina/tribes/Member.java (revision 590752)
++++ java/org/apache/catalina/tribes/Member.java (working copy)
+@@ -1,119 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes;
+-
+-/**
+- * The Member interface, defines a member in the group.
+- * Each member can carry a set of properties, defined by the actual implementation.<BR>
+- * A member is identified by the host/ip/uniqueId<br>
+- * The host is what interface the member is listening to, to receive data<br>
+- * The port is what port the member is listening to, to receive data<br>
+- * The uniqueId defines the session id for the member. This is an important feature
+- * since a member that has crashed and the starts up again on the same port/host is
+- * not guaranteed to be the same member, so no state transfers will ever be confused
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-
+-
+-public interface Member {
+-
+- /**
+- * When a member leaves the cluster, the payload of the memberDisappeared member
+- * will be the following bytes. This indicates a soft shutdown, and not a crash
+- */
+- public static final byte[] SHUTDOWN_PAYLOAD = new byte[] {66, 65, 66, 89, 45, 65, 76, 69, 88};
+-
+- /**
+- * Returns the name of this node, should be unique within the group.
+- */
+- public String getName();
+-
+- /**
+- * Returns the listen host for the ChannelReceiver implementation
+- * @return IPv4 or IPv6 representation of the host address this member listens to incoming data
+- * @see ChannelReceiver
+- */
+- public byte[] getHost();
+-
+- /**
+- * Returns the listen port for the ChannelReceiver implementation
+- * @return the listen port for this member, -1 if its not listening on an unsecure port
+- * @see ChannelReceiver
+- */
+- public int getPort();
+-
+- /**
+- * Returns the secure listen port for the ChannelReceiver implementation.
+- * Returns -1 if its not listening to a secure port.
+- * @return the listen port for this member, -1 if its not listening on a secure port
+- * @see ChannelReceiver
+- */
+- public int getSecurePort();
+-
+-
+- /**
+- * Contains information on how long this member has been online.
+- * The result is the number of milli seconds this member has been
+- * broadcasting its membership to the group.
+- * @return nr of milliseconds since this member started.
+- */
+- public long getMemberAliveTime();
+-
+- /**
+- * The current state of the member
+- * @return boolean - true if the member is functioning correctly
+- */
+- public boolean isReady();
+- /**
+- * The current state of the member
+- * @return boolean - true if the member is suspect, but the crash has not been confirmed
+- */
+- public boolean isSuspect();
+-
+- /**
+- *
+- * @return boolean - true if the member has been confirmed to malfunction
+- */
+- public boolean isFailing();
+-
+- /**
+- * returns a UUID unique for this member over all sessions.
+- * If the member crashes and restarts, the uniqueId will be different.
+- * @return byte[]
+- */
+- public byte[] getUniqueId();
+-
+- /**
+- * returns the payload associated with this member
+- * @return byte[]
+- */
+- public byte[] getPayload();
+-
+- /**
+- * returns the command associated with this member
+- * @return byte[]
+- */
+- public byte[] getCommand();
+-
+- /**
+- * Domain for this cluster
+- * @return byte[]
+- */
+- public byte[] getDomain();
+-}
+Index: java/org/apache/catalina/tribes/ByteMessage.java
+===================================================================
+--- java/org/apache/catalina/tribes/ByteMessage.java (revision 590752)
++++ java/org/apache/catalina/tribes/ByteMessage.java (working copy)
+@@ -1,102 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes;
+-
+-import java.io.Serializable;
+-import java.io.Externalizable;
+-import java.io.ObjectInput;
+-import java.io.IOException;
+-import java.io.ObjectOutput;
+-
+-/**
+- * A byte message is not serialized and deserialized by the channel
+- * instead it is sent as a byte array<br>
+- * By default Tribes uses java serialization when it receives an object
+- * to be sent over the wire. Java serialization is not the most
+- * efficient of serializing data, and Tribes might not even
+- * have access to the correct class loaders to deserialize the object properly.
+- * <br>
+- * The ByteMessage class is a class where the channel when it receives it will
+- * not attempt to perform serialization, instead it will simply stream the <code>getMessage()</code>
+- * bytes.<br>
+- * If you are using multiple applications on top of Tribes you should add some sort of header
+- * so that you can decide with the <code>ChannelListener.accept()</code> whether this message was intended
+- * for you.
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-
+-public class ByteMessage implements Serializable, Externalizable {
+- /**
+- * Storage for the message to be sent
+- */
+- private byte[] message;
+-
+-
+- /**
+- * Creates an empty byte message
+- * Constructor also for deserialization
+- */
+- public ByteMessage() {
+- }
+-
+- /**
+- * Creates a byte message wit h
+- * @param data byte[] - the message contents
+- */
+- public ByteMessage(byte[] data) {
+- message = data;
+- }
+-
+- /**
+- * Returns the message contents of this byte message
+- * @return byte[] - message contents, can be null
+- */
+- public byte[] getMessage() {
+- return message;
+- }
+-
+- /**
+- * Sets the message contents of this byte message
+- * @param message byte[]
+- */
+- public void setMessage(byte[] message) {
+- this.message = message;
+- }
+-
+- /**
+- * @see java.io.Externalizable#readExternal
+- * @param in ObjectInput
+- * @throws IOException
+- */
+- public void readExternal(ObjectInput in ) throws IOException {
+- int length = in.readInt();
+- message = new byte[length];
+- in.read(message,0,length);
+- }
+-
+- /**
+- * @see java.io.Externalizable#writeExternal
+- * @param out ObjectOutput
+- * @throws IOException
+- */
+- public void writeExternal(ObjectOutput out) throws IOException {
+- out.writeInt(message!=null?message.length:0);
+- if ( message!=null ) out.write(message,0,message.length);
+- }
+-
+-}
+Index: java/org/apache/catalina/tribes/Channel.java
+===================================================================
+--- java/org/apache/catalina/tribes/Channel.java (revision 590752)
++++ java/org/apache/catalina/tribes/Channel.java (working copy)
+@@ -1,347 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes;
+-
+-import java.io.Serializable;
+-
+-/**
+- * Channel interface<br>
+- * A channel is a representation of a group of nodes all participating in some sort of
+- * communication with each other.<br>
+- * The channel is the main API class for Tribes, this is essentially the only class
+- * that an application needs to be aware of. Through the channel the application can:<br>
+- * 1. send messages<br>
+- * 2. receive message (by registering a <code>ChannelListener</code><br>
+- * 3. get all members of the group <code>getMembers()</code><br>
+- * 4. receive notifications of members added and members disappeared by
+- * registerering a <code>MembershipListener</code><br>
+- * <br>
+- * The channel has 5 major components:<br>
+- * 1. Data receiver, with a built in thread pool to receive messages from other peers<br>
+- * 2. Data sender, an implementation for sending data using NIO or java.io<br>
+- * 3. Membership listener,listens for membership broadcasts<br>
+- * 4. Membership broadcaster, broadcasts membership pings.<br>
+- * 5. Channel interceptors, the ability to manipulate messages as they are sent or arrive<br><br>
+- * The channel layout is:
+- * <pre><code>
+- * ChannelListener_1..ChannelListener_N MembershipListener_1..MembershipListener_N [Application Layer]
+- * \ \ / /
+- * \ \ / /
+- * \ \ / /
+- * \ \ / /
+- * \ \ / /
+- * \ \ / /
+- * ---------------------------------------
+- * |
+- * |
+- * Channel
+- * |
+- * ChannelInterceptor_1
+- * | [Channel stack]
+- * ChannelInterceptor_N
+- * |
+- * Coordinator (implements MessageListener,MembershipListener,ChannelInterceptor)
+- * --------------------
+- * / | \
+- * / | \
+- * / | \
+- * / | \
+- * / | \
+- * MembershipService ChannelSender ChannelReceiver [IO layer]
+- * </code></pre>
+- *
+- * For example usage @see org.apache.catalina.tribes.group.GroupChannel
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-public interface Channel {
+-
+- /**
+- * Start and stop sequences can be controlled by these constants
+- * This allows you to start separate components of the channel <br>
+- * DEFAULT - starts or stops all components in the channel
+- * @see #start(int)
+- * @see #stop(int)
+- */
+- public static final int DEFAULT = 15;
+-
+- /**
+- * Start and stop sequences can be controlled by these constants
+- * This allows you to start separate components of the channel <br>
+- * SND_RX_SEQ - starts or stops the data receiver. Start means opening a server socket
+- * in case of a TCP implementation
+- * @see #start(int)
+- * @see #stop(int)
+- */
+- public static final int SND_RX_SEQ = 1;
+-
+- /**
+- * Start and stop sequences can be controlled by these constants
+- * This allows you to start separate components of the channel <br>
+- * SND_TX_SEQ - starts or stops the data sender. This should not open any sockets,
+- * as sockets are opened on demand when a message is being sent
+- * @see #start(int)
+- * @see #stop(int)
+- */
+- public static final int SND_TX_SEQ = 2;
+-
+- /**
+- * Start and stop sequences can be controlled by these constants
+- * This allows you to start separate components of the channel <br>
+- * MBR_RX_SEQ - starts or stops the membership listener. In a multicast implementation
+- * this will open a datagram socket and join a group and listen for membership messages
+- * members joining
+- * @see #start(int)
+- * @see #stop(int)
+- */
+- public static final int MBR_RX_SEQ = 4;
+-
+- /**
+- * Start and stop sequences can be controlled by these constants
+- * This allows you to start separate components of the channel <br>
+- * MBR_TX_SEQ - starts or stops the membership broadcaster. In a multicast implementation
+- * this will open a datagram socket and join a group and broadcast the local member information
+- * @see #start(int)
+- * @see #stop(int)
+- */
+- public static final int MBR_TX_SEQ = 8;
+-
+- /**
+- * Send options, when a message is sent, it can have an option flag
+- * to trigger certain behavior. Most flags are used to trigger channel interceptors
+- * as the message passes through the channel stack. <br>
+- * However, there are five default flags that every channel implementation must implement<br>
+- * SEND_OPTIONS_BYTE_MESSAGE - The message is a pure byte message and no marshalling or unmarshalling will
+- * be performed.<br>
+- *
+- * @see #send(Member[], Serializable , int)
+- * @see #send(Member[], Serializable, int, ErrorHandler)
+- */
+- public static final int SEND_OPTIONS_BYTE_MESSAGE = 0x0001;
+-
+- /**
+- * Send options, when a message is sent, it can have an option flag
+- * to trigger certain behavior. Most flags are used to trigger channel interceptors
+- * as the message passes through the channel stack. <br>
+- * However, there are five default flags that every channel implementation must implement<br>
+- * SEND_OPTIONS_USE_ACK - Message is sent and an ACK is received when the message has been received by the recipient<br>
+- * If no ack is received, the message is not considered successful<br>
+- * @see #send(Member[], Serializable , int)
+- * @see #send(Member[], Serializable, int, ErrorHandler)
+- */
+- public static final int SEND_OPTIONS_USE_ACK = 0x0002;
+-
+- /**
+- * Send options, when a message is sent, it can have an option flag
+- * to trigger certain behavior. Most flags are used to trigger channel interceptors
+- * as the message passes through the channel stack. <br>
+- * However, there are five default flags that every channel implementation must implement<br>
+- * SEND_OPTIONS_SYNCHRONIZED_ACK - Message is sent and an ACK is received when the message has been received and
+- * processed by the recipient<br>
+- * If no ack is received, the message is not considered successful<br>
+- * @see #send(Member[], Serializable , int)
+- * @see #send(Member[], Serializable, int, ErrorHandler)
+- */
+- public static final int SEND_OPTIONS_SYNCHRONIZED_ACK = 0x0004;
+-
+- /**
+- * Send options, when a message is sent, it can have an option flag
+- * to trigger certain behavior. Most flags are used to trigger channel interceptors
+- * as the message passes through the channel stack. <br>
+- * However, there are five default flags that every channel implementation must implement<br>
+- * SEND_OPTIONS_ASYNCHRONOUS - Message is sent and an ACK is received when the message has been received and
+- * processed by the recipient<br>
+- * If no ack is received, the message is not considered successful<br>
+- * @see #send(Member[], Serializable , int)
+- * @see #send(Member[], Serializable, int, ErrorHandler)
+- */
+- public static final int SEND_OPTIONS_ASYNCHRONOUS = 0x0008;
+-
+- /**
+- * Send options, when a message is sent, it can have an option flag
+- * to trigger certain behavior. Most flags are used to trigger channel interceptors
+- * as the message passes through the channel stack. <br>
+- * However, there are five default flags that every channel implementation must implement<br>
+- * SEND_OPTIONS_SECURE - Message is sent over an encrypted channel<br>
+- * @see #send(Member[], Serializable , int)
+- * @see #send(Member[], Serializable, int, ErrorHandler)
+- */
+- public static final int SEND_OPTIONS_SECURE = 0x0010;
+-
+-
+- /**
+- * Send options, when a message is sent, it can have an option flag
+- * to trigger certain behavior. Most flags are used to trigger channel interceptors
+- * as the message passes through the channel stack. <br>
+- * However, there are five default flags that every channel implementation must implement<br>
+- * SEND_OPTIONS_DEFAULT - the default sending options, just a helper variable. <br>
+- * The default is <code>int SEND_OPTIONS_DEFAULT = SEND_OPTIONS_USE_ACK;</code><br>
+- * @see #SEND_OPTIONS_USE_ACK
+- * @see #send(Member[], Serializable , int)
+- * @see #send(Member[], Serializable, int, ErrorHandler)
+- */
+- public static final int SEND_OPTIONS_DEFAULT = SEND_OPTIONS_USE_ACK;
+-
+-
+- /**
+- * Adds an interceptor to the channel message chain.
+- * @param interceptor ChannelInterceptor
+- */
+- public void addInterceptor(ChannelInterceptor interceptor);
+-
+- /**
+- * Starts up the channel. This can be called multiple times for individual services to start
+- * The svc parameter can be the logical or value of any constants
+- * @param svc int value of <BR>
+- * DEFAULT - will start all services <BR>
+- * MBR_RX_SEQ - starts the membership receiver <BR>
+- * MBR_TX_SEQ - starts the membership broadcaster <BR>
+- * SND_TX_SEQ - starts the replication transmitter<BR>
+- * SND_RX_SEQ - starts the replication receiver<BR>
+- * <b>Note:</b> In order for the membership broadcaster to
+- * transmit the correct information, it has to be started after the replication receiver.
+- * @throws ChannelException if a startup error occurs or the service is already started or an error occurs.
+- */
+- public void start(int svc) throws ChannelException;
+-
+- /**
+- * Shuts down the channel. This can be called multiple times for individual services to shutdown
+- * The svc parameter can be the logical or value of any constants
+- * @param svc int value of <BR>
+- * DEFAULT - will shutdown all services <BR>
+- * MBR_RX_SEQ - stops the membership receiver <BR>
+- * MBR_TX_SEQ - stops the membership broadcaster <BR>
+- * SND_TX_SEQ - stops the replication transmitter<BR>
+- * SND_RX_SEQ - stops the replication receiver<BR>
+- * @throws ChannelException if a startup error occurs or the service is already stopped or an error occurs.
+- */
+- public void stop(int svc) throws ChannelException;
+-
+- /**
+- * Send a message to one or more members in the cluster
+- * @param destination Member[] - the destinations, can not be null or zero length, the reason for that
+- * is that a membership change can occur and at that time the application is uncertain what group the message
+- * actually got sent to.
+- * @param msg Serializable - the message to send, has to be serializable, or a <code>ByteMessage</code> to
+- * send a pure byte array
+- * @param options int - sender options, see class documentation for each interceptor that is configured in order to trigger interceptors
+- * @return a unique Id that identifies the message that is sent
+- * @see ByteMessage
+- * @see #SEND_OPTIONS_USE_ACK
+- * @see #SEND_OPTIONS_ASYNCHRONOUS
+- * @see #SEND_OPTIONS_SYNCHRONIZED_ACK
+- */
+- public UniqueId send(Member[] destination, Serializable msg, int options) throws ChannelException;
+-
+- /**
+- * Send a message to one or more members in the cluster
+- * @param destination Member[] - the destinations, null or zero length means all
+- * @param msg ClusterMessage - the message to send
+- * @param options int - sender options, see class documentation
+- * @param handler ErrorHandler - handle errors through a callback, rather than throw it
+- * @return a unique Id that identifies the message that is sent
+- * @exception ChannelException - if a serialization error happens.
+- */
+- public UniqueId send(Member[] destination, Serializable msg, int options, ErrorHandler handler) throws ChannelException;
+-
+- /**
+- * Sends a heart beat through the interceptor stacks
+- * Use this method to alert interceptors and other components to
+- * clean up garbage, timed out messages etc.<br>
+- * If you application has a background thread, then you can save one thread,
+- * by configuring your channel to not use an internal heartbeat thread
+- * and invoking this method.
+- * @see #setHeartbeat(boolean)
+- */
+- public void heartbeat();
+-
+- /**
+- * Enables or disables internal heartbeat.
+- * @param enable boolean - default value is implementation specific
+- * @see #heartbeat()
+- */
+- public void setHeartbeat(boolean enable);
+-
+- /**
+- * Add a membership listener, will get notified when a new member joins, leaves or crashes
+- * <br>If the membership listener implements the Heartbeat interface
+- * the <code>heartbeat()</code> method will be invoked when the heartbeat runs on the channel
+- * @param listener MembershipListener
+- * @see MembershipListener
+- */
+- public void addMembershipListener(MembershipListener listener);
+-
+- /**
+- * Add a channel listener, this is a callback object when messages are received
+- * <br>If the channel listener implements the Heartbeat interface
+- * the <code>heartbeat()</code> method will be invoked when the heartbeat runs on the channel
+- * @param listener ChannelListener
+- * @see ChannelListener
+- * @see Heartbeat
+- */
+- public void addChannelListener(ChannelListener listener);
+-
+- /**
+- * remove a membership listener, listeners are removed based on Object.hashCode and Object.equals
+- * @param listener MembershipListener
+- * @see MembershipListener
+- */
+- public void removeMembershipListener(MembershipListener listener);
+- /**
+- * remove a channel listener, listeners are removed based on Object.hashCode and Object.equals
+- * @param listener ChannelListener
+- * @see ChannelListener
+- */
+- public void removeChannelListener(ChannelListener listener);
+-
+- /**
+- * Returns true if there are any members in the group,
+- * this call is the same as <code>getMembers().length>0</code>
+- * @return boolean - true if there are any members automatically discovered
+- */
+- public boolean hasMembers() ;
+-
+- /**
+- * Get all current group members
+- * @return all members or empty array, never null
+- */
+- public Member[] getMembers() ;
+-
+- /**
+- * Return the member that represents this node. This is also the data
+- * that gets broadcasted through the membership broadcaster component
+- * @param incAlive - optimization, true if you want it to calculate alive time
+- * since the membership service started.
+- * @return Member
+- */
+- public Member getLocalMember(boolean incAlive);
+-
+- /**
+- * Returns the member from the membership service with complete and
+- * recent data. Some implementations might serialize and send
+- * membership information along with a message, and instead of sending
+- * complete membership details, only send the primary identifier for the member
+- * but not the payload or other information. When such message is received
+- * the application can retrieve the cached member through this call.<br>
+- * In most cases, this is not necessary.
+- * @param mbr Member
+- * @return Member
+- */
+- public Member getMember(Member mbr);
+-
+-
+-}
+Index: java/org/apache/catalina/tribes/tipis/Streamable.java
+===================================================================
+--- java/org/apache/catalina/tribes/tipis/Streamable.java (revision 590752)
++++ java/org/apache/catalina/tribes/tipis/Streamable.java (working copy)
+@@ -1,61 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.tipis;
+-
+-import java.io.IOException;
+-
+-/**
+- * Example usage:
+- * <code><pre>
+- * byte[] data = new byte[1024];
+- * Streamable st = ....;
+- * while ( !st.eof() ) {
+- * int length = st.read(data,0,data.length);
+- * String s = new String(data,0,length);
+- * System.out.println(s);
+- * }
+- * </pre></code>
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public interface Streamable {
+-
+- /**
+- * returns true if the stream has reached its end
+- * @return boolean
+- */
+- public boolean eof();
+-
+- /**
+- * write data into the byte array starting at offset, maximum bytes read are (data.length-offset)
+- * @param data byte[] - the array to read data into
+- * @param offset int - start position for writing data
+- * @return int - the number of bytes written into the data buffer
+- */
+- public int write(byte[] data, int offset, int length) throws IOException;
+-
+- /**
+- * read data into the byte array starting at offset
+- * @param data byte[] - the array to read data into
+- * @param offset int - start position for writing data
+- * @param length - the desired read length
+- * @return int - the number of bytes read from the data buffer
+- */
+- public int read(byte[] data, int offset, int length) throws IOException;
+-
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/tipis/LazyReplicatedMap.java
+===================================================================
+--- java/org/apache/catalina/tribes/tipis/LazyReplicatedMap.java (revision 590752)
++++ java/org/apache/catalina/tribes/tipis/LazyReplicatedMap.java (working copy)
+@@ -1,190 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.tipis;
+-
+-import java.io.Serializable;
+-
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.ChannelListener;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.MembershipListener;
+-import org.apache.catalina.tribes.group.RpcCallback;
+-import org.apache.catalina.tribes.util.Arrays;
+-import org.apache.catalina.tribes.UniqueId;
+-import org.apache.catalina.tribes.tipis.AbstractReplicatedMap.MapOwner;
+-
+-/**
+- * A smart implementation of a stateful replicated map. uses primary/secondary backup strategy.
+- * One node is always the primary and one node is always the backup.
+- * This map is synchronized across a cluster, and only has one backup member.<br/>
+- * A perfect usage for this map would be a session map for a session manager in a clustered environment.<br/>
+- * The only way to modify this list is to use the <code>put, putAll, remove</code> methods.
+- * entrySet, entrySetFull, keySet, keySetFull, returns all non modifiable sets.<br><br>
+- * If objects (values) in the map change without invoking <code>put()</code> or <code>remove()</code>
+- * the data can be distributed using two different methods:<br>
+- * <code>replicate(boolean)</code> and <code>replicate(Object, boolean)</code><br>
+- * These two methods are very important two understand. The map can work with two set of value objects:<br>
+- * 1. Serializable - the entire object gets serialized each time it is replicated<br>
+- * 2. ReplicatedMapEntry - this interface allows for a isDirty() flag and to replicate diffs if desired.<br>
+- * Implementing the <code>ReplicatedMapEntry</code> interface allows you to decide what objects
+- * get replicated and how much data gets replicated each time.<br>
+- * If you implement a smart AOP mechanism to detect changes in underlying objects, you can replicate
+- * only those changes by implementing the ReplicatedMapEntry interface, and return true when isDiffable()
+- * is invoked.<br><br>
+- *
+- * This map implementation doesn't have a background thread running to replicate changes.
+- * If you do have changes without invoking put/remove then you need to invoke one of the following methods:
+- * <ul>
+- * <li><code>replicate(Object,boolean)</code> - replicates only the object that belongs to the key</li>
+- * <li><code>replicate(boolean)</code> - Scans the entire map for changes and replicates data</li>
+- * </ul>
+- * the <code>boolean</code> value in the <code>replicate</code> method used to decide
+- * whether to only replicate objects that implement the <code>ReplicatedMapEntry</code> interface
+- * or to replicate all objects. If an object doesn't implement the <code>ReplicatedMapEntry</code> interface
+- * each time the object gets replicated the entire object gets serialized, hence a call to <code>replicate(true)</code>
+- * will replicate all objects in this map that are using this node as primary.
+- *
+- * <br><br><b>REMBER TO CALL <code>breakdown()</code> or <code>finalize()</code> when you are done with the map to
+- * avoid memory leaks.<br><br>
+- * @todo implement periodic sync/transfer thread
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public class LazyReplicatedMap extends AbstractReplicatedMap
+- implements RpcCallback, ChannelListener, MembershipListener {
+- protected static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(LazyReplicatedMap.class);
+-
+-
+-
+-//------------------------------------------------------------------------------
+-// CONSTRUCTORS / DESTRUCTORS
+-//------------------------------------------------------------------------------
+- /**
+- * Creates a new map
+- * @param channel The channel to use for communication
+- * @param timeout long - timeout for RPC messags
+- * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+- * @param initialCapacity int - the size of this map, see HashMap
+- * @param loadFactor float - load factor, see HashMap
+- */
+- public LazyReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, int initialCapacity, float loadFactor, ClassLoader[] cls) {
+- super(owner,channel,timeout,mapContextName,initialCapacity,loadFactor, Channel.SEND_OPTIONS_DEFAULT,cls);
+- }
+-
+- /**
+- * Creates a new map
+- * @param channel The channel to use for communication
+- * @param timeout long - timeout for RPC messags
+- * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+- * @param initialCapacity int - the size of this map, see HashMap
+- */
+- public LazyReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, int initialCapacity, ClassLoader[] cls) {
+- super(owner, channel,timeout,mapContextName,initialCapacity, LazyReplicatedMap.DEFAULT_LOAD_FACTOR, Channel.SEND_OPTIONS_DEFAULT, cls);
+- }
+-
+- /**
+- * Creates a new map
+- * @param channel The channel to use for communication
+- * @param timeout long - timeout for RPC messags
+- * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+- */
+- public LazyReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, ClassLoader[] cls) {
+- super(owner, channel,timeout,mapContextName, LazyReplicatedMap.DEFAULT_INITIAL_CAPACITY,LazyReplicatedMap.DEFAULT_LOAD_FACTOR,Channel.SEND_OPTIONS_DEFAULT, cls);
+- }
+-
+-
+-
+-
+-
+-//------------------------------------------------------------------------------
+-// METHODS TO OVERRIDE
+-//------------------------------------------------------------------------------
+- protected int getStateMessageType() {
+- return AbstractReplicatedMap.MapMessage.MSG_STATE;
+- }
+-
+- /**
+- * publish info about a map pair (key/value) to other nodes in the cluster
+- * @param key Object
+- * @param value Object
+- * @return Member - the backup node
+- * @throws ChannelException
+- */
+- protected Member[] publishEntryInfo(Object key, Object value) throws ChannelException {
+- if (! (key instanceof Serializable && value instanceof Serializable) ) return new Member[0];
+- Member[] members = getMapMembers();
+- int firstIdx = getNextBackupIndex();
+- int nextIdx = firstIdx;
+- Member[] backup = new Member[0];
+-
+- //there are no backups
+- if ( members.length == 0 || firstIdx == -1 ) return backup;
+-
+- boolean success = false;
+- do {
+- //select a backup node
+- Member next = members[nextIdx];
+-
+- //increment for the next round of back up selection
+- nextIdx = nextIdx + 1;
+- if ( nextIdx >= members.length ) nextIdx = 0;
+-
+- if (next == null) {
+- continue;
+- }
+- MapMessage msg = null;
+- try {
+- backup = wrap(next);
+- //publish the backup data to one node
+- msg = new MapMessage(getMapContextName(), MapMessage.MSG_BACKUP, false,
+- (Serializable) key, (Serializable) value, null, channel.getLocalMember(false), backup);
+- if ( log.isTraceEnabled() )
+- log.trace("Publishing backup data:"+msg+" to: "+next.getName());
+- UniqueId id = getChannel().send(backup, msg, getChannelSendOptions());
+- if ( log.isTraceEnabled() )
+- log.trace("Data published:"+msg+" msg Id:"+id);
+- //we published out to a backup, mark the test success
+- success = true;
+- }catch ( ChannelException x ) {
+- log.error("Unable to replicate backup key:"+key+" to backup:"+next+". Reason:"+x.getMessage(),x);
+- }
+- try {
+- //publish the data out to all nodes
+- Member[] proxies = excludeFromSet(backup, getMapMembers());
+- if (success && proxies.length > 0 ) {
+- msg = new MapMessage(getMapContextName(), MapMessage.MSG_PROXY, false,
+- (Serializable) key, null, null, channel.getLocalMember(false),backup);
+- if ( log.isTraceEnabled() )
+- log.trace("Publishing proxy data:"+msg+" to: "+Arrays.toNameString(proxies));
+- getChannel().send(proxies, msg, getChannelSendOptions());
+- }
+- }catch ( ChannelException x ) {
+- //log the error, but proceed, this should only happen if a node went down,
+- //and if the node went down, then it can't receive the message, the others
+- //should still get it.
+- log.error("Unable to replicate proxy key:"+key+" to backup:"+next+". Reason:"+x.getMessage(),x);
+- }
+- } while ( !success && (firstIdx!=nextIdx));
+- return backup;
+- }
+-
+-
+-
+-
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/tipis/ReplicatedMap.java
+===================================================================
+--- java/org/apache/catalina/tribes/tipis/ReplicatedMap.java (revision 590752)
++++ java/org/apache/catalina/tribes/tipis/ReplicatedMap.java (working copy)
+@@ -1,123 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.tipis;
+-
+-import java.io.Serializable;
+-
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.ChannelListener;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.MembershipListener;
+-import org.apache.catalina.tribes.group.RpcCallback;
+-import org.apache.catalina.tribes.tipis.AbstractReplicatedMap.MapOwner;
+-
+-/**
+- * All-to-all replication for a hash map implementation. Each node in the cluster will carry an identical
+- * copy of the map.<br><br>
+- * This map implementation doesn't have a background thread running to replicate changes.
+- * If you do have changes without invoking put/remove then you need to invoke one of the following methods:
+- * <ul>
+- * <li><code>replicate(Object,boolean)</code> - replicates only the object that belongs to the key</li>
+- * <li><code>replicate(boolean)</code> - Scans the entire map for changes and replicates data</li>
+- * </ul>
+- * the <code>boolean</code> value in the <code>replicate</code> method used to decide
+- * whether to only replicate objects that implement the <code>ReplicatedMapEntry</code> interface
+- * or to replicate all objects. If an object doesn't implement the <code>ReplicatedMapEntry</code> interface
+- * each time the object gets replicated the entire object gets serialized, hence a call to <code>replicate(true)</code>
+- * will replicate all objects in this map that are using this node as primary.
+- *
+- * <br><br><b>REMBER TO CALL <code>breakdown()</code> or <code>finalize()</code> when you are done with the map to
+- * avoid memory leaks.<br><br>
+- * @todo implement periodic sync/transfer thread
+- * @author Filip Hanik
+- * @version 1.0
+- *
+- * @todo memberDisappeared, should do nothing except change map membership
+- * by default it relocates the primary objects
+- */
+-public class ReplicatedMap extends AbstractReplicatedMap implements RpcCallback, ChannelListener, MembershipListener {
+-
+- protected static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(ReplicatedMap.class);
+-
+-//------------------------------------------------------------------------------
+-// CONSTRUCTORS / DESTRUCTORS
+-//------------------------------------------------------------------------------
+- /**
+- * Creates a new map
+- * @param channel The channel to use for communication
+- * @param timeout long - timeout for RPC messags
+- * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+- * @param initialCapacity int - the size of this map, see HashMap
+- * @param loadFactor float - load factor, see HashMap
+- */
+- public ReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, int initialCapacity,float loadFactor, ClassLoader[] cls) {
+- super(owner,channel, timeout, mapContextName, initialCapacity, loadFactor, Channel.SEND_OPTIONS_DEFAULT, cls);
+- }
+-
+- /**
+- * Creates a new map
+- * @param channel The channel to use for communication
+- * @param timeout long - timeout for RPC messags
+- * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+- * @param initialCapacity int - the size of this map, see HashMap
+- */
+- public ReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, int initialCapacity, ClassLoader[] cls) {
+- super(owner,channel, timeout, mapContextName, initialCapacity, AbstractReplicatedMap.DEFAULT_LOAD_FACTOR,Channel.SEND_OPTIONS_DEFAULT, cls);
+- }
+-
+- /**
+- * Creates a new map
+- * @param channel The channel to use for communication
+- * @param timeout long - timeout for RPC messags
+- * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+- */
+- public ReplicatedMap(MapOwner owner, Channel channel, long timeout, String mapContextName, ClassLoader[] cls) {
+- super(owner, channel, timeout, mapContextName,AbstractReplicatedMap.DEFAULT_INITIAL_CAPACITY, AbstractReplicatedMap.DEFAULT_LOAD_FACTOR, Channel.SEND_OPTIONS_DEFAULT, cls);
+- }
+-
+-//------------------------------------------------------------------------------
+-// METHODS TO OVERRIDE
+-//------------------------------------------------------------------------------
+- protected int getStateMessageType() {
+- return AbstractReplicatedMap.MapMessage.MSG_STATE_COPY;
+- }
+-
+- /**
+- * publish info about a map pair (key/value) to other nodes in the cluster
+- * @param key Object
+- * @param value Object
+- * @return Member - the backup node
+- * @throws ChannelException
+- */
+- protected Member[] publishEntryInfo(Object key, Object value) throws ChannelException {
+- if (! (key instanceof Serializable && value instanceof Serializable) ) return new Member[0];
+- //select a backup node
+- Member[] backup = getMapMembers();
+-
+- if (backup == null || backup.length == 0) return null;
+-
+- //publish the data out to all nodes
+- MapMessage msg = new MapMessage(getMapContextName(), MapMessage.MSG_COPY, false,
+- (Serializable) key, (Serializable) value, null,channel.getLocalMember(false), backup);
+-
+- getChannel().send(getMapMembers(), msg, getChannelSendOptions());
+-
+- return backup;
+- }
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/tipis/ReplicatedMapEntry.java
+===================================================================
+--- java/org/apache/catalina/tribes/tipis/ReplicatedMapEntry.java (revision 590752)
++++ java/org/apache/catalina/tribes/tipis/ReplicatedMapEntry.java (working copy)
+@@ -1,124 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.tipis;
+-
+-import java.io.IOException;
+-import java.io.Serializable;
+-
+-/**
+- *
+- * For smarter replication, an object can implement this interface to replicate diffs<br>
+- * The replication logic will call the methods in the following order:<br>
+- * <code>
+- * 1. if ( entry.isDirty() ) <br>
+- * try {
+- * 2. entry.lock();<br>
+- * 3. byte[] diff = entry.getDiff();<br>
+- * 4. entry.reset();<br>
+- * } finally {<br>
+- * 5. entry.unlock();<br>
+- * }<br>
+- * }<br>
+- * </code>
+- * <br>
+- * <br>
+- * When the data is deserialized the logic is called in the following order<br>
+- * <code>
+- * 1. ReplicatedMapEntry entry = (ReplicatedMapEntry)objectIn.readObject();<br>
+- * 2. if ( isBackup(entry)||isPrimary(entry) ) entry.setOwner(owner); <br>
+- * </code>
+- * <br>
+- *
+- *
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public interface ReplicatedMapEntry extends Serializable {
+-
+- /**
+- * Has the object changed since last replication
+- * and is not in a locked state
+- * @return boolean
+- */
+- public boolean isDirty();
+-
+- /**
+- * If this returns true, the map will extract the diff using getDiff()
+- * Otherwise it will serialize the entire object.
+- * @return boolean
+- */
+- public boolean isDiffable();
+-
+- /**
+- * Returns a diff and sets the dirty map to false
+- * @return byte[]
+- * @throws IOException
+- */
+- public byte[] getDiff() throws IOException;
+-
+-
+- /**
+- * Applies a diff to an existing object.
+- * @param diff byte[]
+- * @param offset int
+- * @param length int
+- * @throws IOException
+- */
+- public void applyDiff(byte[] diff, int offset, int length) throws IOException, ClassNotFoundException;
+-
+- /**
+- * Resets the current diff state and resets the dirty flag
+- */
+- public void resetDiff();
+-
+- /**
+- * Lock during serialization
+- */
+- public void lock();
+-
+- /**
+- * Unlock after serialization
+- */
+- public void unlock();
+-
+- /**
+- * This method is called after the object has been
+- * created on a remote map. On this method,
+- * the object can initialize itself for any data that wasn't
+- *
+- * @param owner Object
+- */
+- public void setOwner(Object owner);
+-
+- /**
+- * For accuracy checking, a serialized attribute can contain a version number
+- * This number increases as modifications are made to the data.
+- * The replicated map can use this to ensure accuracy on a periodic basis
+- * @return long - the version number or -1 if the data is not versioned
+- */
+- public long getVersion();
+-
+- /**
+- * Forces a certain version to a replicated map entry<br>
+- * @param version long
+- */
+- public void setVersion(long version);
+-
+-
+-
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/tipis/AbstractReplicatedMap.java
+===================================================================
+--- java/org/apache/catalina/tribes/tipis/AbstractReplicatedMap.java (revision 590752)
++++ java/org/apache/catalina/tribes/tipis/AbstractReplicatedMap.java (working copy)
+@@ -1,1474 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.tipis;
+-
+-import java.io.IOException;
+-import java.io.ObjectInput;
+-import java.io.ObjectOutput;
+-import java.io.Serializable;
+-import java.io.UnsupportedEncodingException;
+-import java.util.ArrayList;
+-import java.util.Collection;
+-import java.util.Collections;
+-import java.util.HashMap;
+-import java.util.Iterator;
+-import java.util.LinkedHashSet;
+-import java.util.Map;
+-import java.util.Set;
+-
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.ChannelListener;
+-import org.apache.catalina.tribes.Heartbeat;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.MembershipListener;
+-import org.apache.catalina.tribes.group.Response;
+-import org.apache.catalina.tribes.group.RpcCallback;
+-import org.apache.catalina.tribes.group.RpcChannel;
+-import org.apache.catalina.tribes.io.XByteBuffer;
+-import org.apache.catalina.tribes.membership.MemberImpl;
+-import org.apache.catalina.tribes.util.Arrays;
+-import org.apache.juli.logging.Log;
+-import org.apache.juli.logging.LogFactory;
+-import java.util.concurrent.ConcurrentHashMap;
+-
+-/**
+- *
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public abstract class AbstractReplicatedMap extends ConcurrentHashMap implements RpcCallback, ChannelListener, MembershipListener, Heartbeat {
+- protected static Log log = LogFactory.getLog(AbstractReplicatedMap.class);
+-
+- /**
+- * The default initial capacity - MUST be a power of two.
+- */
+- public static final int DEFAULT_INITIAL_CAPACITY = 16;
+-
+- /**
+- * The load factor used when none specified in constructor.
+- **/
+- public static final float DEFAULT_LOAD_FACTOR = 0.75f;
+-
+- /**
+- * Used to identify the map
+- */
+- final String chset = "ISO-8859-1";
+-
+-//------------------------------------------------------------------------------
+-// INSTANCE VARIABLES
+-//------------------------------------------------------------------------------
+- protected abstract int getStateMessageType();
+-
+-
+- /**
+- * Timeout for RPC messages, how long we will wait for a reply
+- */
+- protected transient long rpcTimeout = 5000;
+- /**
+- * Reference to the channel for sending messages
+- */
+- protected transient Channel channel;
+- /**
+- * The RpcChannel to send RPC messages through
+- */
+- protected transient RpcChannel rpcChannel;
+- /**
+- * The Map context name makes this map unique, this
+- * allows us to have more than one map shared
+- * through one channel
+- */
+- protected transient byte[] mapContextName;
+- /**
+- * Has the state been transferred
+- */
+- protected transient boolean stateTransferred = false;
+- /**
+- * Simple lock object for transfers
+- */
+- protected transient Object stateMutex = new Object();
+- /**
+- * A list of members in our map
+- */
+- protected transient HashMap mapMembers = new HashMap();
+- /**
+- * Our default send options
+- */
+- protected transient int channelSendOptions = Channel.SEND_OPTIONS_DEFAULT;
+- /**
+- * The owner of this map, ala a SessionManager for example
+- */
+- protected transient MapOwner mapOwner;
+- /**
+- * External class loaders if serialization and deserialization is to be performed successfully.
+- */
+- protected transient ClassLoader[] externalLoaders;
+-
+- /**
+- * The node we are currently backing up data to, this index will rotate
+- * on a round robin basis
+- */
+- protected transient int currentNode = 0;
+-
+- /**
+- * Since the map keeps internal membership
+- * this is the timeout for a ping message to be responded to
+- * If a remote map doesn't respond within this timeframe,
+- * its considered dead.
+- */
+- protected transient long accessTimeout = 5000;
+-
+- /**
+- * Readable string of the mapContextName value
+- */
+- protected transient String mapname = "";
+-
+-//------------------------------------------------------------------------------
+-// map owner interface
+-//------------------------------------------------------------------------------
+-
+- public static interface MapOwner {
+- public void objectMadePrimay(Object key, Object value);
+- }
+-
+-//------------------------------------------------------------------------------
+-// CONSTRUCTORS
+-//------------------------------------------------------------------------------
+-
+- /**
+- * Creates a new map
+- * @param channel The channel to use for communication
+- * @param timeout long - timeout for RPC messags
+- * @param mapContextName String - unique name for this map, to allow multiple maps per channel
+- * @param initialCapacity int - the size of this map, see HashMap
+- * @param loadFactor float - load factor, see HashMap
+- * @param cls - a list of classloaders to be used for deserialization of objects.
+- */
+- public AbstractReplicatedMap(MapOwner owner,
+- Channel channel,
+- long timeout,
+- String mapContextName,
+- int initialCapacity,
+- float loadFactor,
+- int channelSendOptions,
+- ClassLoader[] cls) {
+- super(initialCapacity, loadFactor, 15);
+- init(owner, channel, mapContextName, timeout, channelSendOptions, cls);
+-
+- }
+-
+- /**
+- * Helper methods, wraps a single member in an array
+- * @param m Member
+- * @return Member[]
+- */
+- protected Member[] wrap(Member m) {
+- if ( m == null ) return new Member[0];
+- else return new Member[] {m};
+- }
+-
+- /**
+- * Initializes the map by creating the RPC channel, registering itself as a channel listener
+- * This method is also responsible for initiating the state transfer
+- * @param owner Object
+- * @param channel Channel
+- * @param mapContextName String
+- * @param timeout long
+- * @param channelSendOptions int
+- * @param cls ClassLoader[]
+- */
+- protected void init(MapOwner owner, Channel channel, String mapContextName, long timeout, int channelSendOptions,ClassLoader[] cls) {
+- log.info("Initializing AbstractReplicatedMap with context name:"+mapContextName);
+- this.mapOwner = owner;
+- this.externalLoaders = cls;
+- this.channelSendOptions = channelSendOptions;
+- this.channel = channel;
+- this.rpcTimeout = timeout;
+-
+- try {
+- this.mapname = mapContextName;
+- //unique context is more efficient if it is stored as bytes
+- this.mapContextName = mapContextName.getBytes(chset);
+- } catch (UnsupportedEncodingException x) {
+- log.warn("Unable to encode mapContextName[" + mapContextName + "] using getBytes(" + chset +") using default getBytes()", x);
+- this.mapContextName = mapContextName.getBytes();
+- }
+- if ( log.isTraceEnabled() ) log.trace("Created Lazy Map with name:"+mapContextName+", bytes:"+Arrays.toString(this.mapContextName));
+-
+- //create an rpc channel and add the map as a listener
+- this.rpcChannel = new RpcChannel(this.mapContextName, channel, this);
+- //add this map as a message listener
+- this.channel.addChannelListener(this);
+- //listen for membership notifications
+- this.channel.addMembershipListener(this);
+-
+-
+- try {
+- //broadcast our map, this just notifies other members of our existence
+- broadcast(MapMessage.MSG_INIT, true);
+- //transfer state from another map
+- transferState();
+- //state is transferred, we are ready for messaging
+- broadcast(MapMessage.MSG_START, true);
+- } catch (ChannelException x) {
+- log.warn("Unable to send map start message.");
+- throw new RuntimeException("Unable to start replicated map.",x);
+- }
+- }
+-
+-
+- /**
+- * Sends a ping out to all the members in the cluster, not just map members
+- * that this map is alive.
+- * @param timeout long
+- * @throws ChannelException
+- */
+- protected void ping(long timeout) throws ChannelException {
+- //send out a map membership message, only wait for the first reply
+- MapMessage msg = new MapMessage(this.mapContextName,
+- MapMessage.MSG_INIT,
+- false,
+- null,
+- null,
+- null,
+- channel.getLocalMember(false),
+- null);
+- if ( channel.getMembers().length > 0 ) {
+- //send a ping, wait for all nodes to reply
+- Response[] resp = rpcChannel.send(channel.getMembers(),
+- msg, rpcChannel.ALL_REPLY,
+- (channelSendOptions),
+- (int) accessTimeout);
+- for (int i = 0; i < resp.length; i++) {
+- memberAlive(resp[i].getSource());
+- } //for
+- }
+- //update our map of members, expire some if we didn't receive a ping back
+- synchronized (mapMembers) {
+- Iterator it = mapMembers.entrySet().iterator();
+- long now = System.currentTimeMillis();
+- while ( it.hasNext() ) {
+- Map.Entry entry = (Map.Entry)it.next();
+- long access = ((Long)entry.getValue()).longValue();
+- if ( (now - access) > timeout ) {
+- it.remove();
+- memberDisappeared( (Member) entry.getKey());
+- }
+- }
+- }//synch
+- }
+-
+- /**
+- * We have received a member alive notification
+- * @param member Member
+- */
+- protected void memberAlive(Member member) {
+- synchronized (mapMembers) {
+- if (!mapMembers.containsKey(member)) {
+- mapMemberAdded(member);
+- } //end if
+- mapMembers.put(member, new Long(System.currentTimeMillis()));
+- }
+- }
+-
+- /**
+- * Helper method to broadcast a message to all members in a channel
+- * @param msgtype int
+- * @param rpc boolean
+- * @throws ChannelException
+- */
+- protected void broadcast(int msgtype, boolean rpc) throws ChannelException {
+- //send out a map membership message, only wait for the first reply
+- MapMessage msg = new MapMessage(this.mapContextName, msgtype,
+- false, null, null, null, channel.getLocalMember(false), null);
+- if ( rpc) {
+- Response[] resp = rpcChannel.send(channel.getMembers(), msg, rpcChannel.FIRST_REPLY, (channelSendOptions),rpcTimeout);
+- for (int i = 0; i < resp.length; i++) {
+- mapMemberAdded(resp[i].getSource());
+- messageReceived(resp[i].getMessage(), resp[i].getSource());
+- }
+- } else {
+- channel.send(channel.getMembers(),msg,channelSendOptions);
+- }
+- }
+-
+- public void breakdown() {
+- finalize();
+- }
+-
+- public void finalize() {
+- try {broadcast(MapMessage.MSG_STOP,false); }catch ( Exception ignore){}
+- //cleanup
+- if (this.rpcChannel != null) {
+- this.rpcChannel.breakdown();
+- }
+- if (this.channel != null) {
+- this.channel.removeChannelListener(this);
+- this.channel.removeMembershipListener(this);
+- }
+- this.rpcChannel = null;
+- this.channel = null;
+- this.mapMembers.clear();
+- super.clear();
+- this.stateTransferred = false;
+- this.externalLoaders = null;
+- }
+-
+- public int hashCode() {
+- return Arrays.hashCode(this.mapContextName);
+- }
+-
+- public boolean equals(Object o) {
+- if ( o == null ) return false;
+- if ( !(o instanceof AbstractReplicatedMap)) return false;
+- if ( !(o.getClass().equals(this.getClass())) ) return false;
+- AbstractReplicatedMap other = (AbstractReplicatedMap)o;
+- return Arrays.equals(mapContextName,other.mapContextName);
+- }
+-
+-//------------------------------------------------------------------------------
+-// GROUP COM INTERFACES
+-//------------------------------------------------------------------------------
+- public Member[] getMapMembers(HashMap members) {
+- synchronized (members) {
+- Member[] result = new Member[members.size()];
+- members.keySet().toArray(result);
+- return result;
+- }
+- }
+- public Member[] getMapMembers() {
+- return getMapMembers(this.mapMembers);
+- }
+-
+- public Member[] getMapMembersExcl(Member[] exclude) {
+- synchronized (mapMembers) {
+- HashMap list = (HashMap)mapMembers.clone();
+- for (int i=0; i<exclude.length;i++) list.remove(exclude[i]);
+- return getMapMembers(list);
+- }
+- }
+-
+-
+- /**
+- * Replicates any changes to the object since the last time
+- * The object has to be primary, ie, if the object is a proxy or a backup, it will not be replicated<br>
+- * @param complete - if set to true, the object is replicated to its backup
+- * if set to false, only objects that implement ReplicatedMapEntry and the isDirty() returns true will
+- * be replicated
+- */
+- public void replicate(Object key, boolean complete) {
+- if ( log.isTraceEnabled() )
+- log.trace("Replicate invoked on key:"+key);
+- MapEntry entry = (MapEntry)super.get(key);
+- if ( entry == null ) return;
+- if ( !entry.isSerializable() ) return;
+- if (entry != null && entry.isPrimary() && entry.getBackupNodes()!= null && entry.getBackupNodes().length > 0) {
+- Object value = entry.getValue();
+- //check to see if we need to replicate this object isDirty()||complete
+- boolean repl = complete || ( (value instanceof ReplicatedMapEntry) && ( (ReplicatedMapEntry) value).isDirty());
+-
+- if (!repl) {
+- if ( log.isTraceEnabled() )
+- log.trace("Not replicating:"+key+", no change made");
+-
+- return;
+- }
+- //check to see if the message is diffable
+- boolean diff = ( (value instanceof ReplicatedMapEntry) && ( (ReplicatedMapEntry) value).isDiffable());
+- MapMessage msg = null;
+- if (diff) {
+- ReplicatedMapEntry rentry = (ReplicatedMapEntry)entry.getValue();
+- try {
+- rentry.lock();
+- //construct a diff message
+- msg = new MapMessage(mapContextName, MapMessage.MSG_BACKUP,
+- true, (Serializable) entry.getKey(), null,
+- rentry.getDiff(),
+- entry.getPrimary(),
+- entry.getBackupNodes());
+- } catch (IOException x) {
+- log.error("Unable to diff object. Will replicate the entire object instead.", x);
+- } finally {
+- rentry.unlock();
+- }
+-
+- }
+- if (msg == null) {
+- //construct a complete
+- msg = new MapMessage(mapContextName, MapMessage.MSG_BACKUP,
+- false, (Serializable) entry.getKey(),
+- (Serializable) entry.getValue(),
+- null, entry.getPrimary(),entry.getBackupNodes());
+-
+- }
+- try {
+- if ( channel!=null && entry.getBackupNodes()!= null && entry.getBackupNodes().length > 0 ) {
+- channel.send(entry.getBackupNodes(), msg, channelSendOptions);
+- }
+- } catch (ChannelException x) {
+- log.error("Unable to replicate data.", x);
+- }
+- } //end if
+-
+- }
+-
+- /**
+- * This can be invoked by a periodic thread to replicate out any changes.
+- * For maps that don't store objects that implement ReplicatedMapEntry, this
+- * method should be used infrequently to avoid large amounts of data transfer
+- * @param complete boolean
+- */
+- public void replicate(boolean complete) {
+- Iterator i = super.entrySet().iterator();
+- while (i.hasNext()) {
+- Map.Entry e = (Map.Entry) i.next();
+- replicate(e.getKey(), complete);
+- } //while
+-
+- }
+-
+- public void transferState() {
+- try {
+- Member[] members = getMapMembers();
+- Member backup = members.length > 0 ? (Member) members[0] : null;
+- if (backup != null) {
+- MapMessage msg = new MapMessage(mapContextName, getStateMessageType(), false,
+- null, null, null, null, null);
+- Response[] resp = rpcChannel.send(new Member[] {backup}, msg, rpcChannel.FIRST_REPLY, channelSendOptions, rpcTimeout);
+- if (resp.length > 0) {
+- synchronized (stateMutex) {
+- msg = (MapMessage) resp[0].getMessage();
+- msg.deserialize(getExternalLoaders());
+- ArrayList list = (ArrayList) msg.getValue();
+- for (int i = 0; i < list.size(); i++) {
+- messageReceived( (Serializable) list.get(i), resp[0].getSource());
+- } //for
+- }
+- } else {
+- log.warn("Transfer state, 0 replies, probably a timeout.");
+- }
+- }
+- } catch (ChannelException x) {
+- log.error("Unable to transfer LazyReplicatedMap state.", x);
+- } catch (IOException x) {
+- log.error("Unable to transfer LazyReplicatedMap state.", x);
+- } catch (ClassNotFoundException x) {
+- log.error("Unable to transfer LazyReplicatedMap state.", x);
+- }
+- stateTransferred = true;
+- }
+-
+- /**
+- * @todo implement state transfer
+- * @param msg Serializable
+- * @return Serializable - null if no reply should be sent
+- */
+- public Serializable replyRequest(Serializable msg, final Member sender) {
+- if (! (msg instanceof MapMessage))return null;
+- MapMessage mapmsg = (MapMessage) msg;
+-
+- //map init request
+- if (mapmsg.getMsgType() == mapmsg.MSG_INIT) {
+- mapmsg.setPrimary(channel.getLocalMember(false));
+- return mapmsg;
+- }
+-
+- //map start request
+- if (mapmsg.getMsgType() == mapmsg.MSG_START) {
+- mapmsg.setPrimary(channel.getLocalMember(false));
+- mapMemberAdded(sender);
+- return mapmsg;
+- }
+-
+- //backup request
+- if (mapmsg.getMsgType() == mapmsg.MSG_RETRIEVE_BACKUP) {
+- MapEntry entry = (MapEntry)super.get(mapmsg.getKey());
+- if (entry == null || (!entry.isSerializable()) )return null;
+- mapmsg.setValue( (Serializable) entry.getValue());
+- return mapmsg;
+- }
+-
+- //state transfer request
+- if (mapmsg.getMsgType() == mapmsg.MSG_STATE || mapmsg.getMsgType() == mapmsg.MSG_STATE_COPY) {
+- synchronized (stateMutex) { //make sure we dont do two things at the same time
+- ArrayList list = new ArrayList();
+- Iterator i = super.entrySet().iterator();
+- while (i.hasNext()) {
+- Map.Entry e = (Map.Entry) i.next();
+- MapEntry entry = (MapEntry) super.get(e.getKey());
+- if ( entry != null && entry.isSerializable() ) {
+- boolean copy = (mapmsg.getMsgType() == mapmsg.MSG_STATE_COPY);
+- MapMessage me = new MapMessage(mapContextName,
+- copy?MapMessage.MSG_COPY:MapMessage.MSG_PROXY,
+- false, (Serializable) entry.getKey(), copy?(Serializable) entry.getValue():null, null, entry.getPrimary(),entry.getBackupNodes());
+- list.add(me);
+- }
+- }
+- mapmsg.setValue(list);
+- return mapmsg;
+-
+- } //synchronized
+- }
+-
+- return null;
+-
+- }
+-
+- /**
+- * If the reply has already been sent to the requesting thread,
+- * the rpc callback can handle any data that comes in after the fact.
+- * @param msg Serializable
+- * @param sender Member
+- */
+- public void leftOver(Serializable msg, Member sender) {
+- //left over membership messages
+- if (! (msg instanceof MapMessage))return;
+-
+- MapMessage mapmsg = (MapMessage) msg;
+- try {
+- mapmsg.deserialize(getExternalLoaders());
+- if (mapmsg.getMsgType() == MapMessage.MSG_START) {
+- mapMemberAdded(mapmsg.getPrimary());
+- } else if (mapmsg.getMsgType() == MapMessage.MSG_INIT) {
+- memberAlive(mapmsg.getPrimary());
+- }
+- } catch (IOException x ) {
+- log.error("Unable to deserialize MapMessage.",x);
+- } catch (ClassNotFoundException x ) {
+- log.error("Unable to deserialize MapMessage.",x);
+- }
+- }
+-
+- public void messageReceived(Serializable msg, Member sender) {
+- if (! (msg instanceof MapMessage)) return;
+-
+- MapMessage mapmsg = (MapMessage) msg;
+- if ( log.isTraceEnabled() ) {
+- log.trace("Map["+mapname+"] received message:"+mapmsg);
+- }
+-
+- try {
+- mapmsg.deserialize(getExternalLoaders());
+- } catch (IOException x) {
+- log.error("Unable to deserialize MapMessage.", x);
+- return;
+- } catch (ClassNotFoundException x) {
+- log.error("Unable to deserialize MapMessage.", x);
+- return;
+- }
+- if ( log.isTraceEnabled() )
+- log.trace("Map message received from:"+sender.getName()+" msg:"+mapmsg);
+- if (mapmsg.getMsgType() == MapMessage.MSG_START) {
+- mapMemberAdded(mapmsg.getPrimary());
+- }
+-
+- if (mapmsg.getMsgType() == MapMessage.MSG_STOP) {
+- memberDisappeared(mapmsg.getPrimary());
+- }
+-
+- if (mapmsg.getMsgType() == MapMessage.MSG_PROXY) {
+- MapEntry entry = (MapEntry)super.get(mapmsg.getKey());
+- if ( entry==null ) {
+- entry = new MapEntry(mapmsg.getKey(), mapmsg.getValue());
+- entry.setBackup(false);
+- entry.setProxy(true);
+- entry.setBackupNodes(mapmsg.getBackupNodes());
+- entry.setPrimary(mapmsg.getPrimary());
+- super.put(entry.getKey(), entry);
+- } else {
+- entry.setProxy(true);
+- entry.setBackup(false);
+- entry.setBackupNodes(mapmsg.getBackupNodes());
+- entry.setPrimary(mapmsg.getPrimary());
+- }
+- }
+-
+- if (mapmsg.getMsgType() == MapMessage.MSG_REMOVE) {
+- super.remove(mapmsg.getKey());
+- }
+-
+- if (mapmsg.getMsgType() == MapMessage.MSG_BACKUP || mapmsg.getMsgType() == MapMessage.MSG_COPY) {
+- MapEntry entry = (MapEntry)super.get(mapmsg.getKey());
+- if (entry == null) {
+- entry = new MapEntry(mapmsg.getKey(), mapmsg.getValue());
+- entry.setBackup(mapmsg.getMsgType() == MapMessage.MSG_BACKUP);
+- entry.setProxy(false);
+- entry.setBackupNodes(mapmsg.getBackupNodes());
+- entry.setPrimary(mapmsg.getPrimary());
+- if (mapmsg.getValue()!=null && mapmsg.getValue() instanceof ReplicatedMapEntry ) {
+- ((ReplicatedMapEntry)mapmsg.getValue()).setOwner(getMapOwner());
+- }
+- } else {
+- entry.setBackup(mapmsg.getMsgType() == MapMessage.MSG_BACKUP);
+- entry.setProxy(false);
+- entry.setBackupNodes(mapmsg.getBackupNodes());
+- entry.setPrimary(mapmsg.getPrimary());
+- if (entry.getValue() instanceof ReplicatedMapEntry) {
+- ReplicatedMapEntry diff = (ReplicatedMapEntry) entry.getValue();
+- if (mapmsg.isDiff()) {
+- try {
+- diff.lock();
+- diff.applyDiff(mapmsg.getDiffValue(), 0, mapmsg.getDiffValue().length);
+- } catch (Exception x) {
+- log.error("Unable to apply diff to key:" + entry.getKey(), x);
+- } finally {
+- diff.unlock();
+- }
+- } else {
+- if ( mapmsg.getValue()!=null ) entry.setValue(mapmsg.getValue());
+- ((ReplicatedMapEntry)entry.getValue()).setOwner(getMapOwner());
+- } //end if
+- } else if (mapmsg.getValue() instanceof ReplicatedMapEntry) {
+- ReplicatedMapEntry re = (ReplicatedMapEntry)mapmsg.getValue();
+- re.setOwner(getMapOwner());
+- entry.setValue(re);
+- } else {
+- if ( mapmsg.getValue()!=null ) entry.setValue(mapmsg.getValue());
+- } //end if
+- } //end if
+- super.put(entry.getKey(), entry);
+- } //end if
+- }
+-
+- public boolean accept(Serializable msg, Member sender) {
+- boolean result = false;
+- if (msg instanceof MapMessage) {
+- if ( log.isTraceEnabled() ) log.trace("Map["+mapname+"] accepting...."+msg);
+- result = Arrays.equals(mapContextName, ( (MapMessage) msg).getMapId());
+- if ( log.isTraceEnabled() ) log.trace("Msg["+mapname+"] accepted["+result+"]...."+msg);
+- }
+- return result;
+- }
+-
+- public void mapMemberAdded(Member member) {
+- if ( member.equals(getChannel().getLocalMember(false)) ) return;
+- boolean memberAdded = false;
+- //select a backup node if we don't have one
+- synchronized (mapMembers) {
+- if (!mapMembers.containsKey(member) ) {
+- mapMembers.put(member, new Long(System.currentTimeMillis()));
+- memberAdded = true;
+- }
+- }
+- if ( memberAdded ) {
+- synchronized (stateMutex) {
+- Iterator i = super.entrySet().iterator();
+- while (i.hasNext()) {
+- Map.Entry e = (Map.Entry) i.next();
+- MapEntry entry = (MapEntry) super.get(e.getKey());
+- if ( entry == null ) continue;
+- if (entry.isPrimary() && (entry.getBackupNodes() == null || entry.getBackupNodes().length == 0)) {
+- try {
+- Member[] backup = publishEntryInfo(entry.getKey(), entry.getValue());
+- entry.setBackupNodes(backup);
+- entry.setPrimary(channel.getLocalMember(false));
+- } catch (ChannelException x) {
+- log.error("Unable to select backup node.", x);
+- } //catch
+- } //end if
+- } //while
+- } //synchronized
+- }//end if
+- }
+-
+- public boolean inSet(Member m, Member[] set) {
+- if ( set == null ) return false;
+- boolean result = false;
+- for (int i=0; i<set.length && (!result); i++ )
+- if ( m.equals(set[i]) ) result = true;
+- return result;
+- }
+-
+- public Member[] excludeFromSet(Member[] mbrs, Member[] set) {
+- ArrayList result = new ArrayList();
+- for (int i=0; i<set.length; i++ ) {
+- boolean include = true;
+- for (int j=0; j<mbrs.length; j++ )
+- if ( mbrs[j].equals(set[i]) ) include = false;
+- if ( include ) result.add(set[i]);
+- }
+- return (Member[])result.toArray(new Member[result.size()]);
+- }
+-
+- public void memberAdded(Member member) {
+- //do nothing
+- }
+-
+- public void memberDisappeared(Member member) {
+- boolean removed = false;
+- synchronized (mapMembers) {
+- removed = (mapMembers.remove(member) != null );
+- if (!removed) {
+- if (log.isDebugEnabled()) log.debug("Member["+member+"] disappeared, but was not present in the map.");
+- return; //the member was not part of our map.
+- }
+- }
+-
+- Iterator i = super.entrySet().iterator();
+- while (i.hasNext()) {
+- Map.Entry e = (Map.Entry) i.next();
+- MapEntry entry = (MapEntry) super.get(e.getKey());
+- if (entry==null) continue;
+- if (entry.isPrimary() && inSet(member,entry.getBackupNodes())) {
+- if (log.isDebugEnabled()) log.debug("[1] Primary choosing a new backup");
+- try {
+- Member[] backup = publishEntryInfo(entry.getKey(), entry.getValue());
+- entry.setBackupNodes(backup);
+- entry.setPrimary(channel.getLocalMember(false));
+- } catch (ChannelException x) {
+- log.error("Unable to relocate[" + entry.getKey() + "] to a new backup node", x);
+- }
+- } else if (member.equals(entry.getPrimary())) {
+- if (log.isDebugEnabled()) log.debug("[2] Primary disappeared");
+- entry.setPrimary(null);
+- } //end if
+-
+- if ( entry.isProxy() &&
+- entry.getPrimary() == null &&
+- entry.getBackupNodes()!=null &&
+- entry.getBackupNodes().length == 1 &&
+- entry.getBackupNodes()[0].equals(member) ) {
+- //remove proxies that have no backup nor primaries
+- if (log.isDebugEnabled()) log.debug("[3] Removing orphaned proxy");
+- i.remove();
+- } else if ( entry.getPrimary() == null &&
+- entry.isBackup() &&
+- entry.getBackupNodes()!=null &&
+- entry.getBackupNodes().length == 1 &&
+- entry.getBackupNodes()[0].equals(channel.getLocalMember(false)) ) {
+- try {
+- if (log.isDebugEnabled()) log.debug("[4] Backup becoming primary");
+- entry.setPrimary(channel.getLocalMember(false));
+- entry.setBackup(false);
+- entry.setProxy(false);
+- Member[] backup = publishEntryInfo(entry.getKey(), entry.getValue());
+- entry.setBackupNodes(backup);
+- if ( mapOwner!=null ) mapOwner.objectMadePrimay(entry.getKey(),entry.getValue());
+-
+- } catch (ChannelException x) {
+- log.error("Unable to relocate[" + entry.getKey() + "] to a new backup node", x);
+- }
+- }
+-
+- } //while
+- }
+-
+- public int getNextBackupIndex() {
+- int size = mapMembers.size();
+- if (mapMembers.size() == 0)return -1;
+- int node = currentNode++;
+- if (node >= size) {
+- node = 0;
+- currentNode = 0;
+- }
+- return node;
+- }
+- public Member getNextBackupNode() {
+- Member[] members = getMapMembers();
+- int node = getNextBackupIndex();
+- if ( members.length == 0 || node==-1) return null;
+- if ( node >= members.length ) node = 0;
+- return members[node];
+- }
+-
+- protected abstract Member[] publishEntryInfo(Object key, Object value) throws ChannelException;
+-
+- public void heartbeat() {
+- try {
+- ping(accessTimeout);
+- }catch ( Exception x ) {
+- log.error("Unable to send AbstractReplicatedMap.ping message",x);
+- }
+- }
+-
+-//------------------------------------------------------------------------------
+-// METHODS TO OVERRIDE
+-//------------------------------------------------------------------------------
+-
+- /**
+- * Removes an object from this map, it will also remove it from
+- *
+- * @param key Object
+- * @return Object
+- */
+- public Object remove(Object key) {
+- return remove(key,true);
+- }
+- public Object remove(Object key, boolean notify) {
+- MapEntry entry = (MapEntry)super.remove(key);
+-
+- try {
+- if (getMapMembers().length > 0 && notify) {
+- MapMessage msg = new MapMessage(getMapContextName(), MapMessage.MSG_REMOVE, false, (Serializable) key, null, null, null,null);
+- getChannel().send(getMapMembers(), msg, getChannelSendOptions());
+- }
+- } catch ( ChannelException x ) {
+- log.error("Unable to replicate out data for a LazyReplicatedMap.remove operation",x);
+- }
+- return entry!=null?entry.getValue():null;
+- }
+-
+- public MapEntry getInternal(Object key) {
+- return (MapEntry)super.get(key);
+- }
+-
+- public Object get(Object key) {
+- MapEntry entry = (MapEntry)super.get(key);
+- if (log.isTraceEnabled()) log.trace("Requesting id:"+key+" entry:"+entry);
+- if ( entry == null ) return null;
+- if ( !entry.isPrimary() ) {
+- //if the message is not primary, we need to retrieve the latest value
+- try {
+- Member[] backup = null;
+- MapMessage msg = null;
+- if ( !entry.isBackup() ) {
+- //make sure we don't retrieve from ourselves
+- msg = new MapMessage(getMapContextName(), MapMessage.MSG_RETRIEVE_BACKUP, false,
+- (Serializable) key, null, null, null,null);
+- Response[] resp = getRpcChannel().send(entry.getBackupNodes(),msg, this.getRpcChannel().FIRST_REPLY, Channel.SEND_OPTIONS_DEFAULT, getRpcTimeout());
+- if (resp == null || resp.length == 0) {
+- //no responses
+- log.warn("Unable to retrieve remote object for key:" + key);
+- return null;
+- }
+- msg = (MapMessage) resp[0].getMessage();
+- msg.deserialize(getExternalLoaders());
+- backup = entry.getBackupNodes();
+- if ( entry.getValue() instanceof ReplicatedMapEntry ) {
+- ReplicatedMapEntry val = (ReplicatedMapEntry)entry.getValue();
+- val.setOwner(getMapOwner());
+- }
+- if ( msg.getValue()!=null ) entry.setValue(msg.getValue());
+- }
+- if (entry.isBackup()) {
+- //select a new backup node
+- backup = publishEntryInfo(key, entry.getValue());
+- } else if ( entry.isProxy() ) {
+- //invalidate the previous primary
+- msg = new MapMessage(getMapContextName(),MapMessage.MSG_PROXY,false,(Serializable)key,null,null,channel.getLocalMember(false),backup);
+- Member[] dest = getMapMembersExcl(backup);
+- if ( dest!=null && dest.length >0) {
+- getChannel().send(dest, msg, getChannelSendOptions());
+- }
+- }
+- entry.setPrimary(channel.getLocalMember(false));
+- entry.setBackupNodes(backup);
+- entry.setBackup(false);
+- entry.setProxy(false);
+-
+-
+- } catch (Exception x) {
+- log.error("Unable to replicate out data for a LazyReplicatedMap.get operation", x);
+- return null;
+- }
+- }
+- if (log.isTraceEnabled()) log.trace("Requesting id:"+key+" result:"+entry.getValue());
+- if ( entry.getValue() != null && entry.getValue() instanceof ReplicatedMapEntry ) {
+- ReplicatedMapEntry val = (ReplicatedMapEntry)entry.getValue();
+- //hack, somehow this is not being set above
+- val.setOwner(getMapOwner());
+-
+- }
+- return entry.getValue();
+- }
+-
+-
+- protected void printMap(String header) {
+- try {
+- System.out.println("\nDEBUG MAP:"+header);
+- System.out.println("Map["+ new String(mapContextName, chset) + ", Map Size:" + super.size());
+- Member[] mbrs = getMapMembers();
+- for ( int i=0; i<mbrs.length;i++ ) {
+- System.out.println("Mbr["+(i+1)+"="+mbrs[i].getName());
+- }
+- Iterator i = super.entrySet().iterator();
+- int cnt = 0;
+-
+- while (i.hasNext()) {
+- Map.Entry e = (Map.Entry) i.next();
+- System.out.println( (++cnt) + ". " + super.get(e.getKey()));
+- }
+- System.out.println("EndMap]\n\n");
+- }catch ( Exception ignore) {
+- ignore.printStackTrace();
+- }
+- }
+-
+- /**
+- * Returns true if the key has an entry in the map.
+- * The entry can be a proxy or a backup entry, invoking <code>get(key)</code>
+- * will make this entry primary for the group
+- * @param key Object
+- * @return boolean
+- */
+- public boolean containsKey(Object key) {
+- return super.containsKey(key);
+- }
+-
+- public Object put(Object key, Object value) {
+- return put(key,value,true);
+- }
+-
+- public Object put(Object key, Object value, boolean notify) {
+- MapEntry entry = new MapEntry(key,value);
+- entry.setBackup(false);
+- entry.setProxy(false);
+- entry.setPrimary(channel.getLocalMember(false));
+-
+- Object old = null;
+-
+- //make sure that any old values get removed
+- if ( containsKey(key) ) old = remove(key);
+- try {
+- if ( notify ) {
+- Member[] backup = publishEntryInfo(key, value);
+- entry.setBackupNodes(backup);
+- }
+- } catch (ChannelException x) {
+- log.error("Unable to replicate out data for a LazyReplicatedMap.put operation", x);
+- }
+- super.put(key,entry);
+- return old;
+- }
+-
+-
+- /**
+- * Copies all values from one map to this instance
+- * @param m Map
+- */
+- public void putAll(Map m) {
+- Iterator i = m.entrySet().iterator();
+- while ( i.hasNext() ) {
+- Map.Entry entry = (Map.Entry)i.next();
+- put(entry.getKey(),entry.getValue());
+- }
+- }
+-
+- public void clear() {
+- clear(true);
+- }
+-
+- public void clear(boolean notify) {
+- if ( notify ) {
+- //only delete active keys
+- Iterator keys = keySet().iterator();
+- while (keys.hasNext())
+- remove(keys.next());
+- } else {
+- super.clear();
+- }
+- }
+-
+- public boolean containsValue(Object value) {
+- if ( value == null ) {
+- return super.containsValue(value);
+- } else {
+- Iterator i = super.entrySet().iterator();
+- while (i.hasNext()) {
+- Map.Entry e = (Map.Entry) i.next();
+- MapEntry entry = (MapEntry) super.get(e.getKey());
+- if (entry!=null && entry.isPrimary() && value.equals(entry.getValue())) return true;
+- }//while
+- return false;
+- }//end if
+- }
+-
+- public Object clone() {
+- throw new UnsupportedOperationException("This operation is not valid on a replicated map");
+- }
+-
+- /**
+- * Returns the entire contents of the map
+- * Map.Entry.getValue() will return a LazyReplicatedMap.MapEntry object containing all the information
+- * about the object.
+- * @return Set
+- */
+- public Set entrySetFull() {
+- return super.entrySet();
+- }
+-
+- public Set keySetFull() {
+- return super.keySet();
+- }
+-
+- public int sizeFull() {
+- return super.size();
+- }
+-
+- public Set entrySet() {
+- LinkedHashSet set = new LinkedHashSet(super.size());
+- Iterator i = super.entrySet().iterator();
+- while ( i.hasNext() ) {
+- Map.Entry e = (Map.Entry)i.next();
+- Object key = e.getKey();
+- MapEntry entry = (MapEntry)super.get(key);
+- if ( entry != null && entry.isPrimary() ) set.add(entry.getValue());
+- }
+- return Collections.unmodifiableSet(set);
+- }
+-
+- public Set keySet() {
+- //todo implement
+- //should only return keys where this is active.
+- LinkedHashSet set = new LinkedHashSet(super.size());
+- Iterator i = super.entrySet().iterator();
+- while ( i.hasNext() ) {
+- Map.Entry e = (Map.Entry)i.next();
+- Object key = e.getKey();
+- MapEntry entry = (MapEntry)super.get(key);
+- if ( entry!=null && entry.isPrimary() ) set.add(key);
+- }
+- return Collections.unmodifiableSet(set);
+-
+- }
+-
+-
+- public int size() {
+- //todo, implement a counter variable instead
+- //only count active members in this node
+- int counter = 0;
+- Iterator it = super.entrySet().iterator();
+- while (it!=null && it.hasNext() ) {
+- Map.Entry e = (Map.Entry) it.next();
+- if ( e != null ) {
+- MapEntry entry = (MapEntry) super.get(e.getKey());
+- if (entry!=null && entry.isPrimary() && entry.getValue() != null) counter++;
+- }
+- }
+- return counter;
+- }
+-
+- protected boolean removeEldestEntry(Map.Entry eldest) {
+- return false;
+- }
+-
+- public boolean isEmpty() {
+- return size()==0;
+- }
+-
+- public Collection values() {
+- ArrayList values = new ArrayList();
+- Iterator i = super.entrySet().iterator();
+- while ( i.hasNext() ) {
+- Map.Entry e = (Map.Entry)i.next();
+- MapEntry entry = (MapEntry)super.get(e.getKey());
+- if (entry!=null && entry.isPrimary() && entry.getValue()!=null) values.add(entry.getValue());
+- }
+- return Collections.unmodifiableCollection(values);
+- }
+-
+-
+-//------------------------------------------------------------------------------
+-// Map Entry class
+-//------------------------------------------------------------------------------
+- public static class MapEntry implements Map.Entry {
+- private boolean backup;
+- private boolean proxy;
+- private Member[] backupNodes;
+- private Member primary;
+- private Object key;
+- private Object value;
+-
+- public MapEntry(Object key, Object value) {
+- setKey(key);
+- setValue(value);
+-
+- }
+-
+- public boolean isKeySerializable() {
+- return (key == null) || (key instanceof Serializable);
+- }
+-
+- public boolean isValueSerializable() {
+- return (value==null) || (value instanceof Serializable);
+- }
+-
+- public boolean isSerializable() {
+- return isKeySerializable() && isValueSerializable();
+- }
+-
+- public boolean isBackup() {
+- return backup;
+- }
+-
+- public void setBackup(boolean backup) {
+- this.backup = backup;
+- }
+-
+- public boolean isProxy() {
+- return proxy;
+- }
+-
+- public boolean isPrimary() {
+- return ( (!proxy) && (!backup));
+- }
+-
+- public void setProxy(boolean proxy) {
+- this.proxy = proxy;
+- }
+-
+- public boolean isDiffable() {
+- return (value instanceof ReplicatedMapEntry) &&
+- ((ReplicatedMapEntry)value).isDiffable();
+- }
+-
+- public void setBackupNodes(Member[] nodes) {
+- this.backupNodes = nodes;
+- }
+-
+- public Member[] getBackupNodes() {
+- return backupNodes;
+- }
+-
+- public void setPrimary(Member m) {
+- primary = m;
+- }
+-
+- public Member getPrimary() {
+- return primary;
+- }
+-
+- public Object getValue() {
+- return value;
+- }
+-
+- public Object setValue(Object value) {
+- Object old = this.value;
+- this.value = value;
+- return old;
+- }
+-
+- public Object getKey() {
+- return key;
+- }
+-
+- public Object setKey(Object key) {
+- Object old = this.key;
+- this.key = key;
+- return old;
+- }
+-
+- public int hashCode() {
+- return key.hashCode();
+- }
+-
+- public boolean equals(Object o) {
+- return key.equals(o);
+- }
+-
+- /**
+- * apply a diff, or an entire object
+- * @param data byte[]
+- * @param offset int
+- * @param length int
+- * @param diff boolean
+- * @throws IOException
+- * @throws ClassNotFoundException
+- */
+- public void apply(byte[] data, int offset, int length, boolean diff) throws IOException, ClassNotFoundException {
+- if (isDiffable() && diff) {
+- ReplicatedMapEntry rentry = (ReplicatedMapEntry) value;
+- try {
+- rentry.lock();
+- rentry.applyDiff(data, offset, length);
+- } finally {
+- rentry.unlock();
+- }
+- } else if (length == 0) {
+- value = null;
+- proxy = true;
+- } else {
+- value = XByteBuffer.deserialize(data, offset, length);
+- }
+- }
+-
+- public String toString() {
+- StringBuffer buf = new StringBuffer("MapEntry[key:");
+- buf.append(getKey()).append("; ");
+- buf.append("value:").append(getValue()).append("; ");
+- buf.append("primary:").append(isPrimary()).append("; ");
+- buf.append("backup:").append(isBackup()).append("; ");
+- buf.append("proxy:").append(isProxy()).append(";]");
+- return buf.toString();
+- }
+-
+- }
+-
+-//------------------------------------------------------------------------------
+-// map message to send to and from other maps
+-//------------------------------------------------------------------------------
+-
+- public static class MapMessage implements Serializable {
+- public static final int MSG_BACKUP = 1;
+- public static final int MSG_RETRIEVE_BACKUP = 2;
+- public static final int MSG_PROXY = 3;
+- public static final int MSG_REMOVE = 4;
+- public static final int MSG_STATE = 5;
+- public static final int MSG_START = 6;
+- public static final int MSG_STOP = 7;
+- public static final int MSG_INIT = 8;
+- public static final int MSG_COPY = 9;
+- public static final int MSG_STATE_COPY = 10;
+-
+- private byte[] mapId;
+- private int msgtype;
+- private boolean diff;
+- private transient Serializable key;
+- private transient Serializable value;
+- private byte[] valuedata;
+- private byte[] keydata;
+- private byte[] diffvalue;
+- private Member[] nodes;
+- private Member primary;
+-
+- public String toString() {
+- StringBuffer buf = new StringBuffer("MapMessage[context=");
+- buf.append(new String(mapId));
+- buf.append("; type=");
+- buf.append(getTypeDesc());
+- buf.append("; key=");
+- buf.append(key);
+- buf.append("; value=");
+- buf.append(value);
+- return buf.toString();
+- }
+-
+- public String getTypeDesc() {
+- switch (msgtype) {
+- case MSG_BACKUP: return "MSG_BACKUP";
+- case MSG_RETRIEVE_BACKUP: return "MSG_RETRIEVE_BACKUP";
+- case MSG_PROXY: return "MSG_PROXY";
+- case MSG_REMOVE: return "MSG_REMOVE";
+- case MSG_STATE: return "MSG_STATE";
+- case MSG_START: return "MSG_START";
+- case MSG_STOP: return "MSG_STOP";
+- case MSG_INIT: return "MSG_INIT";
+- case MSG_STATE_COPY: return "MSG_STATE_COPY";
+- case MSG_COPY: return "MSG_COPY";
+- default : return "UNKNOWN";
+- }
+- }
+-
+- public MapMessage() {}
+-
+- public MapMessage(byte[] mapId,int msgtype, boolean diff,
+- Serializable key, Serializable value,
+- byte[] diffvalue, Member primary, Member[] nodes) {
+- this.mapId = mapId;
+- this.msgtype = msgtype;
+- this.diff = diff;
+- this.key = key;
+- this.value = value;
+- this.diffvalue = diffvalue;
+- this.nodes = nodes;
+- this.primary = primary;
+- setValue(value);
+- setKey(key);
+- }
+-
+- public void deserialize(ClassLoader[] cls) throws IOException, ClassNotFoundException {
+- key(cls);
+- value(cls);
+- }
+-
+- public int getMsgType() {
+- return msgtype;
+- }
+-
+- public boolean isDiff() {
+- return diff;
+- }
+-
+- public Serializable getKey() {
+- try {
+- return key(null);
+- } catch ( Exception x ) {
+- log.error("Deserialization error of the MapMessage.key",x);
+- return null;
+- }
+- }
+-
+- public Serializable key(ClassLoader[] cls) throws IOException, ClassNotFoundException {
+- if ( key!=null ) return key;
+- if ( keydata == null || keydata.length == 0 ) return null;
+- key = XByteBuffer.deserialize(keydata,0,keydata.length,cls);
+- keydata = null;
+- return key;
+- }
+-
+- public byte[] getKeyData() {
+- return keydata;
+- }
+-
+- public Serializable getValue() {
+- try {
+- return value(null);
+- } catch ( Exception x ) {
+- log.error("Deserialization error of the MapMessage.value",x);
+- return null;
+- }
+- }
+-
+- public Serializable value(ClassLoader[] cls) throws IOException, ClassNotFoundException {
+- if ( value!=null ) return value;
+- if ( valuedata == null || valuedata.length == 0 ) return null;
+- value = XByteBuffer.deserialize(valuedata,0,valuedata.length,cls);
+- valuedata = null;;
+- return value;
+- }
+-
+- public byte[] getValueData() {
+- return valuedata;
+- }
+-
+- public byte[] getDiffValue() {
+- return diffvalue;
+- }
+-
+- public Member[] getBackupNodes() {
+- return nodes;
+- }
+-
+- private void setBackUpNodes(Member[] nodes) {
+- this.nodes = nodes;
+- }
+-
+- public Member getPrimary() {
+- return primary;
+- }
+-
+- private void setPrimary(Member m) {
+- primary = m;
+- }
+-
+- public byte[] getMapId() {
+- return mapId;
+- }
+-
+- public void setValue(Serializable value) {
+- try {
+- if ( value != null ) valuedata = XByteBuffer.serialize(value);
+- this.value = value;
+- }catch ( IOException x ) {
+- throw new RuntimeException(x);
+- }
+- }
+-
+- public void setKey(Serializable key) {
+- try {
+- if (key != null) keydata = XByteBuffer.serialize(key);
+- this.key = key;
+- } catch (IOException x) {
+- throw new RuntimeException(x);
+- }
+- }
+-
+- protected Member[] readMembers(ObjectInput in) throws IOException, ClassNotFoundException {
+- int nodecount = in.readInt();
+- Member[] members = new Member[nodecount];
+- for ( int i=0; i<members.length; i++ ) {
+- byte[] d = new byte[in.readInt()];
+- in.read(d);
+- if (d.length > 0) members[i] = MemberImpl.getMember(d);
+- }
+- return members;
+- }
+-
+- protected void writeMembers(ObjectOutput out,Member[] members) throws IOException {
+- if ( members == null ) members = new Member[0];
+- out.writeInt(members.length);
+- for (int i=0; i<members.length; i++ ) {
+- if ( members[i] != null ) {
+- byte[] d = members[i] != null ? ( (MemberImpl)members[i]).getData(false) : new byte[0];
+- out.writeInt(d.length);
+- out.write(d);
+- }
+- }
+- }
+-
+-
+- /**
+- * shallow clone
+- * @return Object
+- */
+- public Object clone() {
+- MapMessage msg = new MapMessage(this.mapId, this.msgtype, this.diff, this.key, this.value, this.diffvalue, this.primary, this.nodes);
+- msg.keydata = this.keydata;
+- msg.valuedata = this.valuedata;
+- return msg;
+- }
+- } //MapMessage
+-
+-
+- public Channel getChannel() {
+- return channel;
+- }
+-
+- public byte[] getMapContextName() {
+- return mapContextName;
+- }
+-
+- public RpcChannel getRpcChannel() {
+- return rpcChannel;
+- }
+-
+- public long getRpcTimeout() {
+- return rpcTimeout;
+- }
+-
+- public Object getStateMutex() {
+- return stateMutex;
+- }
+-
+- public boolean isStateTransferred() {
+- return stateTransferred;
+- }
+-
+- public MapOwner getMapOwner() {
+- return mapOwner;
+- }
+-
+- public ClassLoader[] getExternalLoaders() {
+- return externalLoaders;
+- }
+-
+- public int getChannelSendOptions() {
+- return channelSendOptions;
+- }
+-
+- public long getAccessTimeout() {
+- return accessTimeout;
+- }
+-
+- public void setMapOwner(MapOwner mapOwner) {
+- this.mapOwner = mapOwner;
+- }
+-
+- public void setExternalLoaders(ClassLoader[] externalLoaders) {
+- this.externalLoaders = externalLoaders;
+- }
+-
+- public void setChannelSendOptions(int channelSendOptions) {
+- this.channelSendOptions = channelSendOptions;
+- }
+-
+- public void setAccessTimeout(long accessTimeout) {
+- this.accessTimeout = accessTimeout;
+- }
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/RemoteProcessException.java
+===================================================================
+--- java/org/apache/catalina/tribes/RemoteProcessException.java (revision 590752)
++++ java/org/apache/catalina/tribes/RemoteProcessException.java (working copy)
+@@ -1,47 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes;
+-
+-/**
+- * <p>Title: RemoteProcessException</p>
+- *
+- * <p>Description: Message thrown by a sender when USE_SYNC_ACK receives a FAIL_ACK_COMMAND.<br>
+- * This means that the message was received on the remote node but the processing of the message failed.
+- * This message will be embedded in a ChannelException.FaultyMember
+- * </p>
+- * @see ChannelException
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public class RemoteProcessException extends RuntimeException {
+- public RemoteProcessException() {
+- super();
+- }
+-
+- public RemoteProcessException(String message) {
+- super(message);
+- }
+-
+- public RemoteProcessException(String message, Throwable cause) {
+- super(message, cause);
+- }
+-
+- public RemoteProcessException(Throwable cause) {
+- super(cause);
+- }
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/ChannelSender.java
+===================================================================
+--- java/org/apache/catalina/tribes/ChannelSender.java (revision 590752)
++++ java/org/apache/catalina/tribes/ChannelSender.java (working copy)
+@@ -1,69 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes;
+-
+-
+-/**
+- * ChannelReceiver Interface<br>
+- * The <code>ChannelSender</code> interface is the data sender component
+- * at the bottom layer, the IO layer (for layers see the javadoc for the {@link Channel} interface).<br>
+- * The channel sender must support "silent" members, ie, be able to send a message to a member
+- * that is not in the membership, but is part of the destination parameter
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-public interface ChannelSender extends Heartbeat
+-{
+- /**
+- * Notify the sender of a member being added to the group.<br>
+- * Optional. This can be an empty implementation, that does nothing
+- * @param member Member
+- */
+- public void add(Member member);
+- /**
+- * Notification that a member has been removed or crashed.
+- * Can be used to clean up open connections etc
+- * @param member Member
+- */
+- public void remove(Member member);
+-
+- /**
+- * Start the channel sender
+- * @throws IOException if preprocessing takes place and an error happens
+- */
+- public void start() throws java.io.IOException;
+- /**
+- * Stop the channel sender
+- */
+- public void stop();
+-
+- /**
+- * A channel heartbeat, use this method to clean up resources
+- */
+- public void heartbeat() ;
+-
+- /**
+- * Send a message to one or more recipients.
+- * @param message ChannelMessage - the message to be sent
+- * @param destination Member[] - the destinations
+- * @throws ChannelException - if an error happens, the ChannelSender MUST report
+- * individual send failures on a per member basis, using ChannelException.addFaultyMember
+- * @see ChannelException#addFaultyMember(Member,java.lang.Exception)
+- */
+- public void sendMessage(ChannelMessage message, Member[] destination) throws ChannelException;
+-}
+Index: java/org/apache/catalina/tribes/membership/LocalStrings.properties
+===================================================================
+--- java/org/apache/catalina/tribes/membership/LocalStrings.properties (revision 590752)
++++ java/org/apache/catalina/tribes/membership/LocalStrings.properties (working copy)
+@@ -1,16 +0,0 @@
+-# 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.
+-
+-cluster.mbean.register.already=MBean {0} already registered!
+Index: java/org/apache/catalina/tribes/membership/StaticMember.java
+===================================================================
+--- java/org/apache/catalina/tribes/membership/StaticMember.java (revision 590752)
++++ java/org/apache/catalina/tribes/membership/StaticMember.java (working copy)
+@@ -1,77 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.membership;
+-
+-import java.io.IOException;
+-import org.apache.catalina.tribes.util.Arrays;
+-
+-/**
+- * <p>Title: </p>
+- *
+- * <p>Description: </p>
+- *
+- * <p>Company: </p>
+- *
+- * @author not attributable
+- * @version 1.0
+- */
+-public class StaticMember extends MemberImpl {
+- public StaticMember() {
+- super();
+- }
+-
+- public StaticMember(String host, int port, long aliveTime) throws IOException {
+- super(host, port, aliveTime);
+- }
+-
+- public StaticMember(String host, int port, long aliveTime, byte[] payload) throws IOException {
+- super(host, port, aliveTime, payload);
+- }
+-
+- /**
+- * @param host String, either in byte array string format, like {214,116,1,3}
+- * or as a regular hostname, 127.0.0.1 or tomcat01.mydomain.com
+- */
+- public void setHost(String host) {
+- if ( host == null ) return;
+- if ( host.startsWith("{") ) setHost(Arrays.fromString(host));
+- else try { setHostname(host); }catch (IOException x) { throw new RuntimeException(x);}
+-
+- }
+-
+- /**
+- * @param domain String, either in byte array string format, like {214,116,1,3}
+- * or as a regular string value like 'mydomain'. The latter will be converted using ISO-8859-1 encoding
+- */
+- public void setDomain(String domain) {
+- if ( domain == null ) return;
+- if ( domain.startsWith("{") ) setDomain(Arrays.fromString(domain));
+- else setDomain(Arrays.convert(domain));
+- }
+-
+- /**
+- * @param id String, must be in byte array string format, like {214,116,1,3} and exactly 16 bytes long
+- */
+- public void setUniqueId(String id) {
+- byte[] uuid = Arrays.fromString(id);
+- if ( uuid==null || uuid.length != 16 ) throw new RuntimeException("UUID must be exactly 16 bytes, not:"+id);
+- setUniqueId(uuid);
+- }
+-
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/membership/mbeans-descriptors.xml
+===================================================================
+--- java/org/apache/catalina/tribes/membership/mbeans-descriptors.xml (revision 590752)
++++ java/org/apache/catalina/tribes/membership/mbeans-descriptors.xml (working copy)
+@@ -1,89 +0,0 @@
+-<?xml version="1.0" encoding="UTF-8"?>
+-<!--
+- 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.
+--->
+-<!DOCTYPE mbeans-descriptors PUBLIC
+- "-//Apache Software Foundation//DTD Model MBeans Configuration File"
+- "http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd">
+-<mbeans-descriptors>
+-
+- <mbean name="McastService"
+- description="Cluster Membership service implementation"
+- domain="Catalina"
+- group="Cluster"
+- type="org.apache.catalina.tribes.membership.McastService">
+- <attribute name="info"
+- description="Class version info"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute name="mcastAddr"
+- description="Multicast IP Address"
+- type="java.lang.String"/>
+- <attribute name="mcastBindAddress"
+- description="Multicast IP Interface address (default auto)"
+- type="java.lang.String"/>
+- <attribute name="mcastPort"
+- description="Multicast UDP Port"
+- type="int"/>
+- <attribute name="mcastFrequency"
+- description="Ping Frequency at msec"
+- type="long"/>
+- <attribute name="mcastClusterDomain"
+- description="Cluster Domain of this member"
+- type="java.lang.String"/>
+- <attribute name="mcastDropTime"
+- description="Timeout from frequency ping after member disapper notify"
+- type="long"/>
+- <attribute name="mcastSoTimeout"
+- description="Multicast Socket Timeout"
+- type="int"/>
+- <attribute name="mcastTTL"
+- description=""
+- type="int"/>
+- <attribute name="recoveryCounter"
+- description="Counter after membership failure socket restarted"
+- type="int"/>
+- <attribute name="recoveryEnabled"
+- description="Membership recovery enabled"
+- is="true"
+- type="boolean"/>
+- <attribute name="recoverySleepTime"
+- description="Sleep time between next socket recovery (5000 msec)"
+- type="long"/>
+- <attribute name="localMemberName"
+- description="Complete local receiver information"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute name="membersByName"
+- description="Complete remote sender information"
+- type="[Ljava.lang.String;"
+- writeable="false"/>
+-
+- <operation name="start"
+- description="Start the cluster membership"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- <operation name="stop"
+- description="Stop the cluster membership"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- </mbean>
+-
+-</mbeans-descriptors>
+Index: java/org/apache/catalina/tribes/membership/McastService.java
+===================================================================
+--- java/org/apache/catalina/tribes/membership/McastService.java (revision 590752)
++++ java/org/apache/catalina/tribes/membership/McastService.java (working copy)
+@@ -1,574 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.membership;
+-
+-import java.util.Properties;
+-
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.MembershipListener;
+-import org.apache.catalina.tribes.MembershipService;
+-import org.apache.catalina.tribes.util.StringManager;
+-import org.apache.catalina.tribes.util.UUIDGenerator;
+-import java.io.IOException;
+-
+-/**
+- * A <b>membership</b> implementation using simple multicast.
+- * This is the representation of a multicast membership service.
+- * This class is responsible for maintaining a list of active cluster nodes in the cluster.
+- * If a node fails to send out a heartbeat, the node will be dismissed.
+- *
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-
+-
+-public class McastService implements MembershipService,MembershipListener {
+-
+- private static org.apache.juli.logging.Log log =
+- org.apache.juli.logging.LogFactory.getLog( McastService.class );
+-
+- /**
+- * The string manager for this package.
+- */
+- protected StringManager sm = StringManager.getManager(Constants.Package);
+-
+- /**
+- * The descriptive information about this implementation.
+- */
+- private static final String info = "McastService/2.1";
+-
+- /**
+- * The implementation specific properties
+- */
+- protected Properties properties = new Properties();
+- /**
+- * A handle to the actual low level implementation
+- */
+- protected McastServiceImpl impl;
+- /**
+- * A membership listener delegate (should be the cluster :)
+- */
+- protected MembershipListener listener;
+- /**
+- * The local member
+- */
+- protected MemberImpl localMember ;
+- private int mcastSoTimeout;
+- private int mcastTTL;
+-
+- protected byte[] payload;
+-
+- protected byte[] domain;
+-
+- /**
+- * Create a membership service.
+- */
+- public McastService() {
+- //default values
+- properties.setProperty("mcastPort","45564");
+- properties.setProperty("mcastAddress","228.0.0.4");
+- properties.setProperty("memberDropTime","3000");
+- properties.setProperty("mcastFrequency","500");
+-
+- }
+-
+- /**
+- * Return descriptive information about this implementation and the
+- * corresponding version number, in the format
+- * <code><description>/<version></code>.
+- */
+- public String getInfo() {
+- return (info);
+- }
+-
+- /**
+- *
+- * @param properties
+- * <BR/>All are required<BR />
+- * 1. mcastPort - the port to listen to<BR>
+- * 2. mcastAddress - the mcast group address<BR>
+- * 4. bindAddress - the bind address if any - only one that can be null<BR>
+- * 5. memberDropTime - the time a member is gone before it is considered gone.<BR>
+- * 6. mcastFrequency - the frequency of sending messages<BR>
+- * 7. tcpListenPort - the port this member listens to<BR>
+- * 8. tcpListenHost - the bind address of this member<BR>
+- * @exception java.lang.IllegalArgumentException if a property is missing.
+- */
+- public void setProperties(Properties properties) {
+- hasProperty(properties,"mcastPort");
+- hasProperty(properties,"mcastAddress");
+- hasProperty(properties,"memberDropTime");
+- hasProperty(properties,"mcastFrequency");
+- hasProperty(properties,"tcpListenPort");
+- hasProperty(properties,"tcpListenHost");
+- this.properties = properties;
+- }
+-
+- /**
+- * Return the properties, see setProperties
+- */
+- public Properties getProperties() {
+- return properties;
+- }
+-
+- /**
+- * Return the local member name
+- */
+- public String getLocalMemberName() {
+- return localMember.toString() ;
+- }
+-
+- /**
+- * Return the local member
+- */
+- public Member getLocalMember(boolean alive) {
+- if ( alive && localMember != null && impl != null) localMember.setMemberAliveTime(System.currentTimeMillis()-impl.getServiceStartTime());
+- return localMember;
+- }
+-
+- /**
+- * Sets the local member properties for broadcasting
+- */
+- public void setLocalMemberProperties(String listenHost, int listenPort) {
+- properties.setProperty("tcpListenHost",listenHost);
+- properties.setProperty("tcpListenPort",String.valueOf(listenPort));
+- try {
+- if (localMember != null) {
+- localMember.setHostname(listenHost);
+- localMember.setPort(listenPort);
+- } else {
+- localMember = new MemberImpl(listenHost, listenPort, 0);
+- localMember.setUniqueId(UUIDGenerator.randomUUID(true));
+- localMember.setPayload(getPayload());
+- localMember.setDomain(getDomain());
+- }
+- localMember.getData(true, true);
+- }catch ( IOException x ) {
+- throw new IllegalArgumentException(x);
+- }
+- }
+-
+- public void setAddress(String addr) {
+- properties.setProperty("mcastAddress", addr);
+- }
+-
+- /**
+- * @deprecated use setAddress
+- * @param addr String
+- */
+- public void setMcastAddr(String addr) {
+- setAddress(addr);
+- }
+-
+- public String getAddress() {
+- return properties.getProperty("mcastAddress");
+- }
+-
+- /**
+- * @deprecated use getAddress
+- * @return String
+- */
+- public String getMcastAddr() {
+- return getAddress();
+- }
+-
+- public void setMcastBindAddress(String bindaddr) {
+- setBind(bindaddr);
+- }
+-
+- public void setBind(String bindaddr) {
+- properties.setProperty("mcastBindAddress", bindaddr);
+- }
+- /**
+- * @deprecated use getBind
+- * @return String
+- */
+- public String getMcastBindAddress() {
+- return getBind();
+- }
+-
+- public String getBind() {
+- return properties.getProperty("mcastBindAddress");
+- }
+-
+- /**
+- * @deprecated use setPort
+- * @param port int
+- */
+- public void setMcastPort(int port) {
+- setPort(port);
+- }
+-
+- public void setPort(int port) {
+- properties.setProperty("mcastPort", String.valueOf(port));
+- }
+-
+- public void setRecoveryCounter(int recoveryCounter) {
+- properties.setProperty("recoveryCounter", String.valueOf(recoveryCounter));
+- }
+-
+- public void setRecoveryEnabled(boolean recoveryEnabled) {
+- properties.setProperty("recoveryEnabled", String.valueOf(recoveryEnabled));
+- }
+-
+- public void setRecoverySleepTime(long recoverySleepTime) {
+- properties.setProperty("recoverySleepTime", String.valueOf(recoverySleepTime));
+- }
+-
+-
+- /**
+- * @deprecated use getPort()
+- * @return int
+- */
+- public int getMcastPort() {
+- return getPort();
+- }
+- public int getPort() {
+- String p = properties.getProperty("mcastPort");
+- return new Integer(p).intValue();
+- }
+-
+- /**
+- * @deprecated use setFrequency
+- * @param time long
+- */
+- public void setMcastFrequency(long time) {
+- setFrequency(time);
+- }
+-
+- public void setFrequency(long time) {
+- properties.setProperty("mcastFrequency", String.valueOf(time));
+- }
+-
+- /**
+- * @deprecated use getFrequency
+- * @return long
+- */
+- public long getMcastFrequency() {
+- return getFrequency();
+- }
+-
+- public long getFrequency() {
+- String p = properties.getProperty("mcastFrequency");
+- return new Long(p).longValue();
+- }
+-
+- public void setMcastDropTime(long time) {
+- setDropTime(time);
+- }
+- public void setDropTime(long time) {
+- properties.setProperty("memberDropTime", String.valueOf(time));
+- }
+-
+- /**
+- * @deprecated use getDropTime
+- * @return long
+- */
+- public long getMcastDropTime() {
+- return getDropTime();
+- }
+-
+- public long getDropTime() {
+- String p = properties.getProperty("memberDropTime");
+- return new Long(p).longValue();
+- }
+-
+- /**
+- * Check if a required property is available.
+- * @param properties The set of properties
+- * @param name The property to check for
+- */
+- protected void hasProperty(Properties properties, String name){
+- if ( properties.getProperty(name)==null) throw new IllegalArgumentException("McastService:Required property \""+name+"\" is missing.");
+- }
+-
+- /**
+- * Start broadcasting and listening to membership pings
+- * @throws java.lang.Exception if a IO error occurs
+- */
+- public void start() throws java.lang.Exception {
+- start(MembershipService.MBR_RX);
+- start(MembershipService.MBR_TX);
+- }
+-
+- public void start(int level) throws java.lang.Exception {
+- hasProperty(properties,"mcastPort");
+- hasProperty(properties,"mcastAddress");
+- hasProperty(properties,"memberDropTime");
+- hasProperty(properties,"mcastFrequency");
+- hasProperty(properties,"tcpListenPort");
+- hasProperty(properties,"tcpListenHost");
+-
+- if ( impl != null ) {
+- impl.start(level);
+- return;
+- }
+- String host = getProperties().getProperty("tcpListenHost");
+- int port = Integer.parseInt(getProperties().getProperty("tcpListenPort"));
+-
+- if ( localMember == null ) {
+- localMember = new MemberImpl(host, port, 100);
+- localMember.setUniqueId(UUIDGenerator.randomUUID(true));
+- } else {
+- localMember.setHostname(host);
+- localMember.setPort(port);
+- localMember.setMemberAliveTime(100);
+- }
+- if ( this.payload != null ) localMember.setPayload(payload);
+- if ( this.domain != null ) localMember.setDomain(domain);
+- localMember.setServiceStartTime(System.currentTimeMillis());
+- java.net.InetAddress bind = null;
+- if ( properties.getProperty("mcastBindAddress")!= null ) {
+- bind = java.net.InetAddress.getByName(properties.getProperty("mcastBindAddress"));
+- }
+- int ttl = -1;
+- int soTimeout = -1;
+- if ( properties.getProperty("mcastTTL") != null ) {
+- try {
+- ttl = Integer.parseInt(properties.getProperty("mcastTTL"));
+- } catch ( Exception x ) {
+- log.error("Unable to parse mcastTTL="+properties.getProperty("mcastTTL"),x);
+- }
+- }
+- if ( properties.getProperty("mcastSoTimeout") != null ) {
+- try {
+- soTimeout = Integer.parseInt(properties.getProperty("mcastSoTimeout"));
+- } catch ( Exception x ) {
+- log.error("Unable to parse mcastSoTimeout="+properties.getProperty("mcastSoTimeout"),x);
+- }
+- }
+-
+- impl = new McastServiceImpl((MemberImpl)localMember,Long.parseLong(properties.getProperty("mcastFrequency")),
+- Long.parseLong(properties.getProperty("memberDropTime")),
+- Integer.parseInt(properties.getProperty("mcastPort")),
+- bind,
+- java.net.InetAddress.getByName(properties.getProperty("mcastAddress")),
+- ttl,
+- soTimeout,
+- this);
+- String value = properties.getProperty("recoveryEnabled","true");
+- boolean recEnabled = Boolean.valueOf(value).booleanValue() ;
+- impl.setRecoveryEnabled(recEnabled);
+- int recCnt = Integer.parseInt(properties.getProperty("recoveryCounter","10"));
+- impl.setRecoveryCounter(recCnt);
+- long recSlpTime = Long.parseLong(properties.getProperty("recoverySleepTime","5000"));
+- impl.setRecoverySleepTime(recSlpTime);
+-
+-
+- impl.start(level);
+-
+-
+- }
+-
+-
+- /**
+- * Stop broadcasting and listening to membership pings
+- */
+- public void stop(int svc) {
+- try {
+- if ( impl != null && impl.stop(svc) ) impl = null;
+- } catch ( Exception x) {
+- log.error("Unable to stop the mcast service, level:"+svc+".",x);
+- }
+- }
+-
+-
+- /**
+- * Return all the members by name
+- */
+- public String[] getMembersByName() {
+- Member[] currentMembers = getMembers();
+- String [] membernames ;
+- if(currentMembers != null) {
+- membernames = new String[currentMembers.length];
+- for (int i = 0; i < currentMembers.length; i++) {
+- membernames[i] = currentMembers[i].toString() ;
+- }
+- } else
+- membernames = new String[0] ;
+- return membernames ;
+- }
+-
+- /**
+- * Return the member by name
+- */
+- public Member findMemberByName(String name) {
+- Member[] currentMembers = getMembers();
+- for (int i = 0; i < currentMembers.length; i++) {
+- if (name.equals(currentMembers[i].toString()))
+- return currentMembers[i];
+- }
+- return null;
+- }
+-
+- /**
+- * has members?
+- */
+- public boolean hasMembers() {
+- if ( impl == null || impl.membership == null ) return false;
+- return impl.membership.hasMembers();
+- }
+-
+- public Member getMember(Member mbr) {
+- if ( impl == null || impl.membership == null ) return null;
+- return impl.membership.getMember(mbr);
+- }
+-
+- /**
+- * Return all the members
+- */
+- protected static final Member[]EMPTY_MEMBERS = new Member[0];
+- public Member[] getMembers() {
+- if ( impl == null || impl.membership == null ) return EMPTY_MEMBERS;
+- return impl.membership.getMembers();
+- }
+- /**
+- * Add a membership listener, this version only supports one listener per service,
+- * so calling this method twice will result in only the second listener being active.
+- * @param listener The listener
+- */
+- public void setMembershipListener(MembershipListener listener) {
+- this.listener = listener;
+- }
+- /**
+- * Remove the membership listener
+- */
+- public void removeMembershipListener(){
+- listener = null;
+- }
+-
+- public void memberAdded(Member member) {
+- if ( listener!=null ) listener.memberAdded(member);
+- }
+-
+- /**
+- * Callback from the impl when a new member has been received
+- * @param member The member
+- */
+- public void memberDisappeared(Member member)
+- {
+- if ( listener!=null ) listener.memberDisappeared(member);
+- }
+-
+- /**
+- * @deprecated use getSoTimeout
+- * @return int
+- */
+- public int getMcastSoTimeout() {
+- return getSoTimeout();
+- }
+-
+- public int getSoTimeout() {
+- return mcastSoTimeout;
+- }
+-
+- /**
+- * @deprecated use setSoTimeout
+- * @param mcastSoTimeout int
+- */
+- public void setMcastSoTimeout(int mcastSoTimeout) {
+- setSoTimeout(mcastSoTimeout);
+- }
+-
+- public void setSoTimeout(int mcastSoTimeout) {
+- this.mcastSoTimeout = mcastSoTimeout;
+- properties.setProperty("mcastSoTimeout", String.valueOf(mcastSoTimeout));
+- }
+-
+- /**
+- * @deprecated use getTtl
+- * @return int
+- */
+- public int getMcastTTL() {
+- return getTtl();
+- }
+-
+- public int getTtl() {
+- return mcastTTL;
+- }
+-
+- public byte[] getPayload() {
+- return payload;
+- }
+-
+- public byte[] getDomain() {
+- return domain;
+- }
+-
+- /**
+- * @deprecated use setTtl
+- * @param mcastTTL int
+- */
+- public void setMcastTTL(int mcastTTL) {
+- setTtl(mcastTTL);
+- }
+-
+- public void setTtl(int mcastTTL) {
+- this.mcastTTL = mcastTTL;
+- properties.setProperty("mcastTTL", String.valueOf(mcastTTL));
+- }
+-
+- public void setPayload(byte[] payload) {
+- this.payload = payload;
+- if ( localMember != null ) {
+- localMember.setPayload(payload);
+- localMember.getData(true,true);
+- try {
+- if (impl != null) impl.send(false);
+- }catch ( Exception x ) {
+- log.error("Unable to send payload update.",x);
+- }
+- }
+- }
+-
+- public void setDomain(byte[] domain) {
+- this.domain = domain;
+- if ( localMember != null ) {
+- localMember.setDomain(domain);
+- localMember.getData(true,true);
+- try {
+- if (impl != null) impl.send(false);
+- }catch ( Exception x ) {
+- log.error("Unable to send domain update.",x);
+- }
+- }
+- }
+-
+- /**
+- * Simple test program
+- * @param args Command-line arguments
+- * @throws Exception If an error occurs
+- */
+- public static void main(String args[]) throws Exception {
+- if(log.isInfoEnabled())
+- log.info("Usage McastService hostname tcpport");
+- McastService service = new McastService();
+- java.util.Properties p = new java.util.Properties();
+- p.setProperty("mcastPort","5555");
+- p.setProperty("mcastAddress","224.10.10.10");
+- p.setProperty("mcastClusterDomain","catalina");
+- p.setProperty("bindAddress","localhost");
+- p.setProperty("memberDropTime","3000");
+- p.setProperty("mcastFrequency","500");
+- p.setProperty("tcpListenPort","4000");
+- p.setProperty("tcpListenHost","127.0.0.1");
+- service.setProperties(p);
+- service.start();
+- Thread.sleep(60*1000*60);
+- }
+-}
+Index: java/org/apache/catalina/tribes/membership/MemberImpl.java
+===================================================================
+--- java/org/apache/catalina/tribes/membership/MemberImpl.java (revision 590752)
++++ java/org/apache/catalina/tribes/membership/MemberImpl.java (working copy)
+@@ -1,601 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.membership;
+-
+-import java.io.IOException;
+-import java.io.ObjectInput;
+-import java.io.ObjectOutput;
+-import java.util.Arrays;
+-
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.io.XByteBuffer;
+-import org.apache.catalina.tribes.transport.SenderState;
+-
+-/**
+- * A <b>membership</b> implementation using simple multicast.
+- * This is the representation of a multicast member.
+- * Carries the host, and port of the this or other cluster nodes.
+- *
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-public class MemberImpl implements Member, java.io.Externalizable {
+-
+- /**
+- * Public properties specific to this implementation
+- */
+- public static final transient String TCP_LISTEN_PORT = "tcpListenPort";
+- public static final transient String TCP_LISTEN_HOST = "tcpListenHost";
+- public static final transient String MEMBER_NAME = "memberName";
+-
+- public static final transient byte[] TRIBES_MBR_BEGIN = new byte[] {84, 82, 73, 66, 69, 83, 45, 66};
+- public static final transient byte[] TRIBES_MBR_END = new byte[] {84, 82, 73, 66, 69, 83, 45, 69};
+-
+- /**
+- * The listen host for this member
+- */
+- protected byte[] host;
+- protected transient String hostname;
+- /**
+- * The tcp listen port for this member
+- */
+- protected int port;
+-
+- /**
+- * The tcp/SSL listen port for this member
+- */
+- protected int securePort = -1;
+-
+- /**
+- * Counter for how many broadcast messages have been sent from this member
+- */
+- protected int msgCount = 0;
+- /**
+- * The number of milliseconds since this members was
+- * created, is kept track of using the start time
+- */
+- protected long memberAliveTime = 0;
+-
+- /**
+- * For the local member only
+- */
+- protected transient long serviceStartTime;
+-
+- /**
+- * To avoid serialization over and over again, once the local dataPkg
+- * has been set, we use that to transmit data
+- */
+- protected transient byte[] dataPkg = null;
+-
+- /**
+- * Unique session Id for this member
+- */
+- protected byte[] uniqueId = new byte[16];
+-
+- /**
+- * Custom payload that an app framework can broadcast
+- * Also used to transport stop command.
+- */
+- protected byte[] payload = new byte[0];
+-
+- /**
+- * Command, so that the custom payload doesn't have to be used
+- * This is for internal tribes use, such as SHUTDOWN_COMMAND
+- */
+- protected byte[] command = new byte[0];
+-
+- /**
+- * Domain if we want to filter based on domain.
+- */
+- protected byte[] domain = new byte[0];
+-
+- /**
+- * Empty constructor for serialization
+- */
+- public MemberImpl() {
+-
+- }
+-
+- /**
+- * Construct a new member object
+- * @param name - the name of this member, cluster unique
+- * @param domain - the cluster domain name of this member
+- * @param host - the tcp listen host
+- * @param port - the tcp listen port
+- */
+- public MemberImpl(String host,
+- int port,
+- long aliveTime) throws IOException {
+- setHostname(host);
+- this.port = port;
+- this.memberAliveTime=aliveTime;
+- }
+-
+- public MemberImpl(String host,
+- int port,
+- long aliveTime,
+- byte[] payload) throws IOException {
+- this(host,port,aliveTime);
+- setPayload(payload);
+- }
+-
+- public boolean isReady() {
+- return SenderState.getSenderState(this).isReady();
+- }
+- public boolean isSuspect() {
+- return SenderState.getSenderState(this).isSuspect();
+- }
+- public boolean isFailing() {
+- return SenderState.getSenderState(this).isFailing();
+- }
+-
+- /**
+- * Increment the message count.
+- */
+- protected void inc() {
+- msgCount++;
+- }
+-
+- /**
+- * Create a data package to send over the wire representing this member.
+- * This is faster than serialization.
+- * @return - the bytes for this member deserialized
+- * @throws Exception
+- */
+- public byte[] getData() {
+- return getData(true);
+- }
+- /**
+- * Highly optimized version of serializing a member into a byte array
+- * Returns a cached byte[] reference, do not modify this data
+- * @param getalive boolean
+- * @return byte[]
+- */
+- public byte[] getData(boolean getalive) {
+- return getData(getalive,false);
+- }
+-
+-
+- public int getDataLength() {
+- return TRIBES_MBR_BEGIN.length+ //start pkg
+- 4+ //data length
+- 8+ //alive time
+- 4+ //port
+- 4+ //secure port
+- 1+ //host length
+- host.length+ //host
+- 4+ //command length
+- command.length+ //command
+- 4+ //domain length
+- domain.length+ //domain
+- 16+ //unique id
+- 4+ //payload length
+- payload.length+ //payload
+- TRIBES_MBR_END.length; //end pkg
+- }
+-
+- /**
+- *
+- * @param getalive boolean - calculate memberAlive time
+- * @param reset boolean - reset the cached data package, and create a new one
+- * @return byte[]
+- */
+- public byte[] getData(boolean getalive, boolean reset) {
+- if ( reset ) dataPkg = null;
+- //look in cache first
+- if ( dataPkg!=null ) {
+- if ( getalive ) {
+- //you'd be surprised, but System.currentTimeMillis
+- //shows up on the profiler
+- long alive=System.currentTimeMillis()-getServiceStartTime();
+- XByteBuffer.toBytes( (long) alive, dataPkg, TRIBES_MBR_BEGIN.length+4);
+- }
+- return dataPkg;
+- }
+-
+- //package looks like
+- //start package TRIBES_MBR_BEGIN.length
+- //package length - 4 bytes
+- //alive - 8 bytes
+- //port - 4 bytes
+- //secure port - 4 bytes
+- //host length - 1 byte
+- //host - hl bytes
+- //clen - 4 bytes
+- //command - clen bytes
+- //dlen - 4 bytes
+- //domain - dlen bytes
+- //uniqueId - 16 bytes
+- //payload length - 4 bytes
+- //payload plen bytes
+- //end package TRIBES_MBR_END.length
+- byte[] addr = host;
+- long alive=System.currentTimeMillis()-getServiceStartTime();
+- byte hl = (byte)addr.length;
+- byte[] data = new byte[getDataLength()];
+-
+- int bodylength = (getDataLength() - TRIBES_MBR_BEGIN.length - TRIBES_MBR_END.length - 4);
+-
+- int pos = 0;
+-
+- //TRIBES_MBR_BEGIN
+- System.arraycopy(TRIBES_MBR_BEGIN,0,data,pos,TRIBES_MBR_BEGIN.length);
+- pos += TRIBES_MBR_BEGIN.length;
+-
+- //body length
+- XByteBuffer.toBytes(bodylength,data,pos);
+- pos += 4;
+-
+- //alive data
+- XByteBuffer.toBytes((long)alive,data,pos);
+- pos += 8;
+- //port
+- XByteBuffer.toBytes(port,data,pos);
+- pos += 4;
+- //secure port
+- XByteBuffer.toBytes(securePort,data,pos);
+- pos += 4;
+- //host length
+- data[pos++] = hl;
+- //host
+- System.arraycopy(addr,0,data,pos,addr.length);
+- pos+=addr.length;
+- //clen - 4 bytes
+- XByteBuffer.toBytes(command.length,data,pos);
+- pos+=4;
+- //command - clen bytes
+- System.arraycopy(command,0,data,pos,command.length);
+- pos+=command.length;
+- //dlen - 4 bytes
+- XByteBuffer.toBytes(domain.length,data,pos);
+- pos+=4;
+- //domain - dlen bytes
+- System.arraycopy(domain,0,data,pos,domain.length);
+- pos+=domain.length;
+- //unique Id
+- System.arraycopy(uniqueId,0,data,pos,uniqueId.length);
+- pos+=uniqueId.length;
+- //payload
+- XByteBuffer.toBytes(payload.length,data,pos);
+- pos+=4;
+- System.arraycopy(payload,0,data,pos,payload.length);
+- pos+=payload.length;
+-
+- //TRIBES_MBR_END
+- System.arraycopy(TRIBES_MBR_END,0,data,pos,TRIBES_MBR_END.length);
+- pos += TRIBES_MBR_END.length;
+-
+- //create local data
+- dataPkg = data;
+- return data;
+- }
+- /**
+- * Deserializes a member from data sent over the wire
+- * @param data - the bytes received
+- * @return a member object.
+- */
+- public static MemberImpl getMember(byte[] data, MemberImpl member) {
+- return getMember(data,0,data.length,member);
+- }
+-
+- public static MemberImpl getMember(byte[] data, int offset, int length, MemberImpl member) {
+- //package looks like
+- //start package TRIBES_MBR_BEGIN.length
+- //package length - 4 bytes
+- //alive - 8 bytes
+- //port - 4 bytes
+- //secure port - 4 bytes
+- //host length - 1 byte
+- //host - hl bytes
+- //clen - 4 bytes
+- //command - clen bytes
+- //dlen - 4 bytes
+- //domain - dlen bytes
+- //uniqueId - 16 bytes
+- //payload length - 4 bytes
+- //payload plen bytes
+- //end package TRIBES_MBR_END.length
+-
+- int pos = offset;
+-
+- if (XByteBuffer.firstIndexOf(data,offset,TRIBES_MBR_BEGIN)!=pos) {
+- throw new IllegalArgumentException("Invalid package, should start with:"+org.apache.catalina.tribes.util.Arrays.toString(TRIBES_MBR_BEGIN));
+- }
+-
+- if ( length < (TRIBES_MBR_BEGIN.length+4) ) {
+- throw new ArrayIndexOutOfBoundsException("Member package to small to validate.");
+- }
+-
+- pos += TRIBES_MBR_BEGIN.length;
+-
+- int bodylength = XByteBuffer.toInt(data,pos);
+- pos += 4;
+-
+- if ( length < (bodylength+4+TRIBES_MBR_BEGIN.length+TRIBES_MBR_END.length) ) {
+- throw new ArrayIndexOutOfBoundsException("Not enough bytes in member package.");
+- }
+-
+- int endpos = pos+bodylength;
+- if (XByteBuffer.firstIndexOf(data,endpos,TRIBES_MBR_END)!=endpos) {
+- throw new IllegalArgumentException("Invalid package, should end with:"+org.apache.catalina.tribes.util.Arrays.toString(TRIBES_MBR_END));
+- }
+-
+-
+- byte[] alived = new byte[8];
+- System.arraycopy(data, pos, alived, 0, 8);
+- pos += 8;
+- byte[] portd = new byte[4];
+- System.arraycopy(data, pos, portd, 0, 4);
+- pos += 4;
+-
+- byte[] sportd = new byte[4];
+- System.arraycopy(data, pos, sportd, 0, 4);
+- pos += 4;
+-
+-
+-
+- byte hl = data[pos++];
+- byte[] addr = new byte[hl];
+- System.arraycopy(data, pos, addr, 0, hl);
+- pos += hl;
+-
+- int cl = XByteBuffer.toInt(data, pos);
+- pos += 4;
+-
+- byte[] command = new byte[cl];
+- System.arraycopy(data, pos, command, 0, command.length);
+- pos += command.length;
+-
+- int dl = XByteBuffer.toInt(data, pos);
+- pos += 4;
+-
+- byte[] domain = new byte[dl];
+- System.arraycopy(data, pos, domain, 0, domain.length);
+- pos += domain.length;
+-
+- byte[] uniqueId = new byte[16];
+- System.arraycopy(data, pos, uniqueId, 0, 16);
+- pos += 16;
+-
+- int pl = XByteBuffer.toInt(data, pos);
+- pos += 4;
+-
+- byte[] payload = new byte[pl];
+- System.arraycopy(data, pos, payload, 0, payload.length);
+- pos += payload.length;
+-
+- member.setHost(addr);
+- member.setPort(XByteBuffer.toInt(portd, 0));
+- member.setSecurePort(XByteBuffer.toInt(sportd, 0));
+- member.setMemberAliveTime(XByteBuffer.toLong(alived, 0));
+- member.setUniqueId(uniqueId);
+- member.payload = payload;
+- member.domain = domain;
+- member.command = command;
+-
+- member.dataPkg = new byte[length];
+- System.arraycopy(data, offset, member.dataPkg, 0, length);
+-
+- return member;
+- }
+-
+- public static MemberImpl getMember(byte[] data) {
+- return getMember(data,new MemberImpl());
+- }
+-
+- public static MemberImpl getMember(byte[] data, int offset, int length) {
+- return getMember(data,offset,length,new MemberImpl());
+- }
+-
+- /**
+- * Return the name of this object
+- * @return a unique name to the cluster
+- */
+- public String getName() {
+- return "tcp://"+getHostname()+":"+getPort();
+- }
+-
+- /**
+- * Return the listen port of this member
+- * @return - tcp listen port
+- */
+- public int getPort() {
+- return this.port;
+- }
+-
+- /**
+- * Return the TCP listen host for this member
+- * @return IP address or host name
+- */
+- public byte[] getHost() {
+- return host;
+- }
+-
+- public String getHostname() {
+- if ( this.hostname != null ) return hostname;
+- else {
+- try {
+- this.hostname = java.net.InetAddress.getByAddress(host).getHostName();
+- return this.hostname;
+- }catch ( IOException x ) {
+- throw new RuntimeException("Unable to parse hostname.",x);
+- }
+- }
+- }
+-
+- /**
+- * Contains information on how long this member has been online.
+- * The result is the number of milli seconds this member has been
+- * broadcasting its membership to the cluster.
+- * @return nr of milliseconds since this member started.
+- */
+- public long getMemberAliveTime() {
+- return memberAliveTime;
+- }
+-
+- public long getServiceStartTime() {
+- return serviceStartTime;
+- }
+-
+- public byte[] getUniqueId() {
+- return uniqueId;
+- }
+-
+- public byte[] getPayload() {
+- return payload;
+- }
+-
+- public byte[] getCommand() {
+- return command;
+- }
+-
+- public byte[] getDomain() {
+- return domain;
+- }
+-
+- public int getSecurePort() {
+- return securePort;
+- }
+-
+- public void setMemberAliveTime(long time) {
+- memberAliveTime=time;
+- }
+-
+-
+-
+- /**
+- * String representation of this object
+- */
+- public String toString() {
+- StringBuffer buf = new StringBuffer("org.apache.catalina.tribes.membership.MemberImpl[");
+- buf.append(getName()).append(",");
+- buf.append(getHostname()).append(",");
+- buf.append(port).append(", alive=");
+- buf.append(memberAliveTime).append(",");
+- buf.append("id=").append(bToS(this.uniqueId)).append(", ");
+- buf.append("payload=").append(bToS(this.payload,8)).append(", ");
+- buf.append("command=").append(bToS(this.command,8)).append(", ");
+- buf.append("domain=").append(bToS(this.domain,8)).append(", ");
+- buf.append("]");
+- return buf.toString();
+- }
+- public static String bToS(byte[] data) {
+- return bToS(data,data.length);
+- }
+- public static String bToS(byte[] data, int max) {
+- StringBuffer buf = new StringBuffer(4*16);
+- buf.append("{");
+- for (int i=0; data!=null && i<data.length; i++ ) {
+- buf.append(String.valueOf(data[i])).append(" ");
+- if ( i==max ) {
+- buf.append("...("+data.length+")");
+- break;
+- }
+- }
+- buf.append("}");
+- return buf.toString();
+- }
+-
+- /**
+- * @see java.lang.Object#hashCode()
+- * @return The hash code
+- */
+- public int hashCode() {
+- return getHost()[0]+getHost()[1]+getHost()[2]+getHost()[3];
+- }
+-
+- /**
+- * Returns true if the param o is a McastMember with the same name
+- * @param o
+- */
+- public boolean equals(Object o) {
+- if ( o instanceof MemberImpl ) {
+- return Arrays.equals(this.getHost(),((MemberImpl)o).getHost()) &&
+- this.getPort() == ((MemberImpl)o).getPort() &&
+- Arrays.equals(this.getUniqueId(),((MemberImpl)o).getUniqueId());
+- }
+- else
+- return false;
+- }
+-
+- public void setHost(byte[] host) {
+- this.host = host;
+- }
+-
+- public void setHostname(String host) throws IOException {
+- hostname = host;
+- this.host = java.net.InetAddress.getByName(host).getAddress();
+- }
+-
+- public void setMsgCount(int msgCount) {
+- this.msgCount = msgCount;
+- }
+-
+- public void setPort(int port) {
+- this.port = port;
+- this.dataPkg = null;
+- }
+-
+- public void setServiceStartTime(long serviceStartTime) {
+- this.serviceStartTime = serviceStartTime;
+- }
+-
+- public void setUniqueId(byte[] uniqueId) {
+- this.uniqueId = uniqueId!=null?uniqueId:new byte[16];
+- getData(true,true);
+- }
+-
+- public void setPayload(byte[] payload) {
+- byte[] oldpayload = this.payload;
+- this.payload = payload!=null?payload:new byte[0];
+- if ( this.getData(true,true).length > McastServiceImpl.MAX_PACKET_SIZE ) {
+- this.payload = oldpayload;
+- throw new IllegalArgumentException("Payload is to large for tribes to handle.");
+- }
+-
+- }
+-
+- public void setCommand(byte[] command) {
+- this.command = command!=null?command:new byte[0];
+- getData(true,true);
+- }
+-
+- public void setDomain(byte[] domain) {
+- this.domain = domain!=null?domain:new byte[0];
+- getData(true,true);
+- }
+-
+- public void setSecurePort(int securePort) {
+- this.securePort = securePort;
+- }
+-
+- public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+- int length = in.readInt();
+- byte[] message = new byte[length];
+- in.read(message);
+- getMember(message,this);
+-
+- }
+-
+- public void writeExternal(ObjectOutput out) throws IOException {
+- byte[] data = this.getData();
+- out.writeInt(data.length);
+- out.write(data);
+- }
+-
+-}
+Index: java/org/apache/catalina/tribes/membership/McastServiceImpl.java
+===================================================================
+--- java/org/apache/catalina/tribes/membership/McastServiceImpl.java (revision 590752)
++++ java/org/apache/catalina/tribes/membership/McastServiceImpl.java (working copy)
+@@ -1,534 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.membership;
+-
+-
+-import java.io.IOException;
+-import java.net.DatagramPacket;
+-import java.net.InetAddress;
+-import java.net.InetSocketAddress;
+-import java.net.MulticastSocket;
+-import java.net.SocketTimeoutException;
+-import java.util.Arrays;
+-
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.MembershipListener;
+-import java.net.BindException;
+-
+-/**
+- * A <b>membership</b> implementation using simple multicast.
+- * This is the representation of a multicast membership service.
+- * This class is responsible for maintaining a list of active cluster nodes in the cluster.
+- * If a node fails to send out a heartbeat, the node will be dismissed.
+- * This is the low level implementation that handles the multicasting sockets.
+- * Need to fix this, could use java.nio and only need one thread to send and receive, or
+- * just use a timeout on the receive
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-public class McastServiceImpl
+-{
+- private static org.apache.juli.logging.Log log =
+- org.apache.juli.logging.LogFactory.getLog( McastService.class );
+-
+- protected static int MAX_PACKET_SIZE = 65535;
+- /**
+- * Internal flag used for the listen thread that listens to the multicasting socket.
+- */
+- protected boolean doRunSender = false;
+- protected boolean doRunReceiver = false;
+- protected int startLevel = 0;
+- /**
+- * Socket that we intend to listen to
+- */
+- protected MulticastSocket socket;
+- /**
+- * The local member that we intend to broad cast over and over again
+- */
+- protected MemberImpl member;
+- /**
+- * The multicast address
+- */
+- protected InetAddress address;
+- /**
+- * The multicast port
+- */
+- protected int port;
+- /**
+- * The time it takes for a member to expire.
+- */
+- protected long timeToExpiration;
+- /**
+- * How often to we send out a broadcast saying we are alive, must be smaller than timeToExpiration
+- */
+- protected long sendFrequency;
+- /**
+- * Reuse the sendPacket, no need to create a new one everytime
+- */
+- protected DatagramPacket sendPacket;
+- /**
+- * Reuse the receivePacket, no need to create a new one everytime
+- */
+- protected DatagramPacket receivePacket;
+- /**
+- * The membership, used so that we calculate memberships when they arrive or don't arrive
+- */
+- protected Membership membership;
+- /**
+- * The actual listener, for callback when shits goes down
+- */
+- protected MembershipListener service;
+- /**
+- * Thread to listen for pings
+- */
+- protected ReceiverThread receiver;
+- /**
+- * Thread to send pings
+- */
+- protected SenderThread sender;
+-
+- /**
+- * When was the service started
+- */
+- protected long serviceStartTime = System.currentTimeMillis();
+-
+- /**
+- * Time to live for the multicast packets that are being sent out
+- */
+- protected int mcastTTL = -1;
+- /**
+- * Read timeout on the mcast socket
+- */
+- protected int mcastSoTimeout = -1;
+- /**
+- * bind address
+- */
+- protected InetAddress mcastBindAddress = null;
+-
+- /**
+- * nr of times the system has to fail before a recovery is initiated
+- */
+- protected int recoveryCounter = 10;
+-
+- /**
+- * The time the recovery thread sleeps between recovery attempts
+- */
+- protected long recoverySleepTime = 5000;
+-
+- /**
+- * Add the ability to turn on/off recovery
+- */
+- protected boolean recoveryEnabled = true;
+- /**
+- * Create a new mcast service impl
+- * @param member - the local member
+- * @param sendFrequency - the time (ms) in between pings sent out
+- * @param expireTime - the time (ms) for a member to expire
+- * @param port - the mcast port
+- * @param bind - the bind address (not sure this is used yet)
+- * @param mcastAddress - the mcast address
+- * @param service - the callback service
+- * @throws IOException
+- */
+- public McastServiceImpl(
+- MemberImpl member,
+- long sendFrequency,
+- long expireTime,
+- int port,
+- InetAddress bind,
+- InetAddress mcastAddress,
+- int ttl,
+- int soTimeout,
+- MembershipListener service)
+- throws IOException {
+- this.member = member;
+- this.address = mcastAddress;
+- this.port = port;
+- this.mcastSoTimeout = soTimeout;
+- this.mcastTTL = ttl;
+- this.mcastBindAddress = bind;
+- this.timeToExpiration = expireTime;
+- this.service = service;
+- this.sendFrequency = sendFrequency;
+- init();
+- }
+-
+- public void init() throws IOException {
+- setupSocket();
+- sendPacket = new DatagramPacket(new byte[MAX_PACKET_SIZE],MAX_PACKET_SIZE);
+- sendPacket.setAddress(address);
+- sendPacket.setPort(port);
+- receivePacket = new DatagramPacket(new byte[MAX_PACKET_SIZE],MAX_PACKET_SIZE);
+- receivePacket.setAddress(address);
+- receivePacket.setPort(port);
+- member.setCommand(new byte[0]);
+- member.getData(true, true);
+- if ( membership == null ) membership = new Membership(member);
+- }
+-
+- protected void setupSocket() throws IOException {
+- if (mcastBindAddress != null) {
+- try {
+- log.info("Attempting to bind the multicast socket to "+address+":"+port);
+- socket = new MulticastSocket(new InetSocketAddress(address,port));
+- } catch (BindException e) {
+- /*
+- * On some plattforms (e.g. Linux) it is not possible to bind
+- * to the multicast address. In this case only bind to the
+- * port.
+- */
+- log.info("Binding to multicast address, failed. Binding to port only.");
+- socket = new MulticastSocket(port);
+- }
+- } else {
+- socket = new MulticastSocket(port);
+- }
+- socket.setLoopbackMode(false); //hint that we don't need loop back messages
+- if (mcastBindAddress != null) {
+- if(log.isInfoEnabled())
+- log.info("Setting multihome multicast interface to:" +mcastBindAddress);
+- socket.setInterface(mcastBindAddress);
+- } //end if
+- //force a so timeout so that we don't block forever
+- if ( mcastSoTimeout <= 0 ) mcastSoTimeout = (int)sendFrequency;
+- if(log.isInfoEnabled())
+- log.info("Setting cluster mcast soTimeout to "+mcastSoTimeout);
+- socket.setSoTimeout(mcastSoTimeout);
+-
+- if ( mcastTTL >= 0 ) {
+- if(log.isInfoEnabled())
+- log.info("Setting cluster mcast TTL to " + mcastTTL);
+- socket.setTimeToLive(mcastTTL);
+- }
+- }
+-
+-
+-
+- /**
+- * Start the service
+- * @param level 1 starts the receiver, level 2 starts the sender
+- * @throws IOException if the service fails to start
+- * @throws IllegalStateException if the service is already started
+- */
+- public synchronized void start(int level) throws IOException {
+- boolean valid = false;
+- if ( (level & Channel.MBR_RX_SEQ)==Channel.MBR_RX_SEQ ) {
+- if ( receiver != null ) throw new IllegalStateException("McastService.receive already running.");
+- if ( sender == null ) socket.joinGroup(address);
+- doRunReceiver = true;
+- receiver = new ReceiverThread();
+- receiver.setDaemon(true);
+- receiver.start();
+- valid = true;
+- }
+- if ( (level & Channel.MBR_TX_SEQ)==Channel.MBR_TX_SEQ ) {
+- if ( sender != null ) throw new IllegalStateException("McastService.send already running.");
+- if ( receiver == null ) socket.joinGroup(address);
+- //make sure at least one packet gets out there
+- send(false);
+- doRunSender = true;
+- serviceStartTime = System.currentTimeMillis();
+- sender = new SenderThread(sendFrequency);
+- sender.setDaemon(true);
+- sender.start();
+- //we have started the receiver, but not yet waited for membership to establish
+- valid = true;
+- }
+- if (!valid) {
+- throw new IllegalArgumentException("Invalid start level. Only acceptable levels are Channel.MBR_RX_SEQ and Channel.MBR_TX_SEQ");
+- }
+- //pause, once or twice
+- waitForMembers(level);
+- startLevel = (startLevel | level);
+- }
+-
+- private void waitForMembers(int level) {
+- long memberwait = sendFrequency*2;
+- if(log.isInfoEnabled())
+- log.info("Sleeping for "+memberwait+" milliseconds to establish cluster membership, start level:"+level);
+- try {Thread.sleep(memberwait);}catch (InterruptedException ignore){}
+- if(log.isInfoEnabled())
+- log.info("Done sleeping, membership established, start level:"+level);
+- }
+-
+- /**
+- * Stops the service
+- * @throws IOException if the service fails to disconnect from the sockets
+- */
+- public synchronized boolean stop(int level) throws IOException {
+- boolean valid = false;
+-
+- if ( (level & Channel.MBR_RX_SEQ)==Channel.MBR_RX_SEQ ) {
+- valid = true;
+- doRunReceiver = false;
+- if ( receiver !=null ) receiver.interrupt();
+- receiver = null;
+- }
+- if ( (level & Channel.MBR_TX_SEQ)==Channel.MBR_TX_SEQ ) {
+- valid = true;
+- doRunSender = false;
+- if ( sender != null )sender.interrupt();
+- sender = null;
+- }
+-
+- if (!valid) {
+- throw new IllegalArgumentException("Invalid stop level. Only acceptable levels are Channel.MBR_RX_SEQ and Channel.MBR_TX_SEQ");
+- }
+- startLevel = (startLevel & (~level));
+- //we're shutting down, send a shutdown message and close the socket
+- if ( startLevel == 0 ) {
+- //send a stop message
+- member.setCommand(Member.SHUTDOWN_PAYLOAD);
+- member.getData(true, true);
+- send(false);
+- //leave mcast group
+- try {socket.leaveGroup(address);}catch ( Exception ignore){}
+- serviceStartTime = Long.MAX_VALUE;
+- }
+- return (startLevel == 0);
+- }
+-
+- /**
+- * Receive a datagram packet, locking wait
+- * @throws IOException
+- */
+- public void receive() throws IOException {
+- try {
+- socket.receive(receivePacket);
+- if(receivePacket.getLength() > MAX_PACKET_SIZE) {
+- log.error("Multicast packet received was too long, dropping package:"+receivePacket.getLength());
+- } else {
+- byte[] data = new byte[receivePacket.getLength()];
+- System.arraycopy(receivePacket.getData(), receivePacket.getOffset(), data, 0, data.length);
+- final MemberImpl m = MemberImpl.getMember(data);
+- if (log.isTraceEnabled()) log.trace("Mcast receive ping from member " + m);
+- Thread t = null;
+- if (Arrays.equals(m.getCommand(), Member.SHUTDOWN_PAYLOAD)) {
+- if (log.isDebugEnabled()) log.debug("Member has shutdown:" + m);
+- membership.removeMember(m);
+- t = new Thread() {
+- public void run() {
+- service.memberDisappeared(m);
+- }
+- };
+- } else if (membership.memberAlive(m)) {
+- if (log.isDebugEnabled()) log.debug("Mcast add member " + m);
+- t = new Thread() {
+- public void run() {
+- service.memberAdded(m);
+- }
+- };
+- } //end if
+- if ( t != null ) t.start();
+- }
+- } catch (SocketTimeoutException x ) {
+- //do nothing, this is normal, we don't want to block forever
+- //since the receive thread is the same thread
+- //that does membership expiration
+- }
+- checkExpired();
+- }
+-
+- protected Object expiredMutex = new Object();
+- protected void checkExpired() {
+- synchronized (expiredMutex) {
+- MemberImpl[] expired = membership.expire(timeToExpiration);
+- for (int i = 0; i < expired.length; i++) {
+- final MemberImpl member = expired[i];
+- if (log.isDebugEnabled())
+- log.debug("Mcast exipre member " + expired[i]);
+- try {
+- Thread t = new Thread() {
+- public void run() {
+- service.memberDisappeared(member);
+- }
+- };
+- t.start();
+- } catch (Exception x) {
+- log.error("Unable to process member disappeared message.", x);
+- }
+- }
+- }
+- }
+-
+- /**
+- * Send a ping
+- * @throws Exception
+- */
+- public void send(boolean checkexpired) throws IOException{
+- //ignore if we haven't started the sender
+- //if ( (startLevel&Channel.MBR_TX_SEQ) != Channel.MBR_TX_SEQ ) return;
+- member.inc();
+- if(log.isTraceEnabled())
+- log.trace("Mcast send ping from member " + member);
+- byte[] data = member.getData();
+- DatagramPacket p = new DatagramPacket(data,data.length);
+- p.setAddress(address);
+- p.setPort(port);
+- socket.send(p);
+- if ( checkexpired ) checkExpired();
+- }
+-
+- public long getServiceStartTime() {
+- return this.serviceStartTime;
+- }
+-
+- public int getRecoveryCounter() {
+- return recoveryCounter;
+- }
+-
+- public boolean isRecoveryEnabled() {
+- return recoveryEnabled;
+- }
+-
+- public long getRecoverySleepTime() {
+- return recoverySleepTime;
+- }
+-
+- public class ReceiverThread extends Thread {
+- int errorCounter = 0;
+- public ReceiverThread() {
+- super();
+- setName("Tribes-MembershipReceiver");
+- }
+- public void run() {
+- while ( doRunReceiver ) {
+- try {
+- receive();
+- errorCounter=0;
+- } catch ( ArrayIndexOutOfBoundsException ax ) {
+- //we can ignore this, as it means we have an invalid package
+- //but we will log it to debug
+- if ( log.isDebugEnabled() )
+- log.debug("Invalid member mcast package.",ax);
+- } catch ( Exception x ) {
+- if (errorCounter==0) log.warn("Error receiving mcast package. Sleeping 500ms",x);
+- else log.debug("Error receiving mcast package. Sleeping 500ms",x);
+- try { Thread.sleep(500); } catch ( Exception ignore ){}
+- if ( (++errorCounter)>=recoveryCounter ) {
+- errorCounter=0;
+- new RecoveryThread(McastServiceImpl.this);
+- }
+- }
+- }
+- }
+- }//class ReceiverThread
+-
+- public class SenderThread extends Thread {
+- long time;
+- int errorCounter=0;
+- public SenderThread(long time) {
+- this.time = time;
+- setName("Tribes-MembershipSender");
+-
+- }
+- public void run() {
+- while ( doRunSender ) {
+- try {
+- send(true);
+- errorCounter = 0;
+- } catch ( Exception x ) {
+- if (errorCounter==0) log.warn("Unable to send mcast message.",x);
+- else log.debug("Unable to send mcast message.",x);
+- if ( (++errorCounter)>=recoveryCounter ) {
+- errorCounter=0;
+- new RecoveryThread(McastServiceImpl.this);
+- }
+- }
+- try { Thread.sleep(time); } catch ( Exception ignore ) {}
+- }
+- }
+- }//class SenderThread
+-
+- protected static class RecoveryThread extends Thread {
+- static boolean running = false;
+- McastServiceImpl parent = null;
+- public RecoveryThread(McastServiceImpl parent) {
+- this.parent = parent;
+- if (!init(this)) parent = null;
+- }
+-
+- public static synchronized boolean init(RecoveryThread t) {
+- if ( running ) return false;
+- if ( !t.parent.isRecoveryEnabled()) return false;
+- running = true;
+- t.setName("Tribes-MembershipRecovery");
+- t.setDaemon(true);
+- t.start();
+- return true;
+- }
+-
+- public boolean stopService() {
+- try {
+- parent.stop(Channel.MBR_RX_SEQ | Channel.MBR_TX_SEQ);
+- return true;
+- } catch (Exception x) {
+- log.warn("Recovery thread failed to stop membership service.", x);
+- return false;
+- }
+- }
+- public boolean startService() {
+- try {
+- parent.init();
+- parent.start(Channel.MBR_RX_SEQ | Channel.MBR_TX_SEQ);
+- return true;
+- } catch (Exception x) {
+- log.warn("Recovery thread failed to start membership service.", x);
+- return false;
+- }
+- }
+- public void run() {
+- boolean success = false;
+- int attempt = 0;
+- try {
+- while (!success) {
+- if(log.isInfoEnabled())
+- log.info("Tribes membership, running recovery thread, multicasting is not functional.");
+- if (stopService() & startService()) {
+- success = true;
+- if(log.isInfoEnabled())
+- log.info("Membership recovery was successful.");
+- }
+- try {
+- if (!success) {
+- if(log.isInfoEnabled())
+- log.info("Recovery attempt "+(++attempt)+" failed, trying again in " +parent.recoverySleepTime+ " seconds");
+- Thread.sleep(parent.recoverySleepTime);
+- }
+- }catch (InterruptedException ignore) {
+- }
+- }
+- }finally {
+- running = false;
+- }
+- }
+- }
+-
+- public void setRecoveryCounter(int recoveryCounter) {
+- this.recoveryCounter = recoveryCounter;
+- }
+-
+- public void setRecoveryEnabled(boolean recoveryEnabled) {
+- this.recoveryEnabled = recoveryEnabled;
+- }
+-
+- public void setRecoverySleepTime(long recoverySleepTime) {
+- this.recoverySleepTime = recoverySleepTime;
+- }
+-}
+Index: java/org/apache/catalina/tribes/membership/Membership.java
+===================================================================
+--- java/org/apache/catalina/tribes/membership/Membership.java (revision 590752)
++++ java/org/apache/catalina/tribes/membership/Membership.java (working copy)
+@@ -1,324 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.membership;
+-
+-
+-import java.util.ArrayList;
+-import java.util.Arrays;
+-import java.util.HashMap;
+-import java.util.Iterator;
+-
+-import org.apache.catalina.tribes.Member;
+-import java.util.Comparator;
+-
+-/**
+- * A <b>membership</b> implementation using simple multicast.
+- * This is the representation of a multicast membership.
+- * This class is responsible for maintaining a list of active cluster nodes in the cluster.
+- * If a node fails to send out a heartbeat, the node will be dismissed.
+- *
+- * @author Filip Hanik
+- * @author Peter Rossbach
+- * @version $Revision$, $Date$
+- */
+-public class Membership
+-{
+- protected static final MemberImpl[] EMPTY_MEMBERS = new MemberImpl[0];
+-
+- /**
+- * The name of this membership, has to be the same as the name for the local
+- * member
+- */
+- protected MemberImpl local;
+-
+- /**
+- * A map of all the members in the cluster.
+- */
+- protected HashMap map = new HashMap();
+-
+- /**
+- * A list of all the members in the cluster.
+- */
+- protected MemberImpl[] members = EMPTY_MEMBERS;
+-
+- /**
+- * sort members by alive time
+- */
+- protected Comparator memberComparator = new MemberComparator();
+-
+- public Object clone() {
+- synchronized (members) {
+- Membership clone = new Membership(local, memberComparator);
+- clone.map = (HashMap) map.clone();
+- clone.members = new MemberImpl[members.length];
+- System.arraycopy(members,0,clone.members,0,members.length);
+- return clone;
+- }
+- }
+-
+- /**
+- * Constructs a new membership
+- * @param name - has to be the name of the local member. Used to filter the local member from the cluster membership
+- */
+- public Membership(MemberImpl local, boolean includeLocal) {
+- this.local = local;
+- if ( includeLocal ) addMember(local);
+- }
+-
+- public Membership(MemberImpl local) {
+- this(local,false);
+- }
+-
+- public Membership(MemberImpl local, Comparator comp) {
+- this(local,comp,false);
+- }
+-
+- public Membership(MemberImpl local, Comparator comp, boolean includeLocal) {
+- this(local,includeLocal);
+- this.memberComparator = comp;
+- }
+- /**
+- * Reset the membership and start over fresh.
+- * Ie, delete all the members and wait for them to ping again and join this membership
+- */
+- public synchronized void reset() {
+- map.clear();
+- members = EMPTY_MEMBERS ;
+- }
+-
+- /**
+- * Notify the membership that this member has announced itself.
+- *
+- * @param member - the member that just pinged us
+- * @return - true if this member is new to the cluster, false otherwise.
+- * @return - false if this member is the local member or updated.
+- */
+- public synchronized boolean memberAlive(MemberImpl member) {
+- boolean result = false;
+- //ignore ourselves
+- if ( member.equals(local) ) return result;
+-
+- //return true if the membership has changed
+- MbrEntry entry = (MbrEntry)map.get(member);
+- if ( entry == null ) {
+- entry = addMember(member);
+- result = true;
+- } else {
+- //update the member alive time
+- MemberImpl updateMember = entry.getMember() ;
+- if(updateMember.getMemberAliveTime() != member.getMemberAliveTime()) {
+- //update fields that can change
+- updateMember.setMemberAliveTime(member.getMemberAliveTime());
+- updateMember.setPayload(member.getPayload());
+- updateMember.setCommand(member.getCommand());
+- Arrays.sort(members, memberComparator);
+- }
+- }
+- entry.accessed();
+- return result;
+- }
+-
+- /**
+- * Add a member to this component and sort array with memberComparator
+- * @param member The member to add
+- */
+- public synchronized MbrEntry addMember(MemberImpl member) {
+- synchronized (members) {
+- MbrEntry entry = new MbrEntry(member);
+- if (!map.containsKey(member) ) {
+- map.put(member, entry);
+- MemberImpl results[] = new MemberImpl[members.length + 1];
+- for (int i = 0; i < members.length; i++) results[i] = members[i];
+- results[members.length] = member;
+- members = results;
+- Arrays.sort(members, memberComparator);
+- }
+- return entry;
+- }
+- }
+-
+- /**
+- * Remove a member from this component.
+- *
+- * @param member The member to remove
+- */
+- public void removeMember(MemberImpl member) {
+- map.remove(member);
+- synchronized (members) {
+- int n = -1;
+- for (int i = 0; i < members.length; i++) {
+- if (members[i] == member || members[i].equals(member)) {
+- n = i;
+- break;
+- }
+- }
+- if (n < 0) return;
+- MemberImpl results[] = new MemberImpl[members.length - 1];
+- int j = 0;
+- for (int i = 0; i < members.length; i++) {
+- if (i != n)
+- results[j++] = members[i];
+- }
+- members = results;
+- }
+- }
+-
+- /**
+- * Runs a refresh cycle and returns a list of members that has expired.
+- * This also removes the members from the membership, in such a way that
+- * getMembers() = getMembers() - expire()
+- * @param maxtime - the max time a member can remain unannounced before it is considered dead.
+- * @return the list of expired members
+- */
+- public synchronized MemberImpl[] expire(long maxtime) {
+- if(!hasMembers() )
+- return EMPTY_MEMBERS;
+-
+- ArrayList list = null;
+- Iterator i = map.values().iterator();
+- while(i.hasNext()) {
+- MbrEntry entry = (MbrEntry)i.next();
+- if( entry.hasExpired(maxtime) ) {
+- if(list == null) // only need a list when members are expired (smaller gc)
+- list = new java.util.ArrayList();
+- list.add(entry.getMember());
+- }
+- }
+-
+- if(list != null) {
+- MemberImpl[] result = new MemberImpl[list.size()];
+- list.toArray(result);
+- for( int j=0; j<result.length; j++) {
+- removeMember(result[j]);
+- }
+- return result;
+- } else {
+- return EMPTY_MEMBERS ;
+- }
+- }
+-
+- /**
+- * Returning that service has members or not
+- */
+- public boolean hasMembers() {
+- return members.length > 0 ;
+- }
+-
+-
+- public MemberImpl getMember(Member mbr) {
+- if(hasMembers()) {
+- MemberImpl result = null;
+- for ( int i=0; i<this.members.length && result==null; i++ ) {
+- if ( members[i].equals(mbr) ) result = members[i];
+- }//for
+- return result;
+- } else {
+- return null;
+- }
+- }
+-
+- public boolean contains(Member mbr) {
+- return getMember(mbr)!=null;
+- }
+-
+- /**
+- * Returning a list of all the members in the membership
+- * We not need a copy: add and remove generate new arrays.
+- */
+- public MemberImpl[] getMembers() {
+- if(hasMembers()) {
+- return members;
+- } else {
+- return EMPTY_MEMBERS;
+- }
+- }
+-
+- /**
+- * get a copy from all member entries
+- */
+- protected synchronized MbrEntry[] getMemberEntries()
+- {
+- MbrEntry[] result = new MbrEntry[map.size()];
+- java.util.Iterator i = map.entrySet().iterator();
+- int pos = 0;
+- while ( i.hasNext() )
+- result[pos++] = ((MbrEntry)((java.util.Map.Entry)i.next()).getValue());
+- return result;
+- }
+-
+- // --------------------------------------------- Inner Class
+-
+- private class MemberComparator implements java.util.Comparator {
+-
+- public int compare(Object o1, Object o2) {
+- try {
+- return compare((MemberImpl) o1, (MemberImpl) o2);
+- } catch (ClassCastException x) {
+- return 0;
+- }
+- }
+-
+- public int compare(MemberImpl m1, MemberImpl m2) {
+- //longer alive time, means sort first
+- long result = m2.getMemberAliveTime() - m1.getMemberAliveTime();
+- if (result < 0)
+- return -1;
+- else if (result == 0)
+- return 0;
+- else
+- return 1;
+- }
+- }
+-
+- /**
+- * Inner class that represents a member entry
+- */
+- protected static class MbrEntry
+- {
+-
+- protected MemberImpl mbr;
+- protected long lastHeardFrom;
+-
+- public MbrEntry(MemberImpl mbr) {
+- this.mbr = mbr;
+- }
+-
+- /**
+- * Indicate that this member has been accessed.
+- */
+- public void accessed(){
+- lastHeardFrom = System.currentTimeMillis();
+- }
+-
+- /**
+- * Return the actual Member object
+- */
+- public MemberImpl getMember() {
+- return mbr;
+- }
+-
+- /**
+- * Check if this dude has expired
+- * @param maxtime The time threshold
+- */
+- public boolean hasExpired(long maxtime) {
+- long delta = System.currentTimeMillis() - lastHeardFrom;
+- return delta > maxtime;
+- }
+- }
+-}
+Index: java/org/apache/catalina/tribes/membership/Constants.java
+===================================================================
+--- java/org/apache/catalina/tribes/membership/Constants.java (revision 590752)
++++ java/org/apache/catalina/tribes/membership/Constants.java (working copy)
+@@ -1,40 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-
+-package org.apache.catalina.tribes.membership;
+-
+-import org.apache.catalina.tribes.util.Arrays;
+-
+-
+-/**
+- * Manifest constants for the <code>org.apache.catalina.tribes.membership</code>
+- * package.
+- *
+- * @author Peter Rossbach
+- * @version $Revision$ $Date$
+- * @author Filip Hanik
+- */
+-
+-public class Constants {
+-
+- public static final String Package = "org.apache.catalina.tribes.membership";
+- public static void main(String[] args) throws Exception {
+- System.out.println(Arrays.toString("TRIBES-B".getBytes()));
+- System.out.println(Arrays.toString("TRIBES-E".getBytes()));
+- }
+-}
+Index: java/org/apache/catalina/tribes/Constants.java
+===================================================================
+--- java/org/apache/catalina/tribes/Constants.java (revision 590752)
++++ java/org/apache/catalina/tribes/Constants.java (working copy)
+@@ -1,32 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-
+-package org.apache.catalina.tribes;
+-
+-/**
+- * Manifest constants for the <code>org.apache.catalina.tribes</code>
+- * package.
+- *
+- * @author Bip Thelin
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-
+-public final class Constants {
+- public static final String Package = "org.apache.catalina.tribes";
+-}
+Index: java/org/apache/catalina/tribes/MembershipService.java
+===================================================================
+--- java/org/apache/catalina/tribes/MembershipService.java (revision 590752)
++++ java/org/apache/catalina/tribes/MembershipService.java (working copy)
+@@ -1,135 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes;
+-
+-
+-/**
+- * MembershipService Interface<br>
+- * The <code>MembershipService</code> interface is the membership component
+- * at the bottom layer, the IO layer (for layers see the javadoc for the {@link Channel} interface).<br>
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-
+-
+-public interface MembershipService {
+-
+- public static final int MBR_RX = Channel.MBR_RX_SEQ;
+- public static final int MBR_TX = Channel.MBR_TX_SEQ;
+-
+- /**
+- * Sets the properties for the membership service. This must be called before
+- * the <code>start()</code> method is called.
+- * The properties are implementation specific.
+- * @param properties - to be used to configure the membership service.
+- */
+- public void setProperties(java.util.Properties properties);
+- /**
+- * Returns the properties for the configuration used.
+- */
+- public java.util.Properties getProperties();
+- /**
+- * Starts the membership service. If a membership listeners is added
+- * the listener will start to receive membership events.
+- * Performs a start level 1 and 2
+- * @throws java.lang.Exception if the service fails to start.
+- */
+- public void start() throws java.lang.Exception;
+-
+- /**
+- * Starts the membership service. If a membership listeners is added
+- * the listener will start to receive membership events.
+- * @param level - level MBR_RX starts listening for members, level MBR_TX
+- * starts broad casting the server
+- * @throws java.lang.Exception if the service fails to start.
+- * @throws java.lang.IllegalArgumentException if the level is incorrect.
+- */
+- public void start(int level) throws java.lang.Exception;
+-
+-
+- /**
+- * Starts the membership service. If a membership listeners is added
+- * the listener will start to receive membership events.
+- * @param level - level MBR_RX stops listening for members, level MBR_TX
+- * stops broad casting the server
+- * @throws java.lang.Exception if the service fails to stop
+- * @throws java.lang.IllegalArgumentException if the level is incorrect.
+- */
+-
+- public void stop(int level);
+-
+- /**
+- * @return true if the the group contains members
+- */
+- public boolean hasMembers();
+-
+-
+- /**
+- *
+- * @param mbr Member
+- * @return Member
+- */
+- public Member getMember(Member mbr);
+- /**
+- * Returns a list of all the members in the cluster.
+- */
+-
+- public Member[] getMembers();
+-
+- /**
+- * Returns the member object that defines this member
+- */
+- public Member getLocalMember(boolean incAliveTime);
+-
+- /**
+- * Return all members by name
+- */
+- public String[] getMembersByName() ;
+-
+- /**
+- * Return the member by name
+- */
+- public Member findMemberByName(String name) ;
+-
+- /**
+- * Sets the local member properties for broadcasting
+- */
+- public void setLocalMemberProperties(String listenHost, int listenPort);
+-
+- /**
+- * Sets the membership listener, only one listener can be added.
+- * If you call this method twice, the last listener will be used.
+- * @param listener The listener
+- */
+- public void setMembershipListener(MembershipListener listener);
+-
+- /**
+- * removes the membership listener.
+- */
+- public void removeMembershipListener();
+-
+- /**
+- * Set a payload to be broadcasted with each membership
+- * broadcast.
+- * @param payload byte[]
+- */
+- public void setPayload(byte[] payload);
+-
+- public void setDomain(byte[] domain);
+-
+-}
+Index: java/org/apache/catalina/tribes/transport/LocalStrings.properties
+===================================================================
+--- java/org/apache/catalina/tribes/transport/LocalStrings.properties (revision 590752)
++++ java/org/apache/catalina/tribes/transport/LocalStrings.properties (working copy)
+@@ -1,84 +0,0 @@
+-# 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.
+-
+-AsyncSocketSender.create.thread=Create sender [{0}:{1,number,integer}] queue thread to tcp background replication
+-AsyncSocketSender.queue.message=Queue message to [{0}:{1,number,integer}] id=[{2}] size={3}
+-AsyncSocketSender.send.error=Unable to asynchronously send session with id=[{0}] - message will be ignored.
+-AsyncSocketSender.queue.empty=Queue in sender [{0}:{1,number,integer}] returned null element!
+-cluster.mbean.register.already=MBean {0} already registered!
+-IDataSender.ack.eof=EOF reached at local port [{0}:{1,number,integer}]
+-IDataSender.ack.receive=Got ACK at local port [{0}:{1,number,integer}]
+-IDataSender.ack.missing=Unable to read acknowledgement from [{0}:{1,number,integer}] in {2,number,integer} ms. Disconnecting socket, and trying again.
+-IDataSender.ack.read=Read wait ack char '{2}' [{0}:{1,number,integer}]
+-IDataSender.ack.start=Waiting for ACK message [{0}:{1,number,integer}]
+-IDataSender.ack.wrong=Missing correct ACK after 10 bytes read at local port [{0}:{1,number,integer}]
+-IDataSender.closeSocket=Sender close socket to [{0}:{1,number,integer}] (close count {2,number,integer})
+-IDataSender.connect=Sender connect to [{0}:{1,number,integer}] (connect count {2,number,integer})
+-IDataSender.create=Create sender [{0}:{1,number,integer}]
+-IDataSender.disconnect=Sender disconnect from [{0}:{1,number,integer}] (disconnect count {2,number,integer})
+-IDataSender.message.disconnect=Message transfered: Sender can't disconnect from [{0}:{1,number,integer}]
+-IDataSender.message.create=Message transfered: Sender can't create current socket [{0}:{1,number,integer}]
+-IDataSender.openSocket=Sender open socket to [{0}:{1,number,integer}] (open count {2,number,integer})
+-IDataSender.openSocket.failure=Open sender socket [{0}:{1,number,integer}] failure! (open failure count {2,number,integer})
+-IDataSender.send.again=Send data again to [{0}:{1,number,integer}]
+-IDataSender.send.crash=Send message crashed [{0}:{1,number,integer}] type=[{2}], id=[{3}]
+-IDataSender.send.message=Send message to [{0}:{1,number,integer}] id=[{2}] size={3,number,integer}
+-IDataSender.send.lost=Message lost: [{0}:{1,number,integer}] type=[{2}], id=[{3}]
+-IDataSender.senderModes.Configured=Configured a data replication sender for mode {0}
+-IDataSender.senderModes.Instantiate=Can't instantiate a data replication sender of class {0}
+-IDataSender.senderModes.Missing=Can't configure a data replication sender for mode {0}
+-IDataSender.senderModes.Resources=Can't load data replication sender mapping list
+-IDataSender.stats=Send stats from [{0}:{1,number,integer}], Nr of bytes sent={2,number,integer} over {3} = {4,number,integer} bytes/request, processing time {5,number,integer} msec, avg processing time {6,number,integer} msec
+-PoolSocketSender.senderQueue.sender.failed=PoolSocketSender create new sender to [{0}:{1,number,integer}] failed
+-PoolSocketSender.noMoreSender=No socket sender available for client [{0}:{1,number,integer}] did it disappeared?
+-ReplicationTransmitter.getProperty=get property {0}
+-ReplicationTransmitter.setProperty=set property {0}: {1} old value {2}
+-ReplicationTransmitter.started=Start ClusterSender at cluster {0} with name {1}
+-ReplicationTransmitter.stopped=Stopped ClusterSender at cluster {0} with name {1}
+-ReplicationValve.crossContext.add=add Cross Context session replication container to replicationValve threadlocal
+-ReplicationValve.crossContext.registerSession=register Cross context session id={0} from context {1}
+-ReplicationValve.crossContext.remove=remove Cross Context session replication container from replicationValve threadlocal
+-ReplicationValve.crossContext.sendDelta=send Cross Context session delta from context {0}.
+-ReplicationValve.filter.loading=Loading request filters={0}
+-ReplicationValve.filter.token=Request filter={0}
+-ReplicationValve.filter.token.failure=Unable to compile filter={0}
+-ReplicationValve.invoke.uri=Invoking replication request on {0}
+-ReplicationValve.nocluster=No cluster configured for this request.
+-ReplicationValve.resetDeltaRequest=Cluster is standalone: reset Session Request Delta at context {0}
+-ReplicationValve.send.failure=Unable to perform replication request.
+-ReplicationValve.send.invalid.failure=Unable to send session [id={0}] invalid message over cluster.
+-ReplicationValve.session.found=Context {0}: Found session {1} but it isn't a ClusterSession.
+-ReplicationValve.session.indicator=Context {0}: Primarity of session {0} in request attribute {1} is {2}.
+-ReplicationValve.session.invalid=Context {0}: Requested session {1} is invalid, removed or not replicated at this node.
+-ReplicationValve.stats=Average request time= {0} ms for Cluster overhead time={1} ms for {2} requests {3} filter requests {4} send requests {5} cross context requests (Request={6} ms Cluster={7} ms).
+-SimpleTcpCluster.event.log=Cluster receive listener event {0} with data {1}
+-SimpleTcpCluster.getProperty=get property {0}
+-SimpleTcpCluster.setProperty=set property {0}: {1} old value {2}
+-SimpleTcpCluster.default.addClusterListener=Add Default ClusterListener at cluster {0}
+-SimpleTcpCluster.default.addClusterValves=Add Default ClusterValves at cluster {0}
+-SimpleTcpCluster.default.addClusterReceiver=Add Default ClusterReceiver at cluster {0}
+-SimpleTcpCluster.default.addClusterSender=Add Default ClusterSender at cluster {0}
+-SimpleTcpCluster.default.addMembershipService=Add Default Membership Service at cluster {0}
+-SimpleTcpCluster.log.receive=RECEIVE {0,date}:{0,time} {1,number} {2}:{3,number,integer} {4} {5}
+-SimpleTcpCluster.log.send=SEND {0,date}:{0,time} {1,number} {2}:{3,number,integer} {4}
+-SimpleTcpCluster.log.send.all=SEND {0,date}:{0,time} {1,number} - {2}
+-SocketReplictionListener.allreadyExists=ServerSocket [{0}:{1}] allready started!
+-SocketReplictionListener.accept.failure=ServerSocket [{0}:{1}] - Exception to start thread or accept server socket
+-SocketReplictionListener.open=Open Socket at [{0}:{1}]
+-SocketReplictionListener.openclose.failure=ServerSocket [{0}:{1}] - Exception to open or close server socket
+-SocketReplictionListener.portbusy=Port busy at [{0}:{i}] - reason [{2}]
+-SocketReplictionListener.serverSocket.notExists=Fatal error: Receiver socket not bound address={0} port={1} maxport={2}
+-SocketReplictionListener.timeout=Receiver ServerSocket no started [{0}:{1}] - reason: timeout={2} or listen={3}
+-SocketReplictionListener.unlockSocket.failure=UnLocksocket failure at ServerSocket [{0:{1}]
+Index: java/org/apache/catalina/tribes/transport/ReceiverBase.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/ReceiverBase.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/ReceiverBase.java (working copy)
+@@ -1,482 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.transport;
+-
+-import java.io.IOException;
+-import java.net.InetAddress;
+-import java.net.InetSocketAddress;
+-import java.net.ServerSocket;
+-import java.util.concurrent.ExecutorService;
+-import java.util.concurrent.LinkedBlockingQueue;
+-import java.util.concurrent.ThreadPoolExecutor;
+-import java.util.concurrent.TimeUnit;
+-
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.ChannelReceiver;
+-import org.apache.catalina.tribes.MessageListener;
+-import org.apache.catalina.tribes.io.ListenCallback;
+-import org.apache.juli.logging.Log;
+-
+-/**
+- * <p>Title: </p>
+- *
+- * <p>Description: </p>
+- *
+- * <p>Company: </p>
+- *
+- * @author not attributable
+- * @version 1.0
+- */
+-public abstract class ReceiverBase implements ChannelReceiver, ListenCallback, RxTaskPool.TaskCreator {
+-
+- public static final int OPTION_DIRECT_BUFFER = 0x0004;
+-
+-
+- protected static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(ReceiverBase.class);
+-
+- private MessageListener listener;
+- private String host = "auto";
+- private InetAddress bind;
+- private int port = 4000;
+- private int securePort = -1;
+- private int rxBufSize = 43800;
+- private int txBufSize = 25188;
+- private boolean listen = false;
+- private RxTaskPool pool;
+- private boolean direct = true;
+- private long tcpSelectorTimeout = 5000;
+- //how many times to search for an available socket
+- private int autoBind = 100;
+- private int maxThreads = Integer.MAX_VALUE;
+- private int minThreads = 6;
+- private int maxTasks = 100;
+- private int minTasks = 10;
+- private boolean tcpNoDelay = true;
+- private boolean soKeepAlive = false;
+- private boolean ooBInline = true;
+- private boolean soReuseAddress = true;
+- private boolean soLingerOn = true;
+- private int soLingerTime = 3;
+- private int soTrafficClass = 0x04 | 0x08 | 0x010;
+- private int timeout = 3000; //3 seconds
+- private boolean useBufferPool = true;
+-
+- private ExecutorService executor;
+-
+-
+- public ReceiverBase() {
+- }
+-
+- public void start() throws IOException {
+- if ( executor == null ) {
+- executor = new ThreadPoolExecutor(minThreads,maxThreads,60,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());
+- }
+- }
+-
+- public void stop() {
+- if ( executor != null ) executor.shutdownNow();//ignore left overs
+- executor = null;
+- }
+-
+- /**
+- * getMessageListener
+- *
+- * @return MessageListener
+- * @todo Implement this org.apache.catalina.tribes.ChannelReceiver method
+- */
+- public MessageListener getMessageListener() {
+- return listener;
+- }
+-
+- /**
+- *
+- * @return The port
+- * @todo Implement this org.apache.catalina.tribes.ChannelReceiver method
+- */
+- public int getPort() {
+- return port;
+- }
+-
+- public int getRxBufSize() {
+- return rxBufSize;
+- }
+-
+- public int getTxBufSize() {
+- return txBufSize;
+- }
+-
+- /**
+- * @deprecated use getMinThreads()/getMaxThreads()
+- * @return int
+- */
+- public int getTcpThreadCount() {
+- return getMaxThreads();
+- }
+-
+- /**
+- * setMessageListener
+- *
+- * @param listener MessageListener
+- * @todo Implement this org.apache.catalina.tribes.ChannelReceiver method
+- */
+- public void setMessageListener(MessageListener listener) {
+- this.listener = listener;
+- }
+-
+- /**
+- * @deprecated use setPort
+- * @param tcpListenPort int
+- */
+- public void setTcpListenPort(int tcpListenPort) {
+- setPort(tcpListenPort);
+- }
+-
+- /**
+- * @deprecated use setAddress
+- * @param tcpListenHost String
+- */
+- public void setTcpListenAddress(String tcpListenHost) {
+- setAddress(tcpListenHost);
+- }
+-
+- public void setRxBufSize(int rxBufSize) {
+- this.rxBufSize = rxBufSize;
+- }
+-
+- public void setTxBufSize(int txBufSize) {
+- this.txBufSize = txBufSize;
+- }
+-
+- /**
+- * @deprecated use setMaxThreads/setMinThreads
+- * @param tcpThreadCount int
+- */
+- public void setTcpThreadCount(int tcpThreadCount) {
+- setMaxThreads(tcpThreadCount);
+- setMinThreads(tcpThreadCount);
+- }
+-
+- /**
+- * @return Returns the bind.
+- */
+- public InetAddress getBind() {
+- if (bind == null) {
+- try {
+- if ("auto".equals(host)) {
+- host = java.net.InetAddress.getLocalHost().getHostAddress();
+- }
+- if (log.isDebugEnabled())
+- log.debug("Starting replication listener on address:"+ host);
+- bind = java.net.InetAddress.getByName(host);
+- } catch (IOException ioe) {
+- log.error("Failed bind replication listener on address:"+ host, ioe);
+- }
+- }
+- return bind;
+- }
+-
+- /**
+- * recursive bind to find the next available port
+- * @param socket ServerSocket
+- * @param portstart int
+- * @param retries int
+- * @return int
+- * @throws IOException
+- */
+- protected int bind(ServerSocket socket, int portstart, int retries) throws IOException {
+- InetSocketAddress addr = null;
+- while ( retries > 0 ) {
+- try {
+- addr = new InetSocketAddress(getBind(), portstart);
+- socket.bind(addr);
+- setPort(portstart);
+- log.info("Receiver Server Socket bound to:"+addr);
+- return 0;
+- }catch ( IOException x) {
+- retries--;
+- if ( retries <= 0 ) {
+- log.info("Unable to bind server socket to:"+addr+" throwing error.");
+- throw x;
+- }
+- portstart++;
+- try {Thread.sleep(25);}catch( InterruptedException ti){Thread.currentThread().interrupted();}
+- retries = bind(socket,portstart,retries);
+- }
+- }
+- return retries;
+- }
+-
+- public void messageDataReceived(ChannelMessage data) {
+- if ( this.listener != null ) {
+- if ( listener.accept(data) ) listener.messageReceived(data);
+- }
+- }
+-
+- public int getWorkerThreadOptions() {
+- int options = 0;
+- if ( getDirect() ) options = options | OPTION_DIRECT_BUFFER;
+- return options;
+- }
+-
+-
+- /**
+- * @param bind The bind to set.
+- */
+- public void setBind(java.net.InetAddress bind) {
+- this.bind = bind;
+- }
+-
+- /**
+- * @deprecated use getPort
+- * @return int
+- */
+- public int getTcpListenPort() {
+- return getPort();
+- }
+-
+-
+- public boolean getDirect() {
+- return direct;
+- }
+-
+-
+-
+- public void setDirect(boolean direct) {
+- this.direct = direct;
+- }
+-
+-
+- public String getAddress() {
+- getBind();
+- return this.host;
+- }
+-
+- public String getHost() {
+- return getAddress();
+- }
+-
+- public long getSelectorTimeout() {
+- return tcpSelectorTimeout;
+- }
+- /**
+- * @deprecated use getSelectorTimeout
+- * @return long
+- */
+- public long getTcpSelectorTimeout() {
+- return getSelectorTimeout();
+- }
+-
+- public boolean doListen() {
+- return listen;
+- }
+-
+- public MessageListener getListener() {
+- return listener;
+- }
+-
+- public RxTaskPool getTaskPool() {
+- return pool;
+- }
+-
+- /**
+- * @deprecated use getAddress
+- * @return String
+- */
+- public String getTcpListenAddress() {
+- return getAddress();
+- }
+-
+- public int getAutoBind() {
+- return autoBind;
+- }
+-
+- public int getMaxThreads() {
+- return maxThreads;
+- }
+-
+- public int getMinThreads() {
+- return minThreads;
+- }
+-
+- public boolean getTcpNoDelay() {
+- return tcpNoDelay;
+- }
+-
+- public boolean getSoKeepAlive() {
+- return soKeepAlive;
+- }
+-
+- public boolean getOoBInline() {
+- return ooBInline;
+- }
+-
+-
+- public boolean getSoLingerOn() {
+- return soLingerOn;
+- }
+-
+- public int getSoLingerTime() {
+- return soLingerTime;
+- }
+-
+- public boolean getSoReuseAddress() {
+- return soReuseAddress;
+- }
+-
+- public int getSoTrafficClass() {
+- return soTrafficClass;
+- }
+-
+- public int getTimeout() {
+- return timeout;
+- }
+-
+- public boolean getUseBufferPool() {
+- return useBufferPool;
+- }
+-
+- public int getSecurePort() {
+- return securePort;
+- }
+-
+- public int getMinTasks() {
+- return minTasks;
+- }
+-
+- public int getMaxTasks() {
+- return maxTasks;
+- }
+-
+- public ExecutorService getExecutor() {
+- return executor;
+- }
+-
+- public boolean isListening() {
+- return listen;
+- }
+-
+- /**
+- * @deprecated use setSelectorTimeout
+- * @param selTimeout long
+- */
+- public void setTcpSelectorTimeout(long selTimeout) {
+- setSelectorTimeout(selTimeout);
+- }
+-
+- public void setSelectorTimeout(long selTimeout) {
+- tcpSelectorTimeout = selTimeout;
+- }
+-
+- public void setListen(boolean doListen) {
+- this.listen = doListen;
+- }
+-
+-
+- public void setAddress(String host) {
+- this.host = host;
+- }
+- public void setHost(String host) {
+- setAddress(host);
+- }
+-
+- public void setListener(MessageListener listener) {
+- this.listener = listener;
+- }
+-
+- public void setLog(Log log) {
+- this.log = log;
+- }
+-
+- public void setPool(RxTaskPool pool) {
+- this.pool = pool;
+- }
+-
+- public void setPort(int port) {
+- this.port = port;
+- }
+-
+- public void setAutoBind(int autoBind) {
+- this.autoBind = autoBind;
+- if ( this.autoBind <= 0 ) this.autoBind = 1;
+- }
+-
+- public void setMaxThreads(int maxThreads) {
+- this.maxThreads = maxThreads;
+- }
+-
+- public void setMinThreads(int minThreads) {
+- this.minThreads = minThreads;
+- }
+-
+- public void setTcpNoDelay(boolean tcpNoDelay) {
+- this.tcpNoDelay = tcpNoDelay;
+- }
+-
+- public void setSoKeepAlive(boolean soKeepAlive) {
+- this.soKeepAlive = soKeepAlive;
+- }
+-
+- public void setOoBInline(boolean ooBInline) {
+- this.ooBInline = ooBInline;
+- }
+-
+-
+- public void setSoLingerOn(boolean soLingerOn) {
+- this.soLingerOn = soLingerOn;
+- }
+-
+- public void setSoLingerTime(int soLingerTime) {
+- this.soLingerTime = soLingerTime;
+- }
+-
+- public void setSoReuseAddress(boolean soReuseAddress) {
+- this.soReuseAddress = soReuseAddress;
+- }
+-
+- public void setSoTrafficClass(int soTrafficClass) {
+- this.soTrafficClass = soTrafficClass;
+- }
+-
+- public void setTimeout(int timeout) {
+- this.timeout = timeout;
+- }
+-
+- public void setUseBufferPool(boolean useBufferPool) {
+- this.useBufferPool = useBufferPool;
+- }
+-
+- public void setSecurePort(int securePort) {
+- this.securePort = securePort;
+- }
+-
+- public void setMinTasks(int minTasks) {
+- this.minTasks = minTasks;
+- }
+-
+- public void setMaxTasks(int maxTasks) {
+- this.maxTasks = maxTasks;
+- }
+-
+- public void setExecutor(ExecutorService executor) {
+- this.executor = executor;
+- }
+-
+- public void heartbeat() {
+- //empty operation
+- }
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/transport/AbstractRxTask.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/AbstractRxTask.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/AbstractRxTask.java (working copy)
+@@ -1,89 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.transport;
+-
+-import org.apache.catalina.tribes.io.ListenCallback;
+-
+-
+-
+-
+-/**
+- * @author Filip Hanik
+- * @version $Revision$ $Date$
+- */
+-public abstract class AbstractRxTask implements Runnable
+-{
+-
+- public static final int OPTION_DIRECT_BUFFER = ReceiverBase.OPTION_DIRECT_BUFFER;
+-
+- private ListenCallback callback;
+- private RxTaskPool pool;
+- private boolean doRun = true;
+- private int options;
+- protected boolean useBufferPool = true;
+-
+- public AbstractRxTask(ListenCallback callback) {
+- this.callback = callback;
+- }
+-
+- public void setTaskPool(RxTaskPool pool) {
+- this.pool = pool;
+- }
+-
+- public void setOptions(int options) {
+- this.options = options;
+- }
+-
+- public void setCallback(ListenCallback callback) {
+- this.callback = callback;
+- }
+-
+- public void setDoRun(boolean doRun) {
+- this.doRun = doRun;
+- }
+-
+- public RxTaskPool getTaskPool() {
+- return pool;
+- }
+-
+- public int getOptions() {
+- return options;
+- }
+-
+- public ListenCallback getCallback() {
+- return callback;
+- }
+-
+- public boolean isDoRun() {
+- return doRun;
+- }
+-
+- public void close()
+- {
+- doRun = false;
+- notify();
+- }
+-
+- public void setUseBufferPool(boolean usebufpool) {
+- useBufferPool = usebufpool;
+- }
+-
+- public boolean getUseBufferPool() {
+- return useBufferPool;
+- }
+-}
+Index: java/org/apache/catalina/tribes/transport/SenderState.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/SenderState.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/SenderState.java (working copy)
+@@ -1,115 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.transport;
+-
+-import org.apache.catalina.tribes.Member;
+-import java.util.HashMap;
+-
+-
+-/**
+- *
+- * @author Filip Hanik
+- * @version 1.0
+- * @since 5.5.16
+- */
+-
+-public class SenderState {
+-
+- public static final int READY = 0;
+- public static final int SUSPECT = 1;
+- public static final int FAILING = 2;
+- /**
+- * The descriptive information about this implementation.
+- */
+- private static final String info = "SenderState/1.0";
+-
+-
+- protected static HashMap memberStates = new HashMap();
+-
+- public static SenderState getSenderState(Member member) {
+- return getSenderState(member,true);
+- }
+-
+- public static SenderState getSenderState(Member member, boolean create) {
+- SenderState state = (SenderState)memberStates.get(member);
+- if ( state == null && create) {
+- synchronized ( memberStates ) {
+- state = (SenderState)memberStates.get(member);
+- if ( state == null ) {
+- state = new SenderState();
+- memberStates.put(member,state);
+- }
+- }
+- }
+- return state;
+- }
+-
+- public static void removeSenderState(Member member) {
+- synchronized ( memberStates ) {
+- memberStates.remove(member);
+- }
+- }
+-
+-
+- // ----------------------------------------------------- Instance Variables
+-
+- private int state = READY;
+-
+- // ----------------------------------------------------- Constructor
+-
+-
+- private SenderState() {
+- this(READY);
+- }
+-
+- private SenderState(int state) {
+- this.state = state;
+- }
+-
+- /**
+- *
+- * @return boolean
+- */
+- public boolean isSuspect() {
+- return (state == SUSPECT) || (state == FAILING);
+- }
+-
+- public void setSuspect() {
+- state = SUSPECT;
+- }
+-
+- public boolean isReady() {
+- return state == READY;
+- }
+-
+- public void setReady() {
+- state = READY;
+- }
+-
+- public boolean isFailing() {
+- return state == FAILING;
+- }
+-
+- public void setFailing() {
+- state = FAILING;
+- }
+-
+-
+- // ----------------------------------------------------- Public Properties
+-
+-}
+Index: java/org/apache/catalina/tribes/transport/PooledSender.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/PooledSender.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/PooledSender.java (working copy)
+@@ -1,215 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.transport;
+-
+-import java.io.IOException;
+-import java.util.List;
+-import org.apache.catalina.tribes.Member;
+-
+-/**
+- * <p>Title: </p>
+- *
+- * <p>Description: </p>
+- *
+- * <p>Company: </p>
+- *
+- * @author not attributable
+- * @version 1.0
+- */
+-public abstract class PooledSender extends AbstractSender implements MultiPointSender {
+-
+- private SenderQueue queue = null;
+- private int poolSize = 25;
+- public PooledSender() {
+- queue = new SenderQueue(this,poolSize);
+- }
+-
+- public abstract DataSender getNewDataSender();
+-
+- public DataSender getSender() {
+- return queue.getSender(getTimeout());
+- }
+-
+- public void returnSender(DataSender sender) {
+- sender.keepalive();
+- queue.returnSender(sender);
+- }
+-
+- public synchronized void connect() throws IOException {
+- //do nothing, happens in the socket sender itself
+- queue.open();
+- setConnected(true);
+- }
+-
+- public synchronized void disconnect() {
+- queue.close();
+- setConnected(false);
+- }
+-
+-
+- public int getInPoolSize() {
+- return queue.getInPoolSize();
+- }
+-
+- public int getInUsePoolSize() {
+- return queue.getInUsePoolSize();
+- }
+-
+-
+- public void setPoolSize(int poolSize) {
+- this.poolSize = poolSize;
+- queue.setLimit(poolSize);
+- }
+-
+- public int getPoolSize() {
+- return poolSize;
+- }
+-
+- public boolean keepalive() {
+- //do nothing, the pool checks on every return
+- return (queue==null)?false:queue.checkIdleKeepAlive();
+- }
+-
+- public void add(Member member) {
+- // no op, senders created upon demans
+- }
+-
+- public void remove(Member member) {
+- //no op for now, should not cancel out any keys
+- //can create serious sync issues
+- //all TCP connections are cleared out through keepalive
+- //and if remote node disappears
+- }
+- // ----------------------------------------------------- Inner Class
+-
+- private class SenderQueue {
+- private int limit = 25;
+-
+- PooledSender parent = null;
+-
+- private List notinuse = null;
+-
+- private List inuse = null;
+-
+- private boolean isOpen = true;
+-
+- public SenderQueue(PooledSender parent, int limit) {
+- this.limit = limit;
+- this.parent = parent;
+- notinuse = new java.util.LinkedList();
+- inuse = new java.util.LinkedList();
+- }
+-
+- /**
+- * @return Returns the limit.
+- */
+- public int getLimit() {
+- return limit;
+- }
+- /**
+- * @param limit The limit to set.
+- */
+- public void setLimit(int limit) {
+- this.limit = limit;
+- }
+- /**
+- * @return
+- */
+- public int getInUsePoolSize() {
+- return inuse.size();
+- }
+-
+- /**
+- * @return
+- */
+- public int getInPoolSize() {
+- return notinuse.size();
+- }
+-
+- public synchronized boolean checkIdleKeepAlive() {
+- DataSender[] list = new DataSender[notinuse.size()];
+- notinuse.toArray(list);
+- boolean result = false;
+- for (int i=0; i<list.length; i++) {
+- result = result | list[i].keepalive();
+- }
+- return result;
+- }
+-
+- public synchronized DataSender getSender(long timeout) {
+- long start = System.currentTimeMillis();
+- while ( true ) {
+- if (!isOpen)throw new IllegalStateException("Queue is closed");
+- DataSender sender = null;
+- if (notinuse.size() == 0 && inuse.size() < limit) {
+- sender = parent.getNewDataSender();
+- } else if (notinuse.size() > 0) {
+- sender = (DataSender) notinuse.remove(0);
+- }
+- if (sender != null) {
+- inuse.add(sender);
+- return sender;
+- }//end if
+- long delta = System.currentTimeMillis() - start;
+- if ( delta > timeout && timeout>0) return null;
+- else {
+- try {
+- wait(Math.max(timeout - delta,1));
+- }catch (InterruptedException x){}
+- }//end if
+- }
+- }
+-
+- public synchronized void returnSender(DataSender sender) {
+- if ( !isOpen) {
+- sender.disconnect();
+- return;
+- }
+- //to do
+- inuse.remove(sender);
+- //just in case the limit has changed
+- if ( notinuse.size() < this.getLimit() ) notinuse.add(sender);
+- else try {sender.disconnect(); } catch ( Exception ignore){}
+- notify();
+- }
+-
+- public synchronized void close() {
+- isOpen = false;
+- Object[] unused = notinuse.toArray();
+- Object[] used = inuse.toArray();
+- for (int i = 0; i < unused.length; i++) {
+- DataSender sender = (DataSender) unused[i];
+- sender.disconnect();
+- }//for
+- for (int i = 0; i < used.length; i++) {
+- DataSender sender = (DataSender) used[i];
+- sender.disconnect();
+- }//for
+- notinuse.clear();
+- inuse.clear();
+- notify();
+-
+-
+-
+- }
+-
+- public synchronized void open() {
+- isOpen = true;
+- notify();
+- }
+- }
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/transport/mbeans-descriptors.xml
+===================================================================
+--- java/org/apache/catalina/tribes/transport/mbeans-descriptors.xml (revision 590752)
++++ java/org/apache/catalina/tribes/transport/mbeans-descriptors.xml (working copy)
+@@ -1,851 +0,0 @@
+-<?xml version="1.0" encoding="UTF-8"?>
+-<!--
+- 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.
+--->
+-<!DOCTYPE mbeans-descriptors PUBLIC
+- "-//Apache Software Foundation//DTD Model MBeans Configuration File"
+- "http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd">
+-<mbeans-descriptors>
+-
+- <mbean name="SimpleTcpCluster"
+- description="Tcp Cluster implementation"
+- domain="Catalina"
+- group="Cluster"
+- type="org.apache.catalina.ha.tcp.SimpleTcpCluster">
+- <attribute name="info"
+- description="Class version info"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute name="notifyLifecycleListenerOnFailure"
+- description="notify lifecycleListener from message transfer failure"
+- is="true"
+- type="boolean"/>
+- <attribute name="clusterName"
+- description="name of cluster"
+- type="java.lang.String"/>
+- <attribute name="managerClassName"
+- description="session mananager classname"
+- type="java.lang.String"/>
+- <attribute name="clusterLogName"
+- description="Name of cluster transfer log device"
+- type="java.lang.String"/>
+- <attribute name="doClusterLog"
+- is="true"
+- description="enable cluster log transfer logging"
+- type="boolean"/>
+- <operation name="setProperty"
+- description="set a property to all cluster managers (with prefix 'manager.')"
+- impact="ACTION"
+- returnType="void">
+- <parameter name="key"
+- description="Property name"
+- type="java.lang.String"/>
+- <parameter name="value"
+- description="Property value"
+- type="java.lang.String"/>
+- </operation>
+-
+- <operation name="send"
+- description="send message to all cluster members"
+- impact="ACTION"
+- returnType="void">
+- <parameter name="message"
+- description="replication message"
+- type="org.apache.catalina.ha.ClusterMessage"/>
+- </operation>
+-
+- <operation name="sendClusterDomain"
+- description="send message to all cluster members with same domain"
+- impact="ACTION"
+- returnType="void">
+- <parameter name="message"
+- description="replication message"
+- type="org.apache.catalina.ha.ClusterMessage"/>
+- </operation>
+-
+- <operation name="sendToMember"
+- description="send message to one cluster member"
+- impact="ACTION"
+- returnType="void">
+- <parameter name="message"
+- description="replication message"
+- type="org.apache.catalina..cluster.ClusterMessage"/>
+- <parameter name="member"
+- description="cluster member"
+- type="java.lang.String"/>
+- </operation>
+-
+- <operation name="start"
+- description="Start the cluster"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- <operation name="stop"
+- description="Stop the cluster"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- </mbean>
+-
+- <mbean name="ClusterReceiverBase"
+- description="Tcp Cluster NioReceiver implementation"
+- domain="Catalina"
+- group="Cluster"
+- type="org.apache.catalina.ha.tcp.ClusterReceiverBase">
+- <attribute name="info"
+- description="Class version info"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute name="tcpListenAddress"
+- description="tcp listener address"
+- type="java.lang.String"/>
+- <attribute name="tcpListenPort"
+- description="tcp listener port"
+- type="int"/>
+- <attribute name="tcpThreadCount"
+- description="number of tcp listener worker threads"
+- type="int"/>
+- <attribute name="tcpSelectorTimeout"
+- description="tcp listener Selector timeout"
+- type="long"/>
+- <attribute name="nrOfMsgsReceived"
+- description="number of messages received from other nodes"
+- type="long"
+- writeable="false"/>
+- <attribute name="receivedTime"
+- description="total time message send time"
+- type="long"
+- writeable="false"/>
+- <attribute name="receivedProcessingTime"
+- description="received processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="minReceivedProcessingTime"
+- description="minimal received processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="avgReceivedProcessingTime"
+- description="received processing time / nrOfRequests"
+- type="double"
+- writeable="false"/>
+- <attribute name="maxReceivedProcessingTime"
+- description="maximal received processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="doReceivedProcessingStats"
+- description="create received processing time stats"
+- is="true"
+- type="boolean" />
+- <attribute name="avgTotalReceivedBytes"
+- description="received totalReceivedBytes / nrOfMsgsReceived"
+- type="long"
+- writeable="false"/>
+- <attribute name="totalReceivedBytes"
+- description="number of bytes received"
+- type="long"
+- writeable="false"/>
+- <attribute name="sendAck"
+- description="send ack after data received"
+- is="true"
+- type="boolean"
+- writeable="false" />
+- <attribute name="compress"
+- description="data received compressed"
+- is="true"
+- type="boolean"
+- writeable="false" />
+- <attribute name="doListen"
+- description="is port really started"
+- is="true"
+- type="boolean"
+- writeable="false" />
+-
+- <operation name="resetStatistics"
+- description="Reset all statistics"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- <operation name="start"
+- description="Start the cluster"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- <operation name="stop"
+- description="Stop the cluster"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- </mbean>
+-
+- <mbean name="SocketReplicationListener"
+- description="Tcp Cluster SocketReplicationListener implementation"
+- domain="Catalina"
+- group="Cluster"
+- type="org.apache.catalina.ha.tcp.SocketReplicationListener">
+- <attribute name="info"
+- description="Class version info"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute name="tcpListenAddress"
+- description="tcp listener address"
+- type="java.lang.String"/>
+- <attribute name="tcpListenPort"
+- description="tcp listener port"
+- type="int"/>
+- <attribute name="tcpListenMaxPort"
+- description="max tcp listen used port"
+- type="int"/>
+- <attribute name="tcpListenTimeout"
+- description="max tcp listen timeout (sec) wait for ServerSocket start"
+- type="int"/>
+- <attribute name="nrOfMsgsReceived"
+- description="number of messages received from other nodes"
+- type="long"
+- writeable="false"/>
+- <attribute name="receivedTime"
+- description="total time message send time"
+- type="long"
+- writeable="false"/>
+- <attribute name="receivedProcessingTime"
+- description="received processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="minReceivedProcessingTime"
+- description="minimal received processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="avgReceivedProcessingTime"
+- description="received processing time / nrOfRequests"
+- type="double"
+- writeable="false"/>
+- <attribute name="maxReceivedProcessingTime"
+- description="maximal received processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="doReceivedProcessingStats"
+- description="create received processing time stats"
+- is="true"
+- type="boolean" />
+- <attribute name="avgTotalReceivedBytes"
+- description="received totalReceivedBytes / nrOfMsgsReceived"
+- type="long"
+- writeable="false"/>
+- <attribute name="totalReceivedBytes"
+- description="number of bytes received"
+- type="long"
+- writeable="false"/>
+- <attribute name="sendAck"
+- description="send ack after data received"
+- is="true"
+- type="boolean"
+- writeable="false" />
+- <attribute name="compress"
+- description="data received compressed"
+- is="true"
+- type="boolean"
+- writeable="false" />
+- <attribute name="doListen"
+- description="is port really started"
+- is="true"
+- type="boolean"
+- writeable="false" />
+-
+- <operation name="resetStatistics"
+- description="Reset all statistics"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- <operation name="start"
+- description="Start the cluster"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- <operation name="stop"
+- description="Stop the cluster"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- </mbean>
+-
+- <mbean name="ReplicationTransmitter"
+- description="Tcp replication transmitter"
+- domain="Catalina"
+- group="ClusterSender"
+- type="org.apache.catalina.ha.tcp.ReplicationTransmitter">
+- <attribute name="info"
+- description="Class version info"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute name="replicationMode"
+- description="replication mode (synchnous,pooled.asynchnous,fastasyncqueue)"
+- type="java.lang.String"/>
+- <attribute name="ackTimeout"
+- description="acknowledge timeout"
+- type="long"/>
+- <attribute name="autoConnect"
+- description="is sender disabled, fork a new socket"
+- is="true"
+- type="boolean" />
+- <attribute name="waitForAck"
+- description="Wait for ack after data send"
+- is="true"
+- type="boolean"
+- writeable="false" />
+- <attribute name="processingTime"
+- description="sending processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="minProcessingTime"
+- description="minimal sending processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="avgProcessingTime"
+- description="processing time / nrOfRequests"
+- type="double"
+- writeable="false"/>
+- <attribute name="maxProcessingTime"
+- description="maximal sending processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="doTransmitterProcessingStats"
+- description="create processing time stats"
+- is="true"
+- type="boolean" />
+- <attribute name="nrOfRequests"
+- description="number of send messages to other members"
+- type="long"
+- writeable="false"/>
+- <attribute name="totalBytes"
+- description="number of bytes transfered"
+- type="long"
+- writeable="false"/>
+- <attribute name="failureCounter"
+- description="number of wrong transfers"
+- type="long"
+- writeable="false"/>
+- <attribute name="senderObjectNames"
+- description="get all sender object names"
+- type="[Ljavax.management.ObjectName;"
+- writeable="false"/>
+- <operation name="start"
+- description="Start the cluster"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+- <operation name="stop"
+- description="Stop the cluster"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+- <operation name="resetStatistics"
+- description="Reset all statistics"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+- <operation name="checkKeepAlive"
+- description="Check all sender connection for close socket (keepalive)"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+- </mbean>
+-
+- <mbean name="AsyncSocketSender"
+- description="Async Cluster Sender"
+- domain="Catalina"
+- group="IDataSender"
+- type="org.apache.catalina.ha.tcp.AsyncSocketSender">
+- <attribute name="info"
+- description="Class version info"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute name="address"
+- description="sender ip address"
+- type="java.net.InetAddress"
+- writeable="false"/>
+- <attribute name="port"
+- description="sender port"
+- type="int"
+- writeable="false" />
+- <attribute name="suspect"
+- description="Socket is gone"
+- type="boolean"/>
+- <attribute name="waitForAck"
+- description="Wait for ack after data send"
+- is="true"
+- type="boolean"
+- writeable="false"/>
+- <attribute name="ackTimeout"
+- description="acknowledge timeout"
+- type="long"/>
+- <attribute name="avgMessageSize"
+- writeable="false"
+- description="avg message size (totalbytes/nrOfRequests"
+- type="long"/>
+- <attribute name="queueSize"
+- writeable="false"
+- description="queue size"
+- type="int"/>
+- <attribute name="queuedNrOfBytes"
+- writeable="false"
+- description="number of bytes over all queued messages"
+- type="long"/>
+- <attribute name="messageTransferStarted"
+- description="message is in transfer"
+- type="boolean"
+- is="true"
+- writeable="false"/>
+- <attribute name="keepAliveTimeout"
+- description="active socket keep alive timeout"
+- type="long"/>
+- <attribute name="keepAliveMaxRequestCount"
+- description="max request over this socket"
+- type="int"/>
+- <attribute name="keepAliveCount"
+- description="keep Alive request count"
+- type="int"
+- writeable="false"/>
+- <attribute name="keepAliveConnectTime"
+- description="Connect time for keep alive"
+- type="long"
+- writeable="false"/>
+- <attribute name="resend"
+- description="after send failure make a resend"
+- is="true"
+- type="boolean" />
+- <attribute name="connected"
+- is="true"
+- description="socket connected"
+- type="boolean"
+- writeable="false"/>
+- <attribute name="nrOfRequests"
+- description="number of send messages to other members"
+- type="long"
+- writeable="false"/>
+- <attribute name="totalBytes"
+- description="number of bytes transfered"
+- type="long"
+- writeable="false"/>
+- <attribute name="processingTime"
+- description="sending processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="minProcessingTime"
+- description="minimal sending processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="avgProcessingTime"
+- description="processing time / nrOfRequests"
+- type="double"
+- writeable="false"/>
+- <attribute name="maxProcessingTime"
+- description="maximal sending processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="doProcessingStats"
+- description="create processing time stats"
+- is="true"
+- type="boolean" />
+- <attribute name="waitAckTime"
+- description="sending waitAck time"
+- type="long"
+- writeable="false"/>
+- <attribute name="minWaitAckTime"
+- description="minimal sending waitAck time"
+- type="long"
+- writeable="false"/>
+- <attribute name="avgWaitAckTime"
+- description="waitAck time / nrOfRequests"
+- type="double"
+- writeable="false"/>
+- <attribute name="maxWaitAckTime"
+- description="maximal sending waitAck time"
+- type="long"
+- writeable="false"/>
+- <attribute name="doWaitAckStats"
+- description="create waitAck time stats"
+- is="true"
+- type="boolean" />
+- <attribute name="connectCounter"
+- description="counts connects"
+- type="long"
+- writeable="false"/>
+- <attribute name="disconnectCounter"
+- description="counts disconnects"
+- type="long"
+- writeable="false"/>
+- <attribute name="socketOpenCounter"
+- description="counts open socket (KeepAlive and connects)"
+- type="long"
+- writeable="false"/>
+- <attribute name="socketOpenFailureCounter"
+- description="counts open socket failures"
+- type="long"
+- writeable="false"/>
+- <attribute name="socketCloseCounter"
+- description="counts closed socket (KeepAlive and disconnects)"
+- type="long"
+- writeable="false"/>
+- <attribute name="missingAckCounter"
+- description="counts missing ack"
+- type="long"
+- writeable="false"/>
+- <attribute name="dataResendCounter"
+- description="counts data resends"
+- type="long"
+- writeable="false"/>
+- <attribute name="dataFailureCounter"
+- description="counts data send failures"
+- type="long"
+- writeable="false"/>
+- <attribute name="inQueueCounter"
+- description="counts all queued messages"
+- type="long"
+- writeable="false"/>
+- <attribute name="outQueueCounter"
+- description="counts all successfully sended messages"
+- type="long"
+- writeable="false"/>
+- <operation name="connect"
+- description="connect to other replication node"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+- <operation name="disconnect"
+- description="disconnect to other replication node"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+- <operation name="checkKeepAlive"
+- description="Check connection for close socket"
+- impact="ACTION"
+- returnType="boolean">
+- </operation>
+- <operation name="resetStatistics"
+- description="Reset all statistics"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- </mbean>
+-
+- <mbean name="MultiSocketSender"
+- description="Multi Socket Sender, more than one socket per member"
+- domain="Catalina"
+- group="IDataSender"
+- type="org.apache.catalina.ha.tcp.PooledSocketSender">
+- <attribute name="address"
+- description="sender ip address"
+- type="java.net.InetAddress"
+- writeable="false"/>
+- <attribute name="port"
+- description="sender port"
+- type="int"
+- writeable="false" />
+- <attribute name="suspect"
+- description="Socket is gone"
+- type="boolean"/>
+- <attribute name="ackTimeout"
+- description="acknowledge timeout"
+- type="long"/>
+- <attribute name="waitForAck"
+- description="Wait for ack after data send"
+- is="true"
+- type="boolean"
+- writeable="false" />
+- <attribute name="maxPoolSocketLimit"
+- description="Max parallel sockets"
+- type="int"/>
+- <attribute name="keepAliveTimeout"
+- description="active socket keep alive timeout"
+- type="long"/>
+- <attribute name="keepAliveMaxRequestCount"
+- description="max request over this socket"
+- type="int"/>
+- <attribute name="resend"
+- description="after send failure make a resend"
+- is="true"
+- type="boolean" />
+- <attribute name="connected"
+- is="true"
+- description="socket connected"
+- type="boolean"
+- writeable="false"/>
+- <attribute name="avgMessageSize"
+- writeable="false"
+- description="avg message size (totalbytes/nrOfRequests"
+- type="long"/>
+- <attribute name="nrOfRequests"
+- description="number of send messages to other members"
+- type="long"
+- writeable="false"/>
+- <attribute name="totalBytes"
+- description="number of bytes transfered"
+- type="long"
+- writeable="false"/>
+- <attribute name="connectCounter"
+- description="counts connects"
+- type="long"
+- writeable="false"/>
+- <attribute name="disconnectCounter"
+- description="counts disconnects"
+- type="long"
+- writeable="false"/>
+- <operation name="connect"
+- description="start Queue to connect to ohter replication node"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+- <operation name="disconnect"
+- description="stop Queue to other replication node"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+- <operation name="resetStatistics"
+- description="Reset all statistics"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- </mbean>
+-
+- <mbean name="SocketSender"
+- description="Sync Cluster Sender"
+- domain="Catalina"
+- group="IDataSender"
+- type="org.apache.catalina.ha.tcp.SocketSender">
+- <attribute name="address"
+- description="sender ip address"
+- type="java.net.InetAddress"
+- writeable="false"/>
+- <attribute name="port"
+- description="sender port"
+- type="int"
+- writeable="false" />
+- <attribute name="suspect"
+- description="Socket is gone"
+- type="boolean"/>
+- <attribute name="ackTimeout"
+- description="acknowledge timeout"
+- type="long"/>
+- <attribute name="waitForAck"
+- description="Wait for ack after data send"
+- is="true"
+- type="boolean"
+- writeable="false" />
+- <attribute name="keepAliveTimeout"
+- description="active socket keep alive timeout"
+- type="long"/>
+- <attribute name="keepAliveMaxRequestCount"
+- description="max request over this socket"
+- type="int"/>
+- <attribute name="messageTransferStarted"
+- description="message is in transfer"
+- type="boolean"
+- is="true"
+- writeable="false"/>
+- <attribute name="keepAliveCount"
+- description="keep Alive request count"
+- type="int"
+- writeable="false"/>
+- <attribute name="keepAliveConnectTime"
+- description="Connect time for keep alive"
+- type="long"
+- writeable="false"/>
+- <attribute name="resend"
+- description="after send failure make a resend"
+- is="true"
+- type="boolean" />
+- <attribute name="connected"
+- is="true"
+- description="socket connected"
+- type="boolean"
+- writeable="false"/>
+- <attribute name="avgMessageSize"
+- writeable="false"
+- description="avg message size (totalbytes/nrOfRequests"
+- type="long"/>
+- <attribute name="nrOfRequests"
+- description="number of send messages to other members"
+- type="long"
+- writeable="false"/>
+- <attribute name="totalBytes"
+- description="number of bytes transfered"
+- type="long"
+- writeable="false"/>
+- <attribute name="processingTime"
+- description="sending processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="minProcessingTime"
+- description="minimal sending processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="avgProcessingTime"
+- description="processing time / nrOfRequests"
+- type="double"
+- writeable="false"/>
+- <attribute name="maxProcessingTime"
+- description="maximal sending processing time"
+- type="long"
+- writeable="false"/>
+- <attribute name="doProcessingStats"
+- description="create Processing time stats"
+- is="true"
+- type="boolean" />
+- <attribute name="waitAckTime"
+- description="sending waitAck time"
+- type="long"
+- writeable="false"/>
+- <attribute name="minWaitAckTime"
+- description="minimal sending waitAck time"
+- type="long"
+- writeable="false"/>
+- <attribute name="avgWaitAckTime"
+- description="waitAck time / nrOfRequests"
+- type="double"
+- writeable="false"/>
+- <attribute name="maxWaitAckTime"
+- description="maximal sending waitAck time"
+- type="long"
+- writeable="false"/>
+- <attribute name="doWaitAckStats"
+- description="create waitAck time stats"
+- is="true"
+- type="boolean" />
+- <attribute name="connectCounter"
+- description="counts connects"
+- type="long"
+- writeable="false"/>
+- <attribute name="disconnectCounter"
+- description="counts disconnects"
+- type="long"
+- writeable="false"/>
+- <attribute name="socketCloseCounter"
+- description="counts closed socket (KeepAlive and disconnects)"
+- type="long"
+- writeable="false"/>
+- <attribute name="socketOpenFailureCounter"
+- description="counts open socket failures"
+- type="long"
+- writeable="false"/>
+- <attribute name="socketOpenCounter"
+- description="counts open socket (KeepAlive and connects)"
+- type="long"
+- writeable="false"/>
+- <attribute name="missingAckCounter"
+- description="counts missing ack"
+- type="long"
+- writeable="false"/>
+- <attribute name="dataResendCounter"
+- description="counts data resends"
+- type="long"
+- writeable="false"/>
+- <attribute name="dataFailureCounter"
+- description="counts data send failures"
+- type="long"
+- writeable="false"/>
+- <operation name="connect"
+- description="connect to other replication node"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+- <operation name="disconnect"
+- description="disconnect to other replication node"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+- <operation name="checkKeepAlive"
+- description="Check connection for close socket"
+- impact="ACTION"
+- returnType="boolean">
+- </operation>
+- <operation name="resetStatistics"
+- description="Reset all statistics"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- </mbean>
+-
+- <mbean name="ReplicationValve"
+- description="Valve for simple tcp replication"
+- domain="Catalina"
+- group="Valve"
+- type="org.apache.catalina.ha.tcp.ReplicationValve">
+- <attribute name="info"
+- description="Class version info"
+- type="java.lang.String"
+- writeable="false"/>
+- <attribute name="filter"
+- description="resource filter to disable session replication check"
+- type="java.lang.String"/>
+- <attribute name="primaryIndicator"
+- is="true"
+- description="set indicator that request processing is at primary session node"
+- type="boolean"/>
+- <attribute name="primaryIndicatorName"
+- description="Request attribute name to indicate that request processing is at primary session node"
+- type="java.lang.String"/>
+- <attribute name="doProcessingStats"
+- is="true"
+- description="active statistics counting"
+- type="boolean"/>
+- <attribute name="nrOfRequests"
+- description="number of replicated requests"
+- type="long"
+- writeable="false"/>
+- <attribute name="nrOfFilterRequests"
+- description="number of filtered requests"
+- type="long"
+- writeable="false"/>
+- <attribute name="nrOfSendRequests"
+- description="number of send requests"
+- type="long"
+- writeable="false"/>
+- <attribute name="nrOfCrossContextSendRequests"
+- description="number of send cross context session requests"
+- type="long"
+- writeable="false"/>
+- <attribute name="totalRequestTime"
+- description="total replicated request time"
+- type="long"
+- writeable="false"/>
+- <attribute name="totalSendTime"
+- description="total replicated send time"
+- type="long"
+- writeable="false"/>
+- <attribute name="lastSendTime"
+- description="last replicated request time"
+- type="long"
+- writeable="false"/>
+- <operation name="resetStatistics"
+- description="Reset all statistics"
+- impact="ACTION"
+- returnType="void">
+- </operation>
+-
+- </mbean>
+-
+-
+-</mbeans-descriptors>
+Index: java/org/apache/catalina/tribes/transport/AbstractSender.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/AbstractSender.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/AbstractSender.java (working copy)
+@@ -1,309 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.transport;
+-
+-import java.io.IOException;
+-import java.net.InetAddress;
+-import java.net.UnknownHostException;
+-
+-import org.apache.catalina.tribes.Member;
+-
+-/**
+- * <p>Title: </p>
+- *
+- * <p>Description: </p>
+- *
+- * <p>Company: </p>
+- *
+- * @author not attributable
+- * @version 1.0
+- */
+-public abstract class AbstractSender implements DataSender {
+-
+- private boolean connected = false;
+- private int rxBufSize = 25188;
+- private int txBufSize = 43800;
+- private boolean directBuffer = false;
+- private int keepAliveCount = -1;
+- private int requestCount = 0;
+- private long connectTime;
+- private long keepAliveTime = -1;
+- private long timeout = 3000;
+- private Member destination;
+- private InetAddress address;
+- private int port;
+- private int maxRetryAttempts = 1;//1 resends
+- private int attempt;
+- private boolean tcpNoDelay = true;
+- private boolean soKeepAlive = false;
+- private boolean ooBInline = true;
+- private boolean soReuseAddress = true;
+- private boolean soLingerOn = false;
+- private int soLingerTime = 3;
+- private int soTrafficClass = 0x04 | 0x08 | 0x010;
+- private boolean throwOnFailedAck = true;
+-
+- /**
+- * transfers sender properties from one sender to another
+- * @param from AbstractSender
+- * @param to AbstractSender
+- */
+- public static void transferProperties(AbstractSender from, AbstractSender to) {
+- to.rxBufSize = from.rxBufSize;
+- to.txBufSize = from.txBufSize;
+- to.directBuffer = from.directBuffer;
+- to.keepAliveCount = from.keepAliveCount;
+- to.keepAliveTime = from.keepAliveTime;
+- to.timeout = from.timeout;
+- to.destination = from.destination;
+- to.address = from.address;
+- to.port = from.port;
+- to.maxRetryAttempts = from.maxRetryAttempts;
+- to.tcpNoDelay = from.tcpNoDelay;
+- to.soKeepAlive = from.soKeepAlive;
+- to.ooBInline = from.ooBInline;
+- to.soReuseAddress = from.soReuseAddress;
+- to.soLingerOn = from.soLingerOn;
+- to.soLingerTime = from.soLingerTime;
+- to.soTrafficClass = from.soTrafficClass;
+- to.throwOnFailedAck = from.throwOnFailedAck;
+- }
+-
+-
+- public AbstractSender() {
+-
+- }
+-
+- /**
+- * connect
+- *
+- * @throws IOException
+- * @todo Implement this org.apache.catalina.tribes.transport.DataSender method
+- */
+- public abstract void connect() throws IOException;
+-
+- /**
+- * disconnect
+- *
+- * @todo Implement this org.apache.catalina.tribes.transport.DataSender method
+- */
+- public abstract void disconnect();
+-
+- /**
+- * keepalive
+- *
+- * @return boolean
+- * @todo Implement this org.apache.catalina.tribes.transport.DataSender method
+- */
+- public boolean keepalive() {
+- boolean disconnect = false;
+- if ( keepAliveCount >= 0 && requestCount>keepAliveCount ) disconnect = true;
+- else if ( keepAliveTime >= 0 && (System.currentTimeMillis()-connectTime)>keepAliveTime ) disconnect = true;
+- if ( disconnect ) disconnect();
+- return disconnect;
+- }
+-
+- protected void setConnected(boolean connected){
+- this.connected = connected;
+- }
+-
+- public boolean isConnected() {
+- return connected;
+- }
+-
+- public long getConnectTime() {
+- return connectTime;
+- }
+-
+- public Member getDestination() {
+- return destination;
+- }
+-
+-
+- public int getKeepAliveCount() {
+- return keepAliveCount;
+- }
+-
+- public long getKeepAliveTime() {
+- return keepAliveTime;
+- }
+-
+- public int getRequestCount() {
+- return requestCount;
+- }
+-
+- public int getRxBufSize() {
+- return rxBufSize;
+- }
+-
+- public long getTimeout() {
+- return timeout;
+- }
+-
+- public int getTxBufSize() {
+- return txBufSize;
+- }
+-
+- public InetAddress getAddress() {
+- return address;
+- }
+-
+- public int getPort() {
+- return port;
+- }
+-
+- public int getMaxRetryAttempts() {
+- return maxRetryAttempts;
+- }
+-
+- public void setDirect(boolean direct) {
+- setDirectBuffer(direct);
+- }
+-
+- public void setDirectBuffer(boolean directBuffer) {
+- this.directBuffer = directBuffer;
+- }
+-
+- public boolean getDirect() {
+- return getDirectBuffer();
+- }
+-
+- public boolean getDirectBuffer() {
+- return this.directBuffer;
+- }
+-
+- public int getAttempt() {
+- return attempt;
+- }
+-
+- public boolean getTcpNoDelay() {
+- return tcpNoDelay;
+- }
+-
+- public boolean getSoKeepAlive() {
+- return soKeepAlive;
+- }
+-
+- public boolean getOoBInline() {
+- return ooBInline;
+- }
+-
+- public boolean getSoReuseAddress() {
+- return soReuseAddress;
+- }
+-
+- public boolean getSoLingerOn() {
+- return soLingerOn;
+- }
+-
+- public int getSoLingerTime() {
+- return soLingerTime;
+- }
+-
+- public int getSoTrafficClass() {
+- return soTrafficClass;
+- }
+-
+- public boolean getThrowOnFailedAck() {
+- return throwOnFailedAck;
+- }
+-
+- public void setKeepAliveCount(int keepAliveCount) {
+- this.keepAliveCount = keepAliveCount;
+- }
+-
+- public void setKeepAliveTime(long keepAliveTime) {
+- this.keepAliveTime = keepAliveTime;
+- }
+-
+- public void setRequestCount(int requestCount) {
+- this.requestCount = requestCount;
+- }
+-
+- public void setRxBufSize(int rxBufSize) {
+- this.rxBufSize = rxBufSize;
+- }
+-
+- public void setTimeout(long timeout) {
+- this.timeout = timeout;
+- }
+-
+- public void setTxBufSize(int txBufSize) {
+- this.txBufSize = txBufSize;
+- }
+-
+- public void setConnectTime(long connectTime) {
+- this.connectTime = connectTime;
+- }
+-
+- public void setMaxRetryAttempts(int maxRetryAttempts) {
+- this.maxRetryAttempts = maxRetryAttempts;
+- }
+-
+- public void setAttempt(int attempt) {
+- this.attempt = attempt;
+- }
+-
+- public void setTcpNoDelay(boolean tcpNoDelay) {
+- this.tcpNoDelay = tcpNoDelay;
+- }
+-
+- public void setSoKeepAlive(boolean soKeepAlive) {
+- this.soKeepAlive = soKeepAlive;
+- }
+-
+- public void setOoBInline(boolean ooBInline) {
+- this.ooBInline = ooBInline;
+- }
+-
+- public void setSoReuseAddress(boolean soReuseAddress) {
+- this.soReuseAddress = soReuseAddress;
+- }
+-
+- public void setSoLingerOn(boolean soLingerOn) {
+- this.soLingerOn = soLingerOn;
+- }
+-
+- public void setSoLingerTime(int soLingerTime) {
+- this.soLingerTime = soLingerTime;
+- }
+-
+- public void setSoTrafficClass(int soTrafficClass) {
+- this.soTrafficClass = soTrafficClass;
+- }
+-
+- public void setThrowOnFailedAck(boolean throwOnFailedAck) {
+- this.throwOnFailedAck = throwOnFailedAck;
+- }
+-
+- public void setDestination(Member destination) throws UnknownHostException {
+- this.destination = destination;
+- this.address = InetAddress.getByAddress(destination.getHost());
+- this.port = destination.getPort();
+-
+- }
+-
+- public void setPort(int port) {
+- this.port = port;
+- }
+-
+- public void setAddress(InetAddress address) {
+- this.address = address;
+- }
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/transport/nio/PooledParallelSender.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/nio/PooledParallelSender.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/nio/PooledParallelSender.java (working copy)
+@@ -1,82 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.transport.nio;
+-
+-import java.io.IOException;
+-
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.transport.DataSender;
+-import org.apache.catalina.tribes.transport.MultiPointSender;
+-import org.apache.catalina.tribes.transport.PooledSender;
+-
+-/**
+- * <p>Title: </p>
+- *
+- * <p>Description: </p>
+- *
+- * <p>Company: </p>
+- *
+- * @author not attributable
+- * @version 1.0
+- */
+-public class PooledParallelSender extends PooledSender implements MultiPointSender {
+- protected boolean connected = true;
+- public PooledParallelSender() {
+- super();
+- }
+-
+- public void sendMessage(Member[] destination, ChannelMessage message) throws ChannelException {
+- if ( !connected ) throw new ChannelException("Sender not connected.");
+- ParallelNioSender sender = (ParallelNioSender)getSender();
+- if (sender == null) {
+- ChannelException cx = new ChannelException("Unable to retrieve a data sender, time out error.");
+- for (int i = 0; i < destination.length; i++) cx.addFaultyMember(destination[i], new NullPointerException("Unable to retrieve a sender from the sender pool"));
+- throw cx;
+- } else {
+- try {
+- sender.sendMessage(destination, message);
+- sender.keepalive();
+- } finally {
+- if (!connected) disconnect();
+- returnSender(sender);
+- }
+- }
+- }
+-
+- public DataSender getNewDataSender() {
+- try {
+- ParallelNioSender sender = new ParallelNioSender();
+- sender.transferProperties(this,sender);
+- return sender;
+- } catch ( IOException x ) {
+- throw new RuntimeException("Unable to open NIO selector.",x);
+- }
+- }
+-
+- public synchronized void disconnect() {
+- this.connected = false;
+- super.disconnect();
+- }
+-
+- public synchronized void connect() throws IOException {
+- this.connected = true;
+- super.connect();
+- }
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/transport/nio/NioReplicationTask.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/nio/NioReplicationTask.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/nio/NioReplicationTask.java (working copy)
+@@ -1,302 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.transport.nio;
+-import java.io.IOException;
+-import java.nio.ByteBuffer;
+-import java.nio.channels.SelectionKey;
+-import java.nio.channels.SocketChannel;
+-
+-import org.apache.catalina.tribes.io.ObjectReader;
+-import org.apache.catalina.tribes.transport.Constants;
+-import org.apache.catalina.tribes.transport.AbstractRxTask;
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.io.ListenCallback;
+-import org.apache.catalina.tribes.io.ChannelData;
+-import org.apache.catalina.tribes.io.BufferPool;
+-import java.nio.channels.CancelledKeyException;
+-import org.apache.catalina.tribes.UniqueId;
+-import org.apache.catalina.tribes.RemoteProcessException;
+-import org.apache.catalina.tribes.util.Logs;
+-
+-/**
+- * A worker thread class which can drain channels and echo-back the input. Each
+- * instance is constructed with a reference to the owning thread pool object.
+- * When started, the thread loops forever waiting to be awakened to service the
+- * channel associated with a SelectionKey object. The worker is tasked by
+- * calling its serviceChannel() method with a SelectionKey object. The
+- * serviceChannel() method stores the key reference in the thread object then
+- * calls notify() to wake it up. When the channel has been drained, the worker
+- * thread returns itself to its parent pool.
+- *
+- * @author Filip Hanik
+- *
+- * @version $Revision$, $Date$
+- */
+-public class NioReplicationTask extends AbstractRxTask {
+-
+- private static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog( NioReplicationTask.class );
+-
+- private ByteBuffer buffer = null;
+- private SelectionKey key;
+- private int rxBufSize;
+- private NioReceiver receiver;
+- public NioReplicationTask (ListenCallback callback, NioReceiver receiver)
+- {
+- super(callback);
+- this.receiver = receiver;
+- }
+-
+- // loop forever waiting for work to do
+- public synchronized void run() {
+- if ( buffer == null ) {
+- if ( (getOptions() & OPTION_DIRECT_BUFFER) == OPTION_DIRECT_BUFFER) {
+- buffer = ByteBuffer.allocateDirect(getRxBufSize());
+- } else {
+- buffer = ByteBuffer.allocate(getRxBufSize());
+- }
+- } else {
+- buffer.clear();
+- }
+- if (key == null) {
+- return; // just in case
+- }
+- if ( log.isTraceEnabled() )
+- log.trace("Servicing key:"+key);
+-
+- try {
+- ObjectReader reader = (ObjectReader)key.attachment();
+- if ( reader == null ) {
+- if ( log.isTraceEnabled() )
+- log.trace("No object reader, cancelling:"+key);
+- cancelKey(key);
+- } else {
+- if ( log.isTraceEnabled() )
+- log.trace("Draining channel:"+key);
+-
+- drainChannel(key, reader);
+- }
+- } catch (Exception e) {
+- //this is common, since the sockets on the other
+- //end expire after a certain time.
+- if ( e instanceof CancelledKeyException ) {
+- //do nothing
+- } else if ( e instanceof IOException ) {
+- //dont spew out stack traces for IO exceptions unless debug is enabled.
+- if (log.isDebugEnabled()) log.debug ("IOException in replication worker, unable to drain channel. Probable cause: Keep alive socket closed["+e.getMessage()+"].", e);
+- else log.warn ("IOException in replication worker, unable to drain channel. Probable cause: Keep alive socket closed["+e.getMessage()+"].");
+- } else if ( log.isErrorEnabled() ) {
+- //this is a real error, log it.
+- log.error("Exception caught in TcpReplicationThread.drainChannel.",e);
+- }
+- cancelKey(key);
+- } finally {
+-
+- }
+- key = null;
+- // done, ready for more, return to pool
+- getTaskPool().returnWorker (this);
+- }
+-
+- /**
+- * Called to initiate a unit of work by this worker thread
+- * on the provided SelectionKey object. This method is
+- * synchronized, as is the run() method, so only one key
+- * can be serviced at a given time.
+- * Before waking the worker thread, and before returning
+- * to the main selection loop, this key's interest set is
+- * updated to remove OP_READ. This will cause the selector
+- * to ignore read-readiness for this channel while the
+- * worker thread is servicing it.
+- */
+- public synchronized void serviceChannel (SelectionKey key) {
+- if ( log.isTraceEnabled() ) log.trace("About to service key:"+key);
+- ObjectReader reader = (ObjectReader)key.attachment();
+- if ( reader != null ) reader.setLastAccess(System.currentTimeMillis());
+- this.key = key;
+- key.interestOps (key.interestOps() & (~SelectionKey.OP_READ));
+- key.interestOps (key.interestOps() & (~SelectionKey.OP_WRITE));
+- }
+-
+- /**
+- * The actual code which drains the channel associated with
+- * the given key. This method assumes the key has been
+- * modified prior to invocation to turn off selection
+- * interest in OP_READ. When this method completes it
+- * re-enables OP_READ and calls wakeup() on the selector
+- * so the selector will resume watching this channel.
+- */
+- protected void drainChannel (final SelectionKey key, ObjectReader reader) throws Exception {
+- reader.setLastAccess(System.currentTimeMillis());
+- reader.access();
+- SocketChannel channel = (SocketChannel) key.channel();
+- int count;
+- buffer.clear(); // make buffer empty
+-
+- // loop while data available, channel is non-blocking
+- while ((count = channel.read (buffer)) > 0) {
+- buffer.flip(); // make buffer readable
+- if ( buffer.hasArray() )
+- reader.append(buffer.array(),0,count,false);
+- else
+- reader.append(buffer,count,false);
+- buffer.clear(); // make buffer empty
+- //do we have at least one package?
+- if ( reader.hasPackage() ) break;
+- }
+-
+- int pkgcnt = reader.count();
+-
+- if (count < 0 && pkgcnt == 0 ) {
+- //end of stream, and no more packages to process
+- remoteEof(key);
+- return;
+- }
+-
+- ChannelMessage[] msgs = pkgcnt == 0? ChannelData.EMPTY_DATA_ARRAY : reader.execute();
+-
+- registerForRead(key,reader);//register to read new data, before we send it off to avoid dead locks
+-
+- for ( int i=0; i<msgs.length; i++ ) {
+- /**
+- * Use send ack here if you want to ack the request to the remote
+- * server before completing the request
+- * This is considered an asynchronized request
+- */
+- if (ChannelData.sendAckAsync(msgs[i].getOptions())) sendAck(key,channel,Constants.ACK_COMMAND);
+- try {
+- if ( Logs.MESSAGES.isTraceEnabled() ) {
+- try {
+- Logs.MESSAGES.trace("NioReplicationThread - Received msg:" + new UniqueId(msgs[i].getUniqueId()) + " at " + new java.sql.Timestamp(System.currentTimeMillis()));
+- }catch ( Throwable t ) {}
+- }
+- //process the message
+- getCallback().messageDataReceived(msgs[i]);
+- /**
+- * Use send ack here if you want the request to complete on this
+- * server before sending the ack to the remote server
+- * This is considered a synchronized request
+- */
+- if (ChannelData.sendAckSync(msgs[i].getOptions())) sendAck(key,channel,Constants.ACK_COMMAND);
+- }catch ( RemoteProcessException e ) {
+- if ( log.isDebugEnabled() ) log.error("Processing of cluster message failed.",e);
+- if (ChannelData.sendAckSync(msgs[i].getOptions())) sendAck(key,channel,Constants.FAIL_ACK_COMMAND);
+- }catch ( Exception e ) {
+- log.error("Processing of cluster message failed.",e);
+- if (ChannelData.sendAckSync(msgs[i].getOptions())) sendAck(key,channel,Constants.FAIL_ACK_COMMAND);
+- }
+- if ( getUseBufferPool() ) {
+- BufferPool.getBufferPool().returnBuffer(msgs[i].getMessage());
+- msgs[i].setMessage(null);
+- }
+- }
+-
+- if (count < 0) {
+- remoteEof(key);
+- return;
+- }
+- }
+-
+- private void remoteEof(SelectionKey key) {
+- // close channel on EOF, invalidates the key
+- if ( log.isDebugEnabled() ) log.debug("Channel closed on the remote end, disconnecting");
+- cancelKey(key);
+- }
+-
+- protected void registerForRead(final SelectionKey key, ObjectReader reader) {
+- if ( log.isTraceEnabled() )
+- log.trace("Adding key for read event:"+key);
+- reader.finish();
+- //register our OP_READ interest
+- Runnable r = new Runnable() {
+- public void run() {
+- try {
+- if (key.isValid()) {
+- // cycle the selector so this key is active again
+- key.selector().wakeup();
+- // resume interest in OP_READ, OP_WRITE
+- int resumeOps = key.interestOps() | SelectionKey.OP_READ;
+- key.interestOps(resumeOps);
+- if ( log.isTraceEnabled() )
+- log.trace("Registering key for read:"+key);
+- }
+- } catch (CancelledKeyException ckx ) {
+- NioReceiver.cancelledKey(key);
+- if ( log.isTraceEnabled() )
+- log.trace("CKX Cancelling key:"+key);
+-
+- } catch (Exception x) {
+- log.error("Error registering key for read:"+key,x);
+- }
+- }
+- };
+- receiver.addEvent(r);
+- }
+-
+- private void cancelKey(final SelectionKey key) {
+- if ( log.isTraceEnabled() )
+- log.trace("Adding key for cancel event:"+key);
+-
+- ObjectReader reader = (ObjectReader)key.attachment();
+- if ( reader != null ) {
+- reader.setCancelled(true);
+- reader.finish();
+- }
+- Runnable cx = new Runnable() {
+- public void run() {
+- if ( log.isTraceEnabled() )
+- log.trace("Cancelling key:"+key);
+-
+- NioReceiver.cancelledKey(key);
+- }
+- };
+- receiver.addEvent(cx);
+- }
+-
+-
+-
+-
+-
+- /**
+- * send a reply-acknowledgement (6,2,3)
+- * @param key
+- * @param channel
+- */
+- protected void sendAck(SelectionKey key, SocketChannel channel, byte[] command) {
+-
+- try {
+- ByteBuffer buf = ByteBuffer.wrap(command);
+- int total = 0;
+- while ( total < command.length ) {
+- total += channel.write(buf);
+- }
+- if (log.isTraceEnabled()) {
+- log.trace("ACK sent to " + channel.socket().getPort());
+- }
+- } catch ( java.io.IOException x ) {
+- log.warn("Unable to send ACK back through channel, channel disconnected?: "+x.getMessage());
+- }
+- }
+-
+- public void setRxBufSize(int rxBufSize) {
+- this.rxBufSize = rxBufSize;
+- }
+-
+- public int getRxBufSize() {
+- return rxBufSize;
+- }
+-}
+Index: java/org/apache/catalina/tribes/transport/nio/ParallelNioSender.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/nio/ParallelNioSender.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/nio/ParallelNioSender.java (working copy)
+@@ -1,308 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.transport.nio;
+-
+-
+-import java.io.IOException;
+-import java.nio.channels.SelectionKey;
+-import java.nio.channels.Selector;
+-import java.util.HashMap;
+-import java.util.Iterator;
+-import java.util.Map;
+-
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.io.ChannelData;
+-import org.apache.catalina.tribes.io.XByteBuffer;
+-import org.apache.catalina.tribes.transport.MultiPointSender;
+-import org.apache.catalina.tribes.transport.SenderState;
+-import org.apache.catalina.tribes.transport.AbstractSender;
+-import java.net.UnknownHostException;
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.util.Logs;
+-import org.apache.catalina.tribes.UniqueId;
+-
+-/**
+- * <p>Title: </p>
+- *
+- * <p>Description: </p>
+- *
+- * <p>Company: </p>
+- *
+- * @author not attributable
+- * @version 1.0
+- */
+-public class ParallelNioSender extends AbstractSender implements MultiPointSender {
+-
+- protected static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(ParallelNioSender.class);
+- protected long selectTimeout = 5000; //default 5 seconds, same as send timeout
+- protected Selector selector;
+- protected HashMap nioSenders = new HashMap();
+-
+- public ParallelNioSender() throws IOException {
+- selector = Selector.open();
+- setConnected(true);
+- }
+-
+-
+- public synchronized void sendMessage(Member[] destination, ChannelMessage msg) throws ChannelException {
+- long start = System.currentTimeMillis();
+- byte[] data = XByteBuffer.createDataPackage((ChannelData)msg);
+- NioSender[] senders = setupForSend(destination);
+- connect(senders);
+- setData(senders,data);
+-
+- int remaining = senders.length;
+- ChannelException cx = null;
+- try {
+- //loop until complete, an error happens, or we timeout
+- long delta = System.currentTimeMillis() - start;
+- boolean waitForAck = (Channel.SEND_OPTIONS_USE_ACK & msg.getOptions()) == Channel.SEND_OPTIONS_USE_ACK;
+- while ( (remaining>0) && (delta<getTimeout()) ) {
+- try {
+- remaining -= doLoop(selectTimeout, getMaxRetryAttempts(),waitForAck,msg);
+- } catch (Exception x ) {
+- int faulty = (cx == null)?0:cx.getFaultyMembers().length;
+- if ( cx == null ) {
+- if ( x instanceof ChannelException ) cx = (ChannelException)x;
+- else cx = new ChannelException("Parallel NIO send failed.", x);
+- } else {
+- if (x instanceof ChannelException) cx.addFaultyMember( ( (ChannelException) x).getFaultyMembers());
+- }
+- //count down the remaining on an error
+- if (faulty<cx.getFaultyMembers().length) remaining -= (cx.getFaultyMembers().length-faulty);
+- }
+- //bail out if all remaining senders are failing
+- if ( cx != null && cx.getFaultyMembers().length == remaining ) throw cx;
+- delta = System.currentTimeMillis() - start;
+- }
+- if ( remaining > 0 ) {
+- //timeout has occured
+- ChannelException cxtimeout = new ChannelException("Operation has timed out("+getTimeout()+" ms.).");
+- if ( cx==null ) cx = new ChannelException("Operation has timed out("+getTimeout()+" ms.).");
+- for (int i=0; i<senders.length; i++ ) {
+- if (!senders[i].isComplete() ) cx.addFaultyMember(senders[i].getDestination(),cxtimeout);
+- }
+- throw cx;
+- } else if ( cx != null ) {
+- //there was an error
+- throw cx;
+- }
+- } catch (Exception x ) {
+- try { this.disconnect(); } catch (Exception ignore) {}
+- if ( x instanceof ChannelException ) throw (ChannelException)x;
+- else throw new ChannelException(x);
+- }
+-
+- }
+-
+- private int doLoop(long selectTimeOut, int maxAttempts, boolean waitForAck, ChannelMessage msg) throws IOException, ChannelException {
+- int completed = 0;
+- int selectedKeys = selector.select(selectTimeOut);
+-
+- if (selectedKeys == 0) {
+- return 0;
+- }
+-
+- Iterator it = selector.selectedKeys().iterator();
+- while (it.hasNext()) {
+- SelectionKey sk = (SelectionKey) it.next();
+- it.remove();
+- int readyOps = sk.readyOps();
+- sk.interestOps(sk.interestOps() & ~readyOps);
+- NioSender sender = (NioSender) sk.attachment();
+- try {
+- if (sender.process(sk,waitForAck)) {
+- completed++;
+- sender.setComplete(true);
+- if ( Logs.MESSAGES.isTraceEnabled() ) {
+- Logs.MESSAGES.trace("ParallelNioSender - Sent msg:" + new UniqueId(msg.getUniqueId()) + " at " +new java.sql.Timestamp(System.currentTimeMillis())+ " to "+sender.getDestination().getName());
+- }
+- SenderState.getSenderState(sender.getDestination()).setReady();
+- }//end if
+- } catch (Exception x) {
+- SenderState state = SenderState.getSenderState(sender.getDestination());
+- int attempt = sender.getAttempt()+1;
+- boolean retry = (sender.getAttempt() <= maxAttempts && maxAttempts>0);
+- synchronized (state) {
+-
+- //sk.cancel();
+- if (state.isSuspect()) state.setFailing();
+- if (state.isReady()) {
+- state.setSuspect();
+- if ( retry )
+- log.warn("Member send is failing for:" + sender.getDestination().getName() +" ; Setting to suspect and retrying.");
+- else
+- log.warn("Member send is failing for:" + sender.getDestination().getName() +" ; Setting to suspect.", x);
+- }
+- }
+- if ( !isConnected() ) {
+- log.warn("Not retrying send for:" + sender.getDestination().getName() + "; Sender is disconnected.");
+- ChannelException cx = new ChannelException("Send failed, and sender is disconnected. Not retrying.",x);
+- cx.addFaultyMember(sender.getDestination(),x);
+- throw cx;
+- }
+-
+- byte[] data = sender.getMessage();
+- if ( retry ) {
+- try {
+- sender.disconnect();
+- sender.connect();
+- sender.setAttempt(attempt);
+- sender.setMessage(data);
+- }catch ( Exception ignore){
+- state.setFailing();
+- }
+- } else {
+- ChannelException cx = new ChannelException("Send failed, attempt:"+sender.getAttempt()+" max:"+maxAttempts,x);
+- cx.addFaultyMember(sender.getDestination(),x);
+- throw cx;
+- }//end if
+- }
+- }
+- return completed;
+-
+- }
+-
+- private void connect(NioSender[] senders) throws ChannelException {
+- ChannelException x = null;
+- for (int i=0; i<senders.length; i++ ) {
+- try {
+- if (!senders[i].isConnected()) senders[i].connect();
+- }catch ( IOException io ) {
+- if ( x==null ) x = new ChannelException(io);
+- x.addFaultyMember(senders[i].getDestination(),io);
+- }
+- }
+- if ( x != null ) throw x;
+- }
+-
+- private void setData(NioSender[] senders, byte[] data) throws ChannelException {
+- ChannelException x = null;
+- for (int i=0; i<senders.length; i++ ) {
+- try {
+- senders[i].setMessage(data);
+- }catch ( IOException io ) {
+- if ( x==null ) x = new ChannelException(io);
+- x.addFaultyMember(senders[i].getDestination(),io);
+- }
+- }
+- if ( x != null ) throw x;
+- }
+-
+-
+- private NioSender[] setupForSend(Member[] destination) throws ChannelException {
+- ChannelException cx = null;
+- NioSender[] result = new NioSender[destination.length];
+- for ( int i=0; i<destination.length; i++ ) {
+- NioSender sender = (NioSender)nioSenders.get(destination[i]);
+- try {
+-
+- if (sender == null) {
+- sender = new NioSender();
+- sender.transferProperties(this, sender);
+- nioSenders.put(destination[i], sender);
+- }
+- if (sender != null) {
+- sender.reset();
+- sender.setDestination(destination[i]);
+- sender.setSelector(selector);
+- result[i] = sender;
+- }
+- }catch ( UnknownHostException x ) {
+- if (cx == null) cx = new ChannelException("Unable to setup NioSender.", x);
+- cx.addFaultyMember(destination[i], x);
+- }
+- }
+- if ( cx != null ) throw cx;
+- else return result;
+- }
+-
+- public void connect() {
+- //do nothing, we connect on demand
+- setConnected(true);
+- }
+-
+-
+- private synchronized void close() throws ChannelException {
+- ChannelException x = null;
+- Object[] members = nioSenders.keySet().toArray();
+- for (int i=0; i<members.length; i++ ) {
+- Member mbr = (Member)members[i];
+- try {
+- NioSender sender = (NioSender)nioSenders.get(mbr);
+- sender.disconnect();
+- }catch ( Exception e ) {
+- if ( x == null ) x = new ChannelException(e);
+- x.addFaultyMember(mbr,e);
+- }
+- nioSenders.remove(mbr);
+- }
+- if ( x != null ) throw x;
+- }
+-
+- public void add(Member member) {
+-
+- }
+-
+- public void remove(Member member) {
+- //disconnect senders
+- NioSender sender = (NioSender)nioSenders.remove(member);
+- if ( sender != null ) sender.disconnect();
+- }
+-
+-
+- public synchronized void disconnect() {
+- setConnected(false);
+- try {close(); }catch (Exception x){}
+-
+- }
+-
+- public void finalize() {
+- try {disconnect(); }catch ( Exception ignore){}
+- }
+-
+- public boolean keepalive() {
+- boolean result = false;
+- for ( Iterator i = nioSenders.entrySet().iterator(); i.hasNext(); ) {
+- Map.Entry entry = (Map.Entry)i.next();
+- NioSender sender = (NioSender)entry.getValue();
+- if ( sender.keepalive() ) {
+- //nioSenders.remove(entry.getKey());
+- i.remove();
+- result = true;
+- } else {
+- try {
+- sender.read(null);
+- }catch ( IOException x ) {
+- sender.disconnect();
+- sender.reset();
+- //nioSenders.remove(entry.getKey());
+- i.remove();
+- result = true;
+- }catch ( Exception x ) {
+- log.warn("Error during keepalive test for sender:"+sender,x);
+- }
+- }
+- }
+- //clean up any cancelled keys
+- if ( result ) try { selector.selectNow(); }catch (Exception ignore){}
+- return result;
+- }
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/transport/nio/NioSender.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/nio/NioSender.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/nio/NioSender.java (working copy)
+@@ -1,340 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.transport.nio;
+-
+-import java.io.IOException;
+-import java.net.InetSocketAddress;
+-import java.nio.ByteBuffer;
+-import java.nio.channels.SelectionKey;
+-import java.nio.channels.Selector;
+-import java.nio.channels.SocketChannel;
+-import java.util.Arrays;
+-
+-import org.apache.catalina.tribes.io.XByteBuffer;
+-import org.apache.catalina.tribes.transport.AbstractSender;
+-import org.apache.catalina.tribes.transport.DataSender;
+-import org.apache.catalina.tribes.RemoteProcessException;
+-import java.io.EOFException;
+-import java.net.*;
+-
+-/**
+- * This class is NOT thread safe and should never be used with more than one thread at a time
+- *
+- * This is a state machine, handled by the process method
+- * States are:
+- * - NOT_CONNECTED -> connect() -> CONNECTED
+- * - CONNECTED -> setMessage() -> READY TO WRITE
+- * - READY_TO_WRITE -> write() -> READY TO WRITE | READY TO READ
+- * - READY_TO_READ -> read() -> READY_TO_READ | TRANSFER_COMPLETE
+- * - TRANSFER_COMPLETE -> CONNECTED
+- *
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public class NioSender extends AbstractSender implements DataSender{
+-
+- protected static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(NioSender.class);
+-
+-
+-
+- protected Selector selector;
+- protected SocketChannel socketChannel;
+-
+- /*
+- * STATE VARIABLES *
+- */
+- protected ByteBuffer readbuf = null;
+- protected ByteBuffer writebuf = null;
+- protected byte[] current = null;
+- protected XByteBuffer ackbuf = new XByteBuffer(128,true);
+- protected int remaining = 0;
+- protected boolean complete;
+-
+- protected boolean connecting = false;
+-
+- public NioSender() {
+- super();
+-
+- }
+-
+- /**
+- * State machine to send data
+- * @param key SelectionKey
+- * @return boolean
+- * @throws IOException
+- */
+- public boolean process(SelectionKey key, boolean waitForAck) throws IOException {
+- int ops = key.readyOps();
+- key.interestOps(key.interestOps() & ~ops);
+- //in case disconnect has been called
+- if ((!isConnected()) && (!connecting)) throw new IOException("Sender has been disconnected, can't selection key.");
+- if ( !key.isValid() ) throw new IOException("Key is not valid, it must have been cancelled.");
+- if ( key.isConnectable() ) {
+- if ( socketChannel.finishConnect() ) {
+- completeConnect();
+- if ( current != null ) key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
+- return false;
+- } else {
+- //wait for the connection to finish
+- key.interestOps(key.interestOps() | SelectionKey.OP_CONNECT);
+- return false;
+- }//end if
+- } else if ( key.isWritable() ) {
+- boolean writecomplete = write(key);
+- if ( writecomplete ) {
+- //we are completed, should we read an ack?
+- if ( waitForAck ) {
+- //register to read the ack
+- key.interestOps(key.interestOps() | SelectionKey.OP_READ);
+- } else {
+- //if not, we are ready, setMessage will reregister us for another write interest
+- //do a health check, we have no way of verify a disconnected
+- //socket since we don't register for OP_READ on waitForAck=false
+- read(key);//this causes overhead
+- setRequestCount(getRequestCount()+1);
+- return true;
+- }
+- } else {
+- //we are not complete, lets write some more
+- key.interestOps(key.interestOps()|SelectionKey.OP_WRITE);
+- }//end if
+- } else if ( key.isReadable() ) {
+- boolean readcomplete = read(key);
+- if ( readcomplete ) {
+- setRequestCount(getRequestCount()+1);
+- return true;
+- } else {
+- key.interestOps(key.interestOps() | SelectionKey.OP_READ);
+- }//end if
+- } else {
+- //unknown state, should never happen
+- log.warn("Data is in unknown state. readyOps="+ops);
+- throw new IOException("Data is in unknown state. readyOps="+ops);
+- }//end if
+- return false;
+- }
+-
+- private void completeConnect() throws SocketException {
+- //we connected, register ourselves for writing
+- setConnected(true);
+- connecting = false;
+- setRequestCount(0);
+- setConnectTime(System.currentTimeMillis());
+- socketChannel.socket().setSendBufferSize(getTxBufSize());
+- socketChannel.socket().setReceiveBufferSize(getRxBufSize());
+- socketChannel.socket().setSoTimeout((int)getTimeout());
+- socketChannel.socket().setSoLinger(getSoLingerOn(),getSoLingerOn()?getSoLingerTime():0);
+- socketChannel.socket().setTcpNoDelay(getTcpNoDelay());
+- socketChannel.socket().setKeepAlive(getSoKeepAlive());
+- socketChannel.socket().setReuseAddress(getSoReuseAddress());
+- socketChannel.socket().setOOBInline(getOoBInline());
+- socketChannel.socket().setSoLinger(getSoLingerOn(),getSoLingerTime());
+- socketChannel.socket().setTrafficClass(getSoTrafficClass());
+- }
+-
+-
+-
+- protected boolean read(SelectionKey key) throws IOException {
+- //if there is no message here, we are done
+- if ( current == null ) return true;
+- int read = socketChannel.read(readbuf);
+- //end of stream
+- if ( read == -1 ) throw new IOException("Unable to receive an ack message. EOF on socket channel has been reached.");
+- //no data read
+- else if ( read == 0 ) return false;
+- readbuf.flip();
+- ackbuf.append(readbuf,read);
+- readbuf.clear();
+- if (ackbuf.doesPackageExist() ) {
+- byte[] ackcmd = ackbuf.extractDataPackage(true).getBytes();
+- boolean ack = Arrays.equals(ackcmd,org.apache.catalina.tribes.transport.Constants.ACK_DATA);
+- boolean fack = Arrays.equals(ackcmd,org.apache.catalina.tribes.transport.Constants.FAIL_ACK_DATA);
+- if ( fack && getThrowOnFailedAck() ) throw new RemoteProcessException("Received a failed ack:org.apache.catalina.tribes.transport.Constants.FAIL_ACK_DATA");
+- return ack || fack;
+- } else {
+- return false;
+- }
+- }
+-
+-
+- protected boolean write(SelectionKey key) throws IOException {
+- if ( (!isConnected()) || (this.socketChannel==null)) {
+- throw new IOException("NioSender is not connected, this should not occur.");
+- }
+- if ( current != null ) {
+- if ( remaining > 0 ) {
+- //weve written everything, or we are starting a new package
+- //protect against buffer overwrite
+- int byteswritten = socketChannel.write(writebuf);
+- if (byteswritten == -1 ) throw new EOFException();
+- remaining -= byteswritten;
+- //if the entire message was written from the buffer
+- //reset the position counter
+- if ( remaining < 0 ) {
+- remaining = 0;
+- }
+- }
+- return (remaining==0);
+- }
+- //no message to send, we can consider that complete
+- return true;
+- }
+-
+- /**
+- * connect - blocking in this operation
+- *
+- * @throws IOException
+- * @todo Implement this org.apache.catalina.tribes.transport.IDataSender method
+- */
+- public synchronized void connect() throws IOException {
+- if ( connecting ) return;
+- connecting = true;
+- if ( isConnected() ) throw new IOException("NioSender is already in connected state.");
+- if ( readbuf == null ) {
+- readbuf = getReadBuffer();
+- } else {
+- readbuf.clear();
+- }
+- if ( writebuf == null ) {
+- writebuf = getWriteBuffer();
+- } else {
+- writebuf.clear();
+- }
+-
+- InetSocketAddress addr = new InetSocketAddress(getAddress(),getPort());
+- if ( socketChannel != null ) throw new IOException("Socket channel has already been established. Connection might be in progress.");
+- socketChannel = SocketChannel.open();
+- socketChannel.configureBlocking(false);
+- if ( socketChannel.connect(addr) ) {
+- completeConnect();
+- socketChannel.register(getSelector(), SelectionKey.OP_WRITE, this);
+- } else {
+- socketChannel.register(getSelector(), SelectionKey.OP_CONNECT, this);
+- }
+- }
+-
+-
+- /**
+- * disconnect
+- *
+- * @todo Implement this org.apache.catalina.tribes.transport.IDataSender method
+- */
+- public void disconnect() {
+- try {
+- connecting = false;
+- setConnected(false);
+- if ( socketChannel != null ) {
+- try {
+- try {socketChannel.socket().close();}catch ( Exception x){}
+- //error free close, all the way
+- //try {socket.shutdownOutput();}catch ( Exception x){}
+- //try {socket.shutdownInput();}catch ( Exception x){}
+- //try {socket.close();}catch ( Exception x){}
+- try {socketChannel.close();}catch ( Exception x){}
+- }finally {
+- socketChannel = null;
+- }
+- }
+- } catch ( Exception x ) {
+- log.error("Unable to disconnect NioSender. msg="+x.getMessage());
+- if ( log.isDebugEnabled() ) log.debug("Unable to disconnect NioSender. msg="+x.getMessage(),x);
+- } finally {
+- }
+-
+- }
+-
+- public void reset() {
+- if ( isConnected() && readbuf == null) {
+- readbuf = getReadBuffer();
+- }
+- if ( readbuf != null ) readbuf.clear();
+- if ( writebuf != null ) writebuf.clear();
+- current = null;
+- ackbuf.clear();
+- remaining = 0;
+- complete = false;
+- setAttempt(0);
+- setRequestCount(0);
+- setConnectTime(-1);
+- }
+-
+- private ByteBuffer getReadBuffer() {
+- return getBuffer(getRxBufSize());
+- }
+-
+- private ByteBuffer getWriteBuffer() {
+- return getBuffer(getTxBufSize());
+- }
+-
+- private ByteBuffer getBuffer(int size) {
+- return (getDirectBuffer()?ByteBuffer.allocateDirect(size):ByteBuffer.allocate(size));
+- }
+-
+- /**
+- * sendMessage
+- *
+- * @param data ChannelMessage
+- * @throws IOException
+- * @todo Implement this org.apache.catalina.tribes.transport.IDataSender method
+- */
+- public synchronized void setMessage(byte[] data) throws IOException {
+- setMessage(data,0,data.length);
+- }
+-
+- public synchronized void setMessage(byte[] data,int offset, int length) throws IOException {
+- if ( data != null ) {
+- current = data;
+- remaining = length;
+- ackbuf.clear();
+- if ( writebuf != null ) writebuf.clear();
+- else writebuf = getBuffer(length);
+- if ( writebuf.capacity() < length ) writebuf = getBuffer(length);
+- writebuf.put(data,offset,length);
+- //writebuf.rewind();
+- //set the limit so that we don't write non wanted data
+- //writebuf.limit(length);
+- writebuf.flip();
+- if (isConnected()) {
+- socketChannel.register(getSelector(), SelectionKey.OP_WRITE, this);
+- }
+- }
+- }
+-
+- public byte[] getMessage() {
+- return current;
+- }
+-
+-
+-
+- public boolean isComplete() {
+- return complete;
+- }
+-
+- public Selector getSelector() {
+- return selector;
+- }
+-
+- public void setSelector(Selector selector) {
+- this.selector = selector;
+- }
+-
+-
+- public void setComplete(boolean complete) {
+- this.complete = complete;
+- }
+-}
+Index: java/org/apache/catalina/tribes/transport/nio/NioReceiver.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/nio/NioReceiver.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/nio/NioReceiver.java (working copy)
+@@ -1,386 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.transport.nio;
+-
+-import java.io.IOException;
+-import java.net.ServerSocket;
+-import java.nio.channels.SelectableChannel;
+-import java.nio.channels.SelectionKey;
+-import java.nio.channels.Selector;
+-import java.nio.channels.ServerSocketChannel;
+-import java.nio.channels.SocketChannel;
+-import java.util.Iterator;
+-
+-import org.apache.catalina.tribes.ChannelReceiver;
+-import org.apache.catalina.tribes.io.ListenCallback;
+-import org.apache.catalina.tribes.io.ObjectReader;
+-import org.apache.catalina.tribes.transport.Constants;
+-import org.apache.catalina.tribes.transport.ReceiverBase;
+-import org.apache.catalina.tribes.transport.RxTaskPool;
+-import org.apache.catalina.tribes.transport.AbstractRxTask;
+-import org.apache.catalina.tribes.util.StringManager;
+-import java.util.LinkedList;
+-import java.util.Set;
+-import java.nio.channels.CancelledKeyException;
+-
+-/**
+- * @author Filip Hanik
+- * @version $Revision$ $Date$
+- */
+-public class NioReceiver extends ReceiverBase implements Runnable, ChannelReceiver, ListenCallback {
+-
+- protected static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(NioReceiver.class);
+-
+- /**
+- * The string manager for this package.
+- */
+- protected StringManager sm = StringManager.getManager(Constants.Package);
+-
+- /**
+- * The descriptive information about this implementation.
+- */
+- private static final String info = "NioReceiver/1.0";
+-
+- private Selector selector = null;
+- private ServerSocketChannel serverChannel = null;
+-
+- protected LinkedList events = new LinkedList();
+-// private Object interestOpsMutex = new Object();
+-
+- public NioReceiver() {
+- }
+-
+- /**
+- * Return descriptive information about this implementation and the
+- * corresponding version number, in the format
+- * <code><description>/<version></code>.
+- */
+- public String getInfo() {
+- return (info);
+- }
+-
+-// public Object getInterestOpsMutex() {
+-// return interestOpsMutex;
+-// }
+-
+- public void stop() {
+- this.stopListening();
+- super.stop();
+- }
+-
+- /**
+- * start cluster receiver
+- * @throws Exception
+- * @see org.apache.catalina.tribes.ClusterReceiver#start()
+- */
+- public void start() throws IOException {
+- super.start();
+- try {
+- setPool(new RxTaskPool(getMaxThreads(),getMinThreads(),this));
+- } catch (Exception x) {
+- log.fatal("ThreadPool can initilzed. Listener not started", x);
+- if ( x instanceof IOException ) throw (IOException)x;
+- else throw new IOException(x.getMessage());
+- }
+- try {
+- getBind();
+- bind();
+- Thread t = new Thread(this, "NioReceiver");
+- t.setDaemon(true);
+- t.start();
+- } catch (Exception x) {
+- log.fatal("Unable to start cluster receiver", x);
+- if ( x instanceof IOException ) throw (IOException)x;
+- else throw new IOException(x.getMessage());
+- }
+- }
+-
+- public AbstractRxTask createRxTask() {
+- NioReplicationTask thread = new NioReplicationTask(this,this);
+- thread.setUseBufferPool(this.getUseBufferPool());
+- thread.setRxBufSize(getRxBufSize());
+- thread.setOptions(getWorkerThreadOptions());
+- return thread;
+- }
+-
+-
+-
+- protected void bind() throws IOException {
+- // allocate an unbound server socket channel
+- serverChannel = ServerSocketChannel.open();
+- // Get the associated ServerSocket to bind it with
+- ServerSocket serverSocket = serverChannel.socket();
+- // create a new Selector for use below
+- selector = Selector.open();
+- // set the port the server channel will listen to
+- //serverSocket.bind(new InetSocketAddress(getBind(), getTcpListenPort()));
+- bind(serverSocket,getTcpListenPort(),getAutoBind());
+- // set non-blocking mode for the listening socket
+- serverChannel.configureBlocking(false);
+- // register the ServerSocketChannel with the Selector
+- serverChannel.register(selector, SelectionKey.OP_ACCEPT);
+-
+- }
+-
+- public void addEvent(Runnable event) {
+- if ( selector != null ) {
+- synchronized (events) {
+- events.add(event);
+- }
+- if ( log.isTraceEnabled() ) log.trace("Adding event to selector:"+event);
+- if ( isListening() && selector!=null ) selector.wakeup();
+- }
+- }
+-
+- public void events() {
+- if ( events.size() == 0 ) return;
+- synchronized (events) {
+- Runnable r = null;
+- while ( (events.size() > 0) && (r = (Runnable)events.removeFirst()) != null ) {
+- try {
+- if ( log.isTraceEnabled() ) log.trace("Processing event in selector:"+r);
+- r.run();
+- } catch ( Exception x ) {
+- log.error("",x);
+- }
+- }
+- events.clear();
+- }
+- }
+-
+- public static void cancelledKey(SelectionKey key) {
+- ObjectReader reader = (ObjectReader)key.attachment();
+- if ( reader != null ) {
+- reader.setCancelled(true);
+- reader.finish();
+- }
+- key.cancel();
+- key.attach(null);
+- try { ((SocketChannel)key.channel()).socket().close(); } catch (IOException e) { if (log.isDebugEnabled()) log.debug("", e); }
+- try { key.channel().close(); } catch (IOException e) { if (log.isDebugEnabled()) log.debug("", e); }
+-
+- }
+- protected long lastCheck = System.currentTimeMillis();
+- protected void socketTimeouts() {
+- long now = System.currentTimeMillis();
+- if ( (now-lastCheck) < getSelectorTimeout() ) return;
+- //timeout
+- Selector tmpsel = selector;
+- Set keys = (isListening()&&tmpsel!=null)?tmpsel.keys():null;
+- if ( keys == null ) return;
+- for (Iterator iter = keys.iterator(); iter.hasNext(); ) {
+- SelectionKey key = (SelectionKey) iter.next();
+- try {
+-// if (key.interestOps() == SelectionKey.OP_READ) {
+-// //only timeout sockets that we are waiting for a read from
+-// ObjectReader ka = (ObjectReader) key.attachment();
+-// long delta = now - ka.getLastAccess();
+-// if (delta > (long) getTimeout()) {
+-// cancelledKey(key);
+-// }
+-// }
+-// else
+- if ( key.interestOps() == 0 ) {
+- //check for keys that didn't make it in.
+- ObjectReader ka = (ObjectReader) key.attachment();
+- if ( ka != null ) {
+- long delta = now - ka.getLastAccess();
+- if (delta > (long) getTimeout() && (!ka.isAccessed())) {
+- log.warn("Channel key is registered, but has had no interest ops for the last "+getTimeout()+" ms. (cancelled:"+ka.isCancelled()+"):"+key+" last access:"+new java.sql.Timestamp(ka.getLastAccess()));
+-// System.out.println("Interest:"+key.interestOps());
+-// System.out.println("Ready Ops:"+key.readyOps());
+-// System.out.println("Valid:"+key.isValid());
+- ka.setLastAccess(now);
+- //key.interestOps(SelectionKey.OP_READ);
+- }//end if
+- } else {
+- cancelledKey(key);
+- }//end if
+- }//end if
+- }catch ( CancelledKeyException ckx ) {
+- cancelledKey(key);
+- }
+- }
+- lastCheck = System.currentTimeMillis();
+- }
+-
+-
+- /**
+- * get data from channel and store in byte array
+- * send it to cluster
+- * @throws IOException
+- * @throws java.nio.channels.ClosedChannelException
+- */
+- protected void listen() throws Exception {
+- if (doListen()) {
+- log.warn("ServerSocketChannel already started");
+- return;
+- }
+-
+- setListen(true);
+-
+- while (doListen() && selector != null) {
+- // this may block for a long time, upon return the
+- // selected set contains keys of the ready channels
+- try {
+- events();
+- socketTimeouts();
+- int n = selector.select(getTcpSelectorTimeout());
+- if (n == 0) {
+- //there is a good chance that we got here
+- //because the TcpReplicationThread called
+- //selector wakeup().
+- //if that happens, we must ensure that that
+- //thread has enough time to call interestOps
+-// synchronized (interestOpsMutex) {
+- //if we got the lock, means there are no
+- //keys trying to register for the
+- //interestOps method
+-// }
+- continue; // nothing to do
+- }
+- // get an iterator over the set of selected keys
+- Iterator it = selector.selectedKeys().iterator();
+- // look at each key in the selected set
+- while (it.hasNext()) {
+- SelectionKey key = (SelectionKey) it.next();
+- // Is a new connection coming in?
+- if (key.isAcceptable()) {
+- ServerSocketChannel server = (ServerSocketChannel) key.channel();
+- SocketChannel channel = server.accept();
+- channel.socket().setReceiveBufferSize(getRxBufSize());
+- channel.socket().setSendBufferSize(getTxBufSize());
+- channel.socket().setTcpNoDelay(getTcpNoDelay());
+- channel.socket().setKeepAlive(getSoKeepAlive());
+- channel.socket().setOOBInline(getOoBInline());
+- channel.socket().setReuseAddress(getSoReuseAddress());
+- channel.socket().setSoLinger(getSoLingerOn(),getSoLingerTime());
+- channel.socket().setTrafficClass(getSoTrafficClass());
+- channel.socket().setSoTimeout(getTimeout());
+- Object attach = new ObjectReader(channel);
+- registerChannel(selector,
+- channel,
+- SelectionKey.OP_READ,
+- attach);
+- }
+- // is there data to read on this channel?
+- if (key.isReadable()) {
+- readDataFromSocket(key);
+- } else {
+- key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE));
+- }
+-
+- // remove key from selected set, it's been handled
+- it.remove();
+- }
+- } catch (java.nio.channels.ClosedSelectorException cse) {
+- // ignore is normal at shutdown or stop listen socket
+- } catch (java.nio.channels.CancelledKeyException nx) {
+- log.warn("Replication client disconnected, error when polling key. Ignoring client.");
+- } catch (Throwable x) {
+- try {
+- log.error("Unable to process request in NioReceiver", x);
+- }catch ( Throwable tx ) {
+- //in case an out of memory error, will affect the logging framework as well
+- tx.printStackTrace();
+- }
+- }
+-
+- }
+- serverChannel.close();
+- if (selector != null)
+- selector.close();
+- }
+-
+-
+-
+- /**
+- * Close Selector.
+- *
+- * @see org.apache.catalina.tribes.transport.ClusterReceiverBase#stopListening()
+- */
+- protected void stopListening() {
+- setListen(false);
+- if (selector != null) {
+- try {
+- selector.wakeup();
+- selector.close();
+- } catch (Exception x) {
+- log.error("Unable to close cluster receiver selector.", x);
+- } finally {
+- selector = null;
+- }
+- }
+- }
+-
+- // ----------------------------------------------------------
+-
+- /**
+- * Register the given channel with the given selector for
+- * the given operations of interest
+- */
+- protected void registerChannel(Selector selector,
+- SelectableChannel channel,
+- int ops,
+- Object attach) throws Exception {
+- if (channel == null)return; // could happen
+- // set the new channel non-blocking
+- channel.configureBlocking(false);
+- // register it with the selector
+- channel.register(selector, ops, attach);
+- }
+-
+- /**
+- * Start thread and listen
+- */
+- public void run() {
+- try {
+- listen();
+- } catch (Exception x) {
+- log.error("Unable to run replication listener.", x);
+- }
+- }
+-
+- // ----------------------------------------------------------
+-
+- /**
+- * Sample data handler method for a channel with data ready to read.
+- * @param key A SelectionKey object associated with a channel
+- * determined by the selector to be ready for reading. If the
+- * channel returns an EOF condition, it is closed here, which
+- * automatically invalidates the associated key. The selector
+- * will then de-register the channel on the next select call.
+- */
+- protected void readDataFromSocket(SelectionKey key) throws Exception {
+- NioReplicationTask task = (NioReplicationTask) getTaskPool().getRxTask();
+- if (task == null) {
+- // No threads/tasks available, do nothing, the selection
+- // loop will keep calling this method until a
+- // thread becomes available, the thread pool itself has a waiting mechanism
+- // so we will not wait here.
+- if (log.isDebugEnabled()) log.debug("No TcpReplicationThread available");
+- } else {
+- // invoking this wakes up the worker thread then returns
+- //add task to thread pool
+- task.serviceChannel(key);
+- getExecutor().execute(task);
+- }
+- }
+-
+-
+-}
+Index: java/org/apache/catalina/tribes/transport/MultiPointSender.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/MultiPointSender.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/MultiPointSender.java (working copy)
+@@ -1,38 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.transport;
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.Member;
+-
+-/**
+- * @author Filip Hanik
+- * @version $Revision$ $Date$
+- * @since 5.5.16
+- */
+-
+-public interface MultiPointSender extends DataSender
+-{
+- public void sendMessage(Member[] destination, ChannelMessage data) throws ChannelException;
+- public void setRxBufSize(int size);
+- public void setTxBufSize(int size);
+- public void setMaxRetryAttempts(int attempts);
+- public void setDirectBuffer(boolean directBuf);
+- public void add(Member member);
+- public void remove(Member member);
+-}
+Index: java/org/apache/catalina/tribes/transport/ReplicationTransmitter.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/ReplicationTransmitter.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/ReplicationTransmitter.java (working copy)
+@@ -1,135 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.transport;
+-
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.ChannelSender;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.util.StringManager;
+-import org.apache.catalina.tribes.transport.nio.PooledParallelSender;
+-
+-/**
+- * Transmit message to other cluster members
+- * Actual senders are created based on the replicationMode
+- * type
+- *
+- * @author Filip Hanik
+- * @version $Revision$ $Date$
+- */
+-public class ReplicationTransmitter implements ChannelSender {
+- private static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(ReplicationTransmitter.class);
+-
+- /**
+- * The descriptive information about this implementation.
+- */
+- private static final String info = "ReplicationTransmitter/3.0";
+-
+- /**
+- * The string manager for this package.
+- */
+- protected StringManager sm = StringManager.getManager(Constants.Package);
+-
+-
+-
+- public ReplicationTransmitter() {
+- }
+-
+- private MultiPointSender transport = new PooledParallelSender();
+-
+- /**
+- * Return descriptive information about this implementation and the
+- * corresponding version number, in the format
+- * <code><description>/<version></code>.
+- */
+- public String getInfo() {
+- return (info);
+- }
+-
+- public MultiPointSender getTransport() {
+- return transport;
+- }
+-
+- public void setTransport(MultiPointSender transport) {
+- this.transport = transport;
+- }
+-
+- // ------------------------------------------------------------- public
+-
+- /**
+- * Send data to one member
+- * @see org.apache.catalina.tribes.ClusterSender#sendMessage(org.apache.catalina.tribes.ClusterMessage, org.apache.catalina.tribes.Member)
+- */
+- public void sendMessage(ChannelMessage message, Member[] destination) throws ChannelException {
+- MultiPointSender sender = getTransport();
+- sender.sendMessage(destination,message);
+- }
+-
+-
+- /**
+- * start the sender and register transmitter mbean
+- *
+- * @see org.apache.catalina.tribes.ClusterSender#start()
+- */
+- public void start() throws java.io.IOException {
+- getTransport().connect();
+- }
+-
+- /*
+- * stop the sender and deregister mbeans (transmitter, senders)
+- *
+- * @see org.apache.catalina.tribes.ClusterSender#stop()
+- */
+- public synchronized void stop() {
+- getTransport().disconnect();
+- }
+-
+- /**
+- * Call transmitter to check for sender socket status
+- *
+- * @see SimpleTcpCluster#backgroundProcess()
+- */
+-
+- public void heartbeat() {
+- if (getTransport()!=null) getTransport().keepalive();
+- }
+-
+- /**
+- * add new cluster member and create sender ( s. replicationMode) transfer
+- * current properties to sender
+- *
+- * @see org.apache.catalina.tribes.ClusterSender#add(org.apache.catalina.tribes.Member)
+- */
+- public synchronized void add(Member member) {
+- getTransport().add(member);
+- }
+-
+- /**
+- * remove sender from transmitter. ( deregister mbean and disconnect sender )
+- *
+- * @see org.apache.catalina.tribes.ClusterSender#remove(org.apache.catalina.tribes.Member)
+- */
+- public synchronized void remove(Member member) {
+- getTransport().remove(member);
+- }
+-
+- // ------------------------------------------------------------- protected
+-
+-
+-
+-}
+Index: java/org/apache/catalina/tribes/transport/RxTaskPool.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/RxTaskPool.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/RxTaskPool.java (working copy)
+@@ -1,159 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.transport;
+-import java.util.Iterator;
+-import java.util.LinkedList;
+-import java.util.List;
+-import java.util.concurrent.ThreadFactory;
+-
+-/**
+- * @author not attributable
+- * @version 1.0
+- */
+-
+-public class RxTaskPool
+-{
+- /**
+- * A very simple thread pool class. The pool size is set at
+- * construction time and remains fixed. Threads are cycled
+- * through a FIFO idle queue.
+- */
+-
+- List idle = new LinkedList();
+- List used = new LinkedList();
+-
+- Object mutex = new Object();
+- boolean running = true;
+-
+- private static int counter = 1;
+- private int maxTasks;
+- private int minTasks;
+-
+- private TaskCreator creator = null;
+-
+- private static synchronized int inc() {
+- return counter++;
+- }
+-
+-
+- public RxTaskPool (int maxTasks, int minTasks, TaskCreator creator) throws Exception {
+- // fill up the pool with worker threads
+- this.maxTasks = maxTasks;
+- this.minTasks = minTasks;
+- this.creator = creator;
+- }
+-
+- protected void configureTask(AbstractRxTask task) {
+- synchronized (task) {
+- task.setTaskPool(this);
+-// task.setName(task.getClass().getName() + "[" + inc() + "]");
+-// task.setDaemon(true);
+-// task.setPriority(Thread.MAX_PRIORITY);
+-// task.start();
+- }
+- }
+-
+- /**
+- * Find an idle worker thread, if any. Could return null.
+- */
+- public AbstractRxTask getRxTask()
+- {
+- AbstractRxTask worker = null;
+- synchronized (mutex) {
+- while ( worker == null && running ) {
+- if (idle.size() > 0) {
+- try {
+- worker = (AbstractRxTask) idle.remove(0);
+- } catch (java.util.NoSuchElementException x) {
+- //this means that there are no available workers
+- worker = null;
+- }
+- } else if ( used.size() < this.maxTasks && creator != null) {
+- worker = creator.createRxTask();
+- configureTask(worker);
+- } else {
+- try { mutex.wait(); } catch ( java.lang.InterruptedException x ) {Thread.currentThread().interrupted();}
+- }
+- }//while
+- if ( worker != null ) used.add(worker);
+- }
+- return (worker);
+- }
+-
+- public int available() {
+- return idle.size();
+- }
+-
+- /**
+- * Called by the worker thread to return itself to the
+- * idle pool.
+- */
+- public void returnWorker (AbstractRxTask worker) {
+- if ( running ) {
+- synchronized (mutex) {
+- used.remove(worker);
+- //if ( idle.size() < minThreads && !idle.contains(worker)) idle.add(worker);
+- if ( idle.size() < maxTasks && !idle.contains(worker)) idle.add(worker); //let max be the upper limit
+- else {
+- worker.setDoRun(false);
+- synchronized (worker){worker.notify();}
+- }
+- mutex.notify();
+- }
+- }else {
+- worker.setDoRun(false);
+- synchronized (worker){worker.notify();}
+- }
+- }
+-
+- public int getMaxThreads() {
+- return maxTasks;
+- }
+-
+- public int getMinThreads() {
+- return minTasks;
+- }
+-
+- public void stop() {
+- running = false;
+- synchronized (mutex) {
+- Iterator i = idle.iterator();
+- while ( i.hasNext() ) {
+- AbstractRxTask worker = (AbstractRxTask)i.next();
+- returnWorker(worker);
+- i.remove();
+- }
+- }
+- }
+-
+- public void setMaxTasks(int maxThreads) {
+- this.maxTasks = maxThreads;
+- }
+-
+- public void setMinTasks(int minThreads) {
+- this.minTasks = minThreads;
+- }
+-
+- public TaskCreator getTaskCreator() {
+- return this.creator;
+- }
+-
+- public static interface TaskCreator {
+- public AbstractRxTask createRxTask();
+- }
+-}
+Index: java/org/apache/catalina/tribes/transport/bio/MultipointBioSender.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/bio/MultipointBioSender.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/bio/MultipointBioSender.java (working copy)
+@@ -1,144 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.transport.bio;
+-
+-import java.io.IOException;
+-import java.util.HashMap;
+-import java.util.Map;
+-
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.io.ChannelData;
+-import org.apache.catalina.tribes.io.XByteBuffer;
+-import org.apache.catalina.tribes.transport.MultiPointSender;
+-import org.apache.catalina.tribes.transport.AbstractSender;
+-import org.apache.catalina.tribes.Channel;
+-
+-/**
+- *
+- * @author Filip Hanik
+- * @version $Revision$ $Date$
+- *
+- */
+-public class MultipointBioSender extends AbstractSender implements MultiPointSender {
+- public MultipointBioSender() {
+- }
+-
+- protected long selectTimeout = 1000;
+- protected HashMap bioSenders = new HashMap();
+-
+- public synchronized void sendMessage(Member[] destination, ChannelMessage msg) throws ChannelException {
+- byte[] data = XByteBuffer.createDataPackage((ChannelData)msg);
+- BioSender[] senders = setupForSend(destination);
+- ChannelException cx = null;
+- for ( int i=0; i<senders.length; i++ ) {
+- try {
+- senders[i].sendMessage(data,(msg.getOptions()&Channel.SEND_OPTIONS_USE_ACK)==Channel.SEND_OPTIONS_USE_ACK);
+- } catch (Exception x) {
+- if (cx == null) cx = new ChannelException(x);
+- cx.addFaultyMember(destination[i],x);
+- }
+- }
+- if (cx!=null ) throw cx;
+- }
+-
+-
+-
+- protected BioSender[] setupForSend(Member[] destination) throws ChannelException {
+- ChannelException cx = null;
+- BioSender[] result = new BioSender[destination.length];
+- for ( int i=0; i<destination.length; i++ ) {
+- try {
+- BioSender sender = (BioSender) bioSenders.get(destination[i]);
+- if (sender == null) {
+- sender = new BioSender();
+- sender.transferProperties(this,sender);
+- sender.setDestination(destination[i]);
+- bioSenders.put(destination[i], sender);
+- }
+- result[i] = sender;
+- if (!result[i].isConnected() ) result[i].connect();
+- result[i].keepalive();
+- }catch (Exception x ) {
+- if ( cx== null ) cx = new ChannelException(x);
+- cx.addFaultyMember(destination[i],x);
+- }
+- }
+- if ( cx!=null ) throw cx;
+- else return result;
+- }
+-
+- public void connect() throws IOException {
+- //do nothing, we connect on demand
+- setConnected(true);
+- }
+-
+-
+- private synchronized void close() throws ChannelException {
+- ChannelException x = null;
+- Object[] members = bioSenders.keySet().toArray();
+- for (int i=0; i<members.length; i++ ) {
+- Member mbr = (Member)members[i];
+- try {
+- BioSender sender = (BioSender)bioSenders.get(mbr);
+- sender.disconnect();
+- }catch ( Exception e ) {
+- if ( x == null ) x = new ChannelException(e);
+- x.addFaultyMember(mbr,e);
+- }
+- bioSenders.remove(mbr);
+- }
+- if ( x != null ) throw x;
+- }
+-
+- public void add(Member member) {
+-
+- }
+-
+- public void remove(Member member) {
+- //disconnect senders
+- BioSender sender = (BioSender)bioSenders.remove(member);
+- if ( sender != null ) sender.disconnect();
+- }
+-
+-
+- public synchronized void disconnect() {
+- try {close(); }catch (Exception x){}
+- setConnected(false);
+- }
+-
+- public void finalize() {
+- try {disconnect(); }catch ( Exception ignore){}
+- }
+-
+-
+- public boolean keepalive() {
+- //throw new UnsupportedOperationException("Method ParallelBioSender.checkKeepAlive() not implemented");
+- boolean result = false;
+- Map.Entry[] entries = (Map.Entry[])bioSenders.entrySet().toArray(new Map.Entry[bioSenders.size()]);
+- for ( int i=0; i<entries.length; i++ ) {
+- BioSender sender = (BioSender)entries[i].getValue();
+- if ( sender.keepalive() ) {
+- bioSenders.remove(entries[i].getKey());
+- }
+- }
+- return result;
+- }
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/transport/bio/BioReplicationTask.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/bio/BioReplicationTask.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/bio/BioReplicationTask.java (working copy)
+@@ -1,166 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.transport.bio;
+-
+-import org.apache.catalina.tribes.io.ObjectReader;
+-import org.apache.catalina.tribes.transport.Constants;
+-import org.apache.catalina.tribes.transport.AbstractRxTask;
+-import java.net.Socket;
+-import java.io.InputStream;
+-import java.io.OutputStream;
+-import org.apache.catalina.tribes.io.ListenCallback;
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.io.ChannelData;
+-import org.apache.catalina.tribes.io.BufferPool;
+-
+-/**
+- * A worker thread class which can drain channels and echo-back the input. Each
+- * instance is constructed with a reference to the owning thread pool object.
+- * When started, the thread loops forever waiting to be awakened to service the
+- * channel associated with a SelectionKey object. The worker is tasked by
+- * calling its serviceChannel() method with a SelectionKey object. The
+- * serviceChannel() method stores the key reference in the thread object then
+- * calls notify() to wake it up. When the channel has been drained, the worker
+- * thread returns itself to its parent pool.
+- *
+- * @author Filip Hanik
+- *
+- * @version $Revision$, $Date$
+- */
+-public class BioReplicationTask extends AbstractRxTask {
+-
+-
+- protected static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog( BioReplicationTask.class );
+-
+- protected Socket socket;
+- protected ObjectReader reader;
+-
+- public BioReplicationTask (ListenCallback callback) {
+- super(callback);
+- }
+-
+- // loop forever waiting for work to do
+- public synchronized void run()
+- {
+- if ( socket == null ) return;
+- try {
+- drainSocket();
+- } catch ( Exception x ) {
+- log.error("Unable to service bio socket");
+- }finally {
+- try {socket.close();}catch ( Exception ignore){}
+- try {reader.close();}catch ( Exception ignore){}
+- reader = null;
+- socket = null;
+- }
+- // done, ready for more, return to pool
+- if ( getTaskPool() != null ) getTaskPool().returnWorker (this);
+- }
+-
+-
+- public synchronized void serviceSocket(Socket socket, ObjectReader reader) {
+- this.socket = socket;
+- this.reader = reader;
+- this.notify(); // awaken the thread
+- }
+-
+- protected void execute(ObjectReader reader) throws Exception{
+- int pkgcnt = reader.count();
+-
+- if ( pkgcnt > 0 ) {
+- ChannelMessage[] msgs = reader.execute();
+- for ( int i=0; i<msgs.length; i++ ) {
+- /**
+- * Use send ack here if you want to ack the request to the remote
+- * server before completing the request
+- * This is considered an asynchronized request
+- */
+- if (ChannelData.sendAckAsync(msgs[i].getOptions())) sendAck(Constants.ACK_COMMAND);
+- try {
+- //process the message
+- getCallback().messageDataReceived(msgs[i]);
+- /**
+- * Use send ack here if you want the request to complete on this
+- * server before sending the ack to the remote server
+- * This is considered a synchronized request
+- */
+- if (ChannelData.sendAckSync(msgs[i].getOptions())) sendAck(Constants.ACK_COMMAND);
+- }catch ( Exception x ) {
+- if (ChannelData.sendAckSync(msgs[i].getOptions())) sendAck(Constants.FAIL_ACK_COMMAND);
+- log.error("Error thrown from messageDataReceived.",x);
+- }
+- if ( getUseBufferPool() ) {
+- BufferPool.getBufferPool().returnBuffer(msgs[i].getMessage());
+- msgs[i].setMessage(null);
+- }
+- }
+- }
+-
+-
+- }
+-
+- /**
+- * The actual code which drains the channel associated with
+- * the given key. This method assumes the key has been
+- * modified prior to invocation to turn off selection
+- * interest in OP_READ. When this method completes it
+- * re-enables OP_READ and calls wakeup() on the selector
+- * so the selector will resume watching this channel.
+- */
+- protected void drainSocket () throws Exception {
+- InputStream in = socket.getInputStream();
+- // loop while data available, channel is non-blocking
+- byte[] buf = new byte[1024];
+- int length = in.read(buf);
+- while ( length >= 0 ) {
+- int count = reader.append(buf,0,length,true);
+- if ( count > 0 ) execute(reader);
+- length = in.read(buf);
+- }
+- }
+-
+-
+-
+-
+- /**
+- * send a reply-acknowledgement (6,2,3)
+- * @param key
+- * @param channel
+- */
+- protected void sendAck(byte[] command) {
+- try {
+- OutputStream out = socket.getOutputStream();
+- out.write(command);
+- out.flush();
+- if (log.isTraceEnabled()) {
+- log.trace("ACK sent to " + socket.getPort());
+- }
+- } catch ( java.io.IOException x ) {
+- log.warn("Unable to send ACK back through channel, channel disconnected?: "+x.getMessage());
+- }
+- }
+-
+- public void close() {
+- setDoRun(false);
+- try {socket.close();}catch ( Exception ignore){}
+- try {reader.close();}catch ( Exception ignore){}
+- reader = null;
+- socket = null;
+- super.close();
+- }
+-}
+Index: java/org/apache/catalina/tribes/transport/bio/BioSender.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/bio/BioSender.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/bio/BioSender.java (working copy)
+@@ -1,296 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.transport.bio;
+-
+-import java.io.IOException;
+-import java.io.InputStream;
+-import java.io.OutputStream;
+-import java.net.InetSocketAddress;
+-import java.net.Socket;
+-import java.util.Arrays;
+-
+-import org.apache.catalina.tribes.RemoteProcessException;
+-import org.apache.catalina.tribes.io.XByteBuffer;
+-import org.apache.catalina.tribes.transport.AbstractSender;
+-import org.apache.catalina.tribes.transport.Constants;
+-import org.apache.catalina.tribes.transport.DataSender;
+-import org.apache.catalina.tribes.transport.SenderState;
+-import org.apache.catalina.tribes.util.StringManager;
+-
+-/**
+- * Send cluster messages with only one socket. Ack and keep Alive Handling is
+- * supported
+- *
+- * @author Peter Rossbach
+- * @author Filip Hanik
+- * @version $Revision$ $Date$
+- * @since 5.5.16
+- */
+-public class BioSender extends AbstractSender implements DataSender {
+-
+- private static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(BioSender.class);
+-
+- /**
+- * The string manager for this package.
+- */
+- protected static StringManager sm = StringManager.getManager(Constants.Package);
+-
+- // ----------------------------------------------------- Instance Variables
+-
+- /**
+- * The descriptive information about this implementation.
+- */
+- private static final String info = "DataSender/3.0";
+-
+-
+- /**
+- * current sender socket
+- */
+- private Socket socket = null;
+- private OutputStream soOut = null;
+- private InputStream soIn = null;
+-
+- protected XByteBuffer ackbuf = new XByteBuffer(Constants.ACK_COMMAND.length,true);
+-
+-
+- // ------------------------------------------------------------- Constructor
+-
+- public BioSender() {
+- }
+-
+-
+- // ------------------------------------------------------------- Properties
+-
+- /**
+- * Return descriptive information about this implementation and the
+- * corresponding version number, in the format
+- * <code><description>/<version></code>.
+- */
+- public String getInfo() {
+- return (info);
+- }
+-
+- // --------------------------------------------------------- Public Methods
+-
+- /**
+- * Connect other cluster member receiver
+- * @see org.apache.catalina.tribes.transport.IDataSender#connect()
+- */
+- public void connect() throws IOException {
+- openSocket();
+- }
+-
+-
+- /**
+- * disconnect and close socket
+- *
+- * @see IDataSender#disconnect()
+- */
+- public void disconnect() {
+- boolean connect = isConnected();
+- closeSocket();
+- if (connect) {
+- if (log.isDebugEnabled())
+- log.debug(sm.getString("IDataSender.disconnect", getAddress().getHostAddress(), new Integer(getPort()), new Long(0)));
+- }
+-
+- }
+-
+- /**
+- * Send message
+- *
+- * @see org.apache.catalina.tribes.transport.IDataSender#sendMessage(,
+- * ChannelMessage)
+- */
+- public void sendMessage(byte[] data, boolean waitForAck) throws IOException {
+- IOException exception = null;
+- setAttempt(0);
+- try {
+- // first try with existing connection
+- pushMessage(data,false,waitForAck);
+- } catch (IOException x) {
+- SenderState.getSenderState(getDestination()).setSuspect();
+- exception = x;
+- if (log.isTraceEnabled()) log.trace(sm.getString("IDataSender.send.again", getAddress().getHostAddress(),new Integer(getPort())),x);
+- while ( getAttempt()<getMaxRetryAttempts() ) {
+- try {
+- setAttempt(getAttempt()+1);
+- // second try with fresh connection
+- pushMessage(data, true,waitForAck);
+- exception = null;
+- } catch (IOException xx) {
+- exception = xx;
+- closeSocket();
+- }
+- }
+- } finally {
+- setRequestCount(getRequestCount()+1);
+- keepalive();
+- if ( exception != null ) throw exception;
+- }
+- }
+-
+-
+- /**
+- * Name of this SockerSender
+- */
+- public String toString() {
+- StringBuffer buf = new StringBuffer("DataSender[(");
+- buf.append(super.toString()).append(")");
+- buf.append(getAddress()).append(":").append(getPort()).append("]");
+- return buf.toString();
+- }
+-
+- // --------------------------------------------------------- Protected Methods
+-
+- /**
+- * open real socket and set time out when waitForAck is enabled
+- * is socket open return directly
+- */
+- protected void openSocket() throws IOException {
+- if(isConnected()) return ;
+- try {
+- socket = new Socket();
+- InetSocketAddress sockaddr = new InetSocketAddress(getAddress(), getPort());
+- socket.connect(sockaddr,(int)getTimeout());
+- socket.setSendBufferSize(getTxBufSize());
+- socket.setReceiveBufferSize(getRxBufSize());
+- socket.setSoTimeout( (int) getTimeout());
+- socket.setTcpNoDelay(getTcpNoDelay());
+- socket.setKeepAlive(getSoKeepAlive());
+- socket.setReuseAddress(getSoReuseAddress());
+- socket.setOOBInline(getOoBInline());
+- socket.setSoLinger(getSoLingerOn(),getSoLingerTime());
+- socket.setTrafficClass(getSoTrafficClass());
+- setConnected(true);
+- soOut = socket.getOutputStream();
+- soIn = socket.getInputStream();
+- setRequestCount(0);
+- setConnectTime(System.currentTimeMillis());
+- if (log.isDebugEnabled())
+- log.debug(sm.getString("IDataSender.openSocket", getAddress().getHostAddress(), new Integer(getPort()), new Long(0)));
+- } catch (IOException ex1) {
+- SenderState.getSenderState(getDestination()).setSuspect();
+- if (log.isDebugEnabled())
+- log.debug(sm.getString("IDataSender.openSocket.failure",getAddress().getHostAddress(), new Integer(getPort()),new Long(0)), ex1);
+- throw (ex1);
+- }
+-
+- }
+-
+- /**
+- * close socket
+- *
+- * @see DataSender#disconnect()
+- * @see DataSender#closeSocket()
+- */
+- protected void closeSocket() {
+- if(isConnected()) {
+- if (socket != null) {
+- try {
+- socket.close();
+- } catch (IOException x) {
+- } finally {
+- socket = null;
+- soOut = null;
+- soIn = null;
+- }
+- }
+- setRequestCount(0);
+- setConnected(false);
+- if (log.isDebugEnabled())
+- log.debug(sm.getString("IDataSender.closeSocket",getAddress().getHostAddress(), new Integer(getPort()),new Long(0)));
+- }
+- }
+-
+- /**
+- * Push messages with only one socket at a time
+- * Wait for ack is needed and make auto retry when write message is failed.
+- * After sending error close and reopen socket again.
+- *
+- * After successfull sending update stats
+- *
+- * WARNING: Subclasses must be very carefull that only one thread call this pushMessage at once!!!
+- *
+- * @see #closeSocket()
+- * @see #openSocket()
+- * @see #writeData(ChannelMessage)
+- *
+- * @param data
+- * data to send
+- * @since 5.5.10
+- */
+-
+- protected void pushMessage(byte[] data, boolean reconnect, boolean waitForAck) throws IOException {
+- keepalive();
+- if ( reconnect ) closeSocket();
+- if (!isConnected()) openSocket();
+- soOut.write(data);
+- soOut.flush();
+- if (waitForAck) waitForAck();
+- SenderState.getSenderState(getDestination()).setReady();
+-
+- }
+-
+- /**
+- * Wait for Acknowledgement from other server
+- * FIXME Please, not wait only for three charcters, better control that the wait ack message is correct.
+- * @param timeout
+- * @throws java.io.IOException
+- * @throws java.net.SocketTimeoutException
+- */
+- protected void waitForAck() throws java.io.IOException {
+- try {
+- boolean ackReceived = false;
+- boolean failAckReceived = false;
+- ackbuf.clear();
+- int bytesRead = 0;
+- int i = soIn.read();
+- while ((i != -1) && (bytesRead < Constants.ACK_COMMAND.length)) {
+- bytesRead++;
+- byte d = (byte)i;
+- ackbuf.append(d);
+- if (ackbuf.doesPackageExist() ) {
+- byte[] ackcmd = ackbuf.extractDataPackage(true).getBytes();
+- ackReceived = Arrays.equals(ackcmd,org.apache.catalina.tribes.transport.Constants.ACK_DATA);
+- failAckReceived = Arrays.equals(ackcmd,org.apache.catalina.tribes.transport.Constants.FAIL_ACK_DATA);
+- ackReceived = ackReceived || failAckReceived;
+- break;
+- }
+- i = soIn.read();
+- }
+- if (!ackReceived) {
+- if (i == -1) throw new IOException(sm.getString("IDataSender.ack.eof",getAddress(), new Integer(socket.getLocalPort())));
+- else throw new IOException(sm.getString("IDataSender.ack.wrong",getAddress(), new Integer(socket.getLocalPort())));
+- } else if ( failAckReceived && getThrowOnFailedAck()) {
+- throw new RemoteProcessException("Received a failed ack:org.apache.catalina.tribes.transport.Constants.FAIL_ACK_DATA");
+- }
+- } catch (IOException x) {
+- String errmsg = sm.getString("IDataSender.ack.missing", getAddress(),new Integer(socket.getLocalPort()), new Long(getTimeout()));
+- if ( SenderState.getSenderState(getDestination()).isReady() ) {
+- SenderState.getSenderState(getDestination()).setSuspect();
+- if ( log.isWarnEnabled() ) log.warn(errmsg, x);
+- } else {
+- if ( log.isDebugEnabled() )log.debug(errmsg, x);
+- }
+- throw x;
+- } finally {
+- ackbuf.clear();
+- }
+- }
+-}
+Index: java/org/apache/catalina/tribes/transport/bio/util/SingleRemoveSynchronizedAddLock.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/bio/util/SingleRemoveSynchronizedAddLock.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/bio/util/SingleRemoveSynchronizedAddLock.java (working copy)
+@@ -1,254 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.transport.bio.util;
+-
+-/**
+- * The class <b>SingleRemoveSynchronizedAddLock</b> implement locking for accessing the queue
+- * by a single remove thread and multiple add threads.
+- *
+- * A thread is only allowed to be either the remove or
+- * an add thread.
+- *
+- * The lock can either be owned by the remove thread
+- * or by a single add thread.
+- *
+- * If the remove thread tries to get the lock,
+- * but the queue is empty, it will block (poll)
+- * until an add threads adds an entry to the queue and
+- * releases the lock.
+- *
+- * If the remove thread and add threads compete for
+- * the lock and an add thread releases the lock, then
+- * the remove thread will get the lock first.
+- *
+- * The remove thread removes all entries in the queue
+- * at once and proceeses them without further
+- * polling the queue.
+- *
+- * The lock is not reentrant, in the sense, that all
+- * threads must release an owned lock before competing
+- * for the lock again!
+- *
+- * @author Rainer Jung
+- * @author Peter Rossbach
+- * @version 1.1
+- */
+-
+-public class SingleRemoveSynchronizedAddLock {
+-
+- public SingleRemoveSynchronizedAddLock() {
+- }
+-
+- public SingleRemoveSynchronizedAddLock(boolean dataAvailable) {
+- this.dataAvailable=dataAvailable;
+- }
+-
+- /**
+- * Time in milliseconds after which threads
+- * waiting for an add lock are woken up.
+- * This is used as a safety measure in case
+- * thread notification via the unlock methods
+- * has a bug.
+- */
+- private long addWaitTimeout = 10000L;
+-
+- /**
+- * Time in milliseconds after which threads
+- * waiting for a remove lock are woken up.
+- * This is used as a safety measure in case
+- * thread notification via the unlock methods
+- * has a bug.
+- */
+- private long removeWaitTimeout = 30000L;
+-
+- /**
+- * The current remove thread.
+- * It is set to the remove thread polling for entries.
+- * It is reset to null when the remove thread
+- * releases the lock and proceeds processing
+- * the removed entries.
+- */
+- private Thread remover = null;
+-
+- /**
+- * A flag indicating, if an add thread owns the lock.
+- */
+- private boolean addLocked = false;
+-
+- /**
+- * A flag indicating, if the remove thread owns the lock.
+- */
+- private boolean removeLocked = false;
+-
+- /**
+- * A flag indicating, if the remove thread is allowed
+- * to wait for the lock. The flag is set to false, when aborting.
+- */
+- private boolean removeEnabled = true;
+-
+- /**
+- * A flag indicating, if the remover needs polling.
+- * It indicates, if the locked object has data available
+- * to be removed.
+- */
+- private boolean dataAvailable = false;
+-
+- /**
+- * @return Value of addWaitTimeout
+- */
+- public synchronized long getAddWaitTimeout() {
+- return addWaitTimeout;
+- }
+-
+- /**
+- * Set value of addWaitTimeout
+- */
+- public synchronized void setAddWaitTimeout(long timeout) {
+- addWaitTimeout = timeout;
+- }
+-
+- /**
+- * @return Value of removeWaitTimeout
+- */
+- public synchronized long getRemoveWaitTimeout() {
+- return removeWaitTimeout;
+- }
+-
+- /**
+- * Set value of removeWaitTimeout
+- */
+- public synchronized void setRemoveWaitTimeout(long timeout) {
+- removeWaitTimeout = timeout;
+- }
+-
+- /**
+- * Check if the locked object has data available
+- * i.e. the remover can stop poling and get the lock.
+- * @return True iff the lock Object has data available.
+- */
+- public synchronized boolean isDataAvailable() {
+- return dataAvailable;
+- }
+-
+- /**
+- * Check if an add thread owns the lock.
+- * @return True iff an add thread owns the lock.
+- */
+- public synchronized boolean isAddLocked() {
+- return addLocked;
+- }
+-
+- /**
+- * Check if the remove thread owns the lock.
+- * @return True iff the remove thread owns the lock.
+- */
+- public synchronized boolean isRemoveLocked() {
+- return removeLocked;
+- }
+-
+- /**
+- * Check if the remove thread is polling.
+- * @return True iff the remove thread is polling.
+- */
+- public synchronized boolean isRemovePolling() {
+- if ( remover != null ) {
+- return true;
+- }
+- return false;
+- }
+-
+- /**
+- * Acquires the lock by an add thread and sets the add flag.
+- * If any add thread or the remove thread already acquired the lock
+- * this add thread will block until the lock is released.
+- */
+- public synchronized void lockAdd() {
+- if ( addLocked || removeLocked ) {
+- do {
+- try {
+- wait(addWaitTimeout);
+- } catch ( InterruptedException e ) {
+- Thread.currentThread().interrupted();
+- }
+- } while ( addLocked || removeLocked );
+- }
+- addLocked=true;
+- }
+-
+- /**
+- * Acquires the lock by the remove thread and sets the remove flag.
+- * If any add thread already acquired the lock or the queue is
+- * empty, the remove thread will block until the lock is released
+- * and the queue is not empty.
+- */
+- public synchronized boolean lockRemove() {
+- removeLocked=false;
+- removeEnabled=true;
+- if ( ( addLocked || ! dataAvailable ) && removeEnabled ) {
+- remover=Thread.currentThread();
+- do {
+- try {
+- wait(removeWaitTimeout);
+- } catch ( InterruptedException e ) {
+- Thread.currentThread().interrupted();
+- }
+- } while ( ( addLocked || ! dataAvailable ) && removeEnabled );
+- remover=null;
+- }
+- if ( removeEnabled ) {
+- removeLocked=true;
+- }
+- return removeLocked;
+- }
+-
+- /**
+- * Releases the lock by an add thread and reset the remove flag.
+- * If the reader thread is polling, notify it.
+- */
+- public synchronized void unlockAdd(boolean dataAvailable) {
+- addLocked=false;
+- this.dataAvailable=dataAvailable;
+- if ( ( remover != null ) && ( dataAvailable || ! removeEnabled ) ) {
+- remover.interrupt();
+- } else {
+- notifyAll();
+- }
+- }
+-
+- /**
+- * Releases the lock by the remove thread and reset the add flag.
+- * Notify all waiting add threads,
+- * that the lock has been released by the remove thread.
+- */
+- public synchronized void unlockRemove() {
+- removeLocked=false;
+- dataAvailable=false;
+- notifyAll();
+- }
+-
+- /**
+- * Abort any polling remover thread
+- */
+- public synchronized void abortRemove() {
+- removeEnabled=false;
+- if ( remover != null ) {
+- remover.interrupt();
+- }
+- }
+-
+-}
+Index: java/org/apache/catalina/tribes/transport/bio/util/FastQueue.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/bio/util/FastQueue.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/bio/util/FastQueue.java (working copy)
+@@ -1,393 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.transport.bio.util;
+-
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.group.InterceptorPayload;
+-
+-
+-
+-/**
+- * A fast queue that remover thread lock the adder thread. <br/>Limit the queue
+- * length when you have strange producer thread problemes.
+- *
+- * FIXME add i18n support to log messages
+- * @author Rainer Jung
+- * @author Peter Rossbach
+- * @version $Revision$ $Date$
+- */
+-public class FastQueue {
+-
+- private static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(FastQueue.class);
+-
+- /**
+- * This is the actual queue
+- */
+- private SingleRemoveSynchronizedAddLock lock = null;
+-
+- /**
+- * First Object at queue (consumer message)
+- */
+- private LinkObject first = null;
+-
+- /**
+- * Last object in queue (producer Object)
+- */
+- private LinkObject last = null;
+-
+- /**
+- * Current Queue elements size
+- */
+- private int size = 0;
+-
+- /**
+- * check lock to detect strange threadings things
+- */
+- private boolean checkLock = false;
+-
+- /**
+- * protocol the thread wait times
+- */
+- private boolean timeWait = false;
+-
+- private boolean inAdd = false;
+-
+- private boolean inRemove = false;
+-
+- private boolean inMutex = false;
+-
+- /**
+- * limit the queue legnth ( default is unlimited)
+- */
+- private int maxQueueLength = 0;
+-
+- /**
+- * addWaitTimeout for producer
+- */
+- private long addWaitTimeout = 10000L;
+-
+-
+- /**
+- * removeWaitTimeout for consumer
+- */
+- private long removeWaitTimeout = 30000L;
+-
+- /**
+- * enabled the queue
+- */
+- private boolean enabled = true;
+-
+- /**
+- * max queue size
+- */
+- private int maxSize = 0;
+-
+- /**
+- * avg size sample interval
+- */
+- private int sampleInterval = 100;
+-
+- /**
+- * Generate Queue SingleRemoveSynchronizedAddLock and set add and wait
+- * Timeouts
+- */
+- public FastQueue() {
+- lock = new SingleRemoveSynchronizedAddLock();
+- lock.setAddWaitTimeout(addWaitTimeout);
+- lock.setRemoveWaitTimeout(removeWaitTimeout);
+- }
+-
+- /**
+- * get current add wait timeout
+- *
+- * @return current wait timeout
+- */
+- public long getAddWaitTimeout() {
+- addWaitTimeout = lock.getAddWaitTimeout();
+- return addWaitTimeout;
+- }
+-
+- /**
+- * Set add wait timeout (default 10000 msec)
+- *
+- * @param timeout
+- */
+- public void setAddWaitTimeout(long timeout) {
+- addWaitTimeout = timeout;
+- lock.setAddWaitTimeout(addWaitTimeout);
+- }
+-
+- /**
+- * get current remove wait timeout
+- *
+- * @return The timeout
+- */
+- public long getRemoveWaitTimeout() {
+- removeWaitTimeout = lock.getRemoveWaitTimeout();
+- return removeWaitTimeout;
+- }
+-
+- /**
+- * set remove wait timeout ( default 30000 msec)
+- *
+- * @param timeout
+- */
+- public void setRemoveWaitTimeout(long timeout) {
+- removeWaitTimeout = timeout;
+- lock.setRemoveWaitTimeout(removeWaitTimeout);
+- }
+-
+- /**
+- * get Max Queue length
+- *
+- * @see org.apache.catalina.tribes.util.IQueue#getMaxQueueLength()
+- */
+- public int getMaxQueueLength() {
+- return maxQueueLength;
+- }
+-
+- public void setMaxQueueLength(int length) {
+- maxQueueLength = length;
+- }
+-
+- public boolean isEnabled() {
+- return enabled;
+- }
+-
+- public void setEnabled(boolean enable) {
+- enabled = enable;
+- if (!enabled) {
+- lock.abortRemove();
+- last = first = null;
+- }
+- }
+-
+- /**
+- * @return Returns the checkLock.
+- */
+- public boolean isCheckLock() {
+- return checkLock;
+- }
+-
+- /**
+- * @param checkLock The checkLock to set.
+- */
+- public void setCheckLock(boolean checkLock) {
+- this.checkLock = checkLock;
+- }
+-
+-
+- /**
+- * @return The max size
+- */
+- public int getMaxSize() {
+- return maxSize;
+- }
+-
+- /**
+- * @param size
+- */
+- public void setMaxSize(int size) {
+- maxSize = size;
+- }
+-
+-
+- /**
+- * unlock queue for next add
+- */
+- public void unlockAdd() {
+- lock.unlockAdd(size > 0 ? true : false);
+- }
+-
+- /**
+- * unlock queue for next remove
+- */
+- public void unlockRemove() {
+- lock.unlockRemove();
+- }
+-
+- /**
+- * start queuing
+- */
+- public void start() {
+- setEnabled(true);
+- }
+-
+- /**
+- * start queuing
+- */
+- public void stop() {
+- setEnabled(false);
+- }
+-
+- public int getSize() {
+- return size;
+- }
+-
+- public SingleRemoveSynchronizedAddLock getLock() {
+- return lock;
+- }
+-
+- /**
+- * Add new data to the queue
+- * @see org.apache.catalina.tribes.util.IQueue#add(java.lang.String, java.lang.Object)
+- * FIXME extract some method
+- */
+- public boolean add(ChannelMessage msg, Member[] destination, InterceptorPayload payload) {
+- boolean ok = true;
+- long time = 0;
+-
+- if (!enabled) {
+- if (log.isInfoEnabled())
+- log.info("FastQueue.add: queue disabled, add aborted");
+- return false;
+- }
+-
+- if (timeWait) {
+- time = System.currentTimeMillis();
+- }
+- lock.lockAdd();
+- try {
+- if (log.isTraceEnabled()) {
+- log.trace("FastQueue.add: starting with size " + size);
+- }
+- if (checkLock) {
+- if (inAdd)
+- log.warn("FastQueue.add: Detected other add");
+- inAdd = true;
+- if (inMutex)
+- log.warn("FastQueue.add: Detected other mutex in add");
+- inMutex = true;
+- }
+-
+- if ((maxQueueLength > 0) && (size >= maxQueueLength)) {
+- ok = false;
+- if (log.isTraceEnabled()) {
+- log.trace("FastQueue.add: Could not add, since queue is full (" + size + ">=" + maxQueueLength + ")");
+- }
+- } else {
+- LinkObject element = new LinkObject(msg,destination, payload);
+- if (size == 0) {
+- first = last = element;
+- size = 1;
+- } else {
+- if (last == null) {
+- ok = false;
+- log.error("FastQueue.add: Could not add, since last is null although size is "+ size + " (>0)");
+- } else {
+- last.append(element);
+- last = element;
+- size++;
+- }
+- }
+- }
+-
+- if (first == null) {
+- log.error("FastQueue.add: first is null, size is " + size + " at end of add");
+- }
+- if (last == null) {
+- log.error("FastQueue.add: last is null, size is " + size+ " at end of add");
+- }
+-
+- if (checkLock) {
+- if (!inMutex) log.warn("FastQueue.add: Cancelled by other mutex in add");
+- inMutex = false;
+- if (!inAdd) log.warn("FastQueue.add: Cancelled by other add");
+- inAdd = false;
+- }
+- if (log.isTraceEnabled()) log.trace("FastQueue.add: add ending with size " + size);
+-
+- } finally {
+- lock.unlockAdd(true);
+- }
+- return ok;
+- }
+-
+- /**
+- * remove the complete queued object list
+- * @see org.apache.catalina.tribes.util.IQueue#remove()
+- * FIXME extract some method
+- */
+- public LinkObject remove() {
+- LinkObject element;
+- boolean gotLock;
+- long time = 0;
+-
+- if (!enabled) {
+- if (log.isInfoEnabled())
+- log.info("FastQueue.remove: queue disabled, remove aborted");
+- return null;
+- }
+-
+- if (timeWait) {
+- time = System.currentTimeMillis();
+- }
+- gotLock = lock.lockRemove();
+- try {
+-
+- if (!gotLock) {
+- if (enabled) {
+- if (log.isInfoEnabled())
+- log.info("FastQueue.remove: Remove aborted although queue enabled");
+- } else {
+- if (log.isInfoEnabled())
+- log.info("FastQueue.remove: queue disabled, remove aborted");
+- }
+- return null;
+- }
+-
+- if (log.isTraceEnabled()) {
+- log.trace("FastQueue.remove: remove starting with size " + size);
+- }
+- if (checkLock) {
+- if (inRemove)
+- log.warn("FastQueue.remove: Detected other remove");
+- inRemove = true;
+- if (inMutex)
+- log.warn("FastQueue.remove: Detected other mutex in remove");
+- inMutex = true;
+- }
+-
+- element = first;
+-
+- first = last = null;
+- size = 0;
+-
+- if (checkLock) {
+- if (!inMutex)
+- log.warn("FastQueue.remove: Cancelled by other mutex in remove");
+- inMutex = false;
+- if (!inRemove)
+- log.warn("FastQueue.remove: Cancelled by other remove");
+- inRemove = false;
+- }
+- if (log.isTraceEnabled()) {
+- log.trace("FastQueue.remove: remove ending with size " + size);
+- }
+-
+- if (timeWait) {
+- time = System.currentTimeMillis();
+- }
+- } finally {
+- lock.unlockRemove();
+- }
+- return element;
+- }
+-
+-}
+Index: java/org/apache/catalina/tribes/transport/bio/util/LinkObject.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/bio/util/LinkObject.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/bio/util/LinkObject.java (working copy)
+@@ -1,108 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.transport.bio.util;
+-
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.ErrorHandler;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.group.InterceptorPayload;
+-
+-/**
+- * The class <b>LinkObject</b> implements an element
+- * for a linked list, consisting of a general
+- * data object and a pointer to the next element.
+- *
+- * @author Rainer Jung
+- * @author Peter Rossbach
+- * @author Filip Hanik
+- * @version $Revision$ $Date$
+-
+- */
+-
+-public class LinkObject {
+-
+- private ChannelMessage msg;
+- private LinkObject next;
+- private byte[] key ;
+- private Member[] destination;
+- private InterceptorPayload payload;
+-
+- /**
+- * Construct a new element from the data object.
+- * Sets the pointer to null.
+- *
+- * @param key The key
+- * @param payload The data object.
+- */
+- public LinkObject(ChannelMessage msg, Member[] destination, InterceptorPayload payload) {
+- this.msg = msg;
+- this.next = null;
+- this.key = msg.getUniqueId();
+- this.payload = payload;
+- this.destination = destination;
+- }
+-
+- /**
+- * Set the next element.
+- * @param next The next element.
+- */
+- public void append(LinkObject next) {
+- this.next = next;
+- }
+-
+- /**
+- * Get the next element.
+- * @return The next element.
+- */
+- public LinkObject next() {
+- return next;
+- }
+-
+- public void setNext(LinkObject next) {
+- this.next = next;
+- }
+-
+- /**
+- * Get the data object from the element.
+- * @return The data object from the element.
+- */
+- public ChannelMessage data() {
+- return msg;
+- }
+-
+- /**
+- * Get the unique message id
+- * @return the unique message id
+- */
+- public byte[] getKey() {
+- return key;
+- }
+-
+- public ErrorHandler getHandler() {
+- return payload!=null?payload.getErrorHandler():null;
+- }
+-
+- public InterceptorPayload getPayload() {
+- return payload;
+- }
+-
+- public Member[] getDestination() {
+- return destination;
+- }
+-
+-}
+Index: java/org/apache/catalina/tribes/transport/bio/BioReceiver.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/bio/BioReceiver.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/bio/BioReceiver.java (working copy)
+@@ -1,157 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.transport.bio;
+-
+-import java.io.IOException;
+-import java.net.ServerSocket;
+-import java.net.Socket;
+-
+-import org.apache.catalina.tribes.ChannelReceiver;
+-import org.apache.catalina.tribes.io.ListenCallback;
+-import org.apache.catalina.tribes.io.ObjectReader;
+-import org.apache.catalina.tribes.transport.ReceiverBase;
+-import org.apache.catalina.tribes.transport.RxTaskPool;
+-import org.apache.catalina.tribes.transport.AbstractRxTask;
+-
+-/**
+- *
+- * @author Filip Hanik
+- * @version $Revision:$ $Date:$
+- */
+-public class BioReceiver extends ReceiverBase implements Runnable, ChannelReceiver, ListenCallback {
+-
+- protected static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(BioReceiver.class);
+-
+- protected ServerSocket serverSocket;
+-
+- public BioReceiver() {
+- }
+-
+- /**
+- *
+- * @throws IOException
+- * @todo Implement this org.apache.catalina.tribes.ChannelReceiver method
+- */
+- public void start() throws IOException {
+- super.start();
+- try {
+- setPool(new RxTaskPool(getMaxThreads(),getMinThreads(),this));
+- } catch (Exception x) {
+- log.fatal("ThreadPool can initilzed. Listener not started", x);
+- if ( x instanceof IOException ) throw (IOException)x;
+- else throw new IOException(x.getMessage());
+- }
+- try {
+- getBind();
+- bind();
+- Thread t = new Thread(this, "BioReceiver");
+- t.setDaemon(true);
+- t.start();
+- } catch (Exception x) {
+- log.fatal("Unable to start cluster receiver", x);
+- if ( x instanceof IOException ) throw (IOException)x;
+- else throw new IOException(x.getMessage());
+- }
+- }
+-
+- public AbstractRxTask createRxTask() {
+- return getReplicationThread();
+- }
+-
+- protected BioReplicationTask getReplicationThread() {
+- BioReplicationTask result = new BioReplicationTask(this);
+- result.setOptions(getWorkerThreadOptions());
+- result.setUseBufferPool(this.getUseBufferPool());
+- return result;
+- }
+-
+- /**
+- *
+- * @todo Implement this org.apache.catalina.tribes.ChannelReceiver method
+- */
+- public void stop() {
+- setListen(false);
+- try {
+- this.serverSocket.close();
+- }catch ( Exception x ) {}
+- super.stop();
+- }
+-
+-
+-
+-
+- protected void bind() throws IOException {
+- // allocate an unbound server socket channel
+- serverSocket = new ServerSocket();
+- // set the port the server channel will listen to
+- //serverSocket.bind(new InetSocketAddress(getBind(), getTcpListenPort()));
+- bind(serverSocket,getPort(),getAutoBind());
+- }
+-
+-
+-
+- public void run() {
+- try {
+- listen();
+- } catch (Exception x) {
+- log.error("Unable to run replication listener.", x);
+- }
+- }
+-
+- public void listen() throws Exception {
+- if (doListen()) {
+- log.warn("ServerSocket already started");
+- return;
+- }
+- setListen(true);
+-
+- while ( doListen() ) {
+- Socket socket = null;
+- if ( getTaskPool().available() < 1 ) {
+- if ( log.isWarnEnabled() )
+- log.warn("All BIO server replication threads are busy, unable to handle more requests until a thread is freed up.");
+- }
+- BioReplicationTask task = (BioReplicationTask)getTaskPool().getRxTask();
+- if ( task == null ) continue; //should never happen
+- try {
+- socket = serverSocket.accept();
+- }catch ( Exception x ) {
+- if ( doListen() ) throw x;
+- }
+- if ( !doListen() ) {
+- task.setDoRun(false);
+- task.serviceSocket(null,null);
+- getExecutor().execute(task);
+- break; //regular shutdown
+- }
+- if ( socket == null ) continue;
+- socket.setReceiveBufferSize(getRxBufSize());
+- socket.setSendBufferSize(getTxBufSize());
+- socket.setTcpNoDelay(getTcpNoDelay());
+- socket.setKeepAlive(getSoKeepAlive());
+- socket.setOOBInline(getOoBInline());
+- socket.setReuseAddress(getSoReuseAddress());
+- socket.setSoLinger(getSoLingerOn(),getSoLingerTime());
+- socket.setTrafficClass(getSoTrafficClass());
+- socket.setSoTimeout(getTimeout());
+- ObjectReader reader = new ObjectReader(socket);
+- task.serviceSocket(socket,reader);
+- }//while
+- }
+-
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/transport/bio/PooledMultiSender.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/bio/PooledMultiSender.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/bio/PooledMultiSender.java (working copy)
+@@ -1,72 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.transport.bio;
+-
+-import org.apache.catalina.tribes.transport.DataSender;
+-import org.apache.catalina.tribes.transport.PooledSender;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.transport.MultiPointSender;
+-import org.apache.catalina.tribes.ChannelMessage;
+-
+-/**
+- * <p>Title: </p>
+- *
+- * <p>Description: </p>
+- *
+- * <p>Company: </p>
+- *
+- * @author not attributable
+- * @version 1.0
+- */
+-public class PooledMultiSender extends PooledSender {
+-
+-
+- public PooledMultiSender() {
+- }
+-
+- public void sendMessage(Member[] destination, ChannelMessage msg) throws ChannelException {
+- MultiPointSender sender = null;
+- try {
+- sender = (MultiPointSender)getSender();
+- if (sender == null) {
+- ChannelException cx = new ChannelException("Unable to retrieve a data sender, time out error.");
+- for (int i = 0; i < destination.length; i++) cx.addFaultyMember(destination[i], new NullPointerException("Unable to retrieve a sender from the sender pool"));
+- throw cx;
+- } else {
+- sender.sendMessage(destination, msg);
+- }
+- sender.keepalive();
+- }finally {
+- if ( sender != null ) returnSender(sender);
+- }
+- }
+-
+- /**
+- * getNewDataSender
+- *
+- * @return DataSender
+- * @todo Implement this org.apache.catalina.tribes.transport.PooledSender
+- * method
+- */
+- public DataSender getNewDataSender() {
+- MultipointBioSender sender = new MultipointBioSender();
+- sender.transferProperties(this,sender);
+- return sender;
+- }
+-
+-}
+Index: java/org/apache/catalina/tribes/transport/DataSender.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/DataSender.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/DataSender.java (working copy)
+@@ -1,45 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.transport;
+-
+-import java.io.IOException;
+-
+-/**
+- * <p>Title: </p>
+- *
+- * <p>Description: </p>
+- *
+- * <p>Company: </p>
+- *
+- * @author not attributable
+- * @version 1.0
+- */
+-public interface DataSender {
+- public void connect() throws IOException;
+- public void disconnect();
+- public boolean isConnected();
+- public void setRxBufSize(int size);
+- public void setTxBufSize(int size);
+- public boolean keepalive();
+- public void setTimeout(long timeout);
+- public void setKeepAliveCount(int maxRequests);
+- public void setKeepAliveTime(long keepAliveTimeInMs);
+- public int getRequestCount();
+- public long getConnectTime();
+-
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/transport/Constants.java
+===================================================================
+--- java/org/apache/catalina/tribes/transport/Constants.java (revision 590752)
++++ java/org/apache/catalina/tribes/transport/Constants.java (working copy)
+@@ -1,43 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-
+-package org.apache.catalina.tribes.transport;
+-
+-import org.apache.catalina.tribes.io.XByteBuffer;
+-
+-/**
+- * Manifest constants for the <code>org.apache.catalina.tribes.transport</code>
+- * package.
+- * @author Filip Hanik
+- * @author Peter Rossbach
+- * @version $Revision$ $Date$
+- */
+-
+-public class Constants {
+-
+- public static final String Package = "org.apache.catalina.tribes.transport";
+-
+- /*
+- * Do not change any of these values!
+- */
+- public static final byte[] ACK_DATA = new byte[] {6, 2, 3};
+- public static final byte[] FAIL_ACK_DATA = new byte[] {11, 0, 5};
+- public static final byte[] ACK_COMMAND = XByteBuffer.createDataPackage(ACK_DATA);
+- public static final byte[] FAIL_ACK_COMMAND = XByteBuffer.createDataPackage(FAIL_ACK_DATA);
+-
+-}
+Index: java/org/apache/catalina/tribes/group/interceptors/StaticMembershipInterceptor.java
+===================================================================
+--- java/org/apache/catalina/tribes/group/interceptors/StaticMembershipInterceptor.java (revision 590752)
++++ java/org/apache/catalina/tribes/group/interceptors/StaticMembershipInterceptor.java (working copy)
+@@ -1,117 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.group.interceptors;
+-
+-import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+-import org.apache.catalina.tribes.Member;
+-import java.util.ArrayList;
+-import org.apache.catalina.tribes.group.AbsoluteOrder;
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.Channel;
+-
+-public class StaticMembershipInterceptor
+- extends ChannelInterceptorBase {
+- protected ArrayList members = new ArrayList();
+- protected Member localMember = null;
+-
+- public StaticMembershipInterceptor() {
+- super();
+- }
+-
+- public void addStaticMember(Member member) {
+- synchronized (members) {
+- if (!members.contains(member)) members.add(member);
+- }
+- }
+-
+- public void removeStaticMember(Member member) {
+- synchronized (members) {
+- if (members.contains(member)) members.remove(member);
+- }
+- }
+-
+- public void setLocalMember(Member member) {
+- this.localMember = member;
+- }
+-
+- /**
+- * has members
+- */
+- public boolean hasMembers() {
+- return super.hasMembers() || (members.size()>0);
+- }
+-
+- /**
+- * Get all current cluster members
+- * @return all members or empty array
+- */
+- public Member[] getMembers() {
+- if ( members.size() == 0 ) return super.getMembers();
+- else {
+- synchronized (members) {
+- Member[] others = super.getMembers();
+- Member[] result = new Member[members.size() + others.length];
+- for (int i = 0; i < others.length; i++) result[i] = others[i];
+- for (int i = 0; i < members.size(); i++) result[i + others.length] = (Member) members.get(i);
+- AbsoluteOrder.absoluteOrder(result);
+- return result;
+- }//sync
+- }//end if
+- }
+-
+- /**
+- *
+- * @param mbr Member
+- * @return Member
+- */
+- public Member getMember(Member mbr) {
+- if ( members.contains(mbr) ) return (Member)members.get(members.indexOf(mbr));
+- else return super.getMember(mbr);
+- }
+-
+- /**
+- * Return the member that represents this node.
+- *
+- * @return Member
+- */
+- public Member getLocalMember(boolean incAlive) {
+- if (this.localMember != null ) return localMember;
+- else return super.getLocalMember(incAlive);
+- }
+-
+- /**
+- * Send notifications upwards
+- * @param svc int
+- * @throws ChannelException
+- */
+- public void start(int svc) throws ChannelException {
+- if ( (Channel.SND_RX_SEQ&svc)==Channel.SND_RX_SEQ ) super.start(Channel.SND_RX_SEQ);
+- if ( (Channel.SND_TX_SEQ&svc)==Channel.SND_TX_SEQ ) super.start(Channel.SND_TX_SEQ);
+- final Member[] mbrs = (Member[])members.toArray(new Member[members.size()]);
+- final ChannelInterceptorBase base = this;
+- Thread t = new Thread() {
+- public void run() {
+- for (int i=0; i<mbrs.length; i++ ) {
+- base.memberAdded(mbrs[i]);
+- }
+- }
+- };
+- t.start();
+- super.start(svc & (~Channel.SND_RX_SEQ) & (~Channel.SND_TX_SEQ));
+- }
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/group/interceptors/TwoPhaseCommitInterceptor.java
+===================================================================
+--- java/org/apache/catalina/tribes/group/interceptors/TwoPhaseCommitInterceptor.java (revision 590752)
++++ java/org/apache/catalina/tribes/group/interceptors/TwoPhaseCommitInterceptor.java (working copy)
+@@ -1,148 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.group.interceptors;
+-
+-import java.util.HashMap;
+-
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+-import org.apache.catalina.tribes.group.InterceptorPayload;
+-import org.apache.catalina.tribes.util.UUIDGenerator;
+-import org.apache.catalina.tribes.util.Arrays;
+-import org.apache.catalina.tribes.UniqueId;
+-import java.util.Map;
+-
+-/**
+- * <p>Title: </p>
+- *
+- * <p>Description: </p>
+- *
+- * <p>Company: </p>
+- *
+- * @author not attributable
+- * @version 1.0
+- */
+-public class TwoPhaseCommitInterceptor extends ChannelInterceptorBase {
+-
+- public static final byte[] START_DATA = new byte[] {113, 1, -58, 2, -34, -60, 75, -78, -101, -12, 32, -29, 32, 111, -40, 4};
+- public static final byte[] END_DATA = new byte[] {54, -13, 90, 110, 47, -31, 75, -24, -81, -29, 36, 52, -58, 77, -110, 56};
+- private static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(TwoPhaseCommitInterceptor.class);
+-
+- protected HashMap messages = new HashMap();
+- protected long expire = 1000 * 60; //one minute expiration
+- protected boolean deepclone = true;
+-
+- public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws
+- ChannelException {
+- //todo, optimize, if destination.length==1, then we can do
+- //msg.setOptions(msg.getOptions() & (~getOptionFlag())
+- //and just send one message
+- if (okToProcess(msg.getOptions()) ) {
+- super.sendMessage(destination, msg, null);
+- ChannelMessage confirmation = null;
+- if ( deepclone ) confirmation = (ChannelMessage)msg.deepclone();
+- else confirmation = (ChannelMessage)msg.clone();
+- confirmation.getMessage().reset();
+- UUIDGenerator.randomUUID(false,confirmation.getUniqueId(),0);
+- confirmation.getMessage().append(START_DATA,0,START_DATA.length);
+- confirmation.getMessage().append(msg.getUniqueId(),0,msg.getUniqueId().length);
+- confirmation.getMessage().append(END_DATA,0,END_DATA.length);
+- super.sendMessage(destination,confirmation,payload);
+- } else {
+- //turn off two phase commit
+- //this wont work if the interceptor has 0 as a flag
+- //since there is no flag to turn off
+- //msg.setOptions(msg.getOptions() & (~getOptionFlag()));
+- super.sendMessage(destination, msg, payload);
+- }
+- }
+-
+- public void messageReceived(ChannelMessage msg) {
+- if (okToProcess(msg.getOptions())) {
+- if ( msg.getMessage().getLength() == (START_DATA.length+msg.getUniqueId().length+END_DATA.length) &&
+- Arrays.contains(msg.getMessage().getBytesDirect(),0,START_DATA,0,START_DATA.length) &&
+- Arrays.contains(msg.getMessage().getBytesDirect(),START_DATA.length+msg.getUniqueId().length,END_DATA,0,END_DATA.length) ) {
+- UniqueId id = new UniqueId(msg.getMessage().getBytesDirect(),START_DATA.length,msg.getUniqueId().length);
+- MapEntry original = (MapEntry)messages.get(id);
+- if ( original != null ) {
+- super.messageReceived(original.msg);
+- messages.remove(id);
+- } else log.warn("Received a confirmation, but original message is missing. Id:"+Arrays.toString(id.getBytes()));
+- } else {
+- UniqueId id = new UniqueId(msg.getUniqueId());
+- MapEntry entry = new MapEntry((ChannelMessage)msg.deepclone(),id,System.currentTimeMillis());
+- messages.put(id,entry);
+- }
+- } else {
+- super.messageReceived(msg);
+- }
+- }
+-
+- public boolean getDeepclone() {
+- return deepclone;
+- }
+-
+- public long getExpire() {
+- return expire;
+- }
+-
+- public void setDeepclone(boolean deepclone) {
+- this.deepclone = deepclone;
+- }
+-
+- public void setExpire(long expire) {
+- this.expire = expire;
+- }
+-
+- public void heartbeat() {
+- try {
+- long now = System.currentTimeMillis();
+- Map.Entry[] entries = (Map.Entry[])messages.entrySet().toArray(new Map.Entry[messages.size()]);
+- for (int i=0; i<entries.length; i++ ) {
+- MapEntry entry = (MapEntry)entries[i].getValue();
+- if ( entry.expired(now,expire) ) {
+- if(log.isInfoEnabled())
+- log.info("Message ["+entry.id+"] has expired. Removing.");
+- messages.remove(entry.id);
+- }//end if
+- }
+- } catch ( Exception x ) {
+- log.warn("Unable to perform heartbeat on the TwoPhaseCommit interceptor.",x);
+- } finally {
+- super.heartbeat();
+- }
+- }
+-
+- public static class MapEntry {
+- public ChannelMessage msg;
+- public UniqueId id;
+- public long timestamp;
+-
+- public MapEntry(ChannelMessage msg, UniqueId id, long timestamp) {
+- this.msg = msg;
+- this.id = id;
+- this.timestamp = timestamp;
+- }
+- public boolean expired(long now, long expiration) {
+- return (now - timestamp ) > expiration;
+- }
+-
+- }
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/group/interceptors/MessageDispatchInterceptor.java
+===================================================================
+--- java/org/apache/catalina/tribes/group/interceptors/MessageDispatchInterceptor.java (revision 590752)
++++ java/org/apache/catalina/tribes/group/interceptors/MessageDispatchInterceptor.java (working copy)
+@@ -1,202 +0,0 @@
+-/*
+- * 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
+- */
+-
+-package org.apache.catalina.tribes.group.interceptors;
+-
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+-import org.apache.catalina.tribes.group.InterceptorPayload;
+-import org.apache.catalina.tribes.transport.bio.util.FastQueue;
+-import org.apache.catalina.tribes.transport.bio.util.LinkObject;
+-import org.apache.catalina.tribes.UniqueId;
+-
+-/**
+- *
+- * The message dispatcher is a way to enable asynchronous communication
+- * through a channel. The dispatcher will look for the <code>Channel.SEND_OPTIONS_ASYNCHRONOUS</code>
+- * flag to be set, if it is, it will queue the message for delivery and immediately return to the sender.
+- *
+- *
+- *
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public class MessageDispatchInterceptor extends ChannelInterceptorBase implements Runnable {
+- protected static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(MessageDispatchInterceptor.class);
+-
+- protected long maxQueueSize = 1024*1024*64; //64MB
+- protected FastQueue queue = new FastQueue();
+- protected boolean run = false;
+- protected Thread msgDispatchThread = null;
+- protected long currentSize = 0;
+- protected boolean useDeepClone = true;
+- protected boolean alwaysSend = true;
+-
+- public MessageDispatchInterceptor() {
+- setOptionFlag(Channel.SEND_OPTIONS_ASYNCHRONOUS);
+- }
+-
+- public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+- boolean async = (msg.getOptions() & Channel.SEND_OPTIONS_ASYNCHRONOUS) == Channel.SEND_OPTIONS_ASYNCHRONOUS;
+- if ( async && run ) {
+- if ( (getCurrentSize()+msg.getMessage().getLength()) > maxQueueSize ) {
+- if ( alwaysSend ) {
+- super.sendMessage(destination,msg,payload);
+- return;
+- } else {
+- throw new ChannelException("Asynchronous queue is full, reached its limit of " + maxQueueSize +" bytes, current:" + getCurrentSize() + " bytes.");
+- }//end if
+- }//end if
+- //add to queue
+- if ( useDeepClone ) msg = (ChannelMessage)msg.deepclone();
+- if (!addToQueue(msg, destination, payload) ) {
+- throw new ChannelException("Unable to add the message to the async queue, queue bug?");
+- }
+- addAndGetCurrentSize(msg.getMessage().getLength());
+- } else {
+- super.sendMessage(destination, msg, payload);
+- }
+- }
+-
+- public boolean addToQueue(ChannelMessage msg, Member[] destination, InterceptorPayload payload) {
+- return queue.add(msg,destination,payload);
+- }
+-
+- public LinkObject removeFromQueue() {
+- return queue.remove();
+- }
+-
+- public void startQueue() {
+- msgDispatchThread = new Thread(this);
+- msgDispatchThread.setName("MessageDispatchInterceptor.MessageDispatchThread");
+- msgDispatchThread.setDaemon(true);
+- msgDispatchThread.setPriority(Thread.MAX_PRIORITY);
+- queue.setEnabled(true);
+- run = true;
+- msgDispatchThread.start();
+- }
+-
+- public void stopQueue() {
+- run = false;
+- msgDispatchThread.interrupt();
+- queue.setEnabled(false);
+- setAndGetCurrentSize(0);
+- }
+-
+-
+- public void setOptionFlag(int flag) {
+- if ( flag != Channel.SEND_OPTIONS_ASYNCHRONOUS ) log.warn("Warning, you are overriding the asynchronous option flag, this will disable the Channel.SEND_OPTIONS_ASYNCHRONOUS that other apps might use.");
+- super.setOptionFlag(flag);
+- }
+-
+- public void setMaxQueueSize(long maxQueueSize) {
+- this.maxQueueSize = maxQueueSize;
+- }
+-
+- public void setUseDeepClone(boolean useDeepClone) {
+- this.useDeepClone = useDeepClone;
+- }
+-
+- public long getMaxQueueSize() {
+- return maxQueueSize;
+- }
+-
+- public boolean getUseDeepClone() {
+- return useDeepClone;
+- }
+-
+- public long getCurrentSize() {
+- return currentSize;
+- }
+-
+- public synchronized long addAndGetCurrentSize(long inc) {
+- currentSize += inc;
+- return currentSize;
+- }
+-
+- public synchronized long setAndGetCurrentSize(long value) {
+- currentSize = value;
+- return value;
+- }
+-
+- public void start(int svc) throws ChannelException {
+- //start the thread
+- if (!run ) {
+- synchronized (this) {
+- if ( !run && ((svc & Channel.SND_TX_SEQ)==Channel.SND_TX_SEQ) ) {//only start with the sender
+- startQueue();
+- }//end if
+- }//sync
+- }//end if
+- super.start(svc);
+- }
+-
+-
+- public void stop(int svc) throws ChannelException {
+- //stop the thread
+- if ( run ) {
+- synchronized (this) {
+- if ( run && ((svc & Channel.SND_TX_SEQ)==Channel.SND_TX_SEQ)) {
+- stopQueue();
+- }//end if
+- }//sync
+- }//end if
+-
+- super.stop(svc);
+- }
+-
+- public void run() {
+- while ( run ) {
+- LinkObject link = removeFromQueue();
+- if ( link == null ) continue; //should not happen unless we exceed wait time
+- while ( link != null && run ) {
+- link = sendAsyncData(link);
+- }//while
+- }//while
+- }//run
+-
+- protected LinkObject sendAsyncData(LinkObject link) {
+- ChannelMessage msg = link.data();
+- Member[] destination = link.getDestination();
+- try {
+- super.sendMessage(destination,msg,null);
+- try {
+- if ( link.getHandler() != null ) link.getHandler().handleCompletion(new UniqueId(msg.getUniqueId()));
+- } catch ( Exception ex ) {
+- log.error("Unable to report back completed message.",ex);
+- }
+- } catch ( Exception x ) {
+- ChannelException cx = null;
+- if ( x instanceof ChannelException ) cx = (ChannelException)x;
+- else cx = new ChannelException(x);
+- if ( log.isDebugEnabled() ) log.debug("Error while processing async message.",x);
+- try {
+- if (link.getHandler() != null) link.getHandler().handleError(cx, new UniqueId(msg.getUniqueId()));
+- } catch ( Exception ex ) {
+- log.error("Unable to report back error message.",ex);
+- }
+- } finally {
+- addAndGetCurrentSize(-msg.getMessage().getLength());
+- link = link.next();
+- }//try
+- return link;
+- }
+-
+-
+-}
+Index: java/org/apache/catalina/tribes/group/interceptors/TcpPingInterceptor.java
+===================================================================
+--- java/org/apache/catalina/tribes/group/interceptors/TcpPingInterceptor.java (revision 590752)
++++ java/org/apache/catalina/tribes/group/interceptors/TcpPingInterceptor.java (working copy)
+@@ -1,179 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.group.interceptors;
+-
+-import java.lang.ref.WeakReference;
+-import java.util.Arrays;
+-import java.util.concurrent.atomic.AtomicInteger;
+-
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.ChannelInterceptor;
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+-import org.apache.catalina.tribes.io.ChannelData;
+-
+-/**
+- *
+- * Sends a ping to all members.
+- * Configure this interceptor with the TcpFailureDetector below it,
+- * and the TcpFailureDetector will act as the membership guide.
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-
+-public class TcpPingInterceptor extends ChannelInterceptorBase {
+-
+- protected static org.apache.juli.logging.Log log =
+- org.apache.juli.logging.LogFactory.getLog(TcpPingInterceptor.class);
+-
+- protected static byte[] TCP_PING_DATA = new byte[] {
+- 79, -89, 115, 72, 121, -33, 67, -55, -97, 111, -119, -128, -95, 91, 7, 20,
+- 125, -39, 82, 91, -21, -33, 67, -102, -73, 126, -66, -113, -127, 103, 30, -74,
+- 55, 21, -66, -121, 69, 33, 76, -88, -65, 10, 77, 19, 83, 56, 21, 50,
+- 85, -10, -108, -73, 58, -33, 33, 120, -111, 4, 125, -41, 114, -124, -64, -43};
+-
+- protected long interval = 1000; //1 second
+-
+- protected boolean useThread = false;
+- protected boolean staticOnly = false;
+- protected boolean running = true;
+- protected PingThread thread = null;
+- protected static AtomicInteger cnt = new AtomicInteger(0);
+-
+- WeakReference<TcpFailureDetector> failureDetector = null;
+- WeakReference<StaticMembershipInterceptor> staticMembers = null;
+-
+- public synchronized void start(int svc) throws ChannelException {
+- super.start(svc);
+- running = true;
+- if ( thread == null ) {
+- thread = new PingThread();
+- thread.setDaemon(true);
+- thread.setName("TcpPingInterceptor.PingThread-"+cnt.addAndGet(1));
+- thread.start();
+- }
+-
+- //acquire the interceptors to invoke on send ping events
+- ChannelInterceptor next = getNext();
+- while ( next != null ) {
+- if ( next instanceof TcpFailureDetector )
+- failureDetector = new WeakReference<TcpFailureDetector>((TcpFailureDetector)next);
+- if ( next instanceof StaticMembershipInterceptor )
+- staticMembers = new WeakReference<StaticMembershipInterceptor>((StaticMembershipInterceptor)next);
+- next = next.getNext();
+- }
+-
+- }
+-
+- public void stop(int svc) throws ChannelException {
+- running = false;
+- if ( thread != null ) thread.interrupt();
+- thread = null;
+- super.stop(svc);
+- }
+-
+- public void heartbeat() {
+- super.heartbeat();
+- if (!getUseThread()) sendPing();
+- }
+-
+- public long getInterval() {
+- return interval;
+- }
+-
+- public void setInterval(long interval) {
+- this.interval = interval;
+- }
+-
+- public void setUseThread(boolean useThread) {
+- this.useThread = useThread;
+- }
+-
+- public void setStaticOnly(boolean staticOnly) {
+- this.staticOnly = staticOnly;
+- }
+-
+- public boolean getUseThread() {
+- return useThread;
+- }
+-
+- public boolean getStaticOnly() {
+- return staticOnly;
+- }
+-
+- protected void sendPing() {
+- if (failureDetector.get()!=null) {
+- //we have a reference to the failure detector
+- //piggy back on that dude
+- failureDetector.get().checkMembers(true);
+- }else {
+- if (staticOnly && staticMembers.get()!=null) {
+- sendPingMessage(staticMembers.get().getMembers());
+- } else {
+- sendPingMessage(getMembers());
+- }
+- }
+- }
+-
+- protected void sendPingMessage(Member[] members) {
+- if ( members == null || members.length == 0 ) return;
+- ChannelData data = new ChannelData(true);//generates a unique Id
+- data.setAddress(getLocalMember(false));
+- data.setTimestamp(System.currentTimeMillis());
+- data.setOptions(getOptionFlag());
+- try {
+- super.sendMessage(members, data, null);
+- }catch (ChannelException x) {
+- log.warn("Unable to send TCP ping.",x);
+- }
+- }
+-
+- public void messageReceived(ChannelMessage msg) {
+- //catch incoming
+- boolean process = true;
+- if ( okToProcess(msg.getOptions()) ) {
+- //check to see if it is a ping message, if so, process = false
+- process = ( (msg.getMessage().getLength() != TCP_PING_DATA.length) ||
+- (!Arrays.equals(TCP_PING_DATA,msg.getMessage().getBytes()) ) );
+- }//end if
+-
+- //ignore the message, it doesnt have the flag set
+- if ( process ) super.messageReceived(msg);
+- else if ( log.isDebugEnabled() ) log.debug("Received a TCP ping packet:"+msg);
+- }//messageReceived
+-
+- protected class PingThread extends Thread {
+- public void run() {
+- while (running) {
+- try {
+- sleep(interval);
+- sendPing();
+- }catch ( InterruptedException ix ) {
+- interrupted();
+- }catch ( Exception x ) {
+- log.warn("Unable to send ping from TCP ping thread.",x);
+- }
+- }
+- }
+- }
+-
+-
+-
+-
+-}
+Index: java/org/apache/catalina/tribes/group/interceptors/NonBlockingCoordinator.java
+===================================================================
+--- java/org/apache/catalina/tribes/group/interceptors/NonBlockingCoordinator.java (revision 590752)
++++ java/org/apache/catalina/tribes/group/interceptors/NonBlockingCoordinator.java (working copy)
+@@ -1,839 +0,0 @@
+-/*
+- * 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
+- */
+-package org.apache.catalina.tribes.group.interceptors;
+-
+-import java.util.concurrent.atomic.AtomicBoolean;
+-
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.ChannelInterceptor;
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.UniqueId;
+-import org.apache.catalina.tribes.group.AbsoluteOrder;
+-import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+-import org.apache.catalina.tribes.group.InterceptorPayload;
+-import org.apache.catalina.tribes.io.ChannelData;
+-import org.apache.catalina.tribes.io.XByteBuffer;
+-import org.apache.catalina.tribes.membership.MemberImpl;
+-import org.apache.catalina.tribes.membership.Membership;
+-import org.apache.catalina.tribes.util.Arrays;
+-import org.apache.catalina.tribes.util.UUIDGenerator;
+-
+-/**
+- * <p>Title: Auto merging leader election algorithm</p>
+- *
+- * <p>Description: Implementation of a simple coordinator algorithm that not only selects a coordinator,
+- * it also merges groups automatically when members are discovered that werent part of the
+- * </p>
+- * <p>This algorithm is non blocking meaning it allows for transactions while the coordination phase is going on
+- * </p>
+- * <p>This implementation is based on a home brewed algorithm that uses the AbsoluteOrder of a membership
+- * to pass a token ring of the current membership.<br>
+- * This is not the same as just using AbsoluteOrder! Consider the following scenario:<br>
+- * Nodes, A,B,C,D,E on a network, in that priority. AbsoluteOrder will only work if all
+- * nodes are receiving pings from all the other nodes.
+- * meaning, that node{i} receives pings from node{all}-node{i}<br>
+- * but the following could happen if a multicast problem occurs.
+- * A has members {B,C,D}<br>
+- * B has members {A,C}<br>
+- * C has members {D,E}<br>
+- * D has members {A,B,C,E}<br>
+- * E has members {A,C,D}<br>
+- * Because the default Tribes membership implementation, relies on the multicast packets to
+- * arrive at all nodes correctly, there is nothing guaranteeing that it will.<br>
+- * <br>
+- * To best explain how this algorithm works, lets take the above example:
+- * For simplicity we assume that a send operation is O(1) for all nodes, although this algorithm will work
+- * where messages overlap, as they all depend on absolute order<br>
+- * Scenario 1: A,B,C,D,E all come online at the same time
+- * Eval phase, A thinks of itself as leader, B thinks of A as leader,
+- * C thinks of itself as leader, D,E think of A as leader<br>
+- * Token phase:<br>
+- * (1) A sends out a message X{A-ldr, A-src, mbrs-A,B,C,D} to B where X is the id for the message(and the view)<br>
+- * (1) C sends out a message Y{C-ldr, C-src, mbrs-C,D,E} to D where Y is the id for the message(and the view)<br>
+- * (2) B receives X{A-ldr, A-src, mbrs-A,B,C,D}, sends X{A-ldr, A-src, mbrs-A,B,C,D} to C <br>
+- * (2) D receives Y{C-ldr, C-src, mbrs-C,D,E} D is aware of A,B, sends Y{A-ldr, C-src, mbrs-A,B,C,D,E} to E<br>
+- * (3) C receives X{A-ldr, A-src, mbrs-A,B,C,D}, sends X{A-ldr, A-src, mbrs-A,B,C,D,E} to D<br>
+- * (3) E receives Y{A-ldr, C-src, mbrs-A,B,C,D,E} sends Y{A-ldr, C-src, mbrs-A,B,C,D,E} to A<br>
+- * (4) D receives X{A-ldr, A-src, mbrs-A,B,C,D,E} sends sends X{A-ldr, A-src, mbrs-A,B,C,D,E} to A<br>
+- * (4) A receives Y{A-ldr, C-src, mbrs-A,B,C,D,E}, holds the message, add E to its list of members<br>
+- * (5) A receives X{A-ldr, A-src, mbrs-A,B,C,D,E} <br>
+- * At this point, the state looks like<br>
+- * A - {A-ldr, mbrs-A,B,C,D,E, id=X}<br>
+- * B - {A-ldr, mbrs-A,B,C,D, id=X}<br>
+- * C - {A-ldr, mbrs-A,B,C,D,E, id=X}<br>
+- * D - {A-ldr, mbrs-A,B,C,D,E, id=X}<br>
+- * E - {A-ldr, mbrs-A,B,C,D,E, id=Y}<br>
+- * <br>
+- * A message doesn't stop until it reaches its original sender, unless its dropped by a higher leader.
+- * As you can see, E still thinks the viewId=Y, which is not correct. But at this point we have
+- * arrived at the same membership and all nodes are informed of each other.<br>
+- * To synchronize the rest we simply perform the following check at A when A receives X:<br>
+- * Original X{A-ldr, A-src, mbrs-A,B,C,D} == Arrived X{A-ldr, A-src, mbrs-A,B,C,D,E}<br>
+- * Since the condition is false, A, will resend the token, and A sends X{A-ldr, A-src, mbrs-A,B,C,D,E} to B
+- * When A receives X again, the token is complete. <br>
+- * Optionally, A can send a message X{A-ldr, A-src, mbrs-A,B,C,D,E confirmed} to A,B,C,D,E who then
+- * install and accept the view.
+- * </p>
+- * <p>
+- * Lets assume that C1 arrives, C1 has lower priority than C, but higher priority than D.<br>
+- * Lets also assume that C1 sees the following view {B,D,E}<br>
+- * C1 waits for a token to arrive. When the token arrives, the same scenario as above will happen.<br>
+- * In the scenario where C1 sees {D,E} and A,B,C can not see C1, no token will ever arrive.<br>
+- * In this case, C1 sends a Z{C1-ldr, C1-src, mbrs-C1,D,E} to D<br>
+- * D receives Z{C1-ldr, C1-src, mbrs-C1,D,E} and sends Z{A-ldr, C1-src, mbrs-A,B,C,C1,D,E} to E<br>
+- * E receives Z{A-ldr, C1-src, mbrs-A,B,C,C1,D,E} and sends it to A<br>
+- * A sends Z{A-ldr, A-src, mbrs-A,B,C,C1,D,E} to B and the chain continues until A receives the token again.
+- * At that time A optionally sends out Z{A-ldr, A-src, mbrs-A,B,C,C1,D,E, confirmed} to A,B,C,C1,D,E
+- * </p>
+- * <p>To ensure that the view gets implemented at all nodes at the same time,
+- * A will send out a VIEW_CONF message, this is the 'confirmed' message that is optional above.
+- * <p>Ideally, the interceptor below this one would be the TcpFailureDetector to ensure correct memberships</p>
+- *
+- * <p>The example above, of course can be simplified with a finite statemachine:<br>
+- * But I suck at writing state machines, my head gets all confused. One day I will document this algorithm though.<br>
+- * Maybe I'll do a state diagram :)
+- * </p>
+- * <h2>State Diagrams</h2>
+- * <a href="http://people.apache.org/~fhanik/tribes/docs/leader-election-initiate-ele...">Initiate an election</a><br><br>
+- * <a href="http://people.apache.org/~fhanik/tribes/docs/leader-election-message-arri...">Receive an election message</a><br><br>
+- *
+- * @author Filip Hanik
+- * @version 1.0
+- *
+- *
+- *
+- */
+-public class NonBlockingCoordinator extends ChannelInterceptorBase {
+-
+- /**
+- * header for a coordination message
+- */
+- protected static final byte[] COORD_HEADER = new byte[] {-86, 38, -34, -29, -98, 90, 65, 63, -81, -122, -6, -110, 99, -54, 13, 63};
+- /**
+- * Coordination request
+- */
+- protected static final byte[] COORD_REQUEST = new byte[] {104, -95, -92, -42, 114, -36, 71, -19, -79, 20, 122, 101, -1, -48, -49, 30};
+- /**
+- * Coordination confirmation, for blocking installations
+- */
+- protected static final byte[] COORD_CONF = new byte[] {67, 88, 107, -86, 69, 23, 76, -70, -91, -23, -87, -25, -125, 86, 75, 20};
+-
+- /**
+- * Alive message
+- */
+- protected static final byte[] COORD_ALIVE = new byte[] {79, -121, -25, -15, -59, 5, 64, 94, -77, 113, -119, -88, 52, 114, -56, -46,
+- -18, 102, 10, 34, -127, -9, 71, 115, -70, 72, -101, 88, 72, -124, 127, 111,
+- 74, 76, -116, 50, 111, 103, 65, 3, -77, 51, -35, 0, 119, 117, 9, -26,
+- 119, 50, -75, -105, -102, 36, 79, 37, -68, -84, -123, 15, -22, -109, 106, -55};
+- /**
+- * Time to wait for coordination timeout
+- */
+- protected long waitForCoordMsgTimeout = 15000;
+- /**
+- * Our current view
+- */
+- protected Membership view = null;
+- /**
+- * Out current viewId
+- */
+- protected UniqueId viewId;
+-
+- /**
+- * Our nonblocking membership
+- */
+- protected Membership membership = null;
+-
+- /**
+- * indicates that we are running an election
+- * and this is the one we are running
+- */
+- protected UniqueId suggestedviewId;
+- protected Membership suggestedView;
+-
+- protected boolean started = false;
+- protected final int startsvc = 0xFFFF;
+-
+- protected Object electionMutex = new Object();
+-
+- protected AtomicBoolean coordMsgReceived = new AtomicBoolean(false);
+-
+- public NonBlockingCoordinator() {
+- super();
+- }
+-
+-//============================================================================================================
+-// COORDINATION HANDLING
+-//============================================================================================================
+-
+- public void startElection(boolean force) throws ChannelException {
+- synchronized (electionMutex) {
+- MemberImpl local = (MemberImpl)getLocalMember(false);
+- MemberImpl[] others = (MemberImpl[])membership.getMembers();
+- fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_START_ELECT,this,"Election initated"));
+- if ( others.length == 0 ) {
+- this.viewId = new UniqueId(UUIDGenerator.randomUUID(false));
+- this.view = new Membership(local,AbsoluteOrder.comp, true);
+- this.handleViewConf(this.createElectionMsg(local,others,local),local,view);
+- return; //the only member, no need for an election
+- }
+- if ( suggestedviewId != null ) {
+-
+- if ( view != null && Arrays.diff(view,suggestedView,local).length == 0 && Arrays.diff(suggestedView,view,local).length == 0) {
+- suggestedviewId = null;
+- suggestedView = null;
+- fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_ELECT_ABANDONED,this,"Election abandoned, running election matches view"));
+- } else {
+- fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_ELECT_ABANDONED,this,"Election abandoned, election running"));
+- }
+- return; //election already running, I'm not allowed to have two of them
+- }
+- if ( view != null && Arrays.diff(view,membership,local).length == 0 && Arrays.diff(membership,view,local).length == 0) {
+- fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_ELECT_ABANDONED,this,"Election abandoned, view matches membership"));
+- return; //already have this view installed
+- }
+- int prio = AbsoluteOrder.comp.compare(local,others[0]);
+- MemberImpl leader = ( prio < 0 )?local:others[0];//am I the leader in my view?
+- if ( local.equals(leader) || force ) {
+- CoordinationMessage msg = createElectionMsg(local, others, leader);
+- suggestedviewId = msg.getId();
+- suggestedView = new Membership(local,AbsoluteOrder.comp,true);
+- Arrays.fill(suggestedView,msg.getMembers());
+- fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_PROCESS_ELECT,this,"Election, sending request"));
+- sendElectionMsg(local,others[0],msg);
+- } else {
+- try {
+- coordMsgReceived.set(false);
+- fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_WAIT_FOR_MSG,this,"Election, waiting for request"));
+- electionMutex.wait(waitForCoordMsgTimeout);
+- }catch ( InterruptedException x ) {
+- Thread.currentThread().interrupted();
+- }
+- if ( suggestedviewId == null && (!coordMsgReceived.get())) {
+- //no message arrived, send the coord msg
+-// fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_WAIT_FOR_MSG,this,"Election, waiting timed out."));
+-// startElection(true);
+- fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_ELECT_ABANDONED,this,"Election abandoned, waiting timed out."));
+- } else {
+- fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_ELECT_ABANDONED,this,"Election abandoned, received a message"));
+- }
+- }//end if
+-
+- }
+- }
+-
+- private CoordinationMessage createElectionMsg(MemberImpl local, MemberImpl[] others, MemberImpl leader) {
+- Membership m = new Membership(local,AbsoluteOrder.comp,true);
+- Arrays.fill(m,others);
+- MemberImpl[] mbrs = m.getMembers();
+- m.reset();
+- CoordinationMessage msg = new CoordinationMessage(leader, local, mbrs,new UniqueId(UUIDGenerator.randomUUID(true)), this.COORD_REQUEST);
+- return msg;
+- }
+-
+- protected void sendElectionMsg(MemberImpl local, MemberImpl next, CoordinationMessage msg) throws ChannelException {
+- fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_SEND_MSG,this,"Sending election message to("+next.getName()+")"));
+- super.sendMessage(new Member[] {next}, createData(msg, local), null);
+- }
+-
+- protected void sendElectionMsgToNextInline(MemberImpl local, CoordinationMessage msg) throws ChannelException {
+- int next = Arrays.nextIndex(local,msg.getMembers());
+- int current = next;
+- msg.leader = msg.getMembers()[0];
+- boolean sent = false;
+- while ( !sent && current >= 0 ) {
+- try {
+- sendElectionMsg(local, (MemberImpl) msg.getMembers()[current], msg);
+- sent = true;
+- }catch ( ChannelException x ) {
+- log.warn("Unable to send election message to:"+msg.getMembers()[current]);
+- current = Arrays.nextIndex(msg.getMembers()[current],msg.getMembers());
+- if ( current == next ) throw x;
+- }
+- }
+- }
+-
+- public Member getNextInLine(MemberImpl local, MemberImpl[] others) {
+- MemberImpl result = null;
+- for ( int i=0; i<others.length; i++ ) {
+-
+- }
+- return result;
+- }
+-
+- public ChannelData createData(CoordinationMessage msg, MemberImpl local) {
+- msg.write();
+- ChannelData data = new ChannelData(true);
+- data.setAddress(local);
+- data.setMessage(msg.getBuffer());
+- data.setOptions(Channel.SEND_OPTIONS_USE_ACK);
+- data.setTimestamp(System.currentTimeMillis());
+- return data;
+- }
+-
+- protected void viewChange(UniqueId viewId, Member[] view) {
+- //invoke any listeners
+- }
+-
+- protected boolean alive(Member mbr) {
+- return TcpFailureDetector.memberAlive(mbr,
+- COORD_ALIVE,
+- false,
+- false,
+- waitForCoordMsgTimeout,
+- waitForCoordMsgTimeout,
+- getOptionFlag());
+- }
+-
+- protected Membership mergeOnArrive(CoordinationMessage msg, Member sender) {
+- fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_PRE_MERGE,this,"Pre merge"));
+- MemberImpl local = (MemberImpl)getLocalMember(false);
+- Membership merged = new Membership(local,AbsoluteOrder.comp,true);
+- Arrays.fill(merged,msg.getMembers());
+- Arrays.fill(merged,getMembers());
+- Member[] diff = Arrays.diff(merged,membership,local);
+- for ( int i=0; i<diff.length; i++ ) {
+- if (!alive(diff[i])) merged.removeMember((MemberImpl)diff[i]);
+- else memberAdded(diff[i],false);
+- }
+- fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_POST_MERGE,this,"Post merge"));
+- return merged;
+- }
+-
+- protected void processCoordMessage(CoordinationMessage msg, Member sender) throws ChannelException {
+- if ( !coordMsgReceived.get() ) {
+- coordMsgReceived.set(true);
+- synchronized (electionMutex) { electionMutex.notifyAll();}
+- }
+- msg.timestamp = System.currentTimeMillis();
+- Membership merged = mergeOnArrive(msg, sender);
+- if (isViewConf(msg)) handleViewConf(msg, sender, merged);
+- else handleToken(msg, sender, merged);
+- ClassLoader loader;
+-
+- }
+-
+- protected void handleToken(CoordinationMessage msg, Member sender,Membership merged) throws ChannelException {
+- MemberImpl local = (MemberImpl)getLocalMember(false);
+- if ( local.equals(msg.getSource()) ) {
+- //my message msg.src=local
+- handleMyToken(local, msg, sender,merged);
+- } else {
+- handleOtherToken(local, msg, sender,merged);
+- }
+- }
+-
+- protected void handleMyToken(MemberImpl local, CoordinationMessage msg, Member sender,Membership merged) throws ChannelException {
+- if ( local.equals(msg.getLeader()) ) {
+- //no leadership change
+- if ( Arrays.sameMembers(msg.getMembers(),merged.getMembers()) ) {
+- msg.type = COORD_CONF;
+- super.sendMessage(Arrays.remove(msg.getMembers(),local),createData(msg,local),null);
+- handleViewConf(msg,local,merged);
+- } else {
+- //membership change
+- suggestedView = new Membership(local,AbsoluteOrder.comp,true);
+- suggestedviewId = msg.getId();
+- Arrays.fill(suggestedView,merged.getMembers());
+- msg.view = (MemberImpl[])merged.getMembers();
+- sendElectionMsgToNextInline(local,msg);
+- }
+- } else {
+- //leadership change
+- suggestedView = null;
+- suggestedviewId = null;
+- msg.view = (MemberImpl[])merged.getMembers();
+- sendElectionMsgToNextInline(local,msg);
+- }
+- }
+-
+- protected void handleOtherToken(MemberImpl local, CoordinationMessage msg, Member sender,Membership merged) throws ChannelException {
+- if ( local.equals(msg.getLeader()) ) {
+- //I am the new leader
+- //startElection(false);
+- } else {
+- msg.view = (MemberImpl[])merged.getMembers();
+- sendElectionMsgToNextInline(local,msg);
+- }
+- }
+-
+- protected void handleViewConf(CoordinationMessage msg, Member sender,Membership merged) throws ChannelException {
+- if ( viewId != null && msg.getId().equals(viewId) ) return;//we already have this view
+- view = new Membership((MemberImpl)getLocalMember(false),AbsoluteOrder.comp,true);
+- Arrays.fill(view,msg.getMembers());
+- viewId = msg.getId();
+-
+- if ( viewId.equals(suggestedviewId) ) {
+- suggestedView = null;
+- suggestedviewId = null;
+- }
+-
+- if (suggestedView != null && AbsoluteOrder.comp.compare(suggestedView.getMembers()[0],merged.getMembers()[0])<0 ) {
+- suggestedView = null;
+- suggestedviewId = null;
+- }
+-
+- viewChange(viewId,view.getMembers());
+- fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_CONF_RX,this,"Accepted View"));
+-
+- if ( suggestedviewId == null && hasHigherPriority(merged.getMembers(),membership.getMembers()) ) {
+- startElection(false);
+- }
+- }
+-
+- protected boolean isViewConf(CoordinationMessage msg) {
+- return Arrays.contains(msg.getType(),0,COORD_CONF,0,COORD_CONF.length);
+- }
+-
+- protected boolean hasHigherPriority(Member[] complete, Member[] local) {
+- if ( local == null || local.length == 0 ) return false;
+- if ( complete == null || complete.length == 0 ) return true;
+- AbsoluteOrder.absoluteOrder(complete);
+- AbsoluteOrder.absoluteOrder(local);
+- return (AbsoluteOrder.comp.compare(complete[0],local[0]) > 0);
+-
+- }
+-
+-
+- /**
+- * Returns coordinator if one is available
+- * @return Member
+- */
+- public Member getCoordinator() {
+- return (view != null && view.hasMembers()) ? view.getMembers()[0] : null;
+- }
+-
+- public Member[] getView() {
+- return (view != null && view.hasMembers()) ? view.getMembers() : new Member[0];
+- }
+-
+- public UniqueId getViewId() {
+- return viewId;
+- }
+-
+- /**
+- * Block in/out messages while a election is going on
+- */
+- protected void halt() {
+-
+- }
+-
+- /**
+- * Release lock for in/out messages election is completed
+- */
+- protected void release() {
+-
+- }
+-
+- /**
+- * Wait for an election to end
+- */
+- protected void waitForRelease() {
+-
+- }
+-
+-
+-//============================================================================================================
+-// OVERRIDDEN METHODS FROM CHANNEL INTERCEPTOR BASE
+-//============================================================================================================
+- public void start(int svc) throws ChannelException {
+- if (membership == null) setupMembership();
+- if (started)return;
+- fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_START, this, "Before start"));
+- super.start(startsvc);
+- started = true;
+- if (view == null) view = new Membership( (MemberImpl)super.getLocalMember(true), AbsoluteOrder.comp, true);
+- fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_START, this, "After start"));
+- startElection(false);
+- }
+-
+- public void stop(int svc) throws ChannelException {
+- try {
+- halt();
+- synchronized (electionMutex) {
+- if (!started)return;
+- started = false;
+- fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_STOP, this, "Before stop"));
+- super.stop(startsvc);
+- this.view = null;
+- this.viewId = null;
+- this.suggestedView = null;
+- this.suggestedviewId = null;
+- this.membership.reset();
+- fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_STOP, this, "After stop"));
+- }
+- }finally {
+- release();
+- }
+- }
+-
+-
+- public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+- waitForRelease();
+- super.sendMessage(destination, msg, payload);
+- }
+-
+- public void messageReceived(ChannelMessage msg) {
+- if ( Arrays.contains(msg.getMessage().getBytesDirect(),0,COORD_ALIVE,0,COORD_ALIVE.length) ) {
+- //ignore message, its an alive message
+- fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_MSG_ARRIVE,this,"Alive Message"));
+-
+- } else if ( Arrays.contains(msg.getMessage().getBytesDirect(),0,COORD_HEADER,0,COORD_HEADER.length) ) {
+- try {
+- CoordinationMessage cmsg = new CoordinationMessage(msg.getMessage());
+- Member[] cmbr = cmsg.getMembers();
+- fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_MSG_ARRIVE,this,"Coord Msg Arrived("+Arrays.toNameString(cmbr)+")"));
+- processCoordMessage(cmsg, msg.getAddress());
+- }catch ( ChannelException x ) {
+- log.error("Error processing coordination message. Could be fatal.",x);
+- }
+- } else {
+- super.messageReceived(msg);
+- }
+- }
+-
+- public boolean accept(ChannelMessage msg) {
+- return super.accept(msg);
+- }
+-
+- public void memberAdded(Member member) {
+- memberAdded(member,true);
+- }
+-
+- public void memberAdded(Member member,boolean elect) {
+- try {
+- if ( membership == null ) setupMembership();
+- if ( membership.memberAlive((MemberImpl)member) ) super.memberAdded(member);
+- try {
+- fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_MBR_ADD,this,"Member add("+member.getName()+")"));
+- if (started && elect) startElection(false);
+- }catch ( ChannelException x ) {
+- log.error("Unable to start election when member was added.",x);
+- }
+- }finally {
+- }
+-
+- }
+-
+- public void memberDisappeared(Member member) {
+- try {
+-
+- membership.removeMember((MemberImpl)member);
+- super.memberDisappeared(member);
+- try {
+- fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_MBR_DEL,this,"Member remove("+member.getName()+")"));
+- if ( started && (isCoordinator() || isHighest()) )
+- startElection(true); //to do, if a member disappears, only the coordinator can start
+- }catch ( ChannelException x ) {
+- log.error("Unable to start election when member was removed.",x);
+- }
+- }finally {
+- }
+- }
+-
+- public boolean isHighest() {
+- Member local = getLocalMember(false);
+- if ( membership.getMembers().length == 0 ) return true;
+- else return AbsoluteOrder.comp.compare(local,membership.getMembers()[0])<=0;
+- }
+-
+- public boolean isCoordinator() {
+- Member coord = getCoordinator();
+- return coord != null && getLocalMember(false).equals(coord);
+- }
+-
+- public void heartbeat() {
+- try {
+- MemberImpl local = (MemberImpl)getLocalMember(false);
+- if ( view != null && (Arrays.diff(view,membership,local).length != 0 || Arrays.diff(membership,view,local).length != 0) ) {
+- if ( isHighest() ) {
+- fireInterceptorEvent(new CoordinationEvent(CoordinationEvent.EVT_START_ELECT, this,
+- "Heartbeat found inconsistency, restart election"));
+- startElection(true);
+- }
+- }
+- } catch ( Exception x ){
+- log.error("Unable to perform heartbeat.",x);
+- } finally {
+- super.heartbeat();
+- }
+- }
+-
+- /**
+- * has members
+- */
+- public boolean hasMembers() {
+-
+- return membership.hasMembers();
+- }
+-
+- /**
+- * Get all current cluster members
+- * @return all members or empty array
+- */
+- public Member[] getMembers() {
+-
+- return membership.getMembers();
+- }
+-
+- /**
+- *
+- * @param mbr Member
+- * @return Member
+- */
+- public Member getMember(Member mbr) {
+-
+- return membership.getMember(mbr);
+- }
+-
+- /**
+- * Return the member that represents this node.
+- *
+- * @return Member
+- */
+- public Member getLocalMember(boolean incAlive) {
+- Member local = super.getLocalMember(incAlive);
+- if ( view == null && (local != null)) setupMembership();
+- return local;
+- }
+-
+- protected synchronized void setupMembership() {
+- if ( membership == null ) {
+- membership = new Membership((MemberImpl)super.getLocalMember(true),AbsoluteOrder.comp,false);
+- }
+- }
+-
+-
+-//============================================================================================================
+-// HELPER CLASSES FOR COORDINATION
+-//============================================================================================================
+-
+-
+-
+-
+- public static class CoordinationMessage {
+- //X{A-ldr, A-src, mbrs-A,B,C,D}
+- protected XByteBuffer buf;
+- protected MemberImpl leader;
+- protected MemberImpl source;
+- protected MemberImpl[] view;
+- protected UniqueId id;
+- protected byte[] type;
+- protected long timestamp = System.currentTimeMillis();
+-
+- public CoordinationMessage(XByteBuffer buf) {
+- this.buf = buf;
+- parse();
+- }
+-
+- public CoordinationMessage(MemberImpl leader,
+- MemberImpl source,
+- MemberImpl[] view,
+- UniqueId id,
+- byte[] type) {
+- this.buf = new XByteBuffer(4096,false);
+- this.leader = leader;
+- this.source = source;
+- this.view = view;
+- this.id = id;
+- this.type = type;
+- this.write();
+- }
+-
+-
+- public byte[] getHeader() {
+- return NonBlockingCoordinator.COORD_HEADER;
+- }
+-
+- public MemberImpl getLeader() {
+- if ( leader == null ) parse();
+- return leader;
+- }
+-
+- public MemberImpl getSource() {
+- if ( source == null ) parse();
+- return source;
+- }
+-
+- public UniqueId getId() {
+- if ( id == null ) parse();
+- return id;
+- }
+-
+- public MemberImpl[] getMembers() {
+- if ( view == null ) parse();
+- return view;
+- }
+-
+- public byte[] getType() {
+- if (type == null ) parse();
+- return type;
+- }
+-
+- public XByteBuffer getBuffer() {
+- return this.buf;
+- }
+-
+- public void parse() {
+- //header
+- int offset = 16;
+- //leader
+- int ldrLen = buf.toInt(buf.getBytesDirect(),offset);
+- offset += 4;
+- byte[] ldr = new byte[ldrLen];
+- System.arraycopy(buf.getBytesDirect(),offset,ldr,0,ldrLen);
+- leader = MemberImpl.getMember(ldr);
+- offset += ldrLen;
+- //source
+- int srcLen = buf.toInt(buf.getBytesDirect(),offset);
+- offset += 4;
+- byte[] src = new byte[srcLen];
+- System.arraycopy(buf.getBytesDirect(),offset,src,0,srcLen);
+- source = MemberImpl.getMember(src);
+- offset += srcLen;
+- //view
+- int mbrCount = buf.toInt(buf.getBytesDirect(),offset);
+- offset += 4;
+- view = new MemberImpl[mbrCount];
+- for (int i=0; i<view.length; i++ ) {
+- int mbrLen = buf.toInt(buf.getBytesDirect(),offset);
+- offset += 4;
+- byte[] mbr = new byte[mbrLen];
+- System.arraycopy(buf.getBytesDirect(), offset, mbr, 0, mbrLen);
+- view[i] = MemberImpl.getMember(mbr);
+- offset += mbrLen;
+- }
+- //id
+- this.id = new UniqueId(buf.getBytesDirect(),offset,16);
+- offset += 16;
+- type = new byte[16];
+- System.arraycopy(buf.getBytesDirect(), offset, type, 0, type.length);
+- offset += 16;
+-
+- }
+-
+- public void write() {
+- buf.reset();
+- //header
+- buf.append(COORD_HEADER,0,COORD_HEADER.length);
+- //leader
+- byte[] ldr = leader.getData(false,false);
+- buf.append(ldr.length);
+- buf.append(ldr,0,ldr.length);
+- ldr = null;
+- //source
+- byte[] src = source.getData(false,false);
+- buf.append(src.length);
+- buf.append(src,0,src.length);
+- src = null;
+- //view
+- buf.append(view.length);
+- for (int i=0; i<view.length; i++ ) {
+- byte[] mbr = view[i].getData(false,false);
+- buf.append(mbr.length);
+- buf.append(mbr,0,mbr.length);
+- }
+- //id
+- buf.append(id.getBytes(),0,id.getBytes().length);
+- buf.append(type,0,type.length);
+- }
+- }
+-
+- public void fireInterceptorEvent(InterceptorEvent event) {
+- if (event instanceof CoordinationEvent &&
+- ((CoordinationEvent)event).type == CoordinationEvent.EVT_CONF_RX)
+- log.info(event);
+- }
+-
+- public static class CoordinationEvent implements InterceptorEvent {
+- public static final int EVT_START = 1;
+- public static final int EVT_MBR_ADD = 2;
+- public static final int EVT_MBR_DEL = 3;
+- public static final int EVT_START_ELECT = 4;
+- public static final int EVT_PROCESS_ELECT = 5;
+- public static final int EVT_MSG_ARRIVE = 6;
+- public static final int EVT_PRE_MERGE = 7;
+- public static final int EVT_POST_MERGE = 8;
+- public static final int EVT_WAIT_FOR_MSG = 9;
+- public static final int EVT_SEND_MSG = 10;
+- public static final int EVT_STOP = 11;
+- public static final int EVT_CONF_RX = 12;
+- public static final int EVT_ELECT_ABANDONED = 13;
+-
+- int type;
+- ChannelInterceptor interceptor;
+- Member coord;
+- Member[] mbrs;
+- String info;
+- Membership view;
+- Membership suggestedView;
+- public CoordinationEvent(int type,ChannelInterceptor interceptor, String info) {
+- this.type = type;
+- this.interceptor = interceptor;
+- this.coord = ((NonBlockingCoordinator)interceptor).getCoordinator();
+- this.mbrs = ((NonBlockingCoordinator)interceptor).membership.getMembers();
+- this.info = info;
+- this.view = ((NonBlockingCoordinator)interceptor).view;
+- this.suggestedView = ((NonBlockingCoordinator)interceptor).suggestedView;
+- }
+-
+- public int getEventType() {
+- return type;
+- }
+-
+- public String getEventTypeDesc() {
+- switch (type) {
+- case EVT_START: return "EVT_START:"+info;
+- case EVT_MBR_ADD: return "EVT_MBR_ADD:"+info;
+- case EVT_MBR_DEL: return "EVT_MBR_DEL:"+info;
+- case EVT_START_ELECT: return "EVT_START_ELECT:"+info;
+- case EVT_PROCESS_ELECT: return "EVT_PROCESS_ELECT:"+info;
+- case EVT_MSG_ARRIVE: return "EVT_MSG_ARRIVE:"+info;
+- case EVT_PRE_MERGE: return "EVT_PRE_MERGE:"+info;
+- case EVT_POST_MERGE: return "EVT_POST_MERGE:"+info;
+- case EVT_WAIT_FOR_MSG: return "EVT_WAIT_FOR_MSG:"+info;
+- case EVT_SEND_MSG: return "EVT_SEND_MSG:"+info;
+- case EVT_STOP: return "EVT_STOP:"+info;
+- case EVT_CONF_RX: return "EVT_CONF_RX:"+info;
+- case EVT_ELECT_ABANDONED: return "EVT_ELECT_ABANDONED:"+info;
+- default: return "Unknown";
+- }
+- }
+-
+- public ChannelInterceptor getInterceptor() {
+- return interceptor;
+- }
+-
+- public String toString() {
+- StringBuffer buf = new StringBuffer("CoordinationEvent[type=");
+- buf.append(type).append("\n\tLocal:");
+- Member local = interceptor.getLocalMember(false);
+- buf.append(local!=null?local.getName():"").append("\n\tCoord:");
+- buf.append(coord!=null?coord.getName():"").append("\n\tView:");
+- buf.append(Arrays.toNameString(view!=null?view.getMembers():null)).append("\n\tSuggested View:");
+- buf.append(Arrays.toNameString(suggestedView!=null?suggestedView.getMembers():null)).append("\n\tMembers:");
+- buf.append(Arrays.toNameString(mbrs)).append("\n\tInfo:");
+- buf.append(info).append("]");
+- return buf.toString();
+- }
+- }
+-
+-
+-
+-
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/group/interceptors/GzipInterceptor.java
+===================================================================
+--- java/org/apache/catalina/tribes/group/interceptors/GzipInterceptor.java (revision 590752)
++++ java/org/apache/catalina/tribes/group/interceptors/GzipInterceptor.java (working copy)
+@@ -1,100 +0,0 @@
+-/*
+- * 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
+- */
+-
+-package org.apache.catalina.tribes.group.interceptors;
+-
+-import java.io.ByteArrayInputStream;
+-import java.io.ByteArrayOutputStream;
+-import java.io.IOException;
+-import java.util.Arrays;
+-import java.util.zip.GZIPInputStream;
+-import java.util.zip.GZIPOutputStream;
+-
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+-import org.apache.catalina.tribes.group.InterceptorPayload;
+-
+-
+-
+-/**
+- *
+- *
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public class GzipInterceptor extends ChannelInterceptorBase {
+- public static final int DEFAULT_BUFFER_SIZE = 2048;
+-
+- public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+- try {
+- byte[] data = compress(msg.getMessage().getBytes());
+- msg.getMessage().trim(msg.getMessage().getLength());
+- msg.getMessage().append(data,0,data.length);
+- getNext().sendMessage(destination, msg, payload);
+- } catch ( IOException x ) {
+- log.error("Unable to compress byte contents");
+- throw new ChannelException(x);
+- }
+- }
+-
+- public void messageReceived(ChannelMessage msg) {
+- try {
+- byte[] data = decompress(msg.getMessage().getBytes());
+- msg.getMessage().trim(msg.getMessage().getLength());
+- msg.getMessage().append(data,0,data.length);
+- getPrevious().messageReceived(msg);
+- } catch ( IOException x ) {
+- log.error("Unable to decompress byte contents",x);
+- }
+- }
+-
+- public static byte[] compress(byte[] data) throws IOException {
+- ByteArrayOutputStream bout = new ByteArrayOutputStream();
+- GZIPOutputStream gout = new GZIPOutputStream(bout);
+- gout.write(data);
+- gout.flush();
+- gout.close();
+- return bout.toByteArray();
+- }
+-
+- /**
+- * @todo Fix to create an automatically growing buffer.
+- * @param data byte[]
+- * @return byte[]
+- * @throws IOException
+- */
+- public static byte[] decompress(byte[] data) throws IOException {
+- ByteArrayInputStream bin = new ByteArrayInputStream(data);
+- GZIPInputStream gin = new GZIPInputStream(bin);
+- byte[] tmp = new byte[DEFAULT_BUFFER_SIZE];
+- int length = gin.read(tmp);
+- byte[] result = new byte[length];
+- System.arraycopy(tmp,0,result,0,length);
+- return result;
+- }
+-
+- public static void main(String[] arg) throws Exception {
+- byte[] data = new byte[1024];
+- Arrays.fill(data,(byte)1);
+- byte[] compress = compress(data);
+- byte[] decompress = decompress(compress);
+- System.out.println("Debug test");
+-
+- }
+-
+-}
+Index: java/org/apache/catalina/tribes/group/interceptors/TcpFailureDetector.java
+===================================================================
+--- java/org/apache/catalina/tribes/group/interceptors/TcpFailureDetector.java (revision 590752)
++++ java/org/apache/catalina/tribes/group/interceptors/TcpFailureDetector.java (working copy)
+@@ -1,323 +0,0 @@
+-/*
+- * 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
+- */
+-package org.apache.catalina.tribes.group.interceptors;
+-
+-import java.net.InetAddress;
+-import java.net.InetSocketAddress;
+-import java.net.Socket;
+-import java.net.SocketTimeoutException;
+-import java.util.Arrays;
+-import java.util.HashMap;
+-
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.ChannelException.FaultyMember;
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.RemoteProcessException;
+-import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+-import org.apache.catalina.tribes.group.InterceptorPayload;
+-import org.apache.catalina.tribes.io.ChannelData;
+-import org.apache.catalina.tribes.io.XByteBuffer;
+-import org.apache.catalina.tribes.membership.MemberImpl;
+-import org.apache.catalina.tribes.membership.Membership;
+-import java.net.ConnectException;
+-
+-/**
+- * <p>Title: A perfect failure detector </p>
+- *
+- * <p>Description: The TcpFailureDetector is a useful interceptor
+- * that adds reliability to the membership layer.</p>
+- * <p>
+- * If the network is busy, or the system is busy so that the membership receiver thread
+- * is not getting enough time to update its table, members can be "timed out"
+- * This failure detector will intercept the memberDisappeared message(unless its a true shutdown message)
+- * and connect to the member using TCP.
+- * </p>
+- * <p>
+- * The TcpFailureDetector works in two ways. <br>
+- * 1. It intercepts memberDisappeared events
+- * 2. It catches send errors
+- * </p>
+- *
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public class TcpFailureDetector extends ChannelInterceptorBase {
+-
+- private static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog( TcpFailureDetector.class );
+-
+- protected static byte[] TCP_FAIL_DETECT = new byte[] {
+- 79, -89, 115, 72, 121, -126, 67, -55, -97, 111, -119, -128, -95, 91, 7, 20,
+- 125, -39, 82, 91, -21, -15, 67, -102, -73, 126, -66, -113, -127, 103, 30, -74,
+- 55, 21, -66, -121, 69, 126, 76, -88, -65, 10, 77, 19, 83, 56, 21, 50,
+- 85, -10, -108, -73, 58, -6, 64, 120, -111, 4, 125, -41, 114, -124, -64, -43};
+-
+- protected boolean performConnectTest = true;
+-
+- protected long connectTimeout = 1000;//1 second default
+-
+- protected boolean performSendTest = true;
+-
+- protected boolean performReadTest = false;
+-
+- protected long readTestTimeout = 5000;//5 seconds
+-
+- protected Membership membership = null;
+-
+- protected HashMap removeSuspects = new HashMap();
+-
+- protected HashMap addSuspects = new HashMap();
+-
+- public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+- try {
+- super.sendMessage(destination, msg, payload);
+- }catch ( ChannelException cx ) {
+- FaultyMember[] mbrs = cx.getFaultyMembers();
+- for ( int i=0; i<mbrs.length; i++ ) {
+- if ( mbrs[i].getCause()!=null &&
+- (!(mbrs[i].getCause() instanceof RemoteProcessException)) ) {//RemoteProcessException's are ok
+- this.memberDisappeared(mbrs[i].getMember());
+- }//end if
+- }//for
+- throw cx;
+- }
+- }
+-
+- public void messageReceived(ChannelMessage msg) {
+- //catch incoming
+- boolean process = true;
+- if ( okToProcess(msg.getOptions()) ) {
+- //check to see if it is a testMessage, if so, process = false
+- process = ( (msg.getMessage().getLength() != TCP_FAIL_DETECT.length) ||
+- (!Arrays.equals(TCP_FAIL_DETECT,msg.getMessage().getBytes()) ) );
+- }//end if
+-
+- //ignore the message, it doesnt have the flag set
+- if ( process ) super.messageReceived(msg);
+- else if ( log.isDebugEnabled() ) log.debug("Received a failure detector packet:"+msg);
+- }//messageReceived
+-
+-
+- public void memberAdded(Member member) {
+- if ( membership == null ) setupMembership();
+- boolean notify = false;
+- synchronized (membership) {
+- if (removeSuspects.containsKey(member)) {
+- //previously marked suspect, system below picked up the member again
+- removeSuspects.remove(member);
+- } else if (membership.getMember( (MemberImpl) member) == null){
+- //if we add it here, then add it upwards too
+- //check to see if it is alive
+- if (memberAlive(member)) {
+- membership.memberAlive( (MemberImpl) member);
+- notify = true;
+- } else {
+- addSuspects.put(member, new Long(System.currentTimeMillis()));
+- }
+- }
+- }
+- if ( notify ) super.memberAdded(member);
+- }
+-
+- public void memberDisappeared(Member member) {
+- if ( membership == null ) setupMembership();
+- boolean notify = false;
+- boolean shutdown = Arrays.equals(member.getCommand(),Member.SHUTDOWN_PAYLOAD);
+- if ( !shutdown )
+- if(log.isInfoEnabled())
+- log.info("Received memberDisappeared["+member+"] message. Will verify.");
+- synchronized (membership) {
+- //check to see if the member really is gone
+- //if the payload is not a shutdown message
+- if (shutdown || !memberAlive(member)) {
+- //not correct, we need to maintain the map
+- membership.removeMember( (MemberImpl) member);
+- removeSuspects.remove(member);
+- notify = true;
+- } else {
+- //add the member as suspect
+- removeSuspects.put(member, new Long(System.currentTimeMillis()));
+- }
+- }
+- if ( notify ) {
+- if(log.isInfoEnabled())
+- log.info("Verification complete. Member disappeared["+member+"]");
+- super.memberDisappeared(member);
+- } else {
+- if(log.isInfoEnabled())
+- log.info("Verification complete. Member still alive["+member+"]");
+-
+- }
+- }
+-
+- public boolean hasMembers() {
+- if ( membership == null ) setupMembership();
+- return membership.hasMembers();
+- }
+-
+- public Member[] getMembers() {
+- if ( membership == null ) setupMembership();
+- return membership.getMembers();
+- }
+-
+- public Member getMember(Member mbr) {
+- if ( membership == null ) setupMembership();
+- return membership.getMember(mbr);
+- }
+-
+- public Member getLocalMember(boolean incAlive) {
+- return super.getLocalMember(incAlive);
+- }
+-
+- public void heartbeat() {
+- super.heartbeat();
+- checkMembers(false);
+- }
+- public void checkMembers(boolean checkAll) {
+-
+- try {
+- if (membership == null) setupMembership();
+- synchronized (membership) {
+- if ( !checkAll ) performBasicCheck();
+- else performForcedCheck();
+- }
+- }catch ( Exception x ) {
+- log.warn("Unable to perform heartbeat on the TcpFailureDetector.",x);
+- } finally {
+-
+- }
+- }
+-
+- protected void performForcedCheck() {
+- //update all alive times
+- Member[] members = super.getMembers();
+- for (int i = 0; members != null && i < members.length; i++) {
+- if (memberAlive(members[i])) {
+- if (membership.memberAlive((MemberImpl)members[i])) super.memberAdded(members[i]);
+- addSuspects.remove(members[i]);
+- } else {
+- if (membership.getMember(members[i])!=null) {
+- membership.removeMember((MemberImpl)members[i]);
+- removeSuspects.remove(members[i]);
+- super.memberDisappeared((MemberImpl)members[i]);
+- }
+- } //end if
+- } //for
+-
+- }
+-
+- protected void performBasicCheck() {
+- //update all alive times
+- Member[] members = super.getMembers();
+- for (int i = 0; members != null && i < members.length; i++) {
+- if (membership.memberAlive( (MemberImpl) members[i])) {
+- //we don't have this one in our membership, check to see if he/she is alive
+- if (memberAlive(members[i])) {
+- log.warn("Member added, even though we werent notified:" + members[i]);
+- super.memberAdded(members[i]);
+- } else {
+- membership.removeMember( (MemberImpl) members[i]);
+- } //end if
+- } //end if
+- } //for
+-
+- //check suspect members if they are still alive,
+- //if not, simply issue the memberDisappeared message
+- MemberImpl[] keys = (MemberImpl[]) removeSuspects.keySet().toArray(new MemberImpl[removeSuspects.size()]);
+- for (int i = 0; i < keys.length; i++) {
+- MemberImpl m = (MemberImpl) keys[i];
+- if (membership.getMember(m) != null && (!memberAlive(m))) {
+- membership.removeMember(m);
+- super.memberDisappeared(m);
+- removeSuspects.remove(m);
+- if(log.isInfoEnabled())
+- log.info("Suspect member, confirmed dead.["+m+"]");
+- } //end if
+- }
+-
+- //check add suspects members if they are alive now,
+- //if they are, simply issue the memberAdded message
+- keys = (MemberImpl[]) addSuspects.keySet().toArray(new MemberImpl[addSuspects.size()]);
+- for (int i = 0; i < keys.length; i++) {
+- MemberImpl m = (MemberImpl) keys[i];
+- if ( membership.getMember(m) == null && (memberAlive(m))) {
+- membership.memberAlive(m);
+- super.memberAdded(m);
+- addSuspects.remove(m);
+- if(log.isInfoEnabled())
+- log.info("Suspect member, confirmed alive.["+m+"]");
+- } //end if
+- }
+- }
+-
+- protected synchronized void setupMembership() {
+- if ( membership == null ) {
+- membership = new Membership((MemberImpl)super.getLocalMember(true));
+- }
+-
+- }
+-
+- protected boolean memberAlive(Member mbr) {
+- return memberAlive(mbr,TCP_FAIL_DETECT,performSendTest,performReadTest,readTestTimeout,connectTimeout,getOptionFlag());
+- }
+-
+- protected static boolean memberAlive(Member mbr, byte[] msgData,
+- boolean sendTest, boolean readTest,
+- long readTimeout, long conTimeout,
+- int optionFlag) {
+- //could be a shutdown notification
+- if ( Arrays.equals(mbr.getCommand(),Member.SHUTDOWN_PAYLOAD) ) return false;
+-
+- Socket socket = new Socket();
+- try {
+- InetAddress ia = InetAddress.getByAddress(mbr.getHost());
+- InetSocketAddress addr = new InetSocketAddress(ia, mbr.getPort());
+- socket.setSoTimeout((int)readTimeout);
+- socket.connect(addr, (int) conTimeout);
+- if ( sendTest ) {
+- ChannelData data = new ChannelData(true);
+- data.setAddress(mbr);
+- data.setMessage(new XByteBuffer(msgData,false));
+- data.setTimestamp(System.currentTimeMillis());
+- int options = optionFlag | Channel.SEND_OPTIONS_BYTE_MESSAGE;
+- if ( readTest ) options = (options | Channel.SEND_OPTIONS_USE_ACK);
+- else options = (options & (~Channel.SEND_OPTIONS_USE_ACK));
+- data.setOptions(options);
+- byte[] message = XByteBuffer.createDataPackage(data);
+- socket.getOutputStream().write(message);
+- if ( readTest ) {
+- int length = socket.getInputStream().read(message);
+- return length > 0;
+- }
+- }//end if
+- return true;
+- } catch ( SocketTimeoutException sx) {
+- //do nothing, we couldn't connect
+- } catch ( ConnectException cx) {
+- //do nothing, we couldn't connect
+- }catch (Exception x ) {
+- log.error("Unable to perform failure detection check, assuming member down.",x);
+- } finally {
+- try {socket.close(); } catch ( Exception ignore ){}
+- }
+- return false;
+- }
+-
+-
+-
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/group/interceptors/ThroughputInterceptor.java
+===================================================================
+--- java/org/apache/catalina/tribes/group/interceptors/ThroughputInterceptor.java (revision 590752)
++++ java/org/apache/catalina/tribes/group/interceptors/ThroughputInterceptor.java (working copy)
+@@ -1,120 +0,0 @@
+-/*
+- * 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
+- */
+-
+-package org.apache.catalina.tribes.group.interceptors;
+-
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+-import org.apache.catalina.tribes.group.InterceptorPayload;
+-import org.apache.catalina.tribes.io.ChannelData;
+-import org.apache.catalina.tribes.io.XByteBuffer;
+-import java.text.DecimalFormat;
+-import org.apache.catalina.tribes.membership.MemberImpl;
+-import java.util.concurrent.atomic.AtomicInteger;
+-import java.util.concurrent.atomic.AtomicLong;
+-
+-
+-
+-/**
+- *
+- *
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public class ThroughputInterceptor extends ChannelInterceptorBase {
+- protected static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(ThroughputInterceptor.class);
+-
+- double mbTx = 0;
+- double mbAppTx = 0;
+- double mbRx = 0;
+- double timeTx = 0;
+- double lastCnt = 0;
+- AtomicLong msgTxCnt = new AtomicLong(1);
+- AtomicLong msgRxCnt = new AtomicLong(0);
+- AtomicLong msgTxErr = new AtomicLong(0);
+- int interval = 10000;
+- AtomicInteger access = new AtomicInteger(0);
+- long txStart = 0;
+- long rxStart = 0;
+- DecimalFormat df = new DecimalFormat("#0.00");
+-
+-
+- public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+- if ( access.addAndGet(1) == 1 ) txStart = System.currentTimeMillis();
+- long bytes = XByteBuffer.getDataPackageLength(((ChannelData)msg).getDataPackageLength());
+- try {
+- super.sendMessage(destination, msg, payload);
+- }catch ( ChannelException x ) {
+- msgTxErr.addAndGet(1);
+- access.addAndGet(-1);
+- throw x;
+- }
+- mbTx += ((double)(bytes*destination.length))/(1024d*1024d);
+- mbAppTx += ((double)(bytes))/(1024d*1024d);
+- if ( access.addAndGet(-1) == 0 ) {
+- long stop = System.currentTimeMillis();
+- timeTx += ( (double) (stop - txStart)) / 1000d;
+- if ((msgTxCnt.get() / interval) >= lastCnt) {
+- lastCnt++;
+- report(timeTx);
+- }
+- }
+- msgTxCnt.addAndGet(1);
+- }
+-
+- public void messageReceived(ChannelMessage msg) {
+- if ( rxStart == 0 ) rxStart = System.currentTimeMillis();
+- long bytes = XByteBuffer.getDataPackageLength(((ChannelData)msg).getDataPackageLength());
+- mbRx += ((double)bytes)/(1024d*1024d);
+- msgRxCnt.addAndGet(1);
+- if ( msgRxCnt.get() % interval == 0 ) report(timeTx);
+- super.messageReceived(msg);
+-
+- }
+-
+- public void report(double timeTx) {
+- StringBuffer buf = new StringBuffer("ThroughputInterceptor Report[\n\tTx Msg:");
+- buf.append(msgTxCnt).append(" messages\n\tSent:");
+- buf.append(df.format(mbTx));
+- buf.append(" MB (total)\n\tSent:");
+- buf.append(df.format(mbAppTx));
+- buf.append(" MB (application)\n\tTime:");
+- buf.append(df.format(timeTx));
+- buf.append(" seconds\n\tTx Speed:");
+- buf.append(df.format(mbTx/timeTx));
+- buf.append(" MB/sec (total)\n\tTxSpeed:");
+- buf.append(df.format(mbAppTx/timeTx));
+- buf.append(" MB/sec (application)\n\tError Msg:");
+- buf.append(msgTxErr).append("\n\tRx Msg:");
+- buf.append(msgRxCnt);
+- buf.append(" messages\n\tRx Speed:");
+- buf.append(df.format(mbRx/((double)((System.currentTimeMillis()-rxStart)/1000))));
+- buf.append(" MB/sec (since 1st msg)\n\tReceived:");
+- buf.append(df.format(mbRx)).append(" MB]\n");
+- if ( log.isInfoEnabled() ) log.info(buf);
+- }
+-
+- public void setInterval(int interval) {
+- this.interval = interval;
+- }
+-
+- public int getInterval() {
+- return interval;
+- }
+-
+-}
+Index: java/org/apache/catalina/tribes/group/interceptors/MessageDispatch15Interceptor.java
+===================================================================
+--- java/org/apache/catalina/tribes/group/interceptors/MessageDispatch15Interceptor.java (revision 590752)
++++ java/org/apache/catalina/tribes/group/interceptors/MessageDispatch15Interceptor.java (working copy)
+@@ -1,112 +0,0 @@
+-/*
+- * 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
+- */
+-package org.apache.catalina.tribes.group.interceptors;
+-
+-import java.util.concurrent.LinkedBlockingQueue;
+-import java.util.concurrent.ThreadPoolExecutor;
+-import java.util.concurrent.atomic.AtomicLong;
+-
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.group.InterceptorPayload;
+-import org.apache.catalina.tribes.transport.bio.util.LinkObject;
+-import java.util.concurrent.TimeUnit;
+-
+-/**
+- *
+- * Same implementation as the MessageDispatchInterceptor
+- * except is ues an atomic long for the currentSize calculation
+- * and uses a thread pool for message sending.
+- *
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-
+-public class MessageDispatch15Interceptor extends MessageDispatchInterceptor {
+-
+- protected AtomicLong currentSize = new AtomicLong(0);
+- protected ThreadPoolExecutor executor = null;
+- protected int maxThreads = 10;
+- protected int maxSpareThreads = 2;
+- protected long keepAliveTime = 5000;
+- protected LinkedBlockingQueue<Runnable> runnablequeue = new LinkedBlockingQueue<Runnable>();
+-
+- public long getCurrentSize() {
+- return currentSize.get();
+- }
+-
+- public long addAndGetCurrentSize(long inc) {
+- return currentSize.addAndGet(inc);
+- }
+-
+- public long setAndGetCurrentSize(long value) {
+- currentSize.set(value);
+- return value;
+- }
+-
+- public boolean addToQueue(ChannelMessage msg, Member[] destination, InterceptorPayload payload) {
+- final LinkObject obj = new LinkObject(msg,destination,payload);
+- Runnable r = new Runnable() {
+- public void run() {
+- sendAsyncData(obj);
+- }
+- };
+- executor.execute(r);
+- return true;
+- }
+-
+- public LinkObject removeFromQueue() {
+- return null; //not used, thread pool contains its own queue.
+- }
+-
+- public void startQueue() {
+- if ( run ) return;
+- executor = new ThreadPoolExecutor(maxSpareThreads,maxThreads,keepAliveTime,TimeUnit.MILLISECONDS,runnablequeue);
+- run = true;
+- }
+-
+- public void stopQueue() {
+- run = false;
+- executor.shutdownNow();
+- setAndGetCurrentSize(0);
+- runnablequeue.clear();
+- }
+-
+- public long getKeepAliveTime() {
+- return keepAliveTime;
+- }
+-
+- public int getMaxSpareThreads() {
+- return maxSpareThreads;
+- }
+-
+- public int getMaxThreads() {
+- return maxThreads;
+- }
+-
+- public void setKeepAliveTime(long keepAliveTime) {
+- this.keepAliveTime = keepAliveTime;
+- }
+-
+- public void setMaxSpareThreads(int maxSpareThreads) {
+- this.maxSpareThreads = maxSpareThreads;
+- }
+-
+- public void setMaxThreads(int maxThreads) {
+- this.maxThreads = maxThreads;
+- }
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/group/interceptors/OrderInterceptor.java
+===================================================================
+--- java/org/apache/catalina/tribes/group/interceptors/OrderInterceptor.java (revision 590752)
++++ java/org/apache/catalina/tribes/group/interceptors/OrderInterceptor.java (working copy)
+@@ -1,329 +0,0 @@
+-/*
+- * 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
+- */
+-
+-package org.apache.catalina.tribes.group.interceptors;
+-
+-import java.util.HashMap;
+-
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+-import org.apache.catalina.tribes.group.InterceptorPayload;
+-import org.apache.catalina.tribes.io.XByteBuffer;
+-import java.util.concurrent.atomic.AtomicInteger;
+-import java.util.concurrent.locks.ReentrantReadWriteLock;
+-
+-
+-
+-/**
+- *
+- * The order interceptor guarantees that messages are received in the same order they were
+- * sent.
+- * This interceptor works best with the ack=true setting. <br>
+- * There is no point in
+- * using this with the replicationMode="fastasynchqueue" as this mode guarantees ordering.<BR>
+- * If you are using the mode ack=false replicationMode=pooled, and have a lot of concurrent threads,
+- * this interceptor can really slow you down, as many messages will be completely out of order
+- * and the queue might become rather large. If this is the case, then you might want to set
+- * the value OrderInterceptor.maxQueue = 25 (meaning that we will never keep more than 25 messages in our queue)
+- * <br><b>Configuration Options</b><br>
+- * OrderInteceptor.expire=<milliseconds> - if a message arrives out of order, how long before we act on it <b>default=3000ms</b><br>
+- * OrderInteceptor.maxQueue=<max queue size> - how much can the queue grow to ensure ordering.
+- * This setting is useful to avoid OutOfMemoryErrors<b>default=Integer.MAX_VALUE</b><br>
+- * OrderInterceptor.forwardExpired=<boolean> - this flag tells the interceptor what to
+- * do when a message has expired or the queue has grown larger than the maxQueue value.
+- * true means that the message is sent up the stack to the receiver that will receive and out of order message
+- * false means, forget the message and reset the message counter. <b>default=true</b>
+- *
+- *
+- * @author Filip Hanik
+- * @version 1.1
+- */
+-public class OrderInterceptor extends ChannelInterceptorBase {
+- private HashMap outcounter = new HashMap();
+- private HashMap incounter = new HashMap();
+- private HashMap incoming = new HashMap();
+- private long expire = 3000;
+- private boolean forwardExpired = true;
+- private int maxQueue = Integer.MAX_VALUE;
+-
+- ReentrantReadWriteLock inLock = new ReentrantReadWriteLock(true);
+- ReentrantReadWriteLock outLock= new ReentrantReadWriteLock(true);
+-
+- public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+- if ( !okToProcess(msg.getOptions()) ) {
+- super.sendMessage(destination, msg, payload);
+- return;
+- }
+- ChannelException cx = null;
+- for (int i=0; i<destination.length; i++ ) {
+- try {
+- int nr = 0;
+- try {
+- outLock.writeLock().lock();
+- nr = incCounter(destination[i]);
+- } finally {
+- outLock.writeLock().unlock();
+- }
+- //reduce byte copy
+- msg.getMessage().append(nr);
+- try {
+- getNext().sendMessage(new Member[] {destination[i]}, msg, payload);
+- } finally {
+- msg.getMessage().trim(4);
+- }
+- }catch ( ChannelException x ) {
+- if ( cx == null ) cx = x;
+- cx.addFaultyMember(x.getFaultyMembers());
+- }
+- }//for
+- if ( cx != null ) throw cx;
+- }
+-
+- public void messageReceived(ChannelMessage msg) {
+- if ( !okToProcess(msg.getOptions()) ) {
+- super.messageReceived(msg);
+- return;
+- }
+- int msgnr = XByteBuffer.toInt(msg.getMessage().getBytesDirect(),msg.getMessage().getLength()-4);
+- msg.getMessage().trim(4);
+- MessageOrder order = new MessageOrder(msgnr,(ChannelMessage)msg.deepclone());
+- try {
+- inLock.writeLock().lock();
+- if ( processIncoming(order) ) processLeftOvers(msg.getAddress(),false);
+- }finally {
+- inLock.writeLock().unlock();
+- }
+- }
+- protected void processLeftOvers(Member member, boolean force) {
+- MessageOrder tmp = (MessageOrder)incoming.get(member);
+- if ( force ) {
+- Counter cnt = getInCounter(member);
+- cnt.setCounter(Integer.MAX_VALUE);
+- }
+- if ( tmp!= null ) processIncoming(tmp);
+- }
+- /**
+- *
+- * @param order MessageOrder
+- * @return boolean - true if a message expired and was processed
+- */
+- protected boolean processIncoming(MessageOrder order) {
+- boolean result = false;
+- Member member = order.getMessage().getAddress();
+- Counter cnt = getInCounter(member);
+-
+- MessageOrder tmp = (MessageOrder)incoming.get(member);
+- if ( tmp != null ) {
+- order = MessageOrder.add(tmp,order);
+- }
+-
+-
+- while ( (order!=null) && (order.getMsgNr() <= cnt.getCounter()) ) {
+- //we are right on target. process orders
+- if ( order.getMsgNr() == cnt.getCounter() ) cnt.inc();
+- else if ( order.getMsgNr() > cnt.getCounter() ) cnt.setCounter(order.getMsgNr());
+- super.messageReceived(order.getMessage());
+- order.setMessage(null);
+- order = order.next;
+- }
+- MessageOrder head = order;
+- MessageOrder prev = null;
+- tmp = order;
+- //flag to empty out the queue when it larger than maxQueue
+- boolean empty = order!=null?order.getCount()>=maxQueue:false;
+- while ( tmp != null ) {
+- //process expired messages or empty out the queue
+- if ( tmp.isExpired(expire) || empty ) {
+- //reset the head
+- if ( tmp == head ) head = tmp.next;
+- cnt.setCounter(tmp.getMsgNr()+1);
+- if ( getForwardExpired() )
+- super.messageReceived(tmp.getMessage());
+- tmp.setMessage(null);
+- tmp = tmp.next;
+- if ( prev != null ) prev.next = tmp;
+- result = true;
+- } else {
+- prev = tmp;
+- tmp = tmp.next;
+- }
+- }
+- if ( head == null ) incoming.remove(member);
+- else incoming.put(member, head);
+- return result;
+- }
+-
+- public void memberAdded(Member member) {
+- //notify upwards
+- super.memberAdded(member);
+- }
+-
+- public void memberDisappeared(Member member) {
+- //reset counters - lock free
+- incounter.remove(member);
+- outcounter.remove(member);
+- //clear the remaining queue
+- processLeftOvers(member,true);
+- //notify upwards
+- super.memberDisappeared(member);
+- }
+-
+- protected int incCounter(Member mbr) {
+- Counter cnt = getOutCounter(mbr);
+- return cnt.inc();
+- }
+-
+- protected Counter getInCounter(Member mbr) {
+- Counter cnt = (Counter)incounter.get(mbr);
+- if ( cnt == null ) {
+- cnt = new Counter();
+- cnt.inc(); //always start at 1 for incoming
+- incounter.put(mbr,cnt);
+- }
+- return cnt;
+- }
+-
+- protected Counter getOutCounter(Member mbr) {
+- Counter cnt = (Counter)outcounter.get(mbr);
+- if ( cnt == null ) {
+- cnt = new Counter();
+- outcounter.put(mbr,cnt);
+- }
+- return cnt;
+- }
+-
+- protected static class Counter {
+- private AtomicInteger value = new AtomicInteger(0);
+-
+- public int getCounter() {
+- return value.get();
+- }
+-
+- public void setCounter(int counter) {
+- this.value.set(counter);
+- }
+-
+- public int inc() {
+- return value.addAndGet(1);
+- }
+- }
+-
+- protected static class MessageOrder {
+- private long received = System.currentTimeMillis();
+- private MessageOrder next;
+- private int msgNr;
+- private ChannelMessage msg = null;
+- public MessageOrder(int msgNr,ChannelMessage msg) {
+- this.msgNr = msgNr;
+- this.msg = msg;
+- }
+-
+- public boolean isExpired(long expireTime) {
+- return (System.currentTimeMillis()-received) > expireTime;
+- }
+-
+- public ChannelMessage getMessage() {
+- return msg;
+- }
+-
+- public void setMessage(ChannelMessage msg) {
+- this.msg = msg;
+- }
+-
+- public void setNext(MessageOrder order) {
+- this.next = order;
+- }
+- public MessageOrder getNext() {
+- return next;
+- }
+-
+- public int getCount() {
+- int counter = 1;
+- MessageOrder tmp = next;
+- while ( tmp != null ) {
+- counter++;
+- tmp = tmp.next;
+- }
+- return counter;
+- }
+-
+- public static MessageOrder add(MessageOrder head, MessageOrder add) {
+- if ( head == null ) return add;
+- if ( add == null ) return head;
+- if ( head == add ) return add;
+-
+- if ( head.getMsgNr() > add.getMsgNr() ) {
+- add.next = head;
+- return add;
+- }
+-
+- MessageOrder iter = head;
+- MessageOrder prev = null;
+- while ( iter.getMsgNr() < add.getMsgNr() && (iter.next !=null ) ) {
+- prev = iter;
+- iter = iter.next;
+- }
+- if ( iter.getMsgNr() < add.getMsgNr() ) {
+- //add after
+- add.next = iter.next;
+- iter.next = add;
+- } else if (iter.getMsgNr() > add.getMsgNr()) {
+- //add before
+- prev.next = add;
+- add.next = iter;
+-
+- } else {
+- throw new ArithmeticException("Message added has the same counter, synchronization bug. Disable the order interceptor");
+- }
+-
+- return head;
+- }
+-
+- public int getMsgNr() {
+- return msgNr;
+- }
+-
+-
+-
+- }
+-
+- public void setExpire(long expire) {
+- this.expire = expire;
+- }
+-
+- public void setForwardExpired(boolean forwardExpired) {
+- this.forwardExpired = forwardExpired;
+- }
+-
+- public void setMaxQueue(int maxQueue) {
+- this.maxQueue = maxQueue;
+- }
+-
+- public long getExpire() {
+- return expire;
+- }
+-
+- public boolean getForwardExpired() {
+- return forwardExpired;
+- }
+-
+- public int getMaxQueue() {
+- return maxQueue;
+- }
+-
+-}
+Index: java/org/apache/catalina/tribes/group/interceptors/DomainFilterInterceptor.java
+===================================================================
+--- java/org/apache/catalina/tribes/group/interceptors/DomainFilterInterceptor.java (revision 590752)
++++ java/org/apache/catalina/tribes/group/interceptors/DomainFilterInterceptor.java (working copy)
+@@ -1,102 +0,0 @@
+-/*
+- * 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
+- */
+-package org.apache.catalina.tribes.group.interceptors;
+-
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+-import org.apache.catalina.tribes.membership.MemberImpl;
+-import org.apache.catalina.tribes.membership.Membership;
+-import java.util.Arrays;
+-
+-/**
+- * <p>Title: Member domain filter interceptor </p>
+- *
+- * <p>Description: Filters membership based on domain.
+- * </p>
+- *
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public class DomainFilterInterceptor extends ChannelInterceptorBase {
+-
+- private static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog( DomainFilterInterceptor.class );
+-
+- protected Membership membership = null;
+-
+- protected byte[] domain = new byte[0];
+-
+- public void messageReceived(ChannelMessage msg) {
+- //should we filter incoming based on domain?
+- super.messageReceived(msg);
+- }//messageReceived
+-
+-
+- public void memberAdded(Member member) {
+- if ( membership == null ) setupMembership();
+- boolean notify = false;
+- synchronized (membership) {
+- notify = Arrays.equals(domain,member.getDomain());
+- if ( notify ) notify = membership.memberAlive((MemberImpl)member);
+- }
+- if ( notify ) super.memberAdded(member);
+- }
+-
+- public void memberDisappeared(Member member) {
+- if ( membership == null ) setupMembership();
+- boolean notify = false;
+- synchronized (membership) {
+- notify = Arrays.equals(domain,member.getDomain());
+- membership.removeMember((MemberImpl)member);
+- }
+- if ( notify ) super.memberDisappeared(member);
+- }
+-
+- public boolean hasMembers() {
+- if ( membership == null ) setupMembership();
+- return membership.hasMembers();
+- }
+-
+- public Member[] getMembers() {
+- if ( membership == null ) setupMembership();
+- return membership.getMembers();
+- }
+-
+- public Member getMember(Member mbr) {
+- if ( membership == null ) setupMembership();
+- return membership.getMember(mbr);
+- }
+-
+- public Member getLocalMember(boolean incAlive) {
+- return super.getLocalMember(incAlive);
+- }
+-
+-
+- protected synchronized void setupMembership() {
+- if ( membership == null ) {
+- membership = new Membership((MemberImpl)super.getLocalMember(true));
+- }
+-
+- }
+-
+- public byte[] getDomain() {
+- return domain;
+- }
+-
+- public void setDomain(byte[] domain) {
+- this.domain = domain;
+- }
+-}
+Index: java/org/apache/catalina/tribes/group/interceptors/FragmentationInterceptor.java
+===================================================================
+--- java/org/apache/catalina/tribes/group/interceptors/FragmentationInterceptor.java (revision 590752)
++++ java/org/apache/catalina/tribes/group/interceptors/FragmentationInterceptor.java (working copy)
+@@ -1,242 +0,0 @@
+-/*
+- * 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
+- */
+-
+-package org.apache.catalina.tribes.group.interceptors;
+-
+-import java.util.Arrays;
+-import java.util.HashMap;
+-import java.util.Set;
+-
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.group.ChannelInterceptorBase;
+-import org.apache.catalina.tribes.group.InterceptorPayload;
+-import org.apache.catalina.tribes.io.XByteBuffer;
+-
+-/**
+- *
+- * The fragmentation interceptor splits up large messages into smaller messages and assembles them on the other end.
+- * This is very useful when you don't want large messages hogging the sending sockets
+- * and smaller messages can make it through.
+- *
+- * <br><b>Configuration Options</b><br>
+- * OrderInteceptor.expire=<milliseconds> - how long do we keep the fragments in memory and wait for the rest to arrive<b>default=60,000ms -> 60seconds</b>
+- * This setting is useful to avoid OutOfMemoryErrors<br>
+- * OrderInteceptor.maxSize=<max message size> - message size in bytes <b>default=1024*100 (around a tenth of a MB)</b><br>
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public class FragmentationInterceptor extends ChannelInterceptorBase {
+- private static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog( FragmentationInterceptor.class );
+-
+- protected HashMap fragpieces = new HashMap();
+- private int maxSize = 1024*100;
+- private long expire = 1000 * 60; //one minute expiration
+- protected boolean deepclone = true;
+-
+-
+- public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+- int size = msg.getMessage().getLength();
+- boolean frag = (size>maxSize) && okToProcess(msg.getOptions());
+- if ( frag ) {
+- frag(destination, msg, payload);
+- } else {
+- msg.getMessage().append(frag);
+- super.sendMessage(destination, msg, payload);
+- }
+- }
+-
+- public void messageReceived(ChannelMessage msg) {
+- boolean isFrag = XByteBuffer.toBoolean(msg.getMessage().getBytesDirect(),msg.getMessage().getLength()-1);
+- msg.getMessage().trim(1);
+- if ( isFrag ) {
+- defrag(msg);
+- } else {
+- super.messageReceived(msg);
+- }
+- }
+-
+-
+- public FragCollection getFragCollection(FragKey key, ChannelMessage msg) {
+- FragCollection coll = (FragCollection)fragpieces.get(key);
+- if ( coll == null ) {
+- synchronized (fragpieces) {
+- coll = (FragCollection)fragpieces.get(key);
+- if ( coll == null ) {
+- coll = new FragCollection(msg);
+- fragpieces.put(key, coll);
+- }
+- }
+- }
+- return coll;
+- }
+-
+- public void removeFragCollection(FragKey key) {
+- fragpieces.remove(key);
+- }
+-
+- public void defrag(ChannelMessage msg ) {
+- FragKey key = new FragKey(msg.getUniqueId());
+- FragCollection coll = getFragCollection(key,msg);
+- coll.addMessage((ChannelMessage)msg.deepclone());
+-
+- if ( coll.complete() ) {
+- removeFragCollection(key);
+- ChannelMessage complete = coll.assemble();
+- super.messageReceived(complete);
+-
+- }
+- }
+-
+- public void frag(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+- int size = msg.getMessage().getLength();
+-
+- int count = ((size / maxSize )+(size%maxSize==0?0:1));
+- ChannelMessage[] messages = new ChannelMessage[count];
+- int remaining = size;
+- for ( int i=0; i<count; i++ ) {
+- ChannelMessage tmp = (ChannelMessage)msg.clone();
+- int offset = (i*maxSize);
+- int length = Math.min(remaining,maxSize);
+- tmp.getMessage().clear();
+- tmp.getMessage().append(msg.getMessage().getBytesDirect(),offset,length);
+- //add the msg nr
+- //tmp.getMessage().append(XByteBuffer.toBytes(i),0,4);
+- tmp.getMessage().append(i);
+- //add the total nr of messages
+- //tmp.getMessage().append(XByteBuffer.toBytes(count),0,4);
+- tmp.getMessage().append(count);
+- //add true as the frag flag
+- //byte[] flag = XByteBuffer.toBytes(true);
+- //tmp.getMessage().append(flag,0,flag.length);
+- tmp.getMessage().append(true);
+- messages[i] = tmp;
+- remaining -= length;
+-
+- }
+- for ( int i=0; i<messages.length; i++ ) {
+- super.sendMessage(destination,messages[i],payload);
+- }
+- }
+-
+- public void heartbeat() {
+- try {
+- Set set = fragpieces.keySet();
+- Object[] keys = set.toArray();
+- for ( int i=0; i<keys.length; i++ ) {
+- FragKey key = (FragKey)keys[i];
+- if ( key != null && key.expired(getExpire()) )
+- removeFragCollection(key);
+- }
+- }catch ( Exception x ) {
+- if ( log.isErrorEnabled() ) {
+- log.error("Unable to perform heartbeat clean up in the frag interceptor",x);
+- }
+- }
+- super.heartbeat();
+- }
+-
+-
+-
+- public int getMaxSize() {
+- return maxSize;
+- }
+-
+- public long getExpire() {
+- return expire;
+- }
+-
+- public void setMaxSize(int maxSize) {
+- this.maxSize = maxSize;
+- }
+-
+- public void setExpire(long expire) {
+- this.expire = expire;
+- }
+-
+- public static class FragCollection {
+- private long received = System.currentTimeMillis();
+- private ChannelMessage msg;
+- private XByteBuffer[] frags;
+- public FragCollection(ChannelMessage msg) {
+- //get the total messages
+- int count = XByteBuffer.toInt(msg.getMessage().getBytesDirect(),msg.getMessage().getLength()-4);
+- frags = new XByteBuffer[count];
+- this.msg = msg;
+- }
+-
+- public void addMessage(ChannelMessage msg) {
+- //remove the total messages
+- msg.getMessage().trim(4);
+- //get the msg nr
+- int nr = XByteBuffer.toInt(msg.getMessage().getBytesDirect(),msg.getMessage().getLength()-4);
+- //remove the msg nr
+- msg.getMessage().trim(4);
+- frags[nr] = msg.getMessage();
+-
+- }
+-
+- public boolean complete() {
+- boolean result = true;
+- for ( int i=0; (i<frags.length) && (result); i++ ) result = (frags[i] != null);
+- return result;
+- }
+-
+- public ChannelMessage assemble() {
+- if ( !complete() ) throw new IllegalStateException("Fragments are missing.");
+- int buffersize = 0;
+- for (int i=0; i<frags.length; i++ ) buffersize += frags[i].getLength();
+- XByteBuffer buf = new XByteBuffer(buffersize,false);
+- msg.setMessage(buf);
+- for ( int i=0; i<frags.length; i++ ) {
+- msg.getMessage().append(frags[i].getBytesDirect(),0,frags[i].getLength());
+- }
+- return msg;
+- }
+-
+- public boolean expired(long expire) {
+- return (System.currentTimeMillis()-received)>expire;
+- }
+-
+-
+-
+- }
+-
+- public static class FragKey {
+- private byte[] uniqueId;
+- private long received = System.currentTimeMillis();
+- public FragKey(byte[] id ) {
+- this.uniqueId = id;
+- }
+- public int hashCode() {
+- return XByteBuffer.toInt(uniqueId,0);
+- }
+-
+- public boolean equals(Object o ) {
+- if ( o instanceof FragKey ) {
+- return Arrays.equals(uniqueId,((FragKey)o).uniqueId);
+- } else return false;
+-
+- }
+-
+- public boolean expired(long expire) {
+- return (System.currentTimeMillis()-received)>expire;
+- }
+-
+- }
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/group/RpcCallback.java
+===================================================================
+--- java/org/apache/catalina/tribes/group/RpcCallback.java (revision 590752)
++++ java/org/apache/catalina/tribes/group/RpcCallback.java (working copy)
+@@ -1,46 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.group;
+-
+-import java.io.Serializable;
+-
+-import org.apache.catalina.tribes.Member;
+-
+-/**
+- * The RpcCallback interface is an interface for the Tribes channel to request a
+- * response object to a request that came in.
+- * @author not attributable
+- * @version 1.0
+- */
+-public interface RpcCallback {
+-
+- /**
+- *
+- * @param msg Serializable
+- * @return Serializable - null if no reply should be sent
+- */
+- public Serializable replyRequest(Serializable msg, Member sender);
+-
+- /**
+- * If the reply has already been sent to the requesting thread,
+- * the rpc callback can handle any data that comes in after the fact.
+- * @param msg Serializable
+- * @param sender Member
+- */
+- public void leftOver(Serializable msg, Member sender);
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/group/ChannelInterceptorBase.java
+===================================================================
+--- java/org/apache/catalina/tribes/group/ChannelInterceptorBase.java (revision 590752)
++++ java/org/apache/catalina/tribes/group/ChannelInterceptorBase.java (working copy)
+@@ -1,172 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.group;
+-
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.ChannelInterceptor;
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.Member;
+-
+-/**
+- * Abstract class for the interceptor base class.
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-
+-public abstract class ChannelInterceptorBase implements ChannelInterceptor {
+-
+- protected static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(
+- ChannelInterceptorBase.class);
+-
+- private ChannelInterceptor next;
+- private ChannelInterceptor previous;
+- //default value, always process
+- protected int optionFlag = 0;
+-
+- public ChannelInterceptorBase() {
+-
+- }
+-
+- public boolean okToProcess(int messageFlags) {
+- if (this.optionFlag == 0 ) return true;
+- return ((optionFlag&messageFlags) == optionFlag);
+- }
+-
+- public final void setNext(ChannelInterceptor next) {
+- this.next = next;
+- }
+-
+- public final ChannelInterceptor getNext() {
+- return next;
+- }
+-
+- public final void setPrevious(ChannelInterceptor previous) {
+- this.previous = previous;
+- }
+-
+- public void setOptionFlag(int optionFlag) {
+- this.optionFlag = optionFlag;
+- }
+-
+- public final ChannelInterceptor getPrevious() {
+- return previous;
+- }
+-
+- public int getOptionFlag() {
+- return optionFlag;
+- }
+-
+- public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws
+- ChannelException {
+- if (getNext() != null) getNext().sendMessage(destination, msg, payload);
+- }
+-
+- public void messageReceived(ChannelMessage msg) {
+- if (getPrevious() != null) getPrevious().messageReceived(msg);
+- }
+-
+- public boolean accept(ChannelMessage msg) {
+- return true;
+- }
+-
+- public void memberAdded(Member member) {
+- //notify upwards
+- if (getPrevious() != null) getPrevious().memberAdded(member);
+- }
+-
+- public void memberDisappeared(Member member) {
+- //notify upwards
+- if (getPrevious() != null) getPrevious().memberDisappeared(member);
+- }
+-
+- public void heartbeat() {
+- if (getNext() != null) getNext().heartbeat();
+- }
+-
+- /**
+- * has members
+- */
+- public boolean hasMembers() {
+- if ( getNext()!=null )return getNext().hasMembers();
+- else return false;
+- }
+-
+- /**
+- * Get all current cluster members
+- * @return all members or empty array
+- */
+- public Member[] getMembers() {
+- if ( getNext()!=null ) return getNext().getMembers();
+- else return null;
+- }
+-
+- /**
+- *
+- * @param mbr Member
+- * @return Member
+- */
+- public Member getMember(Member mbr) {
+- if ( getNext()!=null) return getNext().getMember(mbr);
+- else return null;
+- }
+-
+- /**
+- * Return the member that represents this node.
+- *
+- * @return Member
+- */
+- public Member getLocalMember(boolean incAlive) {
+- if ( getNext()!=null ) return getNext().getLocalMember(incAlive);
+- else return null;
+- }
+-
+- /**
+- * Starts up the channel. This can be called multiple times for individual services to start
+- * The svc parameter can be the logical or value of any constants
+- * @param svc int value of <BR>
+- * DEFAULT - will start all services <BR>
+- * MBR_RX_SEQ - starts the membership receiver <BR>
+- * MBR_TX_SEQ - starts the membership broadcaster <BR>
+- * SND_TX_SEQ - starts the replication transmitter<BR>
+- * SND_RX_SEQ - starts the replication receiver<BR>
+- * @throws ChannelException if a startup error occurs or the service is already started.
+- */
+- public void start(int svc) throws ChannelException {
+- if ( getNext()!=null ) getNext().start(svc);
+- }
+-
+- /**
+- * Shuts down the channel. This can be called multiple times for individual services to shutdown
+- * The svc parameter can be the logical or value of any constants
+- * @param svc int value of <BR>
+- * DEFAULT - will shutdown all services <BR>
+- * MBR_RX_SEQ - stops the membership receiver <BR>
+- * MBR_TX_SEQ - stops the membership broadcaster <BR>
+- * SND_TX_SEQ - stops the replication transmitter<BR>
+- * SND_RX_SEQ - stops the replication receiver<BR>
+- * @throws ChannelException if a startup error occurs or the service is already started.
+- */
+- public void stop(int svc) throws ChannelException {
+- if (getNext() != null) getNext().stop(svc);
+- }
+-
+- public void fireInterceptorEvent(InterceptorEvent event) {
+- //empty operation
+- }
+-
+-
+-}
+Index: java/org/apache/catalina/tribes/group/GroupChannel.java
+===================================================================
+--- java/org/apache/catalina/tribes/group/GroupChannel.java (revision 590752)
++++ java/org/apache/catalina/tribes/group/GroupChannel.java (working copy)
+@@ -1,673 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.group;
+-
+-
+-import java.io.Serializable;
+-import java.util.ArrayList;
+-import java.util.Iterator;
+-
+-import org.apache.catalina.tribes.ByteMessage;
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.ChannelInterceptor;
+-import org.apache.catalina.tribes.ChannelListener;
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.ChannelReceiver;
+-import org.apache.catalina.tribes.ChannelSender;
+-import org.apache.catalina.tribes.ErrorHandler;
+-import org.apache.catalina.tribes.ManagedChannel;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.MembershipListener;
+-import org.apache.catalina.tribes.MembershipService;
+-import org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor;
+-import org.apache.catalina.tribes.io.ChannelData;
+-import org.apache.catalina.tribes.io.XByteBuffer;
+-import org.apache.catalina.tribes.UniqueId;
+-import org.apache.catalina.tribes.Heartbeat;
+-import org.apache.catalina.tribes.io.BufferPool;
+-import org.apache.catalina.tribes.RemoteProcessException;
+-import org.apache.catalina.tribes.util.Logs;
+-import org.apache.catalina.tribes.util.Arrays;
+-
+-/**
+- * The default implementation of a Channel.<br>
+- * The GroupChannel manages the replication channel. It coordinates
+- * message being sent and received with membership announcements.
+- * The channel has an chain of interceptors that can modify the message or perform other logic.<br>
+- * It manages a complete group, both membership and replication.
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-public class GroupChannel extends ChannelInterceptorBase implements ManagedChannel {
+- /**
+- * Flag to determine if the channel manages its own heartbeat
+- * If set to true, the channel will start a local thread for the heart beat.
+- */
+- protected boolean heartbeat = true;
+- /**
+- * If <code>heartbeat == true</code> then how often do we want this
+- * heartbeat to run. default is one minute
+- */
+- protected long heartbeatSleeptime = 5*1000;//every 5 seconds
+-
+- /**
+- * Internal heartbeat thread
+- */
+- protected HeartbeatThread hbthread = null;
+-
+- /**
+- * The <code>ChannelCoordinator</code> coordinates the bottom layer components:<br>
+- * - MembershipService<br>
+- * - ChannelSender <br>
+- * - ChannelReceiver<br>
+- */
+- protected ChannelCoordinator coordinator = new ChannelCoordinator();
+-
+- /**
+- * The first interceptor in the inteceptor stack.
+- * The interceptors are chained in a linked list, so we only need a reference to the
+- * first one
+- */
+- protected ChannelInterceptor interceptors = null;
+-
+- /**
+- * A list of membership listeners that subscribe to membership announcements
+- */
+- protected ArrayList membershipListeners = new ArrayList();
+-
+- /**
+- * A list of channel listeners that subscribe to incoming messages
+- */
+- protected ArrayList channelListeners = new ArrayList();
+-
+- /**
+- * If set to true, the GroupChannel will check to make sure that
+- */
+- protected boolean optionCheck = false;
+-
+- /**
+- * Creates a GroupChannel. This constructor will also
+- * add the first interceptor in the GroupChannel.<br>
+- * The first interceptor is always the channel itself.
+- */
+- public GroupChannel() {
+- addInterceptor(this);
+- }
+-
+-
+- /**
+- * Adds an interceptor to the stack for message processing<br>
+- * Interceptors are ordered in the way they are added.<br>
+- * <code>channel.addInterceptor(A);</code><br>
+- * <code>channel.addInterceptor(C);</code><br>
+- * <code>channel.addInterceptor(B);</code><br>
+- * Will result in a interceptor stack like this:<br>
+- * <code>A -> C -> B</code><br>
+- * The complete stack will look like this:<br>
+- * <code>Channel -> A -> C -> B -> ChannelCoordinator</code><br>
+- * @param interceptor ChannelInterceptorBase
+- */
+- public void addInterceptor(ChannelInterceptor interceptor) {
+- if ( interceptors == null ) {
+- interceptors = interceptor;
+- interceptors.setNext(coordinator);
+- interceptors.setPrevious(null);
+- coordinator.setPrevious(interceptors);
+- } else {
+- ChannelInterceptor last = interceptors;
+- while ( last.getNext() != coordinator ) {
+- last = last.getNext();
+- }
+- last.setNext(interceptor);
+- interceptor.setNext(coordinator);
+- interceptor.setPrevious(last);
+- coordinator.setPrevious(interceptor);
+- }
+- }
+-
+- /**
+- * Sends a heartbeat through the interceptor stack.<br>
+- * Invoke this method from the application on a periodic basis if
+- * you have turned off internal heartbeats <code>channel.setHeartbeat(false)</code>
+- */
+- public void heartbeat() {
+- super.heartbeat();
+- Iterator i = membershipListeners.iterator();
+- while ( i.hasNext() ) {
+- Object o = i.next();
+- if ( o instanceof Heartbeat ) ((Heartbeat)o).heartbeat();
+- }
+- i = channelListeners.iterator();
+- while ( i.hasNext() ) {
+- Object o = i.next();
+- if ( o instanceof Heartbeat ) ((Heartbeat)o).heartbeat();
+- }
+-
+- }
+-
+-
+- /**
+- * Send a message to the destinations specified
+- * @param destination Member[] - destination.length > 1
+- * @param msg Serializable - the message to send
+- * @param options int - sender options, options can trigger guarantee levels and different interceptors to
+- * react to the message see class documentation for the <code>Channel</code> object.<br>
+- * @return UniqueId - the unique Id that was assigned to this message
+- * @throws ChannelException - if an error occurs processing the message
+- * @see org.apache.catalina.tribes.Channel
+- */
+- public UniqueId send(Member[] destination, Serializable msg, int options) throws ChannelException {
+- return send(destination,msg,options,null);
+- }
+-
+- /**
+- *
+- * @param destination Member[] - destination.length > 1
+- * @param msg Serializable - the message to send
+- * @param options int - sender options, options can trigger guarantee levels and different interceptors to
+- * react to the message see class documentation for the <code>Channel</code> object.<br>
+- * @param handler - callback object for error handling and completion notification, used when a message is
+- * sent asynchronously using the <code>Channel.SEND_OPTIONS_ASYNCHRONOUS</code> flag enabled.
+- * @return UniqueId - the unique Id that was assigned to this message
+- * @throws ChannelException - if an error occurs processing the message
+- * @see org.apache.catalina.tribes.Channel
+- */
+- public UniqueId send(Member[] destination, Serializable msg, int options, ErrorHandler handler) throws ChannelException {
+- if ( msg == null ) throw new ChannelException("Cant send a NULL message");
+- XByteBuffer buffer = null;
+- try {
+- if ( destination == null || destination.length == 0) throw new ChannelException("No destination given");
+- ChannelData data = new ChannelData(true);//generates a unique Id
+- data.setAddress(getLocalMember(false));
+- data.setTimestamp(System.currentTimeMillis());
+- byte[] b = null;
+- if ( msg instanceof ByteMessage ){
+- b = ((ByteMessage)msg).getMessage();
+- options = options | SEND_OPTIONS_BYTE_MESSAGE;
+- } else {
+- b = XByteBuffer.serialize(msg);
+- options = options & (~SEND_OPTIONS_BYTE_MESSAGE);
+- }
+- data.setOptions(options);
+- //XByteBuffer buffer = new XByteBuffer(b.length+128,false);
+- buffer = BufferPool.getBufferPool().getBuffer(b.length+128, false);
+- buffer.append(b,0,b.length);
+- data.setMessage(buffer);
+- InterceptorPayload payload = null;
+- if ( handler != null ) {
+- payload = new InterceptorPayload();
+- payload.setErrorHandler(handler);
+- }
+- getFirstInterceptor().sendMessage(destination, data, payload);
+- if ( Logs.MESSAGES.isTraceEnabled() ) {
+- Logs.MESSAGES.trace("GroupChannel - Sent msg:" + new UniqueId(data.getUniqueId()) + " at " +new java.sql.Timestamp(System.currentTimeMillis())+ " to "+Arrays.toNameString(destination));
+- Logs.MESSAGES.trace("GroupChannel - Send Message:" + new UniqueId(data.getUniqueId()) + " is " +msg);
+- }
+-
+- return new UniqueId(data.getUniqueId());
+- }catch ( Exception x ) {
+- if ( x instanceof ChannelException ) throw (ChannelException)x;
+- throw new ChannelException(x);
+- } finally {
+- if ( buffer != null ) BufferPool.getBufferPool().returnBuffer(buffer);
+- }
+- }
+-
+-
+- /**
+- * Callback from the interceptor stack. <br>
+- * When a message is received from a remote node, this method will be invoked by
+- * the previous interceptor.<br>
+- * This method can also be used to send a message to other components within the same application,
+- * but its an extreme case, and you're probably better off doing that logic between the applications itself.
+- * @param msg ChannelMessage
+- */
+- public void messageReceived(ChannelMessage msg) {
+- if ( msg == null ) return;
+- try {
+- if ( Logs.MESSAGES.isTraceEnabled() ) {
+- Logs.MESSAGES.trace("GroupChannel - Received msg:" + new UniqueId(msg.getUniqueId()) + " at " +new java.sql.Timestamp(System.currentTimeMillis())+ " from "+msg.getAddress().getName());
+- }
+-
+- Serializable fwd = null;
+- if ( (msg.getOptions() & SEND_OPTIONS_BYTE_MESSAGE) == SEND_OPTIONS_BYTE_MESSAGE ) {
+- fwd = new ByteMessage(msg.getMessage().getBytes());
+- } else {
+- try {
+- fwd = XByteBuffer.deserialize(msg.getMessage().getBytesDirect(), 0, msg.getMessage().getLength());
+- }catch (Exception sx) {
+- log.error("Unable to deserialize message:"+msg,sx);
+- return;
+- }
+- }
+- if ( Logs.MESSAGES.isTraceEnabled() ) {
+- Logs.MESSAGES.trace("GroupChannel - Receive Message:" + new UniqueId(msg.getUniqueId()) + " is " +fwd);
+- }
+-
+- //get the actual member with the correct alive time
+- Member source = msg.getAddress();
+- boolean rx = false;
+- boolean delivered = false;
+- for ( int i=0; i<channelListeners.size(); i++ ) {
+- ChannelListener channelListener = (ChannelListener)channelListeners.get(i);
+- if (channelListener != null && channelListener.accept(fwd, source)) {
+- channelListener.messageReceived(fwd, source);
+- delivered = true;
+- //if the message was accepted by an RPC channel, that channel
+- //is responsible for returning the reply, otherwise we send an absence reply
+- if ( channelListener instanceof RpcChannel ) rx = true;
+- }
+- }//for
+- if ((!rx) && (fwd instanceof RpcMessage)) {
+- //if we have a message that requires a response,
+- //but none was given, send back an immediate one
+- sendNoRpcChannelReply((RpcMessage)fwd,source);
+- }
+- if ( Logs.MESSAGES.isTraceEnabled() ) {
+- Logs.MESSAGES.trace("GroupChannel delivered["+delivered+"] id:"+new UniqueId(msg.getUniqueId()));
+- }
+-
+- } catch ( Exception x ) {
+- //this could be the channel listener throwing an exception, we should log it
+- //as a warning.
+- if ( log.isWarnEnabled() ) log.warn("Error receiving message:",x);
+- throw new RemoteProcessException("Exception:"+x.getMessage(),x);
+- }
+- }
+-
+- /**
+- * Sends a <code>NoRpcChannelReply</code> message to a member<br>
+- * This method gets invoked by the channel if a RPC message comes in
+- * and no channel listener accepts the message. This avoids timeout
+- * @param msg RpcMessage
+- * @param destination Member - the destination for the reply
+- */
+- protected void sendNoRpcChannelReply(RpcMessage msg, Member destination) {
+- try {
+- //avoid circular loop
+- if ( msg instanceof RpcMessage.NoRpcChannelReply) return;
+- RpcMessage.NoRpcChannelReply reply = new RpcMessage.NoRpcChannelReply(msg.rpcId,msg.uuid);
+- send(new Member[]{destination},reply,Channel.SEND_OPTIONS_ASYNCHRONOUS);
+- } catch ( Exception x ) {
+- log.error("Unable to find rpc channel, failed to send NoRpcChannelReply.",x);
+- }
+- }
+-
+- /**
+- * memberAdded gets invoked by the interceptor below the channel
+- * and the channel will broadcast it to the membership listeners
+- * @param member Member - the new member
+- */
+- public void memberAdded(Member member) {
+- //notify upwards
+- for (int i=0; i<membershipListeners.size(); i++ ) {
+- MembershipListener membershipListener = (MembershipListener)membershipListeners.get(i);
+- if (membershipListener != null) membershipListener.memberAdded(member);
+- }
+- }
+-
+- /**
+- * memberDisappeared gets invoked by the interceptor below the channel
+- * and the channel will broadcast it to the membership listeners
+- * @param member Member - the member that left or crashed
+- */
+- public void memberDisappeared(Member member) {
+- //notify upwards
+- for (int i=0; i<membershipListeners.size(); i++ ) {
+- MembershipListener membershipListener = (MembershipListener)membershipListeners.get(i);
+- if (membershipListener != null) membershipListener.memberDisappeared(member);
+- }
+- }
+-
+- /**
+- * Sets up the default implementation interceptor stack
+- * if no interceptors have been added
+- * @throws ChannelException
+- */
+- protected synchronized void setupDefaultStack() throws ChannelException {
+-
+- if ( getFirstInterceptor() != null &&
+- ((getFirstInterceptor().getNext() instanceof ChannelCoordinator))) {
+- ChannelInterceptor interceptor = null;
+- Class clazz = null;
+- try {
+- clazz = Class.forName("org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor",
+- true,GroupChannel.class.getClassLoader());
+- clazz.newInstance();
+- } catch ( Throwable x ) {
+- clazz = MessageDispatchInterceptor.class;
+- }//catch
+- try {
+- interceptor = (ChannelInterceptor) clazz.newInstance();
+- } catch (Exception x) {
+- throw new ChannelException("Unable to add MessageDispatchInterceptor to interceptor chain.",x);
+- }
+- this.addInterceptor(interceptor);
+- }
+- }
+-
+- /**
+- * Validates the option flags that each interceptor is using and reports
+- * an error if two interceptor share the same flag.
+- * @throws ChannelException
+- */
+- protected void checkOptionFlags() throws ChannelException {
+- StringBuffer conflicts = new StringBuffer();
+- ChannelInterceptor first = interceptors;
+- while ( first != null ) {
+- int flag = first.getOptionFlag();
+- if ( flag != 0 ) {
+- ChannelInterceptor next = first.getNext();
+- while ( next != null ) {
+- int nflag = next.getOptionFlag();
+- if (nflag!=0 && (((flag & nflag) == flag ) || ((flag & nflag) == nflag)) ) {
+- conflicts.append("[");
+- conflicts.append(first.getClass().getName());
+- conflicts.append(":");
+- conflicts.append(flag);
+- conflicts.append(" == ");
+- conflicts.append(next.getClass().getName());
+- conflicts.append(":");
+- conflicts.append(nflag);
+- conflicts.append("] ");
+- }//end if
+- next = next.getNext();
+- }//while
+- }//end if
+- first = first.getNext();
+- }//while
+- if ( conflicts.length() > 0 ) throw new ChannelException("Interceptor option flag conflict: "+conflicts.toString());
+-
+- }
+-
+- /**
+- * Starts the channel
+- * @param svc int - what service to start
+- * @throws ChannelException
+- * @see org.apache.catalina.tribes.Channel#start(int)
+- */
+- public synchronized void start(int svc) throws ChannelException {
+- setupDefaultStack();
+- if (optionCheck) checkOptionFlags();
+- super.start(svc);
+- if ( hbthread == null && heartbeat ) {
+- hbthread = new HeartbeatThread(this,heartbeatSleeptime);
+- hbthread.start();
+- }
+- }
+-
+- /**
+- * Stops the channel
+- * @param svc int
+- * @throws ChannelException
+- * @see org.apache.catalina.tribes.Channel#stop(int)
+- */
+- public synchronized void stop(int svc) throws ChannelException {
+- if (hbthread != null) {
+- hbthread.stopHeartbeat();
+- hbthread = null;
+- }
+- super.stop(svc);
+- }
+-
+- /**
+- * Returns the first interceptor of the stack. Useful for traversal.
+- * @return ChannelInterceptor
+- */
+- public ChannelInterceptor getFirstInterceptor() {
+- if (interceptors != null) return interceptors;
+- else return coordinator;
+- }
+-
+- /**
+- * Returns the channel receiver component
+- * @return ChannelReceiver
+- */
+- public ChannelReceiver getChannelReceiver() {
+- return coordinator.getClusterReceiver();
+- }
+-
+- /**
+- * Returns the channel sender component
+- * @return ChannelSender
+- */
+- public ChannelSender getChannelSender() {
+- return coordinator.getClusterSender();
+- }
+-
+- /**
+- * Returns the membership service component
+- * @return MembershipService
+- */
+- public MembershipService getMembershipService() {
+- return coordinator.getMembershipService();
+- }
+-
+- /**
+- * Sets the channel receiver component
+- * @param clusterReceiver ChannelReceiver
+- */
+- public void setChannelReceiver(ChannelReceiver clusterReceiver) {
+- coordinator.setClusterReceiver(clusterReceiver);
+- }
+-
+- /**
+- * Sets the channel sender component
+- * @param clusterSender ChannelSender
+- */
+- public void setChannelSender(ChannelSender clusterSender) {
+- coordinator.setClusterSender(clusterSender);
+- }
+-
+- /**
+- * Sets the membership component
+- * @param membershipService MembershipService
+- */
+- public void setMembershipService(MembershipService membershipService) {
+- coordinator.setMembershipService(membershipService);
+- }
+-
+- /**
+- * Adds a membership listener to the channel.<br>
+- * Membership listeners are uniquely identified using the equals(Object) method
+- * @param membershipListener MembershipListener
+- */
+- public void addMembershipListener(MembershipListener membershipListener) {
+- if (!this.membershipListeners.contains(membershipListener) )
+- this.membershipListeners.add(membershipListener);
+- }
+-
+- /**
+- * Removes a membership listener from the channel.<br>
+- * Membership listeners are uniquely identified using the equals(Object) method
+- * @param membershipListener MembershipListener
+- */
+-
+- public void removeMembershipListener(MembershipListener membershipListener) {
+- membershipListeners.remove(membershipListener);
+- }
+-
+- /**
+- * Adds a channel listener to the channel.<br>
+- * Channel listeners are uniquely identified using the equals(Object) method
+- * @param channelListener ChannelListener
+- */
+- public void addChannelListener(ChannelListener channelListener) {
+- if (!this.channelListeners.contains(channelListener) ) {
+- this.channelListeners.add(channelListener);
+- } else {
+- throw new IllegalArgumentException("Listener already exists:"+channelListener+"["+channelListener.getClass().getName()+"]");
+- }
+- }
+-
+- /**
+- *
+- * Removes a channel listener from the channel.<br>
+- * Channel listeners are uniquely identified using the equals(Object) method
+- * @param channelListener ChannelListener
+- */
+- public void removeChannelListener(ChannelListener channelListener) {
+- channelListeners.remove(channelListener);
+- }
+-
+- /**
+- * Returns an iterator of all the interceptors in this stack
+- * @return Iterator
+- */
+- public Iterator getInterceptors() {
+- return new InterceptorIterator(this.getNext(),this.coordinator);
+- }
+-
+- /**
+- * Enables/disables the option check<br>
+- * Setting this to true, will make the GroupChannel perform a conflict check
+- * on the interceptors. If two interceptors are using the same option flag
+- * and throw an error upon start.
+- * @param optionCheck boolean
+- */
+- public void setOptionCheck(boolean optionCheck) {
+- this.optionCheck = optionCheck;
+- }
+-
+- /**
+- * Configure local heartbeat sleep time<br>
+- * Only used when <code>getHeartbeat()==true</code>
+- * @param heartbeatSleeptime long - time in milliseconds to sleep between heartbeats
+- */
+- public void setHeartbeatSleeptime(long heartbeatSleeptime) {
+- this.heartbeatSleeptime = heartbeatSleeptime;
+- }
+-
+- /**
+- * Enables or disables local heartbeat.
+- * if <code>setHeartbeat(true)</code> is invoked then the channel will start an internal
+- * thread to invoke <code>Channel.heartbeat()</code> every <code>getHeartbeatSleeptime</code> milliseconds
+- * @param heartbeat boolean
+- */
+- public void setHeartbeat(boolean heartbeat) {
+- this.heartbeat = heartbeat;
+- }
+-
+- /**
+- * @see #setOptionCheck(boolean)
+- * @return boolean
+- */
+- public boolean getOptionCheck() {
+- return optionCheck;
+- }
+-
+- /**
+- * @see #setHeartbeat(boolean)
+- * @return boolean
+- */
+- public boolean getHeartbeat() {
+- return heartbeat;
+- }
+-
+- /**
+- * Returns the sleep time in milliseconds that the internal heartbeat will
+- * sleep in between invokations of <code>Channel.heartbeat()</code>
+- * @return long
+- */
+- public long getHeartbeatSleeptime() {
+- return heartbeatSleeptime;
+- }
+-
+- /**
+- *
+- * <p>Title: Interceptor Iterator</p>
+- *
+- * <p>Description: An iterator to loop through the interceptors in a channel</p>
+- *
+- * @version 1.0
+- */
+- public static class InterceptorIterator implements Iterator {
+- private ChannelInterceptor end;
+- private ChannelInterceptor start;
+- public InterceptorIterator(ChannelInterceptor start, ChannelInterceptor end) {
+- this.end = end;
+- this.start = start;
+- }
+-
+- public boolean hasNext() {
+- return start!=null && start != end;
+- }
+-
+- public Object next() {
+- Object result = null;
+- if ( hasNext() ) {
+- result = start;
+- start = start.getNext();
+- }
+- return result;
+- }
+-
+- public void remove() {
+- //empty operation
+- }
+- }
+-
+- /**
+- *
+- * <p>Title: Internal heartbeat thread</p>
+- *
+- * <p>Description: if <code>Channel.getHeartbeat()==true</code> then a thread of this class
+- * is created</p>
+- *
+- * @version 1.0
+- */
+- public static class HeartbeatThread extends Thread {
+- protected static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(HeartbeatThread.class);
+- protected static int counter = 1;
+- protected static synchronized int inc() {
+- return counter++;
+- }
+-
+- protected boolean doRun = true;
+- protected GroupChannel channel;
+- protected 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();
+- }
+-
+- public void run() {
+- while (doRun) {
+- try {
+- Thread.sleep(sleepTime);
+- channel.heartbeat();
+- } catch ( InterruptedException x ) {
+- interrupted();
+- } catch ( Exception x ) {
+- log.error("Unable to send heartbeat through Tribes interceptor stack. Will try to sleep again.",x);
+- }//catch
+- }//while
+- }//run
+- }//HeartbeatThread
+-
+-
+-
+-}
+Index: java/org/apache/catalina/tribes/group/InterceptorPayload.java
+===================================================================
+--- java/org/apache/catalina/tribes/group/InterceptorPayload.java (revision 590752)
++++ java/org/apache/catalina/tribes/group/InterceptorPayload.java (working copy)
+@@ -1,35 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.group;
+-
+-import org.apache.catalina.tribes.ErrorHandler;
+-
+-/**
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public class InterceptorPayload {
+- private ErrorHandler errorHandler;
+-
+- public ErrorHandler getErrorHandler() {
+- return errorHandler;
+- }
+-
+- public void setErrorHandler(ErrorHandler errorHandler) {
+- this.errorHandler = errorHandler;
+- }
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/group/RpcMessage.java
+===================================================================
+--- java/org/apache/catalina/tribes/group/RpcMessage.java (revision 590752)
++++ java/org/apache/catalina/tribes/group/RpcMessage.java (working copy)
+@@ -1,115 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.group;
+-
+-import java.io.ObjectInput;
+-import java.io.Serializable;
+-import java.io.Externalizable;
+-import java.io.IOException;
+-import java.io.ObjectOutput;
+-import org.apache.catalina.tribes.util.Arrays;
+-
+-/**
+- * <p>Title: </p>
+- *
+- * <p>Description: </p>
+- *
+- * <p>Company: </p>
+- *
+- * @author not attributable
+- * @version 1.0
+- */
+-public class RpcMessage implements Externalizable {
+-
+- protected Serializable message;
+- protected byte[] uuid;
+- protected byte[] rpcId;
+- protected boolean reply = false;
+-
+- public RpcMessage() {
+- //for serialization
+- }
+-
+- public RpcMessage(byte[] rpcId, byte[] uuid, Serializable message) {
+- this.rpcId = rpcId;
+- this.uuid = uuid;
+- this.message = message;
+- }
+-
+- public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {
+- reply = in.readBoolean();
+- int length = in.readInt();
+- uuid = new byte[length];
+- in.read(uuid, 0, length);
+- length = in.readInt();
+- rpcId = new byte[length];
+- in.read(rpcId, 0, length);
+- message = (Serializable)in.readObject();
+- }
+-
+- public void writeExternal(ObjectOutput out) throws IOException {
+- out.writeBoolean(reply);
+- out.writeInt(uuid.length);
+- out.write(uuid, 0, uuid.length);
+- out.writeInt(rpcId.length);
+- out.write(rpcId, 0, rpcId.length);
+- out.writeObject(message);
+- }
+-
+- public String toString() {
+- StringBuffer buf = new StringBuffer("RpcMessage[");
+- buf.append(super.toString());
+- buf.append("] rpcId=");
+- buf.append(Arrays.toString(rpcId));
+- buf.append("; uuid=");
+- buf.append(Arrays.toString(uuid));
+- buf.append("; msg=");
+- buf.append(message);
+- return buf.toString();
+- }
+-
+- public static class NoRpcChannelReply extends RpcMessage {
+- public NoRpcChannelReply() {
+-
+- }
+-
+- public NoRpcChannelReply(byte[] rpcid, byte[] uuid) {
+- super(rpcid,uuid,null);
+- reply = true;
+- }
+-
+- public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+- reply = true;
+- int length = in.readInt();
+- uuid = new byte[length];
+- in.read(uuid, 0, length);
+- length = in.readInt();
+- rpcId = new byte[length];
+- in.read(rpcId, 0, length);
+- }
+-
+- public void writeExternal(ObjectOutput out) throws IOException {
+- out.writeInt(uuid.length);
+- out.write(uuid, 0, uuid.length);
+- out.writeInt(rpcId.length);
+- out.write(rpcId, 0, rpcId.length);
+- }
+- }
+-
+-
+-}
+Index: java/org/apache/catalina/tribes/group/AbsoluteOrder.java
+===================================================================
+--- java/org/apache/catalina/tribes/group/AbsoluteOrder.java (revision 590752)
++++ java/org/apache/catalina/tribes/group/AbsoluteOrder.java (working copy)
+@@ -1,116 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.group;
+-
+-import org.apache.catalina.tribes.Member;
+-import java.util.Comparator;
+-import java.util.Arrays;
+-
+-/**
+- * <p>Title: Membership - Absolute Order</p>
+- *
+- * <p>Description: A simple, yet agreeable and efficient way of ordering members</p>
+- * <p>
+- * Ordering members can serve as a basis for electing a leader or coordinating efforts.<br>
+- * This is stinky simple, it works on the basis of the <code>Member</code> interface
+- * and orders members in the following format:
+- *
+- * <ol>
+- * <li>IP comparison - byte by byte, lower byte higher rank</li>
+- * <li>IPv4 addresses rank higher than IPv6, ie the lesser number of bytes, the higher rank</li>
+- * <li>Port comparison - lower port, higher rank</li>
+- * <li>UniqueId comparison- byte by byte, lower byte higher rank</li>
+- * </ol>
+- *
+- * </p>
+- *
+- * @author Filip Hanik
+- * @version 1.0
+- * @see org.apache.catalina.tribes.Member
+- */
+-public class AbsoluteOrder {
+- public static final AbsoluteComparator comp = new AbsoluteComparator();
+-
+- protected AbsoluteOrder() {
+- super();
+- }
+-
+-
+-
+- public static void absoluteOrder(Member[] members) {
+- if ( members == null || members.length == 0 ) return;
+- Arrays.sort(members,comp);
+- }
+-
+-
+- public static class AbsoluteComparator implements Comparator {
+- public int compare(Object o1, Object o2) {
+- if ( !((o1 instanceof Member) && (o2 instanceof Member)) ) return 0;
+- return compareMembers((Member)o1,(Member)o2);
+- }
+-
+- public int compareMembers(Member m1, Member m2) {
+- int result = compareIps(m1,m2);
+- if ( result == 0 ) result = comparePorts(m1,m2);
+- if ( result == 0 ) result = compareIds(m1,m2);
+- return result;
+- }
+-
+- public int compareIps(Member m1, Member m2) {
+- return compareBytes(m1.getHost(),m2.getHost());
+- }
+-
+- public int comparePorts(Member m1, Member m2) {
+- return compareInts(m1.getPort(),m2.getPort());
+- }
+-
+- public int compareIds(Member m1, Member m2) {
+- return compareBytes(m1.getUniqueId(),m2.getUniqueId());
+- }
+-
+- protected int compareBytes(byte[] d1, byte[] d2) {
+- int result = 0;
+- if ( d1.length == d2.length ) {
+- for (int i=0; (result==0) && (i<d1.length); i++) {
+- result = compareBytes(d1[i],d2[i]);
+- }
+- } else if ( d1.length < d2.length) {
+- result = -1;
+- } else {
+- result = 1;
+- }
+- return result;
+- }
+-
+- protected int compareBytes(byte b1, byte b2) {
+- return compareInts((int)b1,(int)b2);
+- }
+-
+- protected int compareInts(int b1, int b2) {
+- int result = 0;
+- if ( b1 == b2 ) {
+-
+- } else if ( b1 < b2) {
+- result = -1;
+- } else {
+- result = 1;
+- }
+- return result;
+- }
+- }
+-
+-}
+Index: java/org/apache/catalina/tribes/group/ChannelCoordinator.java
+===================================================================
+--- java/org/apache/catalina/tribes/group/ChannelCoordinator.java (revision 590752)
++++ java/org/apache/catalina/tribes/group/ChannelCoordinator.java (working copy)
+@@ -1,316 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.group;
+-
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.ChannelReceiver;
+-import org.apache.catalina.tribes.ChannelSender;
+-
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.MembershipService;
+-import org.apache.catalina.tribes.MessageListener;
+-import org.apache.catalina.tribes.transport.SenderState;
+-import org.apache.catalina.tribes.transport.ReplicationTransmitter;
+-import org.apache.catalina.tribes.membership.McastService;
+-import org.apache.catalina.tribes.transport.nio.NioReceiver;
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.util.Logs;
+-import org.apache.catalina.tribes.UniqueId;
+-import org.apache.catalina.tribes.util.Arrays;
+-
+-
+-/**
+- * The channel coordinator object coordinates the membership service,
+- * the sender and the receiver.
+- * This is the last interceptor in the chain.
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-public class ChannelCoordinator extends ChannelInterceptorBase implements MessageListener {
+- private ChannelReceiver clusterReceiver = new NioReceiver();
+- private ChannelSender clusterSender = new ReplicationTransmitter();
+- private MembershipService membershipService = new McastService();
+-
+- //override optionflag
+- protected int optionFlag = Channel.SEND_OPTIONS_BYTE_MESSAGE|Channel.SEND_OPTIONS_USE_ACK|Channel.SEND_OPTIONS_SYNCHRONIZED_ACK;
+- public int getOptionFlag() {return optionFlag;}
+- public void setOptionFlag(int flag) {optionFlag=flag;}
+-
+- private int startLevel = 0;
+-
+- public ChannelCoordinator() {
+-
+- }
+-
+- public ChannelCoordinator(ChannelReceiver receiver,
+- ChannelSender sender,
+- MembershipService service) {
+- this();
+- this.setClusterReceiver(receiver);
+- this.setClusterSender(sender);
+- this.setMembershipService(service);
+- }
+-
+- /**
+- * Send a message to one or more members in the cluster
+- * @param destination Member[] - the destinations, null or zero length means all
+- * @param msg ClusterMessage - the message to send
+- * @param options int - sender options, see class documentation
+- * @return ClusterMessage[] - the replies from the members, if any.
+- */
+- public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException {
+- if ( destination == null ) destination = membershipService.getMembers();
+- clusterSender.sendMessage(msg,destination);
+- if ( Logs.MESSAGES.isTraceEnabled() ) {
+- Logs.MESSAGES.trace("ChannelCoordinator - Sent msg:" + new UniqueId(msg.getUniqueId()) + " at " +new java.sql.Timestamp(System.currentTimeMillis())+ " to "+Arrays.toNameString(destination));
+- }
+- }
+-
+-
+- /**
+- * Starts up the channel. This can be called multiple times for individual services to start
+- * The svc parameter can be the logical or value of any constants
+- * @param svc int value of <BR>
+- * DEFAULT - will start all services <BR>
+- * MBR_RX_SEQ - starts the membership receiver <BR>
+- * MBR_TX_SEQ - starts the membership broadcaster <BR>
+- * SND_TX_SEQ - starts the replication transmitter<BR>
+- * SND_RX_SEQ - starts the replication receiver<BR>
+- * @throws ChannelException if a startup error occurs or the service is already started.
+- */
+- public void start(int svc) throws ChannelException {
+- this.internalStart(svc);
+- }
+-
+- /**
+- * Shuts down the channel. This can be called multiple times for individual services to shutdown
+- * The svc parameter can be the logical or value of any constants
+- * @param svc int value of <BR>
+- * DEFAULT - will shutdown all services <BR>
+- * MBR_RX_SEQ - stops the membership receiver <BR>
+- * MBR_TX_SEQ - stops the membership broadcaster <BR>
+- * SND_TX_SEQ - stops the replication transmitter<BR>
+- * SND_RX_SEQ - stops the replication receiver<BR>
+- * @throws ChannelException if a startup error occurs or the service is already started.
+- */
+- public void stop(int svc) throws ChannelException {
+- this.internalStop(svc);
+- }
+-
+-
+- /**
+- * Starts up the channel. This can be called multiple times for individual services to start
+- * The svc parameter can be the logical or value of any constants
+- * @param svc int value of <BR>
+- * DEFAULT - will start all services <BR>
+- * MBR_RX_SEQ - starts the membership receiver <BR>
+- * MBR_TX_SEQ - starts the membership broadcaster <BR>
+- * SND_TX_SEQ - starts the replication transmitter<BR>
+- * SND_RX_SEQ - starts the replication receiver<BR>
+- * @throws ChannelException if a startup error occurs or the service is already started.
+- */
+- protected synchronized void internalStart(int svc) throws ChannelException {
+- try {
+- boolean valid = false;
+- //make sure we don't pass down any flags that are unrelated to the bottom layer
+- svc = svc & Channel.DEFAULT;
+-
+- if (startLevel == Channel.DEFAULT) return; //we have already started up all components
+- if (svc == 0 ) return;//nothing to start
+-
+- if (svc == (svc & startLevel)) throw new ChannelException("Channel already started for level:"+svc);
+-
+- //must start the receiver first so that we can coordinate the port it
+- //listens to with the local membership settings
+- if ( Channel.SND_RX_SEQ==(svc & Channel.SND_RX_SEQ) ) {
+- clusterReceiver.setMessageListener(this);
+- clusterReceiver.start();
+- //synchronize, big time FIXME
+- membershipService.setLocalMemberProperties(getClusterReceiver().getHost(), getClusterReceiver().getPort());
+- valid = true;
+- }
+- if ( Channel.SND_TX_SEQ==(svc & Channel.SND_TX_SEQ) ) {
+- clusterSender.start();
+- valid = true;
+- }
+-
+- if ( Channel.MBR_RX_SEQ==(svc & Channel.MBR_RX_SEQ) ) {
+- membershipService.setMembershipListener(this);
+- membershipService.start(MembershipService.MBR_RX);
+- valid = true;
+- }
+- if ( Channel.MBR_TX_SEQ==(svc & Channel.MBR_TX_SEQ) ) {
+- membershipService.start(MembershipService.MBR_TX);
+- valid = true;
+- }
+-
+- if ( !valid) {
+- throw new IllegalArgumentException("Invalid start level, valid levels are:SND_RX_SEQ,SND_TX_SEQ,MBR_TX_SEQ,MBR_RX_SEQ");
+- }
+- startLevel = (startLevel | svc);
+- }catch ( ChannelException cx ) {
+- throw cx;
+- }catch ( Exception x ) {
+- throw new ChannelException(x);
+- }
+- }
+-
+- /**
+- * Shuts down the channel. This can be called multiple times for individual services to shutdown
+- * The svc parameter can be the logical or value of any constants
+- * @param svc int value of <BR>
+- * DEFAULT - will shutdown all services <BR>
+- * MBR_RX_SEQ - starts the membership receiver <BR>
+- * MBR_TX_SEQ - starts the membership broadcaster <BR>
+- * SND_TX_SEQ - starts the replication transmitter<BR>
+- * SND_RX_SEQ - starts the replication receiver<BR>
+- * @throws ChannelException if a startup error occurs or the service is already started.
+- */
+- protected synchronized void internalStop(int svc) throws ChannelException {
+- try {
+- //make sure we don't pass down any flags that are unrelated to the bottom layer
+- svc = svc & Channel.DEFAULT;
+-
+- if (startLevel == 0) return; //we have already stopped up all components
+- if (svc == 0 ) return;//nothing to stop
+-
+- boolean valid = false;
+- if ( Channel.SND_RX_SEQ==(svc & Channel.SND_RX_SEQ) ) {
+- clusterReceiver.stop();
+- clusterReceiver.setMessageListener(null);
+- valid = true;
+- }
+- if ( Channel.SND_TX_SEQ==(svc & Channel.SND_TX_SEQ) ) {
+- clusterSender.stop();
+- valid = true;
+- }
+-
+- if ( Channel.MBR_RX_SEQ==(svc & Channel.MBR_RX_SEQ) ) {
+- membershipService.stop(MembershipService.MBR_RX);
+- membershipService.setMembershipListener(null);
+- valid = true;
+-
+- }
+- if ( Channel.MBR_TX_SEQ==(svc & Channel.MBR_TX_SEQ) ) {
+- valid = true;
+- membershipService.stop(MembershipService.MBR_TX);
+- }
+- if ( !valid) {
+- throw new IllegalArgumentException("Invalid start level, valid levels are:SND_RX_SEQ,SND_TX_SEQ,MBR_TX_SEQ,MBR_RX_SEQ");
+- }
+-
+- startLevel = (startLevel & (~svc));
+-
+- }catch ( Exception x ) {
+- throw new ChannelException(x);
+- } finally {
+-
+- }
+-
+- }
+-
+- public void memberAdded(Member member){
+- SenderState.getSenderState(member);
+- super.memberAdded(member);
+- }
+-
+- public void memberDisappeared(Member member){
+- SenderState.removeSenderState(member);
+- super.memberDisappeared(member);
+- }
+-
+- public void messageReceived(ChannelMessage msg) {
+- if ( Logs.MESSAGES.isTraceEnabled() ) {
+- Logs.MESSAGES.trace("ChannelCoordinator - Received msg:" + new UniqueId(msg.getUniqueId()) + " at " +new java.sql.Timestamp(System.currentTimeMillis())+ " from "+msg.getAddress().getName());
+- }
+- super.messageReceived(msg);
+- }
+-
+-
+- public ChannelReceiver getClusterReceiver() {
+- return clusterReceiver;
+- }
+-
+- public ChannelSender getClusterSender() {
+- return clusterSender;
+- }
+-
+- public MembershipService getMembershipService() {
+- return membershipService;
+- }
+-
+- public void setClusterReceiver(ChannelReceiver clusterReceiver) {
+- if ( clusterReceiver != null ) {
+- this.clusterReceiver = clusterReceiver;
+- this.clusterReceiver.setMessageListener(this);
+- } else {
+- if (this.clusterReceiver!=null ) this.clusterReceiver.setMessageListener(null);
+- this.clusterReceiver = null;
+- }
+- }
+-
+- public void setClusterSender(ChannelSender clusterSender) {
+- this.clusterSender = clusterSender;
+- }
+-
+- public void setMembershipService(MembershipService membershipService) {
+- this.membershipService = membershipService;
+- this.membershipService.setMembershipListener(this);
+- }
+-
+- public void heartbeat() {
+- if ( clusterSender!=null ) clusterSender.heartbeat();
+- super.heartbeat();
+- }
+-
+- /**
+- * has members
+- */
+- public boolean hasMembers() {
+- return this.getMembershipService().hasMembers();
+- }
+-
+- /**
+- * Get all current cluster members
+- * @return all members or empty array
+- */
+- public Member[] getMembers() {
+- return this.getMembershipService().getMembers();
+- }
+-
+- /**
+- *
+- * @param mbr Member
+- * @return Member
+- */
+- public Member getMember(Member mbr){
+- return this.getMembershipService().getMember(mbr);
+- }
+-
+-
+- /**
+- * Return the member that represents this node.
+- *
+- * @return Member
+- */
+- public Member getLocalMember(boolean incAlive) {
+- return this.getMembershipService().getLocalMember(incAlive);
+- }
+-
+-
+-}
+Index: java/org/apache/catalina/tribes/group/RpcChannel.java
+===================================================================
+--- java/org/apache/catalina/tribes/group/RpcChannel.java (revision 590752)
++++ java/org/apache/catalina/tribes/group/RpcChannel.java (working copy)
+@@ -1,262 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.group;
+-
+-import java.io.Serializable;
+-import java.util.ArrayList;
+-import java.util.Arrays;
+-import java.util.HashMap;
+-
+-import org.apache.catalina.tribes.Channel;
+-import org.apache.catalina.tribes.ChannelException;
+-import org.apache.catalina.tribes.ChannelListener;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.util.UUIDGenerator;
+-
+-/**
+- * A channel to handle RPC messaging
+- * @author Filip Hanik
+- */
+-public class RpcChannel implements ChannelListener{
+- protected static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(RpcChannel.class);
+-
+- public static final int FIRST_REPLY = 1;
+- public static final int MAJORITY_REPLY = 2;
+- public static final int ALL_REPLY = 3;
+- public static final int NO_REPLY = 4;
+-
+- private Channel channel;
+- private RpcCallback callback;
+- private byte[] rpcId;
+-
+- private HashMap responseMap = new HashMap();
+-
+- /**
+- * Create an RPC channel. You can have several RPC channels attached to a group
+- * all separated out by the uniqueness
+- * @param rpcId - the unique Id for this RPC group
+- * @param channel Channel
+- * @param callback RpcCallback
+- */
+- public RpcChannel(byte[] rpcId, Channel channel, RpcCallback callback) {
+- this.channel = channel;
+- this.callback = callback;
+- this.rpcId = rpcId;
+- channel.addChannelListener(this);
+- }
+-
+-
+- /**
+- * Send a message and wait for the response.
+- * @param destination Member[] - the destination for the message, and the members you request a reply from
+- * @param message Serializable - the message you are sending out
+- * @param options int - FIRST_REPLY, MAJORITY_REPLY or ALL_REPLY
+- * @param timeout long - timeout in milliseconds, if no reply is received within this time null is returned
+- * @return Response[] - an array of response objects.
+- * @throws ChannelException
+- */
+- public Response[] send(Member[] destination,
+- Serializable message,
+- int rpcOptions,
+- int channelOptions,
+- long timeout) throws ChannelException {
+-
+- if ( destination==null || destination.length == 0 ) return new Response[0];
+-
+- //avoid dead lock
+- channelOptions = channelOptions & ~Channel.SEND_OPTIONS_SYNCHRONIZED_ACK;
+-
+- RpcCollectorKey key = new RpcCollectorKey(UUIDGenerator.randomUUID(false));
+- RpcCollector collector = new RpcCollector(key,rpcOptions,destination.length,timeout);
+- try {
+- synchronized (collector) {
+- if ( rpcOptions != NO_REPLY ) responseMap.put(key, collector);
+- RpcMessage rmsg = new RpcMessage(rpcId, key.id, message);
+- channel.send(destination, rmsg, channelOptions);
+- if ( rpcOptions != NO_REPLY ) collector.wait(timeout);
+- }
+- } catch ( InterruptedException ix ) {
+- Thread.currentThread().interrupted();
+- //throw new ChannelException(ix);
+- }finally {
+- responseMap.remove(key);
+- }
+- return collector.getResponses();
+- }
+-
+- public void messageReceived(Serializable msg, Member sender) {
+- RpcMessage rmsg = (RpcMessage)msg;
+- RpcCollectorKey key = new RpcCollectorKey(rmsg.uuid);
+- if ( rmsg.reply ) {
+- RpcCollector collector = (RpcCollector)responseMap.get(key);
+- if (collector == null) {
+- callback.leftOver(rmsg.message, sender);
+- } else {
+- synchronized (collector) {
+- //make sure it hasn't been removed
+- if ( responseMap.containsKey(key) ) {
+- if ( (rmsg instanceof RpcMessage.NoRpcChannelReply) )
+- collector.destcnt--;
+- else
+- collector.addResponse(rmsg.message, sender);
+- if (collector.isComplete()) collector.notifyAll();
+- } else {
+- if (! (rmsg instanceof RpcMessage.NoRpcChannelReply) )
+- callback.leftOver(rmsg.message, sender);
+- }
+- }//synchronized
+- }//end if
+- } else{
+- Serializable reply = callback.replyRequest(rmsg.message,sender);
+- rmsg.reply = true;
+- rmsg.message = reply;
+- try {
+- channel.send(new Member[] {sender}, rmsg,0);
+- }catch ( Exception x ) {
+- log.error("Unable to send back reply in RpcChannel.",x);
+- }
+- }//end if
+- }
+-
+- public void breakdown() {
+- channel.removeChannelListener(this);
+- }
+-
+- public void finalize() {
+- breakdown();
+- }
+-
+- public boolean accept(Serializable msg, Member sender) {
+- if ( msg instanceof RpcMessage ) {
+- RpcMessage rmsg = (RpcMessage)msg;
+- return Arrays.equals(rmsg.rpcId,rpcId);
+- }else return false;
+- }
+-
+- public Channel getChannel() {
+- return channel;
+- }
+-
+- public RpcCallback getCallback() {
+- return callback;
+- }
+-
+- public byte[] getRpcId() {
+- return rpcId;
+- }
+-
+- public void setChannel(Channel channel) {
+- this.channel = channel;
+- }
+-
+- public void setCallback(RpcCallback callback) {
+- this.callback = callback;
+- }
+-
+- public void setRpcId(byte[] rpcId) {
+- this.rpcId = rpcId;
+- }
+-
+-
+-
+- /**
+- *
+- * Class that holds all response.
+- * @author not attributable
+- * @version 1.0
+- */
+- public static class RpcCollector {
+- public ArrayList responses = new ArrayList();
+- public RpcCollectorKey key;
+- public int options;
+- public int destcnt;
+- public long timeout;
+-
+- public RpcCollector(RpcCollectorKey key, int options, int destcnt, long timeout) {
+- this.key = key;
+- this.options = options;
+- this.destcnt = destcnt;
+- this.timeout = timeout;
+- }
+-
+- public void addResponse(Serializable message, Member sender){
+- Response resp = new Response(sender,message);
+- responses.add(resp);
+- }
+-
+- public boolean isComplete() {
+- if ( destcnt <= 0 ) return true;
+- switch (options) {
+- case ALL_REPLY:
+- return destcnt == responses.size();
+- case MAJORITY_REPLY:
+- {
+- float perc = ((float)responses.size()) / ((float)destcnt);
+- return perc >= 0.50f;
+- }
+- case FIRST_REPLY:
+- return responses.size()>0;
+- default:
+- return false;
+- }
+- }
+-
+- public int hashCode() {
+- return key.hashCode();
+- }
+-
+- public boolean equals(Object o) {
+- if ( o instanceof RpcCollector ) {
+- RpcCollector r = (RpcCollector)o;
+- return r.key.equals(this.key);
+- } else return false;
+- }
+-
+- public Response[] getResponses() {
+- return (Response[])responses.toArray(new Response[responses.size()]);
+- }
+- }
+-
+- public static class RpcCollectorKey {
+- byte[] id;
+- public RpcCollectorKey(byte[] id) {
+- this.id = id;
+- }
+-
+- public int hashCode() {
+- return id[0]+id[1]+id[2]+id[3];
+- }
+-
+- public boolean equals(Object o) {
+- if ( o instanceof RpcCollectorKey ) {
+- RpcCollectorKey r = (RpcCollectorKey)o;
+- return Arrays.equals(id,r.id);
+- } else return false;
+- }
+-
+- }
+-
+- protected static String bToS(byte[] data) {
+- StringBuffer buf = new StringBuffer(4*16);
+- buf.append("{");
+- for (int i=0; data!=null && i<data.length; i++ ) buf.append(String.valueOf(data[i])).append(" ");
+- buf.append("}");
+- return buf.toString();
+- }
+-
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/group/Response.java
+===================================================================
+--- java/org/apache/catalina/tribes/group/Response.java (revision 590752)
++++ java/org/apache/catalina/tribes/group/Response.java (working copy)
+@@ -1,54 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.group;
+-
+-import java.io.Serializable;
+-
+-import org.apache.catalina.tribes.Member;
+-
+-/**
+- * A response object holds a message from a responding partner.
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public class Response {
+- private Member source;
+- private Serializable message;
+- public Response() {
+- }
+-
+- public Response(Member source, Serializable message) {
+- this.source = source;
+- this.message = message;
+- }
+-
+- public void setSource(Member source) {
+- this.source = source;
+- }
+-
+- public void setMessage(Serializable message) {
+- this.message = message;
+- }
+-
+- public Member getSource() {
+- return source;
+- }
+-
+- public Serializable getMessage() {
+- return message;
+- }
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/ChannelMessage.java
+===================================================================
+--- java/org/apache/catalina/tribes/ChannelMessage.java (revision 590752)
++++ java/org/apache/catalina/tribes/ChannelMessage.java (working copy)
+@@ -1,109 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes;
+-
+-import java.io.Serializable;
+-import org.apache.catalina.tribes.io.XByteBuffer;
+-
+-/**
+- * Message that is passed through the interceptor stack after the
+- * data serialized in the Channel object and then passed down to the
+- * interceptor and eventually down to the ChannelSender component
+- * @author Filip Hanik
+- *
+- */
+-public interface ChannelMessage extends Serializable {
+-
+-
+-
+-
+- /**
+- * Get the address that this message originated from.
+- * Almost always <code>Channel.getLocalMember(boolean)</code><br>
+- * This would be set to a different address
+- * if the message was being relayed from a host other than the one
+- * that originally sent it.
+- * @return the source or reply-to address of this message
+- */
+- public Member getAddress();
+-
+- /**
+- * Sets the source or reply-to address of this message
+- * @param member Member
+- */
+- public void setAddress(Member member);
+-
+- /**
+- * Timestamp of when the message was created.
+- * @return long timestamp in milliseconds
+- */
+- public long getTimestamp();
+-
+- /**
+- *
+- * Sets the timestamp of this message
+- * @param timestamp The timestamp
+- */
+- public void setTimestamp(long timestamp);
+-
+- /**
+- * Each message must have a globally unique Id.
+- * interceptors heavily depend on this id for message processing
+- * @return byte
+- */
+- public byte[] getUniqueId();
+-
+- /**
+- * The byte buffer that contains the actual message payload
+- * @param buf XByteBuffer
+- */
+- public void setMessage(XByteBuffer buf);
+-
+- /**
+- * returns the byte buffer that contains the actual message payload
+- * @return XByteBuffer
+- */
+- public XByteBuffer getMessage();
+-
+- /**
+- * The message options is a 32 bit flag set
+- * that triggers interceptors and message behavior.
+- * @see Channel#send(Member[], Serializable, int)
+- * @see ChannelInterceptor#getOptionFlag
+- * @return int - the option bits set for this message
+- */
+- public int getOptions();
+-
+- /**
+- * sets the option bits for this message
+- * @param options int
+- * @see #getOptions()
+- */
+- public void setOptions(int options);
+-
+- /**
+- * Shallow clone, what gets cloned depends on the implementation
+- * @return ChannelMessage
+- */
+- public Object clone();
+-
+- /**
+- * Deep clone, all fields MUST get cloned
+- * @return ChannelMessage
+- */
+- public Object deepclone();
+-}
+Index: java/org/apache/catalina/tribes/ChannelListener.java
+===================================================================
+--- java/org/apache/catalina/tribes/ChannelListener.java (revision 590752)
++++ java/org/apache/catalina/tribes/ChannelListener.java (working copy)
+@@ -1,68 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes;
+-
+-import java.io.Serializable;
+-/**
+- *
+- * <p>Title: ChannelListener</p>
+- *
+- * <p>Description: An interface to listens to incoming messages from a channel </p>
+- * When a message is received, the Channel will invoke the channel listener in a conditional sequence.
+- * <code>if ( listener.accept(msg,sender) ) listener.messageReceived(msg,sender);</code><br>
+- * A ChannelListener implementation MUST NOT return true on <code>accept(Serializable, Member)</code>
+- * if it doesn't intend to process the message. The channel can this way track whether a message
+- * was processed by an above application or if it was just received and forgot about, a featuer required
+- * to support message-response(RPC) calls<br>
+- *
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-
+-public interface ChannelListener {
+-
+- /**
+- * Receive a message from the channel
+- * @param msg Serializable
+- * @param sender - the source of the message
+- */
+- public void messageReceived(Serializable msg, Member sender);
+-
+- /**
+- * Invoked by the channel to determine if the listener will process this message or not.
+- * @param msg Serializable
+- * @param sender Member
+- * @return boolean
+- */
+- public boolean accept(Serializable msg, Member sender);
+-
+- /**
+- *
+- * @param listener Object
+- * @return boolean
+- * @see Object#equals(Object)
+- */
+- public boolean equals(Object listener);
+-
+- /**
+- *
+- * @return int
+- * @see Object#hashCode(int)
+- */
+- public int hashCode();
+-
+-}
+Index: java/org/apache/catalina/tribes/package.html
+===================================================================
+--- java/org/apache/catalina/tribes/package.html (revision 590752)
++++ java/org/apache/catalina/tribes/package.html (working copy)
+@@ -1,86 +0,0 @@
+-<!--
+- 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.
+--->
+-<body>
+-<head><title>Apache Tribes - The Tomcat Cluster Communication Module</title>
+-<h3>QuickStart</h3>
+- <pre><code>
+- //create a channel
+- Channel myChannel = new GroupChannel();
+-
+- //create my listeners
+- MyMessageListener msgListener = new MyMessageListener();
+- MyMemberListener mbrListener = new MyMemberListener();
+-
+- //attach the listeners to the channel
+- myChannel.addMembershipListener(mbrListener);
+- myChannel.addChannelListener(msgListener);
+-
+- //start the channel
+- myChannel.start(Channel.DEFAULT);
+-
+- //create a message to be sent, message must implement java.io.Serializable
+- //for performance reasons you probably want them to implement java.io.Externalizable
+- Serializable myMsg = new MyMessage();
+-
+- //retrieve my current members
+- Member[] group = myChannel.getMembers();
+-
+- //send the message
+- channel.send(group,myMsg,Channel.SEND_OPTIONS_DEFAULT);
+-
+- </code></pre>
+-<h3>Interfaces for the Application Developer</h3>
+- <ol>
+- <li><code>org.apache.catalina.tribes.Channel</code>
+- Main component to interact with to send messages
+- </li>
+- <li><code>org.apache.catalina.tribes.MembershipListener</code>
+- Listen to membership changes
+- </li>
+- <li><code>org.apache.catalina.tribes.ChannelListener</code>
+- Listen to data messages
+- </li>
+- <li><code>org.apache.catalina.tribes.Member</code>
+- Identifies a node, implementation specific, default is org.apache.catalina.tribes.membership.MemberImpl
+- </li>
+- </ol>
+- <h3>Interfaces for the Tribes Component Developer</h3>
+- <ol>
+- <li><code>org.apache.catalina.tribes.Channel</code>
+- Main component to that the application interacts with
+- </li>
+- <li><code>org.apache.catalina.tribes.ChannelReceiver</code>
+- IO Component to receive messages over some network transport
+- </li>
+- <li><code>org.apache.catalina.tribes.ChannelSender</code>
+- IO Component to send messages over some network transport
+- </li>
+- <li><code>org.apache.catalina.tribes.MembershipService</code>
+- IO Component that handles membership discovery and
+- </li>
+- <li><code>org.apache.catalina.tribes.ChannelInterceptor</code>
+- interceptors between the Channel and the IO layer
+- </li>
+- <li><code>org.apache.catalina.tribes.ChannelMessage</code>
+- The message that is sent through the interceptor stack down to the IO layer
+- </li>
+-
+- <li><code>org.apache.catalina.tribes.Member</code>
+- Identifies a node, implementation specific to the underlying IO logic
+- </li>
+- </ol>
+-</body>
+Index: java/org/apache/catalina/tribes/LocalStrings.properties
+===================================================================
+--- java/org/apache/catalina/tribes/LocalStrings.properties (revision 590752)
++++ java/org/apache/catalina/tribes/LocalStrings.properties (working copy)
+@@ -1,16 +0,0 @@
+-# 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.
+-
+-cluster.mbean.register.already=MBean {0} already registered!
+Index: java/org/apache/catalina/tribes/UniqueId.java
+===================================================================
+--- java/org/apache/catalina/tribes/UniqueId.java (revision 590752)
++++ java/org/apache/catalina/tribes/UniqueId.java (working copy)
+@@ -1,72 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes;
+-
+-import org.apache.catalina.tribes.util.Arrays;
+-import java.io.Serializable;
+-
+-/**
+- * <p>Title: Represents a globabally unique Id</p>
+- *
+- * <p>Company: </p>
+- *
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public final class UniqueId implements Serializable{
+- protected byte[] id;
+-
+- public UniqueId() {
+- }
+-
+- public UniqueId(byte[] id) {
+- this.id = id;
+- }
+-
+- public UniqueId(byte[] id, int offset, int length) {
+- this.id = new byte[length];
+- System.arraycopy(id,offset,this.id,0,length);
+- }
+-
+- public int hashCode() {
+- if ( id == null ) return 0;
+- return Arrays.hashCode(id);
+- }
+-
+- public boolean equals(Object other) {
+- boolean result = (other instanceof UniqueId);
+- if ( result ) {
+- UniqueId uid = (UniqueId)other;
+- if ( this.id == null && uid.id == null ) result = true;
+- else if ( this.id == null && uid.id != null ) result = false;
+- else if ( this.id != null && uid.id == null ) result = false;
+- else result = Arrays.equals(this.id,uid.id);
+- }//end if
+- return result;
+- }
+-
+- public byte[] getBytes() {
+- return id;
+- }
+-
+- public String toString() {
+- StringBuffer buf = new StringBuffer("UniqueId");
+- buf.append(org.apache.catalina.tribes.util.Arrays.toString(id));
+- return buf.toString();
+- }
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/mbeans-descriptors.xml
+===================================================================
+--- java/org/apache/catalina/tribes/mbeans-descriptors.xml (revision 590752)
++++ java/org/apache/catalina/tribes/mbeans-descriptors.xml (working copy)
+@@ -1,110 +0,0 @@
+-<?xml version="1.0"?>
+-<!--
+- 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.
+--->
+-<mbeans-descriptors>
+-
+- <mbean name="SimpleTcpCluster"
+- className="org.apache.catalina.mbeans.ClassNameMBean"
+- description="Tcp Cluster implementation"
+- domain="Catalina"
+- group="Cluster"
+- type="org.apache.catalina.ha.tcp.SimpleTcpCluster">
+-
+- <attribute name="protocolStack"
+- description="JavaGroups protocol stack selection"
+- type="java.lang.String"/>
+-
+- </mbean>
+-
+-
+- <mbean name="SimpleTcpReplicationManager"
+- className="org.apache.catalina.mbeans.ClassNameMBean"
+- description="Clustered implementation of the Manager interface"
+- domain="Catalina"
+- group="Manager"
+- type="org.apache.catalina.ha.tcp.SimpleTcpReplicationManager">
+-
+- <attribute name="algorithm"
+- description="The message digest algorithm to be used when generating
+- session identifiers"
+- type="java.lang.String"/>
+-
+- <attribute name="checkInterval"
+- description="The interval (in seconds) between checks for expired
+- sessions"
+- type="int"/>
+-
+- <attribute name="className"
+- description="Fully qualified class name of the managed object"
+- type="java.lang.String"
+- writeable="false"/>
+-
+- <attribute name="distributable"
+- description="The distributable flag for Sessions created by this
+- Manager"
+- type="boolean"/>
+-
+- <attribute name="entropy"
+- description="A String initialization parameter used to increase the
+- entropy of the initialization of our random number
+- generator"
+- type="java.lang.String"/>
+-
+- <attribute name="managedResource"
+- description="The managed resource this MBean is associated with"
+- type="java.lang.Object"/>
+-
+- <attribute name="maxActiveSessions"
+- description="The maximum number of active Sessions allowed, or -1
+- for no limit"
+- type="int"/>
+-
+- <attribute name="maxInactiveInterval"
+- description="The default maximum inactive interval for Sessions
+- created by this Manager"
+- type="int"/>
+-
+- <attribute name="name"
+- description="The descriptive name of this Manager implementation
+- (for logging)"
+- type="java.lang.String"
+- writeable="false"/>
+-
+- </mbean>
+-
+-
+-
+-<mbean name="ReplicationValve"
+- className="org.apache.catalina.mbeans.ClassNameMBean"
+- description="Valve for simple tcp replication"
+- domain="Catalina"
+- group="Valve"
+- type="org.apache.catalina.ha.tcp.ReplicationValve">
+-
+- <attribute name="className"
+- description="Fully qualified class name of the managed object"
+- type="java.lang.String"
+- writeable="false"/>
+-
+- <attribute name="debug"
+- description="The debugging detail level for this component"
+- type="int"/>
+-
+- </mbean>
+-
+-
+-</mbeans-descriptors>
+Index: java/org/apache/catalina/tribes/ManagedChannel.java
+===================================================================
+--- java/org/apache/catalina/tribes/ManagedChannel.java (revision 590752)
++++ java/org/apache/catalina/tribes/ManagedChannel.java (working copy)
+@@ -1,78 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes;
+-
+-import java.util.Iterator;
+-
+-/**
+- * Channel interface
+- * A managed channel interface gives you access to the components of the channels
+- * such as senders, receivers, interceptors etc for configurations purposes
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-public interface ManagedChannel extends Channel {
+-
+- /**
+- * Sets the channel sender
+- * @param sender ChannelSender
+- * @see ChannelSender
+- */
+- public void setChannelSender(ChannelSender sender);
+-
+- /**
+- * Sets the channel receiver
+- * @param receiver ChannelReceiver
+- * @see ChannelReceiver
+- */
+- public void setChannelReceiver(ChannelReceiver receiver);
+-
+- /**
+- * Sets the membership service
+- * @param service MembershipService
+- * @see MembershipService
+- */
+- public void setMembershipService(MembershipService service);
+-
+- /**
+- * returns the channel sender
+- * @return ChannelSender
+- * @see ChannelSender
+- */
+- public ChannelSender getChannelSender();
+-
+- /**
+- * returns the channel receiver
+- * @return ChannelReceiver
+- * @see ChannelReceiver
+- */
+- public ChannelReceiver getChannelReceiver();
+-
+- /**
+- * Returns the membership service
+- * @return MembershipService
+- * @see MembershipService
+- */
+- public MembershipService getMembershipService();
+-
+- /**
+- * Returns the interceptor stack
+- * @return Iterator
+- * @see Channel#addInterceptor(ChannelInterceptor)
+- */
+- public Iterator getInterceptors();
+-}
+Index: java/org/apache/catalina/tribes/io/ChannelData.java
+===================================================================
+--- java/org/apache/catalina/tribes/io/ChannelData.java (revision 590752)
++++ java/org/apache/catalina/tribes/io/ChannelData.java (working copy)
+@@ -1,359 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.io;
+-
+-import java.util.Arrays;
+-
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.membership.MemberImpl;
+-import org.apache.catalina.tribes.util.UUIDGenerator;
+-import org.apache.catalina.tribes.Channel;
+-import java.sql.Timestamp;
+-
+-/**
+- * The <code>ChannelData</code> object is used to transfer a message through the
+- * channel interceptor stack and eventually out on a transport to be sent
+- * to another node. While the message is being processed by the different
+- * interceptors, the message data can be manipulated as each interceptor seems appropriate.
+- * @author Peter Rossbach
+- * @author Filip Hanik
+- * @version $Revision$ $Date$
+- *
+- */
+-public class ChannelData implements ChannelMessage {
+- public static ChannelData[] EMPTY_DATA_ARRAY = new ChannelData[0];
+-
+- public static boolean USE_SECURE_RANDOM_FOR_UUID = false;
+-
+- /**
+- * The options this message was sent with
+- */
+- private int options = 0 ;
+- /**
+- * The message data, stored in a dynamic buffer
+- */
+- private XByteBuffer message ;
+- /**
+- * The timestamp that goes with this message
+- */
+- private long timestamp ;
+- /**
+- * A unique message id
+- */
+- private byte[] uniqueId ;
+- /**
+- * The source or reply-to address for this message
+- */
+- private Member address;
+-
+- /**
+- * Creates an empty channel data with a new unique Id
+- * @see #ChannelData(boolean)
+- */
+- public ChannelData() {
+- this(true);
+- }
+-
+- /**
+- * Create an empty channel data object
+- * @param generateUUID boolean - if true, a unique Id will be generated
+- */
+- public ChannelData(boolean generateUUID) {
+- if ( generateUUID ) generateUUID();
+- }
+-
+-
+-
+- /**
+- * Creates a new channel data object with data
+- * @param uniqueId - unique message id
+- * @param message - message data
+- * @param timestamp - message timestamp
+- */
+- public ChannelData(byte[] uniqueId, XByteBuffer message, long timestamp) {
+- this.uniqueId = uniqueId;
+- this.message = message;
+- this.timestamp = timestamp;
+- }
+-
+- /**
+- * @return Returns the message byte buffer
+- */
+- public XByteBuffer getMessage() {
+- return message;
+- }
+- /**
+- * @param message The message to send.
+- */
+- public void setMessage(XByteBuffer message) {
+- this.message = message;
+- }
+- /**
+- * @return Returns the timestamp.
+- */
+- public long getTimestamp() {
+- return timestamp;
+- }
+- /**
+- * @param timestamp The timestamp to send
+- */
+- public void setTimestamp(long timestamp) {
+- this.timestamp = timestamp;
+- }
+- /**
+- * @return Returns the uniqueId.
+- */
+- public byte[] getUniqueId() {
+- return uniqueId;
+- }
+- /**
+- * @param uniqueId The uniqueId to send.
+- */
+- public void setUniqueId(byte[] uniqueId) {
+- this.uniqueId = uniqueId;
+- }
+- /**
+- * @return returns the message options
+- * see org.apache.catalina.tribes.Channel#sendMessage(org.apache.catalina.tribes.Member[], java.io.Serializable, int)
+- *
+- */
+- public int getOptions() {
+- return options;
+- }
+- /**
+- * @param sets the message options
+- */
+- public void setOptions(int options) {
+- this.options = options;
+- }
+-
+- /**
+- * Returns the source or reply-to address
+- * @return Member
+- */
+- public Member getAddress() {
+- return address;
+- }
+-
+- /**
+- * Sets the source or reply-to address
+- * @param address Member
+- */
+- public void setAddress(Member address) {
+- this.address = address;
+- }
+-
+- /**
+- * Generates a UUID and invokes setUniqueId
+- */
+- public void generateUUID() {
+- byte[] data = new byte[16];
+- UUIDGenerator.randomUUID(USE_SECURE_RANDOM_FOR_UUID,data,0);
+- setUniqueId(data);
+- }
+-
+- public int getDataPackageLength() {
+- int length =
+- 4 + //options
+- 8 + //timestamp off=4
+- 4 + //unique id length off=12
+- uniqueId.length+ //id data off=12+uniqueId.length
+- 4 + //addr length off=12+uniqueId.length+4
+- ((MemberImpl)address).getDataLength()+ //member data off=12+uniqueId.length+4+add.length
+- 4 + //message length off=12+uniqueId.length+4+add.length+4
+- message.getLength();
+- return length;
+-
+- }
+-
+- /**
+- * Serializes the ChannelData object into a byte[] array
+- * @return byte[]
+- */
+- public byte[] getDataPackage() {
+- int length = getDataPackageLength();
+- byte[] data = new byte[length];
+- int offset = 0;
+- return getDataPackage(data,offset);
+- }
+-
+- public byte[] getDataPackage(byte[] data, int offset) {
+- byte[] addr = ((MemberImpl)address).getData(false);
+- XByteBuffer.toBytes(options,data,offset);
+- offset += 4; //options
+- XByteBuffer.toBytes(timestamp,data,offset);
+- offset += 8; //timestamp
+- XByteBuffer.toBytes(uniqueId.length,data,offset);
+- offset += 4; //uniqueId.length
+- System.arraycopy(uniqueId,0,data,offset,uniqueId.length);
+- offset += uniqueId.length; //uniqueId data
+- XByteBuffer.toBytes(addr.length,data,offset);
+- offset += 4; //addr.length
+- System.arraycopy(addr,0,data,offset,addr.length);
+- offset += addr.length; //addr data
+- XByteBuffer.toBytes(message.getLength(),data,offset);
+- offset += 4; //message.length
+- System.arraycopy(message.getBytesDirect(),0,data,offset,message.getLength());
+- offset += message.getLength(); //message data
+- return data;
+- }
+-
+- /**
+- * Deserializes a ChannelData object from a byte array
+- * @param b byte[]
+- * @return ChannelData
+- */
+- public static ChannelData getDataFromPackage(XByteBuffer xbuf) {
+- ChannelData data = new ChannelData(false);
+- int offset = 0;
+- data.setOptions(XByteBuffer.toInt(xbuf.getBytesDirect(),offset));
+- offset += 4; //options
+- data.setTimestamp(XByteBuffer.toLong(xbuf.getBytesDirect(),offset));
+- offset += 8; //timestamp
+- data.uniqueId = new byte[XByteBuffer.toInt(xbuf.getBytesDirect(),offset)];
+- offset += 4; //uniqueId length
+- System.arraycopy(xbuf.getBytesDirect(),offset,data.uniqueId,0,data.uniqueId.length);
+- offset += data.uniqueId.length; //uniqueId data
+- //byte[] addr = new byte[XByteBuffer.toInt(xbuf.getBytesDirect(),offset)];
+- int addrlen = XByteBuffer.toInt(xbuf.getBytesDirect(),offset);
+- offset += 4; //addr length
+- //System.arraycopy(xbuf.getBytesDirect(),offset,addr,0,addr.length);
+- data.setAddress(MemberImpl.getMember(xbuf.getBytesDirect(),offset,addrlen));
+- //offset += addr.length; //addr data
+- offset += addrlen;
+- int xsize = XByteBuffer.toInt(xbuf.getBytesDirect(),offset);
+- offset += 4; //xsize length
+- System.arraycopy(xbuf.getBytesDirect(),offset,xbuf.getBytesDirect(),0,xsize);
+- xbuf.setLength(xsize);
+- data.message = xbuf;
+- return data;
+-
+- }
+-
+- public static ChannelData getDataFromPackage(byte[] b) {
+- ChannelData data = new ChannelData(false);
+- int offset = 0;
+- data.setOptions(XByteBuffer.toInt(b,offset));
+- offset += 4; //options
+- data.setTimestamp(XByteBuffer.toLong(b,offset));
+- offset += 8; //timestamp
+- data.uniqueId = new byte[XByteBuffer.toInt(b,offset)];
+- offset += 4; //uniqueId length
+- System.arraycopy(b,offset,data.uniqueId,0,data.uniqueId.length);
+- offset += data.uniqueId.length; //uniqueId data
+- byte[] addr = new byte[XByteBuffer.toInt(b,offset)];
+- offset += 4; //addr length
+- System.arraycopy(b,offset,addr,0,addr.length);
+- data.setAddress(MemberImpl.getMember(addr));
+- offset += addr.length; //addr data
+- int xsize = XByteBuffer.toInt(b,offset);
+- //data.message = new XByteBuffer(new byte[xsize],false);
+- data.message = BufferPool.getBufferPool().getBuffer(xsize,false);
+- offset += 4; //message length
+- System.arraycopy(b,offset,data.message.getBytesDirect(),0,xsize);
+- data.message.append(b,offset,xsize);
+- offset += xsize; //message data
+- return data;
+- }
+-
+- public int hashCode() {
+- return XByteBuffer.toInt(getUniqueId(),0);
+- }
+-
+- /**
+- * Compares to ChannelData objects, only compares on getUniqueId().equals(o.getUniqueId())
+- * @param o Object
+- * @return boolean
+- */
+- public boolean equals(Object o) {
+- if ( o instanceof ChannelData ) {
+- return Arrays.equals(getUniqueId(),((ChannelData)o).getUniqueId());
+- } else return false;
+- }
+-
+- /**
+- * Create a shallow clone, only the data gets recreated
+- * @return ClusterData
+- */
+- public Object clone() {
+-// byte[] d = this.getDataPackage();
+-// return ClusterData.getDataFromPackage(d);
+- ChannelData clone = new ChannelData(false);
+- clone.options = this.options;
+- clone.message = new XByteBuffer(this.message.getBytesDirect(),false);
+- clone.timestamp = this.timestamp;
+- clone.uniqueId = this.uniqueId;
+- clone.address = this.address;
+- return clone;
+- }
+-
+- /**
+- * Complete clone
+- * @return ClusterData
+- */
+- public Object deepclone() {
+- byte[] d = this.getDataPackage();
+- return ChannelData.getDataFromPackage(d);
+- }
+-
+- /**
+- * Utility method, returns true if the options flag indicates that an ack
+- * is to be sent after the message has been received and processed
+- * @param options int - the options for the message
+- * @return boolean
+- * @see org.apache.catalina.tribes.Channel#SEND_OPTIONS_USE_ACK
+- * @see org.apache.catalina.tribes.Channel#SEND_OPTIONS_SYNCHRONIZED_ACK
+- */
+- public static boolean sendAckSync(int options) {
+- return ( (Channel.SEND_OPTIONS_USE_ACK & options) == Channel.SEND_OPTIONS_USE_ACK) &&
+- ( (Channel.SEND_OPTIONS_SYNCHRONIZED_ACK & options) == Channel.SEND_OPTIONS_SYNCHRONIZED_ACK);
+- }
+-
+-
+- /**
+- * Utility method, returns true if the options flag indicates that an ack
+- * is to be sent after the message has been received but not yet processed
+- * @param options int - the options for the message
+- * @return boolean
+- * @see org.apache.catalina.tribes.Channel#SEND_OPTIONS_USE_ACK
+- * @see org.apache.catalina.tribes.Channel#SEND_OPTIONS_SYNCHRONIZED_ACK
+- */
+- public static boolean sendAckAsync(int options) {
+- return ( (Channel.SEND_OPTIONS_USE_ACK & options) == Channel.SEND_OPTIONS_USE_ACK) &&
+- ( (Channel.SEND_OPTIONS_SYNCHRONIZED_ACK & options) != Channel.SEND_OPTIONS_SYNCHRONIZED_ACK);
+- }
+-
+- public String toString() {
+- StringBuffer buf = new StringBuffer();
+- buf.append("ClusterData[src=");
+- buf.append(getAddress()).append("; id=");
+- buf.append(bToS(getUniqueId())).append("; sent=");
+- buf.append(new Timestamp(this.getTimestamp()).toString()).append("]");
+- return buf.toString();
+- }
+-
+- public static String bToS(byte[] data) {
+- StringBuffer buf = new StringBuffer(4*16);
+- buf.append("{");
+- for (int i=0; data!=null && i<data.length; i++ ) buf.append(String.valueOf(data[i])).append(" ");
+- buf.append("}");
+- return buf.toString();
+- }
+-
+-
+-}
+Index: java/org/apache/catalina/tribes/io/BufferPool.java
+===================================================================
+--- java/org/apache/catalina/tribes/io/BufferPool.java (revision 590752)
++++ java/org/apache/catalina/tribes/io/BufferPool.java (working copy)
+@@ -1,94 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.io;
+-
+-
+-import org.apache.juli.logging.Log;
+-import org.apache.juli.logging.LogFactory;
+-
+-/**
+- *
+- * @author Filip Hanik
+- *
+- * @version 1.0
+- */
+-public class BufferPool {
+- protected static Log log = LogFactory.getLog(BufferPool.class);
+-
+- public static int DEFAULT_POOL_SIZE = 100*1024*1024; //100MB
+-
+-
+-
+- protected static volatile BufferPool instance = null;
+- protected BufferPoolAPI pool = null;
+-
+- 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 = null;
+- Class clazz = null;
+- try {
+- clazz = Class.forName("org.apache.catalina.tribes.io.BufferPool15Impl");
+- pool = (BufferPoolAPI)clazz.newInstance();
+- } catch ( Throwable x ) {
+- try {
+- clazz = Class.forName("org.apache.catalina.tribes.io.BufferPool14Impl");
+- pool = (BufferPoolAPI)clazz.newInstance();
+- } catch ( Throwable e ) {
+- log.warn("Unable to initilize BufferPool, not pooling XByteBuffer objects:"+x.getMessage());
+- if ( log.isDebugEnabled() ) log.debug("Unable to initilize BufferPool, not pooling XByteBuffer objects:",x);
+- }
+- }
+- pool.setMaxSize(DEFAULT_POOL_SIZE);
+- log.info("Created a buffer pool with max size:"+DEFAULT_POOL_SIZE+" bytes of type:"+(clazz!=null?clazz.getName():"null"));
+- instance = new BufferPool(pool);
+- }//end if
+- }//sync
+- }//end if
+- 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();
+- }
+-}
+Index: java/org/apache/catalina/tribes/io/ReplicationStream.java
+===================================================================
+--- java/org/apache/catalina/tribes/io/ReplicationStream.java (revision 590752)
++++ java/org/apache/catalina/tribes/io/ReplicationStream.java (working copy)
+@@ -1,117 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-
+-package org.apache.catalina.tribes.io;
+-
+-import java.io.IOException;
+-import java.io.InputStream;
+-import java.io.ObjectInputStream;
+-import java.io.ObjectStreamClass;
+-
+-/**
+- * Custom subclass of <code>ObjectInputStream</code> that loads from the
+- * class loader for this web application. This allows classes defined only
+- * with the web application to be found correctly.
+- *
+- * @author Craig R. McClanahan
+- * @author Bip Thelin
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-
+-public final class ReplicationStream extends ObjectInputStream {
+-
+-
+- /**
+- * The class loader we will use to resolve classes.
+- */
+- private ClassLoader[] classLoaders = null;
+-
+-
+- /**
+- * Construct a new instance of CustomObjectInputStream
+- *
+- * @param stream The input stream we will read from
+- * @param classLoader The class loader used to instantiate objects
+- *
+- * @exception IOException if an input/output error occurs
+- */
+- public ReplicationStream(InputStream stream,
+- ClassLoader[] classLoaders)
+- throws IOException {
+-
+- super(stream);
+- this.classLoaders = classLoaders;
+- }
+-
+- /**
+- * Load the local class equivalent of the specified stream class
+- * description, by using the class loader assigned to this Context.
+- *
+- * @param classDesc Class description from the input stream
+- *
+- * @exception ClassNotFoundException if this class cannot be found
+- * @exception IOException if an input/output error occurs
+- */
+- public Class resolveClass(ObjectStreamClass classDesc)
+- throws ClassNotFoundException, IOException {
+- String name = classDesc.getName();
+- boolean tryRepFirst = name.startsWith("org.apache.catalina.tribes");
+- try {
+- try
+- {
+- if ( tryRepFirst ) return findReplicationClass(name);
+- else return findExternalClass(name);
+- }
+- catch ( Exception x )
+- {
+- if ( tryRepFirst ) return findExternalClass(name);
+- else return findReplicationClass(name);
+- }
+- } catch (ClassNotFoundException e) {
+- return super.resolveClass(classDesc);
+- }
+- }
+-
+- public Class findReplicationClass(String name)
+- throws ClassNotFoundException, IOException {
+- Class clazz = Class.forName(name, false, getClass().getClassLoader());
+- return clazz;
+- }
+-
+- public Class findExternalClass(String name) throws ClassNotFoundException {
+- ClassNotFoundException cnfe = null;
+- for (int i=0; i<classLoaders.length; i++ ) {
+- try {
+- Class clazz = Class.forName(name, false, classLoaders[i]);
+- return clazz;
+- } catch ( ClassNotFoundException x ) {
+- cnfe = x;
+- }
+- }
+- if ( cnfe != null ) throw cnfe;
+- else throw new ClassNotFoundException(name);
+- }
+-
+- public void close() throws IOException {
+- this.classLoaders = null;
+- super.close();
+- }
+-
+-
+-}
+Index: java/org/apache/catalina/tribes/io/XByteBuffer.java
+===================================================================
+--- java/org/apache/catalina/tribes/io/XByteBuffer.java (revision 590752)
++++ java/org/apache/catalina/tribes/io/XByteBuffer.java (working copy)
+@@ -1,612 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.io;
+-
+-import java.io.ByteArrayInputStream;
+-import java.io.ByteArrayOutputStream;
+-import java.io.IOException;
+-import java.io.InputStream;
+-import java.io.ObjectInputStream;
+-import java.io.ObjectOutputStream;
+-import java.io.Serializable;
+-import java.nio.ByteBuffer;
+-
+-/**
+- * The XByteBuffer provides a dual functionality.
+- * One, it stores message bytes and automatically extends the byte buffer if needed.<BR>
+- * Two, it can encode and decode packages so that they can be defined and identified
+- * as they come in on a socket.
+- * <br>
+- * <b>THIS CLASS IS NOT THREAD SAFE</B><BR>
+- * <br/>
+- * Transfer package:
+- * <ul>
+- * <li><b>START_DATA/b> - 7 bytes - <i>FLT2002</i></li>
+- * <li><b>SIZE</b> - 4 bytes - size of the data package</li>
+- * <li><b>DATA</b> - should be as many bytes as the prev SIZE</li>
+- * <li><b>END_DATA</b> - 7 bytes - <i>TLF2003</i></lI>
+- * </ul>
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-public class XByteBuffer
+-{
+-
+- public static org.apache.juli.logging.Log log =
+- org.apache.juli.logging.LogFactory.getLog( XByteBuffer.class );
+-
+- /**
+- * This is a package header, 7 bytes (FLT2002)
+- */
+- public static final byte[] START_DATA = {70,76,84,50,48,48,50};
+-
+- /**
+- * This is the package footer, 7 bytes (TLF2003)
+- */
+- public static final byte[] END_DATA = {84,76,70,50,48,48,51};
+-
+- /**
+- * Default size on the initial byte buffer
+- */
+- private static final int DEF_SIZE = 2048;
+-
+- /**
+- * Default size to extend the buffer with
+- */
+- private static final int DEF_EXT = 1024;
+-
+- /**
+- * Variable to hold the data
+- */
+- protected byte[] buf = null;
+-
+- /**
+- * Current length of data in the buffer
+- */
+- protected int bufSize = 0;
+-
+- /**
+- * Flag for discarding invalid packages
+- * If this flag is set to true, and append(byte[],...) is called,
+- * the data added will be inspected, and if it doesn't start with
+- * <code>START_DATA</code> it will be thrown away.
+- *
+- */
+- protected boolean discard = true;
+-
+- /**
+- * Constructs a new XByteBuffer
+- * @param size - the initial size of the byte buffer
+- * @todo use a pool of byte[] for performance
+- */
+- public XByteBuffer(int size, boolean discard) {
+- buf = new byte[size];
+- this.discard = discard;
+- }
+-
+- public XByteBuffer(byte[] data,boolean discard) {
+- this(data,data.length+128,discard);
+- }
+-
+- public XByteBuffer(byte[] data, int size,boolean discard) {
+- int length = Math.max(data.length,size);
+- buf = new byte[length];
+- System.arraycopy(data,0,buf,0,data.length);
+- bufSize = data.length;
+- this.discard = discard;
+- }
+-
+- public int getLength() {
+- return bufSize;
+- }
+-
+- public void setLength(int size) {
+- if ( size > buf.length ) throw new ArrayIndexOutOfBoundsException("Size is larger than existing buffer.");
+- bufSize = size;
+- }
+-
+- public void trim(int length) {
+- if ( (bufSize - length) < 0 )
+- throw new ArrayIndexOutOfBoundsException("Can't trim more bytes than are available. length:"+bufSize+" trim:"+length);
+- bufSize -= length;
+- }
+-
+- public void reset() {
+- bufSize = 0;
+- }
+-
+- public byte[] getBytesDirect() {
+- return this.buf;
+- }
+-
+- /**
+- * Returns the bytes in the buffer, in its exact length
+- */
+- public byte[] getBytes() {
+- byte[] b = new byte[bufSize];
+- System.arraycopy(buf,0,b,0,bufSize);
+- return b;
+- }
+-
+- /**
+- * Resets the buffer
+- */
+- public void clear() {
+- bufSize = 0;
+- }
+-
+- /**
+- * Appends the data to the buffer. If the data is incorrectly formatted, ie, the data should always start with the
+- * header, false will be returned and the data will be discarded.
+- * @param b - bytes to be appended
+- * @param off - the offset to extract data from
+- * @param len - the number of bytes to append.
+- * @return true if the data was appended correctly. Returns false if the package is incorrect, ie missing header or something, or the length of data is 0
+- */
+- public boolean append(ByteBuffer b, int len) {
+- int newcount = bufSize + len;
+- if (newcount > buf.length) {
+- expand(newcount);
+- }
+- b.get(buf,bufSize,len);
+-
+- bufSize = newcount;
+-
+- if ( discard ) {
+- if (bufSize > START_DATA.length && (firstIndexOf(buf, 0, START_DATA) == -1)) {
+- bufSize = 0;
+- log.error("Discarded the package, invalid header");
+- return false;
+- }
+- }
+- return true;
+-
+- }
+-
+- public boolean append(byte i) {
+- int newcount = bufSize + 1;
+- if (newcount > buf.length) {
+- expand(newcount);
+- }
+- buf[bufSize] = i;
+- bufSize = newcount;
+- return true;
+- }
+-
+-
+- public boolean append(boolean i) {
+- int newcount = bufSize + 1;
+- if (newcount > buf.length) {
+- expand(newcount);
+- }
+- XByteBuffer.toBytes(i,buf,bufSize);
+- bufSize = newcount;
+- return true;
+- }
+-
+- public boolean append(long i) {
+- int newcount = bufSize + 8;
+- if (newcount > buf.length) {
+- expand(newcount);
+- }
+- XByteBuffer.toBytes(i,buf,bufSize);
+- bufSize = newcount;
+- return true;
+- }
+-
+- public boolean append(int i) {
+- int newcount = bufSize + 4;
+- if (newcount > buf.length) {
+- expand(newcount);
+- }
+- XByteBuffer.toBytes(i,buf,bufSize);
+- bufSize = newcount;
+- return true;
+- }
+-
+- public boolean append(byte[] b, int off, int len) {
+- if ((off < 0) || (off > b.length) || (len < 0) ||
+- ((off + len) > b.length) || ((off + len) < 0)) {
+- throw new IndexOutOfBoundsException();
+- } else if (len == 0) {
+- return false;
+- }
+-
+- int newcount = bufSize + len;
+- if (newcount > buf.length) {
+- expand(newcount);
+- }
+- System.arraycopy(b, off, buf, bufSize, len);
+- bufSize = newcount;
+-
+- if ( discard ) {
+- if (bufSize > START_DATA.length && (firstIndexOf(buf, 0, START_DATA) == -1)) {
+- bufSize = 0;
+- log.error("Discarded the package, invalid header");
+- return false;
+- }
+- }
+- return true;
+- }
+-
+- public void expand(int newcount) {
+- //don't change the allocation strategy
+- byte newbuf[] = new byte[Math.max(buf.length << 1, newcount)];
+- System.arraycopy(buf, 0, newbuf, 0, bufSize);
+- buf = newbuf;
+- }
+-
+- public int getCapacity() {
+- return buf.length;
+- }
+-
+-
+- /**
+- * Internal mechanism to make a check if a complete package exists
+- * within the buffer
+- * @return - true if a complete package (header,compress,size,data,footer) exists within the buffer
+- */
+- public int countPackages() {
+- return countPackages(false);
+- }
+-
+- public int countPackages(boolean first)
+- {
+- int cnt = 0;
+- int pos = START_DATA.length;
+- int start = 0;
+-
+- while ( start < bufSize ) {
+- //first check start header
+- int index = XByteBuffer.firstIndexOf(buf,start,START_DATA);
+- //if the header (START_DATA) isn't the first thing or
+- //the buffer isn't even 14 bytes
+- if ( index != start || ((bufSize-start)<14) ) break;
+- //next 4 bytes are compress flag not needed for count packages
+- //then get the size 4 bytes
+- int size = toInt(buf, pos);
+- //now the total buffer has to be long enough to hold
+- //START_DATA.length+4+size+END_DATA.length
+- pos = start + START_DATA.length + 4 + size;
+- if ( (pos + END_DATA.length) > bufSize) break;
+- //and finally check the footer of the package END_DATA
+- int newpos = firstIndexOf(buf, pos, END_DATA);
+- //mismatch, there is no package
+- if (newpos != pos) break;
+- //increase the packet count
+- cnt++;
+- //reset the values
+- start = pos + END_DATA.length;
+- pos = start + START_DATA.length;
+- //we only want to verify that we have at least one package
+- if ( first ) break;
+- }
+- return cnt;
+- }
+-
+- /**
+- * Method to check if a package exists in this byte buffer.
+- * @return - true if a complete package (header,options,size,data,footer) exists within the buffer
+- */
+- public boolean doesPackageExist() {
+- return (countPackages(true)>0);
+- }
+-
+- /**
+- * Extracts the message bytes from a package.
+- * If no package exists, a IllegalStateException will be thrown.
+- * @param clearFromBuffer - if true, the package will be removed from the byte buffer
+- * @return - returns the actual message bytes (header, compress,size and footer not included).
+- */
+- public XByteBuffer extractDataPackage(boolean clearFromBuffer) {
+- int psize = countPackages(true);
+- if (psize == 0) {
+- throw new java.lang.IllegalStateException("No package exists in XByteBuffer");
+- }
+- int size = toInt(buf, START_DATA.length);
+- XByteBuffer xbuf = BufferPool.getBufferPool().getBuffer(size,false);
+- xbuf.setLength(size);
+- System.arraycopy(buf, START_DATA.length + 4, xbuf.getBytesDirect(), 0, size);
+- if (clearFromBuffer) {
+- int totalsize = START_DATA.length + 4 + size + END_DATA.length;
+- bufSize = bufSize - totalsize;
+- System.arraycopy(buf, totalsize, buf, 0, bufSize);
+- }
+- return xbuf;
+-
+- }
+-
+- public ChannelData extractPackage(boolean clearFromBuffer) throws java.io.IOException {
+- XByteBuffer xbuf = extractDataPackage(clearFromBuffer);
+- ChannelData cdata = ChannelData.getDataFromPackage(xbuf);
+- return cdata;
+- }
+-
+- /**
+- * Creates a complete data package
+- * @param indata - the message data to be contained within the package
+- * @param compressed - compression flag for the indata buffer
+- * @return - a full package (header,size,data,footer)
+- *
+- */
+- public static byte[] createDataPackage(ChannelData cdata) {
+-// return createDataPackage(cdata.getDataPackage());
+- //avoid one extra byte array creation
+- int dlength = cdata.getDataPackageLength();
+- int length = getDataPackageLength(dlength);
+- byte[] data = new byte[length];
+- int offset = 0;
+- System.arraycopy(START_DATA, 0, data, offset, START_DATA.length);
+- offset += START_DATA.length;
+- toBytes(dlength,data, START_DATA.length);
+- offset += 4;
+- cdata.getDataPackage(data,offset);
+- offset += dlength;
+- System.arraycopy(END_DATA, 0, data, offset, END_DATA.length);
+- offset += END_DATA.length;
+- return data;
+- }
+-
+- public static byte[] createDataPackage(byte[] data, int doff, int dlength, byte[] buffer, int bufoff) {
+- if ( (buffer.length-bufoff) > getDataPackageLength(dlength) ) {
+- throw new ArrayIndexOutOfBoundsException("Unable to create data package, buffer is too small.");
+- }
+- System.arraycopy(START_DATA, 0, buffer, bufoff, START_DATA.length);
+- toBytes(data.length,buffer, bufoff+START_DATA.length);
+- System.arraycopy(data, doff, buffer, bufoff+START_DATA.length + 4, dlength);
+- System.arraycopy(END_DATA, 0, buffer, bufoff+START_DATA.length + 4 + data.length, END_DATA.length);
+- return buffer;
+- }
+-
+-
+- public static int getDataPackageLength(int datalength) {
+- int length =
+- START_DATA.length + //header length
+- 4 + //data length indicator
+- datalength + //actual data length
+- END_DATA.length; //footer length
+- return length;
+-
+- }
+-
+- public static byte[] createDataPackage(byte[] data) {
+- int length = getDataPackageLength(data.length);
+- byte[] result = new byte[length];
+- return createDataPackage(data,0,data.length,result,0);
+- }
+-
+-
+-
+-// public static void fillDataPackage(byte[] data, int doff, int dlength, XByteBuffer buf) {
+-// int pkglen = getDataPackageLength(dlength);
+-// if ( buf.getCapacity() < pkglen ) buf.expand(pkglen);
+-// createDataPackage(data,doff,dlength,buf.getBytesDirect(),buf.getLength());
+-// }
+-
+- /**
+- * Convert four bytes to an int
+- * @param b - the byte array containing the four bytes
+- * @param off - the offset
+- * @return the integer value constructed from the four bytes
+- * @exception java.lang.ArrayIndexOutOfBoundsException
+- */
+- public static int toInt(byte[] b,int off){
+- return ( ( (int) b[off+3]) & 0xFF) +
+- ( ( ( (int) b[off+2]) & 0xFF) << 8) +
+- ( ( ( (int) b[off+1]) & 0xFF) << 16) +
+- ( ( ( (int) b[off+0]) & 0xFF) << 24);
+- }
+-
+- /**
+- * Convert eight bytes to a long
+- * @param b - the byte array containing the four bytes
+- * @param off - the offset
+- * @return the long value constructed from the eight bytes
+- * @exception java.lang.ArrayIndexOutOfBoundsException
+- */
+- public static long toLong(byte[] b,int off){
+- return ( ( (long) b[off+7]) & 0xFF) +
+- ( ( ( (long) b[off+6]) & 0xFF) << 8) +
+- ( ( ( (long) b[off+5]) & 0xFF) << 16) +
+- ( ( ( (long) b[off+4]) & 0xFF) << 24) +
+- ( ( ( (long) b[off+3]) & 0xFF) << 32) +
+- ( ( ( (long) b[off+2]) & 0xFF) << 40) +
+- ( ( ( (long) b[off+1]) & 0xFF) << 48) +
+- ( ( ( (long) b[off+0]) & 0xFF) << 56);
+- }
+-
+-
+- /**
+- * Converts an integer to four bytes
+- * @param n - the integer
+- * @return - four bytes in an array
+- * @deprecated use toBytes(boolean,byte[],int)
+- */
+- public static byte[] toBytes(boolean bool) {
+- byte[] b = new byte[1] ;
+- return toBytes(bool,b,0);
+-
+- }
+-
+- public static byte[] toBytes(boolean bool, byte[] data, int offset) {
+- data[offset] = (byte)(bool?1:0);
+- return data;
+- }
+-
+- /**
+- *
+- * @param <any> long
+- * @return use
+- */
+- public static boolean toBoolean(byte[] b, int offset) {
+- return b[offset] != 0;
+- }
+-
+-
+- /**
+- * Converts an integer to four bytes
+- * @param n - the integer
+- * @return - four bytes in an array
+- * @deprecated use toBytes(int,byte[],int)
+- */
+- public static byte[] toBytes(int n) {
+- return toBytes(n,new byte[4],0);
+- }
+-
+- public static byte[] toBytes(int n,byte[] b, int offset) {
+- b[offset+3] = (byte) (n);
+- n >>>= 8;
+- b[offset+2] = (byte) (n);
+- n >>>= 8;
+- b[offset+1] = (byte) (n);
+- n >>>= 8;
+- b[offset+0] = (byte) (n);
+- return b;
+- }
+-
+- /**
+- * Converts an long to eight bytes
+- * @param n - the long
+- * @return - eight bytes in an array
+- * @deprecated use toBytes(long,byte[],int)
+- */
+- public static byte[] toBytes(long n) {
+- return toBytes(n,new byte[8],0);
+- }
+- public static byte[] toBytes(long n, byte[] b, int offset) {
+- b[offset+7] = (byte) (n);
+- n >>>= 8;
+- b[offset+6] = (byte) (n);
+- n >>>= 8;
+- b[offset+5] = (byte) (n);
+- n >>>= 8;
+- b[offset+4] = (byte) (n);
+- n >>>= 8;
+- b[offset+3] = (byte) (n);
+- n >>>= 8;
+- b[offset+2] = (byte) (n);
+- n >>>= 8;
+- b[offset+1] = (byte) (n);
+- n >>>= 8;
+- b[offset+0] = (byte) (n);
+- return b;
+- }
+-
+- /**
+- * Similar to a String.IndexOf, but uses pure bytes
+- * @param src - the source bytes to be searched
+- * @param srcOff - offset on the source buffer
+- * @param find - the string to be found within src
+- * @return - the index of the first matching byte. -1 if the find array is not found
+- */
+- public static int firstIndexOf(byte[] src, int srcOff, byte[] find){
+- int result = -1;
+- if (find.length > src.length) return result;
+- if (find.length == 0 || src.length == 0) return result;
+- if (srcOff >= src.length ) throw new java.lang.ArrayIndexOutOfBoundsException();
+- boolean found = false;
+- int srclen = src.length;
+- int findlen = find.length;
+- byte first = find[0];
+- int pos = srcOff;
+- while (!found) {
+- //find the first byte
+- while (pos < srclen){
+- if (first == src[pos])
+- break;
+- pos++;
+- }
+- if (pos >= srclen)
+- return -1;
+-
+- //we found the first character
+- //match the rest of the bytes - they have to match
+- if ( (srclen - pos) < findlen)
+- return -1;
+- //assume it does exist
+- found = true;
+- for (int i = 1; ( (i < findlen) && found); i++)
+- found = found && (find[i] == src[pos + i]);
+- if (found)
+- result = pos;
+- else if ( (srclen - pos) < findlen)
+- return -1; //no more matches possible
+- else
+- pos++;
+- }
+- return result;
+- }
+-
+-
+- public static Serializable deserialize(byte[] data)
+- throws IOException, ClassNotFoundException, ClassCastException {
+- return deserialize(data,0,data.length);
+- }
+-
+- public static Serializable deserialize(byte[] data, int offset, int length)
+- throws IOException, ClassNotFoundException, ClassCastException {
+- return deserialize(data,offset,length,null);
+- }
+- public static int invokecount = 0;
+- public static Serializable deserialize(byte[] data, int offset, int length, ClassLoader[] cls)
+- throws IOException, ClassNotFoundException, ClassCastException {
+- synchronized (XByteBuffer.class) { invokecount++;}
+- Object message = null;
+- if ( cls == null ) cls = new ClassLoader[0];
+- if (data != null) {
+- InputStream instream = new ByteArrayInputStream(data,offset,length);
+- ObjectInputStream stream = null;
+- stream = (cls.length>0)? new ReplicationStream(instream,cls):new ObjectInputStream(instream);
+- message = stream.readObject();
+- instream.close();
+- stream.close();
+- }
+- if ( message == null ) {
+- return null;
+- } else if (message instanceof Serializable)
+- return (Serializable) message;
+- else {
+- throw new ClassCastException("Message has the wrong class. It should implement Serializable, instead it is:"+message.getClass().getName());
+- }
+- }
+-
+- /**
+- * Serializes a message into cluster data
+- * @param msg ClusterMessage
+- * @param compress boolean
+- * @return
+- * @throws IOException
+- */
+- public static byte[] serialize(Serializable msg) throws IOException {
+- ByteArrayOutputStream outs = new ByteArrayOutputStream();
+- ObjectOutputStream out = new ObjectOutputStream(outs);
+- out.writeObject(msg);
+- out.flush();
+- byte[] data = outs.toByteArray();
+- return data;
+- }
+-
+- public void setDiscard(boolean discard) {
+- this.discard = discard;
+- }
+-
+- public boolean getDiscard() {
+- return discard;
+- }
+-
+-}
+Index: java/org/apache/catalina/tribes/io/ObjectReader.java
+===================================================================
+--- java/org/apache/catalina/tribes/io/ObjectReader.java (revision 590752)
++++ java/org/apache/catalina/tribes/io/ObjectReader.java (working copy)
+@@ -1,165 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.io;
+-
+-import java.io.IOException;
+-import java.net.Socket;
+-import java.nio.ByteBuffer;
+-import java.nio.channels.SocketChannel;
+-
+-import org.apache.catalina.tribes.ChannelMessage;
+-
+-
+-
+-/**
+- * The object reader object is an object used in conjunction with
+- * java.nio TCP messages. This object stores the message bytes in a
+- * <code>XByteBuffer</code> until a full package has been received.
+- * This object uses an XByteBuffer which is an extendable object buffer that also allows
+- * for message encoding and decoding.
+- *
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-public class ObjectReader {
+-
+- protected static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(ObjectReader.class);
+-
+- private XByteBuffer buffer;
+-
+- protected long lastAccess = System.currentTimeMillis();
+-
+- protected boolean accessed = false;
+- private boolean cancelled;
+-
+- /**
+- * Creates an <code>ObjectReader</code> for a TCP NIO socket channel
+- * @param channel - the channel to be read.
+- */
+- public ObjectReader(SocketChannel channel) {
+- this(channel.socket());
+- }
+-
+- /**
+- * Creates an <code>ObjectReader</code> for a TCP socket
+- * @param socket Socket
+- */
+- public ObjectReader(Socket socket) {
+- try{
+- this.buffer = new XByteBuffer(socket.getReceiveBufferSize(), true);
+- }catch ( IOException x ) {
+- //unable to get buffer size
+- log.warn("Unable to retrieve the socket receiver buffer size, setting to default 43800 bytes.");
+- this.buffer = new XByteBuffer(43800,true);
+- }
+- }
+-
+- public synchronized void access() {
+- this.accessed = true;
+- this.lastAccess = System.currentTimeMillis();
+- }
+-
+- public synchronized void finish() {
+- this.accessed = false;
+- this.lastAccess = System.currentTimeMillis();
+- }
+-
+- public boolean isAccessed() {
+- return this.accessed;
+- }
+-
+- /**
+- * Append new bytes to buffer.
+- * @see XByteBuffer#countPackages()
+- * @param data new transfer buffer
+- * @param off offset
+- * @param len length in buffer
+- * @return number of messages that sended to callback
+- * @throws java.io.IOException
+- */
+- public int append(ByteBuffer data, int len, boolean count) throws java.io.IOException {
+- buffer.append(data,len);
+- int pkgCnt = -1;
+- if ( count ) pkgCnt = buffer.countPackages();
+- return pkgCnt;
+- }
+-
+- public int append(byte[] data,int off,int len, boolean count) throws java.io.IOException {
+- buffer.append(data,off,len);
+- int pkgCnt = -1;
+- if ( count ) pkgCnt = buffer.countPackages();
+- return pkgCnt;
+- }
+-
+- /**
+- * Send buffer to cluster listener (callback).
+- * Is message complete receiver send message to callback?
+- *
+- * @see org.apache.catalina.tribes.transport.ClusterReceiverBase#messageDataReceived(ChannelMessage)
+- * @see XByteBuffer#doesPackageExist()
+- * @see XByteBuffer#extractPackage(boolean)
+- *
+- * @return number of received packages/messages
+- * @throws java.io.IOException
+- */
+- public ChannelMessage[] execute() throws java.io.IOException {
+- int pkgCnt = buffer.countPackages();
+- ChannelMessage[] result = new ChannelMessage[pkgCnt];
+- for (int i=0; i<pkgCnt; i++) {
+- ChannelMessage data = buffer.extractPackage(true);
+- result[i] = data;
+- }
+- return result;
+- }
+-
+- public int bufferSize() {
+- return buffer.getLength();
+- }
+-
+-
+- public boolean hasPackage() {
+- return buffer.countPackages(true)>0;
+- }
+- /**
+- * Returns the number of packages that the reader has read
+- * @return int
+- */
+- public int count() {
+- return buffer.countPackages();
+- }
+-
+- public void close() {
+- this.buffer = null;
+- }
+-
+- public long getLastAccess() {
+- return lastAccess;
+- }
+-
+- public boolean isCancelled() {
+- return cancelled;
+- }
+-
+- public void setLastAccess(long lastAccess) {
+- this.lastAccess = lastAccess;
+- }
+-
+- public void setCancelled(boolean cancelled) {
+- this.cancelled = cancelled;
+- }
+-
+-}
+Index: java/org/apache/catalina/tribes/io/BufferPool14Impl.java
+===================================================================
+--- java/org/apache/catalina/tribes/io/BufferPool14Impl.java (revision 590752)
++++ java/org/apache/catalina/tribes/io/BufferPool14Impl.java (working copy)
+@@ -1,69 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.io;
+-
+-import java.util.LinkedList;
+-
+-
+-/**
+- *
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-class BufferPool14Impl implements BufferPool.BufferPoolAPI {
+- protected int maxSize;
+- protected int size = 0;
+- protected LinkedList queue = new LinkedList();
+-
+- public void setMaxSize(int bytes) {
+- this.maxSize = bytes;
+- }
+-
+- public synchronized int addAndGet(int val) {
+- size = size + (val);
+- return size;
+- }
+-
+-
+-
+- public synchronized XByteBuffer getBuffer(int minSize, boolean discard) {
+- XByteBuffer buffer = (XByteBuffer)(queue.size()>0?queue.remove(0):null);
+- if ( buffer != null ) addAndGet(-buffer.getCapacity());
+- if ( buffer == null ) buffer = new XByteBuffer(minSize,discard);
+- else if ( buffer.getCapacity() <= minSize ) buffer.expand(minSize);
+- buffer.setDiscard(discard);
+- buffer.reset();
+- return buffer;
+- }
+-
+- public synchronized void returnBuffer(XByteBuffer buffer) {
+- if ( (size + buffer.getCapacity()) <= maxSize ) {
+- addAndGet(buffer.getCapacity());
+- queue.add(buffer);
+- }
+- }
+-
+- public synchronized void clear() {
+- queue.clear();
+- size = 0;
+- }
+-
+- public int getMaxSize() {
+- return maxSize;
+- }
+-
+-}
+Index: java/org/apache/catalina/tribes/io/DirectByteArrayOutputStream.java
+===================================================================
+--- java/org/apache/catalina/tribes/io/DirectByteArrayOutputStream.java (revision 590752)
++++ java/org/apache/catalina/tribes/io/DirectByteArrayOutputStream.java (working copy)
+@@ -1,63 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.io;
+-
+-import java.io.IOException;
+-import java.io.OutputStream;
+-
+-/**
+- * Byte array output stream that exposes the byte array directly
+- *
+- * @author not attributable
+- * @version 1.0
+- */
+-public class DirectByteArrayOutputStream extends OutputStream {
+-
+- private XByteBuffer buffer;
+-
+- public DirectByteArrayOutputStream(int size) {
+- buffer = new XByteBuffer(size,false);
+- }
+-
+- /**
+- * Writes the specified byte to this output stream.
+- *
+- * @param b the <code>byte</code>.
+- * @throws IOException if an I/O error occurs. In particular, an
+- * <code>IOException</code> may be thrown if the output stream has
+- * been closed.
+- * @todo Implement this java.io.OutputStream method
+- */
+- public void write(int b) throws IOException {
+- buffer.append((byte)b);
+- }
+-
+- public int size() {
+- return buffer.getLength();
+- }
+-
+- public byte[] getArrayDirect() {
+- return buffer.getBytesDirect();
+- }
+-
+- public byte[] getArray() {
+- return buffer.getBytes();
+- }
+-
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/io/BufferPool15Impl.java
+===================================================================
+--- java/org/apache/catalina/tribes/io/BufferPool15Impl.java (revision 590752)
++++ java/org/apache/catalina/tribes/io/BufferPool15Impl.java (working copy)
+@@ -1,63 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.io;
+-
+-import java.util.concurrent.ConcurrentLinkedQueue;
+-import java.util.concurrent.atomic.AtomicInteger;
+-
+-/**
+- *
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-class BufferPool15Impl implements BufferPool.BufferPoolAPI {
+- protected int maxSize;
+- protected AtomicInteger size = new AtomicInteger(0);
+- protected ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();
+-
+- public void setMaxSize(int bytes) {
+- this.maxSize = bytes;
+- }
+-
+-
+- public XByteBuffer getBuffer(int minSize, boolean discard) {
+- XByteBuffer buffer = (XByteBuffer)queue.poll();
+- if ( buffer != null ) size.addAndGet(-buffer.getCapacity());
+- if ( buffer == null ) buffer = new XByteBuffer(minSize,discard);
+- else if ( buffer.getCapacity() <= minSize ) buffer.expand(minSize);
+- buffer.setDiscard(discard);
+- buffer.reset();
+- return buffer;
+- }
+-
+- public void returnBuffer(XByteBuffer buffer) {
+- if ( (size.get() + buffer.getCapacity()) <= maxSize ) {
+- size.addAndGet(buffer.getCapacity());
+- queue.offer(buffer);
+- }
+- }
+-
+- public void clear() {
+- queue.clear();
+- size.set(0);
+- }
+-
+- public int getMaxSize() {
+- return maxSize;
+- }
+-
+-}
+Index: java/org/apache/catalina/tribes/io/ListenCallback.java
+===================================================================
+--- java/org/apache/catalina/tribes/io/ListenCallback.java (revision 590752)
++++ java/org/apache/catalina/tribes/io/ListenCallback.java (working copy)
+@@ -1,42 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes.io;
+-
+-import org.apache.catalina.tribes.ChannelMessage;
+-
+-
+-
+-/**
+- * Internal interface, similar to the MessageListener but used
+- * at the IO base
+- * The listen callback interface is used by the replication system
+- * when data has been received. The interface does not care about
+- * objects and marshalling and just passes the bytes straight through.
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-public interface ListenCallback
+-{
+- /**
+- * This method is invoked on the callback object to notify it that new data has
+- * been received from one of the cluster nodes.
+- * @param data - the message bytes received from the cluster/replication system
+- */
+- public void messageDataReceived(ChannelMessage data);
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/ErrorHandler.java
+===================================================================
+--- java/org/apache/catalina/tribes/ErrorHandler.java (revision 590752)
++++ java/org/apache/catalina/tribes/ErrorHandler.java (working copy)
+@@ -1,46 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes;
+-
+-
+-
+-/**
+- * The <code>ErrorHandler</code> class is used when sending messages
+- * that are sent asynchronously and the application still needs to get
+- * confirmation when the message was sent successfully or when a message errored out.
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public interface ErrorHandler {
+-
+- /**
+- * Invoked if the message is dispatched asynch, and an error occurs
+- * @param x ChannelException - the error that happened
+- * @param id - the unique id for the message
+- * @see Channel#send(Member[], Serializable, int, ErrorHandler)
+- */
+- public void handleError(ChannelException x, UniqueId id);
+-
+- /**
+- * Invoked when the message has been sent successfully.
+- * @param id - the unique id for the message
+- * @see Channel#send(Member[], Serializable, int, ErrorHandler)
+- */
+- public void handleCompletion(UniqueId id);
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/ChannelInterceptor.java
+===================================================================
+--- java/org/apache/catalina/tribes/ChannelInterceptor.java (revision 590752)
++++ java/org/apache/catalina/tribes/ChannelInterceptor.java (working copy)
+@@ -1,180 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes;
+-
+-import org.apache.catalina.tribes.group.InterceptorPayload;
+-
+-/**
+- * A ChannelInterceptor is an interceptor that intercepts
+- * messages and membership messages in the channel stack.
+- * This allows interceptors to modify the message or perform
+- * other actions when a message is sent or received.<br>
+- * Interceptors are tied together in a linked list.
+- * @see org.apache.catalina.tribes.group.ChannelInterceptorBase
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-
+-public interface ChannelInterceptor extends MembershipListener, Heartbeat {
+-
+- /**
+- * An interceptor can react to a message based on a set bit on the
+- * message options. <br>
+- * When a message is sent, the options can be retrieved from ChannelMessage.getOptions()
+- * and if the bit is set, this interceptor will react to it.<br>
+- * A simple evaluation if an interceptor should react to the message would be:<br>
+- * <code>boolean react = (getOptionFlag() == (getOptionFlag() & ChannelMessage.getOptions()));</code><br>
+- * The default option is 0, meaning there is no way for the application to trigger the
+- * interceptor. The interceptor itself will decide.<br>
+- * @return int
+- * @see ChannelMessage#getOptions()
+- */
+- public int getOptionFlag();
+-
+- /**
+- * Sets the option flag
+- * @param flag int
+- * @see #getOptionFlag()
+- */
+- public void setOptionFlag(int flag);
+-
+- /**
+- * Set the next interceptor in the list of interceptors
+- * @param next ChannelInterceptor
+- */
+- public void setNext(ChannelInterceptor next) ;
+-
+- /**
+- * Retrieve the next interceptor in the list
+- * @return ChannelInterceptor - returns the next interceptor in the list or null if no more interceptors exist
+- */
+- public ChannelInterceptor getNext();
+-
+- /**
+- * Set the previous interceptor in the list
+- * @param previous ChannelInterceptor
+- */
+- public void setPrevious(ChannelInterceptor previous);
+-
+- /**
+- * Retrieve the previous interceptor in the list
+- * @return ChannelInterceptor - returns the previous interceptor in the list or null if no more interceptors exist
+- */
+- public ChannelInterceptor getPrevious();
+-
+- /**
+- * The <code>sendMessage</code> method is called when a message is being sent to one more destinations.
+- * The interceptor can modify any of the parameters and then pass on the message down the stack by
+- * invoking <code>getNext().sendMessage(destination,msg,payload)</code><br>
+- * Alternatively the interceptor can stop the message from being sent by not invoking
+- * <code>getNext().sendMessage(destination,msg,payload)</code><br>
+- * If the message is to be sent asynchronous the application can be notified of completion and
+- * errors by passing in an error handler attached to a payload object.<br>
+- * The ChannelMessage.getAddress contains Channel.getLocalMember, and can be overwritten
+- * to simulate a message sent from another node.<br>
+- * @param destination Member[] - the destination for this message
+- * @param msg ChannelMessage - the message to be sent
+- * @param payload InterceptorPayload - the payload, carrying an error handler and future useful data, can be null
+- * @throws ChannelException
+- * @see ErrorHandler
+- * @see InterceptorPayload
+- */
+- public void sendMessage(Member[] destination, ChannelMessage msg, InterceptorPayload payload) throws ChannelException;
+-
+- /**
+- * the <code>messageReceived</code> is invoked when a message is received.
+- * <code>ChannelMessage.getAddress()</code> is the sender, or the reply-to address
+- * if it has been overwritten.
+- * @param data ChannelMessage
+- */
+- public void messageReceived(ChannelMessage data);
+-
+- /**
+- * The <code>heartbeat()</code> method gets invoked periodically
+- * to allow interceptors to clean up resources, time out object and
+- * perform actions that are unrelated to sending/receiving data.
+- */
+- public void heartbeat();
+-
+- /**
+- * Intercepts the <code>Channel.hasMembers()</code> method
+- * @return boolean - if the channel has members in its membership group
+- * @see Channel#hasMembers()
+- */
+- public boolean hasMembers() ;
+-
+- /**
+- * Intercepts the code>Channel.getMembers()</code> method
+- * @return Member[]
+- * @see Channel#getMembers()
+- */
+- public Member[] getMembers() ;
+-
+- /**
+- * Intercepts the code>Channel.getLocalMember(boolean)</code> method
+- * @param incAliveTime boolean
+- * @return Member
+- * @see Channel#getLocalMember(boolean)
+- */
+- public Member getLocalMember(boolean incAliveTime) ;
+-
+- /**
+- * Intercepts the code>Channel.getMember(Member)</code> method
+- * @param mbr Member
+- * @return Member - the actual member information, including stay alive
+- * @see Channel#getMember(Member)
+- */
+- public Member getMember(Member mbr);
+-
+- /**
+- * Starts up the channel. This can be called multiple times for individual services to start
+- * The svc parameter can be the logical or value of any constants
+- * @param svc int value of <BR>
+- * Channel.DEFAULT - will start all services <BR>
+- * Channel.MBR_RX_SEQ - starts the membership receiver <BR>
+- * Channel.MBR_TX_SEQ - starts the membership broadcaster <BR>
+- * Channel.SND_TX_SEQ - starts the replication transmitter<BR>
+- * Channel.SND_RX_SEQ - starts the replication receiver<BR>
+- * @throws ChannelException if a startup error occurs or the service is already started.
+- * @see Channel
+- */
+- public void start(int svc) throws ChannelException;
+-
+- /**
+- * Shuts down the channel. This can be called multiple times for individual services to shutdown
+- * The svc parameter can be the logical or value of any constants
+- * @param svc int value of <BR>
+- * Channel.DEFAULT - will shutdown all services <BR>
+- * Channel.MBR_RX_SEQ - stops the membership receiver <BR>
+- * Channel.MBR_TX_SEQ - stops the membership broadcaster <BR>
+- * Channel.SND_TX_SEQ - stops the replication transmitter<BR>
+- * Channel.SND_RX_SEQ - stops the replication receiver<BR>
+- * @throws ChannelException if a startup error occurs or the service is already started.
+- * @see Channel
+- */
+- public void stop(int svc) throws ChannelException;
+-
+- public void fireInterceptorEvent(InterceptorEvent event);
+-
+- interface InterceptorEvent {
+- int getEventType();
+- String getEventTypeDesc();
+- ChannelInterceptor getInterceptor();
+- }
+-
+-
+-}
+Index: java/org/apache/catalina/tribes/ChannelException.java
+===================================================================
+--- java/org/apache/catalina/tribes/ChannelException.java (revision 590752)
++++ java/org/apache/catalina/tribes/ChannelException.java (working copy)
+@@ -1,178 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes;
+-
+-import java.util.ArrayList;
+-
+-/**
+- * Channel Exception<br>
+- * A channel exception is thrown when an internal error happens
+- * somewhere in the channel. <br>
+- * When a global error happens, the cause can be retrieved using <code>getCause()</code><br><br>
+- * If an application is sending a message and some of the recipients fail to receive it,
+- * the application can retrieve what recipients failed by using the <code>getFaultyMembers()</code>
+- * method. This way, an application will always know if a message was delivered successfully or not.
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-
+-public class ChannelException extends Exception {
+- /**
+- * Empty list to avoid reinstatiating lists
+- */
+- protected static final FaultyMember[] EMPTY_LIST = new FaultyMember[0];
+- /*
+- * Holds a list of faulty members
+- */
+- private ArrayList faultyMembers=null;
+-
+- /**
+- * Constructor, creates a ChannelException
+- * @see java.lang.Exception#Exception()
+- */
+- public ChannelException() {
+- super();
+- }
+-
+- /**
+- * Constructor, creates a ChannelException with an error message
+- * @see java.lang.Exception#Exception(String)
+- */
+- public ChannelException(String message) {
+- super(message);
+- }
+-
+- /**
+- * Constructor, creates a ChannelException with an error message and a cause
+- * @param message String
+- * @param cause Throwable
+- * @see java.lang.Exception#Exception(String,Throwable)
+- */
+- public ChannelException(String message, Throwable cause) {
+- super(message, cause);
+- }
+-
+- /**
+- * Constructor, creates a ChannelException with a cause
+- * @param cause Throwable
+- * @see java.lang.Exception#Exception(Throwable)
+- */
+- public ChannelException(Throwable cause) {
+- super(cause);
+- }
+-
+- /**
+- * Returns the message for this exception
+- * @return String
+- * @see java.lang.Exception#getMessage()
+- */
+- public String getMessage() {
+- StringBuffer buf = new StringBuffer(super.getMessage());
+- if (faultyMembers==null || faultyMembers.size() == 0 ) {
+- buf.append("; No faulty members identified.");
+- } else {
+- buf.append("; Faulty members:");
+- for ( int i=0; i<faultyMembers.size(); i++ ) {
+- FaultyMember mbr = (FaultyMember)faultyMembers.get(i);
+- buf.append(mbr.getMember().getName());
+- buf.append("; ");
+- }
+- }
+- return buf.toString();
+- }
+-
+- /**
+- * Adds a faulty member, and the reason the member failed.
+- * @param mbr Member
+- * @param x Exception
+- */
+- public boolean addFaultyMember(Member mbr, Exception x ) {
+- return addFaultyMember(new FaultyMember(mbr,x));
+- }
+-
+- /**
+- * Adds a list of faulty members
+- * @param mbrs FaultyMember[]
+- */
+- public int addFaultyMember(FaultyMember[] mbrs) {
+- int result = 0;
+- for (int i=0; mbrs!=null && i<mbrs.length; i++ ) {
+- if ( addFaultyMember(mbrs[i]) ) result++;
+- }
+- return result;
+- }
+-
+- /**
+- * Adds a faulty member
+- * @param mbr FaultyMember
+- */
+- public boolean addFaultyMember(FaultyMember mbr) {
+- if ( this.faultyMembers==null ) this.faultyMembers = new ArrayList();
+- if ( !faultyMembers.contains(mbr) ) return faultyMembers.add(mbr);
+- else return false;
+- }
+-
+- /**
+- * Returns an array of members that failed and the reason they failed.
+- * @return FaultyMember[]
+- */
+- public FaultyMember[] getFaultyMembers() {
+- if ( this.faultyMembers==null ) return EMPTY_LIST;
+- return (FaultyMember[])faultyMembers.toArray(new FaultyMember[faultyMembers.size()]);
+- }
+-
+- /**
+- *
+- * <p>Title: FaultyMember class</p>
+- *
+- * <p>Description: Represent a failure to a specific member when a message was sent
+- * to more than one member</p>
+- *
+- * @author Filip Hanik
+- * @version 1.0
+- */
+- public static class FaultyMember {
+- protected Exception cause;
+- protected Member member;
+- public FaultyMember(Member mbr, Exception x) {
+- this.member = mbr;
+- this.cause = x;
+- }
+-
+- public Member getMember() {
+- return member;
+- }
+-
+- public Exception getCause() {
+- return cause;
+- }
+-
+- public String toString() {
+- return "FaultyMember:"+member.toString();
+- }
+-
+- public int hashCode() {
+- return (member!=null)?member.hashCode():0;
+- }
+-
+- public boolean equals(Object o) {
+- if (member==null || (!(o instanceof FaultyMember)) || (((FaultyMember)o).member==null)) return false;
+- return member.equals(((FaultyMember)o).member);
+- }
+- }
+-
+-}
+Index: java/org/apache/catalina/tribes/MessageListener.java
+===================================================================
+--- java/org/apache/catalina/tribes/MessageListener.java (revision 590752)
++++ java/org/apache/catalina/tribes/MessageListener.java (working copy)
+@@ -1,43 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes;
+-
+-/**
+- *
+- * <p>Title: MessageListener</p>
+- *
+- * <p>Description: The listener to be registered with the ChannelReceiver, internal Tribes component</p>
+- *
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-
+-public interface MessageListener {
+-
+- /**
+- * Receive a message from the IO components in the Channel stack
+- * @param msg ChannelMessage
+- */
+- public void messageReceived(ChannelMessage msg);
+-
+- public boolean accept(ChannelMessage msg);
+-
+- public boolean equals(Object listener);
+-
+- public int hashCode();
+-
+-}
+Index: java/org/apache/catalina/tribes/util/Arrays.java
+===================================================================
+--- java/org/apache/catalina/tribes/util/Arrays.java (revision 590752)
++++ java/org/apache/catalina/tribes/util/Arrays.java (working copy)
+@@ -1,221 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.util;
+-
+-import java.util.ArrayList;
+-import java.util.List;
+-
+-import org.apache.catalina.tribes.ChannelMessage;
+-import org.apache.catalina.tribes.Member;
+-import org.apache.catalina.tribes.UniqueId;
+-import org.apache.catalina.tribes.group.AbsoluteOrder;
+-import org.apache.catalina.tribes.membership.MemberImpl;
+-import org.apache.catalina.tribes.membership.Membership;
+-import java.io.UnsupportedEncodingException;
+-import org.apache.juli.logging.Log;
+-import org.apache.juli.logging.LogFactory;
+-import java.util.StringTokenizer;
+-
+-/**
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public class Arrays {
+- protected static Log log = LogFactory.getLog(Arrays.class);
+-
+- public static boolean contains(byte[] source, int srcoffset, byte[] key, int keyoffset, int length) {
+- if ( srcoffset < 0 || srcoffset >= source.length) throw new ArrayIndexOutOfBoundsException("srcoffset is out of bounds.");
+- if ( keyoffset < 0 || keyoffset >= key.length) throw new ArrayIndexOutOfBoundsException("keyoffset is out of bounds.");
+- if ( length > (key.length-keyoffset) ) throw new ArrayIndexOutOfBoundsException("not enough data elements in the key, length is out of bounds.");
+- //we don't have enough data to validate it
+- if ( length > (source.length-srcoffset) ) return false;
+- boolean match = true;
+- int pos = keyoffset;
+- for ( int i=srcoffset; match && i<length; i++ ) {
+- match = (source[i] == key[pos++]);
+- }
+- return match;
+- }
+-
+- public static String toString(byte[] data) {
+- return toString(data,0,data!=null?data.length:0);
+- }
+-
+- public static String toString(byte[] data, int offset, int length) {
+- StringBuffer buf = new StringBuffer("{");
+- if ( data != null && length > 0 ) {
+- buf.append(data[offset++]);
+- for (int i = offset; i < length; i++) {
+- buf.append(", ").append(data[i]);
+- }
+- }
+- buf.append("}");
+- return buf.toString();
+- }
+-
+- public static String toString(Object[] data) {
+- return toString(data,0,data!=null?data.length:0);
+- }
+-
+- public static String toString(Object[] data, int offset, int length) {
+- StringBuffer buf = new StringBuffer("{");
+- if ( data != null && length > 0 ) {
+- buf.append(data[offset++]);
+- for (int i = offset; i < length; i++) {
+- buf.append(", ").append(data[i]);
+- }
+- }
+- buf.append("}");
+- return buf.toString();
+- }
+-
+- public static String toNameString(Member[] data) {
+- return toNameString(data,0,data!=null?data.length:0);
+- }
+-
+- public static String toNameString(Member[] data, int offset, int length) {
+- StringBuffer buf = new StringBuffer("{");
+- if ( data != null && length > 0 ) {
+- buf.append(data[offset++].getName());
+- for (int i = offset; i < length; i++) {
+- buf.append(", ").append(data[i].getName());
+- }
+- }
+- buf.append("}");
+- return buf.toString();
+- }
+-
+- public static int add(int[] data) {
+- int result = 0;
+- for (int i=0;i<data.length; i++ ) result += data[i];
+- return result;
+- }
+-
+- public static UniqueId getUniqudId(ChannelMessage msg) {
+- return new UniqueId(msg.getUniqueId());
+- }
+-
+- public static UniqueId getUniqudId(byte[] data) {
+- return new UniqueId(data);
+- }
+-
+- public static boolean equals(byte[] o1, byte[] o2) {
+- return java.util.Arrays.equals(o1,o2);
+- }
+-
+- public static boolean equals(Object[] o1, Object[] o2) {
+- boolean result = o1.length == o2.length;
+- if ( result ) for (int i=0; i<o1.length && result; i++ ) result = o1[i].equals(o2[i]);
+- return result;
+- }
+-
+- public static boolean sameMembers(Member[] m1, Member[] m2) {
+- AbsoluteOrder.absoluteOrder(m1);
+- AbsoluteOrder.absoluteOrder(m2);
+- return equals(m1,m2);
+- }
+-
+- public static Member[] merge(Member[] m1, Member[] m2) {
+- AbsoluteOrder.absoluteOrder(m1);
+- AbsoluteOrder.absoluteOrder(m2);
+- ArrayList list = new ArrayList(java.util.Arrays.asList(m1));
+- for (int i=0; i<m2.length; i++) if ( !list.contains(m2[i]) ) list.add(m2[i]);
+- Member[] result = new Member[list.size()];
+- list.toArray(result);
+- AbsoluteOrder.absoluteOrder(result);
+- return result;
+- }
+-
+- public static void fill(Membership mbrship, Member[] m) {
+- for (int i=0; i<m.length; i++ ) mbrship.addMember((MemberImpl)m[i]);
+- }
+-
+- public static Member[] diff(Membership complete, Membership local, MemberImpl ignore) {
+- ArrayList result = new ArrayList();
+- MemberImpl[] comp = complete.getMembers();
+- for ( int i=0; i<comp.length; i++ ) {
+- if ( ignore!=null && ignore.equals(comp[i]) ) continue;
+- if ( local.getMember(comp[i]) == null ) result.add(comp[i]);
+- }
+- return (MemberImpl[])result.toArray(new MemberImpl[result.size()]);
+- }
+-
+- public static Member[] remove(Member[] all, Member remove) {
+- return extract(all,new Member[] {remove});
+- }
+-
+- public static Member[] extract(Member[] all, Member[] remove) {
+- List alist = java.util.Arrays.asList(all);
+- ArrayList list = new ArrayList(alist);
+- for (int i=0; i<remove.length; i++ ) list.remove(remove[i]);
+- return (Member[])list.toArray(new Member[list.size()]);
+- }
+-
+- public static int indexOf(Member member, Member[] members) {
+- int result = -1;
+- for (int i=0; (result==-1) && (i<members.length); i++ )
+- if ( member.equals(members[i]) ) result = i;
+- return result;
+- }
+-
+- public static int nextIndex(Member member, Member[] members) {
+- int idx = indexOf(member,members)+1;
+- if (idx >= members.length ) idx = ((members.length>0)?0:-1);
+-
+-//System.out.println("Next index:"+idx);
+-//System.out.println("Member:"+member.getName());
+-//System.out.println("Members:"+toNameString(members));
+- return idx;
+- }
+-
+- public static int hashCode(byte a[]) {
+- if (a == null)
+- return 0;
+-
+- int result = 1;
+- for (int i=0; i<a.length; i++) {
+- byte element = a[i];
+- result = 31 * result + element;
+- }
+- return result;
+- }
+-
+- public static byte[] fromString(String value) {
+- if ( value == null ) return null;
+- if ( !value.startsWith("{") ) throw new RuntimeException("byte arrays must be represented as {1,3,4,5,6}");
+- StringTokenizer t = new StringTokenizer(value,"{,}",false);
+- byte[] result = new byte[t.countTokens()];
+- for (int i=0; i<result.length; i++ ) result[i] = Byte.parseByte(t.nextToken());
+- return result;
+- }
+-
+-
+-
+- public static byte[] convert(String s) {
+- try {
+- return s.getBytes("ISO-8859-1");
+- }catch (UnsupportedEncodingException ux ) {
+- log.error("Unable to convert ["+s+"] into a byte[] using ISO-8859-1 encoding, falling back to default encoding.");
+- return s.getBytes();
+- }
+- }
+-
+-
+-
+-
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/util/StringManager.java
+===================================================================
+--- java/org/apache/catalina/tribes/util/StringManager.java (revision 590752)
++++ java/org/apache/catalina/tribes/util/StringManager.java (working copy)
+@@ -1,253 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-
+-package org.apache.catalina.tribes.util;
+-
+-import java.text.MessageFormat;
+-import java.util.Hashtable;
+-import java.util.Locale;
+-import java.util.MissingResourceException;
+-import java.util.ResourceBundle;
+-import java.net.URLClassLoader;
+-
+-/**
+- * An internationalization / localization helper class which reduces
+- * the bother of handling ResourceBundles and takes care of the
+- * common cases of message formating which otherwise require the
+- * creation of Object arrays and such.
+- *
+- * <p>The StringManager operates on a package basis. One StringManager
+- * per package can be created and accessed via the getManager method
+- * call.
+- *
+- * <p>The StringManager will look for a ResourceBundle named by
+- * the package name given plus the suffix of "LocalStrings". In
+- * practice, this means that the localized information will be contained
+- * in a LocalStrings.properties file located in the package
+- * directory of the classpath.
+- *
+- * <p>Please see the documentation for java.util.ResourceBundle for
+- * more information.
+- *
+- * @author James Duncan Davidson [duncan(a)eng.sun.com]
+- * @author James Todd [gonzo(a)eng.sun.com]
+- */
+-
+-public class StringManager {
+-
+- /**
+- * The ResourceBundle for this StringManager.
+- */
+-
+- private ResourceBundle bundle;
+-
+- private static org.apache.juli.logging.Log log=
+- org.apache.juli.logging.LogFactory.getLog( StringManager.class );
+-
+- /**
+- * Creates a new StringManager for a given package. This is a
+- * private method and all access to it is arbitrated by the
+- * static getManager method call so that only one StringManager
+- * per package will be created.
+- *
+- * @param packageName Name of package to create StringManager for.
+- */
+-
+- private StringManager(String packageName) {
+- String bundleName = packageName + ".LocalStrings";
+- try {
+- bundle = ResourceBundle.getBundle(bundleName);
+- return;
+- } catch( MissingResourceException ex ) {
+- // Try from the current loader ( that's the case for trusted apps )
+- ClassLoader cl=Thread.currentThread().getContextClassLoader();
+- if( cl != null ) {
+- try {
+- bundle=ResourceBundle.getBundle(bundleName, Locale.getDefault(), cl);
+- return;
+- } catch(MissingResourceException ex2) {
+- }
+- }
+- if( cl==null )
+- cl=this.getClass().getClassLoader();
+-
+- if (log.isDebugEnabled())
+- log.debug("Can't find resource " + bundleName +
+- " " + cl);
+- if( cl instanceof URLClassLoader ) {
+- if (log.isDebugEnabled())
+- log.debug( ((URLClassLoader)cl).getURLs());
+- }
+- }
+- }
+-
+- /**
+- * Get a string from the underlying resource bundle.
+- *
+- * @param key The resource name
+- */
+- public String getString(String key) {
+- return MessageFormat.format(getStringInternal(key), (Object [])null);
+- }
+-
+-
+- protected String getStringInternal(String key) {
+- if (key == null) {
+- String msg = "key is null";
+-
+- throw new NullPointerException(msg);
+- }
+-
+- String str = null;
+-
+- if( bundle==null )
+- return key;
+- try {
+- str = bundle.getString(key);
+- } catch (MissingResourceException mre) {
+- str = "Cannot find message associated with key '" + key + "'";
+- }
+-
+- return str;
+- }
+-
+- /**
+- * Get a string from the underlying resource bundle and format
+- * it with the given set of arguments.
+- *
+- * @param key The resource name
+- * @param args Formatting directives
+- */
+-
+- public String getString(String key, Object[] args) {
+- String iString = null;
+- String value = getStringInternal(key);
+-
+- // this check for the runtime exception is some pre 1.1.6
+- // VM's don't do an automatic toString() on the passed in
+- // objects and barf out
+-
+- try {
+- // ensure the arguments are not null so pre 1.2 VM's don't barf
+- Object nonNullArgs[] = args;
+- for (int i=0; i<args.length; i++) {
+- if (args[i] == null) {
+- if (nonNullArgs==args) nonNullArgs=(Object[])args.clone();
+- nonNullArgs[i] = "null";
+- }
+- }
+-
+- iString = MessageFormat.format(value, nonNullArgs);
+- } catch (IllegalArgumentException iae) {
+- StringBuffer buf = new StringBuffer();
+- buf.append(value);
+- for (int i = 0; i < args.length; i++) {
+- buf.append(" arg[" + i + "]=" + args[i]);
+- }
+- iString = buf.toString();
+- }
+- return iString;
+- }
+-
+- /**
+- * Get a string from the underlying resource bundle and format it
+- * with the given object argument. This argument can of course be
+- * a String object.
+- *
+- * @param key The resource name
+- * @param arg Formatting directive
+- */
+-
+- public String getString(String key, Object arg) {
+- Object[] args = new Object[] {arg};
+- return getString(key, args);
+- }
+-
+- /**
+- * Get a string from the underlying resource bundle and format it
+- * with the given object arguments. These arguments can of course
+- * be String objects.
+- *
+- * @param key The resource name
+- * @param arg1 Formatting directive
+- * @param arg2 Formatting directive
+- */
+-
+- public String getString(String key, Object arg1, Object arg2) {
+- Object[] args = new Object[] {arg1, arg2};
+- return getString(key, args);
+- }
+-
+- /**
+- * Get a string from the underlying resource bundle and format it
+- * with the given object arguments. These arguments can of course
+- * be String objects.
+- *
+- * @param key The resource name
+- * @param arg1 Formatting directive
+- * @param arg2 Formatting directive
+- * @param arg3 Formatting directive
+- */
+-
+- public String getString(String key, Object arg1, Object arg2,
+- Object arg3) {
+- Object[] args = new Object[] {arg1, arg2, arg3};
+- return getString(key, args);
+- }
+-
+- /**
+- * Get a string from the underlying resource bundle and format it
+- * with the given object arguments. These arguments can of course
+- * be String objects.
+- *
+- * @param key The resource name
+- * @param arg1 Formatting directive
+- * @param arg2 Formatting directive
+- * @param arg3 Formatting directive
+- * @param arg4 Formatting directive
+- */
+-
+- public String getString(String key, Object arg1, Object arg2,
+- Object arg3, Object arg4) {
+- Object[] args = new Object[] {arg1, arg2, arg3, arg4};
+- return getString(key, args);
+- }
+- // --------------------------------------------------------------
+- // STATIC SUPPORT METHODS
+- // --------------------------------------------------------------
+-
+- private static Hashtable managers = new Hashtable();
+-
+- /**
+- * Get the StringManager for a particular package. If a manager for
+- * a package already exists, it will be reused, else a new
+- * StringManager will be created and returned.
+- *
+- * @param packageName The package name
+- */
+-
+- public synchronized static StringManager getManager(String packageName) {
+- StringManager mgr = (StringManager)managers.get(packageName);
+-
+- if (mgr == null) {
+- mgr = new StringManager(packageName);
+- managers.put(packageName, mgr);
+- }
+- return mgr;
+- }
+-}
+Index: java/org/apache/catalina/tribes/util/Logs.java
+===================================================================
+--- java/org/apache/catalina/tribes/util/Logs.java (revision 590752)
++++ java/org/apache/catalina/tribes/util/Logs.java (working copy)
+@@ -1,29 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.util;
+-
+-import org.apache.juli.logging.Log;
+-import org.apache.juli.logging.LogFactory;
+-/**
+- *
+- * Simple class that holds references to global loggers
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public class Logs {
+- public static Log MESSAGES = LogFactory.getLog( "org.apache.catalina.tribes.MESSAGES" );
+-}
+Index: java/org/apache/catalina/tribes/util/UUIDGenerator.java
+===================================================================
+--- java/org/apache/catalina/tribes/util/UUIDGenerator.java (revision 590752)
++++ java/org/apache/catalina/tribes/util/UUIDGenerator.java (working copy)
+@@ -1,77 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.catalina.tribes.util;
+-
+-import java.security.SecureRandom;
+-import java.util.Random;
+-
+-/**
+- * simple generation of a UUID
+- * @author Filip Hanik
+- * @version 1.0
+- */
+-public class UUIDGenerator {
+- public static final int UUID_LENGTH = 16;
+- public static final int UUID_VERSION = 4;
+- public static final int BYTES_PER_INT = 4;
+- public static final int BITS_PER_BYTE = 8;
+-
+- protected static SecureRandom secrand = null;
+- protected static Random rand = new Random(System.currentTimeMillis());
+- static {
+- secrand = new SecureRandom();
+- secrand.setSeed(rand.nextLong());
+- }
+-
+- public static byte[] randomUUID(boolean secure) {
+- byte[] result = new byte[UUID_LENGTH];
+- return randomUUID(secure,result,0);
+- }
+-
+- public static byte[] randomUUID(boolean secure, byte[] into, int offset) {
+- if ( (offset+UUID_LENGTH)>into.length )
+- throw new ArrayIndexOutOfBoundsException("Unable to fit "+UUID_LENGTH+" bytes into the array. length:"+into.length+" required length:"+(offset+UUID_LENGTH));
+- Random r = (secure&&(secrand!=null))?secrand:rand;
+- nextBytes(into,offset,UUID_LENGTH,r);
+- into[6+offset] &= 0x0F;
+- into[6+offset] |= (UUID_VERSION << 4);
+- into[8+offset] &= 0x3F; //0011 1111
+- into[8+offset] |= 0x80; //1000 0000
+- return into;
+- }
+-
+- /**
+- * Same as java.util.Random.nextBytes except this one we dont have to allocate a new byte array
+- * @param into byte[]
+- * @param offset int
+- * @param length int
+- * @param r Random
+- */
+- public static void nextBytes(byte[] into, int offset, int length, Random r) {
+- int numRequested = length;
+- int numGot = 0, rnd = 0;
+- while (true) {
+- for (int i = 0; i < BYTES_PER_INT; i++) {
+- if (numGot == numRequested) return;
+- rnd = (i == 0 ? r.nextInt() : rnd >> BITS_PER_BYTE);
+- into[offset+numGot] = (byte) rnd;
+- numGot++;
+- }
+- }
+- }
+-
+-}
+\ No newline at end of file
+Index: java/org/apache/catalina/tribes/ChannelReceiver.java
+===================================================================
+--- java/org/apache/catalina/tribes/ChannelReceiver.java (revision 590752)
++++ java/org/apache/catalina/tribes/ChannelReceiver.java (working copy)
+@@ -1,75 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.catalina.tribes;
+-
+-
+-/**
+- * ChannelReceiver Interface<br>
+- * The <code>ChannelReceiver</code> interface is the data receiver component
+- * at the bottom layer, the IO layer (for layers see the javadoc for the {@link Channel} interface).
+- * This class may optionally implement a thread pool for parallel processing of incoming messages.
+- * @author Filip Hanik
+- * @version $Revision$, $Date$
+- */
+-public interface ChannelReceiver extends Heartbeat {
+- /**
+- * Start listening for incoming messages on the host/port
+- * @throws java.io.IOException
+- */
+- public void start() throws java.io.IOException;
+-
+- /**
+- * Stop listening for messages
+- */
+- public void stop();
+-
+- /**
+- * String representation of the IPv4 or IPv6 address that this host is listening
+- * to.
+- * @return the host that this receiver is listening to
+- */
+- public String getHost();
+-
+-
+- /**
+- * Returns the listening port
+- * @return port
+- */
+- public int getPort();
+-
+- /**
+- * Returns the secure listening port
+- * @return port, -1 if a secure port is not activated
+- */
+- public int getSecurePort();
+-
+- /**
+- * Sets the message listener to receive notification of incoming
+- * @param listener MessageListener
+- * @see MessageListener
+- */
+- public void setMessageListener(MessageListener listener);
+-
+- /**
+- * Returns the message listener that is associated with this receiver
+- * @return MessageListener
+- * @see MessageListener
+- */
+- public MessageListener getMessageListener();
+-
+-}
+Index: build.xml
+===================================================================
+--- build.xml (revision 590988)
++++ build.xml (working copy)
+@@ -22,7 +22,9 @@
+
+ <!-- See "build.properties.sample" in the top level directory for all -->
+ <!-- property values you must customize for successful building!!! -->
++<!--
+ <property file="${user.home}/build.properties"/>
++ -->
+ <property file="build.properties"/>
+
+ <property file="build.properties.default"/>
+@@ -60,8 +62,6 @@
+ <property name="jsp-api.jar" value="${tomcat.build}/lib/jsp-api.jar"/>
+ <property name="el-api.jar" value="${tomcat.build}/lib/el-api.jar"/>
+ <property name="catalina.jar" value="${tomcat.build}/lib/catalina.jar"/>
+- <property name="catalina-tribes.jar" value="${tomcat.build}/lib/catalina-tribes.jar"/>
+- <property name="catalina-ha.jar" value="${tomcat.build}/lib/catalina-ha.jar"/>
+ <property name="catalina-ant.jar" value="${tomcat.build}/lib/catalina-ant.jar"/>
+ <property name="catalina-ant-jmx.jar" value="${tomcat.build}/lib/catalina-ant-jmx.jar"/>
+ <property name="tomcat-coyote.jar" value="${tomcat.build}/lib/tomcat-coyote.jar"/>
+@@ -225,34 +225,12 @@
+ <exclude name="**/LocalStrings_*" />
+ <!-- Modules -->
+ <exclude name="org/apache/catalina/ant/**" />
+- <exclude name="org/apache/catalina/cluster/**" />
+- <exclude name="org/apache/catalina/ha/**" />
+- <exclude name="org/apache/catalina/tribes/**" />
+ <exclude name="org/apache/catalina/launcher/**" />
+ <exclude name="org/apache/catalina/storeconfig/**" />
+ <exclude name="org/apache/naming/factory/webservices/**" />
+ </fileset>
+ </jar>
+
+- <!-- Catalina GroupCom/Tribes JAR File -->
+- <jar jarfile="${catalina-tribes.jar}">
+- <fileset dir="${tomcat.classes}">
+- <exclude name="**/package.html" />
+- <exclude name="**/LocalStrings_*" />
+- <!-- Modules -->
+- <include name="org/apache/catalina/tribes/**" />
+- </fileset>
+- </jar>
+- <!-- Catalina Cluster/HA JAR File -->
+- <jar jarfile="${catalina-ha.jar}">
+- <fileset dir="${tomcat.classes}">
+- <exclude name="**/package.html" />
+- <exclude name="**/LocalStrings_*" />
+- <!-- Modules -->
+- <include name="org/apache/catalina/ha/**" />
+- </fileset>
+- </jar>
+-
+ <!-- Catalina Ant Tasks JAR File -->
+ <jar jarfile="${catalina-ant.jar}">
+ <fileset dir="${tomcat.classes}">
+@@ -391,17 +369,8 @@
+ <param name="relative-path" expression=".."/>
+ </style>
+
+- <style basedir="webapps/docs/tribes"
+- destdir="${tomcat.build}/webapps/docs/tribes"
+- extension=".html"
+- style="webapps/docs/tomcat-docs.xsl"
+- excludes="project.xml"
+- includes="*.xml">
+- <param name="relative-path" expression=".."/>
+- </style>
+-
+- <!-- Print friendly version -->
+- <mkdir dir="${tomcat.build}/webapps/docs/printer" />
++ <!-- Print friendly version -->
++ <mkdir dir="${tomcat.build}/webapps/docs/printer" />
+ <copy todir="${tomcat.build}/webapps/docs/printer">
+ <fileset dir=".">
+ <include name="BUILDING.txt"/>
+Index: webapps/docs/not-supported.xml
+===================================================================
+--- webapps/docs/not-supported.xml (revision 0)
++++ webapps/docs/not-supported.xml (revision 0)
+@@ -0,0 +1,41 @@
++<?xml version="1.0"?>
++<!--
++ 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.
++-->
++<!DOCTYPE document [
++ <!ENTITY project SYSTEM "project.xml">
++]>
++<document url="not-supported.html">
++
++ &project;
++
++ <properties>
++ <author email="jclere(a)readhat.com">Jean-Frederic Clere</author>
++ <title>Not supported in this distribution</title>
++ </properties>
++
++<body>
++
++
++<section name="Introduction">
++
++<p>This feature is not supported is this distribution.</p>
++
++</section>
++
++</body>
++
++</document>
+Index: webapps/docs/tribes/leader-election-initiate-election.jpg
+===================================================================
+Cannot display: file marked as a binary type.
+svn:mime-type = application/octet-stream
+Index: webapps/docs/tribes/setup.xml
+===================================================================
+--- webapps/docs/tribes/setup.xml (revision 590752)
++++ webapps/docs/tribes/setup.xml (working copy)
+@@ -1,37 +0,0 @@
+-<?xml version="1.0"?>
+-<!--
+- 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.
+--->
+-<!DOCTYPE document [
+- <!ENTITY project SYSTEM "project.xml">
+-]>
+-<document url="introduction.html">
+-
+- &project;
+-
+- <properties>
+- <author email="fhanik(a)apache.org">Filip Hanik</author>
+- <title>Apache Tribes - Configuration</title>
+- </properties>
+-
+-<body>
+-
+-
+-<section name="Configuration Overview">
+-</section>
+-</body>
+-
+-</document>
+Index: webapps/docs/tribes/introduction.xml
+===================================================================
+--- webapps/docs/tribes/introduction.xml (revision 590752)
++++ webapps/docs/tribes/introduction.xml (working copy)
+@@ -1,271 +0,0 @@
+-<?xml version="1.0"?>
+-<!--
+- 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.
+--->
+-<!DOCTYPE document [
+- <!ENTITY project SYSTEM "project.xml">
+-]>
+-<document url="tribes.html">
+-
+- &project;
+-
+- <properties>
+- <author email="fhanik(a)apache.org">Filip Hanik</author>
+- <title>Apache Tribes - Introduction</title>
+- </properties>
+-
+-<body>
+-
+-
+-<section name="Quick Start">
+-
+- <p>Apache Tribes is a group or peer-to-peer communcation framework that enables you to easily connect
+- your remote objects to communicate with each other.
+- </p>
+- <ul>
+- <li>Import: <code>org.apache.catalina.tribes.Channel</code></li>
+- <li>Import: <code>org.apache.catalina.tribes.Member</code></li>
+- <li>Import: <code>org.apache.catalina.tribes.MembershipListener</code></li>
+- <li>Import: <code>org.apache.catalina.tribes.ChannelListener</code></li>
+- <li>Import: <code>org.apache.catalina.tribes.group.GroupChannel</code></li>
+- <li>Create a class that implements: <code>org.apache.catalina.tribes.ChannelListener</code></li>
+- <li>Create a class that implements: <code>org.apache.catalina.tribes.MembershipListener</code></li>
+- <li>Simple class to demonstrate how to send a message:
+- <source>
+- //create a channel
+- Channel myChannel = new GroupChannel();
+-
+- //create my listeners
+- ChannelListener msgListener = new MyMessageListener();
+- MembershipListener mbrListener = new MyMemberListener();
+-
+- //attach the listeners to the channel
+- myChannel.addMembershipListener(mbrListener);
+- myChannel.addChannelListener(msgListener);
+-
+- //start the channel
+- myChannel.start(Channel.DEFAULT);
+-
+- //create a message to be sent, message must implement java.io.Serializable
+- //for performance reasons you probably want them to implement java.io.Externalizable
+- Serializable myMsg = new MyMessage();
+-
+- //retrieve my current members
+- Member[] group = myChannel.getMembers();
+-
+- //send the message
+- channel.send(group,myMsg,Channel.SEND_OPTIONS_DEFAULT);
+- </source>
+- </li>
+- </ul>
+- <p>
+- Simple yeah? There is a lot more to Tribes than we have shown, hopefully the docs will be able
+- to explain more to you. Remember, that we are always interested in suggestions, improvements, bug fixes
+- and anything that you think would help this project.
+- </p>
+- <p>
+- Note: Tribes is currently built for JDK1.5, you can run on JDK1.4 by a small modifications to locks used from the <code>java.util.concurrent</code> package.
+- </p>
+-</section>
+-
+-
+-<section name="What is Tribes">
+- <p>
+- Tribes is a messaging framework with group communication abilities. Tribes allows you to send and receive
+- messages over a network, it also allows for dynamic discovery of other nodes in the network.<br/>
+- And that is the short story, it really is as simple as that. What makes Tribes useful and unique will be
+- described in the section below.<br/>
+- </p>
+- <p>
+- The Tribes module was started early 2006 and a small part of the code base comes from the clustering module
+- that has been existing since 2003 or 2004.
+- The current cluster implementation has several short comings and many work arounds were created due
+- to the complexity in group communication. Long story short, what should have been two modules a long time
+- ago, will be now. Tribes takes out the complexity of messaging from the replication module and becomes
+- a fully independent and highly flexible group communication module.<br/>
+- </p>
+- <p>
+- In Tomcat the old <code>modules/cluster</code> has now become <code>modules/groupcom</code>(Tribes) and
+- <code>modules/ha</code> (replication). This will allow development to proceed and let the developers
+- focus on the issues they are actually working on rather than getting boggled down in details of a module
+- they are not interested in. The understanding is that both communication and replication are complex enough,
+- and when trying to develop them in the same module, well you know, it becomes a cluster :)<br/>
+- </p>
+- <p>
+- Tribes allows for guaranteed messaging, and can be customized in many ways. Why is this important?<br/>
+- Well, you as a developer want to know that the messages you are sending are reaching their destination.
+- More than that, if a message doesn't reach its destination, the application on top of Tribes will be notified
+- that the message was never sent, and what node it failed.
+- </p>
+-
+-</section>
+-
+-<section name="Why another messaging framework">
+- <p>
+- I am a big fan of reusing code and would never dream of developing something if someone else has already
+- done it and it was available to me and the community I try to serve.<br/>
+- When I did my research to improve the clustering module I was constantly faced with a few obstacles:<br/>
+- 1. The framework wasn't flexible enough<br/>
+- 2. The framework was licensed in a way that neither I nor the community could use it<br/>
+- 3. Several features that I needed were missing<br/>
+- 4. Messaging was guaranteed, but no feedback was reported to me<br/>
+- 5. The semantics of my message delivery had to be configured before runtime<br/>
+- And the list continues...
+- </p>
+- <p>
+- So I came up with Tribes, to address these issues and other issues that came along.
+- When designing Tribes I wanted to make sure I didn't lose any of the flexibility and
+- delivery semantics that the existing frameworks already delivered. The goal was to create a framework
+- that could do everything that the others already did, but to provide more flexibility for the application
+- developer. In the next section will give you the high level overview of what features tribes offers or will offer.
+- </p>
+-</section>
+-
+-<section name="Feature Overview">
+- <p>
+- To give you an idea of the feature set I will list it out here.
+- Some of the features are not yet completed, if that is the case they are marked accordingly.
+- </p>
+- <p>
+- <b>Pluggable modules</b><br/>
+- Tribes is built using interfaces. Any of the modules or components that are part of Tribes can be swapped out
+- to customize your own Tribes implementation.
+- </p>
+- <p>
+- <b>Guaranteed Messaging</b><br/>
+- In the default implementation of Tribes uses TCP for messaging. TCP already has guaranteed message delivery
+- and flow control built in. I believe that the performance of Java TCP, will outperform an implementation of
+- Java/UDP/flow-control/message guarantee since the logic happens further down the stack.<br/>
+- Tribes supports both non-blocking and blocking IO operations. The recommended setting is to use non blocking
+- as it promotes better parallelism when sending and receiving messages. The blocking implementation is available
+- for those platforms where NIO is still a trouble child.
+- </p>
+- <p>
+- <b>Different Guarantee Levels</b><br/>
+- There are three different levels of delivery guarantee when a message is sent.<br/>
+- <ol>
+- <li>IO Based send guarantee. - fastest, least reliable<br/>
+- This means that Tribes considers the message transfer to be successful
+- if the message was sent to the socket send buffer and accepted.<br/>
+- On blocking IO, this would be <code>socket.getOutputStream().write(msg)</code><br/>
+- On non blocking IO, this would be <code>socketChannel.write()</code>, and the buffer byte buffer gets emptied
+- followed by a <code>socketChannel.read()</code> to ensure the channel still open.
+- The <code>read()</code> has been added since <code>write()</code> will succeed if the connection has been "closed"
+- when using NIO.
+- </li>
+- <li>ACK based. - recommended, guaranteed delivery<br/>
+- When the message has been received on a remote node, an ACK is sent back to the sender,
+- indicating that the message was received successfully.
+- </li>
+- <li>SYNC_ACK based. - guaranteed delivery, guaranteed processed, slowest<br/>
+- When the message has been received on a remote node, the node will process
+- the message and if the message was processed successfully, an ACK is sent back to the sender
+- indicating that the message was received and processed successfully.
+- If the message was received, but processing it failed, an ACK_FAIL will be sent back
+- to the sender. This is a unique feature that adds an incredible amount value to the application
+- developer. Most frameworks here will tell you that the message was delivered, and the application
+- developer has to build in logic on whether the message was actually processed properly by the application
+- on the remote node. If configured, Tribes will throw an exception when it receives an ACK_FAIL
+- and associate that exception with the member that didn't process the message.
+- </li>
+- </ol>
+- You can of course write even more sophisticated guarantee levels, and some of them will be mentioned later on
+- in the documentation. One mentionable level would be a 2-Phase-Commit, where the remote applications don't receive
+- the message until all nodes have received the message. Sort of like a all-or-nothing protocol.
+- </p>
+- <p>
+- <b>Per Message Delivery Attributes</b><br/>
+- Perhaps the feature that makes Tribes stand out from the crowd of group communication frameworks.
+- Tribes enables you to send to decide what delivery semantics a message transfer should have on a per
+- message basis. Meaning, that your messages are not delivered based on some static configuration
+- that remains fixed after the message framework has been started.<br/>
+- To give you an example of how powerful this feature is, I'll try to illustrate it with a simple example.
+- Imagine you need to send 10 different messsages, you could send the the following way:
+- <source>
+- Message_1 - asynchronous and fast, no guarantee required, fire and forget
+- Message_2 - all-or-nothing, either all receivers get it, or none.
+- Message_3 - encrypted and SYNC_ACK based
+- Message_4 - asynchronous, SYNC_ACK and call back when the message is processed on the remote nodes
+- Message_5 - totally ordered, this message should be received in the same order on all nodes that have been
+- send totally ordered
+- Message_6 - asynchronous and totally ordered
+- Message_7 - RPC message, send a message, wait for all remote nodes to reply before returning
+- Message_8 - RPC message, wait for the first reply
+- Message_9 - RPC message, asynchronous, don't wait for a reply, collect them via a callback
+- Message_10- sent to a member that is not part of this group
+- </source>
+- As you can imagine by now, these are just examples. The number of different semantics you can apply on a
+- per-message-basis is almost limitless. Tribes allows you to set up to 28 different on a message
+- and then configure Tribes to what flag results in what action on the message.<br/>
+- Imagine a shared transactional cache, probably >90% are reads, and the dirty reads should be completely
+- unordered and delivered as fast as possible. But transactional writes on the other hand, have to
+- be ordered so that no cache gets corrupted. With tribes you would send the write messages totally ordered,
+- while the read messages you simple fire to achieve highest throughput.<br/>
+- There are probably better examples on how this powerful feature can be used, so use your imagination and
+- your experience to think of how this could benefit you in your application.
+- </p>
+- <p>
+- <b>Interceptor based message processing</b><br/>
+- Tribes uses a customizable interceptor stack to process messages that are sent and received.<br/>
+- <i>So what, all frameworks have this!</i><br/>
+- Yes, but in Tribes interceptors can react to a message based on the per-message-attributes
+- that are sent runtime. Meaning, that if you add a encryption interceptor that encrypts message
+- you can decide if this interceptor will encrypt all messages, or only certain messages that are decided
+- by the applications running on top of Tribes.<br/>
+- This is how Tribes is able to send some messages totally ordered and others fire and forget style
+- like the example above.<br/>
+- The number of interceptors that are available will keep growing, and we would appreciate any contributions
+- that you might have.
+- </p>
+- <p>
+- <b>Threadless Interceptor stack</b>
+- The interceptor don't require any separate threads to perform their message manipulation.<br/>
+- Messages that are sent will piggy back on the thread that is sending them all the way through transmission.
+- The exception is the <code>MessageDispatchInterceptor</code> that will queue up the message
+- and send it on a separate thread for asynchronous message delivery.
+- Messages received are controlled by a thread pool in the <code>receiver</code> component.<br/>
+- The channel object can send a <code>heartbeat()</code> through the interceptor stack to allow
+- for timeouts, cleanup and other events.<br/>
+- The <code>MessageDispatchInterceptor</code> is the only interceptor that is configured by default.
+- </p>
+- <p>
+- <b>Parallel Delivery</b><br/>
+- Tribes support parallel delivery of messages. Meaning that node_A could send three messages to node_B in
+- parallel. This feature becomes useful when sending messages with different delivery semantics.
+- Otherwise if Message_1 was sent totally ordered, Message_2 would have to wait for that message to complete.<br/>
+- Through NIO, Tribes is also able to send a message to several receivers at the same time on the same thread.
+- </p>
+- <p>
+- <b>Silent Member Messaging</b><br/>
+- With Tribes you are able to send messages to members that are not in your group.
+- So by default, you can already send messages over a wide area network, even though the dynamic discover
+- module today is limited to local area networks by using multicast for dynamic node discovery.
+- Of course, the membership component will be expanded to support WAN memberships in the future.
+- But this is very useful, when you want to hide members from the rest of the group and only communicate with them
+- </p>
+-</section>
+-
+-<section name="Where can I get Tribes">
+- <p>
+-
+- </p>
+-
+-
+-</section>
+-
+-</body>
+-
+-</document>
+Index: webapps/docs/tribes/tomcat-docs.xsl
+===================================================================
+--- webapps/docs/tribes/tomcat-docs.xsl (revision 590752)
++++ webapps/docs/tribes/tomcat-docs.xsl (working copy)
+@@ -1,452 +0,0 @@
+-<?xml version="1.0" encoding="ISO-8859-1"?>
+-<!--
+- 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.
+--->
+-<!-- Content Stylesheet for "tomcat-docs" Documentation -->
+-
+-<!-- $Id$ -->
+-
+-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+- version="1.0">
+-
+-
+- <!-- Output method -->
+- <xsl:output method="html"
+- encoding="iso-8859-1"
+- indent="no"/>
+-
+-
+- <!-- Defined parameters (overrideable) -->
+- <xsl:param name="home-name" select="'The Tomcat Project'"/>
+- <xsl:param name="home-href" select="'http://tomcat.apache.org/'"/>
+- <xsl:param name="home-logo" select="'/images/tomcat.gif'"/>
+- <xsl:param name="printer-logo" select="'/images/printer.gif'"/>
+- <xsl:param name="apache-logo" select="'/images/asf-logo.gif'"/>
+- <xsl:param name="relative-path" select="'.'"/>
+- <xsl:param name="void-image" select="'/images/void.gif'"/>
+- <xsl:param name="project-menu" select="'menu'"/>
+- <xsl:param name="standalone" select="''"/>
+- <xsl:param name="buglink" select="'http://issues.apache.org/bugzilla/show_bug.cgi?id='"/>
+-
+- <!-- Defined variables (non-overrideable) -->
+- <xsl:variable name="body-bg" select="'#ffffff'"/>
+- <xsl:variable name="body-fg" select="'#000000'"/>
+- <xsl:variable name="body-link" select="'#525D76'"/>
+- <xsl:variable name="banner-bg" select="'#525D76'"/>
+- <xsl:variable name="banner-fg" select="'#ffffff'"/>
+- <xsl:variable name="sub-banner-bg" select="'#828DA6'"/>
+- <xsl:variable name="sub-banner-fg" select="'#ffffff'"/>
+- <xsl:variable name="source-color" select="'#023264'"/>
+- <xsl:variable name="attributes-color" select="'#023264'"/>
+- <xsl:variable name="table-th-bg" select="'#039acc'"/>
+- <xsl:variable name="table-td-bg" select="'#a0ddf0'"/>
+-
+- <!-- Process an entire document into an HTML page -->
+- <xsl:template match="document">
+- <xsl:variable name="project"
+- select="document('project.xml')/project"/>
+- <html>
+- <head>
+- <title><xsl:value-of select="project/title"/> - <xsl:value-of select="properties/title"/></title>
+- <xsl:for-each select="properties/author">
+- <xsl:variable name="name">
+- <xsl:value-of select="."/>
+- </xsl:variable>
+- <xsl:variable name="email">
+- <xsl:value-of select="@email"/>
+- </xsl:variable>
+- <meta name="author" value="{$name}"/>
+- <meta name="email" value="{$email}"/>
+- </xsl:for-each>
+- </head>
+-
+- <body bgcolor="{$body-bg}" text="{$body-fg}" link="{$body-link}"
+- alink="{$body-link}" vlink="{$body-link}">
+-
+- <table border="0" width="100%" cellspacing="0">
+-
+- <xsl:comment>PAGE HEADER</xsl:comment>
+- <tr>
+- <td>
+- <xsl:if test="project/logo">
+- <xsl:variable name="alt">
+- <xsl:value-of select="project/logo"/>
+- </xsl:variable>
+- <xsl:variable name="home">
+- <xsl:value-of select="project/@href"/>
+- </xsl:variable>
+- <xsl:variable name="src">
+- <!--<xsl:value-of select="$relative-path"/>--><xsl:value-of select="project/logo/@href"/>
+- </xsl:variable>
+-
+- <xsl:comment>PROJECT LOGO</xsl:comment>
+- <a href="{$home}">
+- <img src="{$src}" align="right" alt="{$alt}" border="0"/>
+- </a>
+- </xsl:if>
+- </td>
+- <td>
+- <font face="arial,helvetica,sanserif">
+- <h1><xsl:value-of select="$project/title"/></h1>
+- </font>
+- </td>
+- <td>
+- <xsl:comment>APACHE LOGO</xsl:comment>
+- <xsl:variable name="src">
+- <xsl:value-of select="$relative-path"/><xsl:value-of select="$apache-logo"/>
+- </xsl:variable>
+- <a href="http://www.apache.org/">
+- <img src="http://tomcat.apache.org/tomcat-5.5-doc/images/asf-logo.gif" align="right" alt="Apache Logo" border="0"/>
+- </a>
+- </td>
+- </tr>
+- </table>
+-
+- <table border="0" width="100%" cellspacing="4">
+-
+- <xsl:comment>HEADER SEPARATOR</xsl:comment>
+- <tr>
+- <td colspan="2">
+- <hr noshade="noshade" size="1"/>
+- </td>
+- </tr>
+-
+- <tr>
+-
+- <!-- Don't generate a menu if styling printer friendly docs -->
+- <xsl:if test="$project-menu = 'menu'">
+- <xsl:comment>LEFT SIDE NAVIGATION</xsl:comment>
+- <td width="20%" valign="top" nowrap="true">
+- <xsl:apply-templates select="project/body/menu"/>
+- </td>
+- </xsl:if>
+-
+- <xsl:comment>RIGHT SIDE MAIN BODY</xsl:comment>
+- <td width="80%" valign="top" align="left">
+- <table border="0" width="100%" cellspacing="4">
+- <tr>
+- <td align="left" valign="top">
+- <h1><xsl:value-of select="project/title"/></h1>
+- <h2><xsl:value-of select="properties/title"/></h2>
+- </td>
+- <td align="right" valign="top" nowrap="true">
+- <!-- Add the printer friendly link for docs with a menu -->
+- <xsl:if test="$project-menu = 'menu'">
+- <xsl:variable name="src">
+- <xsl:value-of select="$relative-path"/><xsl:value-of select="$printer-logo"/>
+- </xsl:variable>
+- <xsl:variable name="url">
+- <xsl:value-of select="/document/@url"/>
+- </xsl:variable>
+- <small>
+- <a href="printer/{$url}">
+- <img src="{$src}" border="0" alt="Printer Friendly Version"/>
+- <br />print-friendly<br />version
+- </a>
+- </small>
+- </xsl:if>
+- <xsl:if test="$project-menu != 'menu'">
+- <xsl:variable name="void">
+- <xsl:value-of select="$relative-path"/><xsl:value-of select="$void-image"/>
+- </xsl:variable>
+- <img src="{$void}" width="1" height="1" vspace="0" hspace="0" border="0"/>
+- </xsl:if>
+- </td>
+- </tr>
+- </table>
+- <xsl:apply-templates select="body/section"/>
+- </td>
+-
+- </tr>
+-
+- <xsl:comment>FOOTER SEPARATOR</xsl:comment>
+- <tr>
+- <td colspan="2">
+- <hr noshade="noshade" size="1"/>
+- </td>
+- </tr>
+-
+- <xsl:comment>PAGE FOOTER</xsl:comment>
+- <tr><td colspan="2">
+- <div align="center"><font color="{$body-link}" size="-1"><em>
+- Copyright © 1999-2006, Apache Software Foundation
+- </em></font></div>
+- </td></tr>
+-
+- </table>
+- </body>
+- </html>
+-
+- </xsl:template>
+-
+-
+- <!-- Process a menu for the navigation bar -->
+- <xsl:template match="menu">
+- <p><strong><xsl:value-of select="@name"/></strong></p>
+- <ul>
+- <xsl:apply-templates select="item"/>
+- </ul>
+- </xsl:template>
+-
+-
+- <!-- Process a menu item for the navigation bar -->
+- <xsl:template match="item">
+- <xsl:variable name="href">
+- <xsl:value-of select="@href"/>
+- </xsl:variable>
+- <li><a href="{$href}"><xsl:value-of select="@name"/></a></li>
+- </xsl:template>
+-
+-
+- <!-- Process a documentation section -->
+- <xsl:template match="section">
+- <xsl:variable name="name">
+- <xsl:value-of select="@name"/>
+- </xsl:variable>
+- <table border="0" cellspacing="0" cellpadding="2">
+- <!-- Section heading -->
+- <tr><td bgcolor="{$banner-bg}">
+- <font color="{$banner-fg}" face="arial,helvetica.sanserif">
+- <a name="{$name}">
+- <strong><xsl:value-of select="@name"/></strong></a></font>
+- </td></tr>
+- <!-- Section body -->
+- <tr><td><blockquote>
+- <xsl:apply-templates/>
+- </blockquote></td></tr>
+- </table>
+- </xsl:template>
+-
+-
+- <!-- Process a documentation subsection -->
+- <xsl:template match="subsection">
+- <xsl:variable name="name">
+- <xsl:value-of select="@name"/>
+- </xsl:variable>
+- <table border="0" cellspacing="0" cellpadding="2">
+- <!-- Subsection heading -->
+- <tr><td bgcolor="{$sub-banner-bg}">
+- <font color="{$sub-banner-fg}" face="arial,helvetica.sanserif">
+- <a name="{$name}">
+- <strong><xsl:value-of select="@name"/></strong></a></font>
+- </td></tr>
+- <!-- Subsection body -->
+- <tr><td><blockquote>
+- <xsl:apply-templates/>
+- </blockquote></td></tr>
+- </table>
+- </xsl:template>
+-
+-
+- <!-- Process a source code example -->
+- <xsl:template match="source">
+- <xsl:variable name="void">
+- <xsl:value-of select="$relative-path"/><xsl:value-of select="$void-image"/>
+- </xsl:variable>
+- <div align="left">
+- <table cellspacing="4" cellpadding="0" border="0">
+- <tr>
+- <td bgcolor="{$source-color}" width="1" height="1">
+- <img src="{$void}" width="1" height="1" vspace="0" hspace="0" border="0"/>
+- </td>
+- <td bgcolor="{$source-color}" height="1">
+- <img src="{$void}" width="1" height="1" vspace="0" hspace="0" border="0"/>
+- </td>
+- <td bgcolor="{$source-color}" width="1" height="1">
+- <img src="{$void}" width="1" height="1" vspace="0" hspace="0" border="0"/>
+- </td>
+- </tr>
+- <tr>
+- <td bgcolor="{$source-color}" width="1">
+- <img src="{$void}" width="1" height="1" vspace="0" hspace="0" border="0"/>
+- </td>
+- <td bgcolor="#ffffff" height="1"><pre>
+- <xsl:value-of select="."/>
+- </pre></td>
+- <td bgcolor="{$source-color}" width="1">
+- <img src="{$void}" width="1" height="1" vspace="0" hspace="0" border="0"/>
+- </td>
+- </tr>
+- <tr>
+- <td bgcolor="{$source-color}" width="1" height="1">
+- <img src="{$void}" width="1" height="1" vspace="0" hspace="0" border="0"/>
+- </td>
+- <td bgcolor="{$source-color}" height="1">
+- <img src="{$void}" width="1" height="1" vspace="0" hspace="0" border="0"/>
+- </td>
+- <td bgcolor="{$source-color}" width="1" height="1">
+- <img src="{$void}" width="1" height="1" vspace="0" hspace="0" border="0"/>
+- </td>
+- </tr>
+- </table>
+- </div>
+- </xsl:template>
+-
+-
+- <!-- Process an attributes list with nested attribute elements -->
+- <xsl:template match="attributes">
+- <table border="1" cellpadding="5">
+- <tr>
+- <th width="15%" bgcolor="{$attributes-color}">
+- <font color="#ffffff">Attribute</font>
+- </th>
+- <th width="85%" bgcolor="{$attributes-color}">
+- <font color="#ffffff">Description</font>
+- </th>
+- </tr>
+- <xsl:for-each select="attribute">
+- <tr>
+- <td align="left" valign="center">
+- <xsl:if test="@required = 'true'">
+- <strong><code><xsl:value-of select="@name"/></code></strong>
+- </xsl:if>
+- <xsl:if test="@required != 'true'">
+- <code><xsl:value-of select="@name"/></code>
+- </xsl:if>
+- </td>
+- <td align="left" valign="center">
+- <xsl:apply-templates/>
+- </td>
+- </tr>
+- </xsl:for-each>
+- </table>
+- </xsl:template>
+-
+- <!-- Fix relative links in printer friendly versions of the docs -->
+- <xsl:template match="a">
+- <xsl:variable name="href" select="@href"/>
+- <xsl:choose>
+- <xsl:when test="$standalone = 'standalone'">
+- <xsl:apply-templates/>
+- </xsl:when>
+- <xsl:when test="$project-menu != 'menu' and starts-with(@href,'../')">
+- <a href="../{$href}"><xsl:apply-templates/></a>
+- </xsl:when>
+- <xsl:when test="$project-menu != 'menu' and starts-with(@href,'./') and contains(substring(@href,3),'/')">
+- <a href=".{$href}"><xsl:apply-templates/></a>
+- </xsl:when>
+- <xsl:when test="$project-menu != 'menu' and not(contains(@href,'//')) and not(starts-with(@href,'/')) and not(starts-with(@href,'#')) and contains(@href,'/')">
+- <a href="../{$href}"><xsl:apply-templates/></a>
+- </xsl:when>
+- <xsl:when test="$href != ''">
+- <a href="{$href}"><xsl:apply-templates/></a>
+- </xsl:when>
+- <xsl:otherwise>
+- <xsl:variable name="name" select="@name"/>
+- <a name="{$name}"><xsl:apply-templates/></a>
+- </xsl:otherwise>
+- </xsl:choose>
+- </xsl:template>
+-
+- <!-- Changelog related tags -->
+- <xsl:template match="changelog">
+- <table border="0" cellpadding="2" cellspacing="2">
+- <xsl:apply-templates/>
+- </table>
+- </xsl:template>
+-
+- <xsl:template match="changelog/add">
+- <tr>
+- <xsl:variable name="src"><xsl:value-of select="$relative-path"/>/images/add.gif</xsl:variable>
+- <td><img alt="add" class="icon" src="{$src}"/></td>
+- <td><xsl:apply-templates/></td>
+- </tr>
+- </xsl:template>
+-
+- <xsl:template match="changelog/update">
+- <tr>
+- <xsl:variable name="src"><xsl:value-of select="$relative-path"/>/images/update.gif</xsl:variable>
+- <td><img alt="update" class="icon" src="{$src}"/></td>
+- <td><xsl:apply-templates/></td>
+- </tr>
+- </xsl:template>
+-
+- <xsl:template match="changelog/design">
+- <tr>
+- <xsl:variable name="src"><xsl:value-of select="$relative-path"/>/images/design.gif</xsl:variable>
+- <td><img alt="design" class="icon" src="{$src}"/></td>
+- <td><xsl:apply-templates/></td>
+- </tr>
+- </xsl:template>
+-
+- <xsl:template match="changelog/docs">
+- <tr>
+- <xsl:variable name="src"><xsl:value-of select="$relative-path"/>/images/docs.gif</xsl:variable>
+- <td><img alt="docs" class="icon" src="{$src}"/></td>
+- <td><xsl:apply-templates/></td>
+- </tr>
+- </xsl:template>
+-
+- <xsl:template match="changelog/fix">
+- <tr>
+- <xsl:variable name="src"><xsl:value-of select="$relative-path"/>/images/fix.gif</xsl:variable>
+- <td><img alt="fix" class="icon" src="{$src}"/></td>
+- <td><xsl:apply-templates/></td>
+- </tr>
+- </xsl:template>
+-
+- <xsl:template match="changelog/scode">
+- <tr>
+- <xsl:variable name="src"><xsl:value-of select="$relative-path"/>/images/code.gif</xsl:variable>
+- <td><img alt="code" class="icon" src="{$src}"/></td>
+- <td><xsl:apply-templates/></td>
+- </tr>
+- </xsl:template>
+-
+- <!-- Process an attributes list with nested attribute elements -->
+- <xsl:template match="status">
+- <table border="1" cellpadding="5">
+- <tr>
+- <th width="15%" bgcolor="{$attributes-color}">
+- <font color="#ffffff">Priority</font>
+- </th>
+- <th width="50%" bgcolor="{$attributes-color}">
+- <font color="#ffffff">Action Item</font>
+- </th>
+- <th width="25%" bgcolor="{$attributes-color}">
+- <font color="#ffffff">Volunteers</font>
+- </th>
+- <xsl:for-each select="item">
+- <tr>
+- <td align="left" valign="center">
+- <xsl:value-of select="@priority"/>
+- </td>
+- <td align="left" valign="center">
+- <xsl:apply-templates/>
+- </td>
+- <td align="left" valign="center">
+- <xsl:value-of select="@owner"/>
+- </td>
+- </tr>
+- </xsl:for-each>
+- </tr>
+- </table>
+- </xsl:template>
+-
+- <!-- Link to a bug report -->
+- <xsl:template match="bug">
+- <xsl:variable name="link"><xsl:value-of select="$buglink"/><xsl:value-of select="text()"/></xsl:variable>
+- <a href="{$link}"><xsl:apply-templates/></a>
+- </xsl:template>
+-
+- <!-- Process everything else by just passing it through -->
+- <xsl:template match="*|@*">
+- <xsl:copy>
+- <xsl:apply-templates select="@*|*|text()"/>
+- </xsl:copy>
+- </xsl:template>
+-
+-</xsl:stylesheet>
+Index: webapps/docs/tribes/leader-election-message-arrives.dia
+===================================================================
+Cannot display: file marked as a binary type.
+svn:mime-type = application/octet-stream
+Index: webapps/docs/tribes/project.xml
+===================================================================
+--- webapps/docs/tribes/project.xml (revision 590752)
++++ webapps/docs/tribes/project.xml (working copy)
+@@ -1,54 +0,0 @@
+-<?xml version="1.0" encoding="ISO-8859-1"?>
+-<!--
+- 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.
+--->
+-<project name="Apache Tribes Documentation - Top Level Directory"
+- href="http://tomcat.apache.org/">
+-
+- <title>Apache Tribes - The Tomcat Cluster Communication Module</title>
+-
+- <logo href="/images/tomcat.gif">Apache Tomcat</logo>
+-
+-
+- <body>
+-
+- <menu name="Links">
+- <item name="Docs Home" href="../index.html"/>
+- <item name="FAQ" href="http://tomcat.apache.org/faq" />
+- </menu>
+-
+- <menu name="User Guide">
+- <item name="1) Introduction" href="introduction.html"/>
+- <item name="2) Setup" href="setup.html"/>
+- <item name="3) FAQ" href="faq.html"/>
+- </menu>
+-
+- <menu name="Reference">
+- <item name="Release Notes" href="RELEASE-NOTES.txt"/>
+- <item name="JavaDoc" href="/api/index.html"/>
+- </menu>
+-
+- <menu name="Apache Tribes Development">
+- <item name="Membership" href="membership.html"/>
+- <item name="Transport" href="transport.html"/>
+- <item name="Interceptors" href="interceptors.html"/>
+- <item name="Status" href="status.html"/>
+- <item name="Developers" href="developers.html"/>
+- </menu>
+-
+- </body>
+-
+-</project>
+Index: webapps/docs/tribes/faq.xml
+===================================================================
+--- webapps/docs/tribes/faq.xml (revision 590752)
++++ webapps/docs/tribes/faq.xml (working copy)
+@@ -1,37 +0,0 @@
+-<?xml version="1.0"?>
+-<!--
+- 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.
+--->
+-<!DOCTYPE document [
+- <!ENTITY project SYSTEM "project.xml">
+-]>
+-<document url="introduction.html">
+-
+- &project;
+-
+- <properties>
+- <author email="fhanik(a)apache.org">Filip Hanik</author>
+- <title>Apache Tribes - Frequently Asked Questions</title>
+- </properties>
+-
+-<body>
+-
+-
+-<section name="Frequently Asked Questions">
+-</section>
+-</body>
+-
+-</document>
+Index: webapps/docs/tribes/leader-election-message-arrives.jpg
+===================================================================
+Cannot display: file marked as a binary type.
+svn:mime-type = application/octet-stream
+Index: webapps/docs/tribes/leader-election-initiate-election.dia
+===================================================================
+Cannot display: file marked as a binary type.
+svn:mime-type = application/octet-stream
+Index: webapps/docs/class-loader-howto.xml
+===================================================================
+--- webapps/docs/class-loader-howto.xml (revision 590752)
++++ webapps/docs/class-loader-howto.xml (working copy)
+@@ -106,8 +106,6 @@
+ <li><em>catalina.jar</em> - Implementation of the Catalina servlet
+ container portion of Tomcat 6.</li>
+ <li><em>catalina-ant.jar</em> - Tomcat Catalina Ant tasks.</li>
+- <li><em>catalina-ha.jar</em> - High availability package.</li>
+- <li><em>catalina-tribes.jar</em> - Group communication package.</li>
+ <li><em>el-api.jar</em> - EL 2.1 API.</li>
+ <li><em>jasper.jar</em> - Jasper 2 Compiler and Runtime.</li>
+ <li><em>jasper-el.jar</em> - Jasper 2 EL implementation.</li>
+Index: webapps/docs/index.xml
+===================================================================
+--- webapps/docs/index.xml (revision 590752)
++++ webapps/docs/index.xml (working copy)
+@@ -107,8 +107,6 @@
+ Configuring MBean descriptors files for custom components.</li>
+ <li><a href="default-servlet.html"><strong>Default Servlet</strong></a> -
+ Configuring the default servlet and customizing directory listings.</li>
+-<li><a href="cluster-howto.html"><strong>Apache Tomcat Clustering</strong></a> -
+- Enable session replication in a Apache Tomcat environment.</li>
+ <li><a href="balancer-howto.html"><strong>Balancer</strong></a> -
+ Configuring, using, and extending the load balancer application.</li>
+ <li><a href="connectors.html"><strong>Connectors</strong></a> -
+Index: webapps/docs/config/cluster.xml
+===================================================================
+--- webapps/docs/config/cluster.xml (revision 590752)
++++ webapps/docs/config/cluster.xml (working copy)
+@@ -1,151 +0,0 @@
+-<?xml version="1.0"?>
+-<!--
+- 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.
+--->
+-<!DOCTYPE document [
+- <!ENTITY project SYSTEM "project.xml">
+-]>
+-<document url="cluster.html">
+-
+- &project;
+-
+- <properties>
+- <author email="fhanik(a)apache.org">Filip Hanik</author>
+- <title>The Cluster object</title>
+- </properties>
+-
+-<body>
+-
+-
+-<section name="Introduction">
+- <p>
+- The tomcat cluster implementation provides session replication, context attribute replication and
+- cluster wide WAR file deployment.
+- While the <code>Cluster</code> configuration is fairly complex, the default configuration will work
+- for most people out of the box. </p><p>
+- The Tomcat Cluster implementation is very extensible, and hence we have exposed a myriad of options,
+- making the configuration seem like a lot, but don't lose faith, instead you have a tremendous control
+- over what is going on.</p>
+-</section>
+-<section name="Engine vs Host placement">
+- <p>
+- You can place the <code><Cluster></code> element inside either the <code><Engine></code>
+- container or the <code><Host></code> container.<br/>
+- Placing it in the engine, means that you will support clustering in all virtual hosts of Tomcat,
+- and share the messaging component. When you place the <code><Cluster></code> inside the <code><Engine></code>
+- element, the cluster will append the host name of each session manager to the managers name so that two contexts with
+- the same name but sitting inside two different hosts will be distinguishable.
+- </p>
+-</section>
+-<section name="Context Attribute Replication">
+- <p>To configure context attribute replication, simply do this by swapping out the context implementation
+- used for your application context.
+- <source><Context className="org.apache.catalina.ha.context.ReplicatedContext"/></source>
+- This context extends the Tomcat <code><a href="context.html">StandardContext</a></code>
+- so all the options from the <a href="context.html">base implementation</a> are valid.
+- </p>
+-</section>
+-<section name="Nested Components">
+- <p><b><a href="cluster-manager.html">Manager</a>:</b> <br/>
+- The session manager element identifies what kind of session manager is used in this cluster implementation.
+- This manager configuration is identical to the one you would use in a regular <code><a href="context.html#Nested%20xComponents"><Context></a></code> configuration.
+- <br/>The default value is the <code>org.apache.catalina.ha.session.DeltaManager</code> that is closely coupled with
+- the <code>SimpleTcpCluster</code> implementation. Other managers like the <code>org.apache.catalina.ha.session.BackupManager</code>
+- are/could be loosely coupled and don't rely on the <code>SimpleTcpCluster</code> for its data replication.
+- </p>
+- <p><b><a href="cluster-channel.html">Channel</a>:</b> <br/>
+- The Channel and its sub components are all part of the IO layer
+- for the cluster group, and is a module in it's own that we have nick named "Tribes"
+- <br/>
+- Any configuring and tuning of the network layer, the messaging and the membership logic
+- will be done in the channel and its nested components.
+- You can always find out more about <a href="../tribes/introduction.html">Apache Tribes</a>
+- </p>
+- <p><b><a href="cluster-valve.html">Valve</a>:</b> <br/>
+- The Tomcat Cluster implementation uses <code>Tomcat <a href="valve.html">Valves</a></code> to
+- track when requests enter and exit the servlet container. It uses these valves to be able to make
+- intelligent decisions on when to replicate data, which is always at the end of a request.
+- </p>
+- <p><b><a href="cluster-deployer.html">Deployer</a>:</b> <br/>
+- The Deployer component is the Tomcat Farm Deployer. It allows you to deploy and undeploy applications
+- cluster wide.
+- </p>
+- <p><b><a href="cluster-listener.html">ClusterListener</a>:</b> <br/>
+- ClusterListener's are used to track messages sent and received using the <code>SimpleTcpCluster</code>.
+- If you wish to track messages, you can add a listener here, or you can add a valve to the channel object.
+- </p>
+-</section>
+-
+-<section name="Deprecated configuration options">
+- <p>
+- <b>Deprecated settings:</b> In the previous version of Tomcat you were able to control session
+- manager settings using manager.<property>=value.
+- This has been discontinued, as the way it was written interfers with
+- the ability to support multiple different manager classes under one cluster implementation,
+- as the same properties might have the different effect on different managers.
+- </p>
+-</section>
+-
+-<section name="Attributes">
+- <subsection name="SimpleTcpCluster Attributes">
+- <attributes>
+- <attribute name="className" required="true">
+- <p>The main cluster class, currently only one is available,
+- <code>org.apache.catalina.ha.tcp.SimpleTcpCluster</code>
+- </p>
+- </attribute>
+- <attribute name="channelSendOptions" required="true">
+- <p>The Tribes channel send options, default is <code>8</code>.<br/>
+- This option is used to set the flag that all messages sent through the
+- SimpleTcpCluster uses. The flag decides how the messages are sent, and is a simple logical OR.<br/>
+-
+- <source>
+- int options= Channel.SEND_OPTIONS_ASYNCHRONOUS |
+- Channel.SEND_OPTIONS_SYNCHRONIZED_ACK |
+- Channel.SEND_OPTIONS_USE_ACK;
+- </source>
+- Some of the values are:<br/>
+- <code>Channel.SEND_OPTIONS_SYNCHRONIZED_ACK = 0x0004</code><br/>
+- <code>Channel.SEND_OPTIONS_ASYNCHRONOUS = 0x0008</code><br/>
+- <code>Channel.SEND_OPTIONS_USE_ACK = 0x0002</code><br/>
+- So to use ACK and ASYNC messaging, the flag would be <code>10 (8+2) or 0x000B</code><br/>
+- </p>
+- </attribute>
+-
+- <attribute name="heartbeatBackgroundEnabled" required="false">
+- <p>Enable this flag don't forget to disable the channel heartbeat thread.
+- </p>
+- </attribute>
+-
+- <attribute name="doClusterLog" required="false">
+- <p><b>Deprecated since 6.0.0</b></p>
+- <p>Possible values are <code>true</code> or <code>false</code><br/>
+- Value is inherited from Tomcat 5.5 and has no official meaning.
+- to configure logging, use the standard tomcat logging configuration.
+- </p>
+- </attribute>
+- <attribute name="clusterLogName" required="false">
+- <p><b>Deprecated since 6.0.0</b></p>
+- <p>
+- Value is inherited from Tomcat 5.5 and has no official meaning.
+- to configure logging, use the standard tomcat logging configuration.
+- </p>
+- </attribute>
+- </attributes>
+- </subsection>
+-</section>
+-</body>
+-</document>
+Index: webapps/docs/config/cluster-receiver.xml
+===================================================================
+--- webapps/docs/config/cluster-receiver.xml (revision 590752)
++++ webapps/docs/config/cluster-receiver.xml (working copy)
+@@ -1,158 +0,0 @@
+-<?xml version="1.0"?>
+-<!--
+- 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.
+--->
+-<!DOCTYPE document [
+- <!ENTITY project SYSTEM "project.xml">
+-]>
+-<document url="cluster-receiver.html">
+-
+- &project;
+-
+- <properties>
+- <author email="fhanik(a)apache.org">Filip Hanik</author>
+- <title>The Cluster Receiver object</title>
+- </properties>
+-
+-<body>
+-
+-
+-<section name="Introduction">
+- <p>
+- The receiver component is responsible for receiving cluster messages.
+- As you might notice through the configuration, is that the receiving of messages
+- and sending of messages are two different components, this is different from many other
+- frameworks, but there is a good reason for it, to decouple the logic for how messages are sent from
+- how messages are received.<br/>
+- The receiver is very much like the Tomcat Connector, its the base of the thread pool
+- for incoming cluster messages. The receiver is straight forward, but all the socket settings
+- for incoming traffic are managed here.
+- </p>
+-</section>
+-
+-<section name="Blocking vs Non-Blocking Receiver">
+- <p>
+- The receiver supports both a non blocking, <code>org.apache.catalina.tribes.transport.nio.NioReceiver</code>, and a
+- blocking, <code>org.apache.catalina.tribes.transport.bio.BioReceiver</code>. It is preferred to use the non blocking receiver
+- to be able to grow your cluster without running into thread starvation.<br/>
+- Using the non blocking receiver allows you to with a very limited thread count to serve a large number of messages.
+- Usually the rule is to use 1 thread per node in the cluster for small clusters, and then depending on your message frequency
+- and your hardware, you'll find an optimal number of threads peak out at a certain number.
+- </p>
+-</section>
+-
+-<section name="Attributes">
+- <subsection name="Common Attributes">
+- <attributes>
+- <attribute name="className" required="true">
+- The implementation of the receiver component. Two implementations available,
+- <code>org.apache.catalina.tribes.transport.nio.NioReceiver</code> and
+- <code>org.apache.catalina.tribes.transport.bio.BioReceiver</code>.<br/>
+- The <code>org.apache.catalina.tribes.transport.nio.NioReceiver</code> is the
+- preferred implementation
+- </attribute>
+- <attribute name="address" required="false">
+- The address (network interface) to listen for incoming traffic.
+- Same as the bind address. The default value is <code>auto</code> and translates to
+- <code>java.net.InetAddress.getLocalHost().getHostAddress()</code>.
+- </attribute>
+- <attribute name="direct" required="false">
+- Possible values are <code>true</code> or <code>false</code>.
+- Set to true if you want the receiver to use direct bytebuffers when reading data
+- from the sockets.
+- </attribute>
+- <attribute name="port" required="false">
+- The listen port for incoming data. The default value is <code>4000</code>.
+- To avoid port conflicts the receiver will automatically bind to a free port within the range of
+- <code> port <= bindPort <= port+autoBind</code>
+- So for example, if port is 4000, and autoBind is set to 10, then the receiver will open up
+- a server socket on the first available port in the range 4000-4100.
+- </attribute>
+- <attribute name="autoBind" required="false">
+- Default value is <code>100</code>.
+- Use this value if you wish to automatically avoid port conflicts the cluster receiver will try to open a
+- server socket on the <code>port</code> attribute port, and then work up <code>autoBind</code> number of times.
+- </attribute>
+- <attribute name="securePort" required="false">
+- The secure listen port. This port is SSL enabled. If this attribute is omitted no SSL port is opened up.
+- There default value is unset, meaning there is no SSL socket available.
+- </attribute>
+- <attribute name="selectorTimeout" required="false">
+- The value in milliseconds for the polling timeout in the <code>NioReceiver</code>. On older versions of the JDK
+- there have been bugs, that should all now be cleared out where the selector never woke up.
+- The default value is a very high <code>5000</code> milliseconds.
+- </attribute>
+- <attribute name="maxThreads" required="false">
+- The maximum number of threads in the receiver thread pool. The default value is <code>6</code>
+- Adjust this value relative to the number of nodes in the cluster, the number of messages being exchanged and
+- the hardware you are running on. A higher value doesn't mean more effiecency, tune this value according to your
+- own test results.
+- </attribute>
+- <attribute name="minThreads" required="false">
+- Minimum number of threads to be created when the receiver is started up. Default value is <code>6</code>
+- </attribute>
+- <attribute name="ooBInline" required="false">
+- Boolean value for the socket OOBINLINE option. Possible values are <code>true</code> or <code>false</code>.
+- </attribute>
+- <attribute name="rxBufSize" required="false">
+- The receiver buffer size on the receiving sockets. Value is in bytes, the default value is <code>43800</code> bytes.
+- </attribute>
+- <attribute name="txBufSize" required="false">
+- The sending buffer size on the receiving sockets. Value is in bytes, the default value is <code>25188</code> bytes.
+- </attribute>
+- <attribute name="soKeepAlive" required="false">
+- Boolean value for the socket SO_KEEPALIVE option. Possible values are <code>true</code> or <code>false</code>.
+- </attribute>
+- <attribute name="soLingerOn" required="false">
+- Boolean value to determine whether to use the SO_LINGER socket option.
+- Possible values are <code>true</code> or <code>false</code>. Default value is <code>true</code>.
+- </attribute>
+- <attribute name="soLingerTime" required="false">
+- Sets the SO_LINGER socket option time value. The value is in seconds.
+- The default value is <code>3</code> seconds.
+- </attribute>
+- <attribute name="soReuseAddress" required="false">
+- Boolean value for the socket SO_REUSEADDR option. Possible values are <code>true</code> or <code>false</code>.
+- </attribute>
+- <attribute name="soTrafficClass" required="false">
+- Sets the traffic class level for the socket, the value is between 0 and 255.
+- Different values are defined in <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/net/Socket.html#setTrafficCl...">
+- java.net.Socket#setTrafficClass(int)</a>.
+- </attribute>
+- <attribute name="tcpNoDelay" required="false">
+- Boolean value for the socket TCP_NODELAY option. Possible values are <code>true</code> or <code>false</code>.
+- The default value is <code>true</code>
+- </attribute>
+- <attribute name="timeout" required="false">
+- Sets the SO_TIMEOUT option on the socket. The value is in milliseconds and the default value is <code>3000</code>
+- milliseconds.
+- </attribute>
+- <attribute name="useBufferPool" required="false">
+- Boolean value whether to use a shared buffer pool of cached <code>org.apache.catalina.tribes.io.XByteBuffer</code>
+- objects. If set to true, the XByteBuffer that is used to pass a message up the channel, will be recycled at the end
+- of the requests. This means that interceptors in the channel must not maintain a reference to the object
+- after the <code>org.apache.catalina.tribes.ChannelInterceptor#messageReceived</code> method has exited.
+- </attribute>
+- </attributes>
+- </subsection>
+- <subsection name="NioReceiver">
+- </subsection>
+- <subsection name="BioReceiver">
+- </subsection>
+-
+-</section>
+-</body>
+-</document>
+Index: webapps/docs/config/cluster-channel.xml
+===================================================================
+--- webapps/docs/config/cluster-channel.xml (revision 590752)
++++ webapps/docs/config/cluster-channel.xml (working copy)
+@@ -1,102 +0,0 @@
+-<?xml version="1.0"?>
+-<!--
+- 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.
+--->
+-<!DOCTYPE document [
+- <!ENTITY project SYSTEM "project.xml">
+-]>
+-<document url="cluster-channel.html">
+-
+- &project;
+-
+- <properties>
+- <author email="fhanik(a)apache.org">Filip Hanik</author>
+- <title>The Cluster Channel object</title>
+- </properties>
+-
+-<body>
+-
+-
+-<section name="Introduction">
+- The cluster channel is the main component of a small framework we've nicknamed Apache Tribes.<br/>
+- The channel manages a set of sub components and together they create a group communication framework.<br/>
+- This framework is then used internally by the components that need to send messages between different Tomcat instances.
+- <br/>
+- A few examples of these components would be the SimpleTcpCluster that does the messaging for the DeltaManager,
+- or the BackupManager that uses a different replication strategy. The ReplicatedContext object does also
+- use the channel object to communicate context attribute changes.
+-</section>
+-<section name="Nested Components">
+- <p><b><a href="cluster-membership.html">Channel/Membership</a>:</b> <br/>
+- The Membership component is responsible for auto discovering new nodes in the cluster
+- and also to provide for notifications for any nodes that have not responded with a heartbeat.
+- The default implementation uses multicast.<br/>
+- In the membership component you configure how your nodes, aka. members, are to be discovered and/or
+- divided up.
+- You can always find out more about <a href="../tribes/introduction.html">Apache Tribes</a>
+- </p>
+- <p><b><a href="cluster-sender.html">Channel/Sender</a>:</b> <br/>
+- The Sender component manages all outbound connections and data messages that are sent
+- over the network from one node to another.
+- This component allows messages to be sent in parallel.
+- The default implementation uses TCP client sockets, and socket tuning for outgoing messages are
+- configured here.<br/>
+- You can always find out more about <a href="../tribes/introduction.html">Apache Tribes</a>
+- </p>
+- <p><b><a href="cluster-sender.html#transport">Channel/Sender/Transport</a>:</b> <br/>
+- The Transport component is the bottom IO layer for the sender component.
+- The default implementation uses non-blocking TCP client sockets.<br/>
+- You can always find out more about <a href="../tribes/introduction.html">Apache Tribes</a>
+- </p>
+- <p><b><a href="cluster-receiver.html">Channel/Receiver</a>:</b> <br/>
+- The receiver component listens for messages from other nodes.
+- Here you will configure the cluster thread pool, as it will dispatch incoming
+- messages to a thread pool for faster processing.
+- The default implementation uses non-blocking TCP server sockets.<br/>
+- You can always find out more about <a href="../tribes/introduction.html">Apache Tribes</a>
+- </p>
+- <p><b><a href="cluster-interceptor.html">Channel/Interceptor</a>:</b> <br/>
+- The channel will send messages through an interceptor stack. Because of this, you have the ability to
+- customize the way messages are sent and received, and even how membership is handled.<br/>
+- You can always find out more about <a href="../tribes/introduction.html">Apache Tribes</a>
+- </p>
+-</section>
+-
+-
+-<section name="Attributes">
+-
+- <subsection name="Common Attributes">
+-
+- <attributes>
+-
+- <attribute name="className" required="true">
+- The default value here is <code>org.apache.catalina.tribes.group.GroupChannel</code> and is
+- currently the only implementation available.
+- </attribute>
+-
+-
+- </attributes>
+-
+-
+- </subsection>
+-
+-
+-</section>
+-
+-
+-</body>
+-
+-</document>
+Index: webapps/docs/config/cluster-manager.xml
+===================================================================
+--- webapps/docs/config/cluster-manager.xml (revision 590752)
++++ webapps/docs/config/cluster-manager.xml (working copy)
+@@ -1,112 +0,0 @@
+-<?xml version="1.0"?>
+-<!--
+- 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.
+--->
+-<!DOCTYPE document [
+- <!ENTITY project SYSTEM "project.xml">
+-]>
+-<document url="cluster-manager.html">
+-
+- &project;
+-
+- <properties>
+- <author email="fhanik(a)apache.org">Filip Hanik</author>
+- <title>The ClusterManager object</title>
+- </properties>
+-
+-<body>
+-
+-
+-<section name="Introduction">
+- <p>
+- A cluster manager is an extension to Tomcat's session manager interface,
+- <code>org.apache.catalina.Manager</code>
+- A cluster manager must implement the <code>org.apache.catalina.ha.ClusterManager</code> and is solely
+- responsible for how the session is replicated.<br/>
+- There are currently two different managers, the <code>org.apache.catalina.ha.session.DeltaManager</code> replicates deltas
+- of session data to all members in the cluster. This implementation is proven and works very well, but has a limitation
+- as it requires the cluster members to be homogeneous, all nodes must deploy the same applications and be exact replicas.
+- The <code>org.apache.catalina.ha.session.BackupManager</code> also replicates deltas but only to one backup node.
+- The location of the backup node is known to all nodes in the cluster. It also supports heterogeneous deployments,
+- so the manager knows at what locations the webapp is deployed.<br/>
+- We are planning to add more managers with even more sophisticated backup mechanism to support even larger clusters.
+- Check back soon!
+- </p>
+-</section>
+-
+-<section name="The <Manager>">
+- <p>
+- The <code><Manager></code> element defined inside the <code><Cluster></code> element
+- is the template defined for all web applications that are marked <code><distributable/></code>
+- in their <code>web.xml</code> file.
+- However, you can still override the manager implementation on a per web application basis,
+- by putting the <code><Manager></code> inside the <code><Context></code> element either in the
+- <code><a href="context.html">context.xml</a></code> file or the <code><a href="index.html">server.xml</a></code> file.
+- </p>
+-</section>
+-
+-<section name="Attributes">
+- <subsection name="Common Attributes">
+- <attributes>
+- <attribute name="className" required="true">
+- </attribute>
+- <attribute name="name" required="false">
+- <b>The name of this cluster manager, the name is used to identify a session manager on a node.
+- The name might get modified by the <code>Cluster</code> element to make it unique in the container.</b>
+- </attribute>
+- <attribute name="defaultMode" required="false">
+- <b>Deprecated since 6.0.0</b>
+- </attribute>
+- <attribute name="notifyListenersOnReplication" required="false">
+- Set to <code>true</code> if you wish to have session listeners notified when
+- session attributes are being replicated or removed across Tomcat nodes in the cluster.
+- </attribute>
+- <attribute name="expireSessionsOnShutdown" required="false">
+- When a webapplication is being shutdown, Tomcat issues an expire call to each session to
+- notify all the listeners. If you wish for all sessions to expire on all nodes when
+- a shutdown occurs on one node, set this value to <code>true</code>.
+- Default value is <code>false</code>.
+- </attribute>
+-
+- </attributes>
+- </subsection>
+- <subsection name="org.apache.catalina.ha.session.DeltaManager Attributes">
+- <attributes>
+- <attribute name="domainReplication" required="false">
+- Set to true if you wish sessions to be replicated only to members that have the same logical
+- domain set. If set to false, session replication will ignore the domain setting the
+- <code><a href="cluster-membership.html"><Membership></a></code>
+- element.
+- </attribute>
+- <attribute name="expireSessionsOnShutdown" required="false">
+- When a webapplication is being shutdown, Tomcat issues an expire call to each session to
+- notify all the listeners. If you wish for all sessions to expire on all nodes when
+- a shutdown occurs on one node, set this value to <code>true</code>.
+- Default value is <code>false</code>.
+- </attribute>
+- </attributes>
+- </subsection>
+- <subsection name="org.apache.catalina.ha.session.BackupManager Attributes">
+- <attributes>
+- <attribute name="mapSendOptions" required="false">
+- The backup manager uses a replicated map, this map is sending and receiving messages.
+- You can setup the flag for how this map is sending messages, the default value is <code>6</code>(asynchronous).
+- </attribute>
+- </attributes>
+- </subsection>
+-</section>
+-</body>
+-</document>
+Index: webapps/docs/config/cluster-valve.xml
+===================================================================
+--- webapps/docs/config/cluster-valve.xml (revision 590752)
++++ webapps/docs/config/cluster-valve.xml (working copy)
+@@ -1,103 +0,0 @@
+-<?xml version="1.0"?>
+-<!--
+- 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.
+--->
+-<!DOCTYPE document [
+- <!ENTITY project SYSTEM "project.xml">
+-]>
+-<document url="cluster-valve.html">
+-
+- &project;
+-
+- <properties>
+- <author email="fhanik(a)apache.org">Filip Hanik</author>
+- <title>The Cluster Valve object</title>
+- </properties>
+-
+-<body>
+-
+-
+-<section name="Introduction">
+- <p>
+- A cluster valve is no different from any other <a href="valve.html">Tomcat <code>Valve</code></a>.
+- The cluster valves are interceptors in the invokation chain for HTTP requests, and the clustering implementation
+- uses these valves to make intelligent decision around data and when data should be replicated.
+- </p>
+- <p>
+- A cluster valve must implement the <code>org.apache.catalina.ha.ClusterValve</code> interface.
+- This is a simple interface that extends the <code>org.apache.catalina.Valve</code> interface.
+- </p>
+-</section>
+-
+-<section name="org.apache.catalina.ha.tcp.ReplicationValve">
+- The <code>ReplicationValve</code> will notify the cluster at the end of a HTTP request
+- so that the cluster can make a decision whether there is data to be replicated or not.
+- <subsection name="Attributes">
+- <attributes>
+- <attribute name="className" required="true">
+- Set value to <code>org.apache.catalina.ha.tcp.ReplicationValve</code>
+- </attribute>
+- <attribute name="filter" required="false">
+- For known file extensions or urls, you can use a filter to
+- notify the cluster that the session has not been modified during this
+- request and the cluster doesn't have to probe the session managers for changes.
+- If there is a filter match, the cluster assumes there has been no session change.
+- An example filter would look like <code>filter=".*\.gif;.*\.js;.*\.jpg;.*\.png;.*\.htm;.*\.html;.*\.css;.*\.txt;"</code>
+- The filter uses regular expressions and each filter is delimited by a semi colon.
+- <code>Pattern#compile(java.lang.String)</code>
+- </attribute>
+- <attribute name="primaryIndicator" required="false">
+- Boolean value, so to true, and the replication valve will insert a request attribute with the name
+- defined by the <code>primaryIndicatorName</code> attribute.
+- The value inserted into the request attribute is either <code>Boolean.TRUE</code> or
+- <code>Boolean.FALSE</code>
+- </attribute>
+- <attribute name="primaryIndicatorName" required="false">
+- Default value is <code>org.apache.catalina.ha.tcp.isPrimarySession</code>
+- The value defined here is the name of the request attribute that contains the boolean value
+- if the session is primary on this server or not.
+- </attribute>
+- <attribute name="statistics" required="false">
+- Boolean value. Set to <code>true</code> if you want the valve to collect request statistics.
+- Default value is <code>false</code>
+- </attribute>
+- </attributes>
+- </subsection>
+-</section>
+-
+-<section name="org.apache.catalina.ha.session.JvmRouteBinderValve">
+- In case of a mod_jk failover, the <code>JvmRouteBinderValve</code> will replace the
+- <code>jvmWorker</code> attribute in the session Id, to make future requests stick to this
+- node. If you want failback capability, don't enable this valve, but if you want your failover to stick,
+- and for mod_jk not to have to keep probing the node that went down, you use this valve.
+- <subsection name="Attributes">
+- <attributes>
+- <attribute name="className" required="true">
+- <code>org.apache.catalina.ha.session.JvmRouteBinderValve</code>
+- </attribute>
+- <attribute name="enabled" required="false">
+- Default value is <code>true</code>
+- Runtime attribute to turn on and off turn over of the session's jvmRoute value.
+- </attribute>
+-
+- </attributes>
+- </subsection>
+-</section>
+-
+-
+-</body>
+-
+-</document>
+Index: webapps/docs/config/cluster-sender.xml
+===================================================================
+--- webapps/docs/config/cluster-sender.xml (revision 590752)
++++ webapps/docs/config/cluster-sender.xml (working copy)
+@@ -1,163 +0,0 @@
+-<?xml version="1.0"?>
+-<!--
+- 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.
+--->
+-<!DOCTYPE document [
+- <!ENTITY project SYSTEM "project.xml">
+-]>
+-<document url="cluster-sender.html">
+-
+- &project;
+-
+- <properties>
+- <author email="fhanik(a)apache.org">Filip Hanik</author>
+- <title>The Cluster Sender object</title>
+- </properties>
+-
+-<body>
+-
+-
+-<section name="Introduction">
+- <p>
+- The channel sender component is responsible for delivering outgoing cluster messages over the network.
+- In the default implementation, <code>org.apache.catalina.tribes.transport.ReplicationTransmitter</code>,
+- the sender is a fairly empty shell with not much logic around a fairly complex <code><Transport></code>
+- component the implements the actual delivery mechanism.
+- </p>
+-</section>
+-
+-<section name="Concurrent Parallel Delivery">
+- <p>
+- In the default <code>transport</code> implementation, <code>org.apache.catalina.tribes.transport.nio.PooledParallelSender</code>,
+- Apache Tribes implements what we like to call "Concurrent Parallel Delivery".
+- This means that we can send a message to more than one destination at the same time(parallel), and
+- deliver two messages to the same destination at the same time(concurrent). Combine these two and we have
+- "Concurrent Parallel Delivery".
+- </p>
+- <p>
+- When is this useful? The simplest example we can think of is when part of your code is sending a 10MB message,
+- like a war file being deployed, and you need to push through a small 10KB message, say a session being replicated,
+- you don't have to wait for the 10MB message to finish, as a separate thread will push in the small message
+- transmission at the same time. Currently there is no interrupt, pause or priority mechanism avaiable, but check back soon.
+- </p>
+-</section>
+-
+-<section name="Nested Elements">
+- <p>
+- The nested element <code><Transport></code> is is not required, by encouraged, as this is where
+- you would set all the socket options for the outgoing messages. Please see its attributes below.
+- There are two implementations, in a similar manner to the <a href="cluster-receiver.html">receiver</a>, one is non-blocking
+- based and the other is built using blocking IO. <br/>
+- <code>org.apache.catalina.tribes.transport.bio.PooledMultiSender</code> is the blocking implemenation and
+- <code>org.apache.catalina.tribes.transport.nio.PooledParallelSender</code>.
+- Parallel delivery is not available for the blocking implementation due to the fact that it is blocking a thread on sending data.
+- </p>
+-</section>
+-
+-<section name="Attributes">
+- <subsection name="Common Sender Attributes">
+- <attributes>
+- <attribute name="className" required="true">
+- Required, only available implementation is <code>org.apache.catalina.tribes.transport.ReplicationTransmitter</code>
+- </attribute>
+- </attributes>
+- </subsection>
+- <subsection name="Common Transport Attributes">
+- <attributes>
+- <attribute name="className" required="true">
+- Required, an implementation of the <code>org.apache.catalina.tribes.transport.MultiPointSender</code>.<br/>
+- Non-blocking implementation is <code>org.apache.catalina.tribes.transport.nio.PooledParallelSender</code><br/>
+- Blocking implementation is <code>org.apache.catalina.tribes.transport.bio.PooledMultiSender</code>
+- </attribute>
+- <attribute name="rxBufSize" required="false">
+- The receive buffer size on the socket.
+- Default value is <code>25188</code> bytes.
+- </attribute>
+- <attribute name="txBufSize" required="false">
+- The send buffer size on the socket.
+- Default value is <code>43800</code> bytes.
+- </attribute>
+- <attribute name="direct" required="false">
+- Possible values are <code>true</code> or <code>false</code>.
+- Set to true if you want the receiver to use direct bytebuffers when reading data
+- from the sockets. Default value is <code>false</code>
+- </attribute>
+- <attribute name="keepAliveCount" required="false">
+- The number of requests that can go through the socket before the socket is closed, and reopened
+- for the next request. The default value is <code>-1</code>, which is unlimited.
+- </attribute>
+- <attribute name="keepAliveTime" required="false">
+- The number of milliseconds a connection is kept open after its been opened.
+- The default value is <code>-1</code>, which is unlimited.
+- </attribute>
+- <attribute name="timeout" required="false">
+- Sets the SO_TIMEOUT option on the socket. The value is in milliseconds and the default value is <code>3000</code>
+- milliseconds.
+- </attribute>
+- <attribute name="maxRetryAttempts" required="false">
+- How many times do we retry a failed message, that received a IOException at the socket level.
+- The default value is <code>1</code>, meaning we will retry a message that has failed once.
+- In other words, we will attempt a message send no more than twice. One is the original send, and one is the
+- <code>maxRetryAttempts</code>.
+- </attribute>
+- <attribute name="ooBInline" required="false">
+- Boolean value for the socket OOBINLINE option. Possible values are <code>true</code> or <code>false</code>.
+- </attribute>
+- <attribute name="soKeepAlive" required="false">
+- Boolean value for the socket SO_KEEPALIVE option. Possible values are <code>true</code> or <code>false</code>.
+- </attribute>
+- <attribute name="soLingerOn" required="false">
+- Boolean value to determine whether to use the SO_LINGER socket option.
+- Possible values are <code>true</code> or <code>false</code>. Default value is <code>true</code>.
+- </attribute>
+- <attribute name="soLingerTime" required="false">
+- Sets the SO_LINGER socket option time value. The value is in seconds.
+- The default value is <code>3</code> seconds.
+- </attribute>
+- <attribute name="soReuseAddress" required="false">
+- Boolean value for the socket SO_REUSEADDR option. Possible values are <code>true</code> or <code>false</code>.
+- </attribute>
+- <attribute name="soTrafficClass" required="false">
+- Sets the traffic class level for the socket, the value is between 0 and 255.
+- Default value is <code>int soTrafficClass = 0x04 | 0x08 | 0x010;</code>
+- Different values are defined in <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/net/Socket.html#setTrafficCl...">
+- java.net.Socket#setTrafficClass(int)</a>.
+- </attribute>
+- <attribute name="tcpNoDelay" required="false">
+- Boolean value for the socket TCP_NODELAY option. Possible values are <code>true</code> or <code>false</code>.
+- The default value is <code>true</code>
+- </attribute>
+- <attribute name="throwOnFailedAck" required="false">
+- Boolean value, default value is <code>true</code>.
+- If set to true, the sender will throw a <code>org.apache.catalina.tribes.RemoteProcessException</code>
+- when we receive a negative ack from the remote member.
+- Set to false, and Tribes will treat a positive ack the same way as a negative ack, that the message was received.
+- </attribute>
+- </attributes>
+- </subsection>
+- <subsection name="PooledParallelSender Attributes">
+- <attributes>
+- <attribute name="poolSize" required="false">
+- The maximum number of concurrent connections from A to B.
+- The value is based on a per-destination count.
+- The default value is <code>25</code>
+- </attribute>
+-
+- </attributes>
+- </subsection>
+-</section>
+-</body>
+-</document>
+Index: webapps/docs/config/cluster-deployer.xml
+===================================================================
+--- webapps/docs/config/cluster-deployer.xml (revision 590752)
++++ webapps/docs/config/cluster-deployer.xml (working copy)
+@@ -1,62 +0,0 @@
+-<?xml version="1.0"?>
+-<!--
+- 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.
+--->
+-<!DOCTYPE document [
+- <!ENTITY project SYSTEM "project.xml">
+-]>
+-<document url="cluster-deployer.html">
+-
+- &project;
+-
+- <properties>
+- <author email="fhanik(a)apache.org">Filip Hanik</author>
+- <title>The Cluster Deployer object</title>
+- </properties>
+-
+-<body>
+-
+-
+-<section name="Introduction">
+- <p>This goober is currently pretty broken, but we are working hard to fix it</p>
+-
+-
+-</section>
+-
+-
+-<section name="Attributes">
+-
+- <subsection name="Common Attributes">
+-
+- <attributes>
+-
+- <attribute name="className" required="true">
+-
+- </attribute>
+-
+-
+- </attributes>
+-
+-
+- </subsection>
+-
+-
+-</section>
+-
+-
+-</body>
+-
+-</document>
+Index: webapps/docs/config/cluster-listener.xml
+===================================================================
+--- webapps/docs/config/cluster-listener.xml (revision 590752)
++++ webapps/docs/config/cluster-listener.xml (working copy)
+@@ -1,77 +0,0 @@
+-<?xml version="1.0"?>
+-<!--
+- 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.
+--->
+-<!DOCTYPE document [
+- <!ENTITY project SYSTEM "project.xml">
+-]>
+-<document url="cluster-listener.html">
+-
+- &project;
+-
+- <properties>
+- <author email="fhanik(a)apache.org">Filip Hanik</author>
+- <title>The ClusterListener object</title>
+- </properties>
+-
+-<body>
+-
+-
+-<section name="Introduction">
+- <p>
+- The <code>org.apache.catalina.ha.ClusterListener</code> base class
+- lets you listen in on messages that are received by the <code>Cluster</code> component.
+- </p>
+-
+-</section>
+-<section name="org.apache.catalina.ha.session.ClusterSessionListener">
+- <p>
+- When using the DeltaManager, the messages are received by the Cluster object and are propagated to the
+- to the manager through this listener.
+- </p>
+-</section>
+-<section name="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener">
+- <p>
+- Listens for session Id changes. This listener is only used if you are using mod_jk
+- along with the <code>jvmRoute</code> attribute where the session Id can change.
+- In the event of a change, the <code>JvmRouteBinderValve</code> will broadcast the
+- session change and it will get picked up by this listener.
+- </p>
+-</section>
+-
+-<section name="Attributes">
+-
+- <subsection name="Common Attributes">
+-
+- <attributes>
+-
+- <attribute name="className" required="true">
+-
+- </attribute>
+-
+-
+- </attributes>
+-
+-
+- </subsection>
+-
+-
+-</section>
+-
+-
+-</body>
+-
+-</document>
+Index: webapps/docs/config/project.xml
+===================================================================
+--- webapps/docs/config/project.xml (revision 590752)
++++ webapps/docs/config/project.xml (working copy)
+@@ -50,7 +50,6 @@
+ <item name="Context" href="context.html"/>
+ <item name="Engine" href="engine.html"/>
+ <item name="Host" href="host.html"/>
+- <item name="Cluster" href="cluster.html"/>
+ </menu>
+
+ <menu name="Nested Components">
+@@ -62,18 +61,6 @@
+ <item name="Valve" href="valve.html"/>
+ </menu>
+
+- <menu name="Cluster Elements">
+- <item name="Cluster" href="cluster.html"/>
+- <item name="Manager" href="cluster-manager.html"/>
+- <item name="Channel" href="cluster-channel.html"/>
+- <item name="Channel/Membership" href="cluster-membership.html"/>
+- <item name="Channel/Sender" href="cluster-sender.html"/>
+- <item name="Channel/Receiver" href="cluster-receiver.html"/>
+- <item name="Channel/Interceptor" href="cluster-interceptor.html"/>
+- <item name="Valve" href="cluster-valve.html"/>
+- <item name="Deployer" href="cluster-deployer.html"/>
+- <item name="ClusterListener" href="cluster-listener.html"/>
+- </menu>
+ </body>
+
+ </project>
+Index: webapps/docs/config/cluster-membership.xml
+===================================================================
+--- webapps/docs/config/cluster-membership.xml (revision 590752)
++++ webapps/docs/config/cluster-membership.xml (working copy)
+@@ -1,141 +0,0 @@
+-<?xml version="1.0"?>
+-<!--
+- 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.
+--->
+-<!DOCTYPE document [
+- <!ENTITY project SYSTEM "project.xml">
+-]>
+-<document url="cluster-membership.html">
+-
+- &project;
+-
+- <properties>
+- <author email="fhanik(a)apache.org">Filip Hanik</author>
+- <title>The Cluster Membership object</title>
+- </properties>
+-
+-<body>
+-
+-
+-<section name="Introduction">
+- <p>
+- The membership component in the Apache Tribes <a href="cluster-channel.html">Channel</a> is responsible
+- for dynamic discovery of other members(nodes) in the cluster.
+- </p>
+-</section>
+-
+-<section name="Default Implementation">
+- <p>
+- The default implementation of the cluster group notification is built on top of multicast heartbeats
+- sent using UDP packets to a multicast IP address.
+- Cluster members are grouped together by using the same multicast address/port combination.
+- Each member sends out a heartbeat with a given interval (<code>frequency</code>), and this
+- heartbeat is used for dynamic discovery.
+- In a similar fashion, if a heartbeat has not been received in a timeframe specified by <code>dropTime</code>
+- ms. a member is considered suspect and the channel and any membership listener will be notified.
+- </p>
+-</section>
+-
+-
+-
+-<section name="Attributes">
+-
+- <subsection name="Multicast Attributes">
+-
+- <attributes>
+-
+- <attribute name="className" required="true">
+- <p>
+- The default value is <code>org.apache.catalina.tribes.membership.McastService</code>
+- and is currently the only implementation.
+- This implementation uses multicast heartbeats for member discovery.
+- </p>
+- </attribute>
+- <attribute name="address" required="false">
+- <p>
+- The multicast address that the membership will broadcast its presence and listen
+- for other heartbeats on. The default value is <code>228.0.0.4</code>
+- Make sure your network is enabled for multicast traffic.<br/>
+- The multicast address, in conjunction with the <code>port</code> is what
+- creates a cluster group. To divide up your farm into several different group, or to
+- split up QA from production, change the <code>port</code> or the <code>address</code>
+- <br/>Previously known as mcastAddr.
+- </p>
+- </attribute>
+- <attribute name="port" required="false">
+- <p>
+- The multicast port, the default value is <code>45564</code><br/>
+- The multicast port, in conjunction with the <code>address</code> is what
+- creates a cluster group. To divide up your farm into several different group, or to
+- split up QA from production, change the <code>port</code> or the <code>address</code>
+- </p>
+- </attribute>
+- <attribute name="frequency" required="false">
+- <p>
+- The frequency in milliseconds in which heartbeats are sent out. The default value is <code>500</code> ms.<br/>
+- In most cases the default value is sufficient. Changing this value, simply changes the interval in between heartbeats.
+- </p>
+- </attribute>
+- <attribute name="dropTime" required="false">
+- <p>
+- The membership component will time out members and notify the Channel if a member fails to send a heartbeat within
+- a give time. The default value is <code>3000</code> ms. This means, that if a heartbeat is not received from a
+- member in that timeframe, the membership component will notify the cluster of this.<br/>
+- On a high latency network you may wish to increase this value, to protect against false positives.<br/>
+- Apache Tribes also provides a <a href="cluster-interceptor.html#tcpfailuredetector"><code>TcpFailureDetector</code></a> that will
+- verify a timeout using a TCP connection when a heartbeat timeout has occurred. This protects against false positives.
+- </p>
+- </attribute>
+- <attribute name="bind" required="false">
+- <p>
+- Use this attribute if you wish to bind your multicast traffic to a specific network interface.
+- By default, or when this attribute is unset, it tries to bind to <code>0.0.0.0</code> and sometimes on multihomed hosts
+- this becomes a problem.
+- </p>
+- </attribute>
+- <attribute name="ttl" required="false">
+- <p>
+- The time-to-live setting for the multicast heartbeats.
+- This setting should be a value between 0 and 255. The default value is VM implementation specific.
+- </p>
+- </attribute>
+- <attribute name="domain" required="false">
+- <p>
+- Apache Tribes has the ability to logically group members into domains, by using this domain attribute.
+- The <code>org.apache.catalina.tribes.Member.getDomain()</code> method returns the value specified here.
+- </p>
+- </attribute>
+- <attribute name="soTimeout" required="false">
+- <p>
+- The sending and receiving of heartbeats is done on a single thread, hence to avoid blocking this thread forever,
+- you can control the <code>SO_TIMEOUT</code> value on this socket.<br/>
+- If a value smaller or equal to 0 is presented, the code will default this value to frequency
+- </p>
+- </attribute>
+-
+-
+- </attributes>
+-
+-
+- </subsection>
+-
+-
+-</section>
+-
+-
+-</body>
+-
+-</document>
+Index: webapps/docs/config/cluster-interceptor.xml
+===================================================================
+--- webapps/docs/config/cluster-interceptor.xml (revision 590752)
++++ webapps/docs/config/cluster-interceptor.xml (working copy)
+@@ -1,172 +0,0 @@
+-<?xml version="1.0"?>
+-<!--
+- 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.
+--->
+-<!DOCTYPE document [
+- <!ENTITY project SYSTEM "project.xml">
+-]>
+-<document url="cluster-interceptor.html">
+-
+- &project;
+-
+- <properties>
+- <author email="fhanik(a)apache.org">Filip Hanik</author>
+- <title>The Channel Interceptor object</title>
+- </properties>
+-
+-<body>
+-
+-
+-<section name="Introduction">
+- <p>
+- Apache Tribes supports an interceptor architecture to intercept both messages and membership notifications.
+- This architecture allows decoupling of logic and opens the way for some very kewl feature add ons.
+- </p>
+-</section>
+-
+-<section name="Available Interceptors">
+- <p>
+- <ul>
+- <li><code>org.apache.catalina.tribes.group.interceptors.TcpFailureDetector</code></li>
+- <li><code>org.apache.catalina.tribes.group.interceptors.ThroughputInterceptor</code></li>
+- <li><code>org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor</code></li>
+- <li><code>org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor</code></li>
+- <li><code>org.apache.catalina.tribes.group.interceptors.NonBlockingCoordinator</code></li>
+- <li><code>org.apache.catalina.tribes.group.interceptors.OrderInterceptor</code></li>
+- <li><code>org.apache.catalina.tribes.group.interceptors.StaticMembershipInterceptor</code></li>
+- <li><code>org.apache.catalina.tribes.group.interceptors.TwoPhaseCommitInterceptor</code></li>
+- <li><code>org.apache.catalina.tribes.group.interceptors.DomainFilterInterceptor</code></li>
+- <li><code>org.apache.catalina.tribes.group.interceptors.FragmentationInterceptor</code></li>
+- <li><code>org.apache.catalina.tribes.group.interceptors.GzipInterceptor</code></li>
+- </ul>
+- </p>
+-</section>
+-
+-<section name="Static Membership">
+- <p>
+- In addition to dynamic discovery, Apache Tribes also supports static membership, with membership verification.
+- To achieve this add the <code>org.apache.catalina.tribes.group.interceptors.StaticMembershipInterceptor</code>
+- underneath the <code>org.apache.catalina.tribes.group.interceptors.TcpFailureDetector</code> interceptor.
+- Inside the <code>StaticMembershipInterceptor</code> you can add the static members you wish to have.
+- The <code>TcpFailureDetector</code> will do a health check on the static members,and also monitor them for crashes
+- so they will have the same level of notification mechanism as the members that are automatically discovered.
+- <source>
+- <Interceptor className="org.apache.catalina.tribes.group.interceptors.StaticMembershipInterceptor">
+- <Member className="org.apache.catalina.tribes.membership.StaticMember"
+- port="5678"
+- securePort="-1"
+- host="tomcat01.mydomain.com"
+- domain="staging-cluster"
+- uniqueId="{0,1,2,3,4,5,6,7,8,9}"/>
+- </Interceptor>
+-
+- </source>
+- </p>
+-</section>
+-
+-<section name="Attributes">
+-
+- <subsection name="Common Attributes">
+- <attributes>
+- <attribute name="className" required="true">
+- Required, as there is no default
+- </attribute>
+- <attribute name="optionFlag" required="false">
+- If you want the interceptor to trigger on certain message depending on the message's option flag,
+- you can setup the interceptors flag here.
+- The default value is <code>0</code>, meaning this interceptor will trigger on all messages.
+- </attribute>
+- </attributes>
+- </subsection>
+-
+- <subsection name="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor Attributes">
+- <attributes>
+- <attribute name="className" required="true">
+- Required, This dispatcher uses JDK 1.5 java.util.concurrent package
+- </attribute>
+- <attribute name="optionFlag" required="false">
+- The default and hard coded value is <code>8 (org.apache.catalina.tribes.Channel.SEND_OPTIONS_ASYNCHRONOUS)</code>.
+- The dispatcher will trigger on this value only, as it is predefined by Tribes.
+- </attribute>
+- </attributes>
+- </subsection>
+- <subsection name="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor Attributes">
+- <attributes>
+- <attribute name="className" required="true">
+- Required, Same implementation as <code>MessageDispatch15Interceptor</code>, but with JDK 1.4 compliance.
+- </attribute>
+- <attribute name="optionFlag" required="false">
+- The default and hard coded value is <code>8 (org.apache.catalina.tribes.Channel.SEND_OPTIONS_ASYNCHRONOUS)</code>.
+- The dispatcher will trigger on this value only, as it is predefined by Tribes.
+- </attribute>
+- </attributes>
+- </subsection>
+- <subsection name="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector Attributes">
+- <attributes>
+- </attributes>
+- </subsection>
+- <subsection name="org.apache.catalina.tribes.group.interceptors.ThroughputInterceptor Attributes">
+- <attributes>
+- <attribute name="interval" required="false">
+- Defines the interval in number of messages when we are to report the throughput statistics.
+- The report is logged to the <code>org.apache.juli.logging.LogFactory.getLog(ThroughputInterceptor.class)</code>
+- logger under the <code>INFO</code> level.
+- Default value is to report every <code>10000</code> messages.
+- </attribute>
+- </attributes>
+- </subsection>
+-
+- <subsection name="Nested element StaticMember Attributes">
+- <attributes>
+- <attribute name="className" required="true">
+- Only one implementation available:<code>org.apache.catalina.tribes.membership.StaticMember</code>
+- </attribute>
+- <attribute name="port" required="true">
+- The port that this static member listens to for cluster messages
+- </attribute>
+- <attribute name="securePort" required="false">
+- The secure port this static member listens to for encrypted cluster messages
+- default value is <code>-1</code>, this value means the member is not listening on a secure port
+- </attribute>
+- <attribute name="host" required="true">
+- The host (or network interface) that this static member listens for cluster messages.
+- Three different type of values are possible:<br/>
+- 1. IP address in the form of "216.123.1.23"<br/>
+- 2. Hostnames like "tomcat01.mydomain.com" or "tomcat01" as long as they resolve correctly<br/>
+- 3. byte array in string form, for example {216,123,12,3}<br/>
+- </attribute>
+- <attribute name="domain" required="true">
+- The logical cluster domain for this this static member listens for cluster messages.
+- Two different type of values are possible:<br/>
+- 1. Regular string values like "staging-domain" or "tomcat-cluster" will be converted into bytes
+- using ISO-8859-1 encoding.
+- 2. byte array in string form, for example {216,123,12,3}<br/>
+- </attribute>
+- <attribute name="uniqueId" required="true">
+- A universally uniqueId for this static member.
+- The values must be 16 bytes in the following form:<br/>
+- 1. byte array in string form, for example {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}<br/>
+- </attribute>
+- </attributes>
+- </subsection>
+- <!--TODO Document all the interceptors-->
+-
+-</section>
+-
+-
+-</body>
+-
+-</document>
+Index: webapps/docs/project.xml
+===================================================================
+--- webapps/docs/project.xml (revision 590752)
++++ webapps/docs/project.xml (working copy)
+@@ -53,7 +53,7 @@
+ <item name="16) MBean Descriptor"
+ href="mbeans-descriptor-howto.html"/>
+ <item name="17) Default Servlet" href="default-servlet.html"/>
+- <item name="18) Clustering" href="cluster-howto.html"/>
++ <item name="18) Clustering" href="not-supported.html"/>
+ <item name="19) Load Balancer" href="balancer-howto.html"/>
+ <item name="20) Connectors" href="connectors.html"/>
+ <item name="21) Monitoring and Management"
+Index: webapps/docs/cluster-howto.xml
+===================================================================
+--- webapps/docs/cluster-howto.xml (revision 590752)
++++ webapps/docs/cluster-howto.xml (working copy)
+@@ -1,701 +0,0 @@
+-<?xml version="1.0"?>
+-<!--
+- 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.
+--->
+-<!DOCTYPE document [
+- <!ENTITY project SYSTEM "project.xml">
+-]>
+-<document url="cluster-howto.html">
+-
+- &project;
+-
+- <properties>
+- <author email="fhanik(a)apache.org">Filip Hanik</author>
+- <author email="pero(a)apache.org">Peter Rossbach</author>
+- <title>Clustering/Session Replication HOW-TO</title>
+- </properties>
+-
+-<body>
+-
+-
+-<section name="Important Note">
+-<p><b>You can also check the <a href="config/cluster.html">configuration reference documentation.</a></b>
+-</p>
+-</section>
+-
+-<section name="For the impatient">
+- <p>
+- Simply add <source><Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/></source>
+- to your <code><Engine></code> or your <code><Host></code> element to enable clustering.
+- </p>
+- <p>
+- Using the above configuration will enable all-to-all session replication
+- using the <code>DeltaManager</code> to replicate session deltas. By all-to-all we mean that the session gets replicated to all the other
+- nodes in the cluster. This works great for smaller cluster but we don't recommend it for larger clusters(a lot of tomcat nodes).
+- Also when using the delta manager it will replicate to all nodes, even nodes that don't have the application deployed.<br/>
+- To get around this problem, you'll want to use the BackupManager. This manager only replicates the session data to one backup
+- node, and only to nodes that have the application deployed. Downside of the BackupManager: not quite as battle tested as the delta manager.
+- <br/>
+- Here are some of the important default values:<br/>
+- 1. Multicast address is 228.0.0.4<br/>
+- 2. Multicast port is 45564 (the port and the address together determine cluster membership.<br/>
+- 3. The IP broadcasted is <code>java.net.InetAddress.getLocalHost().getHostAddress()</code> (make sure you don't broadcast 127.0.0.1, this is a common error)<br/>
+- 4. The TCP port listening for replication messages is the first available server socket in range <code>4000-4100</code><br/>
+- 5. Two listeners are configured <code>ClusterSessionListener</code> and <code>JvmRouteSessionIDBinderListener</code><br/>
+- 6. Two interceptors are configured <code>TcpFailureDetector</code> and <code>MessageDispatch15Interceptor</code><br/>
+- The following is the default cluster configuration:<br/>
+- <source>
+- <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
+- channelSendOptions="8">
+-
+- <Manager className="org.apache.catalina.ha.session.DeltaManager"
+- expireSessionsOnShutdown="false"
+- notifyListenersOnReplication="true"/>
+-
+- <Channel className="org.apache.catalina.tribes.group.GroupChannel">
+- <Membership className="org.apache.catalina.tribes.membership.McastService"
+- address="228.0.0.4"
+- port="45564"
+- frequency="500"
+- dropTime="3000"/>
+- <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
+- address="auto"
+- port="4000"
+- autoBind="100"
+- selectorTimeout="5000"
+- maxThreads="6"/>
+-
+- <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
+- <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
+- </Sender>
+- <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
+- <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
+- </Channel>
+-
+- <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
+- filter=""/>
+- <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
+-
+- <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
+- tempDir="/tmp/war-temp/"
+- deployDir="/tmp/war-deploy/"
+- watchDir="/tmp/war-listen/"
+- watchEnabled="false"/>
+-
+- <ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"/>
+- <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
+- </Cluster>
+- </source>
+- </p>
+- <p>Will cover this section in more detail later in this document.</p>
+-</section>
+-
+-<section name="Cluster Basics">
+-
+-<p>To run session replication in your Tomcat 6.0 container, the following steps
+-should be completed:</p>
+-<ul>
+- <li>All your session attributes must implement <code>java.io.Serializable</code></li>
+- <li>Uncomment the <code>Cluster</code> element in server.xml</li>
+- <li>If you have defined custom cluster valves, make sure you have the <code>ReplicationValve</code> defined as well under the Cluster element in server.xml</li>
+- <li>If your Tomcat instances are running on the same machine, make sure the <code>tcpListenPort</code>
+- attribute is unique for each instance, in most cases Tomcat is smart enough to resolve this on it's own by autodetecting available ports in the range 4000-4100</li>
+- <li>Make sure your <code>web.xml</code> has the <code><distributable/></code> element
+- or set at your <code><Context distributable="true" /></code></li>
+- <li>If you are using mod_jk, make sure that jvmRoute attribute is set at your Engine <code><Engine name="Catalina" jvmRoute="node01" ></code>
+- and that the jvmRoute attribute value matches your worker name in workers.properties</li>
+- <li>Make sure that all nodes have the same time and sync with NTP service!</li>
+- <li>Make sure that your loadbalancer is configured for sticky session mode.</li>
+-</ul>
+-<p>Load balancing can be achieved through many techniques, as seen in the
+-<a href="balancer-howto.html">Load Balancing</a> chapter.</p>
+-<p>Note: Remember that your session state is tracked by a cookie, so your URL must look the same from the out
+- side otherwise, a new session will be created.</p>
+-<p>Note: Clustering support currently requires the JDK version 1.5 or later.</p>
+-<p>The Cluster module uses the Tomcat JULI logging framework, so you can configure logging
+- through the regular logging.properties file. To track messages, you can enable logging on the key:<code>org.apache.catalina.tribes.MESSAGES</code></p>
+-</section>
+-
+-
+-<section name="Overview">
+-
+-<p>To enable session replication in Tomcat, three different paths can be followed to achieve the exact same thing:</p>
+-<ol>
+- <li>Using session persistence, and saving the session to a shared file system (PersistenceManager + FileStore)</li>
+- <li>Using session persistence, and saving the session to a shared database (PersistenceManager + JDBCStore)</li>
+- <li>Using in-memory-replication, using the SimpleTcpCluster that ships with Tomcat 6 (lib/catalina-tribes.jar + lib/catalina-ha.jar)</li>
+-</ol>
+-
+-<p>In this release of session replication, Tomcat can perform an all-to-all replication of session state using the <code>DeltaManager</code> or
+- perform backup replication to only one node using the <code>BackupManager</code>.
+- The all-to-all replication is an algorithm that is only efficient when the clusters are small. For larger clusters, to use
+- a primary-secondary session replication where the session will only be stored at one backup server simply setup the BackupManager. <br/>
+- Currently you can use the domain worker attribute (mod_jk > 1.2.8) to build cluster partitions
+- with the potential of having a more scaleable cluster solution with the DeltaManager(you'll need to configure the domain interceptor for this).
+- In order to keep the network traffic down in an all-to-all environment, you can split your cluster
+- into smaller groups. This can be easily achieved by using different multicast addresses for the different groups.
+- A very simple setup would look like this:
+- </p>
+-
+-<source>
+- DNS Round Robin
+- |
+- Load Balancer
+- / \
+- Cluster1 Cluster2
+- / \ / \
+- Tomcat1 Tomcat2 Tomcat3 Tomcat4
+-</source>
+-
+-<p>What is important to mention here, is that session replication is only the beginning of clustering.
+- Another popular concept used to implement clusters is farming, ie, you deploy your apps only to one
+- server, and the cluster will distribute the deployments across the entire cluster.
+- This is all capabilities that can go into with the FarmWarDeployer (s. cluster example at <code>server.xml</code>)</p>
+-<p>In the next section will go deeper into how session replication works and how to configure it.</p>
+-
+-</section>
+-
+-<section name="Cluster Information">
+-<p>Membership is established using multicast heartbeats.
+- Hence, if you wish to subdivide your clusters, you can do this by
+- changing the multicast IP address or port in the <code><Membership></code> element.
+-</p>
+-<p>
+- The heartbeat contains the IP address of the Tomcat node and the TCP port that
+- Tomcat listens to for replication traffic. All data communication happens over TCP.
+-</p>
+-<p>
+- The <code>ReplicationValve</code> is used to find out when the request has been completed and initiate the
+- replication, if any. Data is only replicated if the session has changed (by calling setAttribute or removeAttribute
+- on the session).
+-</p>
+-<p>
+- One of the most important performance considerations is the synchronous versus asynchronous replication.
+- In a synchronous replication mode the request doesn't return until the replicated session has been
+- sent over the wire and reinstantiated on all the other cluster nodes.
+- Synchronous vs asynchronous is configured using the <code>channelSendOptions</code>
+- flag and is an integer value. The default value for the <code>SimpleTcpCluster/DeltaManager</code> combo is
+- 8, which is asynchronous. You can read more on the <a href="tribes/introduction.html">send flag(overview)</a> or the
+- <a href="http://tomcat.apache.org/tomcat-6.0-doc/api/org/apache/catalina/tribes/Ch...">send flag(javadoc)</a>.
+- During async replication, the request is returned before the data has been replicated. async replication yields shorter
+- request times, and synchronous replication guarantees the session to be replicated before the request returns.
+-</p>
+-</section>
+-
+-<section name="Bind session after crash to failover node">
+-<p>
+- If you are using mod_jk and not using sticky sessions or for some reasons sticky session don't
+- work, or you are simply failing over, the session id will need to be modified as it previously contained
+- the worker id of the previous tomcat (as defined by jvmRoute in the Engine element).
+- To solve this, we will use the JvmRouteBinderValve.
+-</p>
+-<p>
+- The JvmRouteBinderValve rewrites the session id to ensure that the next request will remain sticky
+- (and not fall back to go to random nodes since the worker is no longer available) after a fail over.
+- The valve rewrites the JSESSIONID value in the cookie with the same name.
+- Not having this valve in place, will make it harder to ensure stickyness in case of a failure for the mod_jk module.
+-</p>
+-<p>
+- By default, if no valves are configured, the JvmRouteBinderValve is added on.
+- The cluster message listener called JvmRouteSessionIDBinderListener is also defined by default and is used to actually rewrite the
+- session id on the other nodes in the cluster once a fail over has occurred.
+- Remember, if you are adding your own valves or cluster listeners in server.xml then the defaults are no longer valid,
+- make sure that you add in all the appropriate valves and listeners as defined by the default.
+-</p>
+-<p>
+- <b>Hint:</b><br/>
+- With attribute <i>sessionIdAttribute</i> you can change the request attribute name that included the old session id.
+- Default attribuite name is <i>org.apache.catalina.cluster.session.JvmRouteOrignalSessionID</i>.
+-</p>
+-<p>
+- <b>Trick:</b><br/>
+- You can enable this mod_jk turnover mode via JMX before you drop a node to all backup nodes!
+- Set enable true on all JvmRouteBinderValve backups, disable worker at mod_jk
+- and then drop node and restart it! Then enable mod_jk Worker and disable JvmRouteBinderValves again.
+- This use case means that only requested session are migrated.
+-</p>
+-
+-
+-
+-</section>
+-
+-<section name="Configuration Example">
+- <source>
+- <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
+- channelSendOptions="6">
+-
+- <Manager className="org.apache.catalina.ha.session.BackupManager"
+- expireSessionsOnShutdown="false"
+- notifyListenersOnReplication="true"
+- mapSendOptions="6"/>
+- <!--
+- <Manager className="org.apache.catalina.ha.session.DeltaManager"
+- expireSessionsOnShutdown="false"
+- notifyListenersOnReplication="true"/>
+- -->
+- <Channel className="org.apache.catalina.tribes.group.GroupChannel">
+- <Membership className="org.apache.catalina.tribes.membership.McastService"
+- address="228.0.0.4"
+- port="45564"
+- frequency="500"
+- dropTime="3000"/>
+- <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
+- address="auto"
+- port="5000"
+- selectorTimeout="100"
+- maxThreads="6"/>
+-
+- <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
+- <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
+- </Sender>
+- <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
+- <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
+- <Interceptor className="org.apache.catalina.tribes.group.interceptors.ThroughputInterceptor"/>
+- </Channel>
+-
+- <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
+- filter=".*\.gif;.*\.js;.*\.jpg;.*\.png;.*\.htm;.*\.html;.*\.css;.*\.txt;"/>
+-
+- <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
+- tempDir="/tmp/war-temp/"
+- deployDir="/tmp/war-deploy/"
+- watchDir="/tmp/war-listen/"
+- watchEnabled="false"/>
+-
+- <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
+- </Cluster>
+- </source>
+- <p>
+- Break it down!!
+- </p>
+- <source>
+- <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
+- channelSendOptions="6">
+- </source>
+- <p>
+- The main element, inside this element all cluster details can be configured.
+- The <code>channelSendOptions</code> is the flag that is attached to each message sent by the
+- SimpleTcpCluster class or any objects that are invoking the SimpleTcpCluster.send method.
+- The description of the send flags is available at <a href="http://tomcat.apache.org/tomcat-6.0-doc/api/org/apache/catalina/tribes/Ch...">
+- our javadoc site</a>
+- The <code>DeltaManager</code> sends information using the SimpleTcpCluster.send method, while the backup manager
+- sends it itself directly through the channel.
+- <br/>For more info, Please visit the <a href="config/cluster.html">reference documentation</a>
+- </p>
+- <source>
+- <Manager className="org.apache.catalina.ha.session.BackupManager"
+- expireSessionsOnShutdown="false"
+- notifyListenersOnReplication="true"
+- mapSendOptions="6"/>
+- <!--
+- <Manager className="org.apache.catalina.ha.session.DeltaManager"
+- expireSessionsOnShutdown="false"
+- notifyListenersOnReplication="true"/>
+- -->
+- </source>
+- <p>
+- This is a template for the manager configuration that will be used if no manager is defined in the <Context>
+- element. In Tomcat 5.x each webapp marked distributable had to use the same manager, this is no longer the case
+- since Tomcat 6 you can define a manager class for each webapp, so that you can mix managers in your cluster.
+- Obviously the managers on one node's application has to correspond with the same manager on the same application on the other node.
+- If no manager has been specified for the webapp, and the webapp is marked <distributable/> Tomcat will take this manager configuration
+- and create a manager instance cloning this configuration.
+- <br/>For more info, Please visit the <a href="config/cluster-manager.html">reference documentation</a>
+- </p>
+- <source>
+- <Channel className="org.apache.catalina.tribes.group.GroupChannel">
+- </source>
+- <p>
+- The channel element is <a href="tribes/introduction.html">Tribes</a>, the group communication framework
+- used inside Tomcat. This element encapsulates everything that has to do with communication and membership logic.
+- <br/>For more info, Please visit the <a href="config/cluster-channel.html">reference documentation</a>
+- </p>
+- <source>
+- <Membership className="org.apache.catalina.tribes.membership.McastService"
+- address="228.0.0.4"
+- port="45564"
+- frequency="500"
+- dropTime="3000"/>
+- </source>
+- <p>
+- Membership is done using multicasting. Please note that Tribes also supports static memberships using the
+- <code>StaticMembershipInterceptor</code> if you want to extend your membership to points beyond multicasting.
+- The address attribute is the multicast address used and the port is the multicast port. These two together
+- create the cluster separation. If you want a QA cluster and a production cluster, the easiest config is to
+- have the QA cluster be on a separate multicast address/port combination the the production cluster.<br/>
+- The membership component broadcasts TCP adress/port of itselt to the other nodes so that communication between
+- nodes can be done over TCP. Please note that the address being broadcasted is the one of the
+- <code>Receiver.address</code> attribute.
+- <br/>For more info, Please visit the <a href="config/cluster-membership.html">reference documentation</a>
+- </p>
+- <source>
+- <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
+- address="auto"
+- port="5000"
+- selectorTimeout="100"
+- maxThreads="6"/>
+- </source>
+- <p>
+- In tribes the logic of sending and receiving data has been broken into two functional components. The Receiver, as the name suggests
+- is responsible for receiving messages. Since the Tribes stack is thread less, (a popular improvement now adopted by other frameworks as well),
+- there is a thread pool in this component that has a maxThreads and minThreads setting.<br/>
+- The address attribute is the host address that will be broadcasted by the membership component to the other nodes.
+- <br/>For more info, Please visit the <a href="config/cluster-receiver.html">reference documentation</a>
+- </p>
+- <source>
+-
+- <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
+- <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
+- </Sender>
+- </source>
+- <p>
+- The sender component, as the name indicates is responsible for sending messages to other nodes.
+- The sender has a shell component, the <code>ReplicationTransmitter</code> but the real stuff done is done in the
+- sub component, <code>Transport</code>.
+- Tribes support having a pool of senders, so that messages can be sent in parallel and if using the NIO sender,
+- you can send messages concurrently as well.<br/>
+- Concurrently means one message to multiple senders at the same time and Parallel means multiple messages to multiple senders
+- at the same time.
+- <br/>For more info, Please visit the <a href="config/cluster-sender.html">reference documentation</a>
+- </p>
+- <source>
+- <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
+- <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
+- <Interceptor className="org.apache.catalina.tribes.group.interceptors.ThroughputInterceptor"/>
+- </Channel>
+- </source>
+- <p>
+- Tribes uses a stack to send messages through. Each element in the stack is called an interceptor, and works much like the valves do
+- in the Tomcat servlet container.
+- Using interceptors, logic can be broken into more managable pieces of code. The interceptors configured above are:<br/>
+- TcpFailureDetector - verifies crashed members through TCP, if multicast packets get dropped, this interceptor protects against false positives,
+- ie the node marked as crashed even though it still is alive and running.<br/>
+- MessageDispatch15Interceptor - dispatches messages to a thread (thread pool) to send message asynchrously.<br/>
+- ThroughputInterceptor - prints out simple stats on message traffic.<br/>
+- Please note that the order of interceptors is important. the way they are defined in server.xml is the way they are represented in the
+- channel stack. Think of it as a linked list, with the head being the first most interceptor and the tail the last.
+- <br/>For more info, Please visit the <a href="config/cluster-interceptor.html">reference documentation</a>
+- </p>
+- <source>
+- <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
+- filter=".*\.gif;.*\.js;.*\.jpg;.*\.png;.*\.htm;.*\.html;.*\.css;.*\.txt;"/>
+- </source>
+- <p>
+- The cluster uses valves to track requests to web applications, we've mentioned the ReplicationValve and the JvmRouteBinderValve above.
+- The <Cluster> element itself is not part of the pipeline in Tomcat, instead the cluster adds the valve to its parent container.
+- If the <Cluster> elements is configured in the <Engine> element, the valves get added to the engine and so on.
+- <br/>For more info, Please visit the <a href="config/cluster-valve.html">reference documentation</a>
+- </p>
+- <source>
+- <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
+- tempDir="/tmp/war-temp/"
+- deployDir="/tmp/war-deploy/"
+- watchDir="/tmp/war-listen/"
+- watchEnabled="false"/>
+- </source>
+- <p>
+- The default tomcat cluster supports farmed deployment, ie, the cluster can deploy and undeploy applications on the other nodes.
+- The state of this component is currently in flux but will be addressed soon. There was a change in the deployment algorithm
+- between Tomcat 5.0 and 5.5 and at that point, the logic of this component changed to where the deploy dir has to match the
+- webapps directory.
+- <br/>For more info, Please visit the <a href="config/cluster-deployer.html">reference documentation</a>
+- </p>
+- <source>
+- <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
+- </Cluster>
+- </source>
+- <p>
+- Since the SimpleTcpCluster itself is a sender and receiver of the Channel object, components can register themselves as listeners to
+- the SimpleTcpCluster. The listener above <code>ClusterSessionListener</code> listens for DeltaManager replication messages
+- and applies the deltas to the manager that in turn applies it to the session.
+- <br/>For more info, Please visit the <a href="config/cluster-listener.html">reference documentation</a>
+- </p>
+-
+-</section>
+-
+-<section name="Cluster Architecture">
+-
+-<p><b>Component Levels:</b>
+-<source>
+- Server
+- |
+- Service
+- |
+- Engine
+- | \
+- | --- Cluster --*
+- |
+- Host
+- |
+- ------
+- / \
+- Cluster Context(1-N)
+- | \
+- | -- Manager
+- | \
+- | -- DeltaManager
+- | -- BackupManager
+- |
+- ---------------------------
+- | \
+- Channel \
+- ----------------------------- \
+- | \
+- Interceptor_1 .. \
+- | \
+- Interceptor_N \
+- ----------------------------- \
+- | | | \
+- Receiver Sender Membership \
+- -- Valve
+- | \
+- | -- ReplicationValve
+- | -- JvmRouteBinderValve
+- |
+- -- LifecycleListener
+- |
+- -- ClusterListener
+- | \
+- | -- ClusterSessionListener
+- | -- JvmRouteSessionIDBinderListener
+- |
+- -- Deployer
+- \
+- -- FarmWarDeployer
+-
+-
+-</source>
+-</p>
+-
+-</section>
+-<section name="How it Works">
+-<p>To make it easy to understand how clustering works, We are gonna take you through a series of scenarios.
+- In the scenario we only plan to use two tomcat instances <code>TomcatA</code> and <code>TomcatB</code>.
+- We will cover the following sequence of events:</p>
+-
+-<ol>
+-<li><code>TomcatA</code> starts up</li>
+-<li><code>TomcatB</code> starts up (Wait that TomcatA start is complete)</li>
+-<li><code>TomcatA</code> receives a request, a session <code>S1</code> is created.</li>
+-<li><code>TomcatA</code> crashes</li>
+-<li><code>TomcatB</code> receives a request for session <code>S1</code></li>
+-<li><code>TomcatA</code> starts up</li>
+-<li><code>TomcatA</code> receives a request, invalidate is called on the session (<code>S1</code>)</li>
+-<li><code>TomcatB</code> receives a request, for a new session (<code>S2</code>)</li>
+-<li><code>TomcatA</code> The session <code>S2</code> expires due to inactivity.</li>
+-</ol>
+-
+-<p>Ok, now that we have a good sequence, we will take you through exactly what happens in the session repliction code</p>
+-
+-<ol>
+-<li><b><code>TomcatA</code> starts up</b>
+- <p>
+- Tomcat starts up using the standard start up sequence. When the Host object is created, a cluster object is associated with it.
+- When the contexts are parsed, if the distributable element is in place in web.xml
+- Tomcat asks the Cluster class (in this case <code>SimpleTcpCluster</code>) to create a manager
+- for the replicated context. So with clustering enabled, distributable set in web.xml
+- Tomcat will create a <code>DeltaManager</code> for that context instead of a <code>StandardManager</code>.
+- The cluster class will start up a membership service (multicast) and a replication service (tcp unicast).
+- More on the architecture further down in this document.
+- </p><p></p>
+-</li>
+-<li><b><code>TomcatB</code> starts up</b>
+- <p>
+- When TomcatB starts up, it follows the same sequence as TomcatA did with one exception.
+- The cluster is started and will establish a membership (TomcatA,TomcatB).
+- TomcatB will now request the session state from a server that already exists in the cluster,
+- in this case TomcatA. TomcatA responds to the request, and before TomcatB starts listening
+- for HTTP requests, the state has been transferred from TomcatA to TomcatB.
+- In case TomcatA doesn't respond, TomcatB will time out after 60 seconds, and issue a log
+- entry. The session state gets transferred for each web application that has distributable in
+- its web.xml. Note: To use session replication efficiently, all your tomcat instances should be
+- configured the same.
+- </p><p></p>
+-</li>
+-<li><B><code>TomcatA</code> receives a request, a session <code>S1</code> is created.</B>
+- <p>
+- The request coming in to TomcatA is treated exactly the same way as without session replication.
+- The action happens when the request is completed, the <code>ReplicationValve</code> will intercept
+- the request before the response is returned to the user.
+- At this point it finds that the session has been modified, and it uses TCP to replicata the
+- session to TomcatB. Once the serialized data has been handed off to the operating systems TCP logic,
+- the request returns to the user, back through the valve pipeline.
+- For each request the entire session is replicated, this allows code that modifies attributes
+- in the session without calling setAttribute or removeAttribute to be replicated.
+- a useDirtyFlag configuration parameter can be used to optimize the number of times
+- a session is replicated.
+- </p><p></p>
+-
+-</li>
+-<li><b><code>TomcatA</code> crashes</b>
+- <p>
+- When TomcatA crashes, TomcatB receives a notification that TomcatA has dropped out
+- of the cluster. TomcatB removes TomcatA from its membership list, and TomcatA will no longer
+- be notified of any changes that occurs in TomcatB.
+- The load balancer will redirect the requests from TomcatA to TomcatB and all the sessions
+- are current.
+- </p><p></p>
+-</li>
+-<li><b><code>TomcatB</code> receives a request for session <code>S1</code></b>
+- <p>Nothing exciting, TomcatB will process the request as any other request.
+- </p><p></p>
+-</li>
+-<li><b><code>TomcatA</code> starts up</b>
+- <p>Upon start up, before TomcatA starts taking new request and making itself
+- available to it will follow the start up sequence described above 1) 2).
+- It will join the cluster, contact TomcatB for the current state of all the sessions.
+- And once it receives the session state, it finishes loading and opens its HTTP/mod_jk ports.
+- So no requests will make it to TomcatA until it has received the session state from TomcatB.
+- </p><p></p>
+-</li>
+-<li><b><code>TomcatA</code> receives a request, invalidate is called on the session (<code>S1</code>)</b>
+- <p>The invalidate is call is intercepted, and the session is queued with invalidated sessions.
+- When the request is complete, instead of sending out the session that has changed, it sends out
+- an "expire" message to TomcatB and TomcatB will invalidate the session as well.
+- </p><p></p>
+-
+-</li>
+-<li><b><code>TomcatB</code> receives a request, for a new session (<code>S2</code>)</b>
+- <p>Same scenario as in step 3)
+- </p><p></p>
+-
+-
+-</li>
+-<li><code>TomcatA</code> The session <code>S2</code> expires due to inactivity.
+- <p>The invalidate is call is intercepted the same was as when a session is invalidated by the user,
+- and the session is queued with invalidated sessions.
+- At this point, the invalidet session will not be replicated across until
+- another request comes through the system and checks the invalid queue.
+- </p><p></p>
+-</li>
+-</ol>
+-
+-<p>Phuuuhh! :)</p>
+-
+-<p><b>Membership</b>
+- Clustering membership is established using very simple multicast pings.
+- Each Tomcat instance will periodically send out a multicast ping,
+- in the ping message the instance will broad cast its IP and TCP listen port
+- for replication.
+- If an instance has not received such a ping within a given timeframe, the
+- member is considered dead. Very simple, and very effective!
+- Of course, you need to enable multicasting on your system.
+-</p>
+-
+-<p><b>TCP Replication</b>
+- Once a multicast ping has been received, the member is added to the cluster
+- Upon the next replication request, the sending instance will use the host and
+- port info and establish a TCP socket. Using this socket it sends over the serialized data.
+- The reason I choose TCP sockets is because it has built in flow control and guaranteed delivery.
+- So I know, when I send some data, it will make it there :)
+-</p>
+-
+-<p><b>Distributed locking and pages using frames</b>
+- Tomcat does not keep session instances in sync across the cluster.
+- The implementation of such logic would be to much overhead and cause all
+- kinds of problems. If your client accesses the same session
+- simultanously using multiple requests, then the last request
+- will override the other sessions in the cluster.
+-</p>
+-
+-</section>
+-
+-
+-
+-
+-
+-
+-<section name="Monitoring your Cluster with JMX">
+-<p>Monitoring is a very important question when you use a cluster. Some of the cluster objects are JMX MBeans </p>
+-<p>Add the following parameter to your startup script with Java 5:
+-<source>
+-set CATALINA_OPTS=\
+--Dcom.sun.management.jmxremote \
+--Dcom.sun.management.jmxremote.port=%my.jmx.port% \
+--Dcom.sun.management.jmxremote.ssl=false \
+--Dcom.sun.management.jmxremote.authenticate=false
+-</source>
+-</p>
+-<p>Activate JMX with JDK 1.4:
+-<ol>
+-<li>Install the compat package</li>
+-<li>Install the mx4j-tools.jar at common/lib (use the same mx4j version as your tomcat release)</li>
+-<li>Configure a MX4J JMX HTTP Adaptor at your AJP Connector<p></p>
+-<source>
+-<Connector port="${AJP.PORT}"
+- handler.list="mx"
+- mx.enabled="true"
+- mx.httpHost="${JMX.HOST}"
+- mx.httpPort="${JMX.PORT}"
+- protocol="AJP/1.3" />
+-</source>
+-</li>
+-<li>Start your tomcat and look with your browser to http://${JMX.HOST}:${JMX.PORT}</li>
+-<li>With the connector parameter <code>mx.authMode="basic" mx.authUser="tomcat" mx.authPassword="strange"</code> you can control the access!</li>
+-</ol>
+-</p>
+-<p>
+-List of Cluster Mbeans<br/>
+-<table border="1" cellpadding="5">
+-
+- <tr>
+- <th align="center" bgcolor="aqua">Name</th>
+- <th align="center" bgcolor="aqua">Description</th>
+- <th align="center" bgcolor="aqua">MBean ObjectName - Engine</th>
+- <th align="center" bgcolor="aqua">MBean ObjectName - Host</th>
+- </tr>
+-
+- <tr>
+- <td>Cluster</td>
+- <td>The complete cluster element</td>
+- <td><code>type=Cluster</code></td>
+- <td><code>type=Cluster,host=${HOST}</code></td>
+- </tr>
+-
+- <tr>
+- <td>DeltaManager</td>
+- <td>This manager control the sessions and handle session replication </td>
+- <td><code>type=Manager,path=${APP.CONTEXT.PATH}, host=${HOST}</code></td>
+- <td><code>type=Manager,path=${APP.CONTEXT.PATH}, host=${HOST}</code></td>
+- </tr>
+-
+- <tr>
+- <td>ReplicationValve</td>
+- <td>This valve control the replication to the backup nodes</td>
+- <td><code>type=Valve,name=ReplicationValve</code></td>
+- <td><code>type=Valve,name=ReplicationValve,host=${HOST}</code></td>
+- </tr>
+-
+- <tr>
+- <td>JvmRouteBinderValve</td>
+- <td>This is a cluster fallback valve to change the Session ID to the current tomcat jvmroute.</td>
+- <td><code>type=Valve,name=JvmRouteBinderValve,
+- path=${APP.CONTEXT.PATH}</code></td>
+- <td><code>type=Valve,name=JvmRouteBinderValve,host=${HOST},
+- path=${APP.CONTEXT.PATH}</code></td>
+- </tr>
+-
+-</table>
+-</p>
+-</section>
+-
+-<section name="FAQ">
+-<p>Please see <a href="http://tomcat.apache.org/faq/cluster.html">the clustering section of the FAQ</a>.</p>
+-</section>
+-
+-</body>
+-
+-</document>
+Index: build.sh
+===================================================================
+--- build.sh (revision 0)
++++ build.sh (revision 0)
+@@ -0,0 +1,12 @@
++#
++# Shell script to build Tomcat6
++#
++# build ouput/build.
++ant download
++ant
++# build output/dist
++ant -f dist.xml
++ant -f dist.xml dist-javadoc
++# build extras and release
++ant -f extras.xml
++ant -f dist.xml release
+Index: BUILDING.txt
+===================================================================
+--- BUILDING.txt (revision 590752)
++++ BUILDING.txt (working copy)
+@@ -91,6 +91,8 @@
+ * NOTE: Users accessing the Internet through a proxy must use a properties
+ file to indicate to Ant the proxy configuration. Read below.
+
++* NOTE: This builds output/build.
++
+ * WARNING: Running this command will download binaries to the /usr/share/java
+ directory. Make sure this is appropriate to do on your computer. On Windows,
+ this usually corresponds to the "C:\usr\share\java" directory, unless Cygwin
+@@ -128,11 +130,15 @@
+ The documentation can be easly built:
+
+ cd ${tomcat.source}
++ ant -f dist.xml
+ ant -f dist.xml dist-javadoc
+
++* NOTE: This builds output/dist.
++
+ (6) Building a release running tests:
+
+ cd ${tomcat.source}
++ ant -f extras.xml
+ ant -f dist.xml release
+
+-
++* NOTE: This builds output/extras and output/release.
+Index: build.properties.default
+===================================================================
+--- build.properties.default (revision 590752)
++++ build.properties.default (working copy)
+@@ -35,7 +35,7 @@
+ # Please note this path must be absolute, not relative,
+ # as it is referenced with different working directory
+ # contexts by the various build scripts.
+-base.path=/usr/share/java
++base.path=${basedir}/tcjava
+ #base.path=C:/path/to/the/repository
+ #base.path=/usr/local
+
16 years, 6 months
JBossWeb SVN: r330 - in sandbox: tomcat and 1 other directories.
by jbossweb-commits@lists.jboss.org
Author: jfrederic.clere(a)jboss.com
Date: 2007-11-01 09:13:14 -0400 (Thu, 01 Nov 2007)
New Revision: 330
Added:
sandbox/tomcat/
sandbox/tomcat/tomcat6/
sandbox/tomcat/tomcat6/patch.build.patch
Log:
Patch to improve the build.
Added: sandbox/tomcat/tomcat6/patch.build.patch
===================================================================
--- sandbox/tomcat/tomcat6/patch.build.patch (rev 0)
+++ sandbox/tomcat/tomcat6/patch.build.patch 2007-11-01 13:13:14 UTC (rev 330)
@@ -0,0 +1,61 @@
+Index: build.sh
+===================================================================
+--- build.sh (revision 0)
++++ build.sh (revision 0)
+@@ -0,0 +1,12 @@
++#
++# Shell script to build Tomcat6
++#
++# build ouput/build.
++ant download
++ant
++# build output/dist
++ant -f dist.xml
++ant -f dist.xml dist-javadoc
++# build extras and release
++ant -f extras.xml
++ant -f dist.xml release
+Index: BUILDING.txt
+===================================================================
+--- BUILDING.txt (revision 590752)
++++ BUILDING.txt (working copy)
+@@ -91,6 +91,8 @@
+ * NOTE: Users accessing the Internet through a proxy must use a properties
+ file to indicate to Ant the proxy configuration. Read below.
+
++* NOTE: This builds output/build.
++
+ * WARNING: Running this command will download binaries to the /usr/share/java
+ directory. Make sure this is appropriate to do on your computer. On Windows,
+ this usually corresponds to the "C:\usr\share\java" directory, unless Cygwin
+@@ -128,11 +130,15 @@
+ The documentation can be easly built:
+
+ cd ${tomcat.source}
++ ant -f dist.xml
+ ant -f dist.xml dist-javadoc
+
++* NOTE: This builds output/dist.
++
+ (6) Building a release running tests:
+
+ cd ${tomcat.source}
++ ant -f extras.xml
+ ant -f dist.xml release
+
+-
++* NOTE: This builds output/extras and output/release.
+Index: build.xml
+===================================================================
+--- build.xml (revision 590988)
++++ build.xml (working copy)
+@@ -22,7 +22,9 @@
+
+ <!-- See "build.properties.sample" in the top level directory for all -->
+ <!-- property values you must customize for successful building!!! -->
++<!--
+ <property file="${user.home}/build.properties"/>
++ -->
+ <property file="build.properties"/>
+
+ <property file="build.properties.default"/>
16 years, 6 months