diff options
Diffstat (limited to 'java/common')
132 files changed, 11110 insertions, 3183 deletions
diff --git a/java/common/bin/qpid-run b/java/common/bin/qpid-run index ef4363e88b..15d88992df 100755 --- a/java/common/bin/qpid-run +++ b/java/common/bin/qpid-run @@ -77,10 +77,7 @@ fi #Set the default system properties that we'll use now that they have #all been initialised -declare -a SYSTEM_PROPS -SYSTEM_PROPS[${#SYSTEM_PROPS[@]}]="-Damqj.logging.level=$AMQJ_LOGGING_LEVEL" -SYSTEM_PROPS[${#SYSTEM_PROPS[@]}]="-DQPID_HOME=$QPID_HOME" -SYSTEM_PROPS[${#SYSTEM_PROPS[@]}]="-DQPID_WORK=$QPID_WORK" +SYSTEM_PROPS="-Damqj.logging.level=$AMQJ_LOGGING_LEVEL -DQPID_HOME=$QPID_HOME -DQPID_WORK=$QPID_WORK" #If logprefix or logsuffix set to use PID make that happen #Otherwise just pass the value through for these props @@ -93,7 +90,7 @@ if [ -n "$QPID_LOG_PREFIX" ]; then log $INFO Using qpid logprefix property LOG_PREFIX=" -Dlogprefix=$QPID_LOG_PREFIX" fi - SYSTEM_PROPS[${#SYSTEM_PROPS[@]}]="${LOG_PREFIX}" + SYSTEM_PROPS="${SYSTEM_PROPS} ${LOG_PREFIX}" fi if [ -n "$QPID_LOG_SUFFIX" ]; then @@ -104,10 +101,10 @@ if [ -n "$QPID_LOG_SUFFIX" ]; then log $INFO Using qpig logsuffix property LOG_SUFFIX=" -Dlogsuffix=$QPID_LOG_SUFFIX" fi - SYSTEM_PROPS[${#SYSTEM_PROPS[@]}]="${LOG_SUFFIX}" + SYSTEM_PROPS="${SYSTEM_PROPS} ${LOG_SUFFIX}" fi -log $INFO System Properties set to ${SYSTEM_PROPS[@]} +log $INFO System Properties set to $SYSTEM_PROPS log $INFO QPID_OPTS set to $QPID_OPTS program=$(basename $0) @@ -257,6 +254,6 @@ if $cygwin; then JAVA=$(cygpath -u $JAVA) fi -COMMAND=($JAVA $JAVA_VM $QPID_PNAME $JAVA_GC $JAVA_MEM "${SYSTEM_PROPS[@]}" $JAVA_OPTS $QPID_OPTS "${JAVA_ARGS[@]}") +COMMAND=($JAVA $JAVA_VM $QPID_PNAME $JAVA_GC $JAVA_MEM $SYSTEM_PROPS $JAVA_OPTS $QPID_OPTS "${JAVA_ARGS[@]}") DISPATCH diff --git a/java/common/src/main/java/common.bnd b/java/common/src/main/java/common.bnd index f12fbf9273..ef56ecec9e 100755 --- a/java/common/src/main/java/common.bnd +++ b/java/common/src/main/java/common.bnd @@ -17,7 +17,7 @@ # under the License.
#
-ver: 0.13.0
+ver: 0.9.0
Bundle-SymbolicName: qpid-common
Bundle-Version: ${ver}
diff --git a/java/common/src/main/java/org/apache/mina/common/FixedSizeByteBufferAllocator.java b/java/common/src/main/java/org/apache/mina/common/FixedSizeByteBufferAllocator.java new file mode 100644 index 0000000000..0c311b6645 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/common/FixedSizeByteBufferAllocator.java @@ -0,0 +1,467 @@ +package org.apache.mina.common; + +import org.apache.mina.common.ByteBuffer; + +import java.nio.*; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "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. +* +*/ +public class FixedSizeByteBufferAllocator implements ByteBufferAllocator +{ + + + private static final int MINIMUM_CAPACITY = 1; + + public FixedSizeByteBufferAllocator () + { + } + + public ByteBuffer allocate( int capacity, boolean direct ) + { + java.nio.ByteBuffer nioBuffer; + if( direct ) + { + nioBuffer = java.nio.ByteBuffer.allocateDirect( capacity ); + } + else + { + nioBuffer = java.nio.ByteBuffer.allocate( capacity ); + } + return new FixedSizeByteBuffer( nioBuffer ); + } + + public ByteBuffer wrap( java.nio.ByteBuffer nioBuffer ) + { + return new FixedSizeByteBuffer( nioBuffer ); + } + + public void dispose() + { + } + + + + private static final class FixedSizeByteBuffer extends ByteBuffer + { + private java.nio.ByteBuffer buf; + private int mark = -1; + + + protected FixedSizeByteBuffer( java.nio.ByteBuffer buf ) + { + this.buf = buf; + buf.order( ByteOrder.BIG_ENDIAN ); + } + + public synchronized void acquire() + { + } + + public void release() + { + } + + public java.nio.ByteBuffer buf() + { + return buf; + } + + public boolean isPooled() + { + return false; + } + + public void setPooled( boolean pooled ) + { + } + + public ByteBuffer duplicate() { + return new FixedSizeByteBuffer( this.buf.duplicate() ); + } + + public ByteBuffer slice() { + return new FixedSizeByteBuffer( this.buf.slice() ); + } + + public ByteBuffer asReadOnlyBuffer() { + return new FixedSizeByteBuffer( this.buf.asReadOnlyBuffer() ); + } + + public byte[] array() + { + return buf.array(); + } + + public int arrayOffset() + { + return buf.arrayOffset(); + } + + public boolean isDirect() + { + return buf.isDirect(); + } + + public boolean isReadOnly() + { + return buf.isReadOnly(); + } + + public int capacity() + { + return buf.capacity(); + } + + public ByteBuffer capacity( int newCapacity ) + { + if( newCapacity > capacity() ) + { + throw new IllegalArgumentException(); + } + + return this; + } + + + + public boolean isAutoExpand() + { + return false; + } + + public ByteBuffer setAutoExpand( boolean autoExpand ) + { + if(autoExpand) throw new IllegalArgumentException(); + else return this; + } + + public ByteBuffer expand( int pos, int expectedRemaining ) + { + int end = pos + expectedRemaining; + if( end > capacity() ) + { + // The buffer needs expansion. + capacity( end ); + } + + if( end > limit() ) + { + // We call limit() directly to prevent StackOverflowError + buf.limit( end ); + } + return this; + } + + public int position() + { + return buf.position(); + } + + public ByteBuffer position( int newPosition ) + { + + buf.position( newPosition ); + if( mark > newPosition ) + { + mark = -1; + } + return this; + } + + public int limit() + { + return buf.limit(); + } + + public ByteBuffer limit( int newLimit ) + { + buf.limit( newLimit ); + if( mark > newLimit ) + { + mark = -1; + } + return this; + } + + public ByteBuffer mark() + { + buf.mark(); + mark = position(); + return this; + } + + public int markValue() + { + return mark; + } + + public ByteBuffer reset() + { + buf.reset(); + return this; + } + + public ByteBuffer clear() + { + buf.clear(); + mark = -1; + return this; + } + + public ByteBuffer flip() + { + buf.flip(); + mark = -1; + return this; + } + + public ByteBuffer rewind() + { + buf.rewind(); + mark = -1; + return this; + } + + public byte get() + { + return buf.get(); + } + + public ByteBuffer put( byte b ) + { + buf.put( b ); + return this; + } + + public byte get( int index ) + { + return buf.get( index ); + } + + public ByteBuffer put( int index, byte b ) + { + buf.put( index, b ); + return this; + } + + public ByteBuffer get( byte[] dst, int offset, int length ) + { + buf.get( dst, offset, length ); + return this; + } + + public ByteBuffer put( java.nio.ByteBuffer src ) + { + buf.put( src ); + return this; + } + + public ByteBuffer put( byte[] src, int offset, int length ) + { + buf.put( src, offset, length ); + return this; + } + + public ByteBuffer compact() + { + buf.compact(); + mark = -1; + return this; + } + + public ByteOrder order() + { + return buf.order(); + } + + public ByteBuffer order( ByteOrder bo ) + { + buf.order( bo ); + return this; + } + + public char getChar() + { + return buf.getChar(); + } + + public ByteBuffer putChar( char value ) + { + buf.putChar( value ); + return this; + } + + public char getChar( int index ) + { + return buf.getChar( index ); + } + + public ByteBuffer putChar( int index, char value ) + { + buf.putChar( index, value ); + return this; + } + + public CharBuffer asCharBuffer() + { + return buf.asCharBuffer(); + } + + public short getShort() + { + return buf.getShort(); + } + + public ByteBuffer putShort( short value ) + { + buf.putShort( value ); + return this; + } + + public short getShort( int index ) + { + return buf.getShort( index ); + } + + public ByteBuffer putShort( int index, short value ) + { + buf.putShort( index, value ); + return this; + } + + public ShortBuffer asShortBuffer() + { + return buf.asShortBuffer(); + } + + public int getInt() + { + return buf.getInt(); + } + + public ByteBuffer putInt( int value ) + { + buf.putInt( value ); + return this; + } + + public int getInt( int index ) + { + return buf.getInt( index ); + } + + public ByteBuffer putInt( int index, int value ) + { + buf.putInt( index, value ); + return this; + } + + public IntBuffer asIntBuffer() + { + return buf.asIntBuffer(); + } + + public long getLong() + { + return buf.getLong(); + } + + public ByteBuffer putLong( long value ) + { + buf.putLong( value ); + return this; + } + + public long getLong( int index ) + { + return buf.getLong( index ); + } + + public ByteBuffer putLong( int index, long value ) + { + buf.putLong( index, value ); + return this; + } + + public LongBuffer asLongBuffer() + { + return buf.asLongBuffer(); + } + + public float getFloat() + { + return buf.getFloat(); + } + + public ByteBuffer putFloat( float value ) + { + buf.putFloat( value ); + return this; + } + + public float getFloat( int index ) + { + return buf.getFloat( index ); + } + + public ByteBuffer putFloat( int index, float value ) + { + buf.putFloat( index, value ); + return this; + } + + public FloatBuffer asFloatBuffer() + { + return buf.asFloatBuffer(); + } + + public double getDouble() + { + return buf.getDouble(); + } + + public ByteBuffer putDouble( double value ) + { + buf.putDouble( value ); + return this; + } + + public double getDouble( int index ) + { + return buf.getDouble( index ); + } + + public ByteBuffer putDouble( int index, double value ) + { + buf.putDouble( index, value ); + return this; + } + + public DoubleBuffer asDoubleBuffer() + { + return buf.asDoubleBuffer(); + } + + + } + + +} diff --git a/java/common/src/main/java/org/apache/mina/common/support/DefaultIoFuture.java b/java/common/src/main/java/org/apache/mina/common/support/DefaultIoFuture.java new file mode 100644 index 0000000000..4fd28c4eb5 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/common/support/DefaultIoFuture.java @@ -0,0 +1,227 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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.mina.common.support; + +import org.apache.mina.common.IoFuture; +import org.apache.mina.common.IoSession; +import org.apache.mina.common.IoFutureListener; + +import java.util.List; +import java.util.ArrayList; +import java.util.Iterator; + +/** + * A default implementation of {@link org.apache.mina.common.IoFuture}. + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + */ +public class DefaultIoFuture implements IoFuture +{ + private final IoSession session; + private final Object lock; + private List listeners; + private Object result; + private boolean ready; + + + /** + * Creates a new instance. + * + * @param session an {@link IoSession} which is associated with this future + */ + public DefaultIoFuture( IoSession session ) + { + this.session = session; + this.lock = this; + } + + /** + * Creates a new instance which uses the specified object as a lock. + */ + public DefaultIoFuture( IoSession session, Object lock ) + { + if( lock == null ) + { + throw new NullPointerException( "lock" ); + } + this.session = session; + this.lock = lock; + } + + public IoSession getSession() + { + return session; + } + + public Object getLock() + { + return lock; + } + + public void join() + { + synchronized( lock ) + { + while( !ready ) + { + try + { + lock.wait(); + } + catch( InterruptedException e ) + { + } + } + } + } + + public boolean join( long timeoutInMillis ) + { + long startTime = ( timeoutInMillis <= 0 ) ? 0 : System + .currentTimeMillis(); + long waitTime = timeoutInMillis; + + synchronized( lock ) + { + if( ready ) + { + return ready; + } + else if( waitTime <= 0 ) + { + return ready; + } + + for( ;; ) + { + try + { + lock.wait( waitTime ); + } + catch( InterruptedException e ) + { + } + + if( ready ) + return true; + else + { + waitTime = timeoutInMillis - ( System.currentTimeMillis() - startTime ); + if( waitTime <= 0 ) + { + return ready; + } + } + } + } + } + + public boolean isReady() + { + synchronized( lock ) + { + return ready; + } + } + + /** + * Sets the result of the asynchronous operation, and mark it as finished. + */ + protected void setValue( Object newValue ) + { + synchronized( lock ) + { + // Allow only once. + if( ready ) + { + return; + } + + result = newValue; + ready = true; + lock.notifyAll(); + + notifyListeners(); + } + } + + /** + * Returns the result of the asynchronous operation. + */ + protected Object getValue() + { + synchronized( lock ) + { + return result; + } + } + + public void addListener( IoFutureListener listener ) + { + if( listener == null ) + { + throw new NullPointerException( "listener" ); + } + + synchronized( lock ) + { + if(listeners == null) + { + listeners = new ArrayList(); + } + listeners.add( listener ); + if( ready ) + { + listener.operationComplete( this ); + } + } + } + + public void removeListener( IoFutureListener listener ) + { + if( listener == null ) + { + throw new NullPointerException( "listener" ); + } + + synchronized( lock ) + { + listeners.remove( listener ); + } + } + + private void notifyListeners() + { + synchronized( lock ) + { + + if(listeners != null) + { + + for( Iterator i = listeners.iterator(); i.hasNext(); ) { + ( ( IoFutureListener ) i.next() ).operationComplete( this ); + } + } + } + } +} + + + diff --git a/java/common/src/main/java/org/apache/mina/common/support/IoServiceListenerSupport.java b/java/common/src/main/java/org/apache/mina/common/support/IoServiceListenerSupport.java new file mode 100644 index 0000000000..5723ffbaa9 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/common/support/IoServiceListenerSupport.java @@ -0,0 +1,351 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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.mina.common.support; + +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CountDownLatch; + +import org.apache.mina.common.IoAcceptorConfig; +import org.apache.mina.common.IoConnector; +import org.apache.mina.common.IoFuture; +import org.apache.mina.common.IoFutureListener; +import org.apache.mina.common.IoHandler; +import org.apache.mina.common.IoService; +import org.apache.mina.common.IoServiceConfig; +import org.apache.mina.common.IoServiceListener; +import org.apache.mina.common.IoSession; +import org.apache.mina.util.IdentityHashSet; + +/** + * A helper which provides addition and removal of {@link IoServiceListener}s and firing + * events. + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 446526 $, $Date: 2006-09-15 01:44:11 -0400 (Fri, 15 Sep 2006) $ + */ +public class IoServiceListenerSupport +{ + /** + * A list of {@link IoServiceListener}s. + */ + private final List listeners = new ArrayList(); + + /** + * Tracks managed <tt>serviceAddress</tt>es. + */ + private final Set managedServiceAddresses = new HashSet(); + + /** + * Tracks managed sesssions with <tt>serviceAddress</tt> as a key. + */ + private final Map managedSessions = new HashMap(); + + /** + * Creates a new instance. + */ + public IoServiceListenerSupport() + { + } + + /** + * Adds a new listener. + */ + public void add( IoServiceListener listener ) + { + synchronized( listeners ) + { + listeners.add( listener ); + } + } + + /** + * Removes an existing listener. + */ + public void remove( IoServiceListener listener ) + { + synchronized( listeners ) + { + listeners.remove( listener ); + } + } + + public Set getManagedServiceAddresses() + { + return Collections.unmodifiableSet( managedServiceAddresses ); + } + + public boolean isManaged( SocketAddress serviceAddress ) + { + synchronized( managedServiceAddresses ) + { + return managedServiceAddresses.contains( serviceAddress ); + } + } + + public Set getManagedSessions( SocketAddress serviceAddress ) + { + Set sessions; + synchronized( managedSessions ) + { + sessions = ( Set ) managedSessions.get( serviceAddress ); + if( sessions == null ) + { + sessions = new IdentityHashSet(); + } + } + + synchronized( sessions ) + { + return new IdentityHashSet( sessions ); + } + } + + /** + * Calls {@link IoServiceListener#serviceActivated(IoService, SocketAddress, IoHandler, IoServiceConfig)} + * for all registered listeners. + */ + public void fireServiceActivated( + IoService service, SocketAddress serviceAddress, + IoHandler handler, IoServiceConfig config ) + { + synchronized( managedServiceAddresses ) + { + if( !managedServiceAddresses.add( serviceAddress ) ) + { + return; + } + } + + synchronized( listeners ) + { + for( Iterator i = listeners.iterator(); i.hasNext(); ) + { + ( ( IoServiceListener ) i.next() ).serviceActivated( + service, serviceAddress, handler, config ); + } + } + } + + /** + * Calls {@link IoServiceListener#serviceDeactivated(IoService, SocketAddress, IoHandler, IoServiceConfig)} + * for all registered listeners. + */ + public synchronized void fireServiceDeactivated( + IoService service, SocketAddress serviceAddress, + IoHandler handler, IoServiceConfig config ) + { + synchronized( managedServiceAddresses ) + { + if( !managedServiceAddresses.remove( serviceAddress ) ) + { + return; + } + } + + try + { + synchronized( listeners ) + { + for( Iterator i = listeners.iterator(); i.hasNext(); ) + { + ( ( IoServiceListener ) i.next() ).serviceDeactivated( + service, serviceAddress, handler, config ); + } + } + } + finally + { + disconnectSessions( serviceAddress, config ); + } + } + + + /** + * Calls {@link IoServiceListener#sessionCreated(IoSession)} for all registered listeners. + */ + public void fireSessionCreated( IoSession session ) + { + SocketAddress serviceAddress = session.getServiceAddress(); + + // Get the session set. + boolean firstSession = false; + Set sessions; + synchronized( managedSessions ) + { + sessions = ( Set ) managedSessions.get( serviceAddress ); + if( sessions == null ) + { + sessions = new IdentityHashSet(); + managedSessions.put( serviceAddress, sessions ); + firstSession = true; + } + } + + // If already registered, ignore. + synchronized( sessions ) + { + if ( !sessions.add( session ) ) + { + return; + } + } + + // If the first connector session, fire a virtual service activation event. + if( session.getService() instanceof IoConnector && firstSession ) + { + fireServiceActivated( + session.getService(), session.getServiceAddress(), + session.getHandler(), session.getServiceConfig() ); + } + + // Fire session events. + session.getFilterChain().fireSessionCreated( session ); + session.getFilterChain().fireSessionOpened( session); + + // Fire listener events. + synchronized( listeners ) + { + for( Iterator i = listeners.iterator(); i.hasNext(); ) + { + ( ( IoServiceListener ) i.next() ).sessionCreated( session ); + } + } + } + + /** + * Calls {@link IoServiceListener#sessionDestroyed(IoSession)} for all registered listeners. + */ + public void fireSessionDestroyed( IoSession session ) + { + SocketAddress serviceAddress = session.getServiceAddress(); + + // Get the session set. + Set sessions; + boolean lastSession = false; + synchronized( managedSessions ) + { + sessions = ( Set ) managedSessions.get( serviceAddress ); + // Ignore if unknown. + if( sessions == null ) + { + return; + } + + // Try to remove the remaining empty seession set after removal. + synchronized( sessions ) + { + sessions.remove( session ); + if( sessions.isEmpty() ) + { + managedSessions.remove( serviceAddress ); + lastSession = true; + } + } + } + + // Fire session events. + session.getFilterChain().fireSessionClosed( session ); + + // Fire listener events. + try + { + synchronized( listeners ) + { + for( Iterator i = listeners.iterator(); i.hasNext(); ) + { + ( ( IoServiceListener ) i.next() ).sessionDestroyed( session ); + } + } + } + finally + { + // Fire a virtual service deactivation event for the last session of the connector. + //TODO double-check that this is *STILL* the last session. May not be the case + if( session.getService() instanceof IoConnector && lastSession ) + { + fireServiceDeactivated( + session.getService(), session.getServiceAddress(), + session.getHandler(), session.getServiceConfig() ); + } + } + } + + private void disconnectSessions( SocketAddress serviceAddress, IoServiceConfig config ) + { + if( !( config instanceof IoAcceptorConfig ) ) + { + return; + } + + if( !( ( IoAcceptorConfig ) config ).isDisconnectOnUnbind() ) + { + return; + } + + Set sessions; + synchronized( managedSessions ) + { + sessions = ( Set ) managedSessions.get( serviceAddress ); + } + + if( sessions == null ) + { + return; + } + + Set sessionsCopy; + + // Create a copy to avoid ConcurrentModificationException + synchronized( sessions ) + { + sessionsCopy = new IdentityHashSet( sessions ); + } + + final CountDownLatch latch = new CountDownLatch(sessionsCopy.size()); + + for( Iterator i = sessionsCopy.iterator(); i.hasNext(); ) + { + ( ( IoSession ) i.next() ).close().addListener( new IoFutureListener() + { + public void operationComplete( IoFuture future ) + { + latch.countDown(); + } + } ); + } + + try + { + latch.await(); + } + catch( InterruptedException ie ) + { + // Ignored + } + } +} diff --git a/java/common/src/main/java/org/apache/qpid/transport/SenderClosedException.java b/java/common/src/main/java/org/apache/mina/filter/WriteBufferFullExeception.java index 924c327861..47f19aa76d 100644 --- a/java/common/src/main/java/org/apache/qpid/transport/SenderClosedException.java +++ b/java/common/src/main/java/org/apache/mina/filter/WriteBufferFullExeception.java @@ -1,4 +1,6 @@ -/* +package org.apache.mina.filter; + +import org.apache.mina.common.IoFilter;/* * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -18,35 +20,29 @@ * under the License. * */ -package org.apache.qpid.transport; - -/** - * SenderClosedException - * - */ - -public class SenderClosedException extends SenderException +public class WriteBufferFullExeception extends RuntimeException { + private IoFilter.WriteRequest _writeRequest; - public SenderClosedException(String message, Throwable cause) + public WriteBufferFullExeception() { - super(message, cause); + this(null); } - public SenderClosedException(String message) + public WriteBufferFullExeception(IoFilter.WriteRequest writeRequest) { - super(message); + _writeRequest = writeRequest; } - public SenderClosedException(Throwable cause) + + public void setWriteRequest(IoFilter.WriteRequest writeRequest) { - super(cause); + _writeRequest = writeRequest; } - public void rethrow() + public IoFilter.WriteRequest getWriteRequest() { - throw new SenderClosedException(getMessage(), this); + return _writeRequest; } - } diff --git a/java/common/src/main/java/org/apache/mina/filter/WriteBufferLimitFilterBuilder.java b/java/common/src/main/java/org/apache/mina/filter/WriteBufferLimitFilterBuilder.java new file mode 100644 index 0000000000..4e9db9071a --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/filter/WriteBufferLimitFilterBuilder.java @@ -0,0 +1,272 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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.mina.filter; + +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.DefaultIoFilterChainBuilder; +import org.apache.mina.common.IoFilterAdapter; +import org.apache.mina.common.IoFilterChain; +import org.apache.mina.common.IoSession; +import org.apache.mina.filter.executor.ExecutorFilter; + +import java.util.Iterator; +import java.util.List; + +/** + * This filter will turn the asynchronous filterWrite method in to a blocking send when there are more than + * the prescribed number of messages awaiting filterWrite. It should be used in conjunction with the + * {@link ReadThrottleFilterBuilder} on a server as the blocking writes will allow the read thread to + * cause an Out of Memory exception due to a back log of unprocessed messages. + * + * This is should only be viewed as a temporary work around for DIRMINA-302. + * + * A true solution should not be implemented as a filter as this issue will always occur. On a machine + * where the network is slower than the local producer. + * + * Suggested improvement is to allow implementation of policices on what to do when buffer is full. + * + * They could be: + * Block - As this does + * Wait on a given Future - to drain more of the queue.. in essence this filter with high/low watermarks + * Throw Exception - through the client filterWrite() method to allow them to get immediate feedback on buffer state + * + * <p/> + * <p>Usage: + * <p/> + * <pre><code> + * DefaultFilterChainBuilder builder = ... + * WriteBufferLimitFilterBuilder filter = new WriteBufferLimitFilterBuilder(); + * filter.attach( builder ); + * </code></pre> + * <p/> + * or + * <p/> + * <pre><code> + * IoFilterChain chain = ... + * WriteBufferLimitFilterBuilder filter = new WriteBufferLimitFilterBuilder(); + * filter.attach( chain ); + * </code></pre> + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $ + */ +public class WriteBufferLimitFilterBuilder +{ + public static final String PENDING_SIZE = WriteBufferLimitFilterBuilder.class.getName() + ".pendingSize"; + + private static int DEFAULT_CONNECTION_BUFFER_MESSAGE_COUNT = 5000; + + private volatile boolean throwNotBlock = false; + + private volatile int maximumConnectionBufferCount; + private volatile long maximumConnectionBufferSize; + + private final Object _blockLock = new Object(); + + private int _blockWaiters = 0; + + + public WriteBufferLimitFilterBuilder() + { + this(DEFAULT_CONNECTION_BUFFER_MESSAGE_COUNT); + } + + public WriteBufferLimitFilterBuilder(int maxWriteBufferSize) + { + setMaximumConnectionBufferCount(maxWriteBufferSize); + } + + + /** + * Set the maximum amount pending items in the writeQueue for a given session. + * Changing the value will only take effect when new data is received for a + * connection, including existing connections. Default value is 5000 msgs. + * + * @param maximumConnectionBufferCount New buffer size. Must be > 0 + */ + public void setMaximumConnectionBufferCount(int maximumConnectionBufferCount) + { + this.maximumConnectionBufferCount = maximumConnectionBufferCount; + this.maximumConnectionBufferSize = 0; + } + + public void setMaximumConnectionBufferSize(long maximumConnectionBufferSize) + { + this.maximumConnectionBufferSize = maximumConnectionBufferSize; + this.maximumConnectionBufferCount = 0; + } + + /** + * Attach this filter to the specified filter chain. It will search for the ThreadPoolFilter, and attach itself + * before and after that filter. + * + * @param chain {@link IoFilterChain} to attach self to. + */ + public void attach(IoFilterChain chain) + { + String name = getThreadPoolFilterEntryName(chain.getAll()); + + chain.addBefore(name, getClass().getName() + ".sendlimit", new SendLimit()); + } + + /** + * Attach this filter to the specified builder. It will search for the + * {@link ExecutorFilter}, and attach itself before and after that filter. + * + * @param builder {@link DefaultIoFilterChainBuilder} to attach self to. + */ + public void attach(DefaultIoFilterChainBuilder builder) + { + String name = getThreadPoolFilterEntryName(builder.getAll()); + + builder.addBefore(name, getClass().getName() + ".sendlimit", new SendLimit()); + } + + private String getThreadPoolFilterEntryName(List entries) + { + Iterator i = entries.iterator(); + + while (i.hasNext()) + { + IoFilterChain.Entry entry = (IoFilterChain.Entry) i.next(); + + if (entry.getFilter().getClass().isAssignableFrom(ExecutorFilter.class)) + { + return entry.getName(); + } + } + + throw new IllegalStateException("Chain does not contain a ExecutorFilter"); + } + + + public class SendLimit extends IoFilterAdapter + { + public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception + { + try + { + waitTillSendAllowed(session); + } + catch (WriteBufferFullExeception wbfe) + { + nextFilter.exceptionCaught(session, wbfe); + } + + if (writeRequest.getMessage() instanceof ByteBuffer) + { + increasePendingWriteSize(session, (ByteBuffer) writeRequest.getMessage()); + } + + nextFilter.filterWrite(session, writeRequest); + } + + private void increasePendingWriteSize(IoSession session, ByteBuffer message) + { + synchronized (session) + { + Long pendingSize = getScheduledWriteBytes(session) + message.remaining(); + session.setAttribute(PENDING_SIZE, pendingSize); + } + } + + private boolean sendAllowed(IoSession session) + { + if (session.isClosing()) + { + return true; + } + + int lmswm = maximumConnectionBufferCount; + long lmswb = maximumConnectionBufferSize; + + return (lmswm == 0 || session.getScheduledWriteRequests() < lmswm) + && (lmswb == 0 || getScheduledWriteBytes(session) < lmswb); + } + + private long getScheduledWriteBytes(IoSession session) + { + synchronized (session) + { + Long i = (Long) session.getAttribute(PENDING_SIZE); + return null == i ? 0 : i; + } + } + + private void waitTillSendAllowed(IoSession session) + { + synchronized (_blockLock) + { + if (throwNotBlock) + { + throw new WriteBufferFullExeception(); + } + + _blockWaiters++; + + while (!sendAllowed(session)) + { + try + { + _blockLock.wait(); + } + catch (InterruptedException e) + { + // Ignore. + } + } + _blockWaiters--; + } + } + + public void messageSent(NextFilter nextFilter, IoSession session, Object message) throws Exception + { + if (message instanceof ByteBuffer) + { + decrementPendingWriteSize(session, (ByteBuffer) message); + } + notifyWaitingWriters(); + nextFilter.messageSent(session, message); + } + + private void decrementPendingWriteSize(IoSession session, ByteBuffer message) + { + synchronized (session) + { + session.setAttribute(PENDING_SIZE, getScheduledWriteBytes(session) - message.remaining()); + } + } + + private void notifyWaitingWriters() + { + synchronized (_blockLock) + { + if (_blockWaiters != 0) + { + _blockLock.notifyAll(); + } + } + + } + + }//SentLimit + + +} diff --git a/java/common/src/main/java/org/apache/mina/filter/codec/OurCumulativeProtocolDecoder.java b/java/common/src/main/java/org/apache/mina/filter/codec/OurCumulativeProtocolDecoder.java new file mode 100644 index 0000000000..3f7e206cb4 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/filter/codec/OurCumulativeProtocolDecoder.java @@ -0,0 +1,197 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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.mina.filter.codec; + +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.IoSession; + +/** + * A {@link ProtocolDecoder} that cumulates the content of received + * buffers to a <em>cumulative buffer</em> to help users implement decoders. + * <p> + * If the received {@link ByteBuffer} is only a part of a message. + * decoders should cumulate received buffers to make a message complete or + * to postpone decoding until more buffers arrive. + * <p> + * Here is an example decoder that decodes CRLF terminated lines into + * <code>Command</code> objects: + * <pre> + * public class CRLFTerminatedCommandLineDecoder + * extends CumulativeProtocolDecoder { + * + * private Command parseCommand(ByteBuffer in) { + * // Convert the bytes in the specified buffer to a + * // Command object. + * ... + * } + * + * protected boolean doDecode(IoSession session, ByteBuffer in, + * ProtocolDecoderOutput out) + * throws Exception { + * + * // Remember the initial position. + * int start = in.position(); + * + * // Now find the first CRLF in the buffer. + * byte previous = 0; + * while (in.hasRemaining()) { + * byte current = in.get(); + * + * if (previous == '\r' && current == '\n') { + * // Remember the current position and limit. + * int position = in.position(); + * int limit = in.limit(); + * try { + * in.position(start); + * in.limit(position); + * // The bytes between in.position() and in.limit() + * // now contain a full CRLF terminated line. + * out.write(parseCommand(in.slice())); + * } finally { + * // Set the position to point right after the + * // detected line and set the limit to the old + * // one. + * in.position(position); + * in.limit(limit); + * } + * // Decoded one line; CumulativeProtocolDecoder will + * // call me again until I return false. So just + * // return true until there are no more lines in the + * // buffer. + * return true; + * } + * + * previous = current; + * } + * + * // Could not find CRLF in the buffer. Reset the initial + * // position to the one we recorded above. + * in.position(start); + * + * return false; + * } + * } + * </pre> + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $ + */ +public abstract class OurCumulativeProtocolDecoder extends ProtocolDecoderAdapter { + + private static final String BUFFER = OurCumulativeProtocolDecoder.class + .getName() + + ".Buffer"; + + /** + * Creates a new instance. + */ + protected OurCumulativeProtocolDecoder() { + } + + /** + * Cumulates content of <tt>in</tt> into internal buffer and forwards + * decoding request to {@link #doDecode(IoSession, ByteBuffer, ProtocolDecoderOutput)}. + * <tt>doDecode()</tt> is invoked repeatedly until it returns <tt>false</tt> + * and the cumulative buffer is NOT compacted after decoding ends. + * + * @throws IllegalStateException if your <tt>doDecode()</tt> returned + * <tt>true</tt> not consuming the cumulative buffer. + */ + public void decode(IoSession session, ByteBuffer in, + ProtocolDecoderOutput out) throws Exception { + boolean usingSessionBuffer = true; + ByteBuffer buf = (ByteBuffer) session.getAttribute(BUFFER); + // If we have a session buffer, append data to that; otherwise + // use the buffer read from the network directly. + if (buf != null) { + buf.put(in); + buf.flip(); + } else { + buf = in; + usingSessionBuffer = false; + } + + for (;;) { + int oldPos = buf.position(); + boolean decoded = doDecode(session, buf, out); + if (decoded) { + if (buf.position() == oldPos) { + throw new IllegalStateException( + "doDecode() can't return true when buffer is not consumed."); + } + + if (!buf.hasRemaining()) { + break; + } + } else { + break; + } + } + + + // if there is any data left that cannot be decoded, we store + // it in a buffer in the session and next time this decoder is + // invoked the session buffer gets appended to + if (buf.hasRemaining()) { + storeRemainingInSession(buf, session); + } else { + if (usingSessionBuffer) + removeSessionBuffer(session); + } + } + + /** + * Implement this method to consume the specified cumulative buffer and + * decode its content into message(s). + * + * @param in the cumulative buffer + * @return <tt>true</tt> if and only if there's more to decode in the buffer + * and you want to have <tt>doDecode</tt> method invoked again. + * Return <tt>false</tt> if remaining data is not enough to decode, + * then this method will be invoked again when more data is cumulated. + * @throws Exception if cannot decode <tt>in</tt>. + */ + protected abstract boolean doDecode(IoSession session, ByteBuffer in, + ProtocolDecoderOutput out) throws Exception; + + /** + * Releases the cumulative buffer used by the specified <tt>session</tt>. + * Please don't forget to call <tt>super.dispose( session )</tt> when + * you override this method. + */ + public void dispose(IoSession session) throws Exception { + removeSessionBuffer(session); + } + + private void removeSessionBuffer(IoSession session) { + ByteBuffer buf = (ByteBuffer) session.removeAttribute(BUFFER); + if (buf != null) { + buf.release(); + } + } + + private void storeRemainingInSession(ByteBuffer buf, IoSession session) { + ByteBuffer remainingBuf = ByteBuffer.allocate(buf.capacity()); + remainingBuf.setAutoExpand(true); + remainingBuf.order(buf.order()); + remainingBuf.put(buf); + session.setAttribute(BUFFER, remainingBuf); + } +} diff --git a/java/common/src/main/java/org/apache/mina/filter/codec/QpidProtocolCodecFilter.java b/java/common/src/main/java/org/apache/mina/filter/codec/QpidProtocolCodecFilter.java new file mode 100644 index 0000000000..b8c6f29720 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/filter/codec/QpidProtocolCodecFilter.java @@ -0,0 +1,440 @@ +package org.apache.mina.filter.codec; + + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "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. +* +*/ + +import org.apache.mina.common.*; +import org.apache.mina.common.support.DefaultWriteFuture; +import org.apache.mina.filter.codec.support.SimpleProtocolDecoderOutput; +import org.apache.mina.util.SessionLog; +import org.apache.mina.util.Queue; + + +public class QpidProtocolCodecFilter extends IoFilterAdapter +{ + public static final String ENCODER = QpidProtocolCodecFilter.class.getName() + ".encoder"; + public static final String DECODER = QpidProtocolCodecFilter.class.getName() + ".decoder"; + + private static final Class[] EMPTY_PARAMS = new Class[0]; + private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.wrap( new byte[0] ); + + private final ProtocolCodecFactory factory; + + public QpidProtocolCodecFilter( ProtocolCodecFactory factory ) + { + if( factory == null ) + { + throw new NullPointerException( "factory" ); + } + this.factory = factory; + } + + public QpidProtocolCodecFilter( final ProtocolEncoder encoder, final ProtocolDecoder decoder ) + { + if( encoder == null ) + { + throw new NullPointerException( "encoder" ); + } + if( decoder == null ) + { + throw new NullPointerException( "decoder" ); + } + + this.factory = new ProtocolCodecFactory() + { + public ProtocolEncoder getEncoder() + { + return encoder; + } + + public ProtocolDecoder getDecoder() + { + return decoder; + } + }; + } + + public QpidProtocolCodecFilter( final Class encoderClass, final Class decoderClass ) + { + if( encoderClass == null ) + { + throw new NullPointerException( "encoderClass" ); + } + if( decoderClass == null ) + { + throw new NullPointerException( "decoderClass" ); + } + if( !ProtocolEncoder.class.isAssignableFrom( encoderClass ) ) + { + throw new IllegalArgumentException( "encoderClass: " + encoderClass.getName() ); + } + if( !ProtocolDecoder.class.isAssignableFrom( decoderClass ) ) + { + throw new IllegalArgumentException( "decoderClass: " + decoderClass.getName() ); + } + try + { + encoderClass.getConstructor( EMPTY_PARAMS ); + } + catch( NoSuchMethodException e ) + { + throw new IllegalArgumentException( "encoderClass doesn't have a public default constructor." ); + } + try + { + decoderClass.getConstructor( EMPTY_PARAMS ); + } + catch( NoSuchMethodException e ) + { + throw new IllegalArgumentException( "decoderClass doesn't have a public default constructor." ); + } + + this.factory = new ProtocolCodecFactory() + { + public ProtocolEncoder getEncoder() throws Exception + { + return ( ProtocolEncoder ) encoderClass.newInstance(); + } + + public ProtocolDecoder getDecoder() throws Exception + { + return ( ProtocolDecoder ) decoderClass.newInstance(); + } + }; + } + + public void onPreAdd( IoFilterChain parent, String name, IoFilter.NextFilter nextFilter ) throws Exception + { + if( parent.contains( ProtocolCodecFilter.class ) ) + { + throw new IllegalStateException( "A filter chain cannot contain more than one QpidProtocolCodecFilter." ); + } + } + + public void messageReceived( IoFilter.NextFilter nextFilter, IoSession session, Object message ) throws Exception + { + if( !( message instanceof ByteBuffer ) ) + { + nextFilter.messageReceived( session, message ); + return; + } + + ByteBuffer in = ( ByteBuffer ) message; + ProtocolDecoder decoder = getDecoder( session ); + ProtocolDecoderOutput decoderOut = getDecoderOut( session, nextFilter ); + + try + { + decoder.decode( session, in, decoderOut ); + } + catch( Throwable t ) + { + ProtocolDecoderException pde; + if( t instanceof ProtocolDecoderException ) + { + pde = ( ProtocolDecoderException ) t; + } + else + { + pde = new ProtocolDecoderException( t ); + } + pde.setHexdump( in.getHexDump() ); + throw pde; + } + finally + { + // Dispose the decoder if this session is connectionless. + if( session.getTransportType().isConnectionless() ) + { + disposeDecoder( session ); + } + + // Release the read buffer. + in.release(); + + decoderOut.flush(); + } + } + + public void messageSent( IoFilter.NextFilter nextFilter, IoSession session, Object message ) throws Exception + { + if( message instanceof HiddenByteBuffer ) + { + return; + } + + if( !( message instanceof MessageByteBuffer ) ) + { + nextFilter.messageSent( session, message ); + return; + } + + nextFilter.messageSent( session, ( ( MessageByteBuffer ) message ).message ); + } + + public void filterWrite( IoFilter.NextFilter nextFilter, IoSession session, IoFilter.WriteRequest writeRequest ) throws Exception + { + Object message = writeRequest.getMessage(); + if( message instanceof ByteBuffer ) + { + nextFilter.filterWrite( session, writeRequest ); + return; + } + + ProtocolEncoder encoder = getEncoder( session ); + ProtocolEncoderOutputImpl encoderOut = getEncoderOut( session, nextFilter, writeRequest ); + + try + { + encoder.encode( session, message, encoderOut ); + encoderOut.flush(); + nextFilter.filterWrite( + session, + new IoFilter.WriteRequest( + new MessageByteBuffer( writeRequest.getMessage() ), + writeRequest.getFuture(), writeRequest.getDestination() ) ); + } + catch( Throwable t ) + { + ProtocolEncoderException pee; + if( t instanceof ProtocolEncoderException ) + { + pee = ( ProtocolEncoderException ) t; + } + else + { + pee = new ProtocolEncoderException( t ); + } + throw pee; + } + finally + { + // Dispose the encoder if this session is connectionless. + if( session.getTransportType().isConnectionless() ) + { + disposeEncoder( session ); + } + } + } + + public void sessionClosed( IoFilter.NextFilter nextFilter, IoSession session ) throws Exception + { + // Call finishDecode() first when a connection is closed. + ProtocolDecoder decoder = getDecoder( session ); + ProtocolDecoderOutput decoderOut = getDecoderOut( session, nextFilter ); + try + { + decoder.finishDecode( session, decoderOut ); + } + catch( Throwable t ) + { + ProtocolDecoderException pde; + if( t instanceof ProtocolDecoderException ) + { + pde = ( ProtocolDecoderException ) t; + } + else + { + pde = new ProtocolDecoderException( t ); + } + throw pde; + } + finally + { + // Dispose all. + disposeEncoder( session ); + disposeDecoder( session ); + + decoderOut.flush(); + } + + nextFilter.sessionClosed( session ); + } + + private ProtocolEncoder getEncoder( IoSession session ) throws Exception + { + ProtocolEncoder encoder = ( ProtocolEncoder ) session.getAttribute( ENCODER ); + if( encoder == null ) + { + encoder = factory.getEncoder(); + session.setAttribute( ENCODER, encoder ); + } + return encoder; + } + + private ProtocolEncoderOutputImpl getEncoderOut( IoSession session, IoFilter.NextFilter nextFilter, IoFilter.WriteRequest writeRequest ) + { + return new ProtocolEncoderOutputImpl( session, nextFilter, writeRequest ); + } + + private ProtocolDecoder getDecoder( IoSession session ) throws Exception + { + ProtocolDecoder decoder = ( ProtocolDecoder ) session.getAttribute( DECODER ); + if( decoder == null ) + { + decoder = factory.getDecoder(); + session.setAttribute( DECODER, decoder ); + } + return decoder; + } + + private ProtocolDecoderOutput getDecoderOut( IoSession session, IoFilter.NextFilter nextFilter ) + { + return new SimpleProtocolDecoderOutput( session, nextFilter ); + } + + private void disposeEncoder( IoSession session ) + { + ProtocolEncoder encoder = ( ProtocolEncoder ) session.removeAttribute( ENCODER ); + if( encoder == null ) + { + return; + } + + try + { + encoder.dispose( session ); + } + catch( Throwable t ) + { + SessionLog.warn( + session, + "Failed to dispose: " + encoder.getClass().getName() + + " (" + encoder + ')' ); + } + } + + private void disposeDecoder( IoSession session ) + { + ProtocolDecoder decoder = ( ProtocolDecoder ) session.removeAttribute( DECODER ); + if( decoder == null ) + { + return; + } + + try + { + decoder.dispose( session ); + } + catch( Throwable t ) + { + SessionLog.warn( + session, + "Falied to dispose: " + decoder.getClass().getName() + + " (" + decoder + ')' ); + } + } + + private static class HiddenByteBuffer extends ByteBufferProxy + { + private HiddenByteBuffer( ByteBuffer buf ) + { + super( buf ); + } + } + + private static class MessageByteBuffer extends ByteBufferProxy + { + private final Object message; + + private MessageByteBuffer( Object message ) + { + super( EMPTY_BUFFER ); + this.message = message; + } + + public void acquire() + { + // no-op since we are wraping a zero-byte buffer, this instance is to just curry the message + } + + public void release() + { + // no-op since we are wraping a zero-byte buffer, this instance is to just curry the message + } + } + + private static class ProtocolEncoderOutputImpl implements ProtocolEncoderOutput + { + private ByteBuffer buffer; + + private final IoSession session; + private final IoFilter.NextFilter nextFilter; + private final IoFilter.WriteRequest writeRequest; + + public ProtocolEncoderOutputImpl( IoSession session, IoFilter.NextFilter nextFilter, IoFilter.WriteRequest writeRequest ) + { + this.session = session; + this.nextFilter = nextFilter; + this.writeRequest = writeRequest; + } + + + + public void write( ByteBuffer buf ) + { + if(buffer != null) + { + flush(); + } + buffer = buf; + } + + public void mergeAll() + { + } + + public WriteFuture flush() + { + WriteFuture future = null; + if( buffer == null ) + { + return null; + } + else + { + ByteBuffer buf = buffer; + // Flush only when the buffer has remaining. + if( buf.hasRemaining() ) + { + future = doFlush( buf ); + } + + } + + return future; + } + + + protected WriteFuture doFlush( ByteBuffer buf ) + { + WriteFuture future = new DefaultWriteFuture( session ); + nextFilter.filterWrite( + session, + new IoFilter.WriteRequest( + buf, + future, writeRequest.getDestination() ) ); + return future; + } + } +} + diff --git a/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketAcceptor.java b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketAcceptor.java new file mode 100644 index 0000000000..e5360d32e0 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketAcceptor.java @@ -0,0 +1,547 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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.mina.transport.socket.nio; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.mina.common.ExceptionMonitor; +import org.apache.mina.common.IoAcceptor; +import org.apache.mina.common.IoHandler; +import org.apache.mina.common.IoServiceConfig; +import org.apache.mina.common.support.BaseIoAcceptor; +import org.apache.mina.util.Queue; +import org.apache.mina.util.NewThreadExecutor; +import org.apache.mina.util.NamePreservingRunnable; +import edu.emory.mathcs.backport.java.util.concurrent.Executor; + +/** + * {@link IoAcceptor} for socket transport (TCP/IP). + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $ + */ +public class MultiThreadSocketAcceptor extends SocketAcceptor +{ + /** + * @noinspection StaticNonFinalField + */ + private static volatile int nextId = 0; + + private final Executor executor; + private final Object lock = new Object(); + private final int id = nextId ++; + private final String threadName = "SocketAcceptor-" + id; + private final Map channels = new HashMap(); + + private final Queue registerQueue = new Queue(); + private final Queue cancelQueue = new Queue(); + + private final MultiThreadSocketIoProcessor[] ioProcessors; + private final int processorCount; + + /** + * @noinspection FieldAccessedSynchronizedAndUnsynchronized + */ + private Selector selector; + private Worker worker; + private int processorDistributor = 0; + + /** + * Create an acceptor with a single processing thread using a NewThreadExecutor + */ + public MultiThreadSocketAcceptor() + { + this( 1, new NewThreadExecutor() ); + } + + /** + * Create an acceptor with the desired number of processing threads + * + * @param processorCount Number of processing threads + * @param executor Executor to use for launching threads + */ + public MultiThreadSocketAcceptor( int processorCount, Executor executor ) + { + if( processorCount < 1 ) + { + throw new IllegalArgumentException( "Must have at least one processor" ); + } + + this.executor = executor; + this.processorCount = processorCount; + ioProcessors = new MultiThreadSocketIoProcessor[processorCount]; + + for( int i = 0; i < processorCount; i++ ) + { + ioProcessors[i] = new MultiThreadSocketIoProcessor( "SocketAcceptorIoProcessor-" + id + "." + i, executor ); + } + } + + + /** + * Binds to the specified <code>address</code> and handles incoming connections with the specified + * <code>handler</code>. Backlog value is configured to the value of <code>backlog</code> property. + * + * @throws IOException if failed to bind + */ + public void bind( SocketAddress address, IoHandler handler, IoServiceConfig config ) throws IOException + { + if( handler == null ) + { + throw new NullPointerException( "handler" ); + } + + if( address != null && !( address instanceof InetSocketAddress ) ) + { + throw new IllegalArgumentException( "Unexpected address type: " + address.getClass() ); + } + + if( config == null ) + { + config = getDefaultConfig(); + } + + RegistrationRequest request = new RegistrationRequest( address, handler, config ); + + synchronized( registerQueue ) + { + registerQueue.push( request ); + } + + startupWorker(); + + selector.wakeup(); + + synchronized( request ) + { + while( !request.done ) + { + try + { + request.wait(); + } + catch( InterruptedException e ) + { + ExceptionMonitor.getInstance().exceptionCaught( e ); + } + } + } + + if( request.exception != null ) + { + throw request.exception; + } + } + + + private synchronized void startupWorker() throws IOException + { + synchronized( lock ) + { + if( worker == null ) + { + selector = Selector.open(); + worker = new Worker(); + + executor.execute( new NamePreservingRunnable( worker ) ); + } + } + } + + public void unbind( SocketAddress address ) + { + if( address == null ) + { + throw new NullPointerException( "address" ); + } + + CancellationRequest request = new CancellationRequest( address ); + + try + { + startupWorker(); + } + catch( IOException e ) + { + // IOException is thrown only when Worker thread is not + // running and failed to open a selector. We simply throw + // IllegalArgumentException here because we can simply + // conclude that nothing is bound to the selector. + throw new IllegalArgumentException( "Address not bound: " + address ); + } + + synchronized( cancelQueue ) + { + cancelQueue.push( request ); + } + + selector.wakeup(); + + synchronized( request ) + { + while( !request.done ) + { + try + { + request.wait(); + } + catch( InterruptedException e ) + { + ExceptionMonitor.getInstance().exceptionCaught( e ); + } + } + } + + if( request.exception != null ) + { + request.exception.fillInStackTrace(); + + throw request.exception; + } + } + + + private class Worker implements Runnable + { + public void run() + { + Thread.currentThread().setName(MultiThreadSocketAcceptor.this.threadName ); + + for( ; ; ) + { + try + { + int nKeys = selector.select(); + + registerNew(); + + if( nKeys > 0 ) + { + processSessions( selector.selectedKeys() ); + } + + cancelKeys(); + + if( selector.keys().isEmpty() ) + { + synchronized( lock ) + { + if( selector.keys().isEmpty() && + registerQueue.isEmpty() && + cancelQueue.isEmpty() ) + { + worker = null; + try + { + selector.close(); + } + catch( IOException e ) + { + ExceptionMonitor.getInstance().exceptionCaught( e ); + } + finally + { + selector = null; + } + break; + } + } + } + } + catch( IOException e ) + { + ExceptionMonitor.getInstance().exceptionCaught( e ); + + try + { + Thread.sleep( 1000 ); + } + catch( InterruptedException e1 ) + { + ExceptionMonitor.getInstance().exceptionCaught( e1 ); + } + } + } + } + + private void processSessions( Set keys ) throws IOException + { + Iterator it = keys.iterator(); + while( it.hasNext() ) + { + SelectionKey key = ( SelectionKey ) it.next(); + + it.remove(); + + if( !key.isAcceptable() ) + { + continue; + } + + ServerSocketChannel ssc = ( ServerSocketChannel ) key.channel(); + + SocketChannel ch = ssc.accept(); + + if( ch == null ) + { + continue; + } + + boolean success = false; + try + { + + RegistrationRequest req = ( RegistrationRequest ) key.attachment(); + + MultiThreadSocketSessionImpl session = new MultiThreadSocketSessionImpl( + MultiThreadSocketAcceptor.this, nextProcessor(), getListeners(), + req.config, ch, req.handler, req.address ); + + // New Interface +// SocketSessionImpl session = new SocketSessionImpl( +// SocketAcceptor.this, nextProcessor(), getListeners(), +// req.config, ch, req.handler, req.address ); + + + getFilterChainBuilder().buildFilterChain( session.getFilterChain() ); + req.config.getFilterChainBuilder().buildFilterChain( session.getFilterChain() ); + req.config.getThreadModel().buildFilterChain( session.getFilterChain() ); + session.getIoProcessor().addNew( session ); + success = true; + } + catch( Throwable t ) + { + ExceptionMonitor.getInstance().exceptionCaught( t ); + } + finally + { + if( !success ) + { + ch.close(); + } + } + } + } + } + + private MultiThreadSocketIoProcessor nextProcessor() + { + return ioProcessors[processorDistributor++ % processorCount]; + } + + + private void registerNew() + { + if( registerQueue.isEmpty() ) + { + return; + } + + for( ; ; ) + { + RegistrationRequest req; + + synchronized( registerQueue ) + { + req = ( RegistrationRequest ) registerQueue.pop(); + } + + if( req == null ) + { + break; + } + + ServerSocketChannel ssc = null; + + try + { + ssc = ServerSocketChannel.open(); + ssc.configureBlocking( false ); + + // Configure the server socket, + SocketAcceptorConfig cfg; + if( req.config instanceof SocketAcceptorConfig ) + { + cfg = ( SocketAcceptorConfig ) req.config; + } + else + { + cfg = ( SocketAcceptorConfig ) getDefaultConfig(); + } + + ssc.socket().setReuseAddress( cfg.isReuseAddress() ); + ssc.socket().setReceiveBufferSize( + ( ( SocketSessionConfig ) cfg.getSessionConfig() ).getReceiveBufferSize() ); + + // and bind. + ssc.socket().bind( req.address, cfg.getBacklog() ); + if( req.address == null || req.address.getPort() == 0 ) + { + req.address = ( InetSocketAddress ) ssc.socket().getLocalSocketAddress(); + } + ssc.register( selector, SelectionKey.OP_ACCEPT, req ); + + synchronized( channels ) + { + channels.put( req.address, ssc ); + } + + getListeners().fireServiceActivated( + this, req.address, req.handler, req.config ); + } + catch( IOException e ) + { + req.exception = e; + } + finally + { + synchronized( req ) + { + req.done = true; + + req.notifyAll(); + } + + if( ssc != null && req.exception != null ) + { + try + { + ssc.close(); + } + catch( IOException e ) + { + ExceptionMonitor.getInstance().exceptionCaught( e ); + } + } + } + } + } + + + private void cancelKeys() + { + if( cancelQueue.isEmpty() ) + { + return; + } + + for( ; ; ) + { + CancellationRequest request; + + synchronized( cancelQueue ) + { + request = ( CancellationRequest ) cancelQueue.pop(); + } + + if( request == null ) + { + break; + } + + ServerSocketChannel ssc; + synchronized( channels ) + { + ssc = ( ServerSocketChannel ) channels.remove( request.address ); + } + + // close the channel + try + { + if( ssc == null ) + { + request.exception = new IllegalArgumentException( "Address not bound: " + request.address ); + } + else + { + SelectionKey key = ssc.keyFor( selector ); + request.registrationRequest = ( RegistrationRequest ) key.attachment(); + key.cancel(); + + selector.wakeup(); // wake up again to trigger thread death + + ssc.close(); + } + } + catch( IOException e ) + { + ExceptionMonitor.getInstance().exceptionCaught( e ); + } + finally + { + synchronized( request ) + { + request.done = true; + request.notifyAll(); + } + + if( request.exception == null ) + { + getListeners().fireServiceDeactivated( + this, request.address, + request.registrationRequest.handler, + request.registrationRequest.config ); + } + } + } + } + + private static class RegistrationRequest + { + private InetSocketAddress address; + private final IoHandler handler; + private final IoServiceConfig config; + private IOException exception; + private boolean done; + + private RegistrationRequest( SocketAddress address, IoHandler handler, IoServiceConfig config ) + { + this.address = ( InetSocketAddress ) address; + this.handler = handler; + this.config = config; + } + } + + + private static class CancellationRequest + { + private final SocketAddress address; + private boolean done; + private RegistrationRequest registrationRequest; + private RuntimeException exception; + + private CancellationRequest( SocketAddress address ) + { + this.address = address; + } + } +} diff --git a/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketConnector.java b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketConnector.java new file mode 100644 index 0000000000..7344f70078 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketConnector.java @@ -0,0 +1,486 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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.mina.transport.socket.nio; + +import edu.emory.mathcs.backport.java.util.concurrent.Executor; +import org.apache.mina.common.ConnectFuture; +import org.apache.mina.common.ExceptionMonitor; +import org.apache.mina.common.IoConnector; +import org.apache.mina.common.IoConnectorConfig; +import org.apache.mina.common.IoHandler; +import org.apache.mina.common.IoServiceConfig; +import org.apache.mina.common.support.AbstractIoFilterChain; +import org.apache.mina.common.support.DefaultConnectFuture; +import org.apache.mina.util.NamePreservingRunnable; +import org.apache.mina.util.NewThreadExecutor; +import org.apache.mina.util.Queue; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.util.Iterator; +import java.util.Set; + +/** + * {@link IoConnector} for socket transport (TCP/IP). + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $ + */ +public class MultiThreadSocketConnector extends SocketConnector +{ + /** @noinspection StaticNonFinalField */ + private static volatile int nextId = 0; + + private final Object lock = new Object(); + private final int id = nextId++; + private final String threadName = "SocketConnector-" + id; + + private final Queue connectQueue = new Queue(); + private final MultiThreadSocketIoProcessor[] ioProcessors; + private final int processorCount; + private final Executor executor; + + /** @noinspection FieldAccessedSynchronizedAndUnsynchronized */ + private Selector selector; + private Worker worker; + private int processorDistributor = 0; + private int workerTimeout = 60; // 1 min. + + /** Create a connector with a single processing thread using a NewThreadExecutor */ + public MultiThreadSocketConnector() + { + this(1, new NewThreadExecutor()); + } + + /** + * Create a connector with the desired number of processing threads + * + * @param processorCount Number of processing threads + * @param executor Executor to use for launching threads + */ + public MultiThreadSocketConnector(int processorCount, Executor executor) + { + if (processorCount < 1) + { + throw new IllegalArgumentException("Must have at least one processor"); + } + + this.executor = executor; + this.processorCount = processorCount; + ioProcessors = new MultiThreadSocketIoProcessor[processorCount]; + + for (int i = 0; i < processorCount; i++) + { + ioProcessors[i] = new MultiThreadSocketIoProcessor("SocketConnectorIoProcessor-" + id + "." + i, executor); + } + } + + /** + * How many seconds to keep the connection thread alive between connection requests + * + * @return Number of seconds to keep connection thread alive + */ + public int getWorkerTimeout() + { + return workerTimeout; + } + + /** + * Set how many seconds the connection worker thread should remain alive once idle before terminating itself. + * + * @param workerTimeout Number of seconds to keep thread alive. Must be >=0 + */ + public void setWorkerTimeout(int workerTimeout) + { + if (workerTimeout < 0) + { + throw new IllegalArgumentException("Must be >= 0"); + } + this.workerTimeout = workerTimeout; + } + + public ConnectFuture connect(SocketAddress address, IoHandler handler, IoServiceConfig config) + { + return connect(address, null, handler, config); + } + + public ConnectFuture connect(SocketAddress address, SocketAddress localAddress, + IoHandler handler, IoServiceConfig config) + { + if (address == null) + { + throw new NullPointerException("address"); + } + if (handler == null) + { + throw new NullPointerException("handler"); + } + + if (!(address instanceof InetSocketAddress)) + { + throw new IllegalArgumentException("Unexpected address type: " + + address.getClass()); + } + + if (localAddress != null && !(localAddress instanceof InetSocketAddress)) + { + throw new IllegalArgumentException("Unexpected local address type: " + + localAddress.getClass()); + } + + if (config == null) + { + config = getDefaultConfig(); + } + + SocketChannel ch = null; + boolean success = false; + try + { + ch = SocketChannel.open(); + ch.socket().setReuseAddress(true); + if (localAddress != null) + { + ch.socket().bind(localAddress); + } + + ch.configureBlocking(false); + + if (ch.connect(address)) + { + DefaultConnectFuture future = new DefaultConnectFuture(); + newSession(ch, handler, config, future); + success = true; + return future; + } + + success = true; + } + catch (IOException e) + { + return DefaultConnectFuture.newFailedFuture(e); + } + finally + { + if (!success && ch != null) + { + try + { + ch.close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + } + } + + ConnectionRequest request = new ConnectionRequest(ch, handler, config); + synchronized (lock) + { + try + { + startupWorker(); + } + catch (IOException e) + { + try + { + ch.close(); + } + catch (IOException e2) + { + ExceptionMonitor.getInstance().exceptionCaught(e2); + } + + return DefaultConnectFuture.newFailedFuture(e); + } + } + + synchronized (connectQueue) + { + connectQueue.push(request); + } + selector.wakeup(); + + return request; + } + + private synchronized void startupWorker() throws IOException + { + if (worker == null) + { + selector = Selector.open(); + worker = new Worker(); + executor.execute(new NamePreservingRunnable(worker)); + } + } + + private void registerNew() + { + if (connectQueue.isEmpty()) + { + return; + } + + for (; ;) + { + ConnectionRequest req; + synchronized (connectQueue) + { + req = (ConnectionRequest) connectQueue.pop(); + } + + if (req == null) + { + break; + } + + SocketChannel ch = req.channel; + try + { + ch.register(selector, SelectionKey.OP_CONNECT, req); + } + catch (IOException e) + { + req.setException(e); + } + } + } + + private void processSessions(Set keys) + { + Iterator it = keys.iterator(); + + while (it.hasNext()) + { + SelectionKey key = (SelectionKey) it.next(); + + if (!key.isConnectable()) + { + continue; + } + + SocketChannel ch = (SocketChannel) key.channel(); + ConnectionRequest entry = (ConnectionRequest) key.attachment(); + + boolean success = false; + try + { + ch.finishConnect(); + newSession(ch, entry.handler, entry.config, entry); + success = true; + } + catch (Throwable e) + { + entry.setException(e); + } + finally + { + key.cancel(); + if (!success) + { + try + { + ch.close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + } + } + } + + keys.clear(); + } + + private void processTimedOutSessions(Set keys) + { + long currentTime = System.currentTimeMillis(); + Iterator it = keys.iterator(); + + while (it.hasNext()) + { + SelectionKey key = (SelectionKey) it.next(); + + if (!key.isValid()) + { + continue; + } + + ConnectionRequest entry = (ConnectionRequest) key.attachment(); + + if (currentTime >= entry.deadline) + { + entry.setException(new ConnectException()); + try + { + key.channel().close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + finally + { + key.cancel(); + } + } + } + } + + private void newSession(SocketChannel ch, IoHandler handler, IoServiceConfig config, ConnectFuture connectFuture) + throws IOException + { + MultiThreadSocketSessionImpl session = + new MultiThreadSocketSessionImpl(this, nextProcessor(), getListeners(), + config, ch, handler, ch.socket().getRemoteSocketAddress()); + + //new interface +// SocketSessionImpl session = new SocketSessionImpl( +// this, nextProcessor(), getListeners(), +// config, ch, handler, ch.socket().getRemoteSocketAddress() ); + try + { + getFilterChainBuilder().buildFilterChain(session.getFilterChain()); + config.getFilterChainBuilder().buildFilterChain(session.getFilterChain()); + config.getThreadModel().buildFilterChain(session.getFilterChain()); + } + catch (Throwable e) + { + throw (IOException) new IOException("Failed to create a session.").initCause(e); + } + + // Set the ConnectFuture of the specified session, which will be + // removed and notified by AbstractIoFilterChain eventually. + session.setAttribute( AbstractIoFilterChain.CONNECT_FUTURE, connectFuture ); + + // Forward the remaining process to the SocketIoProcessor. + session.getIoProcessor().addNew(session); + } + + private MultiThreadSocketIoProcessor nextProcessor() + { + return ioProcessors[processorDistributor++ % processorCount]; + } + + private class Worker implements Runnable + { + private long lastActive = System.currentTimeMillis(); + + public void run() + { + Thread.currentThread().setName(MultiThreadSocketConnector.this.threadName); + + for (; ;) + { + try + { + int nKeys = selector.select(1000); + + registerNew(); + + if (nKeys > 0) + { + processSessions(selector.selectedKeys()); + } + + processTimedOutSessions(selector.keys()); + + if (selector.keys().isEmpty()) + { + if (System.currentTimeMillis() - lastActive > workerTimeout * 1000L) + { + synchronized (lock) + { + if (selector.keys().isEmpty() && + connectQueue.isEmpty()) + { + worker = null; + try + { + selector.close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + finally + { + selector = null; + } + break; + } + } + } + } + else + { + lastActive = System.currentTimeMillis(); + } + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + + try + { + Thread.sleep(1000); + } + catch (InterruptedException e1) + { + ExceptionMonitor.getInstance().exceptionCaught(e1); + } + } + } + } + } + + private class ConnectionRequest extends DefaultConnectFuture + { + private final SocketChannel channel; + private final long deadline; + private final IoHandler handler; + private final IoServiceConfig config; + + private ConnectionRequest(SocketChannel channel, IoHandler handler, IoServiceConfig config) + { + this.channel = channel; + long timeout; + if (config instanceof IoConnectorConfig) + { + timeout = ((IoConnectorConfig) config).getConnectTimeoutMillis(); + } + else + { + timeout = ((IoConnectorConfig) getDefaultConfig()).getConnectTimeoutMillis(); + } + this.deadline = System.currentTimeMillis() + timeout; + this.handler = handler; + this.config = config; + } + } +} diff --git a/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketFilterChain.java b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketFilterChain.java new file mode 100644 index 0000000000..67b8c8d820 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketFilterChain.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.mina.transport.socket.nio; + +import java.io.IOException; + +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.IoFilterChain; +import org.apache.mina.common.IoSession; +import org.apache.mina.common.IoFilter.WriteRequest; +import org.apache.mina.common.support.AbstractIoFilterChain; +import org.apache.mina.util.Queue; + +/** + * An {@link IoFilterChain} for socket transport (TCP/IP). + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + */ +class MultiThreadSocketFilterChain extends AbstractIoFilterChain { + + MultiThreadSocketFilterChain( IoSession parent ) + { + super( parent ); + } + + protected void doWrite( IoSession session, WriteRequest writeRequest ) + { + MultiThreadSocketSessionImpl s = (MultiThreadSocketSessionImpl) session; + Queue writeRequestQueue = s.getWriteRequestQueue(); + + // SocketIoProcessor.doFlush() will reset it after write is finished + // because the buffer will be passed with messageSent event. + ( ( ByteBuffer ) writeRequest.getMessage() ).mark(); + synchronized( writeRequestQueue ) + { + writeRequestQueue.push( writeRequest ); + if( writeRequestQueue.size() == 1 && session.getTrafficMask().isWritable() ) + { + // Notify SocketIoProcessor only when writeRequestQueue was empty. + s.getIoProcessor().flush( s ); + } + } + } + + protected void doClose( IoSession session ) throws IOException + { + MultiThreadSocketSessionImpl s = (MultiThreadSocketSessionImpl) session; + s.getIoProcessor().remove( s ); + } +} diff --git a/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketIoProcessor.java b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketIoProcessor.java new file mode 100644 index 0000000000..c23ad8686f --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketIoProcessor.java @@ -0,0 +1,1026 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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.mina.transport.socket.nio; + +import edu.emory.mathcs.backport.java.util.concurrent.Executor; +import edu.emory.mathcs.backport.java.util.concurrent.locks.ReentrantLock; +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.ExceptionMonitor; +import org.apache.mina.common.IdleStatus; +import org.apache.mina.common.IoFilter.WriteRequest; +import org.apache.mina.common.WriteTimeoutException; +import org.apache.mina.util.IdentityHashSet; +import org.apache.mina.util.NamePreservingRunnable; +import org.apache.mina.util.Queue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * Performs all I/O operations for sockets which is connected or bound. This class is used by MINA internally. + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $, + */ +class MultiThreadSocketIoProcessor extends SocketIoProcessor +{ + Logger _logger = LoggerFactory.getLogger(MultiThreadSocketIoProcessor.class); + Logger _loggerRead = LoggerFactory.getLogger(MultiThreadSocketIoProcessor.class + ".Reader"); + Logger _loggerWrite = LoggerFactory.getLogger(MultiThreadSocketIoProcessor.class + ".Writer"); + + private static final long SELECTOR_TIMEOUT = 1000L; + + private int MAX_READ_BYTES_PER_SESSION = 524288; //512K + private int MAX_FLUSH_BYTES_PER_SESSION = 524288; //512K + + private final Object readLock = new Object(); + private final Object writeLock = new Object(); + + private final String threadName; + private final Executor executor; + + private ReentrantLock trafficMaskUpdateLock = new ReentrantLock(); + + /** @noinspection FieldAccessedSynchronizedAndUnsynchronized */ + private volatile Selector selector, writeSelector; + + private final Queue newSessions = new Queue(); + private final Queue removingSessions = new Queue(); + private final BlockingQueue flushingSessions = new LinkedBlockingQueue(); + private final IdentityHashSet flushingSessionsSet = new IdentityHashSet(); + + private final Queue trafficControllingSessions = new Queue(); + + private ReadWorker readWorker; + private WriteWorker writeWorker; + private long lastIdleReadCheckTime = System.currentTimeMillis(); + private long lastIdleWriteCheckTime = System.currentTimeMillis(); + + MultiThreadSocketIoProcessor(String threadName, Executor executor) + { + super(threadName, executor); + this.threadName = threadName; + this.executor = executor; + } + + void addNew(SocketSessionImpl session) throws IOException + { + synchronized (newSessions) + { + newSessions.push(session); + } + + startupWorker(); + + selector.wakeup(); + writeSelector.wakeup(); + } + + void remove(SocketSessionImpl session) throws IOException + { + scheduleRemove(session); + startupWorker(); + selector.wakeup(); + } + + private void startupWorker() throws IOException + { + synchronized (readLock) + { + if (readWorker == null) + { + selector = Selector.open(); + readWorker = new ReadWorker(); + executor.execute(new NamePreservingRunnable(readWorker)); + } + } + + synchronized (writeLock) + { + if (writeWorker == null) + { + writeSelector = Selector.open(); + writeWorker = new WriteWorker(); + executor.execute(new NamePreservingRunnable(writeWorker)); + } + } + + } + + void flush(SocketSessionImpl session) + { + scheduleFlush(session); + Selector selector = this.writeSelector; + + if (selector != null) + { + selector.wakeup(); + } + } + + void updateTrafficMask(SocketSessionImpl session) + { + scheduleTrafficControl(session); + Selector selector = this.selector; + if (selector != null) + { + selector.wakeup(); + } + } + + private void scheduleRemove(SocketSessionImpl session) + { + synchronized (removingSessions) + { + removingSessions.push(session); + } + } + + private void scheduleFlush(SocketSessionImpl session) + { + synchronized (flushingSessionsSet) + { + //if flushingSessions grows to contain Integer.MAX_VALUE sessions + // then this will fail. + if (flushingSessionsSet.add(session)) + { + flushingSessions.offer(session); + } + } + } + + private void scheduleTrafficControl(SocketSessionImpl session) + { + synchronized (trafficControllingSessions) + { + trafficControllingSessions.push(session); + } + } + + private void doAddNewReader() throws InterruptedException + { + if (newSessions.isEmpty()) + { + return; + } + + for (; ;) + { + MultiThreadSocketSessionImpl session; + + synchronized (newSessions) + { + session = (MultiThreadSocketSessionImpl) newSessions.peek(); + } + + if (session == null) + { + break; + } + + SocketChannel ch = session.getChannel(); + + + try + { + + ch.configureBlocking(false); + session.setSelectionKey(ch.register(selector, + SelectionKey.OP_READ, + session)); + + //System.out.println("ReadDebug:"+"Awaiting Registration"); + session.awaitRegistration(); + sessionCreated(session); + } + catch (IOException e) + { + // Clear the AbstractIoFilterChain.CONNECT_FUTURE attribute + // and call ConnectFuture.setException(). + session.getFilterChain().fireExceptionCaught(session, e); + } + } + } + + + private void doAddNewWrite() throws InterruptedException + { + if (newSessions.isEmpty()) + { + return; + } + + for (; ;) + { + MultiThreadSocketSessionImpl session; + + synchronized (newSessions) + { + session = (MultiThreadSocketSessionImpl) newSessions.peek(); + } + + if (session == null) + { + break; + } + + SocketChannel ch = session.getChannel(); + + try + { + ch.configureBlocking(false); + synchronized (flushingSessionsSet) + { + flushingSessionsSet.add(session); + } + + session.setWriteSelectionKey(ch.register(writeSelector, + SelectionKey.OP_WRITE, + session)); + + //System.out.println("WriteDebug:"+"Awaiting Registration"); + session.awaitRegistration(); + sessionCreated(session); + } + catch (IOException e) + { + + // Clear the AbstractIoFilterChain.CONNECT_FUTURE attribute + // and call ConnectFuture.setException(). + session.getFilterChain().fireExceptionCaught(session, e); + } + } + } + + + private void sessionCreated(SocketSessionImpl sessionParam) throws InterruptedException + { + MultiThreadSocketSessionImpl session = (MultiThreadSocketSessionImpl) sessionParam; + synchronized (newSessions) + { + if (!session.created()) + { + _logger.debug("Popping new session"); + newSessions.pop(); + + // AbstractIoFilterChain.CONNECT_FUTURE is cleared inside here + // in AbstractIoFilterChain.fireSessionOpened(). + session.getServiceListeners().fireSessionCreated(session); + + session.doneCreation(); + } + } + } + + private void doRemove() + { + if (removingSessions.isEmpty()) + { + return; + } + + for (; ;) + { + MultiThreadSocketSessionImpl session; + + synchronized (removingSessions) + { + session = (MultiThreadSocketSessionImpl) removingSessions.pop(); + } + + if (session == null) + { + break; + } + + SocketChannel ch = session.getChannel(); + SelectionKey key = session.getReadSelectionKey(); + SelectionKey writeKey = session.getWriteSelectionKey(); + + // Retry later if session is not yet fully initialized. + // (In case that Session.close() is called before addSession() is processed) + if (key == null || writeKey == null) + { + scheduleRemove(session); + break; + } + // skip if channel is already closed + if (!key.isValid() || !writeKey.isValid()) + { + continue; + } + + try + { + //System.out.println("ReadDebug:"+"Removing Session: " + System.identityHashCode(session)); + synchronized (readLock) + { + key.cancel(); + } + synchronized (writeLock) + { + writeKey.cancel(); + } + ch.close(); + } + catch (IOException e) + { + session.getFilterChain().fireExceptionCaught(session, e); + } + finally + { + releaseWriteBuffers(session); + session.getServiceListeners().fireSessionDestroyed(session); + } + } + } + + private void processRead(Set selectedKeys) + { + Iterator it = selectedKeys.iterator(); + + while (it.hasNext()) + { + SelectionKey key = (SelectionKey) it.next(); + MultiThreadSocketSessionImpl session = (MultiThreadSocketSessionImpl) key.attachment(); + + synchronized (readLock) + { + if (key.isValid() && key.isReadable() && session.getTrafficMask().isReadable()) + { + read(session); + } + } + + } + + selectedKeys.clear(); + } + + private void processWrite(Set selectedKeys) + { + Iterator it = selectedKeys.iterator(); + + while (it.hasNext()) + { + SelectionKey key = (SelectionKey) it.next(); + SocketSessionImpl session = (SocketSessionImpl) key.attachment(); + + synchronized (writeLock) + { + if (key.isValid() && key.isWritable() && session.getTrafficMask().isWritable()) + { + + // Clear OP_WRITE + key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE)); + + synchronized (flushingSessionsSet) + { + flushingSessions.offer(session); + } + } + } + } + + selectedKeys.clear(); + } + + private void read(SocketSessionImpl session) + { + + //if (_loggerWrite.isDebugEnabled()) + { + //System.out.println("WriteDebug:"+"Starting read for Session:" + System.identityHashCode(session)); + } + + int totalReadBytes = 0; + + while (totalReadBytes <= MAX_READ_BYTES_PER_SESSION) + { + ByteBuffer buf = ByteBuffer.allocate(session.getReadBufferSize()); + SocketChannel ch = session.getChannel(); + + try + { + buf.clear(); + + int readBytes = 0; + int ret; + + try + { + while ((ret = ch.read(buf.buf())) > 0) + { + readBytes += ret; + totalReadBytes += ret; + } + } + finally + { + buf.flip(); + } + + + if (readBytes > 0) + { + session.increaseReadBytes(readBytes); + + session.getFilterChain().fireMessageReceived(session, buf); + buf = null; + } + + if (ret <= 0) + { + if (ret == 0) + { + if (readBytes == session.getReadBufferSize()) + { + continue; + } + } + else + { + scheduleRemove(session); + } + + break; + } + } + catch (Throwable e) + { + if (e instanceof IOException) + { + scheduleRemove(session); + } + session.getFilterChain().fireExceptionCaught(session, e); + + //Stop Reading this session. + return; + } + finally + { + if (buf != null) + { + buf.release(); + } + } + }//for + + // if (_loggerWrite.isDebugEnabled()) + { + //System.out.println("WriteDebug:"+"Read for Session:" + System.identityHashCode(session) + " got: " + totalReadBytes); + } + } + + + private void notifyReadIdleness() + { + // process idle sessions + long currentTime = System.currentTimeMillis(); + if ((currentTime - lastIdleReadCheckTime) >= 1000) + { + lastIdleReadCheckTime = currentTime; + Set keys = selector.keys(); + if (keys != null) + { + for (Iterator it = keys.iterator(); it.hasNext();) + { + SelectionKey key = (SelectionKey) it.next(); + SocketSessionImpl session = (SocketSessionImpl) key.attachment(); + notifyReadIdleness(session, currentTime); + } + } + } + } + + private void notifyWriteIdleness() + { + // process idle sessions + long currentTime = System.currentTimeMillis(); + if ((currentTime - lastIdleWriteCheckTime) >= 1000) + { + lastIdleWriteCheckTime = currentTime; + Set keys = writeSelector.keys(); + if (keys != null) + { + for (Iterator it = keys.iterator(); it.hasNext();) + { + SelectionKey key = (SelectionKey) it.next(); + SocketSessionImpl session = (SocketSessionImpl) key.attachment(); + notifyWriteIdleness(session, currentTime); + } + } + } + } + + private void notifyReadIdleness(SocketSessionImpl session, long currentTime) + { + notifyIdleness0( + session, currentTime, + session.getIdleTimeInMillis(IdleStatus.BOTH_IDLE), + IdleStatus.BOTH_IDLE, + Math.max(session.getLastIoTime(), session.getLastIdleTime(IdleStatus.BOTH_IDLE))); + notifyIdleness0( + session, currentTime, + session.getIdleTimeInMillis(IdleStatus.READER_IDLE), + IdleStatus.READER_IDLE, + Math.max(session.getLastReadTime(), session.getLastIdleTime(IdleStatus.READER_IDLE))); + + notifyWriteTimeout(session, currentTime, session + .getWriteTimeoutInMillis(), session.getLastWriteTime()); + } + + private void notifyWriteIdleness(SocketSessionImpl session, long currentTime) + { + notifyIdleness0( + session, currentTime, + session.getIdleTimeInMillis(IdleStatus.BOTH_IDLE), + IdleStatus.BOTH_IDLE, + Math.max(session.getLastIoTime(), session.getLastIdleTime(IdleStatus.BOTH_IDLE))); + notifyIdleness0( + session, currentTime, + session.getIdleTimeInMillis(IdleStatus.WRITER_IDLE), + IdleStatus.WRITER_IDLE, + Math.max(session.getLastWriteTime(), session.getLastIdleTime(IdleStatus.WRITER_IDLE))); + + notifyWriteTimeout(session, currentTime, session + .getWriteTimeoutInMillis(), session.getLastWriteTime()); + } + + private void notifyIdleness0(SocketSessionImpl session, long currentTime, + long idleTime, IdleStatus status, + long lastIoTime) + { + if (idleTime > 0 && lastIoTime != 0 + && (currentTime - lastIoTime) >= idleTime) + { + session.increaseIdleCount(status); + session.getFilterChain().fireSessionIdle(session, status); + } + } + + private void notifyWriteTimeout(SocketSessionImpl session, + long currentTime, + long writeTimeout, long lastIoTime) + { + + MultiThreadSocketSessionImpl sesh = (MultiThreadSocketSessionImpl) session; + SelectionKey key = sesh.getWriteSelectionKey(); + + synchronized (writeLock) + { + if (writeTimeout > 0 + && (currentTime - lastIoTime) >= writeTimeout + && key != null && key.isValid() + && (key.interestOps() & SelectionKey.OP_WRITE) != 0) + { + session.getFilterChain().fireExceptionCaught(session, new WriteTimeoutException()); + } + } + } + + private SocketSessionImpl getNextFlushingSession() + { + return (SocketSessionImpl) flushingSessions.poll(); + } + + private void releaseSession(SocketSessionImpl session) + { + synchronized (session.getWriteRequestQueue()) + { + synchronized (flushingSessionsSet) + { + if (session.getScheduledWriteRequests() > 0) + { + if (_loggerWrite.isDebugEnabled()) + { + //System.out.println("WriteDebug:"+"Reflush" + System.identityHashCode(session)); + } + flushingSessions.offer(session); + } + else + { + if (_loggerWrite.isDebugEnabled()) + { + //System.out.println("WriteDebug:"+"Releasing session " + System.identityHashCode(session)); + } + flushingSessionsSet.remove(session); + } + } + } + } + + private void releaseWriteBuffers(SocketSessionImpl session) + { + Queue writeRequestQueue = session.getWriteRequestQueue(); + WriteRequest req; + + //Should this be synchronized? + synchronized (writeRequestQueue) + { + while ((req = (WriteRequest) writeRequestQueue.pop()) != null) + { + try + { + ((ByteBuffer) req.getMessage()).release(); + } + catch (IllegalStateException e) + { + session.getFilterChain().fireExceptionCaught(session, e); + } + finally + { + req.getFuture().setWritten(false); + } + } + } + } + + private void doFlush() + { + MultiThreadSocketSessionImpl session; + + while ((session = (MultiThreadSocketSessionImpl) getNextFlushingSession()) != null) + { + if (!session.isConnected()) + { + releaseWriteBuffers(session); + releaseSession(session); + continue; + } + + SelectionKey key = session.getWriteSelectionKey(); + // Retry later if session is not yet fully initialized. + // (In case that Session.write() is called before addSession() is processed) + if (key == null) + { + scheduleFlush(session); + releaseSession(session); + continue; + } + // skip if channel is already closed + if (!key.isValid()) + { + releaseSession(session); + continue; + } + + try + { + if (doFlush(session)) + { + releaseSession(session); + } + } + catch (IOException e) + { + releaseSession(session); + scheduleRemove(session); + session.getFilterChain().fireExceptionCaught(session, e); + } + + } + + } + + private boolean doFlush(SocketSessionImpl sessionParam) throws IOException + { + MultiThreadSocketSessionImpl session = (MultiThreadSocketSessionImpl) sessionParam; + // Clear OP_WRITE + SelectionKey key = session.getWriteSelectionKey(); + synchronized (writeLock) + { + key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE)); + } + SocketChannel ch = session.getChannel(); + Queue writeRequestQueue = session.getWriteRequestQueue(); + + long totalFlushedBytes = 0; + while (true) + { + WriteRequest req; + + synchronized (writeRequestQueue) + { + req = (WriteRequest) writeRequestQueue.first(); + } + + if (req == null) + { + break; + } + + ByteBuffer buf = (ByteBuffer) req.getMessage(); + if (buf.remaining() == 0) + { + synchronized (writeRequestQueue) + { + writeRequestQueue.pop(); + } + + session.increaseWrittenMessages(); + + buf.reset(); + session.getFilterChain().fireMessageSent(session, req); + continue; + } + + + int writtenBytes = 0; + + // Reported as DIRMINA-362 + //note: todo: fixme: Not sure it is important but if we see NoyYetConnected exceptions or 100% CPU in the kernel then this is it. + if (key.isWritable()) + { + writtenBytes = ch.write(buf.buf()); + totalFlushedBytes += writtenBytes; + } + + if (writtenBytes > 0) + { + session.increaseWrittenBytes(writtenBytes); + } + + if (buf.hasRemaining() || (totalFlushedBytes <= MAX_FLUSH_BYTES_PER_SESSION)) + { + // Kernel buffer is full + synchronized (writeLock) + { + key.interestOps(key.interestOps() | SelectionKey.OP_WRITE); + } + if (_loggerWrite.isDebugEnabled()) + { + //System.out.println("WriteDebug:"+"Written BF: " + (session.getWrittenBytes() - totalFlushedBytes) + " bytes"); + } + return false; + } + } + + if (_loggerWrite.isDebugEnabled()) + { + //System.out.println("WriteDebug:"+"Written : " + (session.getWrittenBytes() - totalFlushedBytes) + " bytes"); + } + return true; + } + + private void doUpdateTrafficMask() + { + if (trafficControllingSessions.isEmpty() || trafficMaskUpdateLock.isLocked()) + { + return; + } + + // Synchronize over entire operation as this method should be called + // from both read and write thread and we don't want the order of the + // updates to get changed. + trafficMaskUpdateLock.lock(); + try + { + for (; ;) + { + MultiThreadSocketSessionImpl session; + + session = (MultiThreadSocketSessionImpl) trafficControllingSessions.pop(); + + if (session == null) + { + break; + } + + SelectionKey key = session.getReadSelectionKey(); + // Retry later if session is not yet fully initialized. + // (In case that Session.suspend??() or session.resume??() is + // called before addSession() is processed) + if (key == null) + { + scheduleTrafficControl(session); + break; + } + // skip if channel is already closed + if (!key.isValid()) + { + continue; + } + + // The normal is OP_READ and, if there are write requests in the + // session's write queue, set OP_WRITE to trigger flushing. + + //Sset to Read and Write if there is nothing then the cost + // is one loop through the flusher. + int ops = SelectionKey.OP_READ; + + // Now mask the preferred ops with the mask of the current session + int mask = session.getTrafficMask().getInterestOps(); + synchronized (readLock) + { + key.interestOps(ops & mask); + } + //Change key to the WriteSelection Key + key = session.getWriteSelectionKey(); + if (key != null && key.isValid()) + { + Queue writeRequestQueue = session.getWriteRequestQueue(); + synchronized (writeRequestQueue) + { + if (!writeRequestQueue.isEmpty()) + { + ops = SelectionKey.OP_WRITE; + synchronized (writeLock) + { + key.interestOps(ops & mask); + } + } + } + } + } + } + finally + { + trafficMaskUpdateLock.unlock(); + } + + } + + private class WriteWorker implements Runnable + { + + public void run() + { + Thread.currentThread().setName(MultiThreadSocketIoProcessor.this.threadName + "Writer"); + + //System.out.println("WriteDebug:"+"Startup"); + for (; ;) + { + try + { + int nKeys = writeSelector.select(SELECTOR_TIMEOUT); + + doAddNewWrite(); + doUpdateTrafficMask(); + + if (nKeys > 0) + { + //System.out.println("WriteDebug:"+nKeys + " keys from writeselector"); + processWrite(writeSelector.selectedKeys()); + } + else + { + //System.out.println("WriteDebug:"+"No keys from writeselector"); + } + + doRemove(); + notifyWriteIdleness(); + + if (flushingSessionsSet.size() > 0) + { + doFlush(); + } + + if (writeSelector.keys().isEmpty()) + { + synchronized (writeLock) + { + + if (writeSelector.keys().isEmpty() && newSessions.isEmpty()) + { + writeWorker = null; + try + { + writeSelector.close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + finally + { + writeSelector = null; + } + + break; + } + } + } + + } + catch (Throwable t) + { + ExceptionMonitor.getInstance().exceptionCaught(t); + + try + { + Thread.sleep(1000); + } + catch (InterruptedException e1) + { + ExceptionMonitor.getInstance().exceptionCaught(e1); + } + } + } + //System.out.println("WriteDebug:"+"Shutdown"); + } + + } + + private class ReadWorker implements Runnable + { + + public void run() + { + Thread.currentThread().setName(MultiThreadSocketIoProcessor.this.threadName + "Reader"); + + //System.out.println("ReadDebug:"+"Startup"); + for (; ;) + { + try + { + int nKeys = selector.select(SELECTOR_TIMEOUT); + + doAddNewReader(); + doUpdateTrafficMask(); + + if (nKeys > 0) + { + //System.out.println("ReadDebug:"+nKeys + " keys from selector"); + + processRead(selector.selectedKeys()); + } + else + { + //System.out.println("ReadDebug:"+"No keys from selector"); + } + + + doRemove(); + notifyReadIdleness(); + + if (selector.keys().isEmpty()) + { + + synchronized (readLock) + { + if (selector.keys().isEmpty() && newSessions.isEmpty()) + { + readWorker = null; + try + { + selector.close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + finally + { + selector = null; + } + + break; + } + } + } + } + catch (Throwable t) + { + ExceptionMonitor.getInstance().exceptionCaught(t); + + try + { + Thread.sleep(1000); + } + catch (InterruptedException e1) + { + ExceptionMonitor.getInstance().exceptionCaught(e1); + } + } + } + //System.out.println("ReadDebug:"+"Shutdown"); + } + + } +} diff --git a/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionConfigImpl.java b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionConfigImpl.java new file mode 100644 index 0000000000..043d4800b6 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionConfigImpl.java @@ -0,0 +1,240 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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.mina.transport.socket.nio; + +import org.apache.mina.common.ExceptionMonitor; +import org.apache.mina.common.IoConnectorConfig; +import org.apache.mina.common.support.BaseIoSessionConfig; + +import java.io.IOException; +import java.net.Socket; +import java.net.SocketException; + +/** + * An {@link IoConnectorConfig} for {@link SocketConnector}. + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $ + */ +public class MultiThreadSocketSessionConfigImpl extends org.apache.mina.transport.socket.nio.SocketSessionConfigImpl +{ + private static boolean SET_RECEIVE_BUFFER_SIZE_AVAILABLE = false; + private static boolean SET_SEND_BUFFER_SIZE_AVAILABLE = false; + private static boolean GET_TRAFFIC_CLASS_AVAILABLE = false; + private static boolean SET_TRAFFIC_CLASS_AVAILABLE = false; + + private static boolean DEFAULT_REUSE_ADDRESS; + private static int DEFAULT_RECEIVE_BUFFER_SIZE; + private static int DEFAULT_SEND_BUFFER_SIZE; + private static int DEFAULT_TRAFFIC_CLASS; + private static boolean DEFAULT_KEEP_ALIVE; + private static boolean DEFAULT_OOB_INLINE; + private static int DEFAULT_SO_LINGER; + private static boolean DEFAULT_TCP_NO_DELAY; + + static + { + initialize(); + } + + private static void initialize() + { + Socket socket = null; + + socket = new Socket(); + + try + { + DEFAULT_REUSE_ADDRESS = socket.getReuseAddress(); + DEFAULT_RECEIVE_BUFFER_SIZE = socket.getReceiveBufferSize(); + DEFAULT_SEND_BUFFER_SIZE = socket.getSendBufferSize(); + DEFAULT_KEEP_ALIVE = socket.getKeepAlive(); + DEFAULT_OOB_INLINE = socket.getOOBInline(); + DEFAULT_SO_LINGER = socket.getSoLinger(); + DEFAULT_TCP_NO_DELAY = socket.getTcpNoDelay(); + + // Check if setReceiveBufferSize is supported. + try + { + socket.setReceiveBufferSize(DEFAULT_RECEIVE_BUFFER_SIZE); + SET_RECEIVE_BUFFER_SIZE_AVAILABLE = true; + } + catch( SocketException e ) + { + SET_RECEIVE_BUFFER_SIZE_AVAILABLE = false; + } + + // Check if setSendBufferSize is supported. + try + { + socket.setSendBufferSize(DEFAULT_SEND_BUFFER_SIZE); + SET_SEND_BUFFER_SIZE_AVAILABLE = true; + } + catch( SocketException e ) + { + SET_SEND_BUFFER_SIZE_AVAILABLE = false; + } + + // Check if getTrafficClass is supported. + try + { + DEFAULT_TRAFFIC_CLASS = socket.getTrafficClass(); + GET_TRAFFIC_CLASS_AVAILABLE = true; + } + catch( SocketException e ) + { + GET_TRAFFIC_CLASS_AVAILABLE = false; + DEFAULT_TRAFFIC_CLASS = 0; + } + } + catch( SocketException e ) + { + throw new ExceptionInInitializerError(e); + } + finally + { + if( socket != null ) + { + try + { + socket.close(); + } + catch( IOException e ) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + } + } + } + + public static boolean isSetReceiveBufferSizeAvailable() { + return SET_RECEIVE_BUFFER_SIZE_AVAILABLE; + } + + public static boolean isSetSendBufferSizeAvailable() { + return SET_SEND_BUFFER_SIZE_AVAILABLE; + } + + public static boolean isGetTrafficClassAvailable() { + return GET_TRAFFIC_CLASS_AVAILABLE; + } + + public static boolean isSetTrafficClassAvailable() { + return SET_TRAFFIC_CLASS_AVAILABLE; + } + + private boolean reuseAddress = DEFAULT_REUSE_ADDRESS; + private int receiveBufferSize = DEFAULT_RECEIVE_BUFFER_SIZE; + private int sendBufferSize = DEFAULT_SEND_BUFFER_SIZE; + private int trafficClass = DEFAULT_TRAFFIC_CLASS; + private boolean keepAlive = DEFAULT_KEEP_ALIVE; + private boolean oobInline = DEFAULT_OOB_INLINE; + private int soLinger = DEFAULT_SO_LINGER; + private boolean tcpNoDelay = DEFAULT_TCP_NO_DELAY; + + /** + * Creates a new instance. + */ + MultiThreadSocketSessionConfigImpl() + { + } + + public boolean isReuseAddress() + { + return reuseAddress; + } + + public void setReuseAddress( boolean reuseAddress ) + { + this.reuseAddress = reuseAddress; + } + + public int getReceiveBufferSize() + { + return receiveBufferSize; + } + + public void setReceiveBufferSize( int receiveBufferSize ) + { + this.receiveBufferSize = receiveBufferSize; + } + + public int getSendBufferSize() + { + return sendBufferSize; + } + + public void setSendBufferSize( int sendBufferSize ) + { + this.sendBufferSize = sendBufferSize; + } + + public int getTrafficClass() + { + return trafficClass; + } + + public void setTrafficClass( int trafficClass ) + { + this.trafficClass = trafficClass; + } + + public boolean isKeepAlive() + { + return keepAlive; + } + + public void setKeepAlive( boolean keepAlive ) + { + this.keepAlive = keepAlive; + } + + public boolean isOobInline() + { + return oobInline; + } + + public void setOobInline( boolean oobInline ) + { + this.oobInline = oobInline; + } + + public int getSoLinger() + { + return soLinger; + } + + public void setSoLinger( int soLinger ) + { + this.soLinger = soLinger; + } + + public boolean isTcpNoDelay() + { + return tcpNoDelay; + } + + public void setTcpNoDelay( boolean tcpNoDelay ) + { + this.tcpNoDelay = tcpNoDelay; + } + + +} diff --git a/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionImpl.java b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionImpl.java new file mode 100644 index 0000000000..be4a2d289d --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionImpl.java @@ -0,0 +1,488 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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.mina.transport.socket.nio; + +import org.apache.mina.common.IoFilter.WriteRequest; +import org.apache.mina.common.IoFilterChain; +import org.apache.mina.common.IoHandler; +import org.apache.mina.common.IoService; +import org.apache.mina.common.IoServiceConfig; +import org.apache.mina.common.IoSession; +import org.apache.mina.common.IoSessionConfig; +import org.apache.mina.common.RuntimeIOException; +import org.apache.mina.common.TransportType; +import org.apache.mina.common.support.BaseIoSessionConfig; +import org.apache.mina.common.support.IoServiceListenerSupport; +import org.apache.mina.util.Queue; + +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * An {@link IoSession} for socket transport (TCP/IP). + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $ + */ +class MultiThreadSocketSessionImpl extends SocketSessionImpl +{ + private final IoService manager; + private final IoServiceConfig serviceConfig; + private final SocketSessionConfig config = new SessionConfigImpl(); + private final MultiThreadSocketIoProcessor ioProcessor; + private final MultiThreadSocketFilterChain filterChain; + private final SocketChannel ch; + private final Queue writeRequestQueue; + private final IoHandler handler; + private final SocketAddress remoteAddress; + private final SocketAddress localAddress; + private final SocketAddress serviceAddress; + private final IoServiceListenerSupport serviceListeners; + private SelectionKey readKey, writeKey; + private int readBufferSize; + private CountDownLatch registeredReadyLatch = new CountDownLatch(2); + private AtomicBoolean created = new AtomicBoolean(false); + + /** + * Creates a new instance. + */ + MultiThreadSocketSessionImpl( IoService manager, + SocketIoProcessor ioProcessor, + IoServiceListenerSupport listeners, + IoServiceConfig serviceConfig, + SocketChannel ch, + IoHandler defaultHandler, + SocketAddress serviceAddress ) + { + super(manager, ioProcessor, listeners, serviceConfig, ch,defaultHandler,serviceAddress); + this.manager = manager; + this.serviceListeners = listeners; + this.ioProcessor = (MultiThreadSocketIoProcessor) ioProcessor; + this.filterChain = new MultiThreadSocketFilterChain(this); + this.ch = ch; + this.writeRequestQueue = new Queue(); + this.handler = defaultHandler; + this.remoteAddress = ch.socket().getRemoteSocketAddress(); + this.localAddress = ch.socket().getLocalSocketAddress(); + this.serviceAddress = serviceAddress; + this.serviceConfig = serviceConfig; + + // Apply the initial session settings + IoSessionConfig sessionConfig = serviceConfig.getSessionConfig(); + if( sessionConfig instanceof SocketSessionConfig ) + { + SocketSessionConfig cfg = ( SocketSessionConfig ) sessionConfig; + this.config.setKeepAlive( cfg.isKeepAlive() ); + this.config.setOobInline( cfg.isOobInline() ); + this.config.setReceiveBufferSize( cfg.getReceiveBufferSize() ); + this.readBufferSize = cfg.getReceiveBufferSize(); + this.config.setReuseAddress( cfg.isReuseAddress() ); + this.config.setSendBufferSize( cfg.getSendBufferSize() ); + this.config.setSoLinger( cfg.getSoLinger() ); + this.config.setTcpNoDelay( cfg.isTcpNoDelay() ); + + if( this.config.getTrafficClass() != cfg.getTrafficClass() ) + { + this.config.setTrafficClass( cfg.getTrafficClass() ); + } + } + } + + void awaitRegistration() throws InterruptedException + { + registeredReadyLatch.countDown(); + + registeredReadyLatch.await(); + } + + boolean created() throws InterruptedException + { + return created.get(); + } + + void doneCreation() + { + created.getAndSet(true); + } + + public IoService getService() + { + return manager; + } + + public IoServiceConfig getServiceConfig() + { + return serviceConfig; + } + + public IoSessionConfig getConfig() + { + return config; + } + + SocketIoProcessor getIoProcessor() + { + return ioProcessor; + } + + public IoFilterChain getFilterChain() + { + return filterChain; + } + + SocketChannel getChannel() + { + return ch; + } + + IoServiceListenerSupport getServiceListeners() + { + return serviceListeners; + } + + SelectionKey getSelectionKey() + { + return readKey; + } + + SelectionKey getReadSelectionKey() + { + return readKey; + } + + SelectionKey getWriteSelectionKey() + { + return writeKey; + } + + void setSelectionKey(SelectionKey key) + { + this.readKey = key; + } + + void setWriteSelectionKey(SelectionKey key) + { + this.writeKey = key; + } + + public IoHandler getHandler() + { + return handler; + } + + protected void close0() + { + filterChain.fireFilterClose( this ); + } + + Queue getWriteRequestQueue() + { + return writeRequestQueue; + } + + /** + @return int Number of write scheduled write requests + @deprecated + */ + public int getScheduledWriteMessages() + { + return getScheduledWriteRequests(); + } + + public int getScheduledWriteRequests() + { + synchronized( writeRequestQueue ) + { + return writeRequestQueue.size(); + } + } + + public int getScheduledWriteBytes() + { + synchronized( writeRequestQueue ) + { + return writeRequestQueue.byteSize(); + } + } + + protected void write0( WriteRequest writeRequest ) + { + filterChain.fireFilterWrite( this, writeRequest ); + } + + public TransportType getTransportType() + { + return TransportType.SOCKET; + } + + public SocketAddress getRemoteAddress() + { + //This is what I had previously +// return ch.socket().getRemoteSocketAddress(); + return remoteAddress; + } + + public SocketAddress getLocalAddress() + { + //This is what I had previously +// return ch.socket().getLocalSocketAddress(); + return localAddress; + } + + public SocketAddress getServiceAddress() + { + return serviceAddress; + } + + protected void updateTrafficMask() + { + this.ioProcessor.updateTrafficMask( this ); + } + + int getReadBufferSize() + { + return readBufferSize; + } + + private class SessionConfigImpl extends BaseIoSessionConfig implements SocketSessionConfig + { + public boolean isKeepAlive() + { + try + { + return ch.socket().getKeepAlive(); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public void setKeepAlive( boolean on ) + { + try + { + ch.socket().setKeepAlive( on ); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public boolean isOobInline() + { + try + { + return ch.socket().getOOBInline(); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public void setOobInline( boolean on ) + { + try + { + ch.socket().setOOBInline( on ); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public boolean isReuseAddress() + { + try + { + return ch.socket().getReuseAddress(); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public void setReuseAddress( boolean on ) + { + try + { + ch.socket().setReuseAddress( on ); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public int getSoLinger() + { + try + { + return ch.socket().getSoLinger(); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public void setSoLinger( int linger ) + { + try + { + if( linger < 0 ) + { + ch.socket().setSoLinger( false, 0 ); + } + else + { + ch.socket().setSoLinger( true, linger ); + } + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public boolean isTcpNoDelay() + { + try + { + return ch.socket().getTcpNoDelay(); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public void setTcpNoDelay( boolean on ) + { + try + { + ch.socket().setTcpNoDelay( on ); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public int getTrafficClass() + { + if( SocketSessionConfigImpl.isGetTrafficClassAvailable() ) + { + try + { + return ch.socket().getTrafficClass(); + } + catch( SocketException e ) + { + // Throw an exception only when setTrafficClass is also available. + if( SocketSessionConfigImpl.isSetTrafficClassAvailable() ) + { + throw new RuntimeIOException( e ); + } + } + } + + return 0; + } + + public void setTrafficClass( int tc ) + { + if( SocketSessionConfigImpl.isSetTrafficClassAvailable() ) + { + try + { + ch.socket().setTrafficClass( tc ); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + } + + public int getSendBufferSize() + { + try + { + return ch.socket().getSendBufferSize(); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public void setSendBufferSize( int size ) + { + if( SocketSessionConfigImpl.isSetSendBufferSizeAvailable() ) + { + try + { + ch.socket().setSendBufferSize( size ); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + } + + public int getReceiveBufferSize() + { + try + { + return ch.socket().getReceiveBufferSize(); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public void setReceiveBufferSize( int size ) + { + if( SocketSessionConfigImpl.isSetReceiveBufferSizeAvailable() ) + { + try + { + ch.socket().setReceiveBufferSize( size ); + MultiThreadSocketSessionImpl.this.readBufferSize = size; + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + } + } +} diff --git a/java/common/src/main/java/org/apache/mina/transport/vmpipe/QpidVmPipeConnector.java b/java/common/src/main/java/org/apache/mina/transport/vmpipe/QpidVmPipeConnector.java new file mode 100644 index 0000000000..a23e546af5 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/transport/vmpipe/QpidVmPipeConnector.java @@ -0,0 +1,151 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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.mina.transport.vmpipe; + +import java.io.IOException; +import java.net.SocketAddress; + +import org.apache.mina.common.ConnectFuture; +import org.apache.mina.common.ExceptionMonitor; +import org.apache.mina.common.IoFilterChain; +import org.apache.mina.common.IoHandler; +import org.apache.mina.common.IoServiceConfig; +import org.apache.mina.common.IoSessionConfig; +import org.apache.mina.common.support.AbstractIoFilterChain; +import org.apache.mina.common.support.BaseIoConnector; +import org.apache.mina.common.support.BaseIoConnectorConfig; +import org.apache.mina.common.support.BaseIoSessionConfig; +import org.apache.mina.common.support.DefaultConnectFuture; +import org.apache.mina.transport.vmpipe.support.VmPipe; +import org.apache.mina.transport.vmpipe.support.VmPipeIdleStatusChecker; +import org.apache.mina.transport.vmpipe.support.VmPipeSessionImpl; +import org.apache.mina.util.AnonymousSocketAddress; + +/** + * Connects to {@link IoHandler}s which is bound on the specified + * {@link VmPipeAddress}. + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $ + */ +public class QpidVmPipeConnector extends VmPipeConnector +{ + private static final IoSessionConfig CONFIG = new BaseIoSessionConfig() {}; + private final IoServiceConfig defaultConfig = new BaseIoConnectorConfig() + { + public IoSessionConfig getSessionConfig() + { + return CONFIG; + } + }; + + /** + * Creates a new instance. + */ + public QpidVmPipeConnector() + { + } + + public ConnectFuture connect( SocketAddress address, IoHandler handler, IoServiceConfig config ) + { + return connect( address, null, handler, config ); + } + + public ConnectFuture connect( SocketAddress address, SocketAddress localAddress, IoHandler handler, IoServiceConfig config ) + { + if( address == null ) + throw new NullPointerException( "address" ); + if( handler == null ) + throw new NullPointerException( "handler" ); + if( ! ( address instanceof VmPipeAddress ) ) + throw new IllegalArgumentException( + "address must be VmPipeAddress." ); + + if( config == null ) + { + config = getDefaultConfig(); + } + + VmPipe entry = ( VmPipe ) VmPipeAcceptor.boundHandlers.get( address ); + if( entry == null ) + { + return DefaultConnectFuture.newFailedFuture( + new IOException( "Endpoint unavailable: " + address ) ); + } + + DefaultConnectFuture future = new DefaultConnectFuture(); + VmPipeSessionImpl localSession = + new VmPipeSessionImpl( + this, + config, + getListeners(), + new Object(), // lock + new AnonymousSocketAddress(), + handler, + entry ); + + // initialize acceptor session + VmPipeSessionImpl remoteSession = localSession.getRemoteSession(); + try + { + IoFilterChain filterChain = remoteSession.getFilterChain(); + entry.getAcceptor().getFilterChainBuilder().buildFilterChain( filterChain ); + entry.getConfig().getFilterChainBuilder().buildFilterChain( filterChain ); + entry.getConfig().getThreadModel().buildFilterChain( filterChain ); + + // The following sentences don't throw any exceptions. + entry.getListeners().fireSessionCreated( remoteSession ); + VmPipeIdleStatusChecker.getInstance().addSession( remoteSession ); + } + catch( Throwable t ) + { + ExceptionMonitor.getInstance().exceptionCaught( t ); + remoteSession.close(); + } + + + // initialize connector session + try + { + IoFilterChain filterChain = localSession.getFilterChain(); + this.getFilterChainBuilder().buildFilterChain( filterChain ); + config.getFilterChainBuilder().buildFilterChain( filterChain ); + config.getThreadModel().buildFilterChain( filterChain ); + + // The following sentences don't throw any exceptions. + localSession.setAttribute( AbstractIoFilterChain.CONNECT_FUTURE, future ); + getListeners().fireSessionCreated( localSession ); + VmPipeIdleStatusChecker.getInstance().addSession( localSession); + } + catch( Throwable t ) + { + future.setException( t ); + } + + + + return future; + } + + public IoServiceConfig getDefaultConfig() + { + return defaultConfig; + } +}
\ No newline at end of file diff --git a/java/common/src/main/java/org/apache/qpid/AMQChannelException.java b/java/common/src/main/java/org/apache/qpid/AMQChannelException.java index 2f6290b55a..ef9420ba87 100644 --- a/java/common/src/main/java/org/apache/qpid/AMQChannelException.java +++ b/java/common/src/main/java/org/apache/qpid/AMQChannelException.java @@ -54,7 +54,6 @@ public class AMQChannelException extends AMQException public AMQFrame getCloseFrame(int channel) { MethodRegistry reg = MethodRegistry.getMethodRegistry(new ProtocolVersion(major,minor)); - return new AMQFrame(channel, reg.createChannelCloseBody(getErrorCode() == null ? AMQConstant.INTERNAL_ERROR.getCode() : getErrorCode().getCode(), getMessageAsShortString(),_classId,_methodId)); + return new AMQFrame(channel, reg.createChannelCloseBody(getErrorCode() == null ? AMQConstant.INTERNAL_ERROR.getCode() : getErrorCode().getCode(), new AMQShortString(getMessage()),_classId,_methodId)); } - } diff --git a/java/common/src/main/java/org/apache/qpid/AMQConnectionException.java b/java/common/src/main/java/org/apache/qpid/AMQConnectionException.java index ca9c9f9dc4..8ef6facef1 100644 --- a/java/common/src/main/java/org/apache/qpid/AMQConnectionException.java +++ b/java/common/src/main/java/org/apache/qpid/AMQConnectionException.java @@ -62,10 +62,9 @@ public class AMQConnectionException extends AMQException MethodRegistry reg = MethodRegistry.getMethodRegistry(new ProtocolVersion(major,minor)); return new AMQFrame(0, reg.createConnectionCloseBody(getErrorCode().getCode(), - getMessageAsShortString(), + new AMQShortString(getMessage()), _classId, _methodId)); } - } diff --git a/java/common/src/main/java/org/apache/qpid/AMQException.java b/java/common/src/main/java/org/apache/qpid/AMQException.java index 86d439d269..b0c6fccc9e 100644 --- a/java/common/src/main/java/org/apache/qpid/AMQException.java +++ b/java/common/src/main/java/org/apache/qpid/AMQException.java @@ -20,7 +20,6 @@ */ package org.apache.qpid; -import org.apache.qpid.framing.AMQShortString; import org.apache.qpid.protocol.AMQConstant; /** @@ -122,19 +121,4 @@ public class AMQException extends Exception return newAMQE; } - - /** - * Truncates the exception message to 255 characters if its length exceeds 255. - * - * @return exception message - */ - public AMQShortString getMessageAsShortString() - { - String message = getMessage(); - if (message != null && message.length() > AMQShortString.MAX_LENGTH) - { - message = message.substring(0, AMQShortString.MAX_LENGTH - 3) + "..."; - } - return new AMQShortString(message); - } } diff --git a/java/common/src/main/java/org/apache/qpid/AMQInvalidArgumentException.java b/java/common/src/main/java/org/apache/qpid/AMQInvalidArgumentException.java index 2bbaaef1fc..baca2a4773 100644 --- a/java/common/src/main/java/org/apache/qpid/AMQInvalidArgumentException.java +++ b/java/common/src/main/java/org/apache/qpid/AMQInvalidArgumentException.java @@ -34,7 +34,7 @@ public class AMQInvalidArgumentException extends AMQException { public AMQInvalidArgumentException(String message, Throwable cause) { - super(AMQConstant.ARGUMENT_INVALID, message, cause); + super(AMQConstant.INVALID_ARGUMENT, message, cause); } public boolean isHardError() diff --git a/java/common/src/main/java/org/apache/qpid/ToyBroker.java b/java/common/src/main/java/org/apache/qpid/ToyBroker.java new file mode 100644 index 0000000000..5423bbb68f --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/ToyBroker.java @@ -0,0 +1,208 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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.qpid; + +import org.apache.qpid.transport.*; +import org.apache.qpid.transport.network.mina.MinaHandler; + +import static org.apache.qpid.transport.util.Functions.str; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.LinkedBlockingQueue; + + +/** + * ToyBroker + * + * @author Rafael H. Schloming + */ + +class ToyBroker extends SessionDelegate +{ + + private ToyExchange exchange; + private Map<String,Consumer> consumers = new ConcurrentHashMap<String,Consumer>(); + + public ToyBroker(ToyExchange exchange) + { + this.exchange = exchange; + } + + public void messageAcquire(Session context, MessageAcquire struct) + { + System.out.println("\n==================> messageAcquire " ); + context.executionResult((int) struct.getId(), new Acquired(struct.getTransfers())); + } + + @Override public void queueDeclare(Session ssn, QueueDeclare qd) + { + exchange.createQueue(qd.getQueue()); + System.out.println("\n==================> declared queue: " + qd.getQueue() + "\n"); + } + + @Override public void exchangeBind(Session ssn, ExchangeBind qb) + { + exchange.bindQueue(qb.getExchange(), qb.getBindingKey(),qb.getQueue()); + System.out.println("\n==================> bound queue: " + qb.getQueue() + " with binding key " + qb.getBindingKey() + "\n"); + } + + @Override public void queueQuery(Session ssn, QueueQuery qq) + { + QueueQueryResult result = new QueueQueryResult().queue(qq.getQueue()); + ssn.executionResult((int) qq.getId(), result); + } + + @Override public void messageSubscribe(Session ssn, MessageSubscribe ms) + { + Consumer c = new Consumer(); + c._queueName = ms.getQueue(); + consumers.put(ms.getDestination(),c); + System.out.println("\n==================> message subscribe : " + ms.getDestination() + " queue: " + ms.getQueue() + "\n"); + } + + @Override public void messageFlow(Session ssn,MessageFlow struct) + { + Consumer c = consumers.get(struct.getDestination()); + c._credit = struct.getValue(); + System.out.println("\n==================> message flow : " + struct.getDestination() + " credit: " + struct.getValue() + "\n"); + } + + @Override public void messageFlush(Session ssn,MessageFlush struct) + { + System.out.println("\n==================> message flush for consumer : " + struct.getDestination() + "\n"); + checkAndSendMessagesToConsumer(ssn,struct.getDestination()); + } + + @Override public void messageTransfer(Session ssn, MessageTransfer xfr) + { + String dest = xfr.getDestination(); + System.out.println("received transfer " + dest); + Header header = xfr.getHeader(); + DeliveryProperties props = header.get(DeliveryProperties.class); + if (props != null) + { + System.out.println("received headers routing_key " + props.getRoutingKey()); + } + + MessageProperties mp = header.get(MessageProperties.class); + System.out.println("MP: " + mp); + if (mp != null) + { + System.out.println(mp.getApplicationHeaders()); + } + + if (exchange.route(dest,props == null ? null : props.getRoutingKey(),xfr)) + { + System.out.println("queued " + xfr); + dispatchMessages(ssn); + } + else + { + + if (props == null || !props.getDiscardUnroutable()) + { + RangeSet ranges = new RangeSet(); + ranges.add(xfr.getId()); + ssn.messageReject(ranges, MessageRejectCode.UNROUTABLE, + "no such destination"); + } + } + ssn.processed(xfr); + } + + private void transferMessageToPeer(Session ssn,String dest, MessageTransfer m) + { + System.out.println("\n==================> Transfering message to: " +dest + "\n"); + ssn.messageTransfer(m.getDestination(), MessageAcceptMode.EXPLICIT, + MessageAcquireMode.PRE_ACQUIRED, + m.getHeader(), m.getBody()); + } + + private void dispatchMessages(Session ssn) + { + for (String dest: consumers.keySet()) + { + checkAndSendMessagesToConsumer(ssn,dest); + } + } + + private void checkAndSendMessagesToConsumer(Session ssn,String dest) + { + Consumer c = consumers.get(dest); + LinkedBlockingQueue<MessageTransfer> queue = exchange.getQueue(c._queueName); + MessageTransfer m = queue.poll(); + while (m != null && c._credit>0) + { + transferMessageToPeer(ssn,dest,m); + c._credit--; + m = queue.poll(); + } + } + + // ugly, but who cares :) + // assumes unit is always no of messages, not bytes + // assumes it's credit mode and not window + private static class Consumer + { + long _credit; + String _queueName; + } + + private static final class ToyBrokerSession extends Session + { + + public ToyBrokerSession(Connection connection, Binary name, long expiry, ToyExchange exchange) + { + super(connection, new ToyBroker(exchange), name, expiry); + } + } + + public static final void main(String[] args) throws IOException + { + final ToyExchange exchange = new ToyExchange(); + ConnectionDelegate delegate = new ServerDelegate() + { + @Override + public void init(Connection conn, ProtocolHeader hdr) + { + conn.setSessionFactory(new Connection.SessionFactory() + { + public Session newSession(Connection conn, Binary name, long expiry) + { + return new ToyBrokerSession(conn, name, expiry, exchange); + } + }); + + super.init(conn, hdr); //To change body of overridden methods use File | Settings | File Templates. + } + + }; + + MinaHandler.accept("0.0.0.0", 5672, delegate); + } + +} diff --git a/java/common/src/main/java/org/apache/qpid/ToyClient.java b/java/common/src/main/java/org/apache/qpid/ToyClient.java new file mode 100644 index 0000000000..5b2db10613 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/ToyClient.java @@ -0,0 +1,108 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid; + +import java.nio.*; +import java.util.*; + +import org.apache.qpid.transport.*; +import org.apache.qpid.transport.network.mina.MinaHandler; + + +/** + * ToyClient + * + * @author Rafael H. Schloming + */ + +class ToyClient implements SessionListener +{ + public void opened(Session ssn) {} + + public void resumed(Session ssn) {} + + public void exception(Session ssn, SessionException exc) + { + exc.printStackTrace(); + } + + public void message(Session ssn, MessageTransfer xfr) + { + System.out.println("msg: " + xfr); + } + + public void closed(Session ssn) {} + + public static final void main(String[] args) + { + Connection conn = new Connection(); + conn.connect("0.0.0.0", 5672, null, "guest", "guest", false); + Session ssn = conn.createSession(); + ssn.setSessionListener(new ToyClient()); + + ssn.queueDeclare("asdf", null, null); + ssn.sync(); + + Map<String,Object> nested = new LinkedHashMap<String,Object>(); + nested.put("list", Arrays.asList("one", "two", "three")); + Map<String,Object> map = new LinkedHashMap<String,Object>(); + + map.put("str", "this is a string"); + + map.put("+int", 3); + map.put("-int", -3); + map.put("maxint", Integer.MAX_VALUE); + map.put("minint", Integer.MIN_VALUE); + + map.put("+short", (short) 1); + map.put("-short", (short) -1); + map.put("maxshort", (short) Short.MAX_VALUE); + map.put("minshort", (short) Short.MIN_VALUE); + + map.put("float", (float) 3.3); + map.put("double", 4.9); + map.put("char", 'c'); + + map.put("table", nested); + map.put("list", Arrays.asList(1, 2, 3)); + map.put("binary", new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + + ssn.messageTransfer("asdf", MessageAcceptMode.EXPLICIT, + MessageAcquireMode.PRE_ACQUIRED, + new Header(new DeliveryProperties(), + new MessageProperties() + .setApplicationHeaders(map)), + "this is the data"); + + ssn.messageTransfer("fdsa", MessageAcceptMode.EXPLICIT, + MessageAcquireMode.PRE_ACQUIRED, + null, + "this should be rejected"); + ssn.sync(); + + Future<QueueQueryResult> future = ssn.queueQuery("asdf"); + System.out.println(future.get().getQueue()); + ssn.sync(); + ssn.close(); + conn.close(); + } + +} diff --git a/java/common/src/main/java/org/apache/qpid/ToyExchange.java b/java/common/src/main/java/org/apache/qpid/ToyExchange.java new file mode 100644 index 0000000000..da6aed9629 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/ToyExchange.java @@ -0,0 +1,154 @@ +package org.apache.qpid; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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. + * + */ + + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.qpid.transport.MessageTransfer; + + +public class ToyExchange +{ + final static String DIRECT = "amq.direct"; + final static String TOPIC = "amq.topic"; + + private Map<String,List<LinkedBlockingQueue<MessageTransfer>>> directEx = new HashMap<String,List<LinkedBlockingQueue<MessageTransfer>>>(); + private Map<String,List<LinkedBlockingQueue<MessageTransfer>>> topicEx = new HashMap<String,List<LinkedBlockingQueue<MessageTransfer>>>(); + private Map<String,LinkedBlockingQueue<MessageTransfer>> queues = new HashMap<String,LinkedBlockingQueue<MessageTransfer>>(); + + public void createQueue(String name) + { + queues.put(name, new LinkedBlockingQueue<MessageTransfer>()); + } + + public LinkedBlockingQueue<MessageTransfer> getQueue(String name) + { + return queues.get(name); + } + + public void bindQueue(String type,String binding,String queueName) + { + LinkedBlockingQueue<MessageTransfer> queue = queues.get(queueName); + binding = normalizeKey(binding); + if(DIRECT.equals(type)) + { + + if (directEx.containsKey(binding)) + { + List<LinkedBlockingQueue<MessageTransfer>> list = directEx.get(binding); + list.add(queue); + } + else + { + List<LinkedBlockingQueue<MessageTransfer>> list = new LinkedList<LinkedBlockingQueue<MessageTransfer>>(); + list.add(queue); + directEx.put(binding,list); + } + } + else + { + if (topicEx.containsKey(binding)) + { + List<LinkedBlockingQueue<MessageTransfer>> list = topicEx.get(binding); + list.add(queue); + } + else + { + List<LinkedBlockingQueue<MessageTransfer>> list = new LinkedList<LinkedBlockingQueue<MessageTransfer>>(); + list.add(queue); + topicEx.put(binding,list); + } + } + } + + public boolean route(String dest, String routingKey, MessageTransfer msg) + { + List<LinkedBlockingQueue<MessageTransfer>> queues; + if(DIRECT.equals(dest)) + { + queues = directEx.get(routingKey); + } + else + { + queues = matchWildCard(routingKey); + } + if(queues != null && queues.size()>0) + { + System.out.println("Message stored in " + queues.size() + " queues"); + storeMessage(msg,queues); + return true; + } + else + { + System.out.println("Message unroutable " + msg); + return false; + } + } + + private String normalizeKey(String routingKey) + { + if(routingKey.indexOf(".*")>1) + { + return routingKey.substring(0,routingKey.indexOf(".*")); + } + else + { + return routingKey; + } + } + + private List<LinkedBlockingQueue<MessageTransfer>> matchWildCard(String routingKey) + { + List<LinkedBlockingQueue<MessageTransfer>> selected = new ArrayList<LinkedBlockingQueue<MessageTransfer>>(); + + for(String key: topicEx.keySet()) + { + Pattern p = Pattern.compile(key); + Matcher m = p.matcher(routingKey); + if (m.find()) + { + for(LinkedBlockingQueue<MessageTransfer> queue : topicEx.get(key)) + { + selected.add(queue); + } + } + } + + return selected; + } + + private void storeMessage(MessageTransfer msg,List<LinkedBlockingQueue<MessageTransfer>> selected) + { + for(LinkedBlockingQueue<MessageTransfer> queue : selected) + { + queue.offer(msg); + } + } + +} diff --git a/java/common/src/main/java/org/apache/qpid/codec/AMQCodecFactory.java b/java/common/src/main/java/org/apache/qpid/codec/AMQCodecFactory.java index c81af9760b..591dbd085b 100644 --- a/java/common/src/main/java/org/apache/qpid/codec/AMQCodecFactory.java +++ b/java/common/src/main/java/org/apache/qpid/codec/AMQCodecFactory.java @@ -20,6 +20,9 @@ */ package org.apache.qpid.codec; +import org.apache.mina.filter.codec.ProtocolCodecFactory; +import org.apache.mina.filter.codec.ProtocolDecoder; +import org.apache.mina.filter.codec.ProtocolEncoder; import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; /** @@ -28,11 +31,14 @@ import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; * * <p/><table id="crc"><caption>CRC Card</caption> * <tr><th> Responsibilities <th> Collaborations. + * <tr><td> Supply the protocol encoder. <td> {@link AMQEncoder} * <tr><td> Supply the protocol decoder. <td> {@link AMQDecoder} * </table> */ -public class AMQCodecFactory +public class AMQCodecFactory implements ProtocolCodecFactory { + /** Holds the protocol encoder. */ + private final AMQEncoder _encoder = new AMQEncoder(); /** Holds the protocol decoder. */ private final AMQDecoder _frameDecoder; @@ -50,6 +56,15 @@ public class AMQCodecFactory _frameDecoder = new AMQDecoder(expectProtocolInitiation, session); } + /** + * Gets the AMQP encoder. + * + * @return The AMQP encoder. + */ + public ProtocolEncoder getEncoder() + { + return _encoder; + } /** * Gets the AMQP decoder. diff --git a/java/common/src/main/java/org/apache/qpid/codec/AMQDecoder.java b/java/common/src/main/java/org/apache/qpid/codec/AMQDecoder.java index 69bf73bb49..281c0761d9 100644 --- a/java/common/src/main/java/org/apache/qpid/codec/AMQDecoder.java +++ b/java/common/src/main/java/org/apache/qpid/codec/AMQDecoder.java @@ -20,9 +20,13 @@ */ package org.apache.qpid.codec; -import java.io.*; -import java.nio.ByteBuffer; -import java.util.*; +import java.util.ArrayList; + +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.IoSession; +import org.apache.mina.common.SimpleByteBufferAllocator; +import org.apache.mina.filter.codec.CumulativeProtocolDecoder; +import org.apache.mina.filter.codec.ProtocolDecoderOutput; import org.apache.qpid.framing.AMQDataBlock; import org.apache.qpid.framing.AMQDataBlockDecoder; @@ -50,8 +54,11 @@ import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; * @todo If protocol initiation decoder not needed, then don't create it. Probably not a big deal, but it adds to the * per-session overhead. */ -public class AMQDecoder +public class AMQDecoder extends CumulativeProtocolDecoder { + + private static final String BUFFER = AMQDecoder.class.getName() + ".Buffer"; + /** Holds the 'normal' AMQP data decoder. */ private AMQDataBlockDecoder _dataBlockDecoder = new AMQDataBlockDecoder(); @@ -60,11 +67,12 @@ public class AMQDecoder /** Flag to indicate whether this decoder needs to handle protocol initiation. */ private boolean _expectProtocolInitiation; + private boolean firstDecode = true; private AMQMethodBodyFactory _bodyFactory; - private List<ByteArrayInputStream> _remainingBufs = new ArrayList<ByteArrayInputStream>(); - + private ByteBuffer _remainingBuf; + /** * Creates a new AMQP decoder. * @@ -76,7 +84,98 @@ public class AMQDecoder _bodyFactory = new AMQMethodBodyFactory(session); } + /** + * Delegates decoding AMQP from the data buffer that Mina has retrieved from the wire, to the data or protocol + * intiation decoders. + * + * @param session The Mina session. + * @param in The raw byte buffer. + * @param out The Mina object output gatherer to write decoded objects to. + * + * @return <tt>true</tt> if the data was decoded, <tt>false<tt> if more is needed and the data should accumulate. + * + * @throws Exception If the data cannot be decoded for any reason. + */ + protected boolean doDecode(IoSession session, ByteBuffer in, ProtocolDecoderOutput out) throws Exception + { + + boolean decoded; + if (_expectProtocolInitiation + || (firstDecode + && (in.remaining() > 0) + && (in.get(in.position()) == (byte)'A'))) + { + decoded = doDecodePI(session, in, out); + } + else + { + decoded = doDecodeDataBlock(session, in, out); + } + if(firstDecode && decoded) + { + firstDecode = false; + } + return decoded; + } + + /** + * Decodes AMQP data, delegating the decoding to an {@link AMQDataBlockDecoder}. + * + * @param session The Mina session. + * @param in The raw byte buffer. + * @param out The Mina object output gatherer to write decoded objects to. + * + * @return <tt>true</tt> if the data was decoded, <tt>false<tt> if more is needed and the data should accumulate. + * + * @throws Exception If the data cannot be decoded for any reason. + */ + protected boolean doDecodeDataBlock(IoSession session, ByteBuffer in, ProtocolDecoderOutput out) throws Exception + { + int pos = in.position(); + boolean enoughData = _dataBlockDecoder.decodable(in.buf()); + in.position(pos); + if (!enoughData) + { + // returning false means it will leave the contents in the buffer and + // call us again when more data has been read + return false; + } + else + { + _dataBlockDecoder.decode(session, in, out); + + return true; + } + } + + /** + * Decodes an AMQP initiation, delegating the decoding to a {@link ProtocolInitiation.Decoder}. + * + * @param session The Mina session. + * @param in The raw byte buffer. + * @param out The Mina object output gatherer to write decoded objects to. + * + * @return <tt>true</tt> if the data was decoded, <tt>false<tt> if more is needed and the data should accumulate. + * + * @throws Exception If the data cannot be decoded for any reason. + */ + private boolean doDecodePI(IoSession session, ByteBuffer in, ProtocolDecoderOutput out) throws Exception + { + boolean enoughData = _piDecoder.decodable(in.buf()); + if (!enoughData) + { + // returning false means it will leave the contents in the buffer and + // call us again when more data has been read + return false; + } + else + { + ProtocolInitiation pi = new ProtocolInitiation(in.buf()); + out.write(pi); + return true; + } + } /** * Sets the protocol initation flag, that determines whether decoding is handled by the data decoder of the protocol @@ -90,169 +189,152 @@ public class AMQDecoder _expectProtocolInitiation = expectProtocolInitiation; } - private class RemainingByteArrayInputStream extends InputStream - { - private int _currentListPos; - private int _markPos; - - @Override - public int read() throws IOException + /** + * Cumulates content of <tt>in</tt> into internal buffer and forwards + * decoding request to {@link #doDecode(IoSession, ByteBuffer, ProtocolDecoderOutput)}. + * <tt>doDecode()</tt> is invoked repeatedly until it returns <tt>false</tt> + * and the cumulative buffer is compacted after decoding ends. + * + * @throws IllegalStateException if your <tt>doDecode()</tt> returned + * <tt>true</tt> not consuming the cumulative buffer. + */ + public void decode( IoSession session, ByteBuffer in, + ProtocolDecoderOutput out ) throws Exception + { + ByteBuffer buf = ( ByteBuffer ) session.getAttribute( BUFFER ); + // if we have a session buffer, append data to that otherwise + // use the buffer read from the network directly + if( buf != null ) { - ByteArrayInputStream currentStream = _remainingBufs.get(_currentListPos); - if(currentStream.available() > 0) - { - return currentStream.read(); - } - else if((_currentListPos == _remainingBufs.size()) - || (++_currentListPos == _remainingBufs.size())) - { - return -1; - } - else - { - - ByteArrayInputStream stream = _remainingBufs.get(_currentListPos); - stream.mark(0); - return stream.read(); - } + buf.put( in ); + buf.flip(); } - - @Override - public int read(final byte[] b, final int off, final int len) throws IOException + else { + buf = in; + } - if(_currentListPos == _remainingBufs.size()) - { - return -1; - } - else + for( ;; ) + { + int oldPos = buf.position(); + boolean decoded = doDecode( session, buf, out ); + if( decoded ) { - ByteArrayInputStream currentStream = _remainingBufs.get(_currentListPos); - final int available = currentStream.available(); - int read = currentStream.read(b, off, len > available ? available : len); - if(read < len) + if( buf.position() == oldPos ) { - if(_currentListPos++ != _remainingBufs.size()) - { - _remainingBufs.get(_currentListPos).mark(0); - } - int correctRead = read == -1 ? 0 : read; - int subRead = read(b, off+correctRead, len-correctRead); - if(subRead == -1) - { - return read; - } - else - { - return correctRead+subRead; - } + throw new IllegalStateException( + "doDecode() can't return true when buffer is not consumed." ); } - else + + if( !buf.hasRemaining() ) { - return len; + break; } } - } - - @Override - public int available() throws IOException - { - int total = 0; - for(int i = _currentListPos; i < _remainingBufs.size(); i++) + else { - total += _remainingBufs.get(i).available(); + break; } - return total; } - @Override - public void mark(final int readlimit) + // if there is any data left that cannot be decoded, we store + // it in a buffer in the session and next time this decoder is + // invoked the session buffer gets appended to + if ( buf.hasRemaining() ) { - _markPos = _currentListPos; - final ByteArrayInputStream stream = _remainingBufs.get(_currentListPos); - if(stream != null) - { - stream.mark(readlimit); - } + storeRemainingInSession( buf, session ); } + else + { + removeSessionBuffer( session ); + } + } + + /** + * Releases the cumulative buffer used by the specified <tt>session</tt>. + * Please don't forget to call <tt>super.dispose( session )</tt> when + * you override this method. + */ + public void dispose( IoSession session ) throws Exception + { + removeSessionBuffer( session ); + } - @Override - public void reset() throws IOException + private void removeSessionBuffer(IoSession session) + { + ByteBuffer buf = ( ByteBuffer ) session.getAttribute( BUFFER ); + if( buf != null ) { - _currentListPos = _markPos; - final int size = _remainingBufs.size(); - if(_currentListPos < size) - { - _remainingBufs.get(_currentListPos).reset(); - } - for(int i = _currentListPos+1; i<size; i++) - { - _remainingBufs.get(i).reset(); - } + buf.release(); + session.removeAttribute( BUFFER ); } } + private static final SimpleByteBufferAllocator SIMPLE_BYTE_BUFFER_ALLOCATOR = new SimpleByteBufferAllocator(); + + private void storeRemainingInSession(ByteBuffer buf, IoSession session) + { + ByteBuffer remainingBuf = SIMPLE_BYTE_BUFFER_ALLOCATOR.allocate( buf.remaining(), false ); + remainingBuf.setAutoExpand( true ); + remainingBuf.put( buf ); + session.setAttribute( BUFFER, remainingBuf ); + } - public ArrayList<AMQDataBlock> decodeBuffer(ByteBuffer buf) throws AMQFrameDecodingException, AMQProtocolVersionException, IOException + public ArrayList<AMQDataBlock> decodeBuffer(java.nio.ByteBuffer buf) throws AMQFrameDecodingException, AMQProtocolVersionException { // get prior remaining data from accumulator ArrayList<AMQDataBlock> dataBlocks = new ArrayList<AMQDataBlock>(); - DataInputStream msg; - - - ByteArrayInputStream bais = new ByteArrayInputStream(buf.array(),buf.arrayOffset()+buf.position(), buf.remaining()); - if(!_remainingBufs.isEmpty()) + ByteBuffer msg; + // if we have a session buffer, append data to that otherwise + // use the buffer read from the network directly + if( _remainingBuf != null ) { - _remainingBufs.add(bais); - msg = new DataInputStream(new RemainingByteArrayInputStream()); + _remainingBuf.put(buf); + _remainingBuf.flip(); + msg = _remainingBuf; } else { - msg = new DataInputStream(bais); + msg = ByteBuffer.wrap(buf); } - - boolean enoughData = true; - while (enoughData) + + if (_expectProtocolInitiation + || (firstDecode + && (msg.remaining() > 0) + && (msg.get(msg.position()) == (byte)'A'))) { - if(!_expectProtocolInitiation) + if (_piDecoder.decodable(msg.buf())) { - enoughData = _dataBlockDecoder.decodable(msg); - if (enoughData) - { - dataBlocks.add(_dataBlockDecoder.createAndPopulateFrame(_bodyFactory, msg)); - } + dataBlocks.add(new ProtocolInitiation(msg.buf())); } - else + } + else + { + boolean enoughData = true; + while (enoughData) { - enoughData = _piDecoder.decodable(msg); - if (enoughData) - { - dataBlocks.add(new ProtocolInitiation(msg)); - } - - } + int pos = msg.position(); - if(!enoughData) - { - if(!_remainingBufs.isEmpty()) + enoughData = _dataBlockDecoder.decodable(msg); + msg.position(pos); + if (enoughData) { - _remainingBufs.remove(_remainingBufs.size()-1); - ListIterator<ByteArrayInputStream> iterator = _remainingBufs.listIterator(); - while(iterator.hasNext() && iterator.next().available() == 0) - { - iterator.remove(); - } + dataBlocks.add(_dataBlockDecoder.createAndPopulateFrame(_bodyFactory, msg)); } - if(bais.available()!=0) + else { - byte[] remaining = new byte[bais.available()]; - bais.read(remaining); - _remainingBufs.add(new ByteArrayInputStream(remaining)); + _remainingBuf = SIMPLE_BYTE_BUFFER_ALLOCATOR.allocate(msg.remaining(), false); + _remainingBuf.setAutoExpand(true); + _remainingBuf.put(msg); } } } + if(firstDecode && dataBlocks.size() > 0) + { + firstDecode = false; + } return dataBlocks; } } diff --git a/java/common/src/main/java/org/apache/qpid/codec/AMQEncoder.java b/java/common/src/main/java/org/apache/qpid/codec/AMQEncoder.java new file mode 100644 index 0000000000..53f48ae1c8 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/codec/AMQEncoder.java @@ -0,0 +1,66 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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.qpid.codec; + +import org.apache.mina.common.IoSession; +import org.apache.mina.filter.codec.ProtocolEncoder; +import org.apache.mina.filter.codec.ProtocolEncoderOutput; + +import org.apache.qpid.framing.AMQDataBlockEncoder; + +/** + * AMQEncoder delegates encoding of AMQP to a data encoder. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Delegate AMQP encoding. <td> {@link AMQDataBlockEncoder} + * </table> + * + * @todo This class just delegates to another, so seems to be pointless. Unless it is going to handle some + * responsibilities in the future, then drop it. + */ +public class AMQEncoder implements ProtocolEncoder +{ + /** The data encoder that is delegated to. */ + private AMQDataBlockEncoder _dataBlockEncoder = new AMQDataBlockEncoder(); + + /** + * Encodes AMQP. + * + * @param session The Mina session. + * @param message The data object to encode. + * @param out The Mina writer to output the raw byte data to. + * + * @throws Exception If the data cannot be encoded for any reason. + */ + public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception + { + _dataBlockEncoder.encode(session, message, out); + } + + /** + * Does nothing. Called by Mina to allow this to clean up resources when it is no longer needed. + * + * @param session The Mina session. + */ + public void dispose(IoSession session) + { } +} diff --git a/java/common/src/main/java/org/apache/qpid/configuration/ClientProperties.java b/java/common/src/main/java/org/apache/qpid/configuration/ClientProperties.java index 62ded5b2d8..0dd21238a7 100644 --- a/java/common/src/main/java/org/apache/qpid/configuration/ClientProperties.java +++ b/java/common/src/main/java/org/apache/qpid/configuration/ClientProperties.java @@ -23,7 +23,7 @@ package org.apache.qpid.configuration; */ public class ClientProperties { - + /** * Currently with Qpid it is not possible to change the client ID. * If one is not specified upon connection construction, an id is generated automatically. @@ -68,50 +68,67 @@ public class ClientProperties * by the broker in TuneOK it will be used as the heartbeat interval. * If not a warning will be printed and the max value specified for * heartbeat in TuneOK will be used - * + * * The default idle timeout is set to 120 secs */ public static final String IDLE_TIMEOUT_PROP_NAME = "idle_timeout"; public static final long DEFAULT_IDLE_TIMEOUT = 120000; - + public static final String HEARTBEAT = "qpid.heartbeat"; public static final int HEARTBEAT_DEFAULT = 120; - + /** * This value will be used to determine the default destination syntax type. * Currently the two types are Binding URL (java only) and the Addressing format (used by - * all clients). + * all clients). */ public static final String DEST_SYNTAX = "qpid.dest_syntax"; - + public static final String USE_LEGACY_MAP_MESSAGE_FORMAT = "qpid.use_legacy_map_message"; - public static final String AMQP_VERSION = "qpid.amqp.version"; - - public static final String QPID_VERIFY_CLIENT_ID = "qpid.verify_client_id"; + /** + * ========================================================== + * Those properties are used when the io size should be bounded + * ========================================================== + */ /** - * System properties to change the default timeout used during - * synchronous operations. + * When set to true the io layer throttle down producers and consumers + * when written or read messages are accumulating and exceeding a certain size. + * This is especially useful when a the producer rate is greater than the network + * speed. + * type: boolean */ - public static final String QPID_SYNC_OP_TIMEOUT = "qpid.sync_op_timeout"; - public static final String AMQJ_DEFAULT_SYNCWRITE_TIMEOUT = "amqj.default_syncwrite_timeout"; + public static final String PROTECTIO_PROP_NAME = "protectio"; + //=== The following properties are only used when the previous one is true. /** - * A default timeout value for synchronous operations + * Max size of read messages that can be stored within the MINA layer + * type: int */ - public static final int DEFAULT_SYNC_OPERATION_TIMEOUT = 60000; + public static final String READ_BUFFER_LIMIT_PROP_NAME = "qpid.read.buffer.limit"; + public static final String READ_BUFFER_LIMIT_DEFAULT = "262144"; + /** + * Max size of written messages that can be stored within the MINA layer + * type: int + */ + public static final String WRITE_BUFFER_LIMIT_PROP_NAME = "qpid.read.buffer.limit"; + public static final String WRITE_BUFFER_LIMIT_DEFAULT = "262144"; + public static final String AMQP_VERSION = "qpid.amqp.version"; + + private static ClientProperties _instance = new ClientProperties(); + /* - public static final QpidProperty<Boolean> IGNORE_SET_CLIENTID_PROP_NAME = + public static final QpidProperty<Boolean> IGNORE_SET_CLIENTID_PROP_NAME = QpidProperty.booleanProperty(false,"qpid.ignore_set_client_id","ignore_setclientID"); - + public static final QpidProperty<Boolean> SYNC_PERSISTENT_PROP_NAME = QpidProperty.booleanProperty(false,"qpid.sync_persistence","sync_persistence"); - - + + public static final QpidProperty<Integer> MAX_PREFETCH_PROP_NAME = QpidProperty.intProperty(500,"qpid.max_prefetch","max_prefetch"); */ - - + + } diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQBody.java b/java/common/src/main/java/org/apache/qpid/framing/AMQBody.java index ebdad12178..fe04155bb8 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQBody.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQBody.java @@ -20,9 +20,7 @@ */ package org.apache.qpid.framing; -import java.io.DataOutputStream; -import java.io.IOException; - +import org.apache.mina.common.ByteBuffer; import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; import org.apache.qpid.AMQException; @@ -36,7 +34,7 @@ public interface AMQBody */ public abstract int getSize(); - public void writePayload(DataOutputStream buffer) throws IOException; + public void writePayload(ByteBuffer buffer); - void handle(final int channelId, final AMQVersionAwareProtocolSession amqProtocolSession) throws AMQException; + void handle(final int channelId, final AMQVersionAwareProtocolSession amqMinaProtocolSession) throws AMQException; } diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQDataBlock.java b/java/common/src/main/java/org/apache/qpid/framing/AMQDataBlock.java index 00c1f5aae5..a2fc3a03ef 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQDataBlock.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQDataBlock.java @@ -20,10 +20,7 @@ */ package org.apache.qpid.framing; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - +import org.apache.mina.common.ByteBuffer; /** * A data block represents something that has a size in bytes and the ability to write itself to a byte @@ -42,6 +39,25 @@ public abstract class AMQDataBlock implements EncodableAMQDataBlock * Writes the datablock to the specified buffer. * @param buffer */ - public abstract void writePayload(DataOutputStream buffer) throws IOException; + public abstract void writePayload(ByteBuffer buffer); + + public ByteBuffer toByteBuffer() + { + final ByteBuffer buffer = ByteBuffer.allocate((int)getSize()); + + writePayload(buffer); + buffer.flip(); + return buffer; + } + + public java.nio.ByteBuffer toNioByteBuffer() + { + final java.nio.ByteBuffer buffer = java.nio.ByteBuffer.allocate((int) getSize()); + + ByteBuffer buf = ByteBuffer.wrap(buffer); + writePayload(buf); + buffer.flip(); + return buffer; + } } diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQDataBlockDecoder.java b/java/common/src/main/java/org/apache/qpid/framing/AMQDataBlockDecoder.java index 2165cadd14..228867b2b0 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQDataBlockDecoder.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQDataBlockDecoder.java @@ -20,14 +20,18 @@ */ package org.apache.qpid.framing; +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.IoSession; +import org.apache.mina.filter.codec.ProtocolDecoderOutput; + +import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.DataInputStream; -import java.io.IOException; - public class AMQDataBlockDecoder { + private static final String SESSION_METHOD_BODY_FACTORY = "QPID_SESSION_METHOD_BODY_FACTORY"; private static final BodyFactory[] _bodiesSupported = new BodyFactory[Byte.MAX_VALUE]; @@ -43,32 +47,27 @@ public class AMQDataBlockDecoder public AMQDataBlockDecoder() { } - public boolean decodable(DataInputStream in) throws AMQFrameDecodingException, IOException + public boolean decodable(java.nio.ByteBuffer in) throws AMQFrameDecodingException { - final int remainingAfterAttributes = in.available() - (1 + 2 + 4 + 1); + final int remainingAfterAttributes = in.remaining() - (1 + 2 + 4 + 1); // type, channel, body length and end byte if (remainingAfterAttributes < 0) { return false; } - in.mark(8); - in.skip(1 + 2); - - + in.position(in.position() + 1 + 2); // Get an unsigned int, lifted from MINA ByteBuffer getUnsignedInt() - final long bodySize = in.readInt() & 0xffffffffL; - - in.reset(); + final long bodySize = in.getInt() & 0xffffffffL; return (remainingAfterAttributes >= bodySize); } - public AMQFrame createAndPopulateFrame(AMQMethodBodyFactory methodBodyFactory, DataInputStream in) - throws AMQFrameDecodingException, AMQProtocolVersionException, IOException + public AMQFrame createAndPopulateFrame(AMQMethodBodyFactory methodBodyFactory, ByteBuffer in) + throws AMQFrameDecodingException, AMQProtocolVersionException { - final byte type = in.readByte(); + final byte type = in.get(); BodyFactory bodyFactory; if (type == AMQMethodBody.TYPE) @@ -85,8 +84,8 @@ public class AMQDataBlockDecoder throw new AMQFrameDecodingException(null, "Unsupported frame type: " + type, null); } - final int channel = in.readUnsignedShort(); - final long bodySize = EncodingUtils.readUnsignedInteger(in); + final int channel = in.getUnsignedShort(); + final long bodySize = in.getUnsignedInt(); // bodySize can be zero if ((channel < 0) || (bodySize < 0)) @@ -97,7 +96,7 @@ public class AMQDataBlockDecoder AMQFrame frame = new AMQFrame(in, channel, bodySize, bodyFactory); - byte marker = in.readByte(); + byte marker = in.get(); if ((marker & 0xFF) != 0xCE) { throw new AMQFrameDecodingException(null, "End of frame marker not found. Read " + marker + " length=" + bodySize @@ -107,4 +106,26 @@ public class AMQDataBlockDecoder return frame; } + public void decode(IoSession session, ByteBuffer in, ProtocolDecoderOutput out) throws Exception + { + AMQMethodBodyFactory bodyFactory = (AMQMethodBodyFactory) session.getAttribute(SESSION_METHOD_BODY_FACTORY); + if (bodyFactory == null) + { + AMQVersionAwareProtocolSession protocolSession = (AMQVersionAwareProtocolSession) session.getAttachment(); + bodyFactory = new AMQMethodBodyFactory(protocolSession); + session.setAttribute(SESSION_METHOD_BODY_FACTORY, bodyFactory); + } + + out.write(createAndPopulateFrame(bodyFactory, in)); + } + + public boolean decodable(ByteBuffer msg) throws AMQFrameDecodingException + { + return decodable(msg.buf()); + } + + public AMQDataBlock createAndPopulateFrame(AMQMethodBodyFactory factory, java.nio.ByteBuffer msg) throws AMQProtocolVersionException, AMQFrameDecodingException + { + return createAndPopulateFrame(factory, ByteBuffer.wrap(msg)); + } } diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQDataBlockEncoder.java b/java/common/src/main/java/org/apache/qpid/framing/AMQDataBlockEncoder.java new file mode 100644 index 0000000000..374644b4f2 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQDataBlockEncoder.java @@ -0,0 +1,61 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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.qpid.framing; + +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.IoSession; +import org.apache.mina.filter.codec.ProtocolEncoderOutput; +import org.apache.mina.filter.codec.demux.MessageEncoder; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.Set; + +public final class AMQDataBlockEncoder implements MessageEncoder +{ + private static final Logger _logger = LoggerFactory.getLogger(AMQDataBlockEncoder.class); + + private final Set _messageTypes = Collections.singleton(EncodableAMQDataBlock.class); + + public AMQDataBlockEncoder() + { } + + public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception + { + final AMQDataBlock frame = (AMQDataBlock) message; + + final ByteBuffer buffer = frame.toByteBuffer(); + + if (_logger.isDebugEnabled()) + { + _logger.debug("Encoded frame byte-buffer is '" + EncodingUtils.convertToHexString(buffer) + "'"); + } + + out.write(buffer); + } + + public Set getMessageTypes() + { + return _messageTypes; + } +} diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQFrame.java b/java/common/src/main/java/org/apache/qpid/framing/AMQFrame.java index 6acf60a5b3..02a46f3748 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQFrame.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQFrame.java @@ -20,9 +20,7 @@ */ package org.apache.qpid.framing; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; +import org.apache.mina.common.ByteBuffer; public class AMQFrame extends AMQDataBlock implements EncodableAMQDataBlock { @@ -38,7 +36,7 @@ public class AMQFrame extends AMQDataBlock implements EncodableAMQDataBlock _bodyFrame = bodyFrame; } - public AMQFrame(final DataInputStream in, final int channel, final long bodySize, final BodyFactory bodyFactory) throws AMQFrameDecodingException, IOException + public AMQFrame(final ByteBuffer in, final int channel, final long bodySize, final BodyFactory bodyFactory) throws AMQFrameDecodingException { this._channel = channel; this._bodyFrame = bodyFactory.createBody(in,bodySize); @@ -55,13 +53,13 @@ public class AMQFrame extends AMQDataBlock implements EncodableAMQDataBlock } - public void writePayload(DataOutputStream buffer) throws IOException + public void writePayload(ByteBuffer buffer) { - buffer.writeByte(_bodyFrame.getFrameType()); + buffer.put(_bodyFrame.getFrameType()); EncodingUtils.writeUnsignedShort(buffer, _channel); EncodingUtils.writeUnsignedInteger(buffer, _bodyFrame.getSize()); _bodyFrame.writePayload(buffer); - buffer.writeByte(FRAME_END_BYTE); + buffer.put(FRAME_END_BYTE); } public final int getChannel() @@ -79,48 +77,48 @@ public class AMQFrame extends AMQDataBlock implements EncodableAMQDataBlock return "Frame channelId: " + _channel + ", bodyFrame: " + String.valueOf(_bodyFrame); } - public static void writeFrame(DataOutputStream buffer, final int channel, AMQBody body) throws IOException + public static void writeFrame(ByteBuffer buffer, final int channel, AMQBody body) { - buffer.writeByte(body.getFrameType()); + buffer.put(body.getFrameType()); EncodingUtils.writeUnsignedShort(buffer, channel); EncodingUtils.writeUnsignedInteger(buffer, body.getSize()); body.writePayload(buffer); - buffer.writeByte(FRAME_END_BYTE); + buffer.put(FRAME_END_BYTE); } - public static void writeFrames(DataOutputStream buffer, final int channel, AMQBody body1, AMQBody body2) throws IOException + public static void writeFrames(ByteBuffer buffer, final int channel, AMQBody body1, AMQBody body2) { - buffer.writeByte(body1.getFrameType()); + buffer.put(body1.getFrameType()); EncodingUtils.writeUnsignedShort(buffer, channel); EncodingUtils.writeUnsignedInteger(buffer, body1.getSize()); body1.writePayload(buffer); - buffer.writeByte(FRAME_END_BYTE); - buffer.writeByte(body2.getFrameType()); + buffer.put(FRAME_END_BYTE); + buffer.put(body2.getFrameType()); EncodingUtils.writeUnsignedShort(buffer, channel); EncodingUtils.writeUnsignedInteger(buffer, body2.getSize()); body2.writePayload(buffer); - buffer.writeByte(FRAME_END_BYTE); + buffer.put(FRAME_END_BYTE); } - public static void writeFrames(DataOutputStream buffer, final int channel, AMQBody body1, AMQBody body2, AMQBody body3) throws IOException + public static void writeFrames(ByteBuffer buffer, final int channel, AMQBody body1, AMQBody body2, AMQBody body3) { - buffer.writeByte(body1.getFrameType()); + buffer.put(body1.getFrameType()); EncodingUtils.writeUnsignedShort(buffer, channel); EncodingUtils.writeUnsignedInteger(buffer, body1.getSize()); body1.writePayload(buffer); - buffer.writeByte(FRAME_END_BYTE); - buffer.writeByte(body2.getFrameType()); + buffer.put(FRAME_END_BYTE); + buffer.put(body2.getFrameType()); EncodingUtils.writeUnsignedShort(buffer, channel); EncodingUtils.writeUnsignedInteger(buffer, body2.getSize()); body2.writePayload(buffer); - buffer.writeByte(FRAME_END_BYTE); - buffer.writeByte(body3.getFrameType()); + buffer.put(FRAME_END_BYTE); + buffer.put(body3.getFrameType()); EncodingUtils.writeUnsignedShort(buffer, channel); EncodingUtils.writeUnsignedInteger(buffer, body3.getSize()); body3.writePayload(buffer); - buffer.writeByte(FRAME_END_BYTE); + buffer.put(FRAME_END_BYTE); } diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBody.java b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBody.java index a076d0e5a1..4763b22290 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBody.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBody.java @@ -20,14 +20,12 @@ */ package org.apache.qpid.framing; +import org.apache.mina.common.ByteBuffer; import org.apache.qpid.AMQChannelException; import org.apache.qpid.AMQConnectionException; import org.apache.qpid.AMQException; import org.apache.qpid.protocol.AMQConstant; -import java.io.DataOutputStream; -import java.io.IOException; - public interface AMQMethodBody extends AMQBody { public static final byte TYPE = 1; @@ -45,12 +43,12 @@ public interface AMQMethodBody extends AMQBody /** @return unsigned short */ public int getMethod(); - public void writeMethodPayload(DataOutputStream buffer) throws IOException; + public void writeMethodPayload(ByteBuffer buffer); public int getSize(); - public void writePayload(DataOutputStream buffer) throws IOException; + public void writePayload(ByteBuffer buffer); //public abstract void populateMethodBodyFromBuffer(ByteBuffer buffer) throws AMQFrameDecodingException; diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyFactory.java b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyFactory.java index 7fceb082ee..1a7022c11b 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyFactory.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyFactory.java @@ -20,14 +20,13 @@ */ package org.apache.qpid.framing; +import org.apache.mina.common.ByteBuffer; + import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.DataInputStream; -import java.io.IOException; - public class AMQMethodBodyFactory implements BodyFactory { private static final Logger _log = LoggerFactory.getLogger(AMQMethodBodyFactory.class); @@ -39,7 +38,7 @@ public class AMQMethodBodyFactory implements BodyFactory _protocolSession = protocolSession; } - public AMQBody createBody(DataInputStream in, long bodySize) throws AMQFrameDecodingException, IOException + public AMQBody createBody(ByteBuffer in, long bodySize) throws AMQFrameDecodingException { return _protocolSession.getMethodRegistry().convertToBody(in, bodySize); } diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyImpl.java b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyImpl.java index c73c1df701..cd3d721065 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyImpl.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyImpl.java @@ -21,16 +21,13 @@ package org.apache.qpid.framing; * */ +import org.apache.mina.common.ByteBuffer; import org.apache.qpid.AMQChannelException; import org.apache.qpid.AMQConnectionException; import org.apache.qpid.AMQException; import org.apache.qpid.protocol.AMQConstant; import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - public abstract class AMQMethodBodyImpl implements AMQMethodBody { public static final byte TYPE = 1; @@ -101,7 +98,7 @@ public abstract class AMQMethodBodyImpl implements AMQMethodBody return 2 + 2 + getBodySize(); } - public void writePayload(DataOutputStream buffer) throws IOException + public void writePayload(ByteBuffer buffer) { EncodingUtils.writeUnsignedShort(buffer, getClazz()); EncodingUtils.writeUnsignedShort(buffer, getMethod()); @@ -109,12 +106,12 @@ public abstract class AMQMethodBodyImpl implements AMQMethodBody } - protected byte readByte(DataInputStream buffer) throws IOException + protected byte readByte(ByteBuffer buffer) { - return buffer.readByte(); + return buffer.get(); } - protected AMQShortString readAMQShortString(DataInputStream buffer) throws IOException + protected AMQShortString readAMQShortString(ByteBuffer buffer) { return EncodingUtils.readAMQShortString(buffer); } @@ -124,27 +121,27 @@ public abstract class AMQMethodBodyImpl implements AMQMethodBody return EncodingUtils.encodedShortStringLength(string); } - protected void writeByte(DataOutputStream buffer, byte b) throws IOException + protected void writeByte(ByteBuffer buffer, byte b) { - buffer.writeByte(b); + buffer.put(b); } - protected void writeAMQShortString(DataOutputStream buffer, AMQShortString string) throws IOException + protected void writeAMQShortString(ByteBuffer buffer, AMQShortString string) { EncodingUtils.writeShortStringBytes(buffer, string); } - protected int readInt(DataInputStream buffer) throws IOException + protected int readInt(ByteBuffer buffer) { - return buffer.readInt(); + return buffer.getInt(); } - protected void writeInt(DataOutputStream buffer, int i) throws IOException + protected void writeInt(ByteBuffer buffer, int i) { - buffer.writeInt(i); + buffer.putInt(i); } - protected FieldTable readFieldTable(DataInputStream buffer) throws AMQFrameDecodingException, IOException + protected FieldTable readFieldTable(ByteBuffer buffer) throws AMQFrameDecodingException { return EncodingUtils.readFieldTable(buffer); } @@ -154,19 +151,19 @@ public abstract class AMQMethodBodyImpl implements AMQMethodBody return EncodingUtils.encodedFieldTableLength(table); //To change body of created methods use File | Settings | File Templates. } - protected void writeFieldTable(DataOutputStream buffer, FieldTable table) throws IOException + protected void writeFieldTable(ByteBuffer buffer, FieldTable table) { EncodingUtils.writeFieldTableBytes(buffer, table); } - protected long readLong(DataInputStream buffer) throws IOException + protected long readLong(ByteBuffer buffer) { - return buffer.readLong(); + return buffer.getLong(); } - protected void writeLong(DataOutputStream buffer, long l) throws IOException + protected void writeLong(ByteBuffer buffer, long l) { - buffer.writeLong(l); + buffer.putLong(l); } protected int getSizeOf(byte[] response) @@ -174,86 +171,87 @@ public abstract class AMQMethodBodyImpl implements AMQMethodBody return (response == null) ? 4 : response.length + 4; } - protected void writeBytes(DataOutputStream buffer, byte[] data) throws IOException + protected void writeBytes(ByteBuffer buffer, byte[] data) { EncodingUtils.writeBytes(buffer,data); } - protected byte[] readBytes(DataInputStream buffer) throws IOException + protected byte[] readBytes(ByteBuffer buffer) { return EncodingUtils.readBytes(buffer); } - protected short readShort(DataInputStream buffer) throws IOException + protected short readShort(ByteBuffer buffer) { return EncodingUtils.readShort(buffer); } - protected void writeShort(DataOutputStream buffer, short s) throws IOException + protected void writeShort(ByteBuffer buffer, short s) { EncodingUtils.writeShort(buffer, s); } - protected Content readContent(DataInputStream buffer) + protected Content readContent(ByteBuffer buffer) { - return null; + return null; //To change body of created methods use File | Settings | File Templates. } protected int getSizeOf(Content body) { - return 0; + return 0; //To change body of created methods use File | Settings | File Templates. } - protected void writeContent(DataOutputStream buffer, Content body) + protected void writeContent(ByteBuffer buffer, Content body) { + //To change body of created methods use File | Settings | File Templates. } - protected byte readBitfield(DataInputStream buffer) throws IOException + protected byte readBitfield(ByteBuffer buffer) { - return readByte(buffer); + return readByte(buffer); //To change body of created methods use File | Settings | File Templates. } - protected int readUnsignedShort(DataInputStream buffer) throws IOException + protected int readUnsignedShort(ByteBuffer buffer) { - return buffer.readUnsignedShort(); + return buffer.getUnsignedShort(); //To change body of created methods use File | Settings | File Templates. } - protected void writeBitfield(DataOutputStream buffer, byte bitfield0) throws IOException + protected void writeBitfield(ByteBuffer buffer, byte bitfield0) { - buffer.writeByte(bitfield0); + buffer.put(bitfield0); } - protected void writeUnsignedShort(DataOutputStream buffer, int s) throws IOException + protected void writeUnsignedShort(ByteBuffer buffer, int s) { EncodingUtils.writeUnsignedShort(buffer, s); } - protected long readUnsignedInteger(DataInputStream buffer) throws IOException + protected long readUnsignedInteger(ByteBuffer buffer) { - return EncodingUtils.readUnsignedInteger(buffer); + return buffer.getUnsignedInt(); } - protected void writeUnsignedInteger(DataOutputStream buffer, long i) throws IOException + protected void writeUnsignedInteger(ByteBuffer buffer, long i) { EncodingUtils.writeUnsignedInteger(buffer, i); } - protected short readUnsignedByte(DataInputStream buffer) throws IOException + protected short readUnsignedByte(ByteBuffer buffer) { - return (short) buffer.readUnsignedByte(); + return buffer.getUnsigned(); } - protected void writeUnsignedByte(DataOutputStream buffer, short unsignedByte) throws IOException + protected void writeUnsignedByte(ByteBuffer buffer, short unsignedByte) { EncodingUtils.writeUnsignedByte(buffer, unsignedByte); } - protected long readTimestamp(DataInputStream buffer) throws IOException + protected long readTimestamp(ByteBuffer buffer) { return EncodingUtils.readTimestamp(buffer); } - protected void writeTimestamp(DataOutputStream buffer, long t) throws IOException + protected void writeTimestamp(ByteBuffer buffer, long t) { EncodingUtils.writeTimestamp(buffer, t); } diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyInstanceFactory.java b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyInstanceFactory.java index df4d8bdcb6..0c61d9db3c 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyInstanceFactory.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyInstanceFactory.java @@ -21,11 +21,10 @@ package org.apache.qpid.framing; -import java.io.DataInputStream; -import java.io.IOException; +import org.apache.mina.common.ByteBuffer; public abstract interface AMQMethodBodyInstanceFactory { - public AMQMethodBody newInstance(DataInputStream buffer, long size) throws AMQFrameDecodingException, IOException; + public AMQMethodBody newInstance(ByteBuffer buffer, long size) throws AMQFrameDecodingException; } diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodFactory.java b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodFactory.java new file mode 100644 index 0000000000..bfcc38ad60 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodFactory.java @@ -0,0 +1,90 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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.qpid.framing; + +import org.apache.mina.common.ByteBuffer; + + +public interface AMQMethodFactory +{ + + // Connection Methods + + ConnectionCloseBody createConnectionClose(); + + // Access Methods + + AccessRequestBody createAccessRequest(boolean active, boolean exclusive, boolean passive, boolean read, AMQShortString realm, boolean write); + + + // Tx Methods + + TxSelectBody createTxSelect(); + + TxCommitBody createTxCommit(); + + TxRollbackBody createTxRollback(); + + // Channel Methods + + ChannelOpenBody createChannelOpen(); + + ChannelCloseBody createChannelClose(int replyCode, AMQShortString replyText); + + ChannelFlowBody createChannelFlow(boolean active); + + + // Exchange Methods + + + ExchangeBoundBody createExchangeBound(AMQShortString exchangeName, + AMQShortString queueName, + AMQShortString routingKey); + + ExchangeDeclareBody createExchangeDeclare(AMQShortString name, AMQShortString type, int ticket); + + + // Queue Methods + + QueueDeclareBody createQueueDeclare(AMQShortString name, FieldTable arguments, boolean autoDelete, boolean durable, boolean exclusive, boolean passive, int ticket); + + QueueBindBody createQueueBind(AMQShortString queueName, AMQShortString exchangeName, AMQShortString routingKey, FieldTable arguments, int ticket); + + QueueDeleteBody createQueueDelete(AMQShortString queueName, boolean ifEmpty, boolean ifUnused, int ticket); + + + // Message Methods + + // In different versions of the protocol we change the class used for message transfer + // abstract this out so the appropriate methods are created + AMQMethodBody createRecover(boolean requeue); + + AMQMethodBody createConsumer(AMQShortString tag, AMQShortString queueName, FieldTable arguments, boolean noAck, boolean exclusive, boolean noLocal, int ticket); + + AMQMethodBody createConsumerCancel(AMQShortString consumerTag); + + AMQMethodBody createAcknowledge(long deliveryTag, boolean multiple); + + AMQMethodBody createRejectBody(long deliveryTag, boolean requeue); + + AMQMethodBody createMessageQos(int prefetchCount, int prefetchSize); + +} diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQShortString.java b/java/common/src/main/java/org/apache/qpid/framing/AMQShortString.java index cc9a33f4cf..39a9beb9e8 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQShortString.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQShortString.java @@ -21,12 +21,11 @@ package org.apache.qpid.framing; +import org.apache.mina.common.ByteBuffer; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; import java.util.*; import java.lang.ref.WeakReference; @@ -38,10 +37,6 @@ import java.lang.ref.WeakReference; */ public final class AMQShortString implements CharSequence, Comparable<AMQShortString> { - /** - * The maximum number of octets in AMQ short string as defined in AMQP specification - */ - public static final int MAX_LENGTH = 255; private static final byte MINUS = (byte)'-'; private static final byte ZERO = (byte) '0'; @@ -123,19 +118,22 @@ public final class AMQShortString implements CharSequence, Comparable<AMQShortSt public AMQShortString(byte[] data) { - if (data == null) - { - throw new NullPointerException("Cannot create AMQShortString with null data[]"); - } - if (data.length > MAX_LENGTH) - { - throw new IllegalArgumentException("Cannot create AMQShortString with number of octets over 255!"); - } + _data = data.clone(); _length = data.length; _offset = 0; } + public AMQShortString(byte[] data, int pos) + { + final int size = data[pos++]; + final byte[] dataCopy = new byte[size]; + System.arraycopy(data,pos,dataCopy,0,size); + _length = size; + _data = dataCopy; + _offset = 0; + } + public AMQShortString(String data) { this((data == null) ? EMPTY_CHAR_ARRAY : data.toCharArray()); @@ -148,12 +146,7 @@ public final class AMQShortString implements CharSequence, Comparable<AMQShortSt { throw new NullPointerException("Cannot create AMQShortString with null char[]"); } - // the current implementation of 0.8/0.9.x short string encoding - // supports only ASCII characters - if (data.length> MAX_LENGTH) - { - throw new IllegalArgumentException("Cannot create AMQShortString with number of octets over 255!"); - } + final int length = data.length; final byte[] stringBytes = new byte[length]; int hash = 0; @@ -172,17 +165,6 @@ public final class AMQShortString implements CharSequence, Comparable<AMQShortSt public AMQShortString(CharSequence charSequence) { - if (charSequence == null) - { - // it should be possible to create short string for null data - charSequence = ""; - } - // the current implementation of 0.8/0.9.x short string encoding - // supports only ASCII characters - if (charSequence.length() > MAX_LENGTH) - { - throw new IllegalArgumentException("Cannot create AMQShortString with number of octets over 255!"); - } final int length = charSequence.length(); final byte[] stringBytes = new byte[length]; int hash = 0; @@ -200,33 +182,31 @@ public final class AMQShortString implements CharSequence, Comparable<AMQShortSt } - private AMQShortString(DataInputStream data, final int length) throws IOException + private AMQShortString(ByteBuffer data, final int length) { - if (length > MAX_LENGTH) + if(data.isDirect() || data.isReadOnly()) { - throw new IllegalArgumentException("Cannot create AMQShortString with number of octets over 255!"); + byte[] dataBytes = new byte[length]; + data.get(dataBytes); + _data = dataBytes; + _offset = 0; + } + else + { + + _data = data.array(); + _offset = data.arrayOffset() + data.position(); + data.skip(length); + } - byte[] dataBytes = new byte[length]; - data.read(dataBytes); - _data = dataBytes; - _offset = 0; _length = length; } private AMQShortString(final byte[] data, final int from, final int to) { - if (data == null) - { - throw new NullPointerException("Cannot create AMQShortString with null data[]"); - } - int length = to - from; - if (length > MAX_LENGTH) - { - throw new IllegalArgumentException("Cannot create AMQShortString with number of octets over 255!"); - } _offset = from; - _length = length; + _length = to - from; _data = data; } @@ -265,9 +245,32 @@ public final class AMQShortString implements CharSequence, Comparable<AMQShortSt return new CharSubSequence(start, end); } - public static AMQShortString readFromBuffer(DataInputStream buffer) throws IOException + public int writeToByteArray(byte[] encoding, int pos) + { + final int size = length(); + encoding[pos++] = (byte) size; + System.arraycopy(_data,_offset,encoding,pos,size); + return pos+size; + } + + public static AMQShortString readFromByteArray(byte[] byteEncodedDestination, int pos) + { + + + final AMQShortString shortString = new AMQShortString(byteEncodedDestination, pos); + if(shortString.length() == 0) + { + return null; + } + else + { + return shortString; + } + } + + public static AMQShortString readFromBuffer(ByteBuffer buffer) { - final int length = buffer.readUnsignedByte(); + final short length = buffer.getUnsigned(); if (length == 0) { return null; @@ -293,13 +296,13 @@ public final class AMQShortString implements CharSequence, Comparable<AMQShortSt } } - public void writeToBuffer(DataOutputStream buffer) throws IOException + public void writeToBuffer(ByteBuffer buffer) { final int size = length(); //buffer.setAutoExpand(true); - buffer.write((byte) size); - buffer.write(_data, _offset, size); + buffer.put((byte) size); + buffer.put(_data, _offset, size); } @@ -687,10 +690,6 @@ public final class AMQShortString implements CharSequence, Comparable<AMQShortSt size += term.length(); } - if (size > MAX_LENGTH) - { - throw new IllegalArgumentException("Cannot create AMQShortString with number of octets over 255!"); - } byte[] data = new byte[size]; int pos = 0; final byte[] delimData = delim._data; diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQType.java b/java/common/src/main/java/org/apache/qpid/framing/AMQType.java index f3da64e639..14fb63da03 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQType.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQType.java @@ -20,9 +20,8 @@ */ package org.apache.qpid.framing; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; +import org.apache.mina.common.ByteBuffer; + import java.math.BigDecimal; /** @@ -61,12 +60,12 @@ public enum AMQType } } - public void writeValueImpl(Object value, DataOutputStream buffer) throws IOException + public void writeValueImpl(Object value, ByteBuffer buffer) { EncodingUtils.writeLongStringBytes(buffer, (String) value); } - public Object readValueFromBuffer(DataInputStream buffer) throws IOException + public Object readValueFromBuffer(ByteBuffer buffer) { return EncodingUtils.readLongString(buffer); } @@ -107,12 +106,12 @@ public enum AMQType } } - public void writeValueImpl(Object value, DataOutputStream buffer) throws IOException + public void writeValueImpl(Object value, ByteBuffer buffer) { EncodingUtils.writeUnsignedInteger(buffer, (Long) value); } - public Object readValueFromBuffer(DataInputStream buffer) throws IOException + public Object readValueFromBuffer(ByteBuffer buffer) { return EncodingUtils.readUnsignedInteger(buffer); } @@ -138,7 +137,7 @@ public enum AMQType } } - public void writeValueImpl(Object value, DataOutputStream buffer) throws IOException + public void writeValueImpl(Object value, ByteBuffer buffer) { BigDecimal bd = (BigDecimal) value; @@ -151,7 +150,7 @@ public enum AMQType EncodingUtils.writeInteger(buffer, unscaled); } - public Object readValueFromBuffer(DataInputStream buffer) throws IOException + public Object readValueFromBuffer(ByteBuffer buffer) { byte places = EncodingUtils.readByte(buffer); @@ -183,12 +182,12 @@ public enum AMQType } } - public void writeValueImpl(Object value, DataOutputStream buffer) throws IOException + public void writeValueImpl(Object value, ByteBuffer buffer) { EncodingUtils.writeLong(buffer, (Long) value); } - public Object readValueFromBuffer(DataInputStream buffer) throws IOException + public Object readValueFromBuffer(ByteBuffer buffer) { return EncodingUtils.readLong(buffer); } @@ -247,7 +246,7 @@ public enum AMQType * @param value An instance of the type. * @param buffer The byte buffer to write it to. */ - public void writeValueImpl(Object value, DataOutputStream buffer) throws IOException + public void writeValueImpl(Object value, ByteBuffer buffer) { // Ensure that the value is a FieldTable. if (!(value instanceof FieldTable)) @@ -268,7 +267,7 @@ public enum AMQType * * @return An instance of the type. */ - public Object readValueFromBuffer(DataInputStream buffer) throws IOException + public Object readValueFromBuffer(ByteBuffer buffer) { try { @@ -302,10 +301,10 @@ public enum AMQType } } - public void writeValueImpl(Object value, DataOutputStream buffer) + public void writeValueImpl(Object value, ByteBuffer buffer) { } - public Object readValueFromBuffer(DataInputStream buffer) + public Object readValueFromBuffer(ByteBuffer buffer) { return null; } @@ -331,12 +330,12 @@ public enum AMQType } } - public void writeValueImpl(Object value, DataOutputStream buffer) throws IOException + public void writeValueImpl(Object value, ByteBuffer buffer) { EncodingUtils.writeLongstr(buffer, (byte[]) value); } - public Object readValueFromBuffer(DataInputStream buffer) throws IOException + public Object readValueFromBuffer(ByteBuffer buffer) { return EncodingUtils.readLongstr(buffer); } @@ -361,12 +360,12 @@ public enum AMQType } } - public void writeValueImpl(Object value, DataOutputStream buffer) throws IOException + public void writeValueImpl(Object value, ByteBuffer buffer) { EncodingUtils.writeLongStringBytes(buffer, (String) value); } - public Object readValueFromBuffer(DataInputStream buffer) throws IOException + public Object readValueFromBuffer(ByteBuffer buffer) { return EncodingUtils.readLongString(buffer); } @@ -392,12 +391,12 @@ public enum AMQType } } - public void writeValueImpl(Object value, DataOutputStream buffer) throws IOException + public void writeValueImpl(Object value, ByteBuffer buffer) { EncodingUtils.writeLongStringBytes(buffer, (String) value); } - public Object readValueFromBuffer(DataInputStream buffer) throws IOException + public Object readValueFromBuffer(ByteBuffer buffer) { return EncodingUtils.readLongString(buffer); } @@ -427,12 +426,12 @@ public enum AMQType } } - public void writeValueImpl(Object value, DataOutputStream buffer) throws IOException + public void writeValueImpl(Object value, ByteBuffer buffer) { EncodingUtils.writeBoolean(buffer, (Boolean) value); } - public Object readValueFromBuffer(DataInputStream buffer) throws IOException + public Object readValueFromBuffer(ByteBuffer buffer) { return EncodingUtils.readBoolean(buffer); } @@ -462,12 +461,12 @@ public enum AMQType } } - public void writeValueImpl(Object value, DataOutputStream buffer) throws IOException + public void writeValueImpl(Object value, ByteBuffer buffer) { EncodingUtils.writeChar(buffer, (Character) value); } - public Object readValueFromBuffer(DataInputStream buffer) throws IOException + public Object readValueFromBuffer(ByteBuffer buffer) { return EncodingUtils.readChar(buffer); } @@ -497,12 +496,12 @@ public enum AMQType } } - public void writeValueImpl(Object value, DataOutputStream buffer) throws IOException + public void writeValueImpl(Object value, ByteBuffer buffer) { EncodingUtils.writeByte(buffer, (Byte) value); } - public Object readValueFromBuffer(DataInputStream buffer) throws IOException + public Object readValueFromBuffer(ByteBuffer buffer) { return EncodingUtils.readByte(buffer); } @@ -536,12 +535,12 @@ public enum AMQType } } - public void writeValueImpl(Object value, DataOutputStream buffer) throws IOException + public void writeValueImpl(Object value, ByteBuffer buffer) { EncodingUtils.writeShort(buffer, (Short) value); } - public Object readValueFromBuffer(DataInputStream buffer) throws IOException + public Object readValueFromBuffer(ByteBuffer buffer) { return EncodingUtils.readShort(buffer); } @@ -578,12 +577,12 @@ public enum AMQType } } - public void writeValueImpl(Object value, DataOutputStream buffer) throws IOException + public void writeValueImpl(Object value, ByteBuffer buffer) { EncodingUtils.writeInteger(buffer, (Integer) value); } - public Object readValueFromBuffer(DataInputStream buffer) throws IOException + public Object readValueFromBuffer(ByteBuffer buffer) { return EncodingUtils.readInteger(buffer); } @@ -625,12 +624,12 @@ public enum AMQType } } - public void writeValueImpl(Object value, DataOutputStream buffer) throws IOException + public void writeValueImpl(Object value, ByteBuffer buffer) { EncodingUtils.writeLong(buffer, (Long) value); } - public Object readValueFromBuffer(DataInputStream buffer) throws IOException + public Object readValueFromBuffer(ByteBuffer buffer) { return EncodingUtils.readLong(buffer); } @@ -660,12 +659,12 @@ public enum AMQType } } - public void writeValueImpl(Object value, DataOutputStream buffer) throws IOException + public void writeValueImpl(Object value, ByteBuffer buffer) { EncodingUtils.writeFloat(buffer, (Float) value); } - public Object readValueFromBuffer(DataInputStream buffer) throws IOException + public Object readValueFromBuffer(ByteBuffer buffer) { return EncodingUtils.readFloat(buffer); } @@ -699,12 +698,12 @@ public enum AMQType } } - public void writeValueImpl(Object value, DataOutputStream buffer) throws IOException + public void writeValueImpl(Object value, ByteBuffer buffer) { EncodingUtils.writeDouble(buffer, (Double) value); } - public Object readValueFromBuffer(DataInputStream buffer) throws IOException + public Object readValueFromBuffer(ByteBuffer buffer) { return EncodingUtils.readDouble(buffer); } @@ -771,9 +770,9 @@ public enum AMQType * @param value An instance of the type. * @param buffer The byte buffer to write it to. */ - public void writeToBuffer(Object value, DataOutputStream buffer) throws IOException + public void writeToBuffer(Object value, ByteBuffer buffer) { - buffer.writeByte(identifier()); + buffer.put(identifier()); writeValueImpl(value, buffer); } @@ -783,7 +782,7 @@ public enum AMQType * @param value An instance of the type. * @param buffer The byte buffer to write it to. */ - abstract void writeValueImpl(Object value, DataOutputStream buffer) throws IOException; + abstract void writeValueImpl(Object value, ByteBuffer buffer); /** * Reads an instance of the type from a specified byte buffer. @@ -792,5 +791,5 @@ public enum AMQType * * @return An instance of the type. */ - abstract Object readValueFromBuffer(DataInputStream buffer) throws IOException; + abstract Object readValueFromBuffer(ByteBuffer buffer); } diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQTypedValue.java b/java/common/src/main/java/org/apache/qpid/framing/AMQTypedValue.java index 1dbedca362..647d531476 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQTypedValue.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQTypedValue.java @@ -20,9 +20,8 @@ */ package org.apache.qpid.framing; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; +import org.apache.mina.common.ByteBuffer; + import java.util.Date; import java.util.Map; import java.math.BigDecimal; @@ -61,7 +60,7 @@ public class AMQTypedValue _value = type.toNativeValue(value); } - private AMQTypedValue(AMQType type, DataInputStream buffer) throws IOException + private AMQTypedValue(AMQType type, ByteBuffer buffer) { _type = type; _value = type.readValueFromBuffer(buffer); @@ -77,7 +76,7 @@ public class AMQTypedValue return _value; } - public void writeToBuffer(DataOutputStream buffer) throws IOException + public void writeToBuffer(ByteBuffer buffer) { _type.writeToBuffer(_value, buffer); } @@ -87,9 +86,9 @@ public class AMQTypedValue return _type.getEncodingSize(_value); } - public static AMQTypedValue readFromBuffer(DataInputStream buffer) throws IOException + public static AMQTypedValue readFromBuffer(ByteBuffer buffer) { - AMQType type = AMQTypeMap.getType(buffer.readByte()); + AMQType type = AMQTypeMap.getType(buffer.get()); return new AMQTypedValue(type, buffer); } diff --git a/java/common/src/main/java/org/apache/qpid/framing/BasicContentHeaderProperties.java b/java/common/src/main/java/org/apache/qpid/framing/BasicContentHeaderProperties.java index 57622b5054..c7d89a9927 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/BasicContentHeaderProperties.java +++ b/java/common/src/main/java/org/apache/qpid/framing/BasicContentHeaderProperties.java @@ -20,9 +20,7 @@ */ package org.apache.qpid.framing; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; +import org.apache.mina.common.ByteBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,6 +35,27 @@ public class BasicContentHeaderProperties implements CommonContentHeaderProperti private static final AMQShortString ZERO_STRING = null; + /** + * We store the encoded form when we decode the content header so that if we need to write it out without modifying + * it we can do so without incurring the expense of reencoding it + */ + private byte[] _encodedForm; + + /** Flag indicating whether the entire content header has been decoded yet */ + private boolean _decoded = true; + + /** + * We have some optimisations for partial decoding for maximum performance. The headers are used in the broker for + * routing in some cases so we can decode that separately. + */ + private boolean _decodedHeaders = true; + + /** + * We have some optimisations for partial decoding for maximum performance. The content type is used by all clients + * to determine the message type + */ + private boolean _decodedContentType = true; + private AMQShortString _contentType; private AMQShortString _encoding; @@ -67,10 +86,10 @@ public class BasicContentHeaderProperties implements CommonContentHeaderProperti private int _propertyFlags = 0; private static final int CONTENT_TYPE_MASK = 1 << 15; - private static final int ENCODING_MASK = 1 << 14; + private static final int ENCONDING_MASK = 1 << 14; private static final int HEADERS_MASK = 1 << 13; private static final int DELIVERY_MODE_MASK = 1 << 12; - private static final int PRIORITY_MASK = 1 << 11; + private static final int PROPRITY_MASK = 1 << 11; private static final int CORRELATION_ID_MASK = 1 << 10; private static final int REPLY_TO_MASK = 1 << 9; private static final int EXPIRATION_MASK = 1 << 8; @@ -82,11 +101,34 @@ public class BasicContentHeaderProperties implements CommonContentHeaderProperti private static final int CLUSTER_ID_MASK = 1 << 2; + /** + * This is 0_10 specific. We use this property to check if some message properties have been changed. + */ + private boolean _hasBeenUpdated = false; + + public boolean reset() + { + boolean result = _hasBeenUpdated; + _hasBeenUpdated = false; + return result; + } + + public void updated() + { + _hasBeenUpdated = true; + } + public BasicContentHeaderProperties() { } public int getPropertyListSize() { + if (_encodedForm != null) + { + return _encodedForm.length; + } + else + { int size = 0; if ((_propertyFlags & (CONTENT_TYPE_MASK)) > 0) @@ -94,7 +136,7 @@ public class BasicContentHeaderProperties implements CommonContentHeaderProperti size += EncodingUtils.encodedShortStringLength(_contentType); } - if ((_propertyFlags & ENCODING_MASK) > 0) + if ((_propertyFlags & ENCONDING_MASK) > 0) { size += EncodingUtils.encodedShortStringLength(_encoding); } @@ -109,7 +151,7 @@ public class BasicContentHeaderProperties implements CommonContentHeaderProperti size += 1; } - if ((_propertyFlags & PRIORITY_MASK) > 0) + if ((_propertyFlags & PROPRITY_MASK) > 0) { size += 1; } @@ -167,10 +209,23 @@ public class BasicContentHeaderProperties implements CommonContentHeaderProperti } return size; + } + } + + private void clearEncodedForm() + { + if (!_decoded && (_encodedForm != null)) + { + // decode(); + } + + _encodedForm = null; } public void setPropertyFlags(int propertyFlags) { + _hasBeenUpdated = true; + clearEncodedForm(); _propertyFlags = propertyFlags; } @@ -179,87 +234,94 @@ public class BasicContentHeaderProperties implements CommonContentHeaderProperti return _propertyFlags; } - public void writePropertyListPayload(DataOutputStream buffer) throws IOException + public void writePropertyListPayload(ByteBuffer buffer) { - if ((_propertyFlags & (CONTENT_TYPE_MASK)) != 0) + if (_encodedForm != null) { - EncodingUtils.writeShortStringBytes(buffer, _contentType); + buffer.put(_encodedForm); } - - if ((_propertyFlags & ENCODING_MASK) != 0) + else { - EncodingUtils.writeShortStringBytes(buffer, _encoding); - } + if ((_propertyFlags & (CONTENT_TYPE_MASK)) != 0) + { + EncodingUtils.writeShortStringBytes(buffer, _contentType); + } - if ((_propertyFlags & HEADERS_MASK) != 0) - { - EncodingUtils.writeFieldTableBytes(buffer, _headers); - } + if ((_propertyFlags & ENCONDING_MASK) != 0) + { + EncodingUtils.writeShortStringBytes(buffer, _encoding); + } - if ((_propertyFlags & DELIVERY_MODE_MASK) != 0) - { - buffer.writeByte(_deliveryMode); - } + if ((_propertyFlags & HEADERS_MASK) != 0) + { + EncodingUtils.writeFieldTableBytes(buffer, _headers); + } - if ((_propertyFlags & PRIORITY_MASK) != 0) - { - buffer.writeByte(_priority); - } + if ((_propertyFlags & DELIVERY_MODE_MASK) != 0) + { + buffer.put(_deliveryMode); + } - if ((_propertyFlags & CORRELATION_ID_MASK) != 0) - { - EncodingUtils.writeShortStringBytes(buffer, _correlationId); - } + if ((_propertyFlags & PROPRITY_MASK) != 0) + { + buffer.put(_priority); + } - if ((_propertyFlags & REPLY_TO_MASK) != 0) - { - EncodingUtils.writeShortStringBytes(buffer, _replyTo); - } + if ((_propertyFlags & CORRELATION_ID_MASK) != 0) + { + EncodingUtils.writeShortStringBytes(buffer, _correlationId); + } - if ((_propertyFlags & EXPIRATION_MASK) != 0) - { - if (_expiration == 0L) + if ((_propertyFlags & REPLY_TO_MASK) != 0) { - EncodingUtils.writeShortStringBytes(buffer, ZERO_STRING); + EncodingUtils.writeShortStringBytes(buffer, _replyTo); } - else + + if ((_propertyFlags & EXPIRATION_MASK) != 0) { - EncodingUtils.writeShortStringBytes(buffer, String.valueOf(_expiration)); + if (_expiration == 0L) + { + EncodingUtils.writeShortStringBytes(buffer, ZERO_STRING); + } + else + { + EncodingUtils.writeShortStringBytes(buffer, String.valueOf(_expiration)); + } } - } - if ((_propertyFlags & MESSAGE_ID_MASK) != 0) - { - EncodingUtils.writeShortStringBytes(buffer, _messageId); - } + if ((_propertyFlags & MESSAGE_ID_MASK) != 0) + { + EncodingUtils.writeShortStringBytes(buffer, _messageId); + } - if ((_propertyFlags & TIMESTAMP_MASK) != 0) - { - EncodingUtils.writeTimestamp(buffer, _timestamp); - } + if ((_propertyFlags & TIMESTAMP_MASK) != 0) + { + EncodingUtils.writeTimestamp(buffer, _timestamp); + } - if ((_propertyFlags & TYPE_MASK) != 0) - { - EncodingUtils.writeShortStringBytes(buffer, _type); - } + if ((_propertyFlags & TYPE_MASK) != 0) + { + EncodingUtils.writeShortStringBytes(buffer, _type); + } - if ((_propertyFlags & USER_ID_MASK) != 0) - { - EncodingUtils.writeShortStringBytes(buffer, _userId); - } + if ((_propertyFlags & USER_ID_MASK) != 0) + { + EncodingUtils.writeShortStringBytes(buffer, _userId); + } - if ((_propertyFlags & APPLICATION_ID_MASK) != 0) - { - EncodingUtils.writeShortStringBytes(buffer, _appId); - } + if ((_propertyFlags & APPLICATION_ID_MASK) != 0) + { + EncodingUtils.writeShortStringBytes(buffer, _appId); + } - if ((_propertyFlags & CLUSTER_ID_MASK) != 0) - { - EncodingUtils.writeShortStringBytes(buffer, _clusterId); + if ((_propertyFlags & CLUSTER_ID_MASK) != 0) + { + EncodingUtils.writeShortStringBytes(buffer, _clusterId); + } } } - public void populatePropertiesFromBuffer(DataInputStream buffer, int propertyFlags, int size) throws AMQFrameDecodingException, IOException + public void populatePropertiesFromBuffer(ByteBuffer buffer, int propertyFlags, int size) throws AMQFrameDecodingException { _propertyFlags = propertyFlags; @@ -269,18 +331,25 @@ public class BasicContentHeaderProperties implements CommonContentHeaderProperti } decode(buffer); + /*_encodedForm = new byte[size]; + buffer.get(_encodedForm, 0, size); + _decoded = false; + _decodedHeaders = false; + _decodedContentType = false;*/ } - private void decode(DataInputStream buffer) throws IOException, AMQFrameDecodingException + private void decode(ByteBuffer buffer) { // ByteBuffer buffer = ByteBuffer.wrap(_encodedForm); - + int pos = buffer.position(); + try + { if ((_propertyFlags & (CONTENT_TYPE_MASK)) != 0) { _contentType = EncodingUtils.readAMQShortString(buffer); } - if ((_propertyFlags & ENCODING_MASK) != 0) + if ((_propertyFlags & ENCONDING_MASK) != 0) { _encoding = EncodingUtils.readAMQShortString(buffer); } @@ -292,12 +361,12 @@ public class BasicContentHeaderProperties implements CommonContentHeaderProperti if ((_propertyFlags & DELIVERY_MODE_MASK) != 0) { - _deliveryMode = buffer.readByte(); + _deliveryMode = buffer.get(); } - if ((_propertyFlags & PRIORITY_MASK) != 0) + if ((_propertyFlags & PROPRITY_MASK) != 0) { - _priority = buffer.readByte(); + _priority = buffer.get(); } if ((_propertyFlags & CORRELATION_ID_MASK) != 0) @@ -344,29 +413,116 @@ public class BasicContentHeaderProperties implements CommonContentHeaderProperti { _clusterId = EncodingUtils.readAMQShortString(buffer); } + } + catch (AMQFrameDecodingException e) + { + throw new RuntimeException("Error in content header data: " + e, e); + } + final int endPos = buffer.position(); + buffer.position(pos); + final int len = endPos - pos; + _encodedForm = new byte[len]; + final int limit = buffer.limit(); + buffer.limit(endPos); + buffer.get(_encodedForm, 0, len); + buffer.limit(limit); + buffer.position(endPos); + _decoded = true; + } + private void decodeUpToHeaders() + { + ByteBuffer buffer = ByteBuffer.wrap(_encodedForm); + try + { + if ((_propertyFlags & (CONTENT_TYPE_MASK)) != 0) + { + byte length = buffer.get(); + buffer.skip(length); + } + + if ((_propertyFlags & ENCONDING_MASK) != 0) + { + byte length = buffer.get(); + buffer.skip(length); + } + + if ((_propertyFlags & HEADERS_MASK) != 0) + { + _headers = EncodingUtils.readFieldTable(buffer); + + } + + _decodedHeaders = true; + } + catch (AMQFrameDecodingException e) + { + throw new RuntimeException("Error in content header data: " + e, e); + } } + private void decodeUpToContentType() + { + ByteBuffer buffer = ByteBuffer.wrap(_encodedForm); + + if ((_propertyFlags & (CONTENT_TYPE_MASK)) != 0) + { + _contentType = EncodingUtils.readAMQShortString(buffer); + } + + _decodedContentType = true; + } + + private void decodeIfNecessary() + { + if (!_decoded) + { + // decode(); + } + } + + private void decodeHeadersIfNecessary() + { + if (!_decoded && !_decodedHeaders) + { + decodeUpToHeaders(); + } + } + + private void decodeContentTypeIfNecessary() + { + if (!_decoded && !_decodedContentType) + { + decodeUpToContentType(); + } + } public AMQShortString getContentType() { + decodeContentTypeIfNecessary(); + return _contentType; } public String getContentTypeAsString() { + decodeContentTypeIfNecessary(); + return (_contentType == null) ? null : _contentType.toString(); } public void setContentType(AMQShortString contentType) { + _hasBeenUpdated = true; + clearEncodedForm(); _propertyFlags |= (CONTENT_TYPE_MASK); _contentType = contentType; } public void setContentType(String contentType) { + _hasBeenUpdated = true; setContentType((contentType == null) ? null : new AMQShortString(contentType)); } @@ -378,23 +534,31 @@ public class BasicContentHeaderProperties implements CommonContentHeaderProperti public AMQShortString getEncoding() { + decodeIfNecessary(); + return _encoding; } public void setEncoding(String encoding) { - _propertyFlags |= ENCODING_MASK; + _hasBeenUpdated = true; + clearEncodedForm(); + _propertyFlags |= ENCONDING_MASK; _encoding = (encoding == null) ? null : new AMQShortString(encoding); } public void setEncoding(AMQShortString encoding) { - _propertyFlags |= ENCODING_MASK; + _hasBeenUpdated = true; + clearEncodedForm(); + _propertyFlags |= ENCONDING_MASK; _encoding = encoding; } public FieldTable getHeaders() { + decodeHeadersIfNecessary(); + if (_headers == null) { setHeaders(FieldTableFactory.newFieldTable()); @@ -405,146 +569,191 @@ public class BasicContentHeaderProperties implements CommonContentHeaderProperti public void setHeaders(FieldTable headers) { + _hasBeenUpdated = true; + clearEncodedForm(); _propertyFlags |= HEADERS_MASK; _headers = headers; } public byte getDeliveryMode() { + decodeIfNecessary(); + return _deliveryMode; } public void setDeliveryMode(byte deliveryMode) { + clearEncodedForm(); _propertyFlags |= DELIVERY_MODE_MASK; _deliveryMode = deliveryMode; } public byte getPriority() { + decodeIfNecessary(); + return _priority; } public void setPriority(byte priority) { - _propertyFlags |= PRIORITY_MASK; + clearEncodedForm(); + _propertyFlags |= PROPRITY_MASK; _priority = priority; } public AMQShortString getCorrelationId() { + decodeIfNecessary(); + return _correlationId; } public String getCorrelationIdAsString() { + decodeIfNecessary(); + return (_correlationId == null) ? null : _correlationId.toString(); } public void setCorrelationId(String correlationId) { + _hasBeenUpdated = true; setCorrelationId((correlationId == null) ? null : new AMQShortString(correlationId)); } public void setCorrelationId(AMQShortString correlationId) { + _hasBeenUpdated = true; + clearEncodedForm(); _propertyFlags |= CORRELATION_ID_MASK; _correlationId = correlationId; } public String getReplyToAsString() { + decodeIfNecessary(); + return (_replyTo == null) ? null : _replyTo.toString(); } public AMQShortString getReplyTo() { + decodeIfNecessary(); + return _replyTo; } public void setReplyTo(String replyTo) { + _hasBeenUpdated = true; setReplyTo((replyTo == null) ? null : new AMQShortString(replyTo)); } public void setReplyTo(AMQShortString replyTo) { + _hasBeenUpdated = true; + clearEncodedForm(); _propertyFlags |= REPLY_TO_MASK; _replyTo = replyTo; } public long getExpiration() { + decodeIfNecessary(); return _expiration; } public void setExpiration(long expiration) { + clearEncodedForm(); _propertyFlags |= EXPIRATION_MASK; _expiration = expiration; } public AMQShortString getMessageId() { + decodeIfNecessary(); + return _messageId; } public String getMessageIdAsString() { + decodeIfNecessary(); + return (_messageId == null) ? null : _messageId.toString(); } public void setMessageId(String messageId) { + _hasBeenUpdated = true; + clearEncodedForm(); _propertyFlags |= MESSAGE_ID_MASK; _messageId = (messageId == null) ? null : new AMQShortString(messageId); } public void setMessageId(AMQShortString messageId) { + _hasBeenUpdated = true; + clearEncodedForm(); _propertyFlags |= MESSAGE_ID_MASK; _messageId = messageId; } public long getTimestamp() { + decodeIfNecessary(); return _timestamp; } public void setTimestamp(long timestamp) { + clearEncodedForm(); _propertyFlags |= TIMESTAMP_MASK; _timestamp = timestamp; } public String getTypeAsString() { + decodeIfNecessary(); + return (_type == null) ? null : _type.toString(); } public AMQShortString getType() { + decodeIfNecessary(); + return _type; } public void setType(String type) { + _hasBeenUpdated = true; setType((type == null) ? null : new AMQShortString(type)); } public void setType(AMQShortString type) { + _hasBeenUpdated = true; + clearEncodedForm(); _propertyFlags |= TYPE_MASK; _type = type; } public String getUserIdAsString() { + decodeIfNecessary(); + return (_userId == null) ? null : _userId.toString(); } public AMQShortString getUserId() { + decodeIfNecessary(); + return _userId; } @@ -555,48 +764,65 @@ public class BasicContentHeaderProperties implements CommonContentHeaderProperti public void setUserId(AMQShortString userId) { + _hasBeenUpdated = true; + clearEncodedForm(); _propertyFlags |= USER_ID_MASK; _userId = userId; } public String getAppIdAsString() { + decodeIfNecessary(); + return (_appId == null) ? null : _appId.toString(); } public AMQShortString getAppId() { + decodeIfNecessary(); + return _appId; } public void setAppId(String appId) { + _hasBeenUpdated = true; setAppId((appId == null) ? null : new AMQShortString(appId)); } public void setAppId(AMQShortString appId) { + _hasBeenUpdated = true; + clearEncodedForm(); _propertyFlags |= APPLICATION_ID_MASK; _appId = appId; + _hasBeenUpdated = true; } public String getClusterIdAsString() { + _hasBeenUpdated = true; + decodeIfNecessary(); return (_clusterId == null) ? null : _clusterId.toString(); } public AMQShortString getClusterId() { + _hasBeenUpdated = true; + decodeIfNecessary(); return _clusterId; } public void setClusterId(String clusterId) { + _hasBeenUpdated = true; setClusterId((clusterId == null) ? null : new AMQShortString(clusterId)); } public void setClusterId(AMQShortString clusterId) { + _hasBeenUpdated = true; + clearEncodedForm(); _propertyFlags |= CLUSTER_ID_MASK; _clusterId = clusterId; } diff --git a/java/common/src/main/java/org/apache/qpid/framing/BodyFactory.java b/java/common/src/main/java/org/apache/qpid/framing/BodyFactory.java index f9580d82b1..59646577e1 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/BodyFactory.java +++ b/java/common/src/main/java/org/apache/qpid/framing/BodyFactory.java @@ -20,13 +20,12 @@ */ package org.apache.qpid.framing; -import java.io.DataInputStream; -import java.io.IOException; +import org.apache.mina.common.ByteBuffer; /** * Any class that is capable of turning a stream of bytes into an AMQ structure must implement this interface. */ public interface BodyFactory { - AMQBody createBody(DataInputStream in, long bodySize) throws AMQFrameDecodingException, IOException; + AMQBody createBody(ByteBuffer in, long bodySize) throws AMQFrameDecodingException; } diff --git a/java/common/src/main/java/org/apache/qpid/framing/CompositeAMQDataBlock.java b/java/common/src/main/java/org/apache/qpid/framing/CompositeAMQDataBlock.java index 15bc20c52d..94030f383e 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/CompositeAMQDataBlock.java +++ b/java/common/src/main/java/org/apache/qpid/framing/CompositeAMQDataBlock.java @@ -20,8 +20,7 @@ */ package org.apache.qpid.framing; -import java.io.DataOutputStream; -import java.io.IOException; +import org.apache.mina.common.ByteBuffer; public class CompositeAMQDataBlock extends AMQDataBlock implements EncodableAMQDataBlock { @@ -50,7 +49,7 @@ public class CompositeAMQDataBlock extends AMQDataBlock implements EncodableAMQD return frameSize; } - public void writePayload(DataOutputStream buffer) throws IOException + public void writePayload(ByteBuffer buffer) { for (int i = 0; i < _blocks.length; i++) { diff --git a/java/common/src/main/java/org/apache/qpid/framing/ContentBody.java b/java/common/src/main/java/org/apache/qpid/framing/ContentBody.java index aedb35f92a..9d39f8aa86 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/ContentBody.java +++ b/java/common/src/main/java/org/apache/qpid/framing/ContentBody.java @@ -20,10 +20,7 @@ */ package org.apache.qpid.framing; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - +import org.apache.mina.common.ByteBuffer; import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; import org.apache.qpid.AMQException; @@ -31,22 +28,27 @@ public class ContentBody implements AMQBody { public static final byte TYPE = 3; - public byte[] _payload; + public ByteBuffer payload; public ContentBody() { } - public ContentBody(DataInputStream buffer, long size) throws AMQFrameDecodingException, IOException + public ContentBody(ByteBuffer buffer, long size) throws AMQFrameDecodingException { - _payload = new byte[(int)size]; - buffer.read(_payload); + if (size > 0) + { + payload = buffer.slice(); + payload.limit((int) size); + buffer.skip((int) size); + } + } - public ContentBody(byte[] payload) + public ContentBody(ByteBuffer payload) { - _payload = payload; + this.payload = payload; } public byte getFrameType() @@ -56,12 +58,23 @@ public class ContentBody implements AMQBody public int getSize() { - return _payload == null ? 0 : _payload.length; + return (payload == null ? 0 : payload.limit()); } - public void writePayload(DataOutputStream buffer) throws IOException + public void writePayload(ByteBuffer buffer) { - buffer.write(_payload); + if (payload != null) + { + if(payload.isDirect() || payload.isReadOnly()) + { + ByteBuffer copy = payload.duplicate(); + buffer.put(copy.rewind()); + } + else + { + buffer.put(payload.array(),payload.arrayOffset(),payload.limit()); + } + } } public void handle(final int channelId, final AMQVersionAwareProtocolSession session) @@ -70,18 +83,32 @@ public class ContentBody implements AMQBody session.contentBodyReceived(channelId, this); } - protected void populateFromBuffer(DataInputStream buffer, long size) throws AMQFrameDecodingException, IOException + protected void populateFromBuffer(ByteBuffer buffer, long size) throws AMQFrameDecodingException { if (size > 0) { - _payload = new byte[(int)size]; - buffer.read(_payload); + payload = buffer.slice(); + payload.limit((int) size); + buffer.skip((int) size); } } public void reduceBufferToFit() { + if (payload != null && (payload.remaining() < payload.capacity() / 2)) + { + int size = payload.limit(); + ByteBuffer newPayload = ByteBuffer.allocate(size); + + newPayload.put(payload); + newPayload.flip(); + + //reduce reference count on payload + payload.release(); + + payload = newPayload; + } } diff --git a/java/common/src/main/java/org/apache/qpid/framing/ContentBodyFactory.java b/java/common/src/main/java/org/apache/qpid/framing/ContentBodyFactory.java index a0b030ab6b..c42995d148 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/ContentBodyFactory.java +++ b/java/common/src/main/java/org/apache/qpid/framing/ContentBodyFactory.java @@ -20,8 +20,7 @@ */ package org.apache.qpid.framing; -import java.io.DataInputStream; -import java.io.IOException; +import org.apache.mina.common.ByteBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,7 +41,7 @@ public class ContentBodyFactory implements BodyFactory _log.debug("Creating content body factory"); } - public AMQBody createBody(DataInputStream in, long bodySize) throws AMQFrameDecodingException, IOException + public AMQBody createBody(ByteBuffer in, long bodySize) throws AMQFrameDecodingException { return new ContentBody(in, bodySize); } diff --git a/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderBody.java b/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderBody.java index 18d0f26152..83e5a7e341 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderBody.java +++ b/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderBody.java @@ -20,10 +20,7 @@ */ package org.apache.qpid.framing; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - +import org.apache.mina.common.ByteBuffer; import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; import org.apache.qpid.AMQException; @@ -39,18 +36,18 @@ public class ContentHeaderBody implements AMQBody public long bodySize; /** must never be null */ - private ContentHeaderProperties properties; + public ContentHeaderProperties properties; public ContentHeaderBody() { } - public ContentHeaderBody(DataInputStream buffer, long size) throws AMQFrameDecodingException, IOException + public ContentHeaderBody(ByteBuffer buffer, long size) throws AMQFrameDecodingException { - classId = buffer.readUnsignedShort(); - weight = buffer.readUnsignedShort(); - bodySize = buffer.readLong(); - int propertyFlags = buffer.readUnsignedShort(); + classId = buffer.getUnsignedShort(); + weight = buffer.getUnsignedShort(); + bodySize = buffer.getLong(); + int propertyFlags = buffer.getUnsignedShort(); ContentHeaderPropertiesFactory factory = ContentHeaderPropertiesFactory.getInstance(); properties = factory.createContentHeaderProperties(classId, propertyFlags, buffer, (int)size - 14); @@ -75,13 +72,13 @@ public class ContentHeaderBody implements AMQBody return TYPE; } - protected void populateFromBuffer(DataInputStream buffer, long size) - throws AMQFrameDecodingException, AMQProtocolVersionException, IOException + protected void populateFromBuffer(ByteBuffer buffer, long size) + throws AMQFrameDecodingException, AMQProtocolVersionException { - classId = buffer.readUnsignedShort(); - weight = buffer.readUnsignedShort(); - bodySize = buffer.readLong(); - int propertyFlags = buffer.readUnsignedShort(); + classId = buffer.getUnsignedShort(); + weight = buffer.getUnsignedShort(); + bodySize = buffer.getLong(); + int propertyFlags = buffer.getUnsignedShort(); ContentHeaderPropertiesFactory factory = ContentHeaderPropertiesFactory.getInstance(); properties = factory.createContentHeaderProperties(classId, propertyFlags, buffer, (int)size - 14); } @@ -93,8 +90,8 @@ public class ContentHeaderBody implements AMQBody * @return * @throws AMQFrameDecodingException */ - public static ContentHeaderBody createFromBuffer(DataInputStream buffer, long size) - throws AMQFrameDecodingException, AMQProtocolVersionException, IOException + public static ContentHeaderBody createFromBuffer(ByteBuffer buffer, long size) + throws AMQFrameDecodingException, AMQProtocolVersionException { ContentHeaderBody body = new ContentHeaderBody(buffer, size); @@ -106,11 +103,11 @@ public class ContentHeaderBody implements AMQBody return 2 + 2 + 8 + 2 + properties.getPropertyListSize(); } - public void writePayload(DataOutputStream buffer) throws IOException + public void writePayload(ByteBuffer buffer) { EncodingUtils.writeUnsignedShort(buffer, classId); EncodingUtils.writeUnsignedShort(buffer, weight); - buffer.writeLong(bodySize); + buffer.putLong(bodySize); EncodingUtils.writeUnsignedShort(buffer, properties.getPropertyFlags()); properties.writePropertyListPayload(buffer); } @@ -131,25 +128,4 @@ public class ContentHeaderBody implements AMQBody { return new AMQFrame(channelId, body); } - - public ContentHeaderProperties getProperties() - { - return properties; - } - - public void setProperties(ContentHeaderProperties props) - { - properties = props; - } - - @Override - public String toString() - { - return "ContentHeaderBody{" + - "classId=" + classId + - ", weight=" + weight + - ", bodySize=" + bodySize + - ", properties=" + properties + - '}'; - } } diff --git a/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderBodyFactory.java b/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderBodyFactory.java index a474e337b7..8d5e2f9fb4 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderBodyFactory.java +++ b/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderBodyFactory.java @@ -20,8 +20,7 @@ */ package org.apache.qpid.framing; -import java.io.DataInputStream; -import java.io.IOException; +import org.apache.mina.common.ByteBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,7 +41,7 @@ public class ContentHeaderBodyFactory implements BodyFactory _log.debug("Creating content header body factory"); } - public AMQBody createBody(DataInputStream in, long bodySize) throws AMQFrameDecodingException, IOException + public AMQBody createBody(ByteBuffer in, long bodySize) throws AMQFrameDecodingException { // all content headers are the same - it is only the properties that differ. // the content header body further delegates construction of properties diff --git a/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderProperties.java b/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderProperties.java index 237929f9a3..7ef538cfdc 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderProperties.java +++ b/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderProperties.java @@ -20,10 +20,7 @@ */ package org.apache.qpid.framing; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - +import org.apache.mina.common.ByteBuffer; /** * There will be an implementation of this interface for each content type. All content types have associated @@ -35,7 +32,7 @@ public interface ContentHeaderProperties * Writes the property list to the buffer, in a suitably encoded form. * @param buffer The buffer to write to */ - void writePropertyListPayload(DataOutputStream buffer) throws IOException; + void writePropertyListPayload(ByteBuffer buffer); /** * Populates the properties from buffer. @@ -43,8 +40,8 @@ public interface ContentHeaderProperties * @param propertyFlags he property flags. * @throws AMQFrameDecodingException when the buffer does not contain valid data */ - void populatePropertiesFromBuffer(DataInputStream buffer, int propertyFlags, int size) - throws AMQFrameDecodingException, IOException; + void populatePropertiesFromBuffer(ByteBuffer buffer, int propertyFlags, int size) + throws AMQFrameDecodingException; /** * @return the size of the encoded property list in bytes. @@ -59,4 +56,5 @@ public interface ContentHeaderProperties */ int getPropertyFlags(); + void updated(); } diff --git a/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderPropertiesFactory.java b/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderPropertiesFactory.java index 43ee8cd1f1..46189b63d7 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderPropertiesFactory.java +++ b/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderPropertiesFactory.java @@ -20,8 +20,7 @@ */ package org.apache.qpid.framing; -import java.io.DataInputStream; -import java.io.IOException; +import org.apache.mina.common.ByteBuffer; import org.apache.qpid.framing.amqp_8_0.BasicConsumeBodyImpl; @@ -39,8 +38,8 @@ public class ContentHeaderPropertiesFactory } public ContentHeaderProperties createContentHeaderProperties(int classId, int propertyFlags, - DataInputStream buffer, int size) - throws AMQFrameDecodingException, IOException + ByteBuffer buffer, int size) + throws AMQFrameDecodingException { ContentHeaderProperties properties; // AMQP version change: "Hardwired" version to major=8, minor=0 diff --git a/java/common/src/main/java/org/apache/qpid/framing/DeferredDataBlock.java b/java/common/src/main/java/org/apache/qpid/framing/DeferredDataBlock.java new file mode 100644 index 0000000000..f6795ff200 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/framing/DeferredDataBlock.java @@ -0,0 +1,50 @@ +package org.apache.qpid.framing; + +import org.apache.mina.common.ByteBuffer; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "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. +* +*/ +public abstract class DeferredDataBlock extends AMQDataBlock +{ + private AMQDataBlock _underlyingDataBlock; + + + public long getSize() + { + if(_underlyingDataBlock == null) + { + _underlyingDataBlock = createAMQDataBlock(); + } + return _underlyingDataBlock.getSize(); + } + + public void writePayload(ByteBuffer buffer) + { + if(_underlyingDataBlock == null) + { + _underlyingDataBlock = createAMQDataBlock(); + } + _underlyingDataBlock.writePayload(buffer); + } + + abstract protected AMQDataBlock createAMQDataBlock(); + +} diff --git a/java/common/src/main/java/org/apache/qpid/framing/EncodingUtils.java b/java/common/src/main/java/org/apache/qpid/framing/EncodingUtils.java index 2d7e27405c..6425f8c591 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/EncodingUtils.java +++ b/java/common/src/main/java/org/apache/qpid/framing/EncodingUtils.java @@ -20,12 +20,11 @@ */ package org.apache.qpid.framing; -import java.io.*; +import org.apache.mina.common.ByteBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.nio.ByteBuffer; import java.nio.charset.Charset; public class EncodingUtils @@ -219,7 +218,7 @@ public class EncodingUtils return 0; } - public static void writeShortStringBytes(DataOutputStream buffer, String s) throws IOException + public static void writeShortStringBytes(ByteBuffer buffer, String s) { if (s != null) { @@ -232,18 +231,18 @@ public class EncodingUtils // TODO: check length fits in an unsigned byte writeUnsignedByte(buffer, (short)encodedString.length); - buffer.write(encodedString); + buffer.put(encodedString); } else { // really writing out unsigned byte - buffer.write((byte) 0); + buffer.put((byte) 0); } } - public static void writeShortStringBytes(DataOutputStream buffer, AMQShortString s) throws IOException + public static void writeShortStringBytes(ByteBuffer buffer, AMQShortString s) { if (s != null) { @@ -253,11 +252,11 @@ public class EncodingUtils else { // really writing out unsigned byte - buffer.write((byte) 0); + buffer.put((byte) 0); } } - public static void writeLongStringBytes(DataOutputStream buffer, String s) throws IOException + public static void writeLongStringBytes(ByteBuffer buffer, String s) { assert (s == null) || (s.length() <= 0xFFFE); if (s != null) @@ -271,7 +270,7 @@ public class EncodingUtils encodedString[i] = (byte) cha[i]; } - buffer.write(encodedString); + buffer.put(encodedString); } else { @@ -279,7 +278,7 @@ public class EncodingUtils } } - public static void writeLongStringBytes(DataOutputStream buffer, char[] s) throws IOException + public static void writeLongStringBytes(ByteBuffer buffer, char[] s) { assert (s == null) || (s.length <= 0xFFFE); if (s != null) @@ -292,7 +291,7 @@ public class EncodingUtils encodedString[i] = (byte) s[i]; } - buffer.write(encodedString); + buffer.put(encodedString); } else { @@ -300,13 +299,13 @@ public class EncodingUtils } } - public static void writeLongStringBytes(DataOutputStream buffer, byte[] bytes) throws IOException + public static void writeLongStringBytes(ByteBuffer buffer, byte[] bytes) { assert (bytes == null) || (bytes.length <= 0xFFFE); if (bytes != null) { writeUnsignedInteger(buffer, bytes.length); - buffer.write(bytes); + buffer.put(bytes); } else { @@ -314,24 +313,24 @@ public class EncodingUtils } } - public static void writeUnsignedByte(DataOutputStream buffer, short b) throws IOException + public static void writeUnsignedByte(ByteBuffer buffer, short b) { byte bv = (byte) b; - buffer.write(bv); + buffer.put(bv); } - public static void writeUnsignedShort(DataOutputStream buffer, int s) throws IOException + public static void writeUnsignedShort(ByteBuffer buffer, int s) { // TODO: Is this comparison safe? Do I need to cast RHS to long? if (s < Short.MAX_VALUE) { - buffer.writeShort(s); + buffer.putShort((short) s); } else { short sv = (short) s; - buffer.write((byte) (0xFF & (sv >> 8))); - buffer.write((byte) (0xFF & sv)); + buffer.put((byte) (0xFF & (sv >> 8))); + buffer.put((byte) (0xFF & sv)); } } @@ -340,12 +339,12 @@ public class EncodingUtils return 4; } - public static void writeUnsignedInteger(DataOutputStream buffer, long l) throws IOException + public static void writeUnsignedInteger(ByteBuffer buffer, long l) { // TODO: Is this comparison safe? Do I need to cast RHS to long? if (l < Integer.MAX_VALUE) { - buffer.writeInt((int) l); + buffer.putInt((int) l); } else { @@ -353,14 +352,14 @@ public class EncodingUtils // FIXME: This *may* go faster if we build this into a local 4-byte array and then // put the array in a single call. - buffer.write((byte) (0xFF & (iv >> 24))); - buffer.write((byte) (0xFF & (iv >> 16))); - buffer.write((byte) (0xFF & (iv >> 8))); - buffer.write((byte) (0xFF & iv)); + buffer.put((byte) (0xFF & (iv >> 24))); + buffer.put((byte) (0xFF & (iv >> 16))); + buffer.put((byte) (0xFF & (iv >> 8))); + buffer.put((byte) (0xFF & iv)); } } - public static void writeFieldTableBytes(DataOutputStream buffer, FieldTable table) throws IOException + public static void writeFieldTableBytes(ByteBuffer buffer, FieldTable table) { if (table != null) { @@ -372,12 +371,12 @@ public class EncodingUtils } } - public static void writeContentBytes(DataOutputStream buffer, Content content) + public static void writeContentBytes(ByteBuffer buffer, Content content) { // TODO: New Content class required for AMQP 0-9. } - public static void writeBooleans(DataOutputStream buffer, boolean[] values) throws IOException + public static void writeBooleans(ByteBuffer buffer, boolean[] values) { byte packedValue = 0; for (int i = 0; i < values.length; i++) @@ -388,16 +387,16 @@ public class EncodingUtils } } - buffer.write(packedValue); + buffer.put(packedValue); } - public static void writeBooleans(DataOutputStream buffer, boolean value) throws IOException + public static void writeBooleans(ByteBuffer buffer, boolean value) { - buffer.write(value ? (byte) 1 : (byte) 0); + buffer.put(value ? (byte) 1 : (byte) 0); } - public static void writeBooleans(DataOutputStream buffer, boolean value0, boolean value1) throws IOException + public static void writeBooleans(ByteBuffer buffer, boolean value0, boolean value1) { byte packedValue = value0 ? (byte) 1 : (byte) 0; @@ -406,10 +405,10 @@ public class EncodingUtils packedValue = (byte) (packedValue | (byte) (1 << 1)); } - buffer.write(packedValue); + buffer.put(packedValue); } - public static void writeBooleans(DataOutputStream buffer, boolean value0, boolean value1, boolean value2) throws IOException + public static void writeBooleans(ByteBuffer buffer, boolean value0, boolean value1, boolean value2) { byte packedValue = value0 ? (byte) 1 : (byte) 0; @@ -423,10 +422,10 @@ public class EncodingUtils packedValue = (byte) (packedValue | (byte) (1 << 2)); } - buffer.write(packedValue); + buffer.put(packedValue); } - public static void writeBooleans(DataOutputStream buffer, boolean value0, boolean value1, boolean value2, boolean value3) throws IOException + public static void writeBooleans(ByteBuffer buffer, boolean value0, boolean value1, boolean value2, boolean value3) { byte packedValue = value0 ? (byte) 1 : (byte) 0; @@ -445,11 +444,11 @@ public class EncodingUtils packedValue = (byte) (packedValue | (byte) (1 << 3)); } - buffer.write(packedValue); + buffer.put(packedValue); } - public static void writeBooleans(DataOutputStream buffer, boolean value0, boolean value1, boolean value2, boolean value3, - boolean value4) throws IOException + public static void writeBooleans(ByteBuffer buffer, boolean value0, boolean value1, boolean value2, boolean value3, + boolean value4) { byte packedValue = value0 ? (byte) 1 : (byte) 0; @@ -473,11 +472,11 @@ public class EncodingUtils packedValue = (byte) (packedValue | (byte) (1 << 4)); } - buffer.write(packedValue); + buffer.put(packedValue); } - public static void writeBooleans(DataOutputStream buffer, boolean value0, boolean value1, boolean value2, boolean value3, - boolean value4, boolean value5) throws IOException + public static void writeBooleans(ByteBuffer buffer, boolean value0, boolean value1, boolean value2, boolean value3, + boolean value4, boolean value5) { byte packedValue = value0 ? (byte) 1 : (byte) 0; @@ -506,11 +505,11 @@ public class EncodingUtils packedValue = (byte) (packedValue | (byte) (1 << 5)); } - buffer.write(packedValue); + buffer.put(packedValue); } - public static void writeBooleans(DataOutputStream buffer, boolean value0, boolean value1, boolean value2, boolean value3, - boolean value4, boolean value5, boolean value6) throws IOException + public static void writeBooleans(ByteBuffer buffer, boolean value0, boolean value1, boolean value2, boolean value3, + boolean value4, boolean value5, boolean value6) { byte packedValue = value0 ? (byte) 1 : (byte) 0; @@ -544,11 +543,11 @@ public class EncodingUtils packedValue = (byte) (packedValue | (byte) (1 << 6)); } - buffer.write(packedValue); + buffer.put(packedValue); } - public static void writeBooleans(DataOutputStream buffer, boolean value0, boolean value1, boolean value2, boolean value3, - boolean value4, boolean value5, boolean value6, boolean value7) throws IOException + public static void writeBooleans(ByteBuffer buffer, boolean value0, boolean value1, boolean value2, boolean value3, + boolean value4, boolean value5, boolean value6, boolean value7) { byte packedValue = value0 ? (byte) 1 : (byte) 0; @@ -587,7 +586,7 @@ public class EncodingUtils packedValue = (byte) (packedValue | (byte) (1 << 7)); } - buffer.write(packedValue); + buffer.put(packedValue); } /** @@ -596,12 +595,12 @@ public class EncodingUtils * @param buffer * @param data */ - public static void writeLongstr(DataOutputStream buffer, byte[] data) throws IOException + public static void writeLongstr(ByteBuffer buffer, byte[] data) { if (data != null) { writeUnsignedInteger(buffer, data.length); - buffer.write(data); + buffer.put(data); } else { @@ -609,14 +608,14 @@ public class EncodingUtils } } - public static void writeTimestamp(DataOutputStream buffer, long timestamp) throws IOException + public static void writeTimestamp(ByteBuffer buffer, long timestamp) { writeLong(buffer, timestamp); } - public static boolean[] readBooleans(DataInputStream buffer) throws IOException + public static boolean[] readBooleans(ByteBuffer buffer) { - final byte packedValue = buffer.readByte(); + final byte packedValue = buffer.get(); if (packedValue == 0) { return ALL_FALSE_ARRAY; @@ -641,9 +640,9 @@ public class EncodingUtils return result; } - public static FieldTable readFieldTable(DataInputStream buffer) throws AMQFrameDecodingException, IOException + public static FieldTable readFieldTable(ByteBuffer buffer) throws AMQFrameDecodingException { - long length = ((long)(buffer.readInt())) & 0xFFFFFFFFL; + long length = buffer.getUnsignedInt(); if (length == 0) { return null; @@ -654,21 +653,21 @@ public class EncodingUtils } } - public static Content readContent(DataInputStream buffer) throws AMQFrameDecodingException + public static Content readContent(ByteBuffer buffer) throws AMQFrameDecodingException { // TODO: New Content class required for AMQP 0-9. return null; } - public static AMQShortString readAMQShortString(DataInputStream buffer) throws IOException + public static AMQShortString readAMQShortString(ByteBuffer buffer) { return AMQShortString.readFromBuffer(buffer); } - public static String readShortString(DataInputStream buffer) throws IOException + public static String readShortString(ByteBuffer buffer) { - short length = (short) (((short)buffer.readByte()) & 0xFF); + short length = buffer.getUnsigned(); if (length == 0) { return null; @@ -681,7 +680,7 @@ public class EncodingUtils // this approach here is valid since we know that all the chars are // ASCII (0-127) byte[] stringBytes = new byte[length]; - buffer.read(stringBytes, 0, length); + buffer.get(stringBytes, 0, length); char[] stringChars = new char[length]; for (int i = 0; i < stringChars.length; i++) { @@ -692,9 +691,9 @@ public class EncodingUtils } } - public static String readLongString(DataInputStream buffer) throws IOException + public static String readLongString(ByteBuffer buffer) { - long length = ((long)(buffer.readInt())) & 0xFFFFFFFFL; + long length = buffer.getUnsignedInt(); if (length == 0) { return ""; @@ -707,7 +706,7 @@ public class EncodingUtils // this approach here is valid since we know that all the chars are // ASCII (0-127) byte[] stringBytes = new byte[(int) length]; - buffer.read(stringBytes, 0, (int) length); + buffer.get(stringBytes, 0, (int) length); char[] stringChars = new char[(int) length]; for (int i = 0; i < stringChars.length; i++) { @@ -718,9 +717,9 @@ public class EncodingUtils } } - public static byte[] readLongstr(DataInputStream buffer) throws IOException + public static byte[] readLongstr(ByteBuffer buffer) { - long length = ((long)(buffer.readInt())) & 0xFFFFFFFFL; + long length = buffer.getUnsignedInt(); if (length == 0) { return null; @@ -728,17 +727,17 @@ public class EncodingUtils else { byte[] result = new byte[(int) length]; - buffer.read(result); + buffer.get(result); return result; } } - public static long readTimestamp(DataInputStream buffer) throws IOException + public static long readTimestamp(ByteBuffer buffer) { // Discard msb from AMQ timestamp // buffer.getUnsignedInt(); - return buffer.readLong(); + return buffer.getLong(); } static byte[] hexToByteArray(String id) @@ -818,14 +817,14 @@ public class EncodingUtils // AMQP_BOOLEAN_PROPERTY_PREFIX - public static void writeBoolean(DataOutputStream buffer, Boolean aBoolean) throws IOException + public static void writeBoolean(ByteBuffer buffer, Boolean aBoolean) { - buffer.write(aBoolean ? 1 : 0); + buffer.put((byte) (aBoolean ? 1 : 0)); } - public static boolean readBoolean(DataInputStream buffer) throws IOException + public static boolean readBoolean(ByteBuffer buffer) { - byte packedValue = buffer.readByte(); + byte packedValue = buffer.get(); return (packedValue == 1); } @@ -836,14 +835,14 @@ public class EncodingUtils } // AMQP_BYTE_PROPERTY_PREFIX - public static void writeByte(DataOutputStream buffer, Byte aByte) throws IOException + public static void writeByte(ByteBuffer buffer, Byte aByte) { - buffer.writeByte(aByte); + buffer.put(aByte); } - public static byte readByte(DataInputStream buffer) throws IOException + public static byte readByte(ByteBuffer buffer) { - return buffer.readByte(); + return buffer.get(); } public static int encodedByteLength() @@ -852,14 +851,14 @@ public class EncodingUtils } // AMQP_SHORT_PROPERTY_PREFIX - public static void writeShort(DataOutputStream buffer, Short aShort) throws IOException + public static void writeShort(ByteBuffer buffer, Short aShort) { - buffer.writeShort(aShort); + buffer.putShort(aShort); } - public static short readShort(DataInputStream buffer) throws IOException + public static short readShort(ByteBuffer buffer) { - return buffer.readShort(); + return buffer.getShort(); } public static int encodedShortLength() @@ -868,14 +867,14 @@ public class EncodingUtils } // INTEGER_PROPERTY_PREFIX - public static void writeInteger(DataOutputStream buffer, Integer aInteger) throws IOException + public static void writeInteger(ByteBuffer buffer, Integer aInteger) { - buffer.writeInt(aInteger); + buffer.putInt(aInteger); } - public static int readInteger(DataInputStream buffer) throws IOException + public static int readInteger(ByteBuffer buffer) { - return buffer.readInt(); + return buffer.getInt(); } public static int encodedIntegerLength() @@ -884,14 +883,14 @@ public class EncodingUtils } // AMQP_LONG_PROPERTY_PREFIX - public static void writeLong(DataOutputStream buffer, Long aLong) throws IOException + public static void writeLong(ByteBuffer buffer, Long aLong) { - buffer.writeLong(aLong); + buffer.putLong(aLong); } - public static long readLong(DataInputStream buffer) throws IOException + public static long readLong(ByteBuffer buffer) { - return buffer.readLong(); + return buffer.getLong(); } public static int encodedLongLength() @@ -900,14 +899,14 @@ public class EncodingUtils } // Float_PROPERTY_PREFIX - public static void writeFloat(DataOutputStream buffer, Float aFloat) throws IOException + public static void writeFloat(ByteBuffer buffer, Float aFloat) { - buffer.writeFloat(aFloat); + buffer.putFloat(aFloat); } - public static float readFloat(DataInputStream buffer) throws IOException + public static float readFloat(ByteBuffer buffer) { - return buffer.readFloat(); + return buffer.getFloat(); } public static int encodedFloatLength() @@ -916,14 +915,14 @@ public class EncodingUtils } // Double_PROPERTY_PREFIX - public static void writeDouble(DataOutputStream buffer, Double aDouble) throws IOException + public static void writeDouble(ByteBuffer buffer, Double aDouble) { - buffer.writeDouble(aDouble); + buffer.putDouble(aDouble); } - public static double readDouble(DataInputStream buffer) throws IOException + public static double readDouble(ByteBuffer buffer) { - return buffer.readDouble(); + return buffer.getDouble(); } public static int encodedDoubleLength() @@ -931,9 +930,9 @@ public class EncodingUtils return 8; } - public static byte[] readBytes(DataInputStream buffer) throws IOException + public static byte[] readBytes(ByteBuffer buffer) { - long length = ((long)(buffer.readInt())) & 0xFFFFFFFFL; + long length = buffer.getUnsignedInt(); if (length == 0) { return null; @@ -941,19 +940,19 @@ public class EncodingUtils else { byte[] dataBytes = new byte[(int)length]; - buffer.read(dataBytes, 0, (int) length); + buffer.get(dataBytes, 0, (int)length); return dataBytes; } } - public static void writeBytes(DataOutputStream buffer, byte[] data) throws IOException + public static void writeBytes(ByteBuffer buffer, byte[] data) { if (data != null) { // TODO: check length fits in an unsigned byte writeUnsignedInteger(buffer, (long)data.length); - buffer.write(data); + buffer.put(data); } else { @@ -969,35 +968,35 @@ public class EncodingUtils return encodedByteLength(); } - public static char readChar(DataInputStream buffer) throws IOException + public static char readChar(ByteBuffer buffer) { // This is valid as we know that the Character is ASCII 0..127 - return (char) buffer.read(); + return (char) buffer.get(); } - public static void writeChar(DataOutputStream buffer, char character) throws IOException + public static void writeChar(ByteBuffer buffer, char character) { // This is valid as we know that the Character is ASCII 0..127 writeByte(buffer, (byte) character); } - public static long readLongAsShortString(DataInputStream buffer) throws IOException + public static long readLongAsShortString(ByteBuffer buffer) { - short length = (short) buffer.readUnsignedByte(); + short length = buffer.getUnsigned(); short pos = 0; if (length == 0) { return 0L; } - byte digit = buffer.readByte(); + byte digit = buffer.get(); boolean isNegative; long result = 0; if (digit == (byte) '-') { isNegative = true; pos++; - digit = buffer.readByte(); + digit = buffer.get(); } else { @@ -1010,7 +1009,7 @@ public class EncodingUtils while (pos < length) { pos++; - digit = buffer.readByte(); + digit = buffer.get(); result = (result << 3) + (result << 1); result += digit - (byte) '0'; } @@ -1018,15 +1017,15 @@ public class EncodingUtils return result; } - public static long readUnsignedInteger(DataInputStream buffer) throws IOException + public static long readUnsignedInteger(ByteBuffer buffer) { - long l = 0xFF & buffer.readByte(); + long l = 0xFF & buffer.get(); l <<= 8; - l = l | (0xFF & buffer.readByte()); + l = l | (0xFF & buffer.get()); l <<= 8; - l = l | (0xFF & buffer.readByte()); + l = l | (0xFF & buffer.get()); l <<= 8; - l = l | (0xFF & buffer.readByte()); + l = l | (0xFF & buffer.get()); return l; } diff --git a/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java b/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java index 721c821bab..22205d49f8 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java +++ b/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java @@ -20,16 +20,12 @@ */ package org.apache.qpid.framing; +import org.apache.mina.common.ByteBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.qpid.AMQPInvalidClassException; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; import java.math.BigDecimal; import java.util.Collections; import java.util.Enumeration; @@ -47,8 +43,8 @@ public class FieldTable private static final String STRICT_AMQP = "STRICT_AMQP"; private final boolean _strictAMQP = Boolean.valueOf(System.getProperty(STRICT_AMQP, "false")); - private byte[] _encodedForm; - private LinkedHashMap<AMQShortString, AMQTypedValue> _properties = null; + private ByteBuffer _encodedForm; + private LinkedHashMap<AMQShortString, AMQTypedValue> _properties; private long _encodedSize; private static final int INITIAL_HASHMAP_CAPACITY = 16; private static final int INITIAL_ENCODED_FORM_SIZE = 256; @@ -56,6 +52,9 @@ public class FieldTable public FieldTable() { super(); + // _encodedForm = ByteBuffer.allocate(INITIAL_ENCODED_FORM_SIZE); + // _encodedForm.setAutoExpand(true); + // _encodedForm.limit(0); } /** @@ -64,12 +63,16 @@ public class FieldTable * @param buffer the buffer from which to read data. The length byte must be read already * @param length the length of the field table. Must be > 0. */ - public FieldTable(DataInputStream buffer, long length) throws IOException + public FieldTable(ByteBuffer buffer, long length) { this(); - _encodedForm = new byte[(int) length]; - buffer.read(_encodedForm); + ByteBuffer encodedForm = buffer.slice(); + encodedForm.limit((int) length); + _encodedForm = ByteBuffer.allocate((int)length); + _encodedForm.put(encodedForm); + _encodedForm.flip(); _encodedSize = length; + buffer.skip((int) length); } public AMQTypedValue getProperty(AMQShortString string) @@ -105,19 +108,13 @@ public class FieldTable { try { - setFromBuffer(); + setFromBuffer(_encodedForm, _encodedSize); } catch (AMQFrameDecodingException e) { _logger.error("Error decoding FieldTable in deferred decoding mode ", e); throw new IllegalArgumentException(e); } - catch (IOException e) - { - _logger.error("Unexpected IO exception decoding field table"); - throw new IllegalArgumentException(e); - - } } private AMQTypedValue setProperty(AMQShortString key, AMQTypedValue val) @@ -769,7 +766,7 @@ public class FieldTable // ************************* Byte Buffer Processing - public void writeToBuffer(DataOutputStream buffer) throws IOException + public void writeToBuffer(ByteBuffer buffer) { final boolean trace = _logger.isDebugEnabled(); @@ -789,21 +786,17 @@ public class FieldTable public byte[] getDataAsBytes() { - if(_encodedForm == null) - { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try - { - putDataInBuffer(new DataOutputStream(baos)); - return baos.toByteArray(); - } - catch (IOException e) - { - throw new IllegalArgumentException("IO Exception should never be thrown here"); - } + final int encodedSize = (int) getEncodedSize(); + final ByteBuffer buffer = ByteBuffer.allocate(encodedSize); // FIXME XXX: Is cast a problem? - } - return _encodedForm.clone(); + putDataInBuffer(buffer); + + final byte[] result = new byte[encodedSize]; + buffer.flip(); + buffer.get(result); + buffer.release(); + + return result; } public long getEncodedSize() @@ -933,8 +926,15 @@ public class FieldTable public Iterator<Map.Entry<AMQShortString, AMQTypedValue>> iterator() { - initMapIfNecessary(); - return _properties.entrySet().iterator(); + if(_encodedForm != null) + { + return new FieldTableIterator(_encodedForm.duplicate().rewind(),(int)_encodedSize); + } + else + { + initMapIfNecessary(); + return _properties.entrySet().iterator(); + } } public Object get(String key) @@ -1002,12 +1002,26 @@ public class FieldTable return _properties.keySet(); } - private void putDataInBuffer(DataOutputStream buffer) throws IOException + private void putDataInBuffer(ByteBuffer buffer) { if (_encodedForm != null) { - buffer.write(_encodedForm); + if(buffer.isDirect() || buffer.isReadOnly()) + { + ByteBuffer encodedForm = _encodedForm.duplicate(); + + if (encodedForm.position() != 0) + { + encodedForm.flip(); + } + + buffer.put(encodedForm); + } + else + { + buffer.put(_encodedForm.array(),_encodedForm.arrayOffset(),(int)_encodedSize); + } } else if (_properties != null) { @@ -1021,27 +1035,41 @@ public class FieldTable final Map.Entry<AMQShortString, AMQTypedValue> me = it.next(); try { + if (_logger.isDebugEnabled()) + { + _logger.debug("Writing Property:" + me.getKey() + " Type:" + me.getValue().getType() + " Value:" + + me.getValue().getValue()); + _logger.debug("Buffer Position:" + buffer.position() + " Remaining:" + buffer.remaining()); + } + // Write the actual parameter name EncodingUtils.writeShortStringBytes(buffer, me.getKey()); me.getValue().writeToBuffer(buffer); } catch (Exception e) { + if (_logger.isDebugEnabled()) + { + _logger.debug("Exception thrown:" + e); + _logger.debug("Writing Property:" + me.getKey() + " Type:" + me.getValue().getType() + " Value:" + + me.getValue().getValue()); + _logger.debug("Buffer Position:" + buffer.position() + " Remaining:" + buffer.remaining()); + } + throw new RuntimeException(e); } } } } - private void setFromBuffer() throws AMQFrameDecodingException, IOException + private void setFromBuffer(ByteBuffer buffer, long length) throws AMQFrameDecodingException { - final ByteArrayInputStream in = new ByteArrayInputStream(_encodedForm); - DataInputStream buffer = new DataInputStream(in); final boolean trace = _logger.isDebugEnabled(); - if (_encodedSize > 0) + if (length > 0) { + final int expectedRemaining = buffer.remaining() - (int) length; _properties = new LinkedHashMap<AMQShortString, AMQTypedValue>(INITIAL_HASHMAP_CAPACITY); @@ -1049,16 +1077,121 @@ public class FieldTable { final AMQShortString key = EncodingUtils.readAMQShortString(buffer); + + _logger.debug("FieldTable::PropFieldTable(buffer," + length + "): Read key '" + key); + AMQTypedValue value = AMQTypedValue.readFromBuffer(buffer); + + if (trace) + { + _logger.debug("FieldTable::PropFieldTable(buffer," + length + "): Read type '" + value.getType() + + "', key '" + key + "', value '" + value.getValue() + "'"); + } + _properties.put(key, value); } - while (in.available() > 0); + while (buffer.remaining() > expectedRemaining); + + } + + _encodedSize = length; + + if (trace) + { + _logger.debug("FieldTable::FieldTable(buffer," + length + "): Done."); + } + } + + private static final class FieldTableEntry implements Map.Entry<AMQShortString, AMQTypedValue> + { + private final AMQTypedValue _value; + private final AMQShortString _key; + + public FieldTableEntry(final AMQShortString key, final AMQTypedValue value) + { + _key = key; + _value = value; + } + + public AMQShortString getKey() + { + return _key; + } + + public AMQTypedValue getValue() + { + return _value; + } + + public AMQTypedValue setValue(final AMQTypedValue value) + { + throw new UnsupportedOperationException(); + } + + public boolean equals(Object o) + { + if(o instanceof FieldTableEntry) + { + FieldTableEntry other = (FieldTableEntry) o; + return (_key == null ? other._key == null : _key.equals(other._key)) + && (_value == null ? other._value == null : _value.equals(other._value)); + } + else + { + return false; + } + } + + public int hashCode() + { + return (getKey()==null ? 0 : getKey().hashCode()) + ^ (getValue()==null ? 0 : getValue().hashCode()); + } + + } + + + private static final class FieldTableIterator implements Iterator<Map.Entry<AMQShortString, AMQTypedValue>> + { + private final ByteBuffer _buffer; + private int _expectedRemaining; + + public FieldTableIterator(ByteBuffer buffer, int length) + { + _buffer = buffer; + _expectedRemaining = buffer.remaining() - length; + } + + public boolean hasNext() + { + return (_buffer.remaining() > _expectedRemaining); } + public Map.Entry<AMQShortString, AMQTypedValue> next() + { + if(hasNext()) + { + final AMQShortString key = EncodingUtils.readAMQShortString(_buffer); + AMQTypedValue value = AMQTypedValue.readFromBuffer(_buffer); + return new FieldTableEntry(key, value); + } + else + { + return null; + } + } + + public void remove() + { + throw new UnsupportedOperationException(); + } } + + + public int hashCode() { initMapIfNecessary(); diff --git a/java/common/src/main/java/org/apache/qpid/framing/FieldTableFactory.java b/java/common/src/main/java/org/apache/qpid/framing/FieldTableFactory.java index 438a46f28b..e9d75137ef 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/FieldTableFactory.java +++ b/java/common/src/main/java/org/apache/qpid/framing/FieldTableFactory.java @@ -20,8 +20,7 @@ */ package org.apache.qpid.framing; -import java.io.DataInputStream; -import java.io.IOException; +import org.apache.mina.common.ByteBuffer; public class FieldTableFactory { @@ -30,7 +29,7 @@ public class FieldTableFactory return new FieldTable(); } - public static FieldTable newFieldTable(DataInputStream byteBuffer, long length) throws AMQFrameDecodingException, IOException + public static FieldTable newFieldTable(ByteBuffer byteBuffer, long length) throws AMQFrameDecodingException { return new FieldTable(byteBuffer, length); } diff --git a/java/common/src/main/java/org/apache/qpid/framing/HeartbeatBody.java b/java/common/src/main/java/org/apache/qpid/framing/HeartbeatBody.java index a6ce721a50..18ab05ffa1 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/HeartbeatBody.java +++ b/java/common/src/main/java/org/apache/qpid/framing/HeartbeatBody.java @@ -20,10 +20,7 @@ */ package org.apache.qpid.framing; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - +import org.apache.mina.common.ByteBuffer; import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; import org.apache.qpid.AMQException; @@ -37,12 +34,12 @@ public class HeartbeatBody implements AMQBody } - public HeartbeatBody(DataInputStream buffer, long size) throws IOException + public HeartbeatBody(ByteBuffer buffer, long size) { if(size > 0) { //allow other implementations to have a payload, but ignore it: - buffer.skip(size); + buffer.skip((int) size); } } @@ -56,7 +53,7 @@ public class HeartbeatBody implements AMQBody return 0;//heartbeats we generate have no payload } - public void writePayload(DataOutputStream buffer) + public void writePayload(ByteBuffer buffer) { } @@ -66,12 +63,12 @@ public class HeartbeatBody implements AMQBody session.heartbeatBodyReceived(channelId, this); } - protected void populateFromBuffer(DataInputStream buffer, long size) throws AMQFrameDecodingException, IOException + protected void populateFromBuffer(ByteBuffer buffer, long size) throws AMQFrameDecodingException { if(size > 0) { //allow other implementations to have a payload, but ignore it: - buffer.skip(size); + buffer.skip((int) size); } } diff --git a/java/common/src/main/java/org/apache/qpid/framing/HeartbeatBodyFactory.java b/java/common/src/main/java/org/apache/qpid/framing/HeartbeatBodyFactory.java index dfc49c6167..c7ada708dc 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/HeartbeatBodyFactory.java +++ b/java/common/src/main/java/org/apache/qpid/framing/HeartbeatBodyFactory.java @@ -20,11 +20,11 @@ */ package org.apache.qpid.framing; -import java.io.DataInputStream; +import org.apache.mina.common.ByteBuffer; public class HeartbeatBodyFactory implements BodyFactory { - public AMQBody createBody(DataInputStream in, long bodySize) throws AMQFrameDecodingException + public AMQBody createBody(ByteBuffer in, long bodySize) throws AMQFrameDecodingException { return new HeartbeatBody(); } diff --git a/java/common/src/main/java/org/apache/qpid/framing/ProtocolInitiation.java b/java/common/src/main/java/org/apache/qpid/framing/ProtocolInitiation.java index 8c018316f0..fb3dd89717 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/ProtocolInitiation.java +++ b/java/common/src/main/java/org/apache/qpid/framing/ProtocolInitiation.java @@ -22,10 +22,6 @@ package org.apache.qpid.framing; import org.apache.qpid.AMQException; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.util.Arrays; @@ -66,30 +62,35 @@ public class ProtocolInitiation extends AMQDataBlock implements EncodableAMQData pv.equals(ProtocolVersion.v0_91) ? 1 : pv.getMinorVersion()); } - public ProtocolInitiation(DataInputStream in) throws IOException + public ProtocolInitiation(ByteBuffer in) { _protocolHeader = new byte[4]; - in.read(_protocolHeader); + in.get(_protocolHeader); - _protocolClass = in.readByte(); - _protocolInstance = in.readByte(); - _protocolMajor = in.readByte(); - _protocolMinor = in.readByte(); + _protocolClass = in.get(); + _protocolInstance = in.get(); + _protocolMajor = in.get(); + _protocolMinor = in.get(); } + public void writePayload(org.apache.mina.common.ByteBuffer buffer) + { + writePayload(buffer.buf()); + } + public long getSize() { return 4 + 1 + 1 + 1 + 1; } - public void writePayload(DataOutputStream buffer) throws IOException + public void writePayload(ByteBuffer buffer) { - buffer.write(_protocolHeader); - buffer.write(_protocolClass); - buffer.write(_protocolInstance); - buffer.write(_protocolMajor); - buffer.write(_protocolMinor); + buffer.put(_protocolHeader); + buffer.put(_protocolClass); + buffer.put(_protocolInstance); + buffer.put(_protocolMajor); + buffer.put(_protocolMinor); } public boolean equals(Object o) @@ -143,9 +144,9 @@ public class ProtocolInitiation extends AMQDataBlock implements EncodableAMQData * @return true if we have enough data to decode the PI frame fully, false if more * data is required */ - public boolean decodable(DataInputStream in) throws IOException + public boolean decodable(ByteBuffer in) { - return (in.available() >= 8); + return (in.remaining() >= 8); } } diff --git a/java/common/src/main/java/org/apache/qpid/framing/SmallCompositeAMQDataBlock.java b/java/common/src/main/java/org/apache/qpid/framing/SmallCompositeAMQDataBlock.java index d2925d13a8..bd763599b0 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/SmallCompositeAMQDataBlock.java +++ b/java/common/src/main/java/org/apache/qpid/framing/SmallCompositeAMQDataBlock.java @@ -21,8 +21,7 @@ package org.apache.qpid.framing; -import java.io.DataOutputStream; -import java.io.IOException; +import org.apache.mina.common.ByteBuffer; public class SmallCompositeAMQDataBlock extends AMQDataBlock implements EncodableAMQDataBlock { @@ -69,7 +68,7 @@ public class SmallCompositeAMQDataBlock extends AMQDataBlock implements Encodabl return frameSize; } - public void writePayload(DataOutputStream buffer) throws IOException + public void writePayload(ByteBuffer buffer) { if (_firstFrame != null) { diff --git a/java/common/src/main/java/org/apache/qpid/framing/VersionSpecificRegistry.java b/java/common/src/main/java/org/apache/qpid/framing/VersionSpecificRegistry.java index ed9136f7c9..76c154581d 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/VersionSpecificRegistry.java +++ b/java/common/src/main/java/org/apache/qpid/framing/VersionSpecificRegistry.java @@ -20,8 +20,7 @@ */ package org.apache.qpid.framing; -import java.io.DataInputStream; -import java.io.IOException; +import org.apache.mina.common.ByteBuffer; import org.apache.qpid.framing.abstraction.ProtocolVersionMethodConverter; @@ -145,7 +144,7 @@ public class VersionSpecificRegistry } - public AMQMethodBody get(short classID, short methodID, DataInputStream in, long size) throws AMQFrameDecodingException, IOException + public AMQMethodBody get(short classID, short methodID, ByteBuffer in, long size) throws AMQFrameDecodingException { AMQMethodBodyInstanceFactory bodyFactory; try diff --git a/java/common/src/main/java/org/apache/qpid/framing/abstraction/ContentChunk.java b/java/common/src/main/java/org/apache/qpid/framing/abstraction/ContentChunk.java index 470b7b05e3..0695349f76 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/abstraction/ContentChunk.java +++ b/java/common/src/main/java/org/apache/qpid/framing/abstraction/ContentChunk.java @@ -21,10 +21,12 @@ package org.apache.qpid.framing.abstraction; +import org.apache.mina.common.ByteBuffer; + public interface ContentChunk { int getSize(); - byte[] getData(); + ByteBuffer getData(); void reduceToFit(); } diff --git a/java/common/src/main/java/org/apache/qpid/framing/abstraction/ProtocolVersionMethodConverter.java b/java/common/src/main/java/org/apache/qpid/framing/abstraction/ProtocolVersionMethodConverter.java index d1e53d6907..7544d9b7e7 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/abstraction/ProtocolVersionMethodConverter.java +++ b/java/common/src/main/java/org/apache/qpid/framing/abstraction/ProtocolVersionMethodConverter.java @@ -23,6 +23,8 @@ package org.apache.qpid.framing.abstraction; import org.apache.qpid.framing.AMQBody; +import java.nio.ByteBuffer; + public interface ProtocolVersionMethodConverter extends MessagePublishInfoConverter { AMQBody convertToBody(ContentChunk contentBody); @@ -30,5 +32,5 @@ public interface ProtocolVersionMethodConverter extends MessagePublishInfoConver void configure(); - AMQBody convertToBody(byte[] input); + AMQBody convertToBody(ByteBuffer buf); } diff --git a/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/MethodConverter_0_9.java b/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/MethodConverter_0_9.java index 90a730d6f7..1c4a29b106 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/MethodConverter_0_9.java +++ b/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/MethodConverter_0_9.java @@ -21,13 +21,16 @@ package org.apache.qpid.framing.amqp_0_9; +import org.apache.mina.common.ByteBuffer; + import org.apache.qpid.framing.abstraction.AbstractMethodConverter; import org.apache.qpid.framing.abstraction.ProtocolVersionMethodConverter; import org.apache.qpid.framing.abstraction.ContentChunk; import org.apache.qpid.framing.abstraction.MessagePublishInfo; import org.apache.qpid.framing.abstraction.MessagePublishInfoImpl; import org.apache.qpid.framing.*; - +import org.apache.qpid.framing.amqp_0_9.*; +import org.apache.qpid.framing.amqp_0_9.BasicPublishBodyImpl; public class MethodConverter_0_9 extends AbstractMethodConverter implements ProtocolVersionMethodConverter { @@ -69,9 +72,9 @@ public class MethodConverter_0_9 extends AbstractMethodConverter implements Prot } - public AMQBody convertToBody(byte[] data) + public AMQBody convertToBody(java.nio.ByteBuffer buf) { - return new ContentBody(data); + return new ContentBody(ByteBuffer.wrap(buf)); } public MessagePublishInfo convertToInfo(AMQMethodBody methodBody) @@ -113,9 +116,9 @@ public class MethodConverter_0_9 extends AbstractMethodConverter implements Prot return _contentBodyChunk.getSize(); } - public byte[] getData() + public ByteBuffer getData() { - return _contentBodyChunk._payload; + return _contentBodyChunk.payload; } public void reduceToFit() diff --git a/java/common/src/main/java/org/apache/qpid/framing/amqp_0_91/MethodConverter_0_91.java b/java/common/src/main/java/org/apache/qpid/framing/amqp_0_91/MethodConverter_0_91.java index 3b0cc3cebc..6e330574bc 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/amqp_0_91/MethodConverter_0_91.java +++ b/java/common/src/main/java/org/apache/qpid/framing/amqp_0_91/MethodConverter_0_91.java @@ -21,6 +21,8 @@ package org.apache.qpid.framing.amqp_0_91; +import org.apache.mina.common.ByteBuffer; + import org.apache.qpid.framing.abstraction.AbstractMethodConverter; import org.apache.qpid.framing.abstraction.ProtocolVersionMethodConverter; import org.apache.qpid.framing.abstraction.ContentChunk; @@ -68,9 +70,9 @@ public class MethodConverter_0_91 extends AbstractMethodConverter implements Pro } - public AMQBody convertToBody(byte[] data) + public AMQBody convertToBody(java.nio.ByteBuffer buf) { - return new ContentBody(data); + return new ContentBody(ByteBuffer.wrap(buf)); } public MessagePublishInfo convertToInfo(AMQMethodBody methodBody) @@ -112,9 +114,9 @@ public class MethodConverter_0_91 extends AbstractMethodConverter implements Pro return _contentBodyChunk.getSize(); } - public byte[] getData() + public ByteBuffer getData() { - return _contentBodyChunk._payload; + return _contentBodyChunk.payload; } public void reduceToFit() diff --git a/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/MethodConverter_8_0.java b/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/MethodConverter_8_0.java index e6d0482f0d..c87820b9b2 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/MethodConverter_8_0.java +++ b/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/MethodConverter_8_0.java @@ -26,8 +26,11 @@ import org.apache.qpid.framing.abstraction.ContentChunk; import org.apache.qpid.framing.abstraction.MessagePublishInfo; import org.apache.qpid.framing.abstraction.AbstractMethodConverter; import org.apache.qpid.framing.abstraction.MessagePublishInfoImpl; +import org.apache.qpid.framing.amqp_8_0.BasicPublishBodyImpl; import org.apache.qpid.framing.*; +import org.apache.mina.common.ByteBuffer; + public class MethodConverter_8_0 extends AbstractMethodConverter implements ProtocolVersionMethodConverter { private int _basicPublishClassId; @@ -57,9 +60,9 @@ public class MethodConverter_8_0 extends AbstractMethodConverter implements Prot return contentBodyChunk.getSize(); } - public byte[] getData() + public ByteBuffer getData() { - return contentBodyChunk._payload; + return contentBodyChunk.payload; } public void reduceToFit() @@ -78,9 +81,9 @@ public class MethodConverter_8_0 extends AbstractMethodConverter implements Prot } - public AMQBody convertToBody(byte[] data) + public AMQBody convertToBody(java.nio.ByteBuffer buf) { - return new ContentBody(data); + return new ContentBody(ByteBuffer.wrap(buf)); } public MessagePublishInfo convertToInfo(AMQMethodBody methodBody) diff --git a/java/common/src/main/java/org/apache/qpid/pool/Job.java b/java/common/src/main/java/org/apache/qpid/pool/Job.java new file mode 100644 index 0000000000..82b600de88 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/pool/Job.java @@ -0,0 +1,253 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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.qpid.pool; + +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A Job is a continuation that batches together other continuations, specifically {@link Event}s, into one continuation. + * The {@link Event}s themselves provide methods to process themselves, so processing a job simply consists of sequentially + * processing all of its aggregated events. + * + * The constructor accepts a maximum number of events for the job, and only runs up to that maximum number when + * processing the job, but the add method does not enforce this maximum. In other words, not all the enqueued events + * may be processed in each run of the job, several runs may be required to clear the queue. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Aggregate many coninuations together into a single continuation. + * <tr><td> Sequentially process aggregated continuations. <td> {@link Event} + * <tr><td> Provide running and completion status of the aggregate continuation. + * <tr><td> Execute a terminal continuation upon job completion. <td> {@link JobCompletionHandler} + * </table> + * + * @todo Could make Job implement Runnable, FutureTask, or a custom Continuation interface, to clarify its status as a + * continuation. Job is a continuation that aggregates other continuations and as such is a usefull re-usable + * piece of code. There may be other palces than the mina filter chain where continuation batching is used within + * qpid, so abstracting this out could provide a usefull building block. This also opens the way to different + * kinds of job with a common interface, e.g. parallel or sequential jobs etc. + * + * @todo For better re-usability could make the completion handler optional. Only run it when one is set. + */ +public class Job implements ReadWriteRunnable +{ + + /** Defines the maximum number of events that will be batched into a single job. */ + public static final int MAX_JOB_EVENTS = Integer.getInteger("amqj.server.read_write_pool.max_events", 10); + + /** The maximum number of events to process per run of the job. More events than this may be queued in the job. */ + private final int _maxEvents; + + /** Holds the queue of events that make up the job. */ + private final java.util.Queue<Runnable> _eventQueue = new ConcurrentLinkedQueue<Runnable>(); + + /** Holds a status flag, that indicates when the job is actively running. */ + private final AtomicBoolean _active = new AtomicBoolean(); + + private final boolean _readJob; + + private ReferenceCountingExecutorService _poolReference; + + private final static Logger _logger = LoggerFactory.getLogger(Job.class); + + public Job(ReferenceCountingExecutorService poolReference, int maxEvents, boolean readJob) + { + _poolReference = poolReference; + _maxEvents = maxEvents; + _readJob = readJob; + } + + /** + * Enqueus a continuation for sequential processing by this job. + * + * @param evt The continuation to enqueue. + */ + public void add(Runnable evt) + { + _eventQueue.add(evt); + } + + /** + * Sequentially processes, up to the maximum number per job, the aggregated continuations in enqueued in this job. + */ + boolean processAll() + { + // limit the number of events processed in one run + int i = _maxEvents; + while( --i != 0 ) + { + Runnable e = _eventQueue.poll(); + if (e == null) + { + return true; + } + else + { + e.run(); + } + } + return false; + } + + /** + * Tests if there are no more enqueued continuations to process. + * + * @return <tt>true</tt> if there are no enqueued continuations in this job, <tt>false</tt> otherwise. + */ + public boolean isComplete() + { + return _eventQueue.peek() == null; + } + + /** + * Marks this job as active if it is inactive. This method is thread safe. + * + * @return <tt>true</tt> if this job was inactive and has now been marked as active, <tt>false</tt> otherwise. + */ + public boolean activate() + { + return _active.compareAndSet(false, true); + } + + /** + * Marks this job as inactive. This method is thread safe. + */ + public void deactivate() + { + _active.set(false); + } + + /** + * Processes a batch of aggregated continuations, marks this job as inactive and call the terminal continuation. + */ + public void run() + { + if(processAll()) + { + deactivate(); + completed(); + } + else + { + notCompleted(); + } + } + + public boolean isRead() + { + return _readJob; + } + + /** + * Adds an {@link Event} to a {@link Job}, triggering the execution of the job if it is not already running. + * + * @param job The job. + * @param event The event to hand off asynchronously. + */ + public static void fireAsynchEvent(ExecutorService pool, Job job, Runnable event) + { + + job.add(event); + + + if(pool == null) + { + return; + } + + // rather than perform additional checks on pool to check that it hasn't shutdown. + // catch the RejectedExecutionException that will result from executing on a shutdown pool + if (job.activate()) + { + try + { + pool.execute(job); + } + catch(RejectedExecutionException e) + { + _logger.warn("Thread pool shutdown while tasks still outstanding"); + } + } + + } + + /** + * Implements a terminal continuation for the {@link Job} for this filter. Whenever the Job completes its processing + * of a batch of events this is called. This method simply re-activates the job, if it has more events to process. + * + * @param session The Mina session to work in. + * @param job The job that completed. + */ + public void completed() + { + if (!isComplete()) + { + final ExecutorService pool = _poolReference.getPool(); + + if(pool == null) + { + return; + } + + + // ritchiem : 2006-12-13 Do we need to perform the additional checks here? + // Can the pool be shutdown at this point? + if (activate()) + { + try + { + pool.execute(this); + } + catch(RejectedExecutionException e) + { + _logger.warn("Thread pool shutdown while tasks still outstanding"); + } + + } + } + } + + public void notCompleted() + { + final ExecutorService pool = _poolReference.getPool(); + + if(pool == null) + { + return; + } + + try + { + pool.execute(this); + } + catch(RejectedExecutionException e) + { + _logger.warn("Thread pool shutdown while tasks still outstanding"); + } + } + +} diff --git a/java/common/src/main/java/org/apache/qpid/protocol/AMQConstant.java b/java/common/src/main/java/org/apache/qpid/protocol/AMQConstant.java index 14d1befaf1..f0f2652ce3 100644 --- a/java/common/src/main/java/org/apache/qpid/protocol/AMQConstant.java +++ b/java/common/src/main/java/org/apache/qpid/protocol/AMQConstant.java @@ -80,7 +80,7 @@ public final class AMQConstant /** * An operator intervened to close the connection for some reason. The client may retry at some later date. */ - public static final AMQConstant CONNECTION_FORCED = new AMQConstant(320, "connection forced", true); + public static final AMQConstant CONTEXT_IN_USE = new AMQConstant(320, "context in use", true); /** The client tried to work with an unknown virtual host or cluster. */ public static final AMQConstant INVALID_PATH = new AMQConstant(402, "invalid path", true); @@ -104,7 +104,7 @@ public final class AMQConstant public static final AMQConstant REQUEST_TIMEOUT = new AMQConstant(408, "Request Timeout", true); - public static final AMQConstant ARGUMENT_INVALID = new AMQConstant(409, "argument invalid", true); + public static final AMQConstant INVALID_ARGUMENT = new AMQConstant(409, "argument invalid", true); /** * The client sent a malformed frame that the server could not decode. This strongly implies a programming error @@ -153,7 +153,10 @@ public final class AMQConstant public static final AMQConstant FRAME_MIN_SIZE = new AMQConstant(4096, "frame min size", true); - public static final AMQConstant INVALID_ARGUMENT = new AMQConstant(542, "invalid argument", true); + /** + * The server does not support the protocol version + */ + public static final AMQConstant UNSUPPORTED_BROKER_PROTOCOL_ERROR = new AMQConstant(542, "broker unsupported protocol", true); /** * The client imp does not support the protocol version */ diff --git a/java/common/src/main/java/org/apache/qpid/protocol/ProtocolEngine.java b/java/common/src/main/java/org/apache/qpid/protocol/ProtocolEngine.java index fd651a2b66..31953ea6ab 100644 --- a/java/common/src/main/java/org/apache/qpid/protocol/ProtocolEngine.java +++ b/java/common/src/main/java/org/apache/qpid/protocol/ProtocolEngine.java @@ -21,11 +21,10 @@ package org.apache.qpid.protocol; import java.net.SocketAddress; -import java.nio.ByteBuffer; +import org.apache.qpid.framing.AMQDataBlock; +import org.apache.qpid.transport.NetworkDriver; import org.apache.qpid.transport.Receiver; -import org.apache.qpid.transport.Sender; -import org.apache.qpid.transport.network.NetworkConnection; /** * A ProtocolEngine is a Receiver for java.nio.ByteBuffers. It takes the data passed to it in the received @@ -33,6 +32,9 @@ import org.apache.qpid.transport.network.NetworkConnection; */ public interface ProtocolEngine extends Receiver<java.nio.ByteBuffer> { + // Sets the network driver providing data for this ProtocolEngine + void setNetworkDriver (NetworkDriver driver); + // Returns the remote address of the NetworkDriver SocketAddress getRemoteAddress(); @@ -56,6 +58,4 @@ public interface ProtocolEngine extends Receiver<java.nio.ByteBuffer> void readerIdle(); - public void setNetworkConnection(NetworkConnection network, Sender<ByteBuffer> sender); - }
\ No newline at end of file diff --git a/java/common/src/main/java/org/apache/qpid/protocol/ProtocolEngineFactory.java b/java/common/src/main/java/org/apache/qpid/protocol/ProtocolEngineFactory.java index 7378edff0c..9df84eef90 100644 --- a/java/common/src/main/java/org/apache/qpid/protocol/ProtocolEngineFactory.java +++ b/java/common/src/main/java/org/apache/qpid/protocol/ProtocolEngineFactory.java @@ -20,12 +20,12 @@ */ package org.apache.qpid.protocol; -import org.apache.qpid.transport.network.NetworkConnection; +import org.apache.qpid.transport.NetworkDriver; public interface ProtocolEngineFactory { // Returns a new instance of a ProtocolEngine - ProtocolEngine newProtocolEngine(); + ProtocolEngine newProtocolEngine(NetworkDriver networkDriver); }
\ No newline at end of file diff --git a/java/common/src/main/java/org/apache/qpid/protocol/ServerProtocolEngine.java b/java/common/src/main/java/org/apache/qpid/security/AMQPCallbackHandler.java index e8362f79f0..a3dad9acdc 100644 --- a/java/common/src/main/java/org/apache/qpid/protocol/ServerProtocolEngine.java +++ b/java/common/src/main/java/org/apache/qpid/security/AMQPCallbackHandler.java @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -18,12 +18,11 @@ * under the License. * */ -package org.apache.qpid.protocol; +package org.apache.qpid.security; + +import javax.security.auth.callback.CallbackHandler; -public interface ServerProtocolEngine extends ProtocolEngine +public interface AMQPCallbackHandler extends CallbackHandler { - /** - * Gets the connection ID associated with this ProtocolEngine - */ - long getConnectionId(); + void initialise(String username,String password); } diff --git a/java/common/src/main/java/org/apache/qpid/security/UsernamePasswordCallbackHandler.java b/java/common/src/main/java/org/apache/qpid/security/UsernamePasswordCallbackHandler.java new file mode 100644 index 0000000000..89a63abeab --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/security/UsernamePasswordCallbackHandler.java @@ -0,0 +1,60 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.security; + +import java.io.IOException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; + +public class UsernamePasswordCallbackHandler implements AMQPCallbackHandler +{ + private String _username; + private String _password; + + public void initialise(String username,String password) + { + _username = username; + _password = password; + } + + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException + { + for (int i = 0; i < callbacks.length; i++) + { + Callback cb = callbacks[i]; + if (cb instanceof NameCallback) + { + ((NameCallback)cb).setName(_username); + } + else if (cb instanceof PasswordCallback) + { + ((PasswordCallback)cb).setPassword((_password).toCharArray()); + } + else + { + throw new UnsupportedCallbackException(cb); + } + } + } +} diff --git a/java/common/src/main/java/org/apache/qpid/ssl/SSLContextFactory.java b/java/common/src/main/java/org/apache/qpid/ssl/SSLContextFactory.java index 01f13408b0..702746b3da 100644 --- a/java/common/src/main/java/org/apache/qpid/ssl/SSLContextFactory.java +++ b/java/common/src/main/java/org/apache/qpid/ssl/SSLContextFactory.java @@ -20,17 +20,18 @@ */ package org.apache.qpid.ssl; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.security.GeneralSecurityException; import java.security.KeyStore; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; -import org.apache.qpid.transport.network.security.ssl.QpidClientX509KeyManager; import org.apache.qpid.transport.network.security.ssl.SSLUtil; /** @@ -38,92 +39,157 @@ import org.apache.qpid.transport.network.security.ssl.SSLUtil; * before this will work. * */ -public class SSLContextFactory -{ - public static final String JAVA_KEY_STORE_CODE = "JKS"; - public static final String TRANSPORT_LAYER_SECURITY_CODE = "TLS"; - public static final String KEY_STORE_CERTIFICATE_TYPE = "SunX509"; - - private SSLContextFactory() +public class SSLContextFactory { + + /** + * Path to the Java keystore file + */ + private String _keyStorePath; + + /** + * Password for the keystore + */ + private String _keyStorePassword; + + /** + * Cert type to use in keystore + */ + private String _keyStoreCertType; + + /** + * Path to the Java truststore file + */ + private String _trustStorePath; + + /** + * Password for the truststore + */ + private String _trustStorePassword; + + /** + * Cert type to use in truststore + */ + private String _trustStoreCertType; + + private KeyManager customKeyManager; + + public SSLContextFactory(String trustStorePath, String trustStorePassword, + String trustStoreCertType) { - //no instances + this(trustStorePath,trustStorePassword,trustStoreCertType, + trustStorePath,trustStorePassword,trustStoreCertType); } - public static SSLContext buildServerContext(final String keyStorePath, - final String keyStorePassword, final String keyStoreCertType) - throws GeneralSecurityException, IOException - { - return buildContext(null, null, null, keyStorePath, keyStorePassword, - keyStoreCertType, null); - } + /** + * Create a factory instance + * @param keystorePath path to the Java keystore file + * @param keystorePassword password for the Java keystore + * @param certType certificate type + */ + public SSLContextFactory(String trustStorePath, String trustStorePassword, String trustStoreCertType, + String keyStorePath, String keyStorePassword, String keyStoreCertType) + { - public static SSLContext buildClientContext(final String trustStorePath, - final String trustStorePassword, final String trustStoreCertType, - final String keyStorePath, final String keyStorePassword, - final String keyStoreCertType, final String certAlias) - throws GeneralSecurityException, IOException - { - return buildContext(trustStorePath, trustStorePassword, - trustStoreCertType, keyStorePath, keyStorePassword, - keyStoreCertType, certAlias); - } - - private static SSLContext buildContext(final String trustStorePath, - final String trustStorePassword, final String trustStoreCertType, - final String keyStorePath, final String keyStorePassword, - final String keyStoreCertType, final String certAlias) - throws GeneralSecurityException, IOException + _trustStorePath = trustStorePath; + _trustStorePassword = trustStorePassword; + + if (_trustStorePassword != null && _trustStorePassword.equals("none")) + { + _trustStorePassword = null; + } + _trustStoreCertType = trustStoreCertType; + + _keyStorePath = keyStorePath; + _keyStorePassword = keyStorePassword; + + if (_keyStorePassword != null && _keyStorePassword.equals("none")) + { + _keyStorePassword = null; + } + _keyStoreCertType = keyStoreCertType; + + if (_trustStorePath == null) { + throw new IllegalArgumentException("A TrustStore path or KeyStore path must be specified"); + } + if (_trustStoreCertType == null) { + throw new IllegalArgumentException("Cert type must be specified"); + } + } + + public SSLContextFactory(String trustStorePath, String trustStorePassword, String trustStoreCertType, + KeyManager customKeyManager) { - // Initialize the SSLContext to work with our key managers. - final SSLContext sslContext = SSLContext - .getInstance(TRANSPORT_LAYER_SECURITY_CODE); - final TrustManager[] trustManagers; - final KeyManager[] keyManagers; - - if (trustStorePath != null) + _trustStorePath = trustStorePath; + _trustStorePassword = trustStorePassword; + + if (_trustStorePassword != null && _trustStorePassword.equals("none")) { - final KeyStore ts = SSLUtil.getInitializedKeyStore(trustStorePath, - trustStorePassword); - final TrustManagerFactory tmf = TrustManagerFactory - .getInstance(trustStoreCertType); - tmf.init(ts); - - trustManagers = tmf.getTrustManagers(); + _trustStorePassword = null; } - else - { - trustManagers = null; + _trustStoreCertType = trustStoreCertType; + + if (_trustStorePath == null) { + throw new IllegalArgumentException("A TrustStore path or KeyStore path must be specified"); + } + if (_trustStoreCertType == null) { + throw new IllegalArgumentException("Cert type must be specified"); } + + this.customKeyManager = customKeyManager; + } + + + /** + * Builds a SSLContext appropriate for use with a server + * @return SSLContext + * @throws GeneralSecurityException + * @throws IOException + */ - if (keyStorePath != null) + public SSLContext buildServerContext() throws GeneralSecurityException, IOException + { + KeyStore ts = SSLUtil.getInitializedKeyStore(_trustStorePath,_trustStorePassword); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(_trustStoreCertType); + tmf.init(ts); + + // Initialize the SSLContext to work with our key managers. + SSLContext sslContext = SSLContext.getInstance("TLS"); + + if (customKeyManager != null) { - if (certAlias != null) - { - keyManagers = new KeyManager[] { new QpidClientX509KeyManager( - certAlias, keyStorePath, keyStorePassword, - keyStoreCertType) }; - } - else - { - final KeyStore ks = SSLUtil.getInitializedKeyStore( - keyStorePath, keyStorePassword); - - char[] keyStoreCharPassword = keyStorePassword == null ? null : keyStorePassword.toCharArray(); - // Set up key manager factory to use our key store - final KeyManagerFactory kmf = KeyManagerFactory - .getInstance(keyStoreCertType); - kmf.init(ks, keyStoreCharPassword); - keyManagers = kmf.getKeyManagers(); - } + sslContext.init(new KeyManager[]{customKeyManager}, + tmf.getTrustManagers(), null); + } else { - keyManagers = null; - } + // Create keystore + KeyStore ks = SSLUtil.getInitializedKeyStore(_keyStorePath,_keyStorePassword); + // Set up key manager factory to use our key store + KeyManagerFactory kmf = KeyManagerFactory.getInstance(_keyStoreCertType); + kmf.init(ks, _keyStorePassword.toCharArray()); - sslContext.init(keyManagers, trustManagers, null); - - return sslContext; - } + sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + } + + return sslContext; + } + + /** + * Creates a SSLContext factory appropriate for use with a client + * @return SSLContext + * @throws GeneralSecurityException + * @throws IOException + */ + public SSLContext buildClientContext() throws GeneralSecurityException, IOException + { + KeyStore ks = SSLUtil.getInitializedKeyStore(_trustStorePath,_trustStorePassword); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(_trustStoreCertType); + tmf.init(ks); + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, tmf.getTrustManagers(), null); + return context; + } + } diff --git a/java/common/src/main/java/org/apache/qpid/thread/QpidThreadExecutor.java b/java/common/src/main/java/org/apache/qpid/thread/QpidThreadExecutor.java index 30010a2d89..38f60c04fe 100644 --- a/java/common/src/main/java/org/apache/qpid/thread/QpidThreadExecutor.java +++ b/java/common/src/main/java/org/apache/qpid/thread/QpidThreadExecutor.java @@ -23,7 +23,7 @@ package org.apache.qpid.thread; import org.apache.qpid.thread.Threading; -import java.util.concurrent.Executor; +import edu.emory.mathcs.backport.java.util.concurrent.Executor; public class QpidThreadExecutor implements Executor { diff --git a/java/common/src/main/java/org/apache/qpid/transport/ClientDelegate.java b/java/common/src/main/java/org/apache/qpid/transport/ClientDelegate.java index 9bdad6b00e..0d9f8c0b28 100644 --- a/java/common/src/main/java/org/apache/qpid/transport/ClientDelegate.java +++ b/java/common/src/main/java/org/apache/qpid/transport/ClientDelegate.java @@ -20,20 +20,28 @@ */ package org.apache.qpid.transport; +import org.ietf.jgss.GSSContext; +import org.ietf.jgss.GSSException; +import org.ietf.jgss.GSSManager; +import org.ietf.jgss.GSSName; +import org.ietf.jgss.Oid; + +import org.apache.qpid.security.UsernamePasswordCallbackHandler; import static org.apache.qpid.transport.Connection.State.OPEN; import static org.apache.qpid.transport.Connection.State.RESUMING; +import org.apache.qpid.transport.util.Logger; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslClient; +import javax.security.sasl.SaslException; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.security.sasl.SaslClient; -import javax.security.sasl.SaslException; - -import org.apache.qpid.transport.util.Logger; - /** * ClientDelegate @@ -44,13 +52,31 @@ public class ClientDelegate extends ConnectionDelegate { private static final Logger log = Logger.get(ClientDelegate.class); + private static final String KRB5_OID_STR = "1.2.840.113554.1.2.2"; + protected static final Oid KRB5_OID; + static + { + Oid oid; + try + { + oid = new Oid(KRB5_OID_STR); + } + catch (GSSException ignore) + { + oid = null; + } - protected final ConnectionSettings _conSettings; + KRB5_OID = oid; + } + + private List<String> clientMechs; + private ConnectionSettings conSettings; public ClientDelegate(ConnectionSettings settings) { - this._conSettings = settings; + this.conSettings = settings; + this.clientMechs = Arrays.asList(settings.getSaslMechs().split(" ")); } public void init(Connection conn, ProtocolHeader hdr) @@ -66,9 +92,9 @@ public class ClientDelegate extends ConnectionDelegate { Map<String,Object> clientProperties = new HashMap<String,Object>(); - if(this._conSettings.getClientProperties() != null) + if(this.conSettings.getClientProperties() != null) { - clientProperties.putAll(_conSettings.getClientProperties()); + clientProperties.putAll(this.conSettings.getClientProperties()); } clientProperties.put("qpid.session_flow", 1); @@ -83,12 +109,41 @@ public class ClientDelegate extends ConnectionDelegate (clientProperties, null, null, conn.getLocale()); return; } + + List<String> choosenMechs = new ArrayList<String>(); + for (String mech:clientMechs) + { + if (brokerMechs.contains(mech)) + { + choosenMechs.add(mech); + } + } + + if (choosenMechs.size() == 0) + { + conn.exception(new ConnectionException("The following SASL mechanisms " + + clientMechs.toString() + + " specified by the client are not supported by the broker")); + return; + } + + String[] mechs = new String[choosenMechs.size()]; + choosenMechs.toArray(mechs); + conn.setServerProperties(start.getServerProperties()); try { - final SaslClient sc = createSaslClient(brokerMechs); - + Map<String,Object> saslProps = new HashMap<String,Object>(); + if (conSettings.isUseSASLEncryption()) + { + saslProps.put(Sasl.QOP, "auth-conf"); + } + UsernamePasswordCallbackHandler handler = + new UsernamePasswordCallbackHandler(); + handler.initialise(conSettings.getUsername(), conSettings.getPassword()); + SaslClient sc = Sasl.createSaslClient + (mechs, null, conSettings.getSaslProtocol(), conSettings.getSaslServerName(), saslProps, handler); conn.setSaslClient(sc); byte[] response = sc.hasInitialResponse() ? @@ -97,22 +152,12 @@ public class ClientDelegate extends ConnectionDelegate (clientProperties, sc.getMechanismName(), response, conn.getLocale()); } - catch (ConnectionException ce) - { - conn.exception(ce); - } catch (SaslException e) { conn.exception(e); } } - - protected SaslClient createSaslClient(List<Object> brokerMechs) throws ConnectionException, SaslException - { - throw new UnsupportedOperationException(); - } - @Override public void connectionSecure(Connection conn, ConnectionSecure secure) { @@ -131,7 +176,7 @@ public class ClientDelegate extends ConnectionDelegate @Override public void connectionTune(Connection conn, ConnectionTune tune) { - int hb_interval = calculateHeartbeatInterval(_conSettings.getHeartbeatInterval(), + int hb_interval = calculateHeartbeatInterval(conSettings.getHeartbeatInterval(), tune.getHeartbeatMin(), tune.getHeartbeatMax() ); @@ -146,12 +191,32 @@ public class ClientDelegate extends ConnectionDelegate //(or that forced by protocol limitations [0xFFFF]) conn.setChannelMax(channelMax == 0 ? Connection.MAX_CHANNEL_MAX : channelMax); - conn.connectionOpen(_conSettings.getVhost(), null, Option.INSIST); + conn.connectionOpen(conSettings.getVhost(), null, Option.INSIST); } @Override public void connectionOpenOk(Connection conn, ConnectionOpenOk ok) { + SaslClient sc = conn.getSaslClient(); + if (sc != null) + { + if (sc.getMechanismName().equals("GSSAPI")) + { + String id = getKerberosUser(); + if (id != null) + { + conn.setUserID(id); + } + } + else if (sc.getMechanismName().equals("EXTERNAL")) + { + if (conn.getSecurityLayer() != null) + { + conn.setUserID(conn.getSecurityLayer().getUserID()); + } + } + } + if (conn.isConnectionResuming()) { conn.setState(RESUMING); @@ -182,7 +247,7 @@ public class ClientDelegate extends ConnectionDelegate int i = heartbeat; if (i == 0) { - log.info("Idle timeout is 0 sec. Heartbeats are disabled."); + log.warn("Idle timeout is zero. Heartbeats are disabled"); return 0; // heartbeats are disabled. } else if (i >= min && i <= max) @@ -191,8 +256,8 @@ public class ClientDelegate extends ConnectionDelegate } else { - log.info("The broker does not support the configured connection idle timeout of %s sec," + - " using the brokers max supported value of %s sec instead.", i,max); + log.warn("Ignoring the idle timeout %s set by the connection," + + " using the brokers max value %s", i,max); return max; } } @@ -221,7 +286,35 @@ public class ClientDelegate extends ConnectionDelegate } + private String getKerberosUser() + { + log.debug("Obtaining userID from kerberos"); + String service = conSettings.getSaslProtocol() + "@" + conSettings.getSaslServerName(); + GSSManager manager = GSSManager.getInstance(); + + try + { + GSSName acceptorName = manager.createName(service, + GSSName.NT_HOSTBASED_SERVICE, KRB5_OID); + + GSSContext secCtx = manager.createContext(acceptorName, + KRB5_OID, + null, + GSSContext.INDEFINITE_LIFETIME); + secCtx.initSecContext(new byte[0], 0, 1); + if (secCtx.getSrcName() != null) + { + return secCtx.getSrcName().toString(); + } + } + catch (GSSException e) + { + log.warn("Unable to retrieve userID from Kerberos due to error",e); + } + + return null; + } } diff --git a/java/common/src/main/java/org/apache/qpid/transport/Connection.java b/java/common/src/main/java/org/apache/qpid/transport/Connection.java index 1c521244d0..e5e10c0e07 100644 --- a/java/common/src/main/java/org/apache/qpid/transport/Connection.java +++ b/java/common/src/main/java/org/apache/qpid/transport/Connection.java @@ -25,29 +25,21 @@ import static org.apache.qpid.transport.Connection.State.CLOSING; import static org.apache.qpid.transport.Connection.State.NEW; import static org.apache.qpid.transport.Connection.State.OPEN; import static org.apache.qpid.transport.Connection.State.OPENING; +import static org.apache.qpid.transport.Connection.State.RESUMING; -import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; import javax.security.sasl.SaslClient; import javax.security.sasl.SaslServer; -import org.apache.qpid.framing.ProtocolVersion; -import org.apache.qpid.transport.network.Assembler; -import org.apache.qpid.transport.network.Disassembler; -import org.apache.qpid.transport.network.InputHandler; -import org.apache.qpid.transport.network.NetworkConnection; -import org.apache.qpid.transport.network.OutgoingNetworkTransport; -import org.apache.qpid.transport.network.Transport; import org.apache.qpid.transport.network.security.SecurityLayer; -import org.apache.qpid.transport.network.security.SecurityLayerFactory; import org.apache.qpid.transport.util.Logger; import org.apache.qpid.transport.util.Waiter; import org.apache.qpid.util.Strings; @@ -73,7 +65,6 @@ public class Connection extends ConnectionInvoker public static final int MAX_CHANNEL_MAX = 0xFFFF; public static final int MIN_USABLE_CHANNEL_NUM = 0; - public enum State { NEW, CLOSED, OPENING, OPEN, CLOSING, CLOSE_RCVD, RESUMING } static class DefaultConnectionListener implements ConnectionListener @@ -121,14 +112,17 @@ public class Connection extends ConnectionInvoker private SaslServer saslServer; private SaslClient saslClient; private int idleTimeout = 0; + private String _authorizationID; private Map<String,Object> _serverProperties; private String userID; private ConnectionSettings conSettings; private SecurityLayer securityLayer; private String _clientId; - + + private static final AtomicLong idGenerator = new AtomicLong(0); + private final long _connectionId = idGenerator.incrementAndGet(); private final AtomicBoolean connectionLost = new AtomicBoolean(false); - + public Connection() {} public void setConnectionDelegate(ConnectionDelegate delegate) @@ -239,24 +233,14 @@ public class Connection extends ConnectionInvoker conSettings = settings; state = OPENING; userID = settings.getUsername(); - - securityLayer = SecurityLayerFactory.newInstance(getConnectionSettings()); - - OutgoingNetworkTransport transport = Transport.getOutgoingTransportInstance(ProtocolVersion.v0_10); - Receiver<ByteBuffer> secureReceiver = securityLayer.receiver(new InputHandler(new Assembler(this))); - if(secureReceiver instanceof ConnectionListener) - { - addConnectionListener((ConnectionListener)secureReceiver); - } - - NetworkConnection network = transport.connect(settings, secureReceiver, null); - final Sender<ByteBuffer> secureSender = securityLayer.sender(network.getSender()); - if(secureSender instanceof ConnectionListener) - { - addConnectionListener((ConnectionListener)secureSender); - } - sender = new Disassembler(secureSender, settings.getMaxFrameSize()); - + delegate = new ClientDelegate(settings); + + TransportBuilder transport = new TransportBuilder(); + transport.init(this); + this.sender = transport.buildSenderPipe(); + transport.buildReceiverPipe(this); + this.securityLayer = transport.getSecurityLayer(); + send(new ProtocolHeader(1, 0, 10)); Waiter w = new Waiter(lock, timeout); @@ -337,31 +321,23 @@ public class Connection extends ConnectionInvoker Waiter w = new Waiter(lock, timeout); while (w.hasTime() && state != OPEN && error == null) { - w.await(); + w.await(); } - + if (state != OPEN) { throw new ConnectionException("Timed out waiting for connection to be ready. Current state is :" + state); } - + Session ssn = _sessionFactory.newSession(this, name, expiry); - registerSession(ssn); + sessions.put(name, ssn); map(ssn); ssn.attach(); return ssn; } } - public void registerSession(Session ssn) - { - synchronized (lock) - { - sessions.put(ssn.getName(),ssn); - } - } - - public void removeSession(Session ssn) + void removeSession(Session ssn) { synchronized (lock) { @@ -376,6 +352,11 @@ public class Connection extends ConnectionInvoker _sessionFactory = sessionFactory; } + public long getConnectionId() + { + return _connectionId; + } + public ConnectionDelegate getConnectionDelegate() { return delegate; @@ -424,7 +405,7 @@ public class Connection extends ConnectionInvoker else { throw new ProtocolViolationException( - "Received frames for an already detached session", null); + "Received frames for an already dettached session", null); } } @@ -473,7 +454,7 @@ public class Connection extends ConnectionInvoker } } - public Session getSession(int channel) + protected Session getSession(int channel) { synchronized (lock) { @@ -487,10 +468,18 @@ public class Connection extends ConnectionInvoker { for (Session ssn : sessions.values()) { - map(ssn); - ssn.resume(); + if (ssn.isTransacted()) + { + removeSession(ssn); + ssn.setState(Session.State.CLOSED); + } + else + { + map(ssn); + ssn.attach(); + ssn.resume(); + } } - setState(OPEN); } } @@ -577,12 +566,12 @@ public class Connection extends ConnectionInvoker { close(ConnectionCloseCode.NORMAL, null); } - + public void mgmtClose() { close(ConnectionCloseCode.CONNECTION_FORCED, "The connection was closed using the broker's management interface."); } - + public void close(ConnectionCloseCode replyCode, String replyText, Option ... _options) { synchronized (lock) @@ -656,6 +645,16 @@ public class Connection extends ConnectionInvoker return idleTimeout; } + public void setAuthorizationID(String authorizationID) + { + _authorizationID = authorizationID; + } + + public String getAuthorizationID() + { + return _authorizationID; + } + public String getUserID() { return userID; @@ -685,24 +684,15 @@ public class Connection extends ConnectionInvoker { return conSettings; } - + public SecurityLayer getSecurityLayer() { return securityLayer; } - + public boolean isConnectionResuming() { return connectionLost.get(); } - protected Collection<Session> getChannels() - { - return channels.values(); - } - - public boolean hasSessionWithName(final String name) - { - return sessions.containsKey(new Binary(name.getBytes())); - } } diff --git a/java/common/src/main/java/org/apache/qpid/transport/ConnectionDelegate.java b/java/common/src/main/java/org/apache/qpid/transport/ConnectionDelegate.java index 393301659d..88dd2d6afa 100644 --- a/java/common/src/main/java/org/apache/qpid/transport/ConnectionDelegate.java +++ b/java/common/src/main/java/org/apache/qpid/transport/ConnectionDelegate.java @@ -85,7 +85,7 @@ public abstract class ConnectionDelegate @Override public void sessionDetach(Connection conn, SessionDetach dtc) { Session ssn = conn.getSession(dtc.getChannel()); - ssn.sessionDetached(dtc.getName(), ssn.getDetachCode() == null? SessionDetachCode.NORMAL: ssn.getDetachCode()); + ssn.sessionDetached(dtc.getName(), SessionDetachCode.NORMAL); conn.unmap(ssn); ssn.closed(); } @@ -95,7 +95,6 @@ public abstract class ConnectionDelegate Session ssn = conn.getSession(dtc.getChannel()); if (ssn != null) { - ssn.setDetachCode(dtc.getCode()); conn.unmap(ssn); ssn.closed(); } diff --git a/java/common/src/main/java/org/apache/qpid/transport/ConnectionSettings.java b/java/common/src/main/java/org/apache/qpid/transport/ConnectionSettings.java index 37a8e594c0..08678b213b 100644 --- a/java/common/src/main/java/org/apache/qpid/transport/ConnectionSettings.java +++ b/java/common/src/main/java/org/apache/qpid/transport/ConnectionSettings.java @@ -30,8 +30,6 @@ import java.util.Map; */ public class ConnectionSettings { - public static final String WILDCARD_ADDRESS = "*"; - String protocol = "tcp"; String host = "localhost"; String vhost; @@ -58,7 +56,7 @@ public class ConnectionSettings boolean verifyHostname; // SASL props - String saslMechs = System.getProperty("qpid.sasl_mechs", null); + String saslMechs = System.getProperty("qpid.sasl_mechs", "PLAIN"); String saslProtocol = System.getProperty("qpid.sasl_protocol", "AMQP"); String saslServerName = System.getProperty("qpid.sasl_server_name", "localhost"); boolean useSASLEncryption; diff --git a/java/common/src/main/java/org/apache/qpid/transport/NetworkDriver.java b/java/common/src/main/java/org/apache/qpid/transport/NetworkDriver.java new file mode 100644 index 0000000000..86af97bf7e --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/transport/NetworkDriver.java @@ -0,0 +1,63 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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.qpid.transport; + +import java.net.BindException; +import java.net.InetAddress; +import java.net.SocketAddress; + +import org.apache.qpid.protocol.ProtocolEngine; +import org.apache.qpid.protocol.ProtocolEngineFactory; +import org.apache.qpid.ssl.SSLContextFactory; + +public interface NetworkDriver extends Sender<java.nio.ByteBuffer> +{ + // Creates a NetworkDriver which attempts to connect to destination on port and attaches the ProtocolEngine to + // it using the SSLContextFactory if provided + void open(int port, InetAddress destination, ProtocolEngine engine, + NetworkDriverConfiguration config, SSLContextFactory sslFactory) + throws OpenException; + + // listens for incoming connections on the specified ports and address and creates a new NetworkDriver which + // processes incoming connections with ProtocolEngines and SSLEngines created from the factories + // (in the case of an SSLContextFactory, if provided) + void bind (int port, InetAddress[] addresses, ProtocolEngineFactory protocolFactory, + NetworkDriverConfiguration config, SSLContextFactory sslFactory) throws BindException; + + // Returns the remote address of the underlying socket + SocketAddress getRemoteAddress(); + + // Returns the local address of the underlying socket + SocketAddress getLocalAddress(); + + /** + * The length of time after which the ProtocolEngines readIdle() method should be called if no data has been + * read in seconds + */ + void setMaxReadIdle(int idleTime); + + /** + * The length of time after which the ProtocolEngines writeIdle() method should be called if no data has been + * written in seconds + */ + void setMaxWriteIdle(int idleTime); + +}
\ No newline at end of file diff --git a/java/common/src/main/java/org/apache/qpid/transport/NetworkTransportConfiguration.java b/java/common/src/main/java/org/apache/qpid/transport/NetworkDriverConfiguration.java index 8d3f7a779a..c38afe5dd5 100644 --- a/java/common/src/main/java/org/apache/qpid/transport/NetworkTransportConfiguration.java +++ b/java/common/src/main/java/org/apache/qpid/transport/NetworkDriverConfiguration.java @@ -25,22 +25,20 @@ package org.apache.qpid.transport; * buffer sizes and set particular options on the socket. NetworkDrivers should honour the values returned * from here if the underlying implementation supports them. */ -public interface NetworkTransportConfiguration +public interface NetworkDriverConfiguration { // Taken from Socket + Boolean getKeepAlive(); + Boolean getOOBInline(); + Boolean getReuseAddress(); + Integer getSoLinger(); // null means off + Integer getSoTimeout(); Boolean getTcpNoDelay(); + Integer getTrafficClass(); // The amount of memory in bytes to allocate to the incoming buffer Integer getReceiveBufferSize(); // The amount of memory in bytes to allocate to the outgoing buffer - Integer getSendBufferSize(); - - Integer getPort(); - - String getHost(); - - String getTransport(); - - Integer getConnectorProcessors(); -} + Integer getSendBufferSize(); +} diff --git a/java/common/src/main/java/org/apache/qpid/transport/ServerDelegate.java b/java/common/src/main/java/org/apache/qpid/transport/ServerDelegate.java index 82fa6ca473..f21df251da 100644 --- a/java/common/src/main/java/org/apache/qpid/transport/ServerDelegate.java +++ b/java/common/src/main/java/org/apache/qpid/transport/ServerDelegate.java @@ -75,7 +75,10 @@ public class ServerDelegate extends ConnectionDelegate if (mechanism == null || mechanism.length() == 0) { - tuneAuthorizedConnection(conn); + conn.connectionTune + (getChannelMax(), + org.apache.qpid.transport.network.ConnectionBinding.MAX_FRAME_SIZE, + 0, getHeartbeatMax()); return; } @@ -94,7 +97,8 @@ public class ServerDelegate extends ConnectionDelegate } catch (SaslException e) { - connectionAuthFailed(conn, e); + conn.exception(e); + conn.connectionClose(ConnectionCloseCode.CONNECTION_FORCED, e.getMessage()); } } @@ -105,52 +109,33 @@ public class ServerDelegate extends ConnectionDelegate return ss; } - protected void secure(final SaslServer ss, final Connection conn, final byte[] response) + private void secure(Connection conn, byte[] response) { + SaslServer ss = conn.getSaslServer(); try { byte[] challenge = ss.evaluateResponse(response); if (ss.isComplete()) { ss.dispose(); - tuneAuthorizedConnection(conn); + conn.connectionTune + (getChannelMax(), + org.apache.qpid.transport.network.ConnectionBinding.MAX_FRAME_SIZE, + 0, getHeartbeatMax()); + conn.setAuthorizationID(ss.getAuthorizationID()); } else { - connectionAuthContinue(conn, challenge); + conn.connectionSecure(challenge); } } catch (SaslException e) { - connectionAuthFailed(conn, e); + conn.exception(e); + conn.connectionClose(ConnectionCloseCode.CONNECTION_FORCED, e.getMessage()); } } - protected void connectionAuthFailed(final Connection conn, Exception e) - { - conn.exception(e); - conn.connectionClose(ConnectionCloseCode.CONNECTION_FORCED, e.getMessage()); - } - - protected void connectionAuthContinue(final Connection conn, byte[] challenge) - { - conn.connectionSecure(challenge); - } - - protected void tuneAuthorizedConnection(final Connection conn) - { - conn.connectionTune - (getChannelMax(), - org.apache.qpid.transport.network.ConnectionBinding.MAX_FRAME_SIZE, - 0, getHeartbeatMax()); - } - - protected void secure(final Connection conn, final byte[] response) - { - final SaslServer ss = conn.getSaslServer(); - secure(ss, conn, response); - } - protected int getHeartbeatMax() { return 0xFFFF; @@ -170,7 +155,22 @@ public class ServerDelegate extends ConnectionDelegate @Override public void connectionTuneOk(Connection conn, ConnectionTuneOk ok) { + int okChannelMax = ok.getChannelMax(); + + if (okChannelMax > getChannelMax()) + { + _logger.error("Connection '" + conn.getConnectionId() + "' being severed, " + + "client connectionTuneOk returned a channelMax (" + okChannelMax + + ") above the servers offered limit (" + getChannelMax() +")"); + //Due to the error we must forcefully close the connection without negotiation + conn.getSender().close(); + return; + } + + //0 means no implied limit, except available server resources + //(or that forced by protocol limitations [0xFFFF]) + conn.setChannelMax(okChannelMax == 0 ? Connection.MAX_CHANNEL_MAX : okChannelMax); } @Override @@ -200,11 +200,4 @@ public class ServerDelegate extends ConnectionDelegate ssn.sessionAttached(atc.getName()); ssn.setState(Session.State.OPEN); } - - protected void setConnectionTuneOkChannelMax(final Connection conn, final int okChannelMax) - { - //0 means no implied limit, except available server resources - //(or that forced by protocol limitations [0xFFFF]) - conn.setChannelMax(okChannelMax == 0 ? Connection.MAX_CHANNEL_MAX : okChannelMax); - } } diff --git a/java/common/src/main/java/org/apache/qpid/transport/Session.java b/java/common/src/main/java/org/apache/qpid/transport/Session.java index 0de558d152..214d4534c1 100644 --- a/java/common/src/main/java/org/apache/qpid/transport/Session.java +++ b/java/common/src/main/java/org/apache/qpid/transport/Session.java @@ -30,8 +30,6 @@ import static org.apache.qpid.transport.Session.State.DETACHED; import static org.apache.qpid.transport.Session.State.NEW; import static org.apache.qpid.transport.Session.State.OPEN; import static org.apache.qpid.transport.Session.State.RESUMING; - -import org.apache.qpid.configuration.ClientProperties; import org.apache.qpid.transport.network.Frame; import static org.apache.qpid.transport.util.Functions.mod; import org.apache.qpid.transport.util.Logger; @@ -44,9 +42,7 @@ import static org.apache.qpid.util.Serial.max; import static org.apache.qpid.util.Strings.toUTF8; import java.nio.ByteBuffer; -import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; @@ -59,6 +55,7 @@ import java.util.concurrent.TimeUnit; public class Session extends SessionInvoker { + private static final Logger log = Logger.get(Session.class); public enum State { NEW, DETACHED, RESUMING, OPEN, CLOSING, CLOSED } @@ -92,9 +89,7 @@ public class Session extends SessionInvoker private int channel; private SessionDelegate delegate; private SessionListener listener = new DefaultSessionListener(); - private final long timeout = Long.getLong(ClientProperties.QPID_SYNC_OP_TIMEOUT, - Long.getLong(ClientProperties.AMQJ_DEFAULT_SYNCWRITE_TIMEOUT, - ClientProperties.DEFAULT_SYNC_OPERATION_TIMEOUT)); + private long timeout = 60000; private boolean autoSync = false; private boolean incomingInit; @@ -122,9 +117,7 @@ public class Session extends SessionInvoker private Thread resumer = null; private boolean transacted = false; - private SessionDetachCode detachCode; - private final Object stateLock = new Object(); - + protected Session(Connection connection, Binary name, long expiry) { this(connection, new SessionDelegate(), name, expiry); @@ -259,8 +252,6 @@ public class Session extends SessionInvoker { synchronized (commands) { - attach(); - for (int i = maxComplete + 1; lt(i, commandsOut); i++) { Method m = commands[mod(i, commands.length)]; @@ -271,48 +262,16 @@ public class Session extends SessionInvoker } else if (m instanceof MessageTransfer) { - MessageTransfer xfr = (MessageTransfer)m; - - if (xfr.getHeader() != null) - { - if (xfr.getHeader().get(DeliveryProperties.class) != null) - { - xfr.getHeader().get(DeliveryProperties.class).setRedelivered(true); - } - else - { - Struct[] structs = xfr.getHeader().getStructs(); - DeliveryProperties deliveryProps = new DeliveryProperties(); - deliveryProps.setRedelivered(true); - - List<Struct> list = Arrays.asList(structs); - list.add(deliveryProps); - xfr.setHeader(new Header(list)); - } - - } - else - { - DeliveryProperties deliveryProps = new DeliveryProperties(); - deliveryProps.setRedelivered(true); - xfr.setHeader(new Header(deliveryProps)); - } + ((MessageTransfer)m).getHeader().get(DeliveryProperties.class).setRedelivered(true); } sessionCommandPoint(m.getId(), 0); send(m); } - + sessionCommandPoint(commandsOut, 0); - sessionFlush(COMPLETED); resumer = Thread.currentThread(); state = RESUMING; - - if(isTransacted()) - { - txSelect(); - } - listener.resumed(this); resumer = null; } @@ -463,10 +422,7 @@ public class Session extends SessionInvoker { return; } - if (copy.size() > 0) - { - sessionCompleted(copy, options); - } + sessionCompleted(copy, options); } } @@ -576,6 +532,17 @@ public class Session extends SessionInvoker { if (m.getEncodedTrack() == Frame.L4) { + + if (state == DETACHED && transacted) + { + state = CLOSED; + delegate.closed(this); + connection.removeSession(this); + throw new SessionException( + "Session failed over, possibly in the middle of a transaction. " + + "Closing the session. Any Transaction in progress will be rolledback."); + } + if (m.hasPayload()) { acquireCredit(); @@ -583,30 +550,24 @@ public class Session extends SessionInvoker synchronized (commands) { - //allow the txSelect operation to be invoked during resume - boolean skipWait = m instanceof TxSelect && state == RESUMING; - - if(!skipWait) + if (state == DETACHED && m.isUnreliable()) { - if (state == DETACHED && m.isUnreliable()) + Thread current = Thread.currentThread(); + if (!current.equals(resumer)) { - Thread current = Thread.currentThread(); - if (!current.equals(resumer)) - { - return; - } + return; } + } - if (state != OPEN && state != CLOSED && state != CLOSING) + if (state != OPEN && state != CLOSED && state != CLOSING) + { + Thread current = Thread.currentThread(); + if (!current.equals(resumer)) { - Thread current = Thread.currentThread(); - if (!current.equals(resumer)) + Waiter w = new Waiter(commands, timeout); + while (w.hasTime() && (state != OPEN && state != CLOSED)) { - Waiter w = new Waiter(commands, timeout); - while (w.hasTime() && (state != OPEN && state != CLOSED)) - { - w.await(); - } + w.await(); } } } @@ -700,12 +661,7 @@ public class Session extends SessionInvoker { sessionCommandPoint(0, 0); } - - boolean replayTransfer = !closing && !transacted && - m instanceof MessageTransfer && - ! m.isUnreliable(); - - if ((replayTransfer) || m.hasCompletionListener()) + if ((!closing && !transacted && m instanceof MessageTransfer) || m.hasCompletionListener()) { commands[mod(next, commands.length)] = m; commandBytes += m.getBodySize(); @@ -970,29 +926,16 @@ public class Session extends SessionInvoker public void close() { - if (log.isDebugEnabled()) - { - log.debug("Closing [%s] in state [%s]", this, state); - } synchronized (commands) { - switch(state) - { - case DETACHED: - state = CLOSED; - delegate.closed(this); - connection.removeSession(this); - listener.closed(this); - break; - case CLOSED: - break; - default: - state = CLOSING; - setClose(true); - sessionRequestTimeout(0); - sessionDetach(name.getBytes()); - awaitClose(); - } + state = CLOSING; + setClose(true); + sessionRequestTimeout(0); + sessionDetach(name.getBytes()); + + awaitClose(); + + } } @@ -1052,8 +995,7 @@ public class Session extends SessionInvoker if(state == CLOSED) { - connection.removeSession(this); - listener.closed(this); + connection.removeSession(this); } } @@ -1066,55 +1008,13 @@ public class Session extends SessionInvoker { return String.format("ssn:%s", name); } - + public void setTransacted(boolean b) { this.transacted = b; } - + public boolean isTransacted(){ return transacted; } - - public void setDetachCode(SessionDetachCode dtc) - { - this.detachCode = dtc; - } - - public SessionDetachCode getDetachCode() - { - return this.detachCode; - } - - public void awaitOpen() - { - switch (state) - { - case NEW: - synchronized(stateLock) - { - Waiter w = new Waiter(stateLock, timeout); - while (w.hasTime() && state == NEW) - { - w.await(); - } - } - - if (state != OPEN) - { - throw new SessionException("Timed out waiting for Session to open"); - } - break; - case DETACHED: - case CLOSING: - case CLOSED: - throw new SessionException("Session closed"); - default : - break; - } - } - - public Object getStateLock() - { - return stateLock; - } + } diff --git a/java/common/src/main/java/org/apache/qpid/transport/SessionDelegate.java b/java/common/src/main/java/org/apache/qpid/transport/SessionDelegate.java index 3341149e5f..5d8e4d5565 100644 --- a/java/common/src/main/java/org/apache/qpid/transport/SessionDelegate.java +++ b/java/common/src/main/java/org/apache/qpid/transport/SessionDelegate.java @@ -76,10 +76,6 @@ public class SessionDelegate @Override public void sessionAttached(Session ssn, SessionAttached atc) { ssn.setState(Session.State.OPEN); - synchronized (ssn.getStateLock()) - { - ssn.getStateLock().notifyAll(); - } } @Override public void sessionTimeout(Session ssn, SessionTimeout t) @@ -206,19 +202,11 @@ public class SessionDelegate public void closed(Session session) { - log.debug("CLOSED: [%s]", session); - synchronized (session.getStateLock()) - { - session.getStateLock().notifyAll(); - } + log.warn("CLOSED: [%s]", session); } public void detached(Session session) { - log.debug("DETACHED: [%s]", session); - synchronized (session.getStateLock()) - { - session.getStateLock().notifyAll(); - } + log.warn("DETACHED: [%s]", session); } } diff --git a/java/common/src/main/java/org/apache/qpid/transport/TransportBuilder.java b/java/common/src/main/java/org/apache/qpid/transport/TransportBuilder.java new file mode 100644 index 0000000000..c08909c6e4 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/transport/TransportBuilder.java @@ -0,0 +1,78 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.transport; + +import java.nio.ByteBuffer; + +import org.apache.qpid.transport.network.Assembler; +import org.apache.qpid.transport.network.Disassembler; +import org.apache.qpid.transport.network.InputHandler; +import org.apache.qpid.transport.network.NetworkTransport; +import org.apache.qpid.transport.network.Transport; +import org.apache.qpid.transport.network.security.SecurityLayer; + +public class TransportBuilder +{ + private Connection con; + private ConnectionSettings settings; + private NetworkTransport transport; + private SecurityLayer securityLayer = new SecurityLayer(); + + public void init(Connection con) throws TransportException + { + this.con = con; + this.settings = con.getConnectionSettings(); + transport = Transport.getTransport(); + transport.init(settings); + securityLayer.init(con); + } + + public Sender<ProtocolEvent> buildSenderPipe() + { + ConnectionSettings settings = con.getConnectionSettings(); + + // Io layer + Sender<ByteBuffer> sender = transport.sender(); + + // Security layer + sender = securityLayer.sender(sender); + + Disassembler dis = new Disassembler(sender, settings.getMaxFrameSize()); + return dis; + } + + public void buildReceiverPipe(Receiver<ProtocolEvent> delegate) + { + Receiver<ByteBuffer> receiver = new InputHandler(new Assembler(delegate)); + + // Security layer + receiver = securityLayer.receiver(receiver); + + //Io layer + transport.receiver(receiver); + } + + public SecurityLayer getSecurityLayer() + { + return securityLayer; + } + +}
\ No newline at end of file diff --git a/java/common/src/main/java/org/apache/qpid/transport/codec/AbstractEncoder.java b/java/common/src/main/java/org/apache/qpid/transport/codec/AbstractEncoder.java index 0ccfcfcb70..908d14a307 100644 --- a/java/common/src/main/java/org/apache/qpid/transport/codec/AbstractEncoder.java +++ b/java/common/src/main/java/org/apache/qpid/transport/codec/AbstractEncoder.java @@ -63,7 +63,6 @@ abstract class AbstractEncoder implements Encoder ENCODINGS.put(Double.class, Type.DOUBLE); ENCODINGS.put(Character.class, Type.CHAR); ENCODINGS.put(byte[].class, Type.VBIN32); - ENCODINGS.put(UUID.class, Type.UUID); } private final Map<String,byte[]> str8cache = new LinkedHashMap<String,byte[]>() diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/IncomingNetworkTransport.java b/java/common/src/main/java/org/apache/qpid/transport/network/IncomingNetworkTransport.java deleted file mode 100644 index b371df639e..0000000000 --- a/java/common/src/main/java/org/apache/qpid/transport/network/IncomingNetworkTransport.java +++ /dev/null @@ -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.qpid.transport.network; - -import javax.net.ssl.SSLContext; - -import org.apache.qpid.protocol.ProtocolEngineFactory; -import org.apache.qpid.transport.NetworkTransportConfiguration; - -public interface IncomingNetworkTransport extends NetworkTransport -{ - public void accept(NetworkTransportConfiguration config, ProtocolEngineFactory factory, SSLContext sslContext); -}
\ No newline at end of file diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/NetworkConnection.java b/java/common/src/main/java/org/apache/qpid/transport/network/NetworkConnection.java deleted file mode 100644 index 7384702525..0000000000 --- a/java/common/src/main/java/org/apache/qpid/transport/network/NetworkConnection.java +++ /dev/null @@ -1,49 +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.qpid.transport.network; - -import java.net.SocketAddress; -import java.nio.ByteBuffer; - -import org.apache.qpid.transport.Sender; - -public interface NetworkConnection -{ - Sender<ByteBuffer> getSender(); - - void start(); - - void close(); - - /** - * Returns the remote address of the underlying socket. - */ - SocketAddress getRemoteAddress(); - - /** - * Returns the local address of the underlying socket. - */ - SocketAddress getLocalAddress(); - - void setMaxWriteIdle(int sec); - - void setMaxReadIdle(int sec); -}
\ No newline at end of file diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/NetworkTransport.java b/java/common/src/main/java/org/apache/qpid/transport/network/NetworkTransport.java index f71d39c381..5e12d7e7c6 100644 --- a/java/common/src/main/java/org/apache/qpid/transport/network/NetworkTransport.java +++ b/java/common/src/main/java/org/apache/qpid/transport/network/NetworkTransport.java @@ -20,11 +20,19 @@ */ package org.apache.qpid.transport.network; -/** - * A network transport is responsible for the establishment of network connections. - * NetworkTransport implementations are pluggable via the {@link Transport} class. - */ +import java.nio.ByteBuffer; + +import org.apache.qpid.transport.Receiver; +import org.apache.qpid.transport.Sender; +import org.apache.qpid.transport.ConnectionSettings; + public interface NetworkTransport { + public void init(ConnectionSettings settings); + + public Sender<ByteBuffer> sender(); + + public void receiver(Receiver<ByteBuffer> delegate); + public void close(); -} +}
\ No newline at end of file diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/OutgoingNetworkTransport.java b/java/common/src/main/java/org/apache/qpid/transport/network/OutgoingNetworkTransport.java deleted file mode 100644 index c3c248761c..0000000000 --- a/java/common/src/main/java/org/apache/qpid/transport/network/OutgoingNetworkTransport.java +++ /dev/null @@ -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.qpid.transport.network; - -import java.nio.ByteBuffer; - -import javax.net.ssl.SSLContext; - -import org.apache.qpid.transport.ConnectionSettings; -import org.apache.qpid.transport.Receiver; - -public interface OutgoingNetworkTransport extends NetworkTransport -{ - public NetworkConnection getConnection(); - - public NetworkConnection connect(ConnectionSettings settings, Receiver<ByteBuffer> delegate, SSLContext sslContext); -}
\ No newline at end of file diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/Transport.java b/java/common/src/main/java/org/apache/qpid/transport/network/Transport.java index da4349ba86..f0bf04d04f 100644 --- a/java/common/src/main/java/org/apache/qpid/transport/network/Transport.java +++ b/java/common/src/main/java/org/apache/qpid/transport/network/Transport.java @@ -1,5 +1,5 @@ /* - * + * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -7,128 +7,50 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "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.qpid.transport.network; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +package org.apache.qpid.transport.network; -import org.apache.qpid.framing.ProtocolVersion; import org.apache.qpid.transport.TransportException; public class Transport -{ - public static final String QPID_TRANSPORT_PROPNAME = "qpid.transport"; - public static final String QPID_TRANSPORT_V0_8_PROPNAME = "qpid.transport.v0_8"; - public static final String QPID_TRANSPORT_V0_9_PROPNAME = "qpid.transport.v0_9"; - public static final String QPID_TRANSPORT_V0_9_1_PROPNAME = "qpid.transport.v0_9_1"; - public static final String QPID_TRANSPORT_V0_10_PROPNAME = "qpid.transport.v0_10"; - public static final String QPID_BROKER_TRANSPORT_PROPNAME = "qpid.broker.transport"; - - // Can't reference the class directly here, as this would preclude the ability to bundle transports separately. - private static final String IO_TRANSPORT_CLASSNAME = "org.apache.qpid.transport.network.io.IoNetworkTransport"; - - public static final String TCP = "tcp"; - - private final static Map<ProtocolVersion,String> OUTGOING_PROTOCOL_TO_IMPLDEFAULTS_MAP; - - static - { - final Map<ProtocolVersion,String> map = new HashMap<ProtocolVersion, String>(); - map.put(ProtocolVersion.v8_0, IO_TRANSPORT_CLASSNAME); - map.put(ProtocolVersion.v0_9, IO_TRANSPORT_CLASSNAME); - map.put(ProtocolVersion.v0_91, IO_TRANSPORT_CLASSNAME); - map.put(ProtocolVersion.v0_10, IO_TRANSPORT_CLASSNAME); - - OUTGOING_PROTOCOL_TO_IMPLDEFAULTS_MAP = Collections.unmodifiableMap(map); - } - - public static IncomingNetworkTransport getIncomingTransportInstance() +{ + private final static Class<?> transportClass; + + static { - return (IncomingNetworkTransport) loadTransportClass( - System.getProperty(QPID_BROKER_TRANSPORT_PROPNAME, IO_TRANSPORT_CLASSNAME)); - } - - public static OutgoingNetworkTransport getOutgoingTransportInstance( - final ProtocolVersion protocolVersion) - { - - final String overrride = getOverrideClassNameFromSystemProperty(protocolVersion); - final String networkTransportClassName; - if (overrride != null) - { - networkTransportClassName = overrride; - } - else - { - networkTransportClassName = OUTGOING_PROTOCOL_TO_IMPLDEFAULTS_MAP.get(protocolVersion); - } - - return (OutgoingNetworkTransport) loadTransportClass(networkTransportClassName); - } - - private static NetworkTransport loadTransportClass(final String networkTransportClassName) - { - if (networkTransportClassName == null) - { - throw new IllegalArgumentException("transport class name must not be null"); - } - try { - final Class<?> clazz = Class.forName(networkTransportClassName); - return (NetworkTransport) clazz.newInstance(); + transportClass = + Class.forName(System.getProperty("qpid.transport", + "org.apache.qpid.transport.network.io.IoNetworkTransport")); + } - catch (InstantiationException e) + catch(Exception e) { - throw new TransportException("Unable to instantiate transport class " + networkTransportClassName, e); - } - catch (IllegalAccessException e) - { - throw new TransportException("Access exception " + networkTransportClassName, e); - } - catch (ClassNotFoundException e) - { - throw new TransportException("Unable to load transport class " + networkTransportClassName, e); + throw new Error("Error occured while loading Qpid Transport",e); } } - - private static String getOverrideClassNameFromSystemProperty(final ProtocolVersion protocolVersion) + + public static NetworkTransport getTransport() throws TransportException { - final String protocolSpecificSystemProperty; - - if (ProtocolVersion.v0_10.equals(protocolVersion)) - { - protocolSpecificSystemProperty = QPID_TRANSPORT_V0_10_PROPNAME; - } - else if (ProtocolVersion.v0_91.equals(protocolVersion)) - { - protocolSpecificSystemProperty = QPID_TRANSPORT_V0_9_1_PROPNAME; - } - else if (ProtocolVersion.v0_9.equals(protocolVersion)) - { - protocolSpecificSystemProperty = QPID_TRANSPORT_V0_9_PROPNAME; - } - else if (ProtocolVersion.v8_0.equals(protocolVersion)) + try { - protocolSpecificSystemProperty = QPID_TRANSPORT_V0_8_PROPNAME; + return (NetworkTransport)transportClass.newInstance(); } - else + catch (Exception e) { - throw new IllegalArgumentException("Unknown ProtocolVersion " + protocolVersion); + throw new TransportException("Error while creating a new transport instance",e); } - - return System.getProperty(protocolSpecificSystemProperty, System.getProperty(QPID_TRANSPORT_PROPNAME)); } -} +}
\ No newline at end of file diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/io/InputHandler_0_9.java b/java/common/src/main/java/org/apache/qpid/transport/network/io/InputHandler_0_9.java new file mode 100644 index 0000000000..ecc5f6d07c --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/transport/network/io/InputHandler_0_9.java @@ -0,0 +1,130 @@ +package org.apache.qpid.transport.network.io; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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. + * + */ + + +import java.nio.ByteBuffer; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.AMQFrameDecodingException; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.AMQMethodBodyFactory; +import org.apache.qpid.framing.BodyFactory; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.ContentBodyFactory; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.ContentHeaderBodyFactory; +import org.apache.qpid.framing.HeartbeatBody; +import org.apache.qpid.framing.HeartbeatBodyFactory; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; +import org.apache.qpid.transport.Receiver; + +public class InputHandler_0_9 implements Receiver<ByteBuffer> +{ + + private AMQVersionAwareProtocolSession _session; + private MethodRegistry _registry; + private BodyFactory bodyFactory; + private static final BodyFactory[] _bodiesSupported = new BodyFactory[Byte.MAX_VALUE]; + + static + { + _bodiesSupported[ContentHeaderBody.TYPE] = ContentHeaderBodyFactory.getInstance(); + _bodiesSupported[ContentBody.TYPE] = ContentBodyFactory.getInstance(); + _bodiesSupported[HeartbeatBody.TYPE] = new HeartbeatBodyFactory(); + } + + public InputHandler_0_9(AMQVersionAwareProtocolSession session) + { + _session = session; + _registry = _session.getMethodRegistry(); + } + + public void closed() + { + // AS FIXME: implement + } + + public void exception(Throwable t) + { + // TODO: propogate exception to things + t.printStackTrace(); + } + + public void received(ByteBuffer buf) + { + org.apache.mina.common.ByteBuffer in = org.apache.mina.common.ByteBuffer.wrap(buf); + try + { + final byte type = in.get(); + if (type == AMQMethodBody.TYPE) + { + bodyFactory = new AMQMethodBodyFactory(_session); + } + else + { + bodyFactory = _bodiesSupported[type]; + } + + if (bodyFactory == null) + { + throw new AMQFrameDecodingException(null, "Unsupported frame type: " + type, null); + } + + final int channel = in.getUnsignedShort(); + final long bodySize = in.getUnsignedInt(); + + // bodySize can be zero + if ((channel < 0) || (bodySize < 0)) + { + throw new AMQFrameDecodingException(null, "Undecodable frame: type = " + type + " channel = " + channel + + " bodySize = " + bodySize, null); + } + + AMQFrame frame = new AMQFrame(in, channel, bodySize, bodyFactory); + + byte marker = in.get(); + if ((marker & 0xFF) != 0xCE) + { + throw new AMQFrameDecodingException(null, "End of frame marker not found. Read " + marker + " length=" + bodySize + + " type=" + type, null); + } + + try + { + frame.getBodyFrame().handle(frame.getChannel(), _session); + } + catch (AMQException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + catch (AMQFrameDecodingException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + +} diff --git a/java/common/src/test/java/org/apache/qpid/transport/network/io/IoAcceptor.java b/java/common/src/main/java/org/apache/qpid/transport/network/io/IoAcceptor.java index e075681acb..8530240dcc 100644 --- a/java/common/src/test/java/org/apache/qpid/transport/network/io/IoAcceptor.java +++ b/java/common/src/main/java/org/apache/qpid/transport/network/io/IoAcceptor.java @@ -80,7 +80,7 @@ public class IoAcceptor<E> extends Thread try { Socket sock = socket.accept(); - IoTransport<E> transport = new IoTransport<E>(sock, binding); + IoTransport<E> transport = new IoTransport<E>(sock, binding,false); } catch (IOException e) { diff --git a/java/common/src/test/java/org/apache/qpid/transport/MockSender.java b/java/common/src/main/java/org/apache/qpid/transport/network/io/IoContext.java index 4b38b7318a..69b3a0ce45 100644 --- a/java/common/src/test/java/org/apache/qpid/transport/MockSender.java +++ b/java/common/src/main/java/org/apache/qpid/transport/network/io/IoContext.java @@ -1,5 +1,5 @@ /* - * + * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -7,41 +7,29 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "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.qpid.transport; +package org.apache.qpid.transport.network.io; +import java.net.Socket; import java.nio.ByteBuffer; -public class MockSender implements Sender<ByteBuffer> -{ - - public void setIdleTimeout(int i) - { - - } - - public void send(ByteBuffer msg) - { +import org.apache.qpid.transport.Sender; - } - - public void flush() - { - - } - - public void close() - { +public interface IoContext +{ + Sender<ByteBuffer> getSender(); + + IoReceiver getReceiver(); - } + Socket getSocket(); } diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/io/IoNetworkConnection.java b/java/common/src/main/java/org/apache/qpid/transport/network/io/IoNetworkConnection.java deleted file mode 100644 index bfc77539ce..0000000000 --- a/java/common/src/main/java/org/apache/qpid/transport/network/io/IoNetworkConnection.java +++ /dev/null @@ -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 limitations - * under the License. - * - */ -package org.apache.qpid.transport.network.io; - -import java.net.Socket; -import java.net.SocketAddress; -import java.nio.ByteBuffer; - -import org.apache.qpid.transport.Receiver; -import org.apache.qpid.transport.Sender; -import org.apache.qpid.transport.network.NetworkConnection; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class IoNetworkConnection implements NetworkConnection -{ - private static final Logger LOGGER = LoggerFactory.getLogger(IoNetworkConnection.class); - private final Socket _socket; - private final long _timeout; - private final IoSender _ioSender; - private final IoReceiver _ioReceiver; - - public IoNetworkConnection(Socket socket, Receiver<ByteBuffer> delegate, - int sendBufferSize, int receiveBufferSize, long timeout) - { - _socket = socket; - _timeout = timeout; - - _ioReceiver = new IoReceiver(_socket, delegate, receiveBufferSize,_timeout); - - _ioSender = new IoSender(_socket, 2 * sendBufferSize, _timeout); - - _ioSender.registerCloseListener(_ioReceiver); - - } - - public void start() - { - _ioSender.initiate(); - _ioReceiver.initiate(); - } - - public Sender<ByteBuffer> getSender() - { - return _ioSender; - } - - public void close() - { - try - { - _ioSender.close(); - } - finally - { - _ioReceiver.close(false); - } - } - - public SocketAddress getRemoteAddress() - { - return _socket.getRemoteSocketAddress(); - } - - public SocketAddress getLocalAddress() - { - return _socket.getLocalSocketAddress(); - } - - public void setMaxWriteIdle(int sec) - { - // TODO implement support for setting heartbeating config in this way - // Currently a socket timeout is used in IoSender - } - - public void setMaxReadIdle(int sec) - { - // TODO implement support for setting heartbeating config in this way - // Currently a socket timeout is used in IoSender - } -} diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/io/IoNetworkTransport.java b/java/common/src/main/java/org/apache/qpid/transport/network/io/IoNetworkTransport.java index e1d1596ec5..dd6a37eca2 100644 --- a/java/common/src/main/java/org/apache/qpid/transport/network/io/IoNetworkTransport.java +++ b/java/common/src/main/java/org/apache/qpid/transport/network/io/IoNetworkTransport.java @@ -21,49 +21,57 @@ package org.apache.qpid.transport.network.io; import java.io.IOException; -import java.net.*; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketException; import java.nio.ByteBuffer; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLServerSocketFactory; - -import org.apache.qpid.protocol.ProtocolEngine; -import org.apache.qpid.protocol.ProtocolEngineFactory; -import org.apache.qpid.transport.*; -import org.apache.qpid.transport.network.IncomingNetworkTransport; -import org.apache.qpid.transport.network.NetworkConnection; -import org.apache.qpid.transport.network.OutgoingNetworkTransport; +import org.apache.qpid.transport.ConnectionSettings; +import org.apache.qpid.transport.Receiver; +import org.apache.qpid.transport.Sender; +import org.apache.qpid.transport.TransportException; +import org.apache.qpid.transport.network.NetworkTransport; import org.apache.qpid.transport.util.Logger; -public class IoNetworkTransport implements OutgoingNetworkTransport, IncomingNetworkTransport +public class IoNetworkTransport implements NetworkTransport, IoContext { + static + { + org.apache.mina.common.ByteBuffer.setAllocator + (new org.apache.mina.common.SimpleByteBufferAllocator()); + org.apache.mina.common.ByteBuffer.setUseDirectBuffers + (Boolean.getBoolean("amqj.enableDirectBuffers")); + } - private static final Logger LOGGER = Logger.get(IoNetworkTransport.class); - - private Socket _socket; - private IoNetworkConnection _connection; - private long _timeout = 60000; - private AcceptingThread _acceptor; + private static final Logger log = Logger.get(IoNetworkTransport.class); - public NetworkConnection connect(ConnectionSettings settings, Receiver<ByteBuffer> delegate, SSLContext sslContext) + private Socket socket; + private Sender<ByteBuffer> sender; + private IoReceiver receiver; + private long timeout = 60000; + private ConnectionSettings settings; + + public void init(ConnectionSettings settings) { - int sendBufferSize = settings.getWriteBufferSize(); - int receiveBufferSize = settings.getReadBufferSize(); - try { - _socket = new Socket(); - _socket.setReuseAddress(true); - _socket.setTcpNoDelay(settings.isTcpNodelay()); - _socket.setSendBufferSize(sendBufferSize); - _socket.setReceiveBufferSize(receiveBufferSize); + this.settings = settings; + InetAddress address = InetAddress.getByName(settings.getHost()); + socket = new Socket(); + socket.setReuseAddress(true); + socket.setTcpNoDelay(settings.isTcpNodelay()); - LOGGER.debug("SO_RCVBUF : %s", _socket.getReceiveBufferSize()); - LOGGER.debug("SO_SNDBUF : %s", _socket.getSendBufferSize()); + log.debug("default-SO_RCVBUF : %s", socket.getReceiveBufferSize()); + log.debug("default-SO_SNDBUF : %s", socket.getSendBufferSize()); - InetAddress address = InetAddress.getByName(settings.getHost()); + socket.setSendBufferSize(settings.getWriteBufferSize()); + socket.setReceiveBufferSize(settings.getReadBufferSize()); + + log.debug("new-SO_RCVBUF : %s", socket.getReceiveBufferSize()); + log.debug("new-SO_SNDBUF : %s", socket.getSendBufferSize()); - _socket.connect(new InetSocketAddress(address, settings.getPort())); + socket.connect(new InetSocketAddress(address, settings.getPort())); } catch (SocketException e) { @@ -73,159 +81,36 @@ public class IoNetworkTransport implements OutgoingNetworkTransport, IncomingNet { throw new TransportException("Error connecting to broker", e); } + } - try - { - _connection = new IoNetworkConnection(_socket, delegate, sendBufferSize, receiveBufferSize, _timeout); - _connection.start(); - } - catch(Exception e) - { - try - { - _socket.close(); - } - catch(IOException ioe) - { - //ignored, throw based on original exception - } - - throw new TransportException("Error creating network connection", e); - } + public void receiver(Receiver<ByteBuffer> delegate) + { + receiver = new IoReceiver(this, delegate, + 2*settings.getReadBufferSize() , timeout); + } - return _connection; + public Sender<ByteBuffer> sender() + { + return new IoSender(this, 2*settings.getWriteBufferSize(), timeout); } public void close() { - if(_connection != null) - { - _connection.close(); - } - if(_acceptor != null) - { - _acceptor.close(); - } + } - public NetworkConnection getConnection() + public Sender<ByteBuffer> getSender() { - return _connection; + return sender; } - public void accept(NetworkTransportConfiguration config, ProtocolEngineFactory factory, SSLContext sslContext) + public IoReceiver getReceiver() { - - try - { - _acceptor = new AcceptingThread(config, factory, sslContext); - - _acceptor.start(); - } - catch (IOException e) - { - throw new TransportException("Unable to start server socket", e); - } - - + return receiver; } - private class AcceptingThread extends Thread + public Socket getSocket() { - private NetworkTransportConfiguration _config; - private ProtocolEngineFactory _factory; - private SSLContext _sslContent; - private ServerSocket _serverSocket; - - private AcceptingThread(NetworkTransportConfiguration config, - ProtocolEngineFactory factory, - SSLContext sslContext) - throws IOException - { - _config = config; - _factory = factory; - _sslContent = sslContext; - - InetSocketAddress address = new InetSocketAddress(config.getHost(), config.getPort()); - - if(sslContext == null) - { - _serverSocket = new ServerSocket(); - } - else - { - SSLServerSocketFactory socketFactory = sslContext.getServerSocketFactory(); - _serverSocket = socketFactory.createServerSocket(); - } - - _serverSocket.bind(address); - _serverSocket.setReuseAddress(true); - - - } - - - /** - Close the underlying ServerSocket if it has not already been closed. - */ - public void close() - { - if (!_serverSocket.isClosed()) - { - try - { - _serverSocket.close(); - } - catch (IOException e) - { - throw new TransportException(e); - } - } - } - - @Override - public void run() - { - try - { - while (true) - { - try - { - Socket socket = _serverSocket.accept(); - socket.setTcpNoDelay(_config.getTcpNoDelay()); - - final Integer sendBufferSize = _config.getSendBufferSize(); - final Integer receiveBufferSize = _config.getReceiveBufferSize(); - - socket.setSendBufferSize(sendBufferSize); - socket.setReceiveBufferSize(receiveBufferSize); - - ProtocolEngine engine = _factory.newProtocolEngine(); - - NetworkConnection connection = new IoNetworkConnection(socket, engine, sendBufferSize, receiveBufferSize, _timeout); - - - engine.setNetworkConnection(connection, connection.getSender()); - - connection.start(); - - - } - catch(RuntimeException e) - { - LOGGER.error(e, "Error in Acceptor thread " + _config.getPort()); - } - } - } - catch (IOException e) - { - LOGGER.debug(e, "SocketException - no new connections will be accepted on port " - + _config.getPort()); - } - } - - + return socket; } - } diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/io/IoReceiver.java b/java/common/src/main/java/org/apache/qpid/transport/network/io/IoReceiver.java index d4b5975e54..19a683d505 100644 --- a/java/common/src/main/java/org/apache/qpid/transport/network/io/IoReceiver.java +++ b/java/common/src/main/java/org/apache/qpid/transport/network/io/IoReceiver.java @@ -20,7 +20,6 @@ */ package org.apache.qpid.transport.network.io; -import org.apache.qpid.common.Closeable; import org.apache.qpid.thread.Threading; import org.apache.qpid.transport.Receiver; import org.apache.qpid.transport.TransportException; @@ -38,77 +37,56 @@ import java.util.concurrent.atomic.AtomicBoolean; * */ -final class IoReceiver implements Runnable, Closeable +final class IoReceiver implements Runnable { private static final Logger log = Logger.get(IoReceiver.class); + private final IoContext ioCtx; private final Receiver<ByteBuffer> receiver; private final int bufferSize; private final Socket socket; private final long timeout; private final AtomicBoolean closed = new AtomicBoolean(false); private final Thread receiverThread; - private static final boolean shutdownBroken; - static - { - String osName = System.getProperty("os.name"); - shutdownBroken = osName == null ? false : osName.matches("(?i).*windows.*"); - } + private final boolean shutdownBroken = + ((String) System.getProperties().get("os.name")).matches("(?i).*windows.*"); - public IoReceiver(Socket socket, Receiver<ByteBuffer> receiver, int bufferSize, long timeout) + public IoReceiver(IoContext ioCtx, Receiver<ByteBuffer> receiver, + int bufferSize, long timeout) { + this.ioCtx = ioCtx; this.receiver = receiver; this.bufferSize = bufferSize; - this.socket = socket; + this.socket = ioCtx.getSocket(); this.timeout = timeout; try { - //Create but deliberately don't start the thread. receiverThread = Threading.getThreadFactory().createThread(this); } catch(Exception e) { - throw new RuntimeException("Error creating IOReceiver thread",e); + throw new Error("Error creating IOReceiver thread",e); } receiverThread.setDaemon(true); receiverThread.setName(String.format("IoReceiver - %s", socket.getRemoteSocketAddress())); - } - - public void initiate() - { receiverThread.start(); } - public void close() - { - close(false); - } - void close(boolean block) { if (!closed.getAndSet(true)) { try { - try + if (shutdownBroken) { - if (shutdownBroken) - { - socket.close(); - } - else - { - socket.shutdownInput(); - } + socket.close(); } - catch(SocketException se) + else { - if(!socket.isClosed() && !socket.isInputShutdown()) - { - throw se; - } + socket.shutdownInput(); } if (block && Thread.currentThread() != receiverThread) { @@ -127,7 +105,6 @@ final class IoReceiver implements Runnable, Closeable { throw new TransportException(e); } - } } diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/io/IoSender.java b/java/common/src/main/java/org/apache/qpid/transport/network/io/IoSender.java index 427487c879..66b97e8225 100644 --- a/java/common/src/main/java/org/apache/qpid/transport/network/io/IoSender.java +++ b/java/common/src/main/java/org/apache/qpid/transport/network/io/IoSender.java @@ -24,14 +24,10 @@ import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; -import org.apache.qpid.common.Closeable; import org.apache.qpid.thread.Threading; import org.apache.qpid.transport.Sender; -import org.apache.qpid.transport.SenderClosedException; import org.apache.qpid.transport.SenderException; import org.apache.qpid.transport.TransportException; import org.apache.qpid.transport.util.Logger; @@ -47,6 +43,7 @@ public final class IoSender implements Runnable, Sender<ByteBuffer> // we can test other cases as well private final static int START = Integer.MAX_VALUE - 10; + private final IoContext ioCtx; private final long timeout; private final Socket socket; private final OutputStream out; @@ -59,13 +56,14 @@ public final class IoSender implements Runnable, Sender<ByteBuffer> private final Object notEmpty = new Object(); private final AtomicBoolean closed = new AtomicBoolean(false); private final Thread senderThread; - private final List<Closeable> _listeners = new ArrayList<Closeable>(); - + private volatile Throwable exception = null; - public IoSender(Socket socket, int bufferSize, long timeout) + + public IoSender(IoContext ioCtx, int bufferSize, long timeout) { - this.socket = socket; + this.ioCtx = ioCtx; + this.socket = ioCtx.getSocket(); this.buffer = new byte[pof2(bufferSize)]; // buffer size must be a power of 2 this.timeout = timeout; @@ -80,20 +78,15 @@ public final class IoSender implements Runnable, Sender<ByteBuffer> try { - //Create but deliberately don't start the thread. - senderThread = Threading.getThreadFactory().createThread(this); + senderThread = Threading.getThreadFactory().createThread(this); } catch(Exception e) { throw new Error("Error creating IOSender thread",e); } - + senderThread.setDaemon(true); senderThread.setName(String.format("IoSender - %s", socket.getRemoteSocketAddress())); - } - - public void initiate() - { senderThread.start(); } @@ -111,11 +104,7 @@ public final class IoSender implements Runnable, Sender<ByteBuffer> { if (closed.get()) { - throw new SenderClosedException("sender is closed", exception); - } - if(!senderThread.isAlive()) - { - throw new SenderException("sender thread not alive"); + throw new SenderException("sender is closed", exception); } final int size = buffer.length; @@ -148,7 +137,7 @@ public final class IoSender implements Runnable, Sender<ByteBuffer> if (closed.get()) { - throw new SenderClosedException("sender is closed", exception); + throw new SenderException("sender is closed", exception); } if (head - tail >= size) @@ -215,20 +204,16 @@ public final class IoSender implements Runnable, Sender<ByteBuffer> senderThread.join(timeout); if (senderThread.isAlive()) { - log.error("join timed out"); throw new SenderException("join timed out"); } } + ioCtx.getReceiver().close(false); } catch (InterruptedException e) { - log.error("interrupted whilst waiting for sender thread to stop"); throw new SenderException(e); } - finally - { - closeListeners(); - } + if (reportException && exception != null) { throw new SenderException(exception); @@ -236,31 +221,9 @@ public final class IoSender implements Runnable, Sender<ByteBuffer> } } - private void closeListeners() - { - Exception ex = null; - for(Closeable listener : _listeners) - { - try - { - listener.close(); - } - catch(Exception e) - { - log.error("Exception closing listener: " + e.getMessage()); - ex = e; - } - } - - if (ex != null) - { - throw new SenderException(ex.getMessage(), ex); - } - } - public void run() { - final int size = buffer.length; + final int size = buffer.length; while (true) { final int hd = head; @@ -341,9 +304,4 @@ public final class IoSender implements Runnable, Sender<ByteBuffer> throw new SenderException(e); } } - - public void registerCloseListener(Closeable listener) - { - _listeners.add(listener); - } } diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/io/IoTransport.java b/java/common/src/main/java/org/apache/qpid/transport/network/io/IoTransport.java new file mode 100644 index 0000000000..bfdbb34978 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/transport/network/io/IoTransport.java @@ -0,0 +1,231 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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.qpid.transport.network.io; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketException; +import java.nio.ByteBuffer; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + +import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; +import org.apache.qpid.ssl.SSLContextFactory; +import org.apache.qpid.transport.Binding; +import org.apache.qpid.transport.Connection; +import org.apache.qpid.transport.ConnectionDelegate; +import org.apache.qpid.transport.Receiver; +import org.apache.qpid.transport.Sender; +import org.apache.qpid.transport.TransportException; +import org.apache.qpid.transport.network.ConnectionBinding; +import org.apache.qpid.transport.network.security.ssl.SSLReceiver; +import org.apache.qpid.transport.network.security.ssl.SSLSender; +import org.apache.qpid.transport.util.Logger; + +/** + * This class provides a socket based transport using the java.io + * classes. + * + * The following params are configurable via JVM arguments + * TCP_NO_DELAY - amqj.tcpNoDelay + * SO_RCVBUF - amqj.receiveBufferSize + * SO_SNDBUF - amqj.sendBufferSize + */ +public final class IoTransport<E> implements IoContext +{ + + static + { + org.apache.mina.common.ByteBuffer.setAllocator + (new org.apache.mina.common.SimpleByteBufferAllocator()); + org.apache.mina.common.ByteBuffer.setUseDirectBuffers + (Boolean.getBoolean("amqj.enableDirectBuffers")); + } + + private static final Logger log = Logger.get(IoTransport.class); + + private static int DEFAULT_READ_WRITE_BUFFER_SIZE = 64 * 1024; + private static int readBufferSize = Integer.getInteger + ("amqj.receiveBufferSize", DEFAULT_READ_WRITE_BUFFER_SIZE); + private static int writeBufferSize = Integer.getInteger + ("amqj.sendBufferSize", DEFAULT_READ_WRITE_BUFFER_SIZE); + + private Socket socket; + private Sender<ByteBuffer> sender; + private E endpoint; + private IoReceiver receiver; + private long timeout = 60000; + + IoTransport(Socket socket, Binding<E,ByteBuffer> binding, boolean ssl) + { + this.socket = socket; + + if (ssl) + { + SSLEngine engine = null; + SSLContext sslCtx; + try + { + sslCtx = createSSLContext(); + } + catch (Exception e) + { + throw new TransportException("Error creating SSL Context", e); + } + + try + { + engine = sslCtx.createSSLEngine(); + engine.setUseClientMode(true); + } + catch(Exception e) + { + throw new TransportException("Error creating SSL Engine", e); + } + + this.sender = new SSLSender(engine,new IoSender(this, 2*writeBufferSize, timeout)); + this.endpoint = binding.endpoint(sender); + this.receiver = new IoReceiver(this, new SSLReceiver(engine,binding.receiver(endpoint),(SSLSender)sender), + 2*readBufferSize, timeout); + + log.info("SSL Sender and Receiver initiated"); + } + else + { + this.sender = new IoSender(this, 2*writeBufferSize, timeout); + this.endpoint = binding.endpoint(sender); + this.receiver = new IoReceiver(this, binding.receiver(endpoint), + 2*readBufferSize, timeout); + } + } + + public Sender<ByteBuffer> getSender() + { + return sender; + } + + public IoReceiver getReceiver() + { + return receiver; + } + + public Socket getSocket() + { + return socket; + } + + public static final <E> E connect(String host, int port, + Binding<E,ByteBuffer> binding, + boolean ssl) + { + Socket socket = createSocket(host, port); + IoTransport<E> transport = new IoTransport<E>(socket, binding,ssl); + return transport.endpoint; + } + + public static final Connection connect(String host, int port, + ConnectionDelegate delegate, + boolean ssl) + { + return connect(host, port, ConnectionBinding.get(delegate),ssl); + } + + public static void connect_0_9(AMQVersionAwareProtocolSession session, String host, int port, boolean ssl) + { + connect(host, port, new Binding_0_9(session),ssl); + } + + private static class Binding_0_9 + implements Binding<AMQVersionAwareProtocolSession,ByteBuffer> + { + + private AMQVersionAwareProtocolSession session; + + Binding_0_9(AMQVersionAwareProtocolSession session) + { + this.session = session; + } + + public AMQVersionAwareProtocolSession endpoint(Sender<ByteBuffer> sender) + { + session.setSender(sender); + return session; + } + + public Receiver<ByteBuffer> receiver(AMQVersionAwareProtocolSession ssn) + { + return new InputHandler_0_9(ssn); + } + + } + + private static Socket createSocket(String host, int port) + { + try + { + InetAddress address = InetAddress.getByName(host); + Socket socket = new Socket(); + socket.setReuseAddress(true); + socket.setTcpNoDelay(Boolean.getBoolean("amqj.tcpNoDelay")); + + log.debug("default-SO_RCVBUF : %s", socket.getReceiveBufferSize()); + log.debug("default-SO_SNDBUF : %s", socket.getSendBufferSize()); + + socket.setSendBufferSize(writeBufferSize); + socket.setReceiveBufferSize(readBufferSize); + + log.debug("new-SO_RCVBUF : %s", socket.getReceiveBufferSize()); + log.debug("new-SO_SNDBUF : %s", socket.getSendBufferSize()); + + socket.connect(new InetSocketAddress(address, port)); + return socket; + } + catch (SocketException e) + { + throw new TransportException("Error connecting to broker", e); + } + catch (IOException e) + { + throw new TransportException("Error connecting to broker", e); + } + } + + private SSLContext createSSLContext() throws Exception + { + String trustStorePath = System.getProperty("javax.net.ssl.trustStore"); + String trustStorePassword = System.getProperty("javax.net.ssl.trustStorePassword"); + String trustStoreCertType = System.getProperty("qpid.ssl.trustStoreCertType","SunX509"); + + String keyStorePath = System.getProperty("javax.net.ssl.keyStore",trustStorePath); + String keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword",trustStorePassword); + String keyStoreCertType = System.getProperty("qpid.ssl.keyStoreCertType","SunX509"); + + SSLContextFactory sslContextFactory = new SSLContextFactory(trustStorePath,trustStorePassword, + trustStoreCertType,keyStorePath, + keyStorePassword,keyStoreCertType); + + return sslContextFactory.buildServerContext(); + + } + +} diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/mina/MINANetworkDriver.java b/java/common/src/main/java/org/apache/qpid/transport/network/mina/MINANetworkDriver.java new file mode 100644 index 0000000000..0f2c0d0226 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/transport/network/mina/MINANetworkDriver.java @@ -0,0 +1,435 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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.qpid.transport.network.mina; + +import org.apache.mina.common.ConnectFuture; +import org.apache.mina.common.ExecutorThreadModel; +import org.apache.mina.common.IdleStatus; +import org.apache.mina.common.IoAcceptor; +import org.apache.mina.common.IoConnector; +import org.apache.mina.common.IoFilterChain; +import org.apache.mina.common.IoHandlerAdapter; +import org.apache.mina.common.IoSession; +import org.apache.mina.common.SimpleByteBufferAllocator; +import org.apache.mina.common.WriteFuture; +import org.apache.mina.filter.ReadThrottleFilterBuilder; +import org.apache.mina.filter.SSLFilter; +import org.apache.mina.filter.WriteBufferLimitFilterBuilder; +import org.apache.mina.filter.executor.ExecutorFilter; +import org.apache.mina.transport.socket.nio.MultiThreadSocketConnector; +import org.apache.mina.transport.socket.nio.SocketAcceptorConfig; +import org.apache.mina.transport.socket.nio.SocketConnector; +import org.apache.mina.transport.socket.nio.SocketConnectorConfig; +import org.apache.mina.transport.socket.nio.SocketSessionConfig; +import org.apache.mina.util.NewThreadExecutor; +import org.apache.mina.util.SessionUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.qpid.protocol.ProtocolEngine; +import org.apache.qpid.protocol.ProtocolEngineFactory; +import org.apache.qpid.ssl.SSLContextFactory; +import org.apache.qpid.thread.QpidThreadExecutor; +import org.apache.qpid.transport.NetworkDriver; +import org.apache.qpid.transport.NetworkDriverConfiguration; +import org.apache.qpid.transport.OpenException; + +import java.io.IOException; +import java.net.BindException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.ByteBuffer; + +public class MINANetworkDriver extends IoHandlerAdapter implements NetworkDriver +{ + + private static final int DEFAULT_BUFFER_SIZE = 32 * 1024; + + ProtocolEngine _protocolEngine; + private boolean _useNIO = false; + private int _processors = 4; + private boolean _executorPool = false; + private SSLContextFactory _sslFactory = null; + private IoConnector _socketConnector; + private IoAcceptor _acceptor; + private IoSession _ioSession; + private ProtocolEngineFactory _factory; + private boolean _protectIO; + private NetworkDriverConfiguration _config; + private Throwable _lastException; + private boolean _acceptingConnections = false; + + private WriteFuture _lastWriteFuture; + + private static final Logger _logger = LoggerFactory.getLogger(MINANetworkDriver.class); + + static + { + org.apache.mina.common.ByteBuffer.setUseDirectBuffers(Boolean.getBoolean("amqj.enableDirectBuffers")); + + //override the MINA defaults to prevent use of the PooledByteBufferAllocator + org.apache.mina.common.ByteBuffer.setAllocator(new SimpleByteBufferAllocator()); + } + + public MINANetworkDriver(boolean useNIO, int processors, boolean executorPool, boolean protectIO) + { + _useNIO = useNIO; + _processors = processors; + _executorPool = executorPool; + _protectIO = protectIO; + } + + public MINANetworkDriver(boolean useNIO, int processors, boolean executorPool, boolean protectIO, + ProtocolEngine protocolEngine, IoSession session) + { + _useNIO = useNIO; + _processors = processors; + _executorPool = executorPool; + _protectIO = protectIO; + _protocolEngine = protocolEngine; + _ioSession = session; + _ioSession.setAttachment(_protocolEngine); + } + + public MINANetworkDriver() + { + + } + + public MINANetworkDriver(IoConnector ioConnector) + { + _socketConnector = ioConnector; + } + + public MINANetworkDriver(IoConnector ioConnector, ProtocolEngine engine) + { + _socketConnector = ioConnector; + _protocolEngine = engine; + } + + public void bind(int port, InetAddress[] addresses, ProtocolEngineFactory factory, + NetworkDriverConfiguration config, SSLContextFactory sslFactory) throws BindException + { + + _factory = factory; + _config = config; + + if (_useNIO) + { + _acceptor = new org.apache.mina.transport.socket.nio.MultiThreadSocketAcceptor(_processors, + new NewThreadExecutor()); + } + else + { + _acceptor = new org.apache.mina.transport.socket.nio.SocketAcceptor(_processors, new NewThreadExecutor()); + } + + SocketAcceptorConfig sconfig = (SocketAcceptorConfig) _acceptor.getDefaultConfig(); + sconfig.setThreadModel(ExecutorThreadModel.getInstance("MINANetworkDriver(Acceptor)")); + SocketSessionConfig sc = (SocketSessionConfig) sconfig.getSessionConfig(); + + if (config != null) + { + sc.setReceiveBufferSize(config.getReceiveBufferSize()); + sc.setSendBufferSize(config.getSendBufferSize()); + sc.setTcpNoDelay(config.getTcpNoDelay()); + } + + if (sslFactory != null) + { + _sslFactory = sslFactory; + } + + if (addresses != null && addresses.length > 0) + { + for (InetAddress addr : addresses) + { + try + { + _acceptor.bind(new InetSocketAddress(addr, port), this, sconfig); + } + catch (IOException e) + { + throw new BindException(String.format("Could not bind to %1s:%2s", addr, port)); + } + } + } + else + { + try + { + _acceptor.bind(new InetSocketAddress(port), this, sconfig); + } + catch (IOException e) + { + throw new BindException(String.format("Could not bind to *:%1s", port)); + } + } + _acceptingConnections = true; + } + + public SocketAddress getRemoteAddress() + { + return _ioSession.getRemoteAddress(); + } + + public SocketAddress getLocalAddress() + { + return _ioSession.getLocalAddress(); + } + + + public void open(int port, InetAddress destination, ProtocolEngine engine, NetworkDriverConfiguration config, + SSLContextFactory sslFactory) throws OpenException + { + if (sslFactory != null) + { + _sslFactory = sslFactory; + } + + if (_useNIO) + { + _socketConnector = new MultiThreadSocketConnector(1, new QpidThreadExecutor()); + } + else + { + _socketConnector = new SocketConnector(1, new QpidThreadExecutor()); // non-blocking + // connector + } + + SocketConnectorConfig cfg = (SocketConnectorConfig) _socketConnector.getDefaultConfig(); + String s = ""; + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); + for(StackTraceElement elt : trace) + { + if(elt.getClassName().contains("Test")) + { + s = elt.getClassName(); + break; + } + } + cfg.setThreadModel(ExecutorThreadModel.getInstance("MINANetworkDriver(Client)-"+s)); + + SocketSessionConfig scfg = (SocketSessionConfig) cfg.getSessionConfig(); + scfg.setTcpNoDelay((config != null) ? config.getTcpNoDelay() : true); + scfg.setSendBufferSize((config != null) ? config.getSendBufferSize() : DEFAULT_BUFFER_SIZE); + scfg.setReceiveBufferSize((config != null) ? config.getReceiveBufferSize() : DEFAULT_BUFFER_SIZE); + + // Don't have the connector's worker thread wait around for other + // connections (we only use + // one SocketConnector per connection at the moment anyway). This allows + // short-running + // clients (like unit tests) to complete quickly. + if (_socketConnector instanceof SocketConnector) + { + ((SocketConnector) _socketConnector).setWorkerTimeout(0); + } + + ConnectFuture future = _socketConnector.connect(new InetSocketAddress(destination, port), this, cfg); + future.join(); + if (!future.isConnected()) + { + throw new OpenException("Could not open connection", _lastException); + } + _ioSession = future.getSession(); + _ioSession.setAttachment(engine); + engine.setNetworkDriver(this); + _protocolEngine = engine; + } + + public void setMaxReadIdle(int idleTime) + { + _ioSession.setIdleTime(IdleStatus.READER_IDLE, idleTime); + } + + public void setMaxWriteIdle(int idleTime) + { + _ioSession.setIdleTime(IdleStatus.WRITER_IDLE, idleTime); + } + + public void close() + { + if (_lastWriteFuture != null) + { + _lastWriteFuture.join(); + } + if (_acceptor != null) + { + _acceptor.unbindAll(); + } + if (_ioSession != null) + { + _ioSession.close(); + } + } + + public void flush() + { + if (_lastWriteFuture != null) + { + _lastWriteFuture.join(); + } + } + + public void send(ByteBuffer msg) + { + org.apache.mina.common.ByteBuffer minaBuf = org.apache.mina.common.ByteBuffer.allocate(msg.capacity()); + minaBuf.put(msg); + minaBuf.flip(); + _lastWriteFuture = _ioSession.write(minaBuf); + } + + public void setIdleTimeout(int i) + { + // MINA doesn't support setting SO_TIMEOUT + } + + public void exceptionCaught(IoSession protocolSession, Throwable throwable) throws Exception + { + if (_protocolEngine != null) + { + _protocolEngine.exception(throwable); + } + else + { + _logger.error("Exception thrown and no ProtocolEngine to handle it", throwable); + } + _lastException = throwable; + } + + /** + * Invoked when a message is received on a particular protocol session. Note + * that a protocol session is directly tied to a particular physical + * connection. + * + * @param protocolSession + * the protocol session that received the message + * @param message + * the message itself (i.e. a decoded frame) + * + * @throws Exception + * if the message cannot be processed + */ + public void messageReceived(IoSession protocolSession, Object message) throws Exception + { + if (message instanceof org.apache.mina.common.ByteBuffer) + { + ((ProtocolEngine) protocolSession.getAttachment()).received(((org.apache.mina.common.ByteBuffer) message).buf()); + } + else + { + throw new IllegalStateException("Handed unhandled message. message.class = " + message.getClass() + " message = " + message); + } + } + + public void sessionClosed(IoSession protocolSession) throws Exception + { + ((ProtocolEngine) protocolSession.getAttachment()).closed(); + } + + public void sessionCreated(IoSession protocolSession) throws Exception + { + // Configure the session with SSL if necessary + SessionUtil.initialize(protocolSession); + if (_executorPool) + { + if (_sslFactory != null) + { + protocolSession.getFilterChain().addAfter("AsynchronousReadFilter", "sslFilter", + new SSLFilter(_sslFactory.buildServerContext())); + } + } + else + { + if (_sslFactory != null) + { + protocolSession.getFilterChain().addBefore("protocolFilter", "sslFilter", + new SSLFilter(_sslFactory.buildServerContext())); + } + } + // Do we want to have read/write buffer limits? + if (_protectIO) + { + //Add IO Protection Filters + IoFilterChain chain = protocolSession.getFilterChain(); + + protocolSession.getFilterChain().addLast("tempExecutorFilterForFilterBuilder", new ExecutorFilter()); + + ReadThrottleFilterBuilder readfilter = new ReadThrottleFilterBuilder(); + readfilter.setMaximumConnectionBufferSize(_config.getReceiveBufferSize()); + readfilter.attach(chain); + + WriteBufferLimitFilterBuilder writefilter = new WriteBufferLimitFilterBuilder(); + writefilter.setMaximumConnectionBufferSize(_config.getSendBufferSize()); + writefilter.attach(chain); + + protocolSession.getFilterChain().remove("tempExecutorFilterForFilterBuilder"); + } + + if (_ioSession == null) + { + _ioSession = protocolSession; + } + + if (_acceptingConnections) + { + // Set up the protocol engine + ProtocolEngine protocolEngine = _factory.newProtocolEngine(this); + MINANetworkDriver newDriver = new MINANetworkDriver(_useNIO, _processors, _executorPool, _protectIO, protocolEngine, protocolSession); + protocolEngine.setNetworkDriver(newDriver); + } + } + + public void sessionIdle(IoSession session, IdleStatus status) throws Exception + { + if (IdleStatus.WRITER_IDLE.equals(status)) + { + ((ProtocolEngine) session.getAttachment()).writerIdle(); + } + else if (IdleStatus.READER_IDLE.equals(status)) + { + ((ProtocolEngine) session.getAttachment()).readerIdle(); + } + } + + private ProtocolEngine getProtocolEngine() + { + return _protocolEngine; + } + + public void setProtocolEngineFactory(ProtocolEngineFactory engineFactory, boolean acceptingConnections) + { + _factory = engineFactory; + _acceptingConnections = acceptingConnections; + } + + public void setProtocolEngine(ProtocolEngine protocolEngine) + { + _protocolEngine = protocolEngine; + if (_ioSession != null) + { + _ioSession.setAttachment(protocolEngine); + } + } + +} diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/mina/MinaHandler.java b/java/common/src/main/java/org/apache/qpid/transport/network/mina/MinaHandler.java new file mode 100644 index 0000000000..b89eed48b0 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/transport/network/mina/MinaHandler.java @@ -0,0 +1,274 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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.qpid.transport.network.mina; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +import org.apache.mina.common.*; + +import org.apache.mina.transport.socket.nio.SocketAcceptor; +import org.apache.mina.transport.socket.nio.SocketSessionConfig; +import org.apache.mina.transport.socket.nio.SocketConnector; +import org.apache.mina.filter.ReadThrottleFilterBuilder; +import org.apache.mina.filter.WriteBufferLimitFilterBuilder; +import org.apache.mina.filter.executor.ExecutorFilter; + +import org.apache.qpid.transport.Binding; +import org.apache.qpid.transport.Connection; +import org.apache.qpid.transport.ConnectionDelegate; +import org.apache.qpid.transport.Receiver; +import org.apache.qpid.transport.Sender; +import org.apache.qpid.transport.network.ConnectionBinding; + +import org.apache.qpid.transport.util.Logger; + +import org.apache.qpid.transport.network.Assembler; +import org.apache.qpid.transport.network.Disassembler; +import org.apache.qpid.transport.network.InputHandler; + +import static org.apache.qpid.transport.util.Functions.*; + +/** + * MinaHandler + * + * @author Rafael H. Schloming + */ +//RA making this public until we sort out the package issues +public class MinaHandler<E> implements IoHandler +{ + /** Default buffer size for pending messages reads */ + private static final String DEFAULT_READ_BUFFER_LIMIT = "262144"; + /** Default buffer size for pending messages writes */ + private static final String DEFAULT_WRITE_BUFFER_LIMIT = "262144"; + private static final int MAX_RCVBUF = 64*1024; + + private static final Logger log = Logger.get(MinaHandler.class); + + static + { + ByteBuffer.setAllocator(new SimpleByteBufferAllocator()); + ByteBuffer.setUseDirectBuffers(Boolean.getBoolean("amqj.enableDirectBuffers")); + } + + private final Binding<E,java.nio.ByteBuffer> binding; + + private MinaHandler(Binding<E,java.nio.ByteBuffer> binding) + { + this.binding = binding; + } + + public void messageReceived(IoSession ssn, Object obj) + { + Attachment<E> attachment = (Attachment<E>) ssn.getAttachment(); + ByteBuffer buf = (ByteBuffer) obj; + try + { + attachment.receiver.received(buf.buf()); + } + catch (Throwable t) + { + log.error(t, "exception handling buffer %s", str(buf.buf())); + throw new RuntimeException(t); + } + } + + public void messageSent(IoSession ssn, Object obj) + { + // do nothing + } + + public void exceptionCaught(IoSession ssn, Throwable e) + { + Attachment<E> attachment = (Attachment<E>) ssn.getAttachment(); + attachment.receiver.exception(e); + } + + /** + * Invoked by MINA when a MINA session for a new connection is created. This method sets up the filter chain on the + * session, which filters the events handled by this handler. The filter chain consists of, handing off events + * to an optional protectio + * + * @param session The MINA session. + * @throws Exception Any underlying exceptions are allowed to fall through to MINA. + */ + public void sessionCreated(IoSession session) throws Exception + { + log.debug("Protocol session created for session " + System.identityHashCode(session)); + + if (Boolean.getBoolean("protectio")) + { + try + { + //Add IO Protection Filters + IoFilterChain chain = session.getFilterChain(); + + session.getFilterChain().addLast("tempExecutorFilterForFilterBuilder", new ExecutorFilter()); + + ReadThrottleFilterBuilder readfilter = new ReadThrottleFilterBuilder(); + readfilter.setMaximumConnectionBufferSize( + Integer.parseInt(System.getProperty("qpid.read.buffer.limit", DEFAULT_READ_BUFFER_LIMIT))); + readfilter.attach(chain); + + WriteBufferLimitFilterBuilder writefilter = new WriteBufferLimitFilterBuilder(); + writefilter.setMaximumConnectionBufferSize( + Integer.parseInt(System.getProperty("qpid.write.buffer.limit", DEFAULT_WRITE_BUFFER_LIMIT))); + writefilter.attach(chain); + session.getFilterChain().remove("tempExecutorFilterForFilterBuilder"); + + log.info("Using IO Read/Write Filter Protection"); + } + catch (Exception e) + { + log.error("Unable to attach IO Read/Write Filter Protection :" + e.getMessage()); + } + } + } + + public void sessionOpened(final IoSession ssn) + { + log.debug("opened: %s", this); + E endpoint = binding.endpoint(new MinaSender(ssn)); + Attachment<E> attachment = + new Attachment<E>(endpoint, binding.receiver(endpoint)); + + // We need to synchronize and notify here because the MINA + // connect future returns the session prior to the attachment + // being set. This is arguably a bug in MINA. + synchronized (ssn) + { + ssn.setAttachment(attachment); + ssn.notifyAll(); + } + } + + public void sessionClosed(IoSession ssn) + { + log.debug("closed: %s", ssn); + Attachment<E> attachment = (Attachment<E>) ssn.getAttachment(); + attachment.receiver.closed(); + ssn.setAttachment(null); + } + + public void sessionIdle(IoSession ssn, IdleStatus status) + { + // do nothing + } + + private static class Attachment<E> + { + + E endpoint; + Receiver<java.nio.ByteBuffer> receiver; + + Attachment(E endpoint, Receiver<java.nio.ByteBuffer> receiver) + { + this.endpoint = endpoint; + this.receiver = receiver; + } + } + + public static final void accept(String host, int port, + Binding<?,java.nio.ByteBuffer> binding) + throws IOException + { + accept(new InetSocketAddress(host, port), binding); + } + + public static final <E> void accept(SocketAddress address, + Binding<E,java.nio.ByteBuffer> binding) + throws IOException + { + IoAcceptor acceptor = new SocketAcceptor(); + acceptor.bind(address, new MinaHandler<E>(binding)); + } + + public static final <E> E connect(String host, int port, + Binding<E,java.nio.ByteBuffer> binding) + { + return connect(new InetSocketAddress(host, port), binding); + } + + public static final <E> E connect(SocketAddress address, + Binding<E,java.nio.ByteBuffer> binding) + { + MinaHandler<E> handler = new MinaHandler<E>(binding); + SocketConnector connector = new SocketConnector(); + IoServiceConfig acceptorConfig = connector.getDefaultConfig(); + acceptorConfig.setThreadModel(ThreadModel.MANUAL); + SocketSessionConfig scfg = (SocketSessionConfig) acceptorConfig.getSessionConfig(); + scfg.setTcpNoDelay(Boolean.getBoolean("amqj.tcpNoDelay")); + Integer sendBufferSize = Integer.getInteger("amqj.sendBufferSize"); + if (sendBufferSize != null && sendBufferSize > 0) + { + scfg.setSendBufferSize(sendBufferSize); + } + Integer receiveBufferSize = Integer.getInteger("amqj.receiveBufferSize"); + if (receiveBufferSize != null && receiveBufferSize > 0) + { + scfg.setReceiveBufferSize(receiveBufferSize); + } + else if (scfg.getReceiveBufferSize() > MAX_RCVBUF) + { + scfg.setReceiveBufferSize(MAX_RCVBUF); + } + connector.setWorkerTimeout(0); + ConnectFuture cf = connector.connect(address, handler); + cf.join(); + IoSession ssn = cf.getSession(); + + // We need to synchronize and wait here because the MINA + // connect future returns the session prior to the attachment + // being set. This is arguably a bug in MINA. + synchronized (ssn) + { + while (ssn.getAttachment() == null) + { + try + { + ssn.wait(); + } + catch (InterruptedException e) + { + throw new RuntimeException(e); + } + } + } + + Attachment<E> attachment = (Attachment<E>) ssn.getAttachment(); + return attachment.endpoint; + } + + public static final void accept(String host, int port, + ConnectionDelegate delegate) + throws IOException + { + accept(host, port, ConnectionBinding.get(delegate)); + } + + public static final Connection connect(String host, int port, + ConnectionDelegate delegate) + { + return connect(host, port, ConnectionBinding.get(delegate)); + } + +} diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/mina/MinaSender.java b/java/common/src/main/java/org/apache/qpid/transport/network/mina/MinaSender.java new file mode 100644 index 0000000000..22b9c5e784 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/transport/network/mina/MinaSender.java @@ -0,0 +1,90 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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.qpid.transport.network.mina; + +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.CloseFuture; +import org.apache.mina.common.IoSession; +import org.apache.mina.common.WriteFuture; +import org.apache.qpid.transport.Sender; +import org.apache.qpid.transport.TransportException; + + +/** + * MinaSender + */ + +public class MinaSender implements Sender<java.nio.ByteBuffer> +{ + private static final int TIMEOUT = 2 * 60 * 1000; + + private final IoSession session; + private WriteFuture lastWrite = null; + + public MinaSender(IoSession session) + { + this.session = session; + } + + public void send(java.nio.ByteBuffer buf) + { + if (session.isClosing()) + { + throw new TransportException("attempted to write to a closed socket"); + } + + synchronized (this) + { + lastWrite = session.write(ByteBuffer.wrap(buf)); + } + } + + public void flush() + { + // pass + } + + public synchronized void close() + { + // MINA will sometimes throw away in-progress writes when you + // ask it to close + synchronized (this) + { + if (lastWrite != null) + { + lastWrite.join(); + } + } + CloseFuture closed = session.close(); + closed.join(); + } + + public void setIdleTimeout(int i) + { + //noop + } + + public long getIdleTimeout() + { + return 0; + } + +} diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/nio/NioHandler.java b/java/common/src/main/java/org/apache/qpid/transport/network/nio/NioHandler.java new file mode 100644 index 0000000000..84e66c25bd --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/transport/network/nio/NioHandler.java @@ -0,0 +1,135 @@ +package org.apache.qpid.transport.network.nio; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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. + * + */ + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.qpid.transport.Connection; +import org.apache.qpid.transport.ConnectionDelegate; +import org.apache.qpid.transport.Receiver; +import org.apache.qpid.transport.network.Assembler; +import org.apache.qpid.transport.network.Disassembler; +import org.apache.qpid.transport.network.InputHandler; + +public class NioHandler implements Runnable +{ + private Receiver<ByteBuffer> _receiver; + private SocketChannel _ch; + private ByteBuffer _readBuf; + private static Map<Long,NioSender> _handlers = new ConcurrentHashMap<Long,NioSender>(); + + private NioHandler(){} + + public static final Connection connect(String host, int port, + ConnectionDelegate delegate) + { + NioHandler handler = new NioHandler(); + return handler.connectInternal(host,port,delegate); + } + + private Connection connectInternal(String host, int port, + ConnectionDelegate delegate) + { + try + { + SocketAddress address = new InetSocketAddress(host,port); + _ch = SocketChannel.open(); + _ch.socket().setReuseAddress(true); + _ch.configureBlocking(true); + _ch.socket().setTcpNoDelay(true); + if (address != null) + { + _ch.socket().connect(address); + } + while (_ch.isConnectionPending()) + { + + } + + } + catch (SocketException e) + { + + e.printStackTrace(); + } + catch (IOException e) + { + e.printStackTrace(); + } + + NioSender sender = new NioSender(_ch); + Connection con = new Connection(); + con.setSender(new Disassembler(sender, 64*1024 - 1)); + con.setConnectionDelegate(delegate); + + _handlers.put(con.getConnectionId(),sender); + + _receiver = new InputHandler(new Assembler(con), InputHandler.State.FRAME_HDR); + + Thread t = new Thread(this); + t.start(); + + return con; + } + + public void run() + { + _readBuf = ByteBuffer.allocate(512); + long read = 0; + while(_ch.isConnected() && _ch.isOpen()) + { + try + { + read = _ch.read(_readBuf); + if (read > 0) + { + _readBuf.flip(); + ByteBuffer b = ByteBuffer.allocate(_readBuf.remaining()); + b.put(_readBuf); + b.flip(); + _readBuf.clear(); + _receiver.received(b); + } + } + catch(Exception e) + { + e.printStackTrace(); + } + } + + //throw new EOFException("The underlying socket/channel has closed"); + } + + public static void startBatchingFrames(int connectionId) + { + NioSender sender = _handlers.get(connectionId); + sender.setStartBatching(); + } + + +} diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/nio/NioSender.java b/java/common/src/main/java/org/apache/qpid/transport/network/nio/NioSender.java new file mode 100644 index 0000000000..2fa875f279 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/transport/network/nio/NioSender.java @@ -0,0 +1,126 @@ +package org.apache.qpid.transport.network.nio; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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. + * + */ + + +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; + +import org.apache.qpid.transport.Sender; + +public class NioSender implements Sender<java.nio.ByteBuffer> +{ + private final Object lock = new Object(); + private SocketChannel _ch; + private boolean _batch = false; + private ByteBuffer _batcher; + + public NioSender(SocketChannel ch) + { + this._ch = ch; + } + + public void send(java.nio.ByteBuffer buf) + { + if (_batch) + { + //System.out.println(_batcher.position() + " , " + buf.remaining() + " , " + buf.position() + ","+_batcher.capacity()); + if (_batcher.position() + buf.remaining() >= _batcher.capacity()) + { + _batcher.flip(); + write(_batcher); + _batcher.clear(); + if (buf.remaining() > _batcher.capacity()) + { + write(buf); + } + else + { + _batcher.put(buf); + } + } + else + { + _batcher.put(buf); + } + } + else + { + write(buf); + } + } + + public void flush() + { + // pass + } + + private void write(java.nio.ByteBuffer buf) + { + synchronized (lock) + { + if( _ch.isConnected() && _ch.isOpen()) + { + try + { + _ch.write(buf); + } + catch(Exception e) + { + e.fillInStackTrace(); + } + } + else + { + throw new RuntimeException("Trying to write on a closed socket"); + } + + } + } + + public void setStartBatching() + { + _batch = true; + _batcher = ByteBuffer.allocate(1024); + } + + public void close() + { + // MINA will sometimes throw away in-progress writes when you + // ask it to close + synchronized (lock) + { + try + { + _ch.close(); + } + catch(Exception e) + { + e.printStackTrace(); + } + } + } + + public void setIdleTimeout(int i) + { + //noop + } +} diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/security/SSLStatus.java b/java/common/src/main/java/org/apache/qpid/transport/network/security/SSLStatus.java deleted file mode 100644 index 9db7dd557a..0000000000 --- a/java/common/src/main/java/org/apache/qpid/transport/network/security/SSLStatus.java +++ /dev/null @@ -1,49 +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.qpid.transport.network.security; - -import java.util.concurrent.atomic.AtomicBoolean; - -public class SSLStatus -{ - private final Object _sslLock = new Object(); - private final AtomicBoolean _sslErrorFlag = new AtomicBoolean(false); - - /** - * Lock used to coordinate the SSL sender with the SSL receiver. - * - * @return lock - */ - public Object getSslLock() - { - return _sslLock; - } - - public boolean getSslErrorFlag() - { - return _sslErrorFlag.get(); - } - - public void setSslErrorFlag() - { - _sslErrorFlag.set(true); - } -} diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/security/SecurityLayer.java b/java/common/src/main/java/org/apache/qpid/transport/network/security/SecurityLayer.java index 9fd65c6e51..3f0966903d 100644 --- a/java/common/src/main/java/org/apache/qpid/transport/network/security/SecurityLayer.java +++ b/java/common/src/main/java/org/apache/qpid/transport/network/security/SecurityLayer.java @@ -25,8 +25,8 @@ import java.nio.ByteBuffer; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; -import org.apache.qpid.ssl.SSLContextFactory; import org.apache.qpid.transport.Connection; +import org.apache.qpid.transport.ConnectionListener; import org.apache.qpid.transport.ConnectionSettings; import org.apache.qpid.transport.Receiver; import org.apache.qpid.transport.Sender; @@ -37,12 +37,149 @@ import org.apache.qpid.transport.network.security.ssl.SSLReceiver; import org.apache.qpid.transport.network.security.ssl.SSLSender; import org.apache.qpid.transport.network.security.ssl.SSLUtil; -public interface SecurityLayer +public class SecurityLayer { - - public Sender<ByteBuffer> sender(Sender<ByteBuffer> delegate); - public Receiver<ByteBuffer> receiver(Receiver<ByteBuffer> delegate); - public String getUserID(); - + ConnectionSettings settings; + Connection con; + SSLSecurityLayer sslLayer; + SASLSecurityLayer saslLayer; + + public void init(Connection con) throws TransportException + { + this.con = con; + this.settings = con.getConnectionSettings(); + if (settings.isUseSSL()) + { + sslLayer = new SSLSecurityLayer(); + } + if (settings.isUseSASLEncryption()) + { + saslLayer = new SASLSecurityLayer(); + } + + } + + public Sender<ByteBuffer> sender(Sender<ByteBuffer> delegate) + { + Sender<ByteBuffer> sender = delegate; + + if (settings.isUseSSL()) + { + sender = sslLayer.sender(sender); + } + + if (settings.isUseSASLEncryption()) + { + sender = saslLayer.sender(sender); + } + + return sender; + } + + public Receiver<ByteBuffer> receiver(Receiver<ByteBuffer> delegate) + { + Receiver<ByteBuffer> receiver = delegate; + + if (settings.isUseSSL()) + { + receiver = sslLayer.receiver(receiver); + } + + if (settings.isUseSASLEncryption()) + { + receiver = saslLayer.receiver(receiver); + } + + return receiver; + } + + public String getUserID() + { + if (settings.isUseSSL()) + { + return sslLayer.getUserID(); + } + else + { + return null; + } + } + + class SSLSecurityLayer + { + SSLEngine engine; + SSLSender sender; + + public SSLSecurityLayer() + { + SSLContext sslCtx; + try + { + sslCtx = SSLUtil.createSSLContext(settings); + } + catch (Exception e) + { + throw new TransportException("Error creating SSL Context", e); + } + + try + { + engine = sslCtx.createSSLEngine(); + engine.setUseClientMode(true); + } + catch(Exception e) + { + throw new TransportException("Error creating SSL Engine", e); + } + } + + public SSLSender sender(Sender<ByteBuffer> delegate) + { + sender = new SSLSender(engine,delegate); + sender.setConnectionSettings(settings); + return sender; + } + + public SSLReceiver receiver(Receiver<ByteBuffer> delegate) + { + if (sender == null) + { + throw new + IllegalStateException("SecurityLayer.sender method should be " + + "invoked before SecurityLayer.receiver"); + } + + SSLReceiver receiver = new SSLReceiver(engine,delegate,sender); + receiver.setConnectionSettings(settings); + return receiver; + } + + public String getUserID() + { + return SSLUtil.retriveIdentity(engine); + } + + } + + class SASLSecurityLayer + { + public SASLSecurityLayer() + { + } + + public SASLSender sender(Sender<ByteBuffer> delegate) + { + SASLSender sender = new SASLSender(delegate); + con.addConnectionListener((ConnectionListener)sender); + return sender; + } + + public SASLReceiver receiver(Receiver<ByteBuffer> delegate) + { + SASLReceiver receiver = new SASLReceiver(delegate); + con.addConnectionListener((ConnectionListener)receiver); + return receiver; + } + + } } - diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/security/SecurityLayerFactory.java b/java/common/src/main/java/org/apache/qpid/transport/network/security/SecurityLayerFactory.java deleted file mode 100644 index 17f89c34ef..0000000000 --- a/java/common/src/main/java/org/apache/qpid/transport/network/security/SecurityLayerFactory.java +++ /dev/null @@ -1,161 +0,0 @@ -package org.apache.qpid.transport.network.security; - -import org.apache.qpid.ssl.SSLContextFactory; -import org.apache.qpid.transport.*; -import org.apache.qpid.transport.network.security.sasl.SASLReceiver; -import org.apache.qpid.transport.network.security.sasl.SASLSender; -import org.apache.qpid.transport.network.security.ssl.SSLReceiver; -import org.apache.qpid.transport.network.security.ssl.SSLSender; -import org.apache.qpid.transport.network.security.ssl.SSLUtil; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; -import java.nio.ByteBuffer; - -public class SecurityLayerFactory -{ - public static SecurityLayer newInstance(ConnectionSettings settings) - { - - SecurityLayer layer = NullSecurityLayer.getInstance(); - - if (settings.isUseSSL()) - { - layer = new SSLSecurityLayer(settings, layer); - } - if (settings.isUseSASLEncryption()) - { - layer = new SASLSecurityLayer(layer); - } - - return layer; - - } - - static class SSLSecurityLayer implements SecurityLayer - { - - private final SSLEngine _engine; - private final SSLStatus _sslStatus = new SSLStatus(); - private String _hostname; - private SecurityLayer _layer; - - - public SSLSecurityLayer(ConnectionSettings settings, SecurityLayer layer) - { - - SSLContext sslCtx; - _layer = layer; - try - { - sslCtx = SSLContextFactory - .buildClientContext(settings.getTrustStorePath(), - settings.getTrustStorePassword(), - settings.getTrustStoreCertType(), - settings.getKeyStorePath(), - settings.getKeyStorePassword(), - settings.getKeyStoreCertType(), - settings.getCertAlias()); - } - catch (Exception e) - { - throw new TransportException("Error creating SSL Context", e); - } - - if(settings.isVerifyHostname()) - { - _hostname = settings.getHost(); - } - - try - { - _engine = sslCtx.createSSLEngine(); - _engine.setUseClientMode(true); - } - catch(Exception e) - { - throw new TransportException("Error creating SSL Engine", e); - } - - } - - public Sender<ByteBuffer> sender(Sender<ByteBuffer> delegate) - { - SSLSender sender = new SSLSender(_engine, _layer.sender(delegate), _sslStatus); - sender.setHostname(_hostname); - return sender; - } - - public Receiver<ByteBuffer> receiver(Receiver<ByteBuffer> delegate) - { - SSLReceiver receiver = new SSLReceiver(_engine, _layer.receiver(delegate), _sslStatus); - receiver.setHostname(_hostname); - return receiver; - } - - public String getUserID() - { - return SSLUtil.retriveIdentity(_engine); - } - } - - - static class SASLSecurityLayer implements SecurityLayer - { - - private SecurityLayer _layer; - - SASLSecurityLayer(SecurityLayer layer) - { - _layer = layer; - } - - public SASLSender sender(Sender<ByteBuffer> delegate) - { - SASLSender sender = new SASLSender(_layer.sender(delegate)); - return sender; - } - - public SASLReceiver receiver(Receiver<ByteBuffer> delegate) - { - SASLReceiver receiver = new SASLReceiver(_layer.receiver(delegate)); - return receiver; - } - - public String getUserID() - { - return _layer.getUserID(); - } - } - - - static class NullSecurityLayer implements SecurityLayer - { - - private static final NullSecurityLayer INSTANCE = new NullSecurityLayer(); - - private NullSecurityLayer() - { - } - - public Sender<ByteBuffer> sender(Sender<ByteBuffer> delegate) - { - return delegate; - } - - public Receiver<ByteBuffer> receiver(Receiver<ByteBuffer> delegate) - { - return delegate; - } - - public String getUserID() - { - return null; - } - - public static NullSecurityLayer getInstance() - { - return INSTANCE; - } - } -} diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/security/sasl/SASLSender.java b/java/common/src/main/java/org/apache/qpid/transport/network/security/sasl/SASLSender.java index 2d9e4e9a7e..27255f79f6 100644 --- a/java/common/src/main/java/org/apache/qpid/transport/network/security/sasl/SASLSender.java +++ b/java/common/src/main/java/org/apache/qpid/transport/network/security/sasl/SASLSender.java @@ -43,7 +43,8 @@ public class SASLSender extends SASLEncryptor implements Sender<ByteBuffer> { this.delegate = delegate; log.debug("SASL Sender enabled"); } - + + @Override public void close() { @@ -64,11 +65,13 @@ public class SASLSender extends SASLEncryptor implements Sender<ByteBuffer> { } } + @Override public void flush() { delegate.flush(); } + @Override public void send(ByteBuffer buf) { if (closed.get()) @@ -105,6 +108,7 @@ public class SASLSender extends SASLEncryptor implements Sender<ByteBuffer> { } } + @Override public void setIdleTimeout(int i) { delegate.setIdleTimeout(i); diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/QpidClientX509KeyManager.java b/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/QpidClientX509KeyManager.java index 4391e8adfc..14f28f8828 100644 --- a/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/QpidClientX509KeyManager.java +++ b/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/QpidClientX509KeyManager.java @@ -20,9 +20,7 @@ */ package org.apache.qpid.transport.network.security.ssl; -import java.io.IOException; import java.net.Socket; -import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.Principal; import java.security.PrivateKey; @@ -42,7 +40,7 @@ public class QpidClientX509KeyManager extends X509ExtendedKeyManager String alias; public QpidClientX509KeyManager(String alias, String keyStorePath, - String keyStorePassword,String keyStoreCertType) throws GeneralSecurityException, IOException + String keyStorePassword,String keyStoreCertType) throws Exception { this.alias = alias; KeyStore ks = SSLUtil.getInitializedKeyStore(keyStorePath,keyStorePassword); @@ -50,45 +48,51 @@ public class QpidClientX509KeyManager extends X509ExtendedKeyManager kmf.init(ks, keyStorePassword.toCharArray()); this.delegate = (X509ExtendedKeyManager)kmf.getKeyManagers()[0]; } - + + @Override public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) { log.debug("chooseClientAlias:Returning alias " + alias); return alias; } + @Override public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { return delegate.chooseServerAlias(keyType, issuers, socket); } + @Override public X509Certificate[] getCertificateChain(String alias) { return delegate.getCertificateChain(alias); } + @Override public String[] getClientAliases(String keyType, Principal[] issuers) { log.debug("getClientAliases:Returning alias " + alias); return new String[]{alias}; } + @Override public PrivateKey getPrivateKey(String alias) { return delegate.getPrivateKey(alias); } + @Override public String[] getServerAliases(String keyType, Principal[] issuers) { return delegate.getServerAliases(keyType, issuers); } - + public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) { log.debug("chooseEngineClientAlias:Returning alias " + alias); return alias; } - + public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) { return delegate.chooseEngineServerAlias(keyType, issuers, engine); diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/SSLReceiver.java b/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/SSLReceiver.java index 8ad40bbfd3..e227a51729 100644 --- a/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/SSLReceiver.java +++ b/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/SSLReceiver.java @@ -24,43 +24,43 @@ import java.nio.ByteBuffer; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLException; import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.net.ssl.SSLEngineResult.Status; -import javax.net.ssl.SSLException; import org.apache.qpid.transport.ConnectionSettings; import org.apache.qpid.transport.Receiver; import org.apache.qpid.transport.TransportException; -import org.apache.qpid.transport.network.security.SSLStatus; import org.apache.qpid.transport.util.Logger; public class SSLReceiver implements Receiver<ByteBuffer> { - private static final Logger log = Logger.get(SSLReceiver.class); - - private final Receiver<ByteBuffer> delegate; - private final SSLEngine engine; - private final int sslBufSize; - private final ByteBuffer localBuffer; - private final SSLStatus _sslStatus; + private Receiver<ByteBuffer> delegate; + private SSLEngine engine; + private SSLSender sender; + private int sslBufSize; private ByteBuffer appData; + private ByteBuffer localBuffer; private boolean dataCached = false; + private final Object notificationToken; + private ConnectionSettings settings; + + private static final Logger log = Logger.get(SSLReceiver.class); - private String _hostname; - - public SSLReceiver(final SSLEngine engine, final Receiver<ByteBuffer> delegate, final SSLStatus sslStatus) + public SSLReceiver(SSLEngine engine, Receiver<ByteBuffer> delegate,SSLSender sender) { this.engine = engine; this.delegate = delegate; + this.sender = sender; this.sslBufSize = engine.getSession().getApplicationBufferSize(); appData = ByteBuffer.allocate(sslBufSize); localBuffer = ByteBuffer.allocate(sslBufSize); - _sslStatus = sslStatus; + notificationToken = sender.getNotificationToken(); } - public void setHostname(String hostname) + public void setConnectionSettings(ConnectionSettings settings) { - _hostname = hostname; + this.settings = settings; } public void closed() @@ -102,9 +102,9 @@ public class SSLReceiver implements Receiver<ByteBuffer> try { SSLEngineResult result = engine.unwrap(netData, appData); - synchronized (_sslStatus.getSslLock()) + synchronized (notificationToken) { - _sslStatus.getSslLock().notifyAll(); + notificationToken.notifyAll(); } int read = result.bytesProduced(); @@ -129,9 +129,9 @@ public class SSLReceiver implements Receiver<ByteBuffer> switch(status) { case CLOSED: - synchronized(_sslStatus.getSslLock()) + synchronized(notificationToken) { - _sslStatus.getSslLock().notifyAll(); + notificationToken.notifyAll(); } return; @@ -163,20 +163,20 @@ public class SSLReceiver implements Receiver<ByteBuffer> break; case NEED_TASK: - doTasks(); + sender.doTasks(); handshakeStatus = engine.getHandshakeStatus(); case FINISHED: - if (_hostname != null) + if (this.settings != null && this.settings.isVerifyHostname() ) { - SSLUtil.verifyHostname(engine, _hostname); + SSLUtil.verifyHostname(engine, this.settings.getHost()); } case NEED_WRAP: case NOT_HANDSHAKING: - synchronized(_sslStatus.getSslLock()) + synchronized(notificationToken) { - _sslStatus.getSslLock().notifyAll(); + notificationToken.notifyAll(); } break; @@ -189,23 +189,14 @@ public class SSLReceiver implements Receiver<ByteBuffer> catch(SSLException e) { log.error(e, "Error caught in SSLReceiver"); - _sslStatus.setSslErrorFlag(); - synchronized(_sslStatus.getSslLock()) + sender.setErrorFlag(); + synchronized(notificationToken) { - _sslStatus.getSslLock().notifyAll(); + notificationToken.notifyAll(); } exception(new TransportException("Error in SSLReceiver",e)); } } } - - private void doTasks() - { - Runnable runnable; - while ((runnable = engine.getDelegatedTask()) != null) { - runnable.run(); - } - } - } diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/SSLSender.java b/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/SSLSender.java index 6f5aa6d86e..cd47a11825 100644 --- a/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/SSLSender.java +++ b/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/SSLSender.java @@ -31,38 +31,35 @@ import javax.net.ssl.SSLEngineResult.Status; import org.apache.qpid.transport.ConnectionSettings; import org.apache.qpid.transport.Sender; import org.apache.qpid.transport.SenderException; -import org.apache.qpid.transport.network.security.SSLStatus; import org.apache.qpid.transport.util.Logger; public class SSLSender implements Sender<ByteBuffer> { - private static final Logger log = Logger.get(SSLSender.class); - - private final Sender<ByteBuffer> delegate; - private final SSLEngine engine; - private final int sslBufSize; - private final ByteBuffer netData; - private final long timeout; - private final SSLStatus _sslStatus; - - private String _hostname; - + private Sender<ByteBuffer> delegate; + private SSLEngine engine; + private int sslBufSize; + private ByteBuffer netData; + private long timeout = 30000; + private ConnectionSettings settings; + + private final Object engineState = new Object(); private final AtomicBoolean closed = new AtomicBoolean(false); + private final AtomicBoolean error = new AtomicBoolean(false); + private static final Logger log = Logger.get(SSLSender.class); - public SSLSender(SSLEngine engine, Sender<ByteBuffer> delegate, SSLStatus sslStatus) + public SSLSender(SSLEngine engine, Sender<ByteBuffer> delegate) { this.engine = engine; this.delegate = delegate; sslBufSize = engine.getSession().getPacketBufferSize(); netData = ByteBuffer.allocate(sslBufSize); timeout = Long.getLong("qpid.ssl_timeout", 60000); - _sslStatus = sslStatus; } - public void setHostname(String hostname) + public void setConnectionSettings(ConnectionSettings settings) { - _hostname = hostname; + this.settings = settings; } public void close() @@ -86,13 +83,13 @@ public class SSLSender implements Sender<ByteBuffer> } - synchronized(_sslStatus.getSslLock()) + synchronized(engineState) { while (!engine.isOutboundDone()) { try { - _sslStatus.getSslLock().wait(); + engineState.wait(); } catch(InterruptedException e) { @@ -151,7 +148,7 @@ public class SSLSender implements Sender<ByteBuffer> HandshakeStatus handshakeStatus; Status status; - while(appData.hasRemaining() && !_sslStatus.getSslErrorFlag()) + while(appData.hasRemaining() && !error.get()) { int read = 0; try @@ -163,7 +160,6 @@ public class SSLSender implements Sender<ByteBuffer> } catch(SSLException e) { - // Should this set _sslError?? throw new SenderException("SSL, Error occurred while encrypting data",e); } @@ -211,7 +207,7 @@ public class SSLSender implements Sender<ByteBuffer> case NEED_UNWRAP: flush(); - synchronized(_sslStatus.getSslLock()) + synchronized(engineState) { switch (engine.getHandshakeStatus()) { @@ -219,7 +215,7 @@ public class SSLSender implements Sender<ByteBuffer> long start = System.currentTimeMillis(); try { - _sslStatus.getSslLock().wait(timeout); + engineState.wait(timeout); } catch(InterruptedException e) { @@ -238,9 +234,9 @@ public class SSLSender implements Sender<ByteBuffer> break; case FINISHED: - if (_hostname != null) + if (this.settings != null && this.settings.isVerifyHostname() ) { - SSLUtil.verifyHostname(engine, _hostname); + SSLUtil.verifyHostname(engine, this.settings.getHost()); } case NOT_HANDSHAKING: @@ -253,7 +249,7 @@ public class SSLSender implements Sender<ByteBuffer> } } - private void doTasks() + public void doTasks() { Runnable runnable; while ((runnable = engine.getDelegatedTask()) != null) { @@ -261,6 +257,16 @@ public class SSLSender implements Sender<ByteBuffer> } } + public Object getNotificationToken() + { + return engineState; + } + + public void setErrorFlag() + { + error.set(true); + } + public void setIdleTimeout(int i) { delegate.setIdleTimeout(i); diff --git a/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/SSLUtil.java b/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/SSLUtil.java index 6bb038a581..fd73915b65 100644 --- a/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/SSLUtil.java +++ b/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/SSLUtil.java @@ -125,6 +125,38 @@ public class SSLUtil return id.toString(); } + public static SSLContext createSSLContext(ConnectionSettings settings) throws Exception + { + SSLContextFactory sslContextFactory; + + if (settings.getCertAlias() == null) + { + sslContextFactory = + new SSLContextFactory(settings.getTrustStorePath(), + settings.getTrustStorePassword(), + settings.getTrustStoreCertType(), + settings.getKeyStorePath(), + settings.getKeyStorePassword(), + settings.getKeyStoreCertType()); + + } else + { + sslContextFactory = + new SSLContextFactory(settings.getTrustStorePath(), + settings.getTrustStorePassword(), + settings.getTrustStoreCertType(), + new QpidClientX509KeyManager(settings.getCertAlias(), + settings.getKeyStorePath(), + settings.getKeyStorePassword(), + settings.getKeyStoreCertType())); + + log.debug("Using custom key manager"); + } + + return sslContextFactory.buildServerContext(); + + } + public static KeyStore getInitializedKeyStore(String storePath, String storePassword) throws GeneralSecurityException, IOException { KeyStore ks = KeyStore.getInstance("JKS"); @@ -144,10 +176,7 @@ public class SSLUtil { throw new IOException("Unable to load keystore resource: " + storePath); } - - char[] storeCharPassword = storePassword == null ? null : storePassword.toCharArray(); - - ks.load(in, storeCharPassword); + ks.load(in, storePassword.toCharArray()); } finally { diff --git a/java/common/src/main/java/org/apache/qpid/url/URLHelper.java b/java/common/src/main/java/org/apache/qpid/url/URLHelper.java index e261860bf3..6f21c327e7 100644 --- a/java/common/src/main/java/org/apache/qpid/url/URLHelper.java +++ b/java/common/src/main/java/org/apache/qpid/url/URLHelper.java @@ -31,6 +31,9 @@ public class URLHelper public static void parseOptions(Map<String, String> optionMap, String options) throws URLSyntaxException { + // options looks like this + // brokerlist='tcp://host:port?option='value',option='value';vm://:3/virtualpath?option='value'',failover='method?option='value',option='value'' + if ((options == null) || (options.indexOf('=') == -1)) { return; diff --git a/java/common/src/main/java/org/apache/qpid/util/FileUtils.java b/java/common/src/main/java/org/apache/qpid/util/FileUtils.java index ac8e3da3c2..516204fbd3 100644 --- a/java/common/src/main/java/org/apache/qpid/util/FileUtils.java +++ b/java/common/src/main/java/org/apache/qpid/util/FileUtils.java @@ -143,9 +143,8 @@ public class FileUtils } /** - * Either opens the specified filename as an input stream or either the filesystem or classpath, - * or uses the default resource loaded using the specified class loader, if opening the file fails - * or no file name is specified. + * Either opens the specified filename as an input stream, or uses the default resource loaded using the + * specified class loader, if opening the file fails or no file name is specified. * * @param filename The name of the file to open. * @param defaultResource The name of the default resource on the classpath if the file cannot be opened. @@ -157,28 +156,28 @@ public class FileUtils { InputStream is = null; + // Flag to indicate whether the default resource should be used. By default this is true, so that the default + // is used when opening the file fails. + boolean useDefault = true; + // Try to open the file if one was specified. if (filename != null) { - // try on filesystem try { is = new BufferedInputStream(new FileInputStream(new File(filename))); + + // Clear the default flag because the file was succesfully opened. + useDefault = false; } catch (FileNotFoundException e) { - is = null; - } - - if (is == null) - { - // failed on filesystem, so try on classpath - is = cl.getResourceAsStream(filename); + // Ignore this exception, the default will be used instead. } } // Load the default resource if a file was not specified, or if opening the file failed. - if (is == null) + if (useDefault) { is = cl.getResourceAsStream(defaultResource); } @@ -340,7 +339,7 @@ public class FileUtils } //else we have a source directory - if (!dst.isDirectory() && !dst.mkdirs()) + if (!dst.isDirectory() && !dst.mkdir()) { throw new UnableToCopyException("Unable to create destination directory"); } diff --git a/java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterClient.java b/java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterClient.java new file mode 100644 index 0000000000..b93dc46741 --- /dev/null +++ b/java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterClient.java @@ -0,0 +1,396 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "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.mina.SocketIOTest; + +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.CloseFuture; +import org.apache.mina.common.ConnectFuture; +import org.apache.mina.common.IoConnector; +import org.apache.mina.common.IoFilterChain; +import org.apache.mina.common.IoHandlerAdapter; +import org.apache.mina.common.IoSession; +import org.apache.mina.common.SimpleByteBufferAllocator; +import org.apache.mina.filter.ReadThrottleFilterBuilder; +import org.apache.mina.filter.WriteBufferLimitFilterBuilder; +import org.apache.mina.transport.socket.nio.SocketSessionConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.concurrent.CountDownLatch; + +public class IOWriterClient implements Runnable +{ + private static final Logger _logger = LoggerFactory.getLogger(IOWriterClient.class); + + public static int DEFAULT_TEST_SIZE = 2; + + private IoSession _session; + + private long _startTime; + + private long[] _chunkTimes; + + public int _chunkCount = 200000; + + private int _chunkSize = 1024; + + private CountDownLatch _notifier; + + private int _maximumWriteQueueLength; + + static public int _PORT = IOWriterServer._PORT; + + public void run() + { + _logger.info("Starting to send " + _chunkCount + " buffers of " + _chunkSize + "B"); + _startTime = System.currentTimeMillis(); + _notifier = new CountDownLatch(1); + + for (int i = 0; i < _chunkCount; i++) + { + ByteBuffer buf = ByteBuffer.allocate(_chunkSize, false); + byte check = (byte) (i % 128); + buf.put(check); + buf.fill((byte) 88, buf.remaining()); + buf.flip(); + + _session.write(buf); + } + + long _sentall = System.currentTimeMillis(); + long _receivedall = _sentall; + try + { + _logger.info("All buffers sent; waiting for receipt from server"); + _notifier.await(); + _receivedall = System.currentTimeMillis(); + } + catch (InterruptedException e) + { + //Ignore + } + _logger.info("Completed"); + _logger.info("Total time waiting for server after last write: " + (_receivedall - _sentall)); + + long totalTime = System.currentTimeMillis() - _startTime; + + _logger.info("Total time: " + totalTime); + _logger.info("MB per second: " + (int) ((1.0 * _chunkSize * _chunkCount) / totalTime)); + long lastChunkTime = _startTime; + double average = 0; + for (int i = 0; i < _chunkTimes.length; i++) + { + if (i == 0) + { + average = _chunkTimes[i] - _startTime; + } + else + { + long delta = _chunkTimes[i] - lastChunkTime; + if (delta != 0) + { + average = (average + delta) / 2; + } + } + lastChunkTime = _chunkTimes[i]; + } + _logger.info("Average chunk time: " + average + "ms"); + _logger.info("Maximum WriteRequestQueue size: " + _maximumWriteQueueLength); + + CloseFuture cf = _session.close(); + _logger.info("Closing session"); + cf.join(); + } + + private class WriterHandler extends IoHandlerAdapter + { + private int _chunksReceived = 0; + + private int _partialBytesRead = 0; + + private byte _partialCheckNumber; + + private int _totalBytesReceived = 0; + + private int _receivedCount = 0; + private int _sentCount = 0; + private static final String DEFAULT_READ_BUFFER = "262144"; + private static final String DEFAULT_WRITE_BUFFER = "262144"; + + public void sessionCreated(IoSession session) throws Exception + { + IoFilterChain chain = session.getFilterChain(); + + ReadThrottleFilterBuilder readfilter = new ReadThrottleFilterBuilder(); + readfilter.setMaximumConnectionBufferSize(Integer.parseInt(System.getProperty("qpid.read.buffer.limit", DEFAULT_READ_BUFFER))); + readfilter.attach(chain); + + WriteBufferLimitFilterBuilder writefilter = new WriteBufferLimitFilterBuilder(); + + writefilter.setMaximumConnectionBufferSize(Integer.parseInt(System.getProperty("qpid.write.buffer.limit", DEFAULT_WRITE_BUFFER))); + + writefilter.attach(chain); + } + + public void messageSent(IoSession session, Object message) throws Exception + { + _maximumWriteQueueLength = Math.max(session.getScheduledWriteRequests(), _maximumWriteQueueLength); + + if (_logger.isDebugEnabled()) + { + ++_sentCount; + if (_sentCount % 1000 == 0) + { + _logger.debug("Sent count " + _sentCount + ":WQueue" + session.getScheduledWriteRequests()); + + } + } + } + + public void messageReceived(IoSession session, Object message) throws Exception + { + if (_logger.isDebugEnabled()) + { + ++_receivedCount; + + if (_receivedCount % 1000 == 0) + { + _logger.debug("Receieved count " + _receivedCount); + } + } + + ByteBuffer result = (ByteBuffer) message; + _totalBytesReceived += result.remaining(); + int size = result.remaining(); + long now = System.currentTimeMillis(); + if (_partialBytesRead > 0) + { + int offset = _chunkSize - _partialBytesRead; + if (size >= offset) + { + _chunkTimes[_chunksReceived++] = now; + result.position(offset); + } + else + { + // have not read even one chunk, including the previous partial bytes + _partialBytesRead += size; + return; + } + } + + + int chunkCount = result.remaining() / _chunkSize; + + for (int i = 0; i < chunkCount; i++) + { + _chunkTimes[_chunksReceived++] = now; + byte check = result.get(); + _logger.debug("Check number " + check + " read"); + if (check != (byte) ((_chunksReceived - 1) % 128)) + { + _logger.error("Check number " + check + " read when expected " + (_chunksReceived % 128)); + } + _logger.debug("Chunk times recorded"); + + try + { + result.skip(_chunkSize - 1); + } + catch (IllegalArgumentException e) + { + _logger.error("Position was: " + result.position()); + _logger.error("Tried to skip to: " + (_chunkSize * i)); + _logger.error("limit was; " + result.limit()); + } + } + _logger.debug("Chunks received now " + _chunksReceived); + _logger.debug("Bytes received: " + _totalBytesReceived); + _partialBytesRead = result.remaining(); + + if (_partialBytesRead > 0) + { + _partialCheckNumber = result.get(); + } + + + if (_chunksReceived >= _chunkCount) + { + _notifier.countDown(); + } + + } + + public void exceptionCaught(IoSession session, Throwable cause) throws Exception + { + _logger.error("Error: " + cause, cause); + } + } + + public void startWriter() throws IOException, InterruptedException + { + + _maximumWriteQueueLength = 0; + + IoConnector ioConnector = null; + + if (Boolean.getBoolean("multinio")) + { + _logger.warn("Using MultiThread NIO"); + ioConnector = new org.apache.mina.transport.socket.nio.MultiThreadSocketConnector(); + } + else + { + _logger.warn("Using MINA NIO"); + ioConnector = new org.apache.mina.transport.socket.nio.SocketConnector(); + } + + SocketSessionConfig scfg = (SocketSessionConfig) ioConnector.getDefaultConfig().getSessionConfig(); + scfg.setTcpNoDelay(true); + scfg.setSendBufferSize(32768); + scfg.setReceiveBufferSize(32768); + + ByteBuffer.setAllocator(new SimpleByteBufferAllocator()); + + + final InetSocketAddress address = new InetSocketAddress("localhost", _PORT); + _logger.info("Attempting connection to " + address); + + //Old mina style +// ioConnector.setHandler(new WriterHandler()); +// ConnectFuture future = ioConnector.connect(address); + ConnectFuture future = ioConnector.connect(address, new WriterHandler()); + // wait for connection to complete + future.join(); + _logger.info("Connection completed"); + // we call getSession which throws an IOException if there has been an error connecting + _session = future.getSession(); + + _chunkTimes = new long[_chunkCount]; + Thread t = new Thread(this); + t.start(); + t.join(); + _logger.info("Test Complete"); + } + + + public void test1k() throws IOException, InterruptedException + { + _logger.info("Starting 1k test"); + _chunkSize = 1024; + startWriter(); + } + + + public void test2k() throws IOException, InterruptedException + { + _logger.info("Starting 2k test"); + _chunkSize = 2048; + startWriter(); + } + + + public void test4k() throws IOException, InterruptedException + { + _logger.info("Starting 4k test"); + _chunkSize = 4096; + startWriter(); + } + + + public void test8k() throws IOException, InterruptedException + { + _logger.info("Starting 8k test"); + _chunkSize = 8192; + startWriter(); + } + + + public void test16k() throws IOException, InterruptedException + { + _logger.info("Starting 16k test"); + _chunkSize = 16384; + startWriter(); + } + + + public void test32k() throws IOException, InterruptedException + { + _logger.info("Starting 32k test"); + _chunkSize = 32768; + startWriter(); + } + + + public static int getIntArg(String[] args, int index, int defaultValue) + { + if (args.length > index) + { + try + { + return Integer.parseInt(args[index]); + } + catch (NumberFormatException e) + { + //Do nothing + } + } + return defaultValue; + } + + public static void main(String[] args) throws IOException, InterruptedException + { + _PORT = getIntArg(args, 0, _PORT); + + int test = getIntArg(args, 1, DEFAULT_TEST_SIZE); + + IOWriterClient w = new IOWriterClient(); + w._chunkCount = getIntArg(args, 2, w._chunkCount); + switch (test) + { + case 0: + w.test1k(); + w.test2k(); + w.test4k(); + w.test8k(); + w.test16k(); + w.test32k(); + break; + case 1: + w.test1k(); + break; + case 2: + w.test2k(); + break; + case 4: + w.test4k(); + break; + case 8: + w.test8k(); + break; + case 16: + w.test16k(); + break; + case 32: + w.test32k(); + break; + } + } +} diff --git a/java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterServer.java b/java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterServer.java new file mode 100644 index 0000000000..423e98c67b --- /dev/null +++ b/java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterServer.java @@ -0,0 +1,157 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "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.mina.SocketIOTest; + +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.IoAcceptor; +import org.apache.mina.common.IoFilterChain; +import org.apache.mina.common.IoHandlerAdapter; +import org.apache.mina.common.IoSession; +import org.apache.mina.common.SimpleByteBufferAllocator; +import org.apache.mina.filter.ReadThrottleFilterBuilder; +import org.apache.mina.filter.WriteBufferLimitFilterBuilder; +import org.apache.mina.transport.socket.nio.SocketSessionConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.InetSocketAddress; + +/** Tests MINA socket performance. This acceptor simply reads data from the network and writes it back again. */ +public class IOWriterServer +{ + private static final Logger _logger = LoggerFactory.getLogger(IOWriterServer.class); + + static public int _PORT = 9999; + + private static final String DEFAULT_READ_BUFFER = "262144"; + private static final String DEFAULT_WRITE_BUFFER = "262144"; + + + private static class TestHandler extends IoHandlerAdapter + { + private int _sentCount = 0; + + private int _bytesSent = 0; + + private int _receivedCount = 0; + + public void sessionCreated(IoSession ioSession) throws java.lang.Exception + { + IoFilterChain chain = ioSession.getFilterChain(); + + ReadThrottleFilterBuilder readfilter = new ReadThrottleFilterBuilder(); + readfilter.setMaximumConnectionBufferSize(Integer.parseInt(System.getProperty("qpid.read.buffer.limit", DEFAULT_READ_BUFFER))); + readfilter.attach(chain); + + WriteBufferLimitFilterBuilder writefilter = new WriteBufferLimitFilterBuilder(); + + writefilter.setMaximumConnectionBufferSize(Integer.parseInt(System.getProperty("qpid.write.buffer.limit", DEFAULT_WRITE_BUFFER))); + + writefilter.attach(chain); + + } + + public void messageReceived(IoSession session, Object message) throws Exception + { + ((ByteBuffer) message).acquire(); + session.write(message); + + if (_logger.isDebugEnabled()) + { + _bytesSent += ((ByteBuffer) message).remaining(); + + _sentCount++; + + if (_sentCount % 1000 == 0) + { + _logger.debug("Bytes sent: " + _bytesSent); + } + } + } + + public void messageSent(IoSession session, Object message) throws Exception + { + if (_logger.isDebugEnabled()) + { + ++_receivedCount; + + if (_receivedCount % 1000 == 0) + { + _logger.debug("Receieved count " + _receivedCount); + } + } + } + + public void exceptionCaught(IoSession session, Throwable cause) throws Exception + { + _logger.error("Error: " + cause, cause); + } + } + + public void startAcceptor() throws IOException + { + IoAcceptor acceptor; + if (Boolean.getBoolean("multinio")) + { + _logger.warn("Using MultiThread NIO"); + acceptor = new org.apache.mina.transport.socket.nio.MultiThreadSocketAcceptor(); + } + else + { + _logger.warn("Using MINA NIO"); + acceptor = new org.apache.mina.transport.socket.nio.SocketAcceptor(); + } + + + SocketSessionConfig sc = (SocketSessionConfig) acceptor.getDefaultConfig().getSessionConfig(); + sc.setTcpNoDelay(true); + sc.setSendBufferSize(32768); + sc.setReceiveBufferSize(32768); + + ByteBuffer.setAllocator(new SimpleByteBufferAllocator()); + + //The old mina style +// acceptor.setLocalAddress(new InetSocketAddress(_PORT)); +// acceptor.setHandler(new TestHandler()); +// acceptor.bind(); + acceptor.bind(new InetSocketAddress(_PORT), new TestHandler()); + + _logger.info("Bound on port " + _PORT + ":" + _logger.isDebugEnabled()); + _logger.debug("debug on"); + } + + public static void main(String[] args) throws IOException + { + + if (args.length > 0) + { + try + { + _PORT = Integer.parseInt(args[0]); + } + catch (NumberFormatException e) + { + //IGNORE so use default port 9999; + } + } + + IOWriterServer a = new IOWriterServer(); + a.startAcceptor(); + } +} diff --git a/java/common/src/test/java/org/apache/qpid/AMQExceptionTest.java b/java/common/src/test/java/org/apache/qpid/AMQExceptionTest.java index f65427e583..ef6cd41492 100644 --- a/java/common/src/test/java/org/apache/qpid/AMQExceptionTest.java +++ b/java/common/src/test/java/org/apache/qpid/AMQExceptionTest.java @@ -23,7 +23,6 @@ package org.apache.qpid; import junit.framework.TestCase; import org.apache.qpid.protocol.AMQConstant; import org.apache.qpid.framing.AMQFrameDecodingException; -import org.apache.qpid.framing.AMQShortString; /** * This test is to ensure that when an AMQException is rethrown that the specified exception is correctly wrapped up. @@ -92,18 +91,6 @@ public class AMQExceptionTest extends TestCase return amqe; } - public void testGetMessageAsString() - { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < 25; i++) - { - sb.append("message [" + i + "]"); - } - AMQException e = new AMQException(AMQConstant.INTERNAL_ERROR, sb.toString(), null); - AMQShortString message = e.getMessageAsShortString(); - assertEquals(sb.substring(0, AMQShortString.MAX_LENGTH - 3) + "...", message.toString()); - } - /** * Private class that extends AMQException but does not have a default exception. */ diff --git a/java/common/src/test/java/org/apache/qpid/codec/AMQDecoderTest.java b/java/common/src/test/java/org/apache/qpid/codec/AMQDecoderTest.java index 272eb75800..62e25e7d79 100644 --- a/java/common/src/test/java/org/apache/qpid/codec/AMQDecoderTest.java +++ b/java/common/src/test/java/org/apache/qpid/codec/AMQDecoderTest.java @@ -21,9 +21,6 @@ package org.apache.qpid.codec; */ -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -49,16 +46,9 @@ public class AMQDecoderTest extends TestCase } - private ByteBuffer getHeartbeatBodyBuffer() throws IOException + public void testSingleFrameDecode() throws AMQProtocolVersionException, AMQFrameDecodingException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - HeartbeatBody.FRAME.writePayload(new DataOutputStream(baos)); - return ByteBuffer.wrap(baos.toByteArray()); - } - - public void testSingleFrameDecode() throws AMQProtocolVersionException, AMQFrameDecodingException, IOException - { - ByteBuffer msg = getHeartbeatBodyBuffer(); + ByteBuffer msg = HeartbeatBody.FRAME.toNioByteBuffer(); ArrayList<AMQDataBlock> frames = _decoder.decodeBuffer(msg); if (frames.get(0) instanceof AMQFrame) { @@ -70,9 +60,9 @@ public class AMQDecoderTest extends TestCase } } - public void testPartialFrameDecode() throws AMQProtocolVersionException, AMQFrameDecodingException, IOException + public void testPartialFrameDecode() throws AMQProtocolVersionException, AMQFrameDecodingException { - ByteBuffer msg = getHeartbeatBodyBuffer(); + ByteBuffer msg = HeartbeatBody.FRAME.toNioByteBuffer(); ByteBuffer msgA = msg.slice(); int msgbPos = msg.remaining() / 2; int msgaLimit = msg.remaining() - msgbPos; @@ -93,10 +83,10 @@ public class AMQDecoderTest extends TestCase } } - public void testMultipleFrameDecode() throws AMQProtocolVersionException, AMQFrameDecodingException, IOException + public void testMultipleFrameDecode() throws AMQProtocolVersionException, AMQFrameDecodingException { - ByteBuffer msgA = getHeartbeatBodyBuffer(); - ByteBuffer msgB = getHeartbeatBodyBuffer(); + ByteBuffer msgA = HeartbeatBody.FRAME.toNioByteBuffer(); + ByteBuffer msgB = HeartbeatBody.FRAME.toNioByteBuffer(); ByteBuffer msg = ByteBuffer.allocate(msgA.remaining() + msgB.remaining()); msg.put(msgA); msg.put(msgB); @@ -116,11 +106,11 @@ public class AMQDecoderTest extends TestCase } } - public void testMultiplePartialFrameDecode() throws AMQProtocolVersionException, AMQFrameDecodingException, IOException + public void testMultiplePartialFrameDecode() throws AMQProtocolVersionException, AMQFrameDecodingException { - ByteBuffer msgA = getHeartbeatBodyBuffer(); - ByteBuffer msgB = getHeartbeatBodyBuffer(); - ByteBuffer msgC = getHeartbeatBodyBuffer(); + ByteBuffer msgA = HeartbeatBody.FRAME.toNioByteBuffer(); + ByteBuffer msgB = HeartbeatBody.FRAME.toNioByteBuffer(); + ByteBuffer msgC = HeartbeatBody.FRAME.toNioByteBuffer(); ByteBuffer sliceA = ByteBuffer.allocate(msgA.remaining() + msgB.remaining() / 2); sliceA.put(msgA); diff --git a/java/common/src/test/java/org/apache/qpid/framing/AMQShortStringTest.java b/java/common/src/test/java/org/apache/qpid/framing/AMQShortStringTest.java index 9a805d87b3..92e7ce0a80 100644 --- a/java/common/src/test/java/org/apache/qpid/framing/AMQShortStringTest.java +++ b/java/common/src/test/java/org/apache/qpid/framing/AMQShortStringTest.java @@ -20,10 +20,6 @@ package org.apache.qpid.framing; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.List; - import junit.framework.TestCase; public class AMQShortStringTest extends TestCase { @@ -109,215 +105,5 @@ public class AMQShortStringTest extends TestCase assertFalse(new AMQShortString("A").equals(new AMQShortString("a"))); } - /** - * Test method for - * {@link org.apache.qpid.framing.AMQShortString#AMQShortString(byte[])}. - */ - public void testCreateAMQShortStringByteArray() - { - byte[] bytes = null; - try - { - bytes = "test".getBytes("UTF-8"); - } - catch (UnsupportedEncodingException e) - { - fail("UTF-8 encoding is not supported anymore by JVM:" + e.getMessage()); - } - AMQShortString string = new AMQShortString(bytes); - assertEquals("constructed amq short string length differs from expected", 4, string.length()); - assertTrue("constructed amq short string differs from expected", string.equals("test")); - } - - /** - * Test method for - * {@link org.apache.qpid.framing.AMQShortString#AMQShortString(java.lang.String)} - * <p> - * Tests short string construction from string with length less than 255. - */ - public void testCreateAMQShortStringString() - { - AMQShortString string = new AMQShortString("test"); - assertEquals("constructed amq short string length differs from expected", 4, string.length()); - assertTrue("constructed amq short string differs from expected", string.equals("test")); - } - - /** - * Test method for - * {@link org.apache.qpid.framing.AMQShortString#AMQShortString(char[])}. - * <p> - * Tests short string construction from char array with length less than 255. - */ - public void testCreateAMQShortStringCharArray() - { - char[] chars = "test".toCharArray(); - AMQShortString string = new AMQShortString(chars); - assertEquals("constructed amq short string length differs from expected", 4, string.length()); - assertTrue("constructed amq short string differs from expected", string.equals("test")); - } - - /** - * Test method for - * {@link org.apache.qpid.framing.AMQShortString#AMQShortString(java.lang.CharSequence)} - * <p> - * Tests short string construction from char sequence with length less than 255. - */ - public void testCreateAMQShortStringCharSequence() - { - AMQShortString string = new AMQShortString((CharSequence) "test"); - assertEquals("constructed amq short string length differs from expected", 4, string.length()); - assertTrue("constructed amq short string differs from expected", string.equals("test")); - } - - /** - * Test method for - * {@link org.apache.qpid.framing.AMQShortString#AMQShortString(byte[])}. - * <p> - * Tests an attempt to create an AMQP short string from byte array with length over 255. - */ - public void testCreateAMQShortStringByteArrayOver255() - { - String test = buildString('a', 256); - byte[] bytes = null; - try - { - bytes = test.getBytes("UTF-8"); - } - catch (UnsupportedEncodingException e) - { - fail("UTF-8 encoding is not supported anymore by JVM:" + e.getMessage()); - } - try - { - new AMQShortString(bytes); - fail("It should not be possible to create AMQShortString with length over 255"); - } - catch (IllegalArgumentException e) - { - assertEquals("Exception message differs from expected", - "Cannot create AMQShortString with number of octets over 255!", e.getMessage()); - } - } - - /** - * Test method for - * {@link org.apache.qpid.framing.AMQShortString#AMQShortString(java.lang.String)} - * <p> - * Tests an attempt to create an AMQP short string from string with length over 255 - */ - public void testCreateAMQShortStringStringOver255() - { - String test = buildString('a', 256); - try - { - new AMQShortString(test); - fail("It should not be possible to create AMQShortString with length over 255"); - } - catch (IllegalArgumentException e) - { - assertEquals("Exception message differs from expected", - "Cannot create AMQShortString with number of octets over 255!", e.getMessage()); - } - } - - /** - * Test method for - * {@link org.apache.qpid.framing.AMQShortString#AMQShortString(char[])}. - * <p> - * Tests an attempt to create an AMQP short string from char array with length over 255. - */ - public void testCreateAMQShortStringCharArrayOver255() - { - String test = buildString('a', 256); - char[] chars = test.toCharArray(); - try - { - new AMQShortString(chars); - fail("It should not be possible to create AMQShortString with length over 255"); - } - catch (IllegalArgumentException e) - { - assertEquals("Exception message differs from expected", - "Cannot create AMQShortString with number of octets over 255!", e.getMessage()); - } - } - - /** - * Test method for - * {@link org.apache.qpid.framing.AMQShortString#AMQShortString(java.lang.CharSequence)} - * <p> - * Tests an attempt to create an AMQP short string from char sequence with length over 255. - */ - public void testCreateAMQShortStringCharSequenceOver255() - { - String test = buildString('a', 256); - try - { - new AMQShortString((CharSequence) test); - fail("It should not be possible to create AMQShortString with length over 255"); - } - catch (IllegalArgumentException e) - { - assertEquals("Exception message differs from expected", - "Cannot create AMQShortString with number of octets over 255!", e.getMessage()); - } - } - - /** - * Tests joining of short strings into a short string with length over 255. - */ - public void testJoinOverflow() - { - List<AMQShortString> data = new ArrayList<AMQShortString>(); - for (int i = 0; i < 25; i++) - { - data.add(new AMQShortString("test data!")); - } - try - { - AMQShortString.join(data, new AMQShortString(" ")); - fail("It should not be possible to create AMQShortString with length over 255"); - } - catch (IllegalArgumentException e) - { - assertEquals("Exception message differs from expected", - "Cannot create AMQShortString with number of octets over 255!", e.getMessage()); - } - } - - /** - * Tests joining of short strings into a short string with length less than 255. - */ - public void testJoin() - { - StringBuilder expected = new StringBuilder(); - List<AMQShortString> data = new ArrayList<AMQShortString>(); - data.add(new AMQShortString("test data 1")); - expected.append("test data 1"); - data.add(new AMQShortString("test data 2")); - expected.append(" test data 2"); - AMQShortString result = AMQShortString.join(data, new AMQShortString(" ")); - assertEquals("join result differs from expected", expected.toString(), result.asString()); - } - - /** - * A helper method to generate a string with given length containing given - * character - * - * @param ch - * char to build string with - * @param length - * target string length - * @return string - */ - private String buildString(char ch, int length) - { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) - { - sb.append(ch); - } - return sb.toString(); - } } diff --git a/java/common/src/test/java/org/apache/qpid/framing/BasicContentHeaderPropertiesTest.java b/java/common/src/test/java/org/apache/qpid/framing/BasicContentHeaderPropertiesTest.java index 5e7783f492..4fd1f60d69 100644 --- a/java/common/src/test/java/org/apache/qpid/framing/BasicContentHeaderPropertiesTest.java +++ b/java/common/src/test/java/org/apache/qpid/framing/BasicContentHeaderPropertiesTest.java @@ -20,9 +20,9 @@ */ package org.apache.qpid.framing; -import junit.framework.TestCase; +import org.apache.mina.common.ByteBuffer; -import java.io.*; +import junit.framework.TestCase; public class BasicContentHeaderPropertiesTest extends TestCase @@ -76,14 +76,15 @@ public class BasicContentHeaderPropertiesTest extends TestCase assertEquals(99, _testProperties.getPropertyFlags()); } - public void testWritePropertyListPayload() throws IOException + public void testWritePropertyListPayload() { - _testProperties.writePropertyListPayload(new DataOutputStream(new ByteArrayOutputStream(300))); + ByteBuffer buf = ByteBuffer.allocate(300); + _testProperties.writePropertyListPayload(buf); } public void testPopulatePropertiesFromBuffer() throws Exception { - DataInputStream buf = new DataInputStream(new ByteArrayInputStream(new byte[300])); + ByteBuffer buf = ByteBuffer.allocate(300); _testProperties.populatePropertiesFromBuffer(buf, 99, 99); } diff --git a/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java b/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java index bb4c9c3884..d4691ba097 100644 --- a/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java +++ b/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java @@ -23,14 +23,14 @@ package org.apache.qpid.framing; import junit.framework.Assert; import junit.framework.TestCase; +import org.apache.mina.common.ByteBuffer; + import org.apache.qpid.AMQInvalidArgumentException; import org.apache.qpid.AMQPInvalidClassException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.*; - public class PropertyFieldTableTest extends TestCase { private static final Logger _logger = LoggerFactory.getLogger(PropertyFieldTableTest.class); @@ -441,7 +441,7 @@ public class PropertyFieldTableTest extends TestCase } /** Check that a nested field table parameter correctly encodes and decodes to a byte buffer. */ - public void testNestedFieldTable() throws IOException + public void testNestedFieldTable() { byte[] testBytes = new byte[] { 0, 1, 2, 3, 4, 5 }; @@ -465,16 +465,14 @@ public class PropertyFieldTableTest extends TestCase outerTable.setFieldTable("innerTable", innerTable); // Write the outer table into the buffer. - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - outerTable.writeToBuffer(new DataOutputStream(baos)); - - byte[] data = baos.toByteArray(); + final ByteBuffer buffer = ByteBuffer.allocate((int) outerTable.getEncodedSize() + 4); + outerTable.writeToBuffer(buffer); + buffer.flip(); // Extract the table back from the buffer again. try { - FieldTable extractedOuterTable = EncodingUtils.readFieldTable(new DataInputStream(new ByteArrayInputStream(data))); + FieldTable extractedOuterTable = EncodingUtils.readFieldTable(buffer); FieldTable extractedTable = extractedOuterTable.getFieldTable("innerTable"); @@ -569,7 +567,7 @@ public class PropertyFieldTableTest extends TestCase Assert.assertEquals("Hello", table.getObject("object-string")); } - public void testwriteBuffer() throws IOException + public void testwriteBuffer() { byte[] bytes = { 99, 98, 97, 96, 95 }; @@ -587,17 +585,15 @@ public class PropertyFieldTableTest extends TestCase table.setString("string", "hello"); table.setString("null-string", null); + final ByteBuffer buffer = ByteBuffer.allocate((int) table.getEncodedSize() + 4); // FIXME XXX: Is cast a problem? - ByteArrayOutputStream baos = new ByteArrayOutputStream((int) table.getEncodedSize() + 4); - table.writeToBuffer(new DataOutputStream(baos)); - - ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); - DataInputStream dis = new DataInputStream(bais); + table.writeToBuffer(buffer); + buffer.flip(); - long length = dis.readInt() & 0xFFFFFFFFL; + long length = buffer.getUnsignedInt(); - FieldTable table2 = new FieldTable(dis, length); + FieldTable table2 = new FieldTable(buffer, length); Assert.assertEquals((Boolean) true, table2.getBoolean("bool")); Assert.assertEquals((Byte) Byte.MAX_VALUE, table2.getByte("byte")); diff --git a/java/common/src/test/java/org/apache/qpid/session/TestSession.java b/java/common/src/test/java/org/apache/qpid/session/TestSession.java new file mode 100644 index 0000000000..aafc91b03b --- /dev/null +++ b/java/common/src/test/java/org/apache/qpid/session/TestSession.java @@ -0,0 +1,277 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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.qpid.session; + +import org.apache.mina.common.*; + +import java.net.SocketAddress; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentHashMap; + +public class TestSession implements IoSession +{ + private final ConcurrentMap attributes = new ConcurrentHashMap(); + + public TestSession() + { + } + + public IoService getService() + { + return null; //TODO + } + + public IoServiceConfig getServiceConfig() + { + return null; //TODO + } + + public IoHandler getHandler() + { + return null; //TODO + } + + public IoSessionConfig getConfig() + { + return null; //TODO + } + + public IoFilterChain getFilterChain() + { + return null; //TODO + } + + public WriteFuture write(Object message) + { + return null; //TODO + } + + public CloseFuture close() + { + return null; //TODO + } + + public Object getAttachment() + { + return getAttribute(""); + } + + public Object setAttachment(Object attachment) + { + return setAttribute("",attachment); + } + + public Object getAttribute(String key) + { + return attributes.get(key); + } + + public Object setAttribute(String key, Object value) + { + return attributes.put(key,value); + } + + public Object setAttribute(String key) + { + return attributes.put(key, Boolean.TRUE); + } + + public Object removeAttribute(String key) + { + return attributes.remove(key); + } + + public boolean containsAttribute(String key) + { + return attributes.containsKey(key); + } + + public Set getAttributeKeys() + { + return attributes.keySet(); + } + + public TransportType getTransportType() + { + return null; //TODO + } + + public boolean isConnected() + { + return false; //TODO + } + + public boolean isClosing() + { + return false; //TODO + } + + public CloseFuture getCloseFuture() + { + return null; //TODO + } + + public SocketAddress getRemoteAddress() + { + return null; //TODO + } + + public SocketAddress getLocalAddress() + { + return null; //TODO + } + + public SocketAddress getServiceAddress() + { + return null; //TODO + } + + public int getIdleTime(IdleStatus status) + { + return 0; //TODO + } + + public long getIdleTimeInMillis(IdleStatus status) + { + return 0; //TODO + } + + public void setIdleTime(IdleStatus status, int idleTime) + { + //TODO + } + + public int getWriteTimeout() + { + return 0; //TODO + } + + public long getWriteTimeoutInMillis() + { + return 0; //TODO + } + + public void setWriteTimeout(int writeTimeout) + { + //TODO + } + + public TrafficMask getTrafficMask() + { + return null; //TODO + } + + public void setTrafficMask(TrafficMask trafficMask) + { + //TODO + } + + public void suspendRead() + { + //TODO + } + + public void suspendWrite() + { + //TODO + } + + public void resumeRead() + { + //TODO + } + + public void resumeWrite() + { + //TODO + } + + public long getReadBytes() + { + return 0; //TODO + } + + public long getWrittenBytes() + { + return 0; //TODO + } + + public long getReadMessages() + { + return 0; + } + + public long getWrittenMessages() + { + return 0; + } + + public long getWrittenWriteRequests() + { + return 0; //TODO + } + + public int getScheduledWriteRequests() + { + return 0; //TODO + } + + public int getScheduledWriteBytes() + { + return 0; //TODO + } + + public long getCreationTime() + { + return 0; //TODO + } + + public long getLastIoTime() + { + return 0; //TODO + } + + public long getLastReadTime() + { + return 0; //TODO + } + + public long getLastWriteTime() + { + return 0; //TODO + } + + public boolean isIdle(IdleStatus status) + { + return false; //TODO + } + + public int getIdleCount(IdleStatus status) + { + return 0; //TODO + } + + public long getLastIdleTime(IdleStatus status) + { + return 0; //TODO + } +} diff --git a/java/common/src/test/java/org/apache/qpid/ssl/SSLContextFactoryTest.java b/java/common/src/test/java/org/apache/qpid/ssl/SSLContextFactoryTest.java deleted file mode 100644 index 288946e064..0000000000 --- a/java/common/src/test/java/org/apache/qpid/ssl/SSLContextFactoryTest.java +++ /dev/null @@ -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. - */ -package org.apache.qpid.ssl; - -import java.io.IOException; - -import javax.net.ssl.SSLContext; - -import org.apache.qpid.test.utils.QpidTestCase; - -public class SSLContextFactoryTest extends QpidTestCase -{ - private static final String BROKER_KEYSTORE_PATH = TEST_RESOURCES_DIR + "/ssl/java_broker_keystore.jks"; - private static final String CLIENT_KEYSTORE_PATH = TEST_RESOURCES_DIR + "/ssl/java_client_keystore.jks"; - private static final String CLIENT_TRUSTSTORE_PATH = TEST_RESOURCES_DIR + "/ssl/java_client_truststore.jks"; - private static final String STORE_PASSWORD = "password"; - private static final String CERT_TYPE = "SunX509"; - private static final String CERT_ALIAS_APP1 = "app1"; - - public void testBuildServerContext() throws Exception - { - SSLContext context = SSLContextFactory.buildServerContext(BROKER_KEYSTORE_PATH, STORE_PASSWORD, CERT_TYPE); - assertNotNull("SSLContext should not be null", context); - } - - public void testBuildServerContextWithIncorrectPassword() throws Exception - { - try - { - SSLContextFactory.buildServerContext(BROKER_KEYSTORE_PATH, "sajdklsad", CERT_TYPE); - fail("Exception was not thrown due to incorrect password"); - } - catch (IOException e) - { - //expected - } - } - - public void testTrustStoreDoesNotExist() throws Exception - { - try - { - SSLContextFactory.buildClientContext("/path/to/nothing", STORE_PASSWORD, CERT_TYPE, CLIENT_KEYSTORE_PATH, STORE_PASSWORD, CERT_TYPE, null); - fail("Exception was not thrown due to incorrect path"); - } - catch (IOException e) - { - //expected - } - } - - public void testBuildClientContextForSSLEncryptionOnly() throws Exception - { - SSLContext context = SSLContextFactory.buildClientContext(CLIENT_TRUSTSTORE_PATH, STORE_PASSWORD, CERT_TYPE, null, null, null, null); - assertNotNull("SSLContext should not be null", context); - } - - public void testBuildClientContextWithForClientAuth() throws Exception - { - SSLContext context = SSLContextFactory.buildClientContext(CLIENT_TRUSTSTORE_PATH, STORE_PASSWORD, CERT_TYPE, CLIENT_KEYSTORE_PATH, STORE_PASSWORD, CERT_TYPE, null); - assertNotNull("SSLContext should not be null", context); - } - - public void testBuildClientContextWithForClientAuthWithCertAlias() throws Exception - { - SSLContext context = SSLContextFactory.buildClientContext(CLIENT_TRUSTSTORE_PATH, STORE_PASSWORD, CERT_TYPE, CLIENT_KEYSTORE_PATH, STORE_PASSWORD, CERT_TYPE, CERT_ALIAS_APP1); - assertNotNull("SSLContext should not be null", context); - } -} diff --git a/java/common/src/test/java/org/apache/qpid/test/utils/QpidTestCase.java b/java/common/src/test/java/org/apache/qpid/test/utils/QpidTestCase.java index e69f95f916..8b470d555e 100644 --- a/java/common/src/test/java/org/apache/qpid/test/utils/QpidTestCase.java +++ b/java/common/src/test/java/org/apache/qpid/test/utils/QpidTestCase.java @@ -24,28 +24,17 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; -import java.net.DatagramSocket; -import java.net.ServerSocket; -import java.util.*; +import java.util.ArrayList; +import java.util.List; import junit.framework.TestCase; import junit.framework.TestResult; -import org.apache.log4j.Level; import org.apache.log4j.Logger; - public class QpidTestCase extends TestCase { - public static final String QPID_HOME = System.getProperty("QPID_HOME"); - public static final String TEST_RESOURCES_DIR = QPID_HOME + "/../test-profiles/test_resources/"; - - private static final Logger _logger = Logger.getLogger(QpidTestCase.class); - - private final Map<Logger, Level> _loggerLevelSetForTest = new HashMap<Logger, Level>(); - private final Map<String, String> _propertiesSetForTest = new HashMap<String, String>(); - - private String _testName; + protected static final Logger _logger = Logger.getLogger(QpidTestCase.class); /** * Some tests are excluded when the property test.excludes is set to true. @@ -65,7 +54,7 @@ public class QpidTestCase extends TestCase String exclusionListString = System.getProperties().getProperty("test.excludelist", ""); List<String> exclusionList = new ArrayList<String>(); - for (String uri : exclusionListURIs.split(";\\s*")) + for (String uri : exclusionListURIs.split("\\s+")) { File file = new File(uri); if (file.exists()) @@ -87,10 +76,6 @@ public class QpidTestCase extends TestCase _logger.warn("Exception when reading exclusion list", e); } } - else - { - _logger.info("Specified exclude file does not exist: " + uri); - } } if (!exclusionListString.equals("")) @@ -142,187 +127,4 @@ public class QpidTestCase extends TestCase return storeClass != null ? storeClass : MEMORY_STORE_CLASS_NAME ; } - - - public static final int MIN_PORT_NUMBER = 1; - public static final int MAX_PORT_NUMBER = 49151; - - - /** - * Gets the next available port starting at a port. - * - * @param fromPort the port to scan for availability - * @throws NoSuchElementException if there are no ports available - */ - protected int getNextAvailable(int fromPort) - { - if ((fromPort < MIN_PORT_NUMBER) || (fromPort > MAX_PORT_NUMBER)) - { - throw new IllegalArgumentException("Invalid start port: " + fromPort); - } - - for (int i = fromPort; i <= MAX_PORT_NUMBER; i++) - { - if (available(i)) { - return i; - } - } - - throw new NoSuchElementException("Could not find an available port above " + fromPort); - } - - /** - * Checks to see if a specific port is available. - * - * @param port the port to check for availability - */ - private boolean available(int port) - { - if ((port < MIN_PORT_NUMBER) || (port > MAX_PORT_NUMBER)) - { - throw new IllegalArgumentException("Invalid start port: " + port); - } - - ServerSocket ss = null; - DatagramSocket ds = null; - try - { - ss = new ServerSocket(port); - ss.setReuseAddress(true); - ds = new DatagramSocket(port); - ds.setReuseAddress(true); - return true; - } - catch (IOException e) - { - } - finally - { - if (ds != null) - { - ds.close(); - } - - if (ss != null) - { - try - { - ss.close(); - } - catch (IOException e) - { - /* should not be thrown */ - } - } - } - - return false; - } - - public int findFreePort() - { - return getNextAvailable(10000); - } - - /** - * Set a System property for duration of this test only. The tearDown will - * guarantee to reset the property to its previous value after the test - * completes. - * - * @param property The property to set - * @param value the value to set it to, if null, the property will be cleared - */ - protected void setTestSystemProperty(final String property, final String value) - { - if (!_propertiesSetForTest.containsKey(property)) - { - // Record the current value so we can revert it later. - _propertiesSetForTest.put(property, System.getProperty(property)); - } - - if (value == null) - { - System.clearProperty(property); - } - else - { - System.setProperty(property, value); - } - } - - /** - * Restore the System property values that were set by this test run. - */ - protected void revertTestSystemProperties() - { - if(!_propertiesSetForTest.isEmpty()) - { - _logger.debug("reverting " + _propertiesSetForTest.size() + " test properties"); - for (String key : _propertiesSetForTest.keySet()) - { - String value = _propertiesSetForTest.get(key); - if (value != null) - { - System.setProperty(key, value); - } - else - { - System.clearProperty(key); - } - } - - _propertiesSetForTest.clear(); - } - } - - /** - * Adjust the VMs Log4j Settings just for this test run - * - * @param logger the logger to change - * @param level the level to set - */ - protected void setLoggerLevel(Logger logger, Level level) - { - assertNotNull("Cannot set level of null logger", logger); - assertNotNull("Cannot set Logger("+logger.getName()+") to null level.",level); - - if (!_loggerLevelSetForTest.containsKey(logger)) - { - // Record the current value so we can revert it later. - _loggerLevelSetForTest.put(logger, logger.getLevel()); - } - - logger.setLevel(level); - } - - /** - * Restore the logging levels defined by this test. - */ - protected void revertLoggingLevels() - { - for (Logger logger : _loggerLevelSetForTest.keySet()) - { - logger.setLevel(_loggerLevelSetForTest.get(logger)); - } - - _loggerLevelSetForTest.clear(); - } - - protected void tearDown() throws java.lang.Exception - { - _logger.info("========== tearDown " + _testName + " =========="); - revertTestSystemProperties(); - revertLoggingLevels(); - } - - protected void setUp() throws Exception - { - _testName = getClass().getSimpleName() + "." + getName(); - _logger.info("========== start " + _testName + " =========="); - } - - protected String getTestName() - { - return _testName; - } } diff --git a/java/common/src/test/java/org/apache/qpid/transport/ConnectionTest.java b/java/common/src/test/java/org/apache/qpid/transport/ConnectionTest.java index 49f6a08007..375a326654 100644 --- a/java/common/src/test/java/org/apache/qpid/transport/ConnectionTest.java +++ b/java/common/src/test/java/org/apache/qpid/transport/ConnectionTest.java @@ -20,27 +20,32 @@ */ package org.apache.qpid.transport; -import static org.apache.qpid.transport.Option.EXPECTED; -import static org.apache.qpid.transport.Option.NONE; -import static org.apache.qpid.transport.Option.SYNC; +import org.apache.mina.util.AvailablePortFinder; + +import org.apache.qpid.test.utils.QpidTestCase; +import org.apache.qpid.transport.network.ConnectionBinding; +import org.apache.qpid.transport.network.io.IoAcceptor; +import org.apache.qpid.transport.util.Logger; +import org.apache.qpid.transport.util.Waiter; -import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.Collections; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.io.IOException; -import org.apache.qpid.test.utils.QpidTestCase; -import org.apache.qpid.transport.network.ConnectionBinding; -import org.apache.qpid.transport.network.io.IoAcceptor; -import org.apache.qpid.transport.util.Waiter; +import static org.apache.qpid.transport.Option.*; /** * ConnectionTest */ + public class ConnectionTest extends QpidTestCase implements SessionListener { + + private static final Logger log = Logger.get(ConnectionTest.class); + private int port; private volatile boolean queue = false; private List<MessageTransfer> messages = new ArrayList<MessageTransfer>(); @@ -53,7 +58,7 @@ public class ConnectionTest extends QpidTestCase implements SessionListener { super.setUp(); - port = findFreePort(); + port = AvailablePortFinder.getNextAvailable(12000); } protected void tearDown() throws Exception @@ -153,8 +158,7 @@ public class ConnectionTest extends QpidTestCase implements SessionListener private Connection connect(final CountDownLatch closed) { - final Connection conn = new Connection(); - conn.setConnectionDelegate(new ClientDelegate(new ConnectionSettings())); + Connection conn = new Connection(); conn.addConnectionListener(new ConnectionListener() { public void opened(Connection conn) {} @@ -178,9 +182,9 @@ public class ConnectionTest extends QpidTestCase implements SessionListener { // Force os.name to be windows to exercise code in IoReceiver // that looks for the value of os.name - setTestSystemProperty("os.name","windows"); + System.setProperty("os.name","windows"); - // Start server as 0-9 to force a ProtocolVersionException + // Start server as 0-9 to froce a ProtocolVersionException startServer(new ProtocolHeader(1, 0, 9)); CountDownLatch closed = new CountDownLatch(1); @@ -215,7 +219,7 @@ public class ConnectionTest extends QpidTestCase implements SessionListener conn.send(protocolHeader); List<Object> utf8 = new ArrayList<Object>(); utf8.add("utf8"); - conn.connectionStart(null, Collections.emptyList(), utf8); + conn.connectionStart(null, Collections.EMPTY_LIST, utf8); } @Override @@ -266,7 +270,40 @@ public class ConnectionTest extends QpidTestCase implements SessionListener } } + class FailoverConnectionListener implements ConnectionListener + { + public void opened(Connection conn) {} + + public void exception(Connection conn, ConnectionException e) + { + throw e; + } + + public void closed(Connection conn) + { + queue = true; + conn.connect("localhost", port, null, "guest", "guest"); + conn.resume(); + } + } + + class TestSessionListener implements SessionListener + { + public void opened(Session s) {} + public void resumed(Session s) {} + public void exception(Session s, SessionException e) {} + public void message(Session s, MessageTransfer xfr) + { + synchronized (incoming) + { + incoming.add(xfr); + incoming.notifyAll(); + } + s.processed(xfr); + } + public void closed(Session s) {} + } public void testResumeNonemptyReplayBuffer() throws Exception { @@ -274,7 +311,6 @@ public class ConnectionTest extends QpidTestCase implements SessionListener Connection conn = new Connection(); conn.addConnectionListener(new FailoverConnectionListener()); - conn.setConnectionDelegate(new ClientDelegate(new ConnectionSettings())); conn.connect("localhost", port, null, "guest", "guest"); Session ssn = conn.createSession(1); ssn.setSessionListener(new TestSessionListener()); @@ -329,7 +365,6 @@ public class ConnectionTest extends QpidTestCase implements SessionListener startServer(); Connection conn = new Connection(); - conn.setConnectionDelegate(new ClientDelegate(new ConnectionSettings())); conn.addConnectionListener(new FailoverConnectionListener()); conn.connect("localhost", port, null, "guest", "guest"); Session ssn = conn.createSession(1); @@ -352,7 +387,6 @@ public class ConnectionTest extends QpidTestCase implements SessionListener startServer(); Connection conn = new Connection(); - conn.setConnectionDelegate(new ClientDelegate(new ConnectionSettings())); conn.connect("localhost", port, null, "guest", "guest"); Session ssn = conn.createSession(); ssn.sessionFlush(EXPECTED); @@ -366,7 +400,6 @@ public class ConnectionTest extends QpidTestCase implements SessionListener { startServer(); Connection conn = new Connection(); - conn.setConnectionDelegate(new ClientDelegate(new ConnectionSettings())); conn.connect("localhost", port, null, "guest", "guest"); conn.connectionHeartbeat(); conn.close(); @@ -377,7 +410,6 @@ public class ConnectionTest extends QpidTestCase implements SessionListener startServer(); Connection conn = new Connection(); - conn.setConnectionDelegate(new ClientDelegate(new ConnectionSettings())); conn.connect("localhost", port, null, "guest", "guest"); Session ssn = conn.createSession(); send(ssn, "EXCP 0"); @@ -397,7 +429,6 @@ public class ConnectionTest extends QpidTestCase implements SessionListener startServer(); Connection conn = new Connection(); - conn.setConnectionDelegate(new ClientDelegate(new ConnectionSettings())); conn.connect("localhost", port, null, "guest", "guest"); Session ssn = conn.createSession(); send(ssn, "EXCP 0", true); @@ -412,38 +443,4 @@ public class ConnectionTest extends QpidTestCase implements SessionListener } } - class FailoverConnectionListener implements ConnectionListener - { - public void opened(Connection conn) {} - - public void exception(Connection conn, ConnectionException e) - { - throw e; - } - - public void closed(Connection conn) - { - queue = true; - conn.connect("localhost", port, null, "guest", "guest"); - conn.resume(); - } - } - - class TestSessionListener implements SessionListener - { - public void opened(Session s) {} - public void resumed(Session s) {} - public void exception(Session s, SessionException e) {} - public void message(Session s, MessageTransfer xfr) - { - synchronized (incoming) - { - incoming.add(xfr); - incoming.notifyAll(); - } - - s.processed(xfr); - } - public void closed(Session s) {} - } } diff --git a/java/common/src/test/java/org/apache/qpid/transport/SessionTimeoutTest.java b/java/common/src/test/java/org/apache/qpid/transport/SessionTimeoutTest.java deleted file mode 100644 index 5f1c1254a2..0000000000 --- a/java/common/src/test/java/org/apache/qpid/transport/SessionTimeoutTest.java +++ /dev/null @@ -1,81 +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.qpid.transport; - -import org.apache.qpid.configuration.ClientProperties; -import org.apache.qpid.test.utils.QpidTestCase; - - -public class SessionTimeoutTest extends QpidTestCase -{ - public void testSessionTimeout() - { - try - { - long timeout = 1; - setTestSystemProperty("qpid.sync_op_timeout", Long.toString(timeout)); - assertSessionTimeout(timeout); - } - finally - { - revertTestSystemProperties(); - } - } - - public void testSessionTimeoutSetWith_amqj_default_syncwrite_timeout() - { - try - { - long timeout = 1; - setTestSystemProperty("amqj.default_syncwrite_timeout", Long.toString(timeout)); - setTestSystemProperty("qpid.sync_op_timeout", null); - assertSessionTimeout(timeout); - } - finally - { - revertTestSystemProperties(); - } - } - - private void assertSessionTimeout(long timeout) - { - Session session = new TestSession(null, null, 0); - long startTime = System.currentTimeMillis(); - try - { - session.awaitOpen(); - fail("SessionTimeoutException is expected!"); - } - catch (SessionException e) - { - long elapsedTime = System.currentTimeMillis() - startTime; - assertTrue("Expected timeout should happened in " + timeout + " ms but timeout occured in " - + elapsedTime + " ms!", elapsedTime >= timeout && elapsedTime < ClientProperties.DEFAULT_SYNC_OPERATION_TIMEOUT); - } - } - - class TestSession extends Session - { - public TestSession(Connection connection, Binary name, long expiry) - { - super(connection, name, expiry); - } - } - -} diff --git a/java/common/src/test/java/org/apache/qpid/transport/TestNetworkConnection.java b/java/common/src/test/java/org/apache/qpid/transport/TestNetworkDriver.java index 8533c64fab..957a7190ee 100644 --- a/java/common/src/test/java/org/apache/qpid/transport/TestNetworkConnection.java +++ b/java/common/src/test/java/org/apache/qpid/transport/TestNetworkDriver.java @@ -25,36 +25,34 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import org.apache.qpid.protocol.ProtocolEngine; import org.apache.qpid.protocol.ProtocolEngineFactory; import org.apache.qpid.ssl.SSLContextFactory; -import org.apache.qpid.transport.network.NetworkConnection; /** * Test implementation of IoSession, which is required for some tests. Methods not being used are not implemented, * so if this class is being used and some methods are to be used, then please update those. */ -public class TestNetworkConnection implements NetworkConnection +public class TestNetworkDriver implements NetworkDriver { + private final ConcurrentMap attributes = new ConcurrentHashMap(); private String _remoteHost = "127.0.0.1"; private String _localHost = "127.0.0.1"; private int _port = 1; private SocketAddress _localAddress = null; private SocketAddress _remoteAddress = null; - private final MockSender _sender; - public TestNetworkConnection() + public TestNetworkDriver() { - _sender = new MockSender(); } - - public void bind(int port, InetAddress[] addresses, ProtocolEngineFactory protocolFactory, - NetworkTransportConfiguration config, SSLContextFactory sslFactory) throws BindException + NetworkDriverConfiguration config, SSLContextFactory sslFactory) throws BindException { - + } public SocketAddress getLocalAddress() @@ -67,40 +65,40 @@ public class TestNetworkConnection implements NetworkConnection return (_remoteAddress != null) ? _remoteAddress : new InetSocketAddress(_remoteHost, _port); } - public void open(int port, InetAddress destination, ProtocolEngine engine, NetworkTransportConfiguration config, + public void open(int port, InetAddress destination, ProtocolEngine engine, NetworkDriverConfiguration config, SSLContextFactory sslFactory) throws OpenException { - + } public void setMaxReadIdle(int idleTime) { - + } public void setMaxWriteIdle(int idleTime) { - + } public void close() { - + } public void flush() { - + } public void send(ByteBuffer msg) { - + } public void setIdleTimeout(int i) { - + } public void setPort(int port) @@ -132,13 +130,4 @@ public class TestNetworkConnection implements NetworkConnection { _remoteAddress = address; } - - public Sender<ByteBuffer> getSender() - { - return _sender; - } - - public void start() - { - } } diff --git a/java/common/src/test/java/org/apache/qpid/transport/network/TransportTest.java b/java/common/src/test/java/org/apache/qpid/transport/network/TransportTest.java deleted file mode 100644 index 7039b904e3..0000000000 --- a/java/common/src/test/java/org/apache/qpid/transport/network/TransportTest.java +++ /dev/null @@ -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.qpid.transport.network; - - -import java.nio.ByteBuffer; - -import javax.net.ssl.SSLContext; - -import org.apache.qpid.framing.ProtocolVersion; -import org.apache.qpid.protocol.ProtocolEngineFactory; -import org.apache.qpid.test.utils.QpidTestCase; -import org.apache.qpid.transport.ConnectionSettings; -import org.apache.qpid.transport.NetworkTransportConfiguration; -import org.apache.qpid.transport.Receiver; -import org.apache.qpid.transport.TransportException; -import org.apache.qpid.transport.network.io.IoNetworkTransport; - -public class TransportTest extends QpidTestCase -{ - - - - public void testDefaultGetOutgoingTransportForv0_8() throws Exception - { - final OutgoingNetworkTransport networkTransport = Transport.getOutgoingTransportInstance(ProtocolVersion.v8_0); - assertNotNull(networkTransport); - assertTrue(networkTransport instanceof IoNetworkTransport); - } - - public void testGloballyOverriddenOutgoingTransportForv0_8() throws Exception - { - setTestSystemProperty(Transport.QPID_TRANSPORT_PROPNAME, TestOutgoingNetworkTransport.class.getName()); - - final OutgoingNetworkTransport networkTransport = Transport.getOutgoingTransportInstance(ProtocolVersion.v8_0); - assertNotNull(networkTransport); - assertTrue(networkTransport instanceof TestOutgoingNetworkTransport); - } - - public void testProtocolSpecificOverriddenOutgoingTransportForv0_8() throws Exception - { - setTestSystemProperty(Transport.QPID_TRANSPORT_V0_8_PROPNAME, TestOutgoingNetworkTransport.class.getName()); - - final OutgoingNetworkTransport networkTransport = Transport.getOutgoingTransportInstance(ProtocolVersion.v8_0); - assertNotNull(networkTransport); - assertTrue(networkTransport instanceof TestOutgoingNetworkTransport); - } - - public void testDefaultGetOutgoingTransportForv0_10() throws Exception - { - final OutgoingNetworkTransport networkTransport = Transport.getOutgoingTransportInstance(ProtocolVersion.v0_10); - assertNotNull(networkTransport); - assertTrue(networkTransport instanceof IoNetworkTransport); - } - - public void testDefaultGetIncomingTransport() throws Exception - { - final IncomingNetworkTransport networkTransport = Transport.getIncomingTransportInstance(); - assertNotNull(networkTransport); - assertTrue(networkTransport instanceof IoNetworkTransport); - } - - public void testOverriddenGetIncomingTransport() throws Exception - { - setTestSystemProperty(Transport.QPID_BROKER_TRANSPORT_PROPNAME, TestIncomingNetworkTransport.class.getName()); - - final IncomingNetworkTransport networkTransport = Transport.getIncomingTransportInstance(); - assertNotNull(networkTransport); - assertTrue(networkTransport instanceof TestIncomingNetworkTransport); - } - - public void testInvalidOutgoingTransportClassName() throws Exception - { - setTestSystemProperty(Transport.QPID_TRANSPORT_PROPNAME, "invalid"); - - try - { - Transport.getOutgoingTransportInstance(ProtocolVersion.v0_10); - fail("Should have failed to load the invalid class"); - } - catch(TransportException te) - { - //expected, ignore - } - } - - public void testInvalidOutgoingTransportProtocolVersion() throws Exception - { - try - { - Transport.getOutgoingTransportInstance(new ProtocolVersion((byte)0, (byte)0)); - fail("Should have failed to load the transport for invalid protocol version"); - } - catch(IllegalArgumentException iae) - { - //expected, ignore - } - } - - public static class TestOutgoingNetworkTransport implements OutgoingNetworkTransport - { - - public void close() - { - throw new UnsupportedOperationException(); - } - - public NetworkConnection getConnection() - { - throw new UnsupportedOperationException(); - } - - public NetworkConnection connect(ConnectionSettings settings, - Receiver<ByteBuffer> delegate, SSLContext sslContext) - { - throw new UnsupportedOperationException(); - } - } - - public static class TestIncomingNetworkTransport implements IncomingNetworkTransport - { - - public void close() - { - throw new UnsupportedOperationException(); - } - - public NetworkConnection getConnection() - { - throw new UnsupportedOperationException(); - } - - public void accept(NetworkTransportConfiguration config, - ProtocolEngineFactory factory, SSLContext sslContext) - { - throw new UnsupportedOperationException(); - } - } -} diff --git a/java/common/src/test/java/org/apache/qpid/transport/network/io/IoTransport.java b/java/common/src/test/java/org/apache/qpid/transport/network/io/IoTransport.java deleted file mode 100644 index 215c6d9931..0000000000 --- a/java/common/src/test/java/org/apache/qpid/transport/network/io/IoTransport.java +++ /dev/null @@ -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.qpid.transport.network.io; - -import java.net.Socket; -import java.nio.ByteBuffer; - -import org.apache.qpid.transport.Binding; -import org.apache.qpid.transport.Sender; -import org.apache.qpid.transport.util.Logger; - -/** - * This class provides a socket based transport using the java.io - * classes. - * - * The following params are configurable via JVM arguments - * TCP_NO_DELAY - amqj.tcpNoDelay - * SO_RCVBUF - amqj.receiveBufferSize - * SO_SNDBUF - amqj.sendBufferSize - */ -public final class IoTransport<E> -{ - - - private static final Logger log = Logger.get(IoTransport.class); - - private static int DEFAULT_READ_WRITE_BUFFER_SIZE = 64 * 1024; - private static int readBufferSize = Integer.getInteger - ("amqj.receiveBufferSize", DEFAULT_READ_WRITE_BUFFER_SIZE); - private static int writeBufferSize = Integer.getInteger - ("amqj.sendBufferSize", DEFAULT_READ_WRITE_BUFFER_SIZE); - - private Socket socket; - private Sender<ByteBuffer> sender; - private E endpoint; - private IoReceiver receiver; - private long timeout = 60000; - - IoTransport(Socket socket, Binding<E,ByteBuffer> binding) - { - this.socket = socket; - setupTransport(socket, binding); - } - - private void setupTransport(Socket socket, Binding<E, ByteBuffer> binding) - { - IoSender ios = new IoSender(socket, 2*writeBufferSize, timeout); - ios.initiate(); - - this.sender = ios; - this.endpoint = binding.endpoint(sender); - this.receiver = new IoReceiver(socket, binding.receiver(endpoint), - 2*readBufferSize, timeout); - this.receiver.initiate(); - - ios.registerCloseListener(this.receiver); - } - - public Sender<ByteBuffer> getSender() - { - return sender; - } - - public IoReceiver getReceiver() - { - return receiver; - } - - public Socket getSocket() - { - return socket; - } - -} diff --git a/java/common/src/test/java/org/apache/qpid/transport/network/mina/MINANetworkDriverTest.java b/java/common/src/test/java/org/apache/qpid/transport/network/mina/MINANetworkDriverTest.java new file mode 100644 index 0000000000..fc8e689ca4 --- /dev/null +++ b/java/common/src/test/java/org/apache/qpid/transport/network/mina/MINANetworkDriverTest.java @@ -0,0 +1,494 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "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.qpid.transport.network.mina; + +import java.net.BindException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import junit.framework.TestCase; + +import org.apache.qpid.framing.AMQDataBlock; +import org.apache.qpid.protocol.ProtocolEngine; +import org.apache.qpid.protocol.ProtocolEngineFactory; +import org.apache.qpid.transport.NetworkDriver; +import org.apache.qpid.transport.OpenException; + +public class MINANetworkDriverTest extends TestCase +{ + + private static final String TEST_DATA = "YHALOTHAR"; + private static int TEST_PORT = 2323; + private NetworkDriver _server; + private NetworkDriver _client; + private CountingProtocolEngine _countingEngine; // Keeps a count of how many bytes it's read + private Exception _thrownEx; + + @Override + public void setUp() + { + _server = new MINANetworkDriver(); + _client = new MINANetworkDriver(); + _thrownEx = null; + _countingEngine = new CountingProtocolEngine(); + // increment the port to prevent tests clashing with each other when + // the port is in TIMED_WAIT state. + TEST_PORT++; + } + + @Override + public void tearDown() + { + if (_server != null) + { + _server.close(); + } + + if (_client != null) + { + _client.close(); + } + } + + /** + * Tests that a socket can't be opened if a driver hasn't been bound + * to the port and can be opened if a driver has been bound. + * @throws BindException + * @throws UnknownHostException + * @throws OpenException + */ + public void testBindOpen() throws BindException, UnknownHostException, OpenException + { + try + { + _client.open(TEST_PORT, InetAddress.getLocalHost(), _countingEngine, null, null); + } + catch (OpenException e) + { + _thrownEx = e; + } + + assertNotNull("Open should have failed since no engine bound", _thrownEx); + + _server.bind(TEST_PORT, null, new EchoProtocolEngineSingletonFactory(), null, null); + + _client.open(TEST_PORT, InetAddress.getLocalHost(), _countingEngine, null, null); + } + + /** + * Tests that a socket can't be opened after a bound NetworkDriver has been closed + * @throws BindException + * @throws UnknownHostException + * @throws OpenException + */ + public void testBindOpenCloseOpen() throws BindException, UnknownHostException, OpenException + { + _server.bind(TEST_PORT, null, new EchoProtocolEngineSingletonFactory(), null, null); + _client.open(TEST_PORT, InetAddress.getLocalHost(), _countingEngine, null, null); + _client.close(); + _server.close(); + + try + { + _client.open(TEST_PORT, InetAddress.getLocalHost(), _countingEngine, null, null); + } + catch (OpenException e) + { + _thrownEx = e; + } + assertNotNull("Open should have failed", _thrownEx); + } + + /** + * Checks that the right exception is thrown when binding a NetworkDriver to an already + * existing socket. + */ + public void testBindPortInUse() + { + try + { + _server.bind(TEST_PORT, null, new EchoProtocolEngineSingletonFactory(), null, null); + } + catch (BindException e) + { + fail("First bind should not fail"); + } + + try + { + _client.bind(TEST_PORT, null, new EchoProtocolEngineSingletonFactory(), null, null); + } + catch (BindException e) + { + _thrownEx = e; + } + assertNotNull("Second bind should throw BindException", _thrownEx); + } + + /** + * tests that bytes sent on a network driver are received at the other end + * + * @throws UnknownHostException + * @throws OpenException + * @throws InterruptedException + * @throws BindException + */ + public void testSend() throws UnknownHostException, OpenException, InterruptedException, BindException + { + // Open a connection from a counting engine to an echo engine + _server.bind(TEST_PORT, null, new EchoProtocolEngineSingletonFactory(), null, null); + _client.open(TEST_PORT, InetAddress.getLocalHost(), _countingEngine, null, null); + + // Tell the counting engine how much data we're sending + _countingEngine.setNewLatch(TEST_DATA.getBytes().length); + + // Send the data and wait for up to 2 seconds to get it back + _client.send(ByteBuffer.wrap(TEST_DATA.getBytes())); + _countingEngine.getLatch().await(2, TimeUnit.SECONDS); + + // Check what we got + assertEquals("Wrong amount of data recieved", TEST_DATA.getBytes().length, _countingEngine.getReadBytes()); + } + + /** + * Opens a connection with a low read idle and check that it gets triggered + * @throws BindException + * @throws OpenException + * @throws UnknownHostException + * + */ + public void testSetReadIdle() throws BindException, UnknownHostException, OpenException + { + // Open a connection from a counting engine to an echo engine + _server.bind(TEST_PORT, null, new EchoProtocolEngineSingletonFactory(), null, null); + _client.open(TEST_PORT, InetAddress.getLocalHost(), _countingEngine, null, null); + assertFalse("Reader should not have been idle", _countingEngine.getReaderHasBeenIdle()); + _client.setMaxReadIdle(1); + sleepForAtLeast(1500); + assertTrue("Reader should have been idle", _countingEngine.getReaderHasBeenIdle()); + } + + /** + * Opens a connection with a low write idle and check that it gets triggered + * @throws BindException + * @throws OpenException + * @throws UnknownHostException + * + */ + public void testSetWriteIdle() throws BindException, UnknownHostException, OpenException + { + // Open a connection from a counting engine to an echo engine + _server.bind(TEST_PORT, null, new EchoProtocolEngineSingletonFactory(), null, null); + _client.open(TEST_PORT, InetAddress.getLocalHost(), _countingEngine, null, null); + assertFalse("Reader should not have been idle", _countingEngine.getWriterHasBeenIdle()); + _client.setMaxWriteIdle(1); + sleepForAtLeast(1500); + assertTrue("Reader should have been idle", _countingEngine.getWriterHasBeenIdle()); + } + + + /** + * Creates and then closes a connection from client to server and checks that the server + * has its closed() method called. Then creates a new client and closes the server to check + * that the client has its closed() method called. + * @throws BindException + * @throws UnknownHostException + * @throws OpenException + */ + public void testClosed() throws BindException, UnknownHostException, OpenException + { + // Open a connection from a counting engine to an echo engine + EchoProtocolEngineSingletonFactory factory = new EchoProtocolEngineSingletonFactory(); + _server.bind(TEST_PORT, null, factory, null, null); + _client.open(TEST_PORT, InetAddress.getLocalHost(), _countingEngine, null, null); + EchoProtocolEngine serverEngine = null; + while (serverEngine == null) + { + serverEngine = factory.getEngine(); + if (serverEngine == null) + { + try + { + Thread.sleep(10); + } + catch (InterruptedException e) + { + } + } + } + assertFalse("Server should not have been closed", serverEngine.getClosed()); + serverEngine.setNewLatch(1); + _client.close(); + try + { + serverEngine.getLatch().await(2, TimeUnit.SECONDS); + } + catch (InterruptedException e) + { + } + assertTrue("Server should have been closed", serverEngine.getClosed()); + + _client.open(TEST_PORT, InetAddress.getLocalHost(), _countingEngine, null, null); + _countingEngine.setClosed(false); + assertFalse("Client should not have been closed", _countingEngine.getClosed()); + _countingEngine.setNewLatch(1); + _server.close(); + try + { + _countingEngine.getLatch().await(2, TimeUnit.SECONDS); + } + catch (InterruptedException e) + { + } + assertTrue("Client should have been closed", _countingEngine.getClosed()); + } + + /** + * Create a connection and instruct the client to throw an exception when it gets some data + * and that the latch gets counted down. + * @throws BindException + * @throws UnknownHostException + * @throws OpenException + * @throws InterruptedException + */ + public void testExceptionCaught() throws BindException, UnknownHostException, OpenException, InterruptedException + { + _server.bind(TEST_PORT, null, new EchoProtocolEngineSingletonFactory(), null, null); + _client.open(TEST_PORT, InetAddress.getLocalHost(), _countingEngine, null, null); + + + assertEquals("Exception should not have been thrown", 1, + _countingEngine.getExceptionLatch().getCount()); + _countingEngine.setErrorOnNextRead(true); + _countingEngine.setNewLatch(TEST_DATA.getBytes().length); + _client.send(ByteBuffer.wrap(TEST_DATA.getBytes())); + _countingEngine.getExceptionLatch().await(2, TimeUnit.SECONDS); + assertEquals("Exception should have been thrown", 0, + _countingEngine.getExceptionLatch().getCount()); + } + + /** + * Opens a connection and checks that the remote address is the one that was asked for + * @throws BindException + * @throws UnknownHostException + * @throws OpenException + */ + public void testGetRemoteAddress() throws BindException, UnknownHostException, OpenException + { + _server.bind(TEST_PORT, null, new EchoProtocolEngineSingletonFactory(), null, null); + _client.open(TEST_PORT, InetAddress.getLocalHost(), _countingEngine, null, null); + assertEquals(new InetSocketAddress(InetAddress.getLocalHost(), TEST_PORT), + _client.getRemoteAddress()); + } + + private class EchoProtocolEngineSingletonFactory implements ProtocolEngineFactory + { + EchoProtocolEngine _engine = null; + + public ProtocolEngine newProtocolEngine(NetworkDriver driver) + { + if (_engine == null) + { + _engine = new EchoProtocolEngine(); + _engine.setNetworkDriver(driver); + } + return getEngine(); + } + + public EchoProtocolEngine getEngine() + { + return _engine; + } + } + + public class CountingProtocolEngine implements ProtocolEngine + { + + protected NetworkDriver _driver; + public ArrayList<ByteBuffer> _receivedBytes = new ArrayList<ByteBuffer>(); + private int _readBytes; + private CountDownLatch _latch = new CountDownLatch(0); + private boolean _readerHasBeenIdle; + private boolean _writerHasBeenIdle; + private boolean _closed = false; + private boolean _nextReadErrors = false; + private CountDownLatch _exceptionLatch = new CountDownLatch(1); + + public void closed() + { + setClosed(true); + _latch.countDown(); + } + + public void setErrorOnNextRead(boolean b) + { + _nextReadErrors = b; + } + + public void setNewLatch(int length) + { + _latch = new CountDownLatch(length); + } + + public long getReadBytes() + { + return _readBytes; + } + + public SocketAddress getRemoteAddress() + { + if (_driver != null) + { + return _driver.getRemoteAddress(); + } + else + { + return null; + } + } + + public SocketAddress getLocalAddress() + { + if (_driver != null) + { + return _driver.getLocalAddress(); + } + else + { + return null; + } + } + + public long getWrittenBytes() + { + return 0; + } + + public void readerIdle() + { + _readerHasBeenIdle = true; + } + + public void setNetworkDriver(NetworkDriver driver) + { + _driver = driver; + } + + public void writeFrame(AMQDataBlock frame) + { + + } + + public void writerIdle() + { + _writerHasBeenIdle = true; + } + + public void exception(Throwable t) + { + _exceptionLatch.countDown(); + } + + public CountDownLatch getExceptionLatch() + { + return _exceptionLatch; + } + + public void received(ByteBuffer msg) + { + // increment read bytes and count down the latch for that many + int bytes = msg.remaining(); + _readBytes += bytes; + for (int i = 0; i < bytes; i++) + { + _latch.countDown(); + } + + // Throw an error if we've been asked too, but we can still count + if (_nextReadErrors) + { + throw new RuntimeException("Was asked to error"); + } + } + + public CountDownLatch getLatch() + { + return _latch; + } + + public boolean getWriterHasBeenIdle() + { + return _writerHasBeenIdle; + } + + public boolean getReaderHasBeenIdle() + { + return _readerHasBeenIdle; + } + + public void setClosed(boolean _closed) + { + this._closed = _closed; + } + + public boolean getClosed() + { + return _closed; + } + + } + + private class EchoProtocolEngine extends CountingProtocolEngine + { + + public void received(ByteBuffer msg) + { + super.received(msg); + msg.rewind(); + _driver.send(msg); + } + } + + public static void sleepForAtLeast(long period) + { + long start = System.currentTimeMillis(); + long timeLeft = period; + while (timeLeft > 0) + { + try + { + Thread.sleep(timeLeft); + } + catch (InterruptedException e) + { + // Ignore it + } + timeLeft = period - (System.currentTimeMillis() - start); + } + } +}
\ No newline at end of file diff --git a/java/common/src/test/java/org/apache/qpid/util/FileUtilsTest.java b/java/common/src/test/java/org/apache/qpid/util/FileUtilsTest.java index d6767eb9c0..7eba5f092e 100644 --- a/java/common/src/test/java/org/apache/qpid/util/FileUtilsTest.java +++ b/java/common/src/test/java/org/apache/qpid/util/FileUtilsTest.java @@ -27,9 +27,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; -import java.io.InputStream; import java.util.List; -import java.util.Properties; public class FileUtilsTest extends TestCase { @@ -184,20 +182,6 @@ public class FileUtilsTest extends TestCase } } - - /** - * Helper method to create a temporary file with test content. - * - * @param test_data The data to store in the file - * - * @return The File reference - */ - private File createTestFileInTmpDir(final String testData) throws Exception - { - final File tmpFile = File.createTempFile("test", "tmp"); - - return createTestFile(tmpFile.getCanonicalPath(), testData); - } /** * Helper method to create a test file with a string content * @@ -318,74 +302,8 @@ public class FileUtilsTest extends TestCase // expected path } } - - /** - * Tests that openFileOrDefaultResource can open a file on the filesystem. - * - */ - public void testOpenFileOrDefaultResourceOpensFileOnFileSystem() throws Exception - { - final File testFile = createTestFileInTmpDir("src=tmpfile"); - final String filenameOnFilesystem = testFile.getCanonicalPath(); - final String defaultResource = "org/apache/qpid/util/default.properties"; - - - final InputStream is = FileUtils.openFileOrDefaultResource(filenameOnFilesystem, defaultResource, this.getClass().getClassLoader()); - assertNotNull("Stream must not be null", is); - final Properties p = new Properties(); - p.load(is); - assertEquals("tmpfile", p.getProperty("src")); - } /** - * Tests that openFileOrDefaultResource can open a file on the classpath. - * - */ - public void testOpenFileOrDefaultResourceOpensFileOnClasspath() throws Exception - { - final String mydefaultsResource = "org/apache/qpid/util/mydefaults.properties"; - final String defaultResource = "org/apache/qpid/util/default.properties"; - - - final InputStream is = FileUtils.openFileOrDefaultResource(mydefaultsResource, defaultResource, this.getClass().getClassLoader()); - assertNotNull("Stream must not be null", is); - final Properties p = new Properties(); - p.load(is); - assertEquals("mydefaults", p.getProperty("src")); - } - - /** - * Tests that openFileOrDefaultResource returns the default resource when file cannot be found. - */ - public void testOpenFileOrDefaultResourceOpensDefaultResource() throws Exception - { - final File fileThatDoesNotExist = new File("/does/not/exist.properties"); - assertFalse("Test must not exist", fileThatDoesNotExist.exists()); - - final String defaultResource = "org/apache/qpid/util/default.properties"; - - final InputStream is = FileUtils.openFileOrDefaultResource(fileThatDoesNotExist.getCanonicalPath(), defaultResource, this.getClass().getClassLoader()); - assertNotNull("Stream must not be null", is); - Properties p = new Properties(); - p.load(is); - assertEquals("default.properties", p.getProperty("src")); - } - - /** - * Tests that openFileOrDefaultResource returns null if neither the file nor - * the default resource can be found.. - */ - public void testOpenFileOrDefaultResourceReturnsNullWhenNeitherCanBeFound() throws Exception - { - - final String mydefaultsResource = "org/apache/qpid/util/doesnotexisteiether.properties"; - final String defaultResource = "org/apache/qpid/util/doesnotexisteiether.properties"; - - final InputStream is = FileUtils.openFileOrDefaultResource(mydefaultsResource, defaultResource, this.getClass().getClassLoader()); - assertNull("Stream must be null", is); - } - - /** * Given two lists of File arrays ensure they are the same length and all entries in Before are in After * * @param filesBefore File[] diff --git a/java/common/src/test/java/org/apache/qpid/util/default.properties b/java/common/src/test/java/org/apache/qpid/util/default.properties deleted file mode 100644 index cb522ea9a7..0000000000 --- a/java/common/src/test/java/org/apache/qpid/util/default.properties +++ /dev/null @@ -1,2 +0,0 @@ -# Used by FileUtilsTests -src=default.properties
\ No newline at end of file diff --git a/java/common/src/test/java/org/apache/qpid/util/mydefaults.properties b/java/common/src/test/java/org/apache/qpid/util/mydefaults.properties deleted file mode 100644 index 6a49d927d0..0000000000 --- a/java/common/src/test/java/org/apache/qpid/util/mydefaults.properties +++ /dev/null @@ -1,2 +0,0 @@ -# Used by FileUtilsTests -src=mydefaults
\ No newline at end of file diff --git a/java/common/templates/method/version/MethodBodyClass.vm b/java/common/templates/method/version/MethodBodyClass.vm index ce8a453eeb..a739110d70 100644 --- a/java/common/templates/method/version/MethodBodyClass.vm +++ b/java/common/templates/method/version/MethodBodyClass.vm @@ -46,11 +46,9 @@ package org.apache.qpid.framing.amqp_$version.getMajor()_$version.getMinor(); -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; import java.util.HashMap; +import org.apache.mina.common.ByteBuffer; import org.apache.qpid.framing.*; import org.apache.qpid.AMQException; @@ -58,7 +56,7 @@ public class ${javaClassName} extends AMQMethodBody_$version.getMajor()_$version { private static final AMQMethodBodyInstanceFactory FACTORY_INSTANCE = new AMQMethodBodyInstanceFactory() { - public AMQMethodBody newInstance(DataInputStream in, long size) throws AMQFrameDecodingException, IOException + public AMQMethodBody newInstance(ByteBuffer in, long size) throws AMQFrameDecodingException { return new ${javaClassName}(in); } @@ -86,7 +84,7 @@ public class ${javaClassName} extends AMQMethodBody_$version.getMajor()_$version // Constructor - public ${javaClassName}(DataInputStream buffer) throws AMQFrameDecodingException, IOException + public ${javaClassName}(ByteBuffer buffer) throws AMQFrameDecodingException { #foreach( $field in $method.ConsolidatedFields ) _$field.Name = read$field.getEncodingType()( buffer ); @@ -171,7 +169,7 @@ public class ${javaClassName} extends AMQMethodBody_$version.getMajor()_$version return size; } - public void writeMethodPayload(DataOutputStream buffer) throws IOException + public void writeMethodPayload(ByteBuffer buffer) { #foreach( $field in $method.ConsolidatedFields ) write$field.getEncodingType()( buffer, _$field.Name ); diff --git a/java/common/templates/model/MethodRegistryClass.vm b/java/common/templates/model/MethodRegistryClass.vm index 8258175ce7..759e5e4a42 100644 --- a/java/common/templates/model/MethodRegistryClass.vm +++ b/java/common/templates/model/MethodRegistryClass.vm @@ -30,8 +30,7 @@ package org.apache.qpid.framing; -import java.io.DataInputStream; -import java.io.IOException; +import org.apache.mina.common.ByteBuffer; import org.apache.qpid.framing.abstraction.ProtocolVersionMethodConverter; @@ -54,8 +53,8 @@ public abstract class MethodRegistry #end - public abstract AMQMethodBody convertToBody(DataInputStream in, long size) - throws AMQFrameDecodingException, IOException; + public abstract AMQMethodBody convertToBody(ByteBuffer in, long size) + throws AMQFrameDecodingException; public abstract int getMaxClassId(); @@ -102,4 +101,4 @@ public abstract class MethodRegistry public abstract ProtocolVersionMethodConverter getProtocolVersionMethodConverter(); -} +}
\ No newline at end of file diff --git a/java/common/templates/model/version/MethodRegistryClass.vm b/java/common/templates/model/version/MethodRegistryClass.vm index 79553f7748..277605e34b 100644 --- a/java/common/templates/model/version/MethodRegistryClass.vm +++ b/java/common/templates/model/version/MethodRegistryClass.vm @@ -35,33 +35,32 @@ import org.apache.qpid.protocol.AMQConstant; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.DataInputStream; -import java.io.IOException; +import org.apache.mina.common.ByteBuffer; import org.apache.qpid.framing.abstraction.ProtocolVersionMethodConverter; public class MethodRegistry_$version.getMajor()_$version.getMinor() extends MethodRegistry { - + private static final Logger _log = LoggerFactory.getLogger(MethodRegistry.class); - private ProtocolVersionMethodConverter _protocolVersionConverter = new MethodConverter_$version.getMajor()_$version.getMinor()(); - -#set( $specificModel = $model.asSingleVersionModel() ) - - -#set( $maxClassId = $specificModel.getMaximumClassId()+1 ) - private final AMQMethodBodyInstanceFactory[][] _factories = new AMQMethodBodyInstanceFactory[$maxClassId][]; - - public MethodRegistry_$version.getMajor()_$version.getMinor()() - { - this(new ProtocolVersion((byte)$version.getMajor(),(byte)$version.getMinor())); + private ProtocolVersionMethodConverter _protocolVersionConverter = new MethodConverter_$version.getMajor()_$version.getMinor()(); + +#set( $specificModel = $model.asSingleVersionModel() ) + + +#set( $maxClassId = $specificModel.getMaximumClassId()+1 ) + private final AMQMethodBodyInstanceFactory[][] _factories = new AMQMethodBodyInstanceFactory[$maxClassId][]; + + public MethodRegistry_$version.getMajor()_$version.getMinor()() + { + this(new ProtocolVersion((byte)$version.getMajor(),(byte)$version.getMinor())); } - - public MethodRegistry_$version.getMajor()_$version.getMinor()(ProtocolVersion pv) - { - super(pv); + + public MethodRegistry_$version.getMajor()_$version.getMinor()(ProtocolVersion pv) + { + super(pv); #foreach( $amqpClass in $specificModel.getClassList() ) #set( $amqpClassNameFirstChar = $amqpClass.getName().substring(0,1) ) #set( $amqpClassNameFirstCharU = $amqpClassNameFirstChar.toUpperCase() ) @@ -69,30 +68,30 @@ public class MethodRegistry_$version.getMajor()_$version.getMinor() extends Meth - // Register method body instance factories for the $amqpClassNameUpperCamel class. + // Register method body instance factories for the $amqpClassNameUpperCamel class. -#set( $maxMethodId = $amqpClass.getMaximumMethodId()+1 ) +#set( $maxMethodId = $amqpClass.getMaximumMethodId()+1 ) _factories[$amqpClass.getClassId()] = new AMQMethodBodyInstanceFactory[$maxMethodId]; - + #foreach( $amqpMethod in $amqpClass.getMethodList() ) #set( $amqpMethodNameFirstChar = $amqpMethod.getName().substring(0,1) ) #set( $amqpMethodNameFirstCharU = $amqpMethodNameFirstChar.toUpperCase() ) #set( $amqpMethodNameUpperCamel = "$amqpMethodNameFirstCharU$amqpMethod.getName().substring(1)" ) _factories[$amqpClass.getClassId()][$amqpMethod.getMethodId()] = ${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}BodyImpl.getFactory(); -#end - +#end + #end - - - } + + + } - public AMQMethodBody convertToBody(DataInputStream in, long size) - throws AMQFrameDecodingException, IOException + public AMQMethodBody convertToBody(ByteBuffer in, long size) + throws AMQFrameDecodingException { - int classId = in.readUnsignedShort(); - int methodId = in.readUnsignedShort(); - + int classId = in.getUnsignedShort(); + int methodId = in.getUnsignedShort(); + AMQMethodBodyInstanceFactory bodyFactory; try { @@ -138,15 +137,15 @@ public class MethodRegistry_$version.getMajor()_$version.getMinor() extends Meth public int getMaxClassId() - { - return $specificModel.getMaximumClassId(); - } + { + return $specificModel.getMaximumClassId(); + } public int getMaxMethodId(int classId) - { - return _factories[classId].length - 1; - } - + { + return _factories[classId].length - 1; + } + #foreach( $amqpClass in $specificModel.getClassList() ) @@ -154,12 +153,12 @@ public class MethodRegistry_$version.getMajor()_$version.getMinor() extends Meth #set( $amqpClassNameFirstCharU = $amqpClassNameFirstChar.toUpperCase() ) #set( $amqpClassNameUpperCamel = "$amqpClassNameFirstCharU$amqpClass.getName().substring(1)" ) - + #foreach( $amqpMethod in $amqpClass.getMethodList() ) #set( $amqpMethodNameFirstChar = $amqpMethod.getName().substring(0,1) ) #set( $amqpMethodNameFirstCharU = $amqpMethodNameFirstChar.toUpperCase() ) #set( $amqpMethodNameUpperCamel = "$amqpMethodNameFirstCharU$amqpMethod.getName().substring(1)" ) - public ${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}Body create${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}Body( + public ${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}Body create${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}Body( #foreach( $field in $amqpMethod.FieldList ) #if( $velocityCount == $amqpMethod.getFieldList().size() ) final $field.NativeType $field.Name @@ -167,9 +166,9 @@ public class MethodRegistry_$version.getMajor()_$version.getMinor() extends Meth final $field.NativeType $field.Name, #end #end - ) + ) { - return new ${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}BodyImpl( + return new ${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}BodyImpl( #foreach( $field in $amqpMethod.FieldList ) #if( $velocityCount == $amqpMethod.getFieldList().size() ) $field.Name @@ -177,18 +176,18 @@ public class MethodRegistry_$version.getMajor()_$version.getMinor() extends Meth $field.Name, #end #end - ); + ); } -#end - +#end + #end - - + + public ProtocolVersionMethodConverter getProtocolVersionMethodConverter() { return _protocolVersionConverter; - } + } } |
