diff options
author | Rajith Muditha Attapattu <rajith@apache.org> | 2011-05-27 15:44:23 +0000 |
---|---|---|
committer | Rajith Muditha Attapattu <rajith@apache.org> | 2011-05-27 15:44:23 +0000 |
commit | 66765100f4257159622cefe57bed50125a5ad017 (patch) | |
tree | a88ee23bb194eb91f0ebb2d9b23ff423e3ea8e37 /qpid/java/common/src | |
parent | 1aeaa7b16e5ce54f10c901d75c4d40f9f88b9db6 (diff) | |
parent | 88b98b2f4152ef59a671fad55a0d08338b6b78ca (diff) | |
download | qpid-python-66765100f4257159622cefe57bed50125a5ad017.tar.gz |
Creating a branch for experimenting with some ideas for JMS client.rajith_jms_client
git-svn-id: https://svn.apache.org/repos/asf/qpid/branches/rajith_jms_client@1128369 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'qpid/java/common/src')
269 files changed, 43245 insertions, 0 deletions
diff --git a/qpid/java/common/src/main/java/common.bnd b/qpid/java/common/src/main/java/common.bnd new file mode 100755 index 0000000000..89c397f400 --- /dev/null +++ b/qpid/java/common/src/main/java/common.bnd @@ -0,0 +1,25 @@ +#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+ver: 0.11.0
+
+Bundle-SymbolicName: qpid-common
+Bundle-Version: ${ver}
+Export-Package: *;version=${ver}
+Bundle-RequiredExecutionEnvironment: J2SE-1.5
diff --git a/qpid/java/common/src/main/java/org/apache/configuration/PropertyNameResolver.java b/qpid/java/common/src/main/java/org/apache/configuration/PropertyNameResolver.java new file mode 100644 index 0000000000..73ee747c07 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/configuration/PropertyNameResolver.java @@ -0,0 +1,129 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ + +package org.apache.configuration; + +import java.util.HashMap; +import java.util.Map; + +public class PropertyNameResolver +{ + public static interface Accessor + { + Object get(String name); + } + + private static Map<Class<?>,Accessor> accessors = new HashMap<Class<?>,Accessor>(); + protected Map<String,QpidProperty> properties; + + private static class BooleanAccessor implements Accessor + { + public Boolean get(String name) + { + return Boolean.getBoolean(name); + } + } + + private static class IntegerAccessor implements Accessor + { + public Integer get(String name) + { + return Integer.getInteger(name); + } + } + + private static class LongAccessor implements Accessor + { + public Long get(String name) + { + return Long.getLong(name); + } + } + + private static class StringAccessor implements Accessor + { + public String get(String name) + { + return System.getProperty(name); + } + } + + static + { + accessors.put(Boolean.class, new BooleanAccessor()); + accessors.put(Integer.class, new IntegerAccessor()); + accessors.put(String.class, new StringAccessor()); + accessors.put(Long.class, new LongAccessor()); + } + + public Integer getIntegerValue(String propName) + { + return properties.get(propName).get(Integer.class); + } + + public Long getLongValue(String propName) + { + return properties.get(propName).get(Long.class); + } + + public String getStringValue(String propName) + { + return properties.get(propName).get(String.class); + } + + public Boolean getBooleanValue(String propName) + { + return properties.get(propName).get(Boolean.class); + } + + public <T> T get(String propName,Class<T> klass) + { + return properties.get(propName).get(klass); + } + + static class QpidProperty + { + private Object defValue; + private String[] names; + + QpidProperty(Object defValue, String ... names) + { + this.defValue = defValue; + this.names = names; + } + + <T> T get(Class<T> klass) + { + Accessor acc = accessors.get(klass); + for (String name : names) + { + Object obj = acc.get(name); + if (obj != null) + { + return klass.cast(obj); + } + } + + return klass.cast(defValue); + } + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/mina/common/FixedSizeByteBufferAllocator.java b/qpid/java/common/src/main/java/org/apache/mina/common/FixedSizeByteBufferAllocator.java new file mode 100644 index 0000000000..0c311b6645 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/mina/common/support/DefaultIoFuture.java b/qpid/java/common/src/main/java/org/apache/mina/common/support/DefaultIoFuture.java new file mode 100644 index 0000000000..4fd28c4eb5 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/mina/common/support/IoServiceListenerSupport.java b/qpid/java/common/src/main/java/org/apache/mina/common/support/IoServiceListenerSupport.java new file mode 100644 index 0000000000..5723ffbaa9 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/mina/filter/WriteBufferFullExeception.java b/qpid/java/common/src/main/java/org/apache/mina/filter/WriteBufferFullExeception.java new file mode 100644 index 0000000000..47f19aa76d --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/mina/filter/WriteBufferFullExeception.java @@ -0,0 +1,48 @@ +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 + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 WriteBufferFullExeception extends RuntimeException +{ + private IoFilter.WriteRequest _writeRequest; + + public WriteBufferFullExeception() + { + this(null); + } + + public WriteBufferFullExeception(IoFilter.WriteRequest writeRequest) + { + _writeRequest = writeRequest; + } + + + public void setWriteRequest(IoFilter.WriteRequest writeRequest) + { + _writeRequest = writeRequest; + } + + public IoFilter.WriteRequest getWriteRequest() + { + return _writeRequest; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/mina/filter/WriteBufferLimitFilterBuilder.java b/qpid/java/common/src/main/java/org/apache/mina/filter/WriteBufferLimitFilterBuilder.java new file mode 100644 index 0000000000..4e9db9071a --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/mina/filter/codec/OurCumulativeProtocolDecoder.java b/qpid/java/common/src/main/java/org/apache/mina/filter/codec/OurCumulativeProtocolDecoder.java new file mode 100644 index 0000000000..3f7e206cb4 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/mina/filter/codec/QpidProtocolCodecFilter.java b/qpid/java/common/src/main/java/org/apache/mina/filter/codec/QpidProtocolCodecFilter.java new file mode 100644 index 0000000000..b8c6f29720 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketAcceptor.java b/qpid/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketAcceptor.java new file mode 100644 index 0000000000..e5360d32e0 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketConnector.java b/qpid/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketConnector.java new file mode 100644 index 0000000000..7344f70078 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketFilterChain.java b/qpid/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketFilterChain.java new file mode 100644 index 0000000000..67b8c8d820 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketIoProcessor.java b/qpid/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketIoProcessor.java new file mode 100644 index 0000000000..c23ad8686f --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionConfigImpl.java b/qpid/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionConfigImpl.java new file mode 100644 index 0000000000..043d4800b6 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionImpl.java b/qpid/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionImpl.java new file mode 100644 index 0000000000..be4a2d289d --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/mina/transport/vmpipe/QpidVmPipeConnector.java b/qpid/java/common/src/main/java/org/apache/mina/transport/vmpipe/QpidVmPipeConnector.java new file mode 100644 index 0000000000..a23e546af5 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/qpid/AMQChannelClosedException.java b/qpid/java/common/src/main/java/org/apache/qpid/AMQChannelClosedException.java new file mode 100644 index 0000000000..1b2eabdc86 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/AMQChannelClosedException.java @@ -0,0 +1,41 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid; + +import org.apache.qpid.protocol.AMQConstant; + +/** + * AMQChannelClosedException indicates that an operation cannot be performed becauase a channel has been closed. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represents a failed operation on a closed channel. + * </table> + * + * @todo Does this duplicate AMQChannelException? + */ +public class AMQChannelClosedException extends AMQException +{ + public AMQChannelClosedException(AMQConstant errorCode, String msg, Throwable cause) + { + super(errorCode, msg, cause); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/AMQChannelException.java b/qpid/java/common/src/main/java/org/apache/qpid/AMQChannelException.java new file mode 100644 index 0000000000..ef9420ba87 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/AMQChannelException.java @@ -0,0 +1,59 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.framing.*; +import org.apache.qpid.protocol.AMQConstant; + +/** + * AMQChannelException indicates that an error that requires the channel to be closed has occurred. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represents an error that rquires the channel to be closed. + * </table> + * + * @todo Does this duplicate AMQChannelClosedException? + */ +public class AMQChannelException extends AMQException +{ + private final int _classId; + private final int _methodId; + /* AMQP version for which exception ocurred */ + private final byte major; + private final byte minor; + + public AMQChannelException(AMQConstant errorCode, String msg, int classId, int methodId, byte major, byte minor, + Throwable cause) + { + super(errorCode, msg, cause); + _classId = classId; + _methodId = methodId; + this.major = major; + this.minor = minor; + } + + 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(), new AMQShortString(getMessage()),_classId,_methodId)); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/AMQConnectionClosedException.java b/qpid/java/common/src/main/java/org/apache/qpid/AMQConnectionClosedException.java new file mode 100644 index 0000000000..b2ce3c1b32 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/AMQConnectionClosedException.java @@ -0,0 +1,44 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.protocol.AMQConstant; + +/** + * AMQConnectionClosedException indicates that a connection has been closed. + * + * <p/>This exception is really used as an event, in order that the method handler that raises it creates an event + * which is propagated to the io handler, in order to notify it of the connection closure. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represents a the closure of a connection. + * </table> + * + * @todo Should review where exceptions-as-events + */ +public class AMQConnectionClosedException extends AMQException +{ + public AMQConnectionClosedException(AMQConstant errorCode, String msg, Throwable cause) + { + super(errorCode, msg, cause); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/AMQConnectionException.java b/qpid/java/common/src/main/java/org/apache/qpid/AMQConnectionException.java new file mode 100644 index 0000000000..8ef6facef1 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/AMQConnectionException.java @@ -0,0 +1,70 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.framing.*; +import org.apache.qpid.protocol.AMQConstant; + +/** + * AMQConnectionException indicates that an error that requires the channel to be closed has occurred. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represents an error that rquires the channel to be closed. + * </table> + * + * @todo Does this duplicate AMQChannelClosedException? + */ +public class AMQConnectionException extends AMQException +{ + private final int _classId; + private final int _methodId; + + /** AMQP version for which exception ocurred, major code. */ + private final byte major; + + /** AMQP version for which exception ocurred, minor code. */ + private final byte minor; + + boolean _closeConnetion; + + public AMQConnectionException(AMQConstant errorCode, String msg, int classId, int methodId, byte major, byte minor, + Throwable cause) + { + super(errorCode, msg, cause); + _classId = classId; + _methodId = methodId; + this.major = major; + this.minor = minor; + } + + public AMQFrame getCloseFrame(int channel) + { + MethodRegistry reg = MethodRegistry.getMethodRegistry(new ProtocolVersion(major,minor)); + return new AMQFrame(0, + reg.createConnectionCloseBody(getErrorCode().getCode(), + new AMQShortString(getMessage()), + _classId, + _methodId)); + + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/AMQConnectionFailureException.java b/qpid/java/common/src/main/java/org/apache/qpid/AMQConnectionFailureException.java new file mode 100644 index 0000000000..f2503e549f --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/AMQConnectionFailureException.java @@ -0,0 +1,62 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util.Collection; + +import org.apache.qpid.protocol.AMQConstant; + +/** + * AMQConnectionFailureException indicates that a connection to a broker could not be formed. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represents failure to connect to a broker. + * </table> + * + * @todo Not an AMQP exception as no status code. + */ +public class AMQConnectionFailureException extends AMQException +{ + Collection<Exception> _exceptions; + + public AMQConnectionFailureException(String message, Throwable cause) + { + super(cause instanceof AMQException ? ((AMQException) cause).getErrorCode() : null, message, cause); + } + + public AMQConnectionFailureException(AMQConstant errorCode, String message, Throwable cause) + { + super(errorCode, message, cause); + } + + public AMQConnectionFailureException(String message, Collection<Exception> exceptions) + { + // Blah, I hate ? but java won't let super() be anything other than the first thing, sorry... + super (null, message, exceptions.isEmpty() ? null : exceptions.iterator().next()); + this._exceptions = exceptions; + } + + public Collection<Exception> getLinkedExceptions() + { + return _exceptions; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/AMQDisconnectedException.java b/qpid/java/common/src/main/java/org/apache/qpid/AMQDisconnectedException.java new file mode 100644 index 0000000000..5ec5c42ab9 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/AMQDisconnectedException.java @@ -0,0 +1,39 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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; + +/** + * AMQDisconnectedException indicates that a broker disconnected without failover. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represents disconnection without failover by the broker. + * </table> + * + * @todo Not an AMQP exception as no status code. + */ +public class AMQDisconnectedException extends AMQException +{ + public AMQDisconnectedException(String msg, Throwable cause) + { + super(null, msg, cause); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/AMQException.java b/qpid/java/common/src/main/java/org/apache/qpid/AMQException.java new file mode 100644 index 0000000000..b0c6fccc9e --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/AMQException.java @@ -0,0 +1,124 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.protocol.AMQConstant; + +/** + * AMQException forms the root exception of all exceptions relating to the AMQ protocol. It provides space to associate + * a required AMQ error code with the exception, which is a numeric value, with a meaning defined by the protocol. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represents an exception condition associated with an AMQ protocol status code. + * </table> + * + * @todo This exception class is also used as a generic exception throughout Qpid code. This usage may not be strictly + * correct if this is to signify a protocol exception. Should review. + */ +public class AMQException extends Exception +{ + /** Holds the AMQ error code constant associated with this exception. */ + private AMQConstant _errorCode; + + /** + * Creates an exception with an optional error code, optional message and optional underlying cause. + * + * @param errorCode The error code. May be null if not to be set. + * @param msg The exception message. May be null if not to be set. + * @param cause The underlying cause of the exception. May be null if not to be set. + */ + public AMQException(AMQConstant errorCode, String msg, Throwable cause) + { + super(((msg == null) ? "" : msg), cause); + _errorCode = errorCode; + } + + /* + * Deprecated constructors brought from M2.1 + */ + @Deprecated + public AMQException(String msg) + { + this(null, (msg == null) ? "" : msg); + } + + @Deprecated + public AMQException(AMQConstant errorCode, String msg) + { + this(errorCode, (msg == null) ? "" : msg, null); + } + + @Deprecated + public AMQException(String msg, Throwable cause) + { + this(null, msg, cause); + } + + @Override + public String toString() + { + return getClass().getName() + ": " + getMessage() + (_errorCode == null ? "" : " [error code " + _errorCode + "]"); + } + + /** + * Gets the AMQ protocol exception code associated with this exception. + * + * @return The AMQ protocol exception code associated with this exception. + */ + public AMQConstant getErrorCode() + { + return _errorCode; + } + + public boolean isHardError() + { + return true; + } + + /** + * Rethrown this exception as a new exception. + * + * Attempt to create a new exception of the same class if they have the default constructor of: + * {AMQConstant.class, String.class, Throwable.class} + * <p> + * Individual subclasses may override as requried to create a new instance. + */ + public AMQException cloneForCurrentThread() + { + Class amqeClass = this.getClass(); + Class<?>[] paramClasses = {AMQConstant.class, String.class, Throwable.class}; + Object[] params = {getErrorCode(), getMessage(), this}; + + AMQException newAMQE; + + try + { + newAMQE = (AMQException) amqeClass.getConstructor(paramClasses).newInstance(params); + } + catch (Exception creationException) + { + newAMQE = new AMQException(getErrorCode(), getMessage(), this); + } + + return newAMQE; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/AMQInternalException.java b/qpid/java/common/src/main/java/org/apache/qpid/AMQInternalException.java new file mode 100644 index 0000000000..59dc800c0e --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/AMQInternalException.java @@ -0,0 +1,49 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.protocol.AMQConstant; + +/** + * InternalException encapsulates error code 541, or {@link AMQConstant#INTERNAL_ERROR} exceptions relating to the + * AMQ protocol. It is used to report internal failures and errors that occur within the broker. + */ +public class AMQInternalException extends AMQException +{ + /** serialVersionUID */ + private static final long serialVersionUID = 2544449432843381112L; + + /** + * Creates an exception with an optional message and optional underlying cause. + * + * @param msg The exception message. May be null if not to be set. + * @param cause The underlying cause of the exception. May be null if not to be set. + */ + public AMQInternalException(String msg, Throwable cause) + { + super(AMQConstant.INTERNAL_ERROR, ((msg == null) ? "Internal error" : msg), cause); + } + + public AMQInternalException(String msg) + { + this(msg, null); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/AMQInvalidArgumentException.java b/qpid/java/common/src/main/java/org/apache/qpid/AMQInvalidArgumentException.java new file mode 100644 index 0000000000..baca2a4773 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/AMQInvalidArgumentException.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ +package org.apache.qpid; + +import org.apache.qpid.protocol.AMQConstant; + +/** + * AMQInvalidArgumentException indicates that an invalid argument has been passed to an AMQP method. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represents an error due to an invalid argument being passed to an AMQP method. + * </table> + */ +public class AMQInvalidArgumentException extends AMQException +{ + public AMQInvalidArgumentException(String message, Throwable cause) + { + super(AMQConstant.INVALID_ARGUMENT, message, cause); + } + + public boolean isHardError() + { + return false; + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/AMQInvalidRoutingKeyException.java b/qpid/java/common/src/main/java/org/apache/qpid/AMQInvalidRoutingKeyException.java new file mode 100644 index 0000000000..c117968a29 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/AMQInvalidRoutingKeyException.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.protocol.AMQConstant; + +/** + * AMQInvalidRoutingKeyException indicates an error with a routing key having an invalid format. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represents a format error in a routing key. + * </table> + */ +public class AMQInvalidRoutingKeyException extends AMQException +{ + public AMQInvalidRoutingKeyException(String message, Throwable cause) + { + super(AMQConstant.INVALID_ROUTING_KEY, message, cause); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/AMQPInvalidClassException.java b/qpid/java/common/src/main/java/org/apache/qpid/AMQPInvalidClassException.java new file mode 100644 index 0000000000..ab5141be9d --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/AMQPInvalidClassException.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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; + +/** + * AMQPInvalidClassException indicates an error when trying to store an illegally typed argument in a field table. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represents illegal argument type for field table values. + * </table> + * + * @todo Could just re-use an exising exception like IllegalArgumentException or ClassCastException. + */ +public class AMQPInvalidClassException extends RuntimeException +{ + /** Error message text when trying to store an unsupported class or null object */ + public static final String INVALID_OBJECT_MSG = "Only Primitive objects allowed. Object is: "; + + public AMQPInvalidClassException(String s) + { + super(s); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/AMQProtocolException.java b/qpid/java/common/src/main/java/org/apache/qpid/AMQProtocolException.java new file mode 100644 index 0000000000..bbc569839a --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/AMQProtocolException.java @@ -0,0 +1,38 @@ +package org.apache.qpid; + +import org.apache.qpid.protocol.AMQConstant; + +/* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT 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 AMQProtocolException extends AMQException +{ + /** + * Constructor for a Protocol Exception + * <p> This is the only provided constructor and the parameters have to be + * set to null when they are unknown. + * + * @param msg A description of the reason of this exception . + * @param errorCode A string specifyin the error code of this exception. + * @param cause The linked Execption. + */ + public AMQProtocolException(AMQConstant errorCode, String msg, Throwable cause) + { + super(errorCode, msg, cause); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/AMQSecurityException.java b/qpid/java/common/src/main/java/org/apache/qpid/AMQSecurityException.java new file mode 100644 index 0000000000..d145d2c21d --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/AMQSecurityException.java @@ -0,0 +1,54 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid; + +import org.apache.qpid.protocol.AMQConstant; + +/** + * SecurityException encapsulates error code 403, or {@link AMQConstant#ACCESS_REFUSED} exceptions relating to the + * AMQ protocol. It is used to report authorisation failures and security errors. + */ +public class AMQSecurityException extends AMQException +{ + /** serialVersionUID */ + private static final long serialVersionUID = 8862069852716968394L; + + /** + * Creates an exception with an optional message and optional underlying cause. + * + * @param msg The exception message. May be null if not to be set. + * @param cause The underlying cause of the exception. May be null if not to be set. + */ + public AMQSecurityException(String msg, Throwable cause) + { + super(AMQConstant.ACCESS_REFUSED, ((msg == null) ? "Permission denied" : msg), cause); + } + + public AMQSecurityException(String msg) + { + this(msg, null); + } + + public AMQSecurityException() + { + this(null); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/AMQStoreException.java b/qpid/java/common/src/main/java/org/apache/qpid/AMQStoreException.java new file mode 100644 index 0000000000..8389fe5efa --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/AMQStoreException.java @@ -0,0 +1,48 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.sql.SQLException; + +/** + * StoreException is a specific type of internal error relating to errors in the message store, such as {@link SQLException}. + */ +public class AMQStoreException extends AMQInternalException +{ + /** serialVersionUID */ + private static final long serialVersionUID = 2859681947490637496L; + + /** + * Creates an exception with an optional message and optional underlying cause. + * + * @param msg The exception message. May be null if not to be set. + * @param cause The underlying cause of the exception. May be null if not to be set. + */ + public AMQStoreException(String msg, Throwable cause) + { + super(((msg == null) ? "Store error" : msg), cause); + } + + public AMQStoreException(String msg) + { + this(msg, null); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/AMQTimeoutException.java b/qpid/java/common/src/main/java/org/apache/qpid/AMQTimeoutException.java new file mode 100644 index 0000000000..4ae8282af5 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/AMQTimeoutException.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.protocol.AMQConstant; + +/** + * AMQTimeoutException indicates that an expected response from a broker took too long. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Indicates that an expected response from a broker took too long. + * </table> + */ +public class AMQTimeoutException extends AMQException +{ + public AMQTimeoutException(String message, Throwable cause) + { + super(AMQConstant.REQUEST_TIMEOUT, message, cause); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/AMQUndeliveredException.java b/qpid/java/common/src/main/java/org/apache/qpid/AMQUndeliveredException.java new file mode 100644 index 0000000000..01a569b693 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/AMQUndeliveredException.java @@ -0,0 +1,54 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid; + +import org.apache.qpid.protocol.AMQConstant; + +/** + * AMQUndeliveredException indicates that a message, marked immediate or mandatory, could not be delivered. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represents failure to delivery a message that must be delivered. + * </table> + */ +public class AMQUndeliveredException extends AMQException +{ + private Object _bounced; + + public AMQUndeliveredException(AMQConstant errorCode, String msg, Object bounced, Throwable cause) + { + super(errorCode, msg, cause); + + _bounced = bounced; + } + + public Object getUndeliveredMessage() + { + return _bounced; + } + + public boolean isHardError() + { + return false; + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/AMQUnknownExchangeType.java b/qpid/java/common/src/main/java/org/apache/qpid/AMQUnknownExchangeType.java new file mode 100644 index 0000000000..0eefc03016 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/AMQUnknownExchangeType.java @@ -0,0 +1,43 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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; + +/** + * AMQUnknownExchangeType represents coding error where unknown exchange type requested from exchange factory. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represents unknown exchange type request. + * <tr><td> + * + * @todo Not an AMQP exception as no status code. + * + * @todo Represent coding error, where unknown exchange type is requested by passing a string parameter. Use a type safe + * enum for the exchange type, or replace with IllegalArgumentException. Should be runtime. + */ +public class AMQUnknownExchangeType extends AMQException +{ + public AMQUnknownExchangeType(String message, Throwable cause) + { + super(null, message, cause); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/AMQUnresolvedAddressException.java b/qpid/java/common/src/main/java/org/apache/qpid/AMQUnresolvedAddressException.java new file mode 100644 index 0000000000..eee3e6afcf --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/AMQUnresolvedAddressException.java @@ -0,0 +1,50 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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; + +/** + * AMQUnresolvedAddressException indicates failure to resolve a socket address. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represents failre to resolve a socket address. + * </table> + * + * @todo Not an AMQP exception as no status code. + * + * @todo Why replace java.nio.UnresolvedAddressException with this? This is checked, which may explain why, but it + * doesn't wrap the underlying exception. + */ +public class AMQUnresolvedAddressException extends AMQException +{ + String _broker; + + public AMQUnresolvedAddressException(String message, String broker, Throwable cause) + { + super(null, message, cause); + _broker = broker; + } + + public String toString() + { + return super.toString() + " Broker, \"" + _broker + "\""; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/ConsoleOutput.java b/qpid/java/common/src/main/java/org/apache/qpid/ConsoleOutput.java new file mode 100644 index 0000000000..00ad5cf08a --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/ConsoleOutput.java @@ -0,0 +1,62 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 static org.apache.qpid.transport.util.Functions.str; + +import java.nio.ByteBuffer; + +import org.apache.qpid.transport.Sender; + + +/** + * ConsoleOutput + * + * @author Rafael H. Schloming + */ + +public class ConsoleOutput implements Sender<ByteBuffer> +{ + + public void send(ByteBuffer buf) + { + System.out.println(str(buf)); + } + + public void flush() + { + // pass + } + + public void close() + { + System.out.println("CLOSED"); + } + + public void setIdleTimeout(int i) + { + // TODO Auto-generated method stub + + } + + + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/QpidConfig.java b/qpid/java/common/src/main/java/org/apache/qpid/QpidConfig.java new file mode 100644 index 0000000000..b4cad44130 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/QpidConfig.java @@ -0,0 +1,111 @@ +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. + * + */ + + +/** + * API to configure the Security parameters of the client. + * The user can choose to pick the config from any source + * and set it using this class. + * + */ +public class QpidConfig +{ + private static QpidConfig _instance = new QpidConfig(); + + private SecurityMechanism[] securityMechanisms = + new SecurityMechanism[]{new SecurityMechanism("PLAIN","org.apache.qpid.security.UsernamePasswordCallbackHandler"), + new SecurityMechanism("CRAM_MD5","org.apache.qpid.security.UsernamePasswordCallbackHandler")}; + + private SaslClientFactory[] saslClientFactories = + new SaslClientFactory[]{new SaslClientFactory("AMQPLAIN","org.apache.qpid.security.amqplain.AmqPlainSaslClientFactory")}; + + private QpidConfig(){} + + public static QpidConfig get() + { + return _instance; + } + + public void setSecurityMechanisms(SecurityMechanism... securityMechanisms) + { + this.securityMechanisms = securityMechanisms; + } + + public SecurityMechanism[] getSecurityMechanisms() + { + return securityMechanisms; + } + + public void setSaslClientFactories(SaslClientFactory... saslClientFactories) + { + this.saslClientFactories = saslClientFactories; + } + + public SaslClientFactory[] getSaslClientFactories() + { + return saslClientFactories; + } + + public static class SecurityMechanism + { + String type; + String handler; + + SecurityMechanism(String type,String handler) + { + this.type = type; + this.handler = handler; + } + + public String getHandler() + { + return handler; + } + + public String getType() + { + return type; + } + } + + public static class SaslClientFactory + { + String type; + String factoryClass; + + SaslClientFactory(String type,String factoryClass) + { + this.type = type; + this.factoryClass = factoryClass; + } + + public String getFactoryClass() + { + return factoryClass; + } + + public String getType() + { + return type; + } + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/SerialException.java b/qpid/java/common/src/main/java/org/apache/qpid/SerialException.java new file mode 100644 index 0000000000..c59a6af779 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/SerialException.java @@ -0,0 +1,40 @@ +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. + * + */ + + +/** + * This exception is used by the serial class (imp RFC 1982) + * + */ +public class SerialException extends ArithmeticException +{ + /** + * Constructs an <code>SerialException</code> with the specified + * detail message. + * + * @param message The exception message. + */ + public SerialException(String message) + { + super(message); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/ToyBroker.java b/qpid/java/common/src/main/java/org/apache/qpid/ToyBroker.java new file mode 100644 index 0000000000..5423bbb68f --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/qpid/ToyClient.java b/qpid/java/common/src/main/java/org/apache/qpid/ToyClient.java new file mode 100644 index 0000000000..5b2db10613 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/qpid/ToyExchange.java b/qpid/java/common/src/main/java/org/apache/qpid/ToyExchange.java new file mode 100644 index 0000000000..da6aed9629 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/qpid/api/Message.java b/qpid/java/common/src/main/java/org/apache/qpid/api/Message.java new file mode 100644 index 0000000000..df6f279026 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/api/Message.java @@ -0,0 +1,126 @@ +package org.apache.qpid.api; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.apache.qpid.transport.MessageProperties; +import org.apache.qpid.transport.DeliveryProperties; +import org.apache.qpid.transport.Header; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 interface Message +{ + public Header getHeader(); + + public void setHeader(Header header); + + public MessageProperties getMessageProperties(); + + public DeliveryProperties getDeliveryProperties(); + + /** + * This will abstract the underlying message data. + * The Message implementation may not hold all message + * data in memory (especially in the case of large messages) + * + * The appendData function might write data to + * <ul> + * <li> Memory (Ex: ByteBuffer) + * <li> To Disk + * <li> To Socket (Stream) + * </ul> + * @param src - the data to append + */ + public void appendData(byte[] src) throws IOException; + + + /** + * This will abstract the underlying message data. + * The Message implementation may not hold all message + * data in memory (especially in the case of large messages) + * + * The appendData function might write data to + * <ul> + * <li> Memory (Ex: ByteBuffer) + * <li> To Disk + * <li> To Socket (Stream) + * </ul> + * @param src - the data to append + */ + public void appendData(ByteBuffer src) throws IOException; + + /** + * This will abstract the underlying message data. + * The Message implementation may not hold all message + * data in memory (especially in the case of large messages) + * + * The read function might copy data from + * <ul> + * <li> From memory (Ex: ByteBuffer) + * <li> From Disk + * <li> From Socket as and when it gets streamed + * </ul> + * @param target The target byte[] which the data gets copied to + */ + public void readData(byte[] target) throws IOException; + + /** + * * This will abstract the underlying message data. + * The Message implementation may not hold all message + * data in memory (especially in the case of large messages) + * + * The read function might copy data from + * <ul> + * <li> From memory (Ex: ByteBuffer) + * <li> From Disk + * <li> From Socket as and when it gets streamed + * </ul> + * + * @return A ByteBuffer containing data + * @throws IOException + */ + public ByteBuffer readData() throws IOException; + + /** + * This should clear the body of the message. + */ + public void clearData(); + + /** + * The provides access to the command Id assigned to the + * message transfer. + * This id is useful when you do + * <ul> + * <li>For message acquiring - If the transfer happend in no-acquire mode + * you could use this id to accquire it. + * <li>For releasing a message. You can use this id to release an acquired + * message + * <li>For Acknowledging a message - You need to pass this ID, in order to + * acknowledge the message + * <li>For Rejecting a message - You need to pass this ID, in order to reject + * the message. + * </ul> + * + * @return the message transfer id. + */ + public int getMessageTransferId(); + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/codec/AMQCodecFactory.java b/qpid/java/common/src/main/java/org/apache/qpid/codec/AMQCodecFactory.java new file mode 100644 index 0000000000..591dbd085b --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/codec/AMQCodecFactory.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.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; + +/** + * AMQCodecFactory is a Mina codec factory. It supplies the encoders and decoders need to read and write the bytes to + * the wire. + * + * <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 implements ProtocolCodecFactory +{ + /** Holds the protocol encoder. */ + private final AMQEncoder _encoder = new AMQEncoder(); + + /** Holds the protocol decoder. */ + private final AMQDecoder _frameDecoder; + + /** + * Creates a new codec factory, specifiying whether it is expected that the first frame of data should be an + * initiation. This is the case for the broker, which always expects to received the protocol initiation on a newly + * connected client. + * + * @param expectProtocolInitiation <tt>true</tt> if the first frame received is going to be a protocol initiation + * frame, <tt>false</tt> if it is going to be a standard AMQ data block. + */ + public AMQCodecFactory(boolean expectProtocolInitiation, AMQVersionAwareProtocolSession session) + { + _frameDecoder = new AMQDecoder(expectProtocolInitiation, session); + } + + /** + * Gets the AMQP encoder. + * + * @return The AMQP encoder. + */ + public ProtocolEncoder getEncoder() + { + return _encoder; + } + + /** + * Gets the AMQP decoder. + * + * @return The AMQP decoder. + */ + public AMQDecoder getDecoder() + { + return _frameDecoder; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/codec/AMQDecoder.java b/qpid/java/common/src/main/java/org/apache/qpid/codec/AMQDecoder.java new file mode 100644 index 0000000000..281c0761d9 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/codec/AMQDecoder.java @@ -0,0 +1,340 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 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; +import org.apache.qpid.framing.AMQFrameDecodingException; +import org.apache.qpid.framing.AMQMethodBodyFactory; +import org.apache.qpid.framing.AMQProtocolVersionException; +import org.apache.qpid.framing.ProtocolInitiation; +import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; + +/** + * AMQDecoder delegates the decoding of AMQP either to a data block decoder, or in the case of new connections, to a + * protocol initiation decoder. It is a cumulative decoder, which means that it can accumulate data to decode in the + * buffer until there is enough data to decode. + * + * <p/>One instance of this class is created per session, so any changes or configuration done at run time to the + * decoder will only affect decoding of the protocol session data to which is it bound. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Delegate protocol initiation to its decoder. <td> {@link ProtocolInitiation.Decoder} + * <tr><td> Delegate AMQP data to its decoder. <td> {@link AMQDataBlockDecoder} + * <tr><td> Accept notification that protocol initiation has completed. + * </table> + * + * @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 extends CumulativeProtocolDecoder +{ + + private static final String BUFFER = AMQDecoder.class.getName() + ".Buffer"; + + /** Holds the 'normal' AMQP data decoder. */ + private AMQDataBlockDecoder _dataBlockDecoder = new AMQDataBlockDecoder(); + + /** Holds the protocol initiation decoder. */ + private ProtocolInitiation.Decoder _piDecoder = new ProtocolInitiation.Decoder(); + + /** Flag to indicate whether this decoder needs to handle protocol initiation. */ + private boolean _expectProtocolInitiation; + private boolean firstDecode = true; + + private AMQMethodBodyFactory _bodyFactory; + + private ByteBuffer _remainingBuf; + + /** + * Creates a new AMQP decoder. + * + * @param expectProtocolInitiation <tt>true</tt> if this decoder needs to handle protocol initiation. + */ + public AMQDecoder(boolean expectProtocolInitiation, AMQVersionAwareProtocolSession session) + { + _expectProtocolInitiation = expectProtocolInitiation; + _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 + * initation decoder. This method is expected to be called with <tt>false</tt> once protocol initation completes. + * + * @param expectProtocolInitiation <tt>true</tt> to use the protocol initiation decoder, <tt>false</tt> to use the + * data decoder. + */ + public void setExpectProtocolInitiation(boolean expectProtocolInitiation) + { + _expectProtocolInitiation = expectProtocolInitiation; + } + + + /** + * 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 ) + { + buf.put( in ); + buf.flip(); + } + else + { + buf = in; + } + + 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 + { + 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 ); + } + + private void removeSessionBuffer(IoSession session) + { + ByteBuffer buf = ( ByteBuffer ) session.getAttribute( BUFFER ); + if( buf != null ) + { + 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(java.nio.ByteBuffer buf) throws AMQFrameDecodingException, AMQProtocolVersionException + { + + // get prior remaining data from accumulator + ArrayList<AMQDataBlock> dataBlocks = new ArrayList<AMQDataBlock>(); + ByteBuffer msg; + // if we have a session buffer, append data to that otherwise + // use the buffer read from the network directly + if( _remainingBuf != null ) + { + _remainingBuf.put(buf); + _remainingBuf.flip(); + msg = _remainingBuf; + } + else + { + msg = ByteBuffer.wrap(buf); + } + + if (_expectProtocolInitiation + || (firstDecode + && (msg.remaining() > 0) + && (msg.get(msg.position()) == (byte)'A'))) + { + if (_piDecoder.decodable(msg.buf())) + { + dataBlocks.add(new ProtocolInitiation(msg.buf())); + } + } + else + { + boolean enoughData = true; + while (enoughData) + { + int pos = msg.position(); + + enoughData = _dataBlockDecoder.decodable(msg); + msg.position(pos); + if (enoughData) + { + dataBlocks.add(_dataBlockDecoder.createAndPopulateFrame(_bodyFactory, msg)); + } + else + { + _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/qpid/java/common/src/main/java/org/apache/qpid/codec/AMQEncoder.java b/qpid/java/common/src/main/java/org/apache/qpid/codec/AMQEncoder.java new file mode 100644 index 0000000000..53f48ae1c8 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/qpid/common/AMQPFilterTypes.java b/qpid/java/common/src/main/java/org/apache/qpid/common/AMQPFilterTypes.java new file mode 100644 index 0000000000..9ed915cc35 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/common/AMQPFilterTypes.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.common; + +import org.apache.qpid.framing.AMQShortString; + +/** + * Specifies the different filter types for consumers that filter their messages. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represent different consumer filter types. + * </table> + */ +public enum AMQPFilterTypes +{ + JMS_SELECTOR("x-filter-jms-selector"), + NO_CONSUME("x-filter-no-consume"), + AUTO_CLOSE("x-filter-auto-close"); + + /** The identifying string for the filter type. */ + private final AMQShortString _value; + + /** + * Creates a new filter type from its identifying string. + * + * @param value The identifying string. + */ + AMQPFilterTypes(String value) + { + _value = new AMQShortString(value); + } + + /** + * Gets the identifying string of the filter type. + * + * @return The identifying string of the filter type. + */ + public AMQShortString getValue() + { + return _value; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/common/ClientProperties.java b/qpid/java/common/src/main/java/org/apache/qpid/common/ClientProperties.java new file mode 100644 index 0000000000..7371c12519 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/common/ClientProperties.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.common; + +import org.apache.qpid.framing.AMQShortString; + +/** + * Specifies the available client property types that different clients can use to identify themselves with. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Specify the available client property types. + * </table> + */ +public enum ClientProperties +{ + instance("instance"), + product("product"), + version("version"), + platform("platform"); + + private final AMQShortString _amqShortString; + + private ClientProperties(String name) + { + _amqShortString = new AMQShortString(name); + } + + + public AMQShortString toAMQShortString() + { + return _amqShortString; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/common/Closeable.java b/qpid/java/common/src/main/java/org/apache/qpid/common/Closeable.java new file mode 100644 index 0000000000..45a98b5843 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/common/Closeable.java @@ -0,0 +1,27 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.common; + + +public interface Closeable +{ + public void close(); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/common/QpidProperties.java b/qpid/java/common/src/main/java/org/apache/qpid/common/QpidProperties.java new file mode 100644 index 0000000000..2c783aeaa4 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/common/QpidProperties.java @@ -0,0 +1,190 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.common; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.Properties; + +/** + * QpidProperties captures the project name, version number, and source code repository revision number from a properties + * file which is generated as part of the build process. Normally, the name and version number are pulled from the module + * name and version number of the Maven build POM, but could come from other sources if the build system is changed. The + * idea behind this, is that every build has these values incorporated directly into its jar file, so that code in the + * wild can be identified, should its origination be forgotten. + * + * <p/>To get the build version of any Qpid code call the {@link #main} method. This version string is usually also + * printed to the console on broker start up. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><td>Load build versioning information into the runtime, for code identification purposes. + * </table> + * + * @todo Code to locate/load/log properties can be factored into a reusable properties utils class. Avoid having this + * same snippet of loading code scattered in many places. + * + * @todo Could also add a build number property for a sequential build number assigned by an automated build system, for + * build reproducability purposes. + */ +public class QpidProperties +{ + /** Used for debugging purposes. */ + private static final Logger _logger = LoggerFactory.getLogger(QpidProperties.class); + + /** The name of the version properties file to load from the class path. */ + public static final String VERSION_RESOURCE = "qpidversion.properties"; + + /** Defines the name of the product property. */ + public static final String PRODUCT_NAME_PROPERTY = "qpid.name"; + + /** Defines the name of the version property. */ + public static final String RELEASE_VERSION_PROPERTY = "qpid.version"; + + /** Defines the name of the source code revision property. */ + public static final String BUILD_VERSION_PROPERTY = "qpid.svnversion"; + + /** Defines the default value for all properties that cannot be loaded. */ + private static final String DEFAULT = "unknown"; + + /** Holds the product name. */ + private static String productName = DEFAULT; + + /** Holds the product version. */ + private static String releaseVersion = DEFAULT; + + /** Holds the source code revision. */ + private static String buildVersion = DEFAULT; + + // Loads the values from the version properties file. + static + { + Properties props = new Properties(); + + try + { + InputStream propertyStream = QpidProperties.class.getClassLoader().getResourceAsStream(VERSION_RESOURCE); + if (propertyStream == null) + { + _logger.warn("Unable to find resource " + VERSION_RESOURCE + " from classloader"); + } + else + { + props.load(propertyStream); + + if (_logger.isDebugEnabled()) + { + _logger.debug("Dumping QpidProperties"); + for (Map.Entry<Object, Object> entry : props.entrySet()) + { + _logger.debug("Property: " + entry.getKey() + " Value: " + entry.getValue()); + } + + _logger.debug("End of property dump"); + } + + productName = readPropertyValue(props, PRODUCT_NAME_PROPERTY); + releaseVersion = readPropertyValue(props, RELEASE_VERSION_PROPERTY); + buildVersion = readPropertyValue(props, BUILD_VERSION_PROPERTY); + } + } + catch (IOException e) + { + // Log a warning about this and leave the values initialized to unknown. + _logger.error("Could not load version.properties resource: " + e, e); + } + } + + /** + * Gets the product name. + * + * @return The product name. + */ + public static String getProductName() + { + return productName; + } + + /** + * Gets the product version. + * + * @return The product version. + */ + public static String getReleaseVersion() + { + return releaseVersion; + } + + /** + * Gets the source code revision. + * + * @return The source code revision. + */ + public static String getBuildVersion() + { + return buildVersion; + } + + /** + * Extracts all of the version information as a printable string. + * + * @return All of the version information as a printable string. + */ + public static String getVersionString() + { + return getProductName() + " - " + getReleaseVersion() + " build: " + getBuildVersion(); + } + + /** + * Helper method to extract a named property from properties. + * + * @param props The properties. + * @param propertyName The named property to extract. + * + * @return The extracted property or a default value if the properties do not contain the named property. + * + * @todo A bit pointless. + */ + private static String readPropertyValue(Properties props, String propertyName) + { + String retVal = (String) props.get(propertyName); + if (retVal == null) + { + retVal = DEFAULT; + } + + return retVal; + } + + /** + * Prints the versioning information to the console. This is extremely usefull for identifying Qpid code in the + * wild, where the origination of the code has been forgotten. + * + * @param args Does not require any arguments. + */ + public static void main(String[] args) + { + System.out.println(getVersionString()); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/configuration/Accessor.java b/qpid/java/common/src/main/java/org/apache/qpid/configuration/Accessor.java new file mode 100644 index 0000000000..dc5b69dc89 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/configuration/Accessor.java @@ -0,0 +1,273 @@ +package org.apache.qpid.configuration; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +public interface Accessor +{ + public Boolean getBoolean(String name); + public Integer getInt(String name); + public Long getLong(String name); + public String getString(String name); + + static class SystemPropertyAccessor implements Accessor + { + public Boolean getBoolean(String name) + { + return Boolean.getBoolean(name); + } + + public Integer getInt(String name) + { + return Integer.getInteger(name); + } + + public Long getLong(String name) + { + return Long.getLong(name); + } + + public String getString(String name) + { + return System.getProperty(name); + } + } + + static class MapAccessor implements Accessor + { + protected Map<Object,Object> source; + + public MapAccessor(Map<Object,Object> map) + { + source = map; + } + + public Boolean getBoolean(String name) + { + if (source != null && source.containsKey(name)) + { + if (source.get(name) instanceof Boolean) + { + return (Boolean)source.get(name); + } + else + { + return Boolean.parseBoolean((String)source.get(name)); + } + } + else + { + return null; + } + } + + public Integer getInt(String name) + { + if (source != null && source.containsKey(name)) + { + if (source.get(name) instanceof Integer) + { + return (Integer)source.get(name); + } + else + { + return Integer.parseInt((String)source.get(name)); + } + } + else + { + return null; + } + } + + public Long getLong(String name) + { + if (source != null && source.containsKey(name)) + { + if (source.get(name) instanceof Long) + { + return (Long)source.get(name); + } + else + { + return Long.parseLong((String)source.get(name)); + } + } + else + { + return null; + } + } + + public String getString(String name) + { + if (source != null && source.containsKey(name)) + { + if (source.get(name) instanceof String) + { + return (String)source.get(name); + } + else + { + return String.valueOf(source.get(name)); + } + } + else + { + return null; + } + } + } + + static class PropertyFileAccessor extends MapAccessor + { + public PropertyFileAccessor(String fileName) throws FileNotFoundException, IOException + { + super(null); + Properties props = new Properties(); + FileInputStream inStream = new FileInputStream(fileName); + try + { + props.load(inStream); + } + finally + { + inStream.close(); + } + source = props; + } + } + + static class CombinedAccessor implements Accessor + { + private List<Accessor> accessors; + + public CombinedAccessor(Accessor...accessors) + { + this.accessors = Arrays.asList(accessors); + } + + public Boolean getBoolean(String name) + { + for (Accessor accessor: accessors) + { + if (accessor.getBoolean(name) != null) + { + return accessor.getBoolean(name); + } + } + return null; + } + + public Integer getInt(String name) + { + for (Accessor accessor: accessors) + { + if (accessor.getBoolean(name) != null) + { + return accessor.getInt(name); + } + } + return null; + } + + public Long getLong(String name) + { + for (Accessor accessor: accessors) + { + if (accessor.getBoolean(name) != null) + { + return accessor.getLong(name); + } + } + return null; + } + + public String getString(String name) + { + for (Accessor accessor: accessors) + { + if (accessor.getBoolean(name) != null) + { + return accessor.getString(name); + } + } + return null; + } + } + + static class ValidationAccessor implements Accessor + { + private List<Validator> validators; + private Accessor delegate; + + public ValidationAccessor(Accessor delegate,Validator...validators) + { + this.validators = Arrays.asList(validators); + this.delegate = delegate; + } + + public Boolean getBoolean(String name) + { + // there is nothing to validate in a boolean + return delegate.getBoolean(name); + } + + public Integer getInt(String name) + { + Integer v = delegate.getInt(name); + for (Validator validator: validators) + { + validator.validate(v); + } + return v; + } + + public Long getLong(String name) + { + Long v = delegate.getLong(name); + for (Validator validator: validators) + { + validator.validate(v); + } + return v; + } + + public String getString(String name) + { + String v = delegate.getString(name); + for (Validator validator: validators) + { + validator.validate(v); + } + return v; + } + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/configuration/ClientProperties.java b/qpid/java/common/src/main/java/org/apache/qpid/configuration/ClientProperties.java new file mode 100644 index 0000000000..0dd21238a7 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/configuration/ClientProperties.java @@ -0,0 +1,134 @@ +/* Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.configuration; + +/** + * This class centralized the Qpid client properties. + */ +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. + * Therefore an exception is always thrown unless this property is set to true. + * type: boolean + */ + public static final String IGNORE_SET_CLIENTID_PROP_NAME = "ignore_setclientID"; + + /** + * This property is currently used within the 0.10 code path only + * The maximum number of pre-fetched messages per destination + * This property is used for all the connection unless it is overwritten by the connectionURL + * type: long + */ + public static final String MAX_PREFETCH_PROP_NAME = "max_prefetch"; + public static final String MAX_PREFETCH_DEFAULT = "500"; + + /** + * When true a sync command is sent after every persistent messages. + * type: boolean + */ + public static final String SYNC_PERSISTENT_PROP_NAME = "sync_persistence"; + + /** + * When true a sync command is sent after sending a message ack. + * type: boolean + */ + public static final String SYNC_ACK_PROP_NAME = "sync_ack"; + + /** + * sync_publish property - {persistent|all} + * If set to 'persistent',then persistent messages will be publish synchronously + * If set to 'all', then all messages regardless of the delivery mode will be + * published synchronously. + */ + public static final String SYNC_PUBLISH_PROP_NAME = "sync_publish"; + + /** + * This value will be used in the following settings + * To calculate the SO_TIMEOUT option of the socket (2*idle_timeout) + * If this values is between the max and min values specified for heartbeat + * 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). + */ + public static final String DEST_SYNTAX = "qpid.dest_syntax"; + + public static final String USE_LEGACY_MAP_MESSAGE_FORMAT = "qpid.use_legacy_map_message"; + + /** + * ========================================================== + * Those properties are used when the io size should be bounded + * ========================================================== + */ + + /** + * 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 PROTECTIO_PROP_NAME = "protectio"; + + //=== The following properties are only used when the previous one is true. + /** + * Max size of read messages that can be stored within the MINA layer + * type: int + */ + 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 = + 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/qpid/java/common/src/main/java/org/apache/qpid/configuration/Configured.java b/qpid/java/common/src/main/java/org/apache/qpid/configuration/Configured.java new file mode 100644 index 0000000000..22903888fe --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/configuration/Configured.java @@ -0,0 +1,44 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.configuration; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a field as having a "configured" value injected into it by a configurator. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Configured +{ + /** + * The Commons Configuration path to the configuration element + */ + String path(); + + /** + * The default value to use should the path not be found in the configuration source + */ + String defaultValue(); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/configuration/PropertyException.java b/qpid/java/common/src/main/java/org/apache/qpid/configuration/PropertyException.java new file mode 100644 index 0000000000..73a336321c --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/configuration/PropertyException.java @@ -0,0 +1,43 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.configuration; + +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; + +/** + * Indicates a failure to parse a property expansion. See {@link PropertyUtils} for the code that does property + * expansions. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaboration + * <tr><td> Represent failure to expand a property name into a value. + * </table> + * + * @todo Not an AMQP exception as no status code. + */ +public class PropertyException extends AMQException +{ + public PropertyException(String message, Throwable cause) + { + super(null, message, cause); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/configuration/PropertyUtils.java b/qpid/java/common/src/main/java/org/apache/qpid/configuration/PropertyUtils.java new file mode 100644 index 0000000000..6e2b25fb2c --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/configuration/PropertyUtils.java @@ -0,0 +1,164 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.configuration; + +import java.util.ArrayList; +import java.util.Iterator; + +/** + * PropertyUtils provides helper methods for dealing with Java properties. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Expand system properties into strings with named expansions. + * </table> + * + * @todo Make the lookup method generic by passing in the properties to use for the expansion, rather than hard coding + * as system properties. The expansion code has greater potential for re-use that way. + * + * @todo Some more property related code could be added to this utils class, which might more appropriately reside under + * org.apache.qpid.util. For example standardised code to load properties from a resource name, currently found in + * QpidProperties and possibly other places could be moved here. + */ +public class PropertyUtils +{ + /** + * Given a string that contains substrings of the form <code>${xxx}</code>, looks up the valuea of 'xxx' as a + * system properties and substitutes tham back into the original string, to provide a property value expanded + * string. + * + * @param value The string to be scanned for property references. May be <code>null</code>, in which case this + * method returns immediately with no effect. + * + * @return The original string with the properties replaced, or <code>null</code> if the original string is + * <code>null</code>. + * + * @throws PropertyException If the string contains an opening <code>${</code> without a balancing <code>}</code>, + * or if the property to expand does not exist as a system property. + */ + public static String replaceProperties(String value) throws PropertyException + { + if (value == null) + { + return null; + } + + ArrayList<String> fragments = new ArrayList<String>(); + ArrayList<String> propertyRefs = new ArrayList<String>(); + parsePropertyString(value, fragments, propertyRefs); + + StringBuffer sb = new StringBuffer(); + Iterator j = propertyRefs.iterator(); + + for (String fragment : fragments) + { + if (fragment == null) + { + String propertyName = (String) j.next(); + + // try to get it from the project or keys + // Backward compatibility + String replacement = System.getProperty(propertyName); + + if (replacement == null) + { + throw new PropertyException("Property ${" + propertyName + "} has not been set", null); + } + + fragment = replacement; + } + + sb.append(fragment); + } + + return sb.toString(); + } + + /** + * Parses the supplied value for properties which are specified using ${foo} syntax. $X is left as is, and $$ + * specifies a single $. + * + * @param value The property string to parse. + * @param fragments Is populated with the string fragments. A null means "insert a property value here. The number + * of nulls in the list when populated is equal to the size of the propertyRefs list. + * @param propertyRefs Populated with the property names to be added into the final string. + */ + private static void parsePropertyString(String value, ArrayList<String> fragments, ArrayList<String> propertyRefs) + throws PropertyException + { + int prev = 0; + int pos; + // search for the next instance of $ from the 'prev' position + while ((pos = value.indexOf("$", prev)) >= 0) + { + + // if there was any text before this, add it as a fragment + if (pos > 0) + { + fragments.add(value.substring(prev, pos)); + } + // if we are at the end of the string, we tack on a $ + // then move past it + if (pos == (value.length() - 1)) + { + fragments.add("$"); + prev = pos + 1; + } + else if (value.charAt(pos + 1) != '{') + { + // peek ahead to see if the next char is a property or not + // not a property: insert the char as a literal + if (value.charAt(pos + 1) == '$') + { + // two $ map to one $ + fragments.add("$"); + prev = pos + 2; + } + else + { + // $X maps to $X for all values of X!='$' + fragments.add(value.substring(pos, pos + 2)); + prev = pos + 2; + } + } + else + { + // property found, extract its name or bail on a typo + int endName = value.indexOf('}', pos); + if (endName < 0) + { + throw new PropertyException("Syntax error in property: " + value, null); + } + + String propertyName = value.substring(pos + 2, endName); + fragments.add(null); + propertyRefs.add(propertyName); + prev = endName + 1; + } + } + // no more $ signs found + // if there is any tail to the file, append it + if (prev < value.length()) + { + fragments.add(value.substring(prev)); + } + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/configuration/QpidProperty.java b/qpid/java/common/src/main/java/org/apache/qpid/configuration/QpidProperty.java new file mode 100644 index 0000000000..9c0aaaec89 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/configuration/QpidProperty.java @@ -0,0 +1,181 @@ +/* Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.configuration; + +import org.apache.qpid.configuration.Accessor.SystemPropertyAccessor; + +abstract class QpidProperty<T> +{ + private T defValue; + private String[] names; + protected Accessor accessor; + + QpidProperty(T defValue, String... names) + { + this(new SystemPropertyAccessor(),defValue,names); + } + + QpidProperty(Accessor accessor,T defValue, String... names) + { + this.accessor = accessor; + this.defValue = defValue; + this.names = names; + } + + T get() + { + for (String name : names) + { + T obj = getByName(name); + if (obj != null) + { + return obj; + } + } + + return defValue; + } + + protected abstract T getByName(String name); + + public static QpidProperty<Boolean> booleanProperty(Boolean defaultValue, + String... names) + { + return new QpidBooleanProperty(defaultValue, names); + } + + public static QpidProperty<Boolean> booleanProperty(Accessor accessor, + Boolean defaultValue,String... names) + { + return new QpidBooleanProperty(accessor,defaultValue, names); + } + + public static QpidProperty<Integer> intProperty(Integer defaultValue, + String... names) + { + return new QpidIntProperty(defaultValue, names); + } + + public static QpidProperty<Integer> intProperty(Accessor accessor, + Integer defaultValue, String... names) + { + return new QpidIntProperty(accessor,defaultValue, names); + } + + public static QpidProperty<Long> longProperty(Long defaultValue, + String... names) + { + return new QpidLongProperty(defaultValue, names); + } + + public static QpidProperty<Long> longProperty(Accessor accessor, + Long defaultValue, String... names) + { + return new QpidLongProperty(accessor,defaultValue, names); + } + + public static QpidProperty<String> stringProperty(String defaultValue, + String... names) + { + return new QpidStringProperty(defaultValue, names); + } + + public static QpidProperty<String> stringProperty(Accessor accessor, + String defaultValue,String... names) + { + return new QpidStringProperty(accessor,defaultValue, names); + } + + static class QpidBooleanProperty extends QpidProperty<Boolean> + { + QpidBooleanProperty(Boolean defValue, String... names) + { + super(defValue, names); + } + + QpidBooleanProperty(Accessor accessor,Boolean defValue, String... names) + { + super(accessor,defValue, names); + } + + @Override + protected Boolean getByName(String name) + { + return accessor.getBoolean(name); + } + } + + static class QpidIntProperty extends QpidProperty<Integer> + { + QpidIntProperty(Integer defValue, String... names) + { + super(defValue, names); + } + + QpidIntProperty(Accessor accessor,Integer defValue, String... names) + { + super(accessor,defValue, names); + } + + @Override + protected Integer getByName(String name) + { + return accessor.getInt(name); + } + } + + static class QpidLongProperty extends QpidProperty<Long> + { + QpidLongProperty(Long defValue, String... names) + { + super(defValue, names); + } + + QpidLongProperty(Accessor accessor,Long defValue, String... names) + { + super(accessor,defValue, names); + } + + @Override + protected Long getByName(String name) + { + return accessor.getLong(name); + } + } + + static class QpidStringProperty extends QpidProperty<String> + { + QpidStringProperty(String defValue, String... names) + { + super(defValue, names); + } + + QpidStringProperty(Accessor accessor,String defValue, String... names) + { + super(accessor,defValue, names); + } + + @Override + protected String getByName(String name) + { + return accessor.getString(name); + } + } + +}
\ No newline at end of file diff --git a/qpid/java/common/src/main/java/org/apache/qpid/configuration/Validator.java b/qpid/java/common/src/main/java/org/apache/qpid/configuration/Validator.java new file mode 100644 index 0000000000..13f7954bbc --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/configuration/Validator.java @@ -0,0 +1,31 @@ +package org.apache.qpid.configuration; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 interface Validator +{ + public void validate(Integer value); + + public void validate(Long value); + + public void validate(String value); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/dtx/XidImpl.java b/qpid/java/common/src/main/java/org/apache/qpid/dtx/XidImpl.java new file mode 100644 index 0000000000..69457ca4a9 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/dtx/XidImpl.java @@ -0,0 +1,259 @@ +/* Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.dtx; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.qpid.AMQInvalidArgumentException; + +import javax.transaction.xa.Xid; + +import java.io.*; +import java.util.Arrays; + +/** + * Implements javax.transaction.dtx.Xid + */ +public class XidImpl implements Xid +{ + /** + * this session's logger + */ + private static final Logger _logger = LoggerFactory.getLogger(XidImpl.class); + + /** + * the transaction branch identifier part of XID as an array of bytes + */ + private byte[] _branchQualifier; + + /** + * the format identifier part of the XID. + */ + private int _formatID; + + /** + * the global transaction identifier part of XID as an array of bytes. + */ + private byte[] _globalTransactionID; + + //--- Constructors + + /** + * Create new Xid. + * this is an empty constructor. + */ + public XidImpl() + { + + } + + /** + * Create a new XidImpl from an existing Xid. + * <p> Usefull for casting external Xids + * + * @param xid Foreign Xid. + */ + public XidImpl(Xid xid) + { + _branchQualifier = xid.getBranchQualifier(); + _formatID = xid.getFormatId(); + _globalTransactionID = xid.getGlobalTransactionId(); + } + + /** + * Create a new Xid. + * + * @param branchQualifier The transaction branch identifier part of XID as an array of bytes. + * @param format The format identifier part of the XID. + * @param globalTransactionID The global transaction identifier part of XID as an array of bytes. + */ + public XidImpl(byte[] branchQualifier, int format, byte[] globalTransactionID) + { + _branchQualifier = branchQualifier; + _formatID = format; + _globalTransactionID = globalTransactionID; + } + + /** + * Create a new Xid form its String form + * 4 1 1 g b + * +---+---+---+---+---+---+---+- -+---+---+- -+---+ + * | format_id | g | b | txn-id | br-id | + * +---+---+---+---+---+---+---+- -+---+---+- -+---+ + * 0 4 5 6 6+g 6+g+b + * format_id: an implementation specific format identifier + * <p/> + * gtrid_length: how many bytes of this form the transaction id + * <p/> + * bqual_length: how many bytes of this form the branch id + * <p/> + * data: a sequence of octets of at most 128 bytes containing the txn id and the + * branch id + * <p/> + * Note - The sum of the two lengths must equal the length of the data field. + * + * @param xid an XID STring Form + * @throws AMQInvalidArgumentException If the string does not represent a valid Xid + */ + public XidImpl(String xid) throws AMQInvalidArgumentException + { + if (_logger.isDebugEnabled()) + { + _logger.debug("converting string " + xid + " into XidImpl"); + } + try + { + DataInputStream input = new DataInputStream(new ByteArrayInputStream(xid.getBytes())); + _formatID = (int) input.readLong(); + int g = input.readByte(); + int b = input.readByte(); + _globalTransactionID = new byte[g]; + _branchQualifier = new byte[b]; + if (input.read(_globalTransactionID, 0, g) != g) + { + throw new AMQInvalidArgumentException("Cannot convert the string " + xid + " into an Xid", null); + } + if (input.read(_branchQualifier, 0, b) != b) + { + throw new AMQInvalidArgumentException("Cannot convert the string " + xid + " into an Xid", null); + } + } + catch (IOException e) + { + throw new AMQInvalidArgumentException("cannot convert the string " + xid + " into an Xid", e); + } + } + + //--- Xid interface implementation + + /** + * Format identifier. O means the OSI CCR format. + * + * @return Global transaction identifier. + */ + public byte[] getGlobalTransactionId() + { + return _globalTransactionID; + } + + /** + * Obtain the transaction branch identifier part of XID as an array of bytes. + * + * @return Branch identifier part of XID. + */ + public byte[] getBranchQualifier() + { + return _branchQualifier; + } + + /** + * Obtain the format identifier part of the XID. + * + * @return Format identifier. O means the OSI CCR format. + */ + public int getFormatId() + { + return _formatID; + } + + //--- Object operations + + /** + * Indicates whether some other Xid is "equal to" this one. + * <p> Two Xids are equal if and only if their three elementary parts are equal + * + * @param o the object to compare this <code>XidImpl</code> against. + * @return true if the <code>XidImpl</code> are equal, false otherwise. + */ + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (o instanceof XidImpl) + { + XidImpl other = (XidImpl) o; + if (_formatID == other.getFormatId()) + { + if (_branchQualifier.length == other.getBranchQualifier().length) + { + for (int i = 0; i < _branchQualifier.length; i++) + { + if (_branchQualifier[i] != other.getBranchQualifier()[i]) + { + return false; + } + } + if (_globalTransactionID.length == other.getGlobalTransactionId().length) + { + for (int i = 0; i < _globalTransactionID.length; i++) + { + if (_globalTransactionID[i] != other.getGlobalTransactionId()[i]) + { + return false; + } + } + // everithing is equal + return true; + } + } + } + } + return false; + } + + @Override + public int hashCode() + { + int result = _branchQualifier != null ? Arrays.hashCode(_branchQualifier) : 0; + result = 31 * result + _formatID; + result = 31 * result + (_globalTransactionID != null ? Arrays.hashCode(_globalTransactionID) : 0); + return result; + } + + //-- Static helper method + /** + * Convert an Xid into the AMQP String format. + * + * 4 1 1 g b + * +---+---+---+---+---+---+---+- -+---+---+- -+---+ + * | format_id | g | b | txn-id | br-id | + * +---+---+---+---+---+---+---+- -+---+---+- -+---+ + * 0 4 5 6 6+g 6+g+b + * format_id: an implementation specific format identifier + * <p/> + * gtrid_length: how many bytes of this form the transaction id + * <p/> + * bqual_length: how many bytes of this form the branch id + * <p/> + * data: a sequence of octets of at most 128 bytes containing the txn id and the + * branch id + * <p/> + * Note - The sum of the two lengths must equal the length of the data field. + * + * @param xid an Xid to convert. + * @return The String representation of this Xid + */ + public static org.apache.qpid.transport.Xid convert(Xid xid) + { + return new org.apache.qpid.transport.Xid(xid.getFormatId(), + xid.getGlobalTransactionId(), + xid.getBranchQualifier()); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/exchange/ExchangeDefaults.java b/qpid/java/common/src/main/java/org/apache/qpid/exchange/ExchangeDefaults.java new file mode 100644 index 0000000000..1989ade4ac --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/exchange/ExchangeDefaults.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.qpid.exchange; + +import org.apache.qpid.framing.AMQShortString; + +/** + * Defines the names of the standard AMQP exchanges that every AMQP broker should provide. These exchange names + * and type are given in the specification. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Defines the standard AMQP exchange names. + * <tr><td> Defines the standard AMQP exchange types. + * </table> + * + * @todo A type safe enum, might be more appropriate for the exchange types. + */ +public class ExchangeDefaults +{ + /** The default direct exchange, which is a special internal exchange that cannot be explicitly bound to. */ + public static final AMQShortString DEFAULT_EXCHANGE_NAME = new AMQShortString("<<default>>"); + + /** The pre-defined topic exchange, the broker SHOULD provide this. */ + public static final AMQShortString TOPIC_EXCHANGE_NAME = new AMQShortString("amq.topic"); + + /** Defines the identifying type name of topic exchanges. */ + public static final AMQShortString TOPIC_EXCHANGE_CLASS = new AMQShortString("topic"); + + /** The pre-defined direct exchange, the broker MUST provide this. */ + public static final AMQShortString DIRECT_EXCHANGE_NAME = new AMQShortString("amq.direct"); + + /** Defines the identifying type name of direct exchanges. */ + public static final AMQShortString DIRECT_EXCHANGE_CLASS = new AMQShortString("direct"); + + /** The pre-defined headers exchange, the specification does not say this needs to be provided. */ + public static final AMQShortString HEADERS_EXCHANGE_NAME = new AMQShortString("amq.match"); + + /** Defines the identifying type name of headers exchanges. */ + public static final AMQShortString HEADERS_EXCHANGE_CLASS = new AMQShortString("headers"); + + /** The pre-defined fanout exchange, the boker MUST provide this. */ + public static final AMQShortString FANOUT_EXCHANGE_NAME = new AMQShortString("amq.fanout"); + + /** Defines the identifying type name of fanout exchanges. */ + public static final AMQShortString FANOUT_EXCHANGE_CLASS = new AMQShortString("fanout"); + + public static final AMQShortString WILDCARD_ANY = new AMQShortString("*"); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQBody.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQBody.java new file mode 100644 index 0000000000..fe04155bb8 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQBody.java @@ -0,0 +1,40 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.qpid.protocol.AMQVersionAwareProtocolSession; +import org.apache.qpid.AMQException; + +public interface AMQBody +{ + public byte getFrameType(); + + /** + * Get the size of the body + * @return unsigned short + */ + public abstract int getSize(); + + public void writePayload(ByteBuffer buffer); + + void handle(final int channelId, final AMQVersionAwareProtocolSession amqMinaProtocolSession) throws AMQException; +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQDataBlock.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQDataBlock.java new file mode 100644 index 0000000000..a2fc3a03ef --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQDataBlock.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.framing; + +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 + * buffer (similar to a byte array). + */ +public abstract class AMQDataBlock implements EncodableAMQDataBlock +{ + /** + * Get the size of buffer needed to store the byte representation of this + * frame. + * @return unsigned integer + */ + public abstract long getSize(); + + /** + * Writes the datablock to the specified buffer. + * @param buffer + */ + 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/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQDataBlockDecoder.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQDataBlockDecoder.java new file mode 100644 index 0000000000..228867b2b0 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQDataBlockDecoder.java @@ -0,0 +1,131 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.ProtocolDecoderOutput; + +import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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]; + + static + { + _bodiesSupported[ContentHeaderBody.TYPE] = ContentHeaderBodyFactory.getInstance(); + _bodiesSupported[ContentBody.TYPE] = ContentBodyFactory.getInstance(); + _bodiesSupported[HeartbeatBody.TYPE] = new HeartbeatBodyFactory(); + } + + Logger _logger = LoggerFactory.getLogger(AMQDataBlockDecoder.class); + + public AMQDataBlockDecoder() + { } + + public boolean decodable(java.nio.ByteBuffer in) throws AMQFrameDecodingException + { + final int remainingAfterAttributes = in.remaining() - (1 + 2 + 4 + 1); + // type, channel, body length and end byte + if (remainingAfterAttributes < 0) + { + return false; + } + + in.position(in.position() + 1 + 2); + // Get an unsigned int, lifted from MINA ByteBuffer getUnsignedInt() + final long bodySize = in.getInt() & 0xffffffffL; + + return (remainingAfterAttributes >= bodySize); + + } + + public AMQFrame createAndPopulateFrame(AMQMethodBodyFactory methodBodyFactory, ByteBuffer in) + throws AMQFrameDecodingException, AMQProtocolVersionException + { + final byte type = in.get(); + + BodyFactory bodyFactory; + if (type == AMQMethodBody.TYPE) + { + bodyFactory = methodBodyFactory; + } + 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); + } + + 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/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQDataBlockEncoder.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQDataBlockEncoder.java new file mode 100644 index 0000000000..374644b4f2 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQFrame.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQFrame.java new file mode 100644 index 0000000000..02a46f3748 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQFrame.java @@ -0,0 +1,125 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 class AMQFrame extends AMQDataBlock implements EncodableAMQDataBlock +{ + private final int _channel; + + private final AMQBody _bodyFrame; + public static final byte FRAME_END_BYTE = (byte) 0xCE; + + + public AMQFrame(final int channel, final AMQBody bodyFrame) + { + _channel = channel; + _bodyFrame = bodyFrame; + } + + 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); + } + + public long getSize() + { + return 1 + 2 + 4 + _bodyFrame.getSize() + 1; + } + + public static final int getFrameOverhead() + { + return 1 + 2 + 4 + 1; + } + + + public void writePayload(ByteBuffer buffer) + { + buffer.put(_bodyFrame.getFrameType()); + EncodingUtils.writeUnsignedShort(buffer, _channel); + EncodingUtils.writeUnsignedInteger(buffer, _bodyFrame.getSize()); + _bodyFrame.writePayload(buffer); + buffer.put(FRAME_END_BYTE); + } + + public final int getChannel() + { + return _channel; + } + + public final AMQBody getBodyFrame() + { + return _bodyFrame; + } + + public String toString() + { + return "Frame channelId: " + _channel + ", bodyFrame: " + String.valueOf(_bodyFrame); + } + + public static void writeFrame(ByteBuffer buffer, final int channel, AMQBody body) + { + buffer.put(body.getFrameType()); + EncodingUtils.writeUnsignedShort(buffer, channel); + EncodingUtils.writeUnsignedInteger(buffer, body.getSize()); + body.writePayload(buffer); + buffer.put(FRAME_END_BYTE); + + } + + public static void writeFrames(ByteBuffer buffer, final int channel, AMQBody body1, AMQBody body2) + { + buffer.put(body1.getFrameType()); + EncodingUtils.writeUnsignedShort(buffer, channel); + EncodingUtils.writeUnsignedInteger(buffer, body1.getSize()); + body1.writePayload(buffer); + buffer.put(FRAME_END_BYTE); + buffer.put(body2.getFrameType()); + EncodingUtils.writeUnsignedShort(buffer, channel); + EncodingUtils.writeUnsignedInteger(buffer, body2.getSize()); + body2.writePayload(buffer); + buffer.put(FRAME_END_BYTE); + + } + + public static void writeFrames(ByteBuffer buffer, final int channel, AMQBody body1, AMQBody body2, AMQBody body3) + { + buffer.put(body1.getFrameType()); + EncodingUtils.writeUnsignedShort(buffer, channel); + EncodingUtils.writeUnsignedInteger(buffer, body1.getSize()); + body1.writePayload(buffer); + buffer.put(FRAME_END_BYTE); + buffer.put(body2.getFrameType()); + EncodingUtils.writeUnsignedShort(buffer, channel); + EncodingUtils.writeUnsignedInteger(buffer, body2.getSize()); + body2.writePayload(buffer); + buffer.put(FRAME_END_BYTE); + buffer.put(body3.getFrameType()); + EncodingUtils.writeUnsignedShort(buffer, channel); + EncodingUtils.writeUnsignedInteger(buffer, body3.getSize()); + body3.writePayload(buffer); + buffer.put(FRAME_END_BYTE); + + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQFrameDecodingException.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQFrameDecodingException.java new file mode 100644 index 0000000000..2373edb478 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQFrameDecodingException.java @@ -0,0 +1,47 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; + +/** + * AMQFrameDecodingException indicates that an AMQP frame cannot be decoded because it does not have the correct + * format as defined by the protocol. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represents a format error in a protocol frame. + * </table> + */ +public class AMQFrameDecodingException extends AMQException +{ + public AMQFrameDecodingException(AMQConstant errorCode, String message, Throwable cause) + { + super(errorCode, message, cause); + } + + public AMQFrameDecodingException(AMQConstant errorCode, String message) + { + super(errorCode, message, null); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBody.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBody.java new file mode 100644 index 0000000000..4763b22290 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBody.java @@ -0,0 +1,83 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.qpid.AMQChannelException; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; + +public interface AMQMethodBody extends AMQBody +{ + public static final byte TYPE = 1; + + /** AMQP version */ + public byte getMajor(); + + public byte getMinor(); + + + + /** @return unsigned short */ + public int getClazz(); + + /** @return unsigned short */ + public int getMethod(); + + public void writeMethodPayload(ByteBuffer buffer); + + + public int getSize(); + + public void writePayload(ByteBuffer buffer); + + //public abstract void populateMethodBodyFromBuffer(ByteBuffer buffer) throws AMQFrameDecodingException; + + //public void populateFromBuffer(ByteBuffer buffer, long size) throws AMQFrameDecodingException; + + public AMQFrame generateFrame(int channelId); + + public String toString(); + + + + /** + * Convenience Method to create a channel not found exception + * + * @param channelId The channel id that is not found + * + * @return new AMQChannelException + */ + public AMQChannelException getChannelNotFoundException(int channelId); + + public AMQChannelException getChannelException(AMQConstant code, String message); + + public AMQChannelException getChannelException(AMQConstant code, String message, Throwable cause); + + public AMQConnectionException getConnectionException(AMQConstant code, String message); + + + public AMQConnectionException getConnectionException(AMQConstant code, String message, Throwable cause); + + + public boolean execute(MethodDispatcher methodDispatcher, int channelId) throws AMQException; +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyFactory.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyFactory.java new file mode 100644 index 0000000000..1a7022c11b --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyFactory.java @@ -0,0 +1,45 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.framing; + +import org.apache.mina.common.ByteBuffer; + +import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AMQMethodBodyFactory implements BodyFactory +{ + private static final Logger _log = LoggerFactory.getLogger(AMQMethodBodyFactory.class); + + private final AMQVersionAwareProtocolSession _protocolSession; + + public AMQMethodBodyFactory(AMQVersionAwareProtocolSession protocolSession) + { + _protocolSession = protocolSession; + } + + public AMQBody createBody(ByteBuffer in, long bodySize) throws AMQFrameDecodingException + { + return _protocolSession.getMethodRegistry().convertToBody(in, bodySize); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyImpl.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyImpl.java new file mode 100644 index 0000000000..cd3d721065 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyImpl.java @@ -0,0 +1,258 @@ +package org.apache.qpid.framing; + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.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; + +public abstract class AMQMethodBodyImpl implements AMQMethodBody +{ + public static final byte TYPE = 1; + + public AMQMethodBodyImpl() + { + } + + public byte getFrameType() + { + return TYPE; + } + + + /** unsigned short */ + abstract protected int getBodySize(); + + + public AMQFrame generateFrame(int channelId) + { + return new AMQFrame(channelId, this); + } + + /** + * Creates an AMQChannelException for the corresponding body type (a channel exception should include the class and + * method ids of the body it resulted from). + */ + + /** + * Convenience Method to create a channel not found exception + * + * @param channelId The channel id that is not found + * + * @return new AMQChannelException + */ + public AMQChannelException getChannelNotFoundException(int channelId) + { + return getChannelException(AMQConstant.NOT_FOUND, "Channel not found for id:" + channelId); + } + + public AMQChannelException getChannelException(AMQConstant code, String message) + { + return new AMQChannelException(code, message, getClazz(), getMethod(), getMajor(), getMinor(), null); + } + + public AMQChannelException getChannelException(AMQConstant code, String message, Throwable cause) + { + return new AMQChannelException(code, message, getClazz(), getMethod(), getMajor(), getMinor(), cause); + } + + public AMQConnectionException getConnectionException(AMQConstant code, String message) + { + return new AMQConnectionException(code, message, getClazz(), getMethod(), getMajor(), getMinor(), null); + } + + public AMQConnectionException getConnectionException(AMQConstant code, String message, Throwable cause) + { + return new AMQConnectionException(code, message, getClazz(), getMethod(), getMajor(), getMinor(), cause); + } + + public void handle(final int channelId, final AMQVersionAwareProtocolSession session) throws AMQException + { + session.methodFrameReceived(channelId, this); + } + + public int getSize() + { + return 2 + 2 + getBodySize(); + } + + public void writePayload(ByteBuffer buffer) + { + EncodingUtils.writeUnsignedShort(buffer, getClazz()); + EncodingUtils.writeUnsignedShort(buffer, getMethod()); + writeMethodPayload(buffer); + } + + + protected byte readByte(ByteBuffer buffer) + { + return buffer.get(); + } + + protected AMQShortString readAMQShortString(ByteBuffer buffer) + { + return EncodingUtils.readAMQShortString(buffer); + } + + protected int getSizeOf(AMQShortString string) + { + return EncodingUtils.encodedShortStringLength(string); + } + + protected void writeByte(ByteBuffer buffer, byte b) + { + buffer.put(b); + } + + protected void writeAMQShortString(ByteBuffer buffer, AMQShortString string) + { + EncodingUtils.writeShortStringBytes(buffer, string); + } + + protected int readInt(ByteBuffer buffer) + { + return buffer.getInt(); + } + + protected void writeInt(ByteBuffer buffer, int i) + { + buffer.putInt(i); + } + + protected FieldTable readFieldTable(ByteBuffer buffer) throws AMQFrameDecodingException + { + return EncodingUtils.readFieldTable(buffer); + } + + protected int getSizeOf(FieldTable table) + { + return EncodingUtils.encodedFieldTableLength(table); //To change body of created methods use File | Settings | File Templates. + } + + protected void writeFieldTable(ByteBuffer buffer, FieldTable table) + { + EncodingUtils.writeFieldTableBytes(buffer, table); + } + + protected long readLong(ByteBuffer buffer) + { + return buffer.getLong(); + } + + protected void writeLong(ByteBuffer buffer, long l) + { + buffer.putLong(l); + } + + protected int getSizeOf(byte[] response) + { + return (response == null) ? 4 : response.length + 4; + } + + protected void writeBytes(ByteBuffer buffer, byte[] data) + { + EncodingUtils.writeBytes(buffer,data); + } + + protected byte[] readBytes(ByteBuffer buffer) + { + return EncodingUtils.readBytes(buffer); + } + + protected short readShort(ByteBuffer buffer) + { + return EncodingUtils.readShort(buffer); + } + + protected void writeShort(ByteBuffer buffer, short s) + { + EncodingUtils.writeShort(buffer, s); + } + + protected Content readContent(ByteBuffer buffer) + { + return null; //To change body of created methods use File | Settings | File Templates. + } + + protected int getSizeOf(Content body) + { + return 0; //To change body of created methods use File | Settings | File Templates. + } + + protected void writeContent(ByteBuffer buffer, Content body) + { + //To change body of created methods use File | Settings | File Templates. + } + + protected byte readBitfield(ByteBuffer buffer) + { + return readByte(buffer); //To change body of created methods use File | Settings | File Templates. + } + + protected int readUnsignedShort(ByteBuffer buffer) + { + return buffer.getUnsignedShort(); //To change body of created methods use File | Settings | File Templates. + } + + protected void writeBitfield(ByteBuffer buffer, byte bitfield0) + { + buffer.put(bitfield0); + } + + protected void writeUnsignedShort(ByteBuffer buffer, int s) + { + EncodingUtils.writeUnsignedShort(buffer, s); + } + + protected long readUnsignedInteger(ByteBuffer buffer) + { + return buffer.getUnsignedInt(); + } + protected void writeUnsignedInteger(ByteBuffer buffer, long i) + { + EncodingUtils.writeUnsignedInteger(buffer, i); + } + + + protected short readUnsignedByte(ByteBuffer buffer) + { + return buffer.getUnsigned(); + } + + protected void writeUnsignedByte(ByteBuffer buffer, short unsignedByte) + { + EncodingUtils.writeUnsignedByte(buffer, unsignedByte); + } + + protected long readTimestamp(ByteBuffer buffer) + { + return EncodingUtils.readTimestamp(buffer); + } + + protected void writeTimestamp(ByteBuffer buffer, long t) + { + EncodingUtils.writeTimestamp(buffer, t); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyInstanceFactory.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyInstanceFactory.java new file mode 100644 index 0000000000..0c61d9db3c --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyInstanceFactory.java @@ -0,0 +1,30 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 abstract interface AMQMethodBodyInstanceFactory +{ + public AMQMethodBody newInstance(ByteBuffer buffer, long size) throws AMQFrameDecodingException; +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQMethodFactory.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQMethodFactory.java new file mode 100644 index 0000000000..bfcc38ad60 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQProtocolClassException.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQProtocolClassException.java new file mode 100644 index 0000000000..ab09c1de6d --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQProtocolClassException.java @@ -0,0 +1,39 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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; + +/** + * AMQProtocolInstanceException indicates that the protocol class is incorrect in a header. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represent incorrect protocol class in frame header. + * </table> + * + * @todo Not an AMQP exception as no status code. + */ +public class AMQProtocolClassException extends AMQProtocolHeaderException +{ + public AMQProtocolClassException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQProtocolHeaderException.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQProtocolHeaderException.java new file mode 100644 index 0000000000..6b819364da --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQProtocolHeaderException.java @@ -0,0 +1,41 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.framing; + +import org.apache.qpid.AMQException; + +/** + * AMQProtocolHeaderException indicates a format error in an AMQP frame header. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represent format error in frame header. + * </table> + * + * @todo Not an AMQP exception as no status code. + */ +public class AMQProtocolHeaderException extends AMQException +{ + public AMQProtocolHeaderException(String message, Throwable cause) + { + super(null, message, cause); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQProtocolInstanceException.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQProtocolInstanceException.java new file mode 100644 index 0000000000..3165c373a9 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQProtocolInstanceException.java @@ -0,0 +1,39 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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; + +/** + * AMQProtocolInstanceException indicates that the protocol instance is incorrect in a header. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represent incorrect protocol instance in frame header. + * </table> + * + * @todo Not an AMQP exception as no status code. + */ +public class AMQProtocolInstanceException extends AMQProtocolHeaderException +{ + public AMQProtocolInstanceException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQProtocolVersionException.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQProtocolVersionException.java new file mode 100644 index 0000000000..c9b0973ea6 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQProtocolVersionException.java @@ -0,0 +1,39 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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; + +/** + * AMQProtocolInstanceException indicates that the client and server differ on expected protocol version in a header. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represent incorrect protocol version in frame header. + * </table> + * + * @todo Not an AMQP exception as no status code. + */ +public class AMQProtocolVersionException extends AMQProtocolHeaderException +{ + public AMQProtocolVersionException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQShortString.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQShortString.java new file mode 100644 index 0000000000..39a9beb9e8 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQShortString.java @@ -0,0 +1,779 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.lang.ref.WeakReference; + +/** + * A short string is a representation of an AMQ Short String + * Short strings differ from the Java String class by being limited to on ASCII characters (0-127) + * and thus can be held more effectively in a byte buffer. + * + */ +public final class AMQShortString implements CharSequence, Comparable<AMQShortString> +{ + private static final byte MINUS = (byte)'-'; + private static final byte ZERO = (byte) '0'; + + private final class TokenizerImpl implements AMQShortStringTokenizer + { + private final byte _delim; + private int _count = -1; + private int _pos = 0; + + public TokenizerImpl(final byte delim) + { + _delim = delim; + } + + public int countTokens() + { + if(_count == -1) + { + _count = 1 + AMQShortString.this.occurences(_delim); + } + return _count; + } + + public AMQShortString nextToken() + { + if(_pos <= AMQShortString.this.length()) + { + int nextDelim = AMQShortString.this.indexOf(_delim, _pos); + if(nextDelim == -1) + { + nextDelim = AMQShortString.this.length(); + } + + AMQShortString nextToken = AMQShortString.this.substring(_pos, nextDelim++); + _pos = nextDelim; + return nextToken; + } + else + { + return null; + } + } + + public boolean hasMoreTokens() + { + return _pos <= AMQShortString.this.length(); + } + } + + private AMQShortString substring(final int from, final int to) + { + return new AMQShortString(_data, from+_offset, to+_offset); + } + + + private static final ThreadLocal<Map<AMQShortString, WeakReference<AMQShortString>>> _localInternMap = + new ThreadLocal<Map<AMQShortString, WeakReference<AMQShortString>>>() + { + protected Map<AMQShortString, WeakReference<AMQShortString>> initialValue() + { + return new WeakHashMap<AMQShortString, WeakReference<AMQShortString>>(); + }; + }; + + private static final Map<AMQShortString, WeakReference<AMQShortString>> _globalInternMap = + new WeakHashMap<AMQShortString, WeakReference<AMQShortString>>(); + + private static final Logger _logger = LoggerFactory.getLogger(AMQShortString.class); + + private final byte[] _data; + private final int _offset; + private int _hashCode; + private String _asString = null; + + private final int _length; + private static final char[] EMPTY_CHAR_ARRAY = new char[0]; + + public static final AMQShortString EMPTY_STRING = new AMQShortString((String)null); + + public AMQShortString(byte[] data) + { + + _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()); + _asString = data; + } + + public AMQShortString(char[] data) + { + if (data == null) + { + throw new NullPointerException("Cannot create AMQShortString with null char[]"); + } + + final int length = data.length; + final byte[] stringBytes = new byte[length]; + int hash = 0; + for (int i = 0; i < length; i++) + { + stringBytes[i] = (byte) (0xFF & data[i]); + hash = (31 * hash) + stringBytes[i]; + } + _hashCode = hash; + _data = stringBytes; + + _length = length; + _offset = 0; + + } + + public AMQShortString(CharSequence charSequence) + { + final int length = charSequence.length(); + final byte[] stringBytes = new byte[length]; + int hash = 0; + for (int i = 0; i < length; i++) + { + stringBytes[i] = ((byte) (0xFF & charSequence.charAt(i))); + hash = (31 * hash) + stringBytes[i]; + + } + + _data = stringBytes; + _hashCode = hash; + _length = length; + _offset = 0; + + } + + private AMQShortString(ByteBuffer data, final int length) + { + if(data.isDirect() || data.isReadOnly()) + { + byte[] dataBytes = new byte[length]; + data.get(dataBytes); + _data = dataBytes; + _offset = 0; + } + else + { + + _data = data.array(); + _offset = data.arrayOffset() + data.position(); + data.skip(length); + + } + _length = length; + + } + + private AMQShortString(final byte[] data, final int from, final int to) + { + _offset = from; + _length = to - from; + _data = data; + } + + public AMQShortString shrink() + { + if(_data.length != _length) + { + byte[] dataBytes = new byte[_length]; + System.arraycopy(_data,_offset,dataBytes,0,_length); + return new AMQShortString(dataBytes,0,_length); + } + else + { + return this; + } + } + + /** + * Get the length of the short string + * @return length of the underlying byte array + */ + public int length() + { + return _length; + } + + public char charAt(int index) + { + + return (char) _data[_offset + index]; + + } + + public CharSequence subSequence(int start, int end) + { + return new CharSubSequence(start, end); + } + + 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 short length = buffer.getUnsigned(); + if (length == 0) + { + return null; + } + else + { + + return new AMQShortString(buffer, length); + } + } + + public byte[] getBytes() + { + if(_offset == 0 && _length == _data.length) + { + return _data.clone(); + } + else + { + byte[] data = new byte[_length]; + System.arraycopy(_data,_offset,data,0,_length); + return data; + } + } + + public void writeToBuffer(ByteBuffer buffer) + { + + final int size = length(); + //buffer.setAutoExpand(true); + buffer.put((byte) size); + buffer.put(_data, _offset, size); + + } + + public boolean endsWith(String s) + { + return endsWith(new AMQShortString(s)); + } + + + public boolean endsWith(AMQShortString otherString) + { + + if (otherString.length() > length()) + { + return false; + } + + + int thisLength = length(); + int otherLength = otherString.length(); + + for (int i = 1; i <= otherLength; i++) + { + if (charAt(thisLength - i) != otherString.charAt(otherLength - i)) + { + return false; + } + } + return true; + } + + public boolean startsWith(String s) + { + return startsWith(new AMQShortString(s)); + } + + public boolean startsWith(AMQShortString otherString) + { + + if (otherString.length() > length()) + { + return false; + } + + for (int i = 0; i < otherString.length(); i++) + { + if (charAt(i) != otherString.charAt(i)) + { + return false; + } + } + + return true; + + } + + public boolean startsWith(CharSequence otherString) + { + if (otherString.length() > length()) + { + return false; + } + + for (int i = 0; i < otherString.length(); i++) + { + if (charAt(i) != otherString.charAt(i)) + { + return false; + } + } + + return true; + } + + + private final class CharSubSequence implements CharSequence + { + private final int _sequenceOffset; + private final int _end; + + public CharSubSequence(final int offset, final int end) + { + _sequenceOffset = offset; + _end = end; + } + + public int length() + { + return _end - _sequenceOffset; + } + + public char charAt(int index) + { + return AMQShortString.this.charAt(index + _sequenceOffset); + } + + public CharSequence subSequence(int start, int end) + { + return new CharSubSequence(start + _sequenceOffset, end + _sequenceOffset); + } + } + + public char[] asChars() + { + final int size = length(); + final char[] chars = new char[size]; + + for (int i = 0; i < size; i++) + { + chars[i] = (char) _data[i + _offset]; + } + + return chars; + } + + + public String asString() + { + if (_asString == null) + { + _asString = new String(asChars()); + } + return _asString; + } + + public boolean equals(Object o) + { + + + if(o instanceof AMQShortString) + { + return equals((AMQShortString)o); + } + if(o instanceof CharSequence) + { + return equals((CharSequence)o); + } + + if (o == null) + { + return false; + } + + if (o == this) + { + return true; + } + + + return false; + + } + + public boolean equals(final AMQShortString otherString) + { + if (otherString == this) + { + return true; + } + + if (otherString == null) + { + return false; + } + + final int hashCode = _hashCode; + + final int otherHashCode = otherString._hashCode; + + if ((hashCode != 0) && (otherHashCode != 0) && (hashCode != otherHashCode)) + { + return false; + } + + final int length = _length; + + if(length != otherString._length) + { + return false; + } + + + final byte[] data = _data; + + final byte[] otherData = otherString._data; + + final int offset = _offset; + + final int otherOffset = otherString._offset; + + if(offset == 0 && otherOffset == 0 && length == data.length && length == otherData.length) + { + return Arrays.equals(data, otherData); + } + else + { + int thisIdx = offset; + int otherIdx = otherOffset; + for(int i = length; i-- != 0; ) + { + if(!(data[thisIdx++] == otherData[otherIdx++])) + { + return false; + } + } + } + + return true; + + } + + public boolean equals(CharSequence s) + { + if(s instanceof AMQShortString) + { + return equals((AMQShortString)s); + } + + if (s == null) + { + return false; + } + + if (s.length() != length()) + { + return false; + } + + for (int i = 0; i < length(); i++) + { + if (charAt(i) != s.charAt(i)) + { + return false; + } + } + + return true; + } + + public int hashCode() + { + int hash = _hashCode; + if (hash == 0) + { + final int size = length(); + + for (int i = 0; i < size; i++) + { + hash = (31 * hash) + _data[i+_offset]; + } + + _hashCode = hash; + } + + return hash; + } + + public void setDirty() + { + _hashCode = 0; + } + + public String toString() + { + return asString(); + } + + public int compareTo(AMQShortString name) + { + if (name == null) + { + return 1; + } + else + { + + if (name.length() < length()) + { + return -name.compareTo(this); + } + + for (int i = 0; i < length(); i++) + { + final byte d = _data[i+_offset]; + final byte n = name._data[i+name._offset]; + if (d < n) + { + return -1; + } + + if (d > n) + { + return 1; + } + } + + return (length() == name.length()) ? 0 : -1; + } + } + + + public AMQShortStringTokenizer tokenize(byte delim) + { + return new TokenizerImpl(delim); + } + + + public AMQShortString intern() + { + + hashCode(); + + Map<AMQShortString, WeakReference<AMQShortString>> localMap = + _localInternMap.get(); + + WeakReference<AMQShortString> ref = localMap.get(this); + AMQShortString internString; + + if(ref != null) + { + internString = ref.get(); + if(internString != null) + { + return internString; + } + } + + + synchronized(_globalInternMap) + { + + ref = _globalInternMap.get(this); + if((ref == null) || ((internString = ref.get()) == null)) + { + internString = shrink(); + ref = new WeakReference(internString); + _globalInternMap.put(internString, ref); + } + + } + localMap.put(internString, ref); + return internString; + + } + + private int occurences(final byte delim) + { + int count = 0; + final int end = _offset + _length; + for(int i = _offset ; i < end ; i++ ) + { + if(_data[i] == delim) + { + count++; + } + } + return count; + } + + private int indexOf(final byte val, final int pos) + { + + for(int i = pos; i < length(); i++) + { + if(_data[_offset+i] == val) + { + return i; + } + } + return -1; + } + + + public static AMQShortString join(final Collection<AMQShortString> terms, + final AMQShortString delim) + { + if(terms.size() == 0) + { + return EMPTY_STRING; + } + + int size = delim.length() * (terms.size() - 1); + for(AMQShortString term : terms) + { + size += term.length(); + } + + byte[] data = new byte[size]; + int pos = 0; + final byte[] delimData = delim._data; + final int delimOffset = delim._offset; + final int delimLength = delim._length; + + + for(AMQShortString term : terms) + { + + if(pos!=0) + { + System.arraycopy(delimData, delimOffset,data,pos, delimLength); + pos+=delimLength; + } + System.arraycopy(term._data,term._offset,data,pos,term._length); + pos+=term._length; + } + + + + return new AMQShortString(data,0,size); + } + + public int toIntValue() + { + int pos = _offset; + int val = 0; + + + boolean isNegative = (_data[pos] == MINUS); + if(isNegative) + { + pos++; + } + + final int end = _length + _offset; + + while(pos < end) + { + int digit = (int) (_data[pos++] - ZERO); + if((digit < 0) || (digit > 9)) + { + throw new NumberFormatException("\""+toString()+"\" is not a valid number"); + } + val = val * 10; + val += digit; + } + if(isNegative) + { + val = val * -1; + } + return val; + } + + public boolean contains(final byte b) + { + final int end = _length + _offset; + for(int i = _offset; i < end; i++) + { + if(_data[i] == b) + { + return true; + } + } + return false; //To change body of created methods use File | Settings | File Templates. + } + + public static AMQShortString valueOf(Object obj) + { + return obj == null ? null : new AMQShortString(String.valueOf(obj)); + } + + + public static void main(String args[]) + { + AMQShortString s = new AMQShortString("a.b.c.d.e.f.g.h.i.j.k"); + AMQShortString s2 = s.substring(2, 7); + + AMQShortStringTokenizer t = s2.tokenize((byte) '.'); + while(t.hasMoreTokens()) + { + System.err.println(t.nextToken()); + } + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQShortStringTokenizer.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQShortStringTokenizer.java new file mode 100644 index 0000000000..e2db8906a1 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQShortStringTokenizer.java @@ -0,0 +1,31 @@ +package org.apache.qpid.framing; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT 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 interface AMQShortStringTokenizer +{ + + public int countTokens(); + + public AMQShortString nextToken(); + + boolean hasMoreTokens(); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQType.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQType.java new file mode 100644 index 0000000000..14fb63da03 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQType.java @@ -0,0 +1,795 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 java.math.BigDecimal; + +/** + * AMQType is a type that represents the different possible AMQP field table types. It provides operations for each + * of the types to perform tasks such as calculating the size of an instance of the type, converting types between AMQP + * and Java native types, and reading and writing instances of AMQP types in binary formats to and from byte buffers. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Get the equivalent one byte identifier for a type. + * <tr><td> Calculate the size of an instance of an AMQP parameter type. <td> {@link EncodingUtils} + * <tr><td> Convert an instance of an AMQP parameter into a compatable Java object tagged with its AMQP type. + * <td> {@link AMQTypedValue} + * <tr><td> Write an instance of an AMQP parameter type to a byte buffer. <td> {@link EncodingUtils} + * <tr><td> Read an instance of an AMQP parameter from a byte buffer. <td> {@link EncodingUtils} + * </table> + */ +public enum AMQType +{ + LONG_STRING('S') + { + public int getEncodingSize(Object value) + { + return EncodingUtils.encodedLongStringLength((String) value); + } + + public String toNativeValue(Object value) + { + if (value != null) + { + return value.toString(); + } + else + { + throw new NullPointerException("Cannot convert: null to String."); + } + } + + public void writeValueImpl(Object value, ByteBuffer buffer) + { + EncodingUtils.writeLongStringBytes(buffer, (String) value); + } + + public Object readValueFromBuffer(ByteBuffer buffer) + { + return EncodingUtils.readLongString(buffer); + } + }, + + INTEGER('i') + { + public int getEncodingSize(Object value) + { + return EncodingUtils.unsignedIntegerLength(); + } + + public Long toNativeValue(Object value) + { + if (value instanceof Long) + { + return (Long) value; + } + else if (value instanceof Integer) + { + return ((Integer) value).longValue(); + } + else if (value instanceof Short) + { + return ((Short) value).longValue(); + } + else if (value instanceof Byte) + { + return ((Byte) value).longValue(); + } + else if ((value instanceof String) || (value == null)) + { + return Long.valueOf((String) value); + } + else + { + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + ") to int."); + } + } + + public void writeValueImpl(Object value, ByteBuffer buffer) + { + EncodingUtils.writeUnsignedInteger(buffer, (Long) value); + } + + public Object readValueFromBuffer(ByteBuffer buffer) + { + return EncodingUtils.readUnsignedInteger(buffer); + } + }, + + DECIMAL('D') + { + public int getEncodingSize(Object value) + { + return EncodingUtils.encodedByteLength() + EncodingUtils.encodedIntegerLength(); + } + + public Object toNativeValue(Object value) + { + if (value instanceof BigDecimal) + { + return (BigDecimal) value; + } + else + { + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + + ") to BigDecimal."); + } + } + + public void writeValueImpl(Object value, ByteBuffer buffer) + { + BigDecimal bd = (BigDecimal) value; + + byte places = new Integer(bd.scale()).byteValue(); + + int unscaled = bd.intValue(); + + EncodingUtils.writeByte(buffer, places); + + EncodingUtils.writeInteger(buffer, unscaled); + } + + public Object readValueFromBuffer(ByteBuffer buffer) + { + byte places = EncodingUtils.readByte(buffer); + + int unscaled = EncodingUtils.readInteger(buffer); + + BigDecimal bd = new BigDecimal(unscaled); + + return bd.setScale(places); + } + }, + + TIMESTAMP('T') + { + public int getEncodingSize(Object value) + { + return EncodingUtils.encodedLongLength(); + } + + public Object toNativeValue(Object value) + { + if (value instanceof Long) + { + return (Long) value; + } + else + { + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + + ") to timestamp."); + } + } + + public void writeValueImpl(Object value, ByteBuffer buffer) + { + EncodingUtils.writeLong(buffer, (Long) value); + } + + public Object readValueFromBuffer(ByteBuffer buffer) + { + return EncodingUtils.readLong(buffer); + } + }, + + /** + * Implements the field table type. The native value of a field table type will be an instance of + * {@link FieldTable}, which itself may contain name/value pairs encoded as {@link AMQTypedValue}s. + */ + FIELD_TABLE('F') + { + /** + * Calculates the size of an instance of the type in bytes. + * + * @param value An instance of the type. + * + * @return The size of the instance of the type in bytes. + */ + public int getEncodingSize(Object value) + { + // Ensure that the value is a FieldTable. + if (!(value instanceof FieldTable)) + { + throw new IllegalArgumentException("Value is not a FieldTable."); + } + + FieldTable ftValue = (FieldTable) value; + + // Loop over all name/value pairs adding up size of each. FieldTable itself keeps track of its encoded + // size as entries are added, so no need to loop over all explicitly. + // EncodingUtils calculation of the encoded field table lenth, will include 4 bytes for its 'size' field. + return EncodingUtils.encodedFieldTableLength(ftValue); + } + + /** + * Converts an instance of the type to an equivalent Java native representation. + * + * @param value An instance of the type. + * + * @return An equivalent Java native representation. + */ + public Object toNativeValue(Object value) + { + // Ensure that the value is a FieldTable. + if (!(value instanceof FieldTable)) + { + throw new IllegalArgumentException("Value is not a FieldTable."); + } + + return (FieldTable) value; + } + + /** + * Writes an instance of the type to a specified byte buffer. + * + * @param value An instance of the type. + * @param buffer The byte buffer to write it to. + */ + public void writeValueImpl(Object value, ByteBuffer buffer) + { + // Ensure that the value is a FieldTable. + if (!(value instanceof FieldTable)) + { + throw new IllegalArgumentException("Value is not a FieldTable."); + } + + FieldTable ftValue = (FieldTable) value; + + // Loop over all name/values writing out into buffer. + ftValue.writeToBuffer(buffer); + } + + /** + * Reads an instance of the type from a specified byte buffer. + * + * @param buffer The byte buffer to write it to. + * + * @return An instance of the type. + */ + public Object readValueFromBuffer(ByteBuffer buffer) + { + try + { + // Read size of field table then all name/value pairs. + return EncodingUtils.readFieldTable(buffer); + } + catch (AMQFrameDecodingException e) + { + throw new IllegalArgumentException("Unable to read field table from buffer.", e); + } + } + }, + + VOID('V') + { + public int getEncodingSize(Object value) + { + return 0; + } + + public Object toNativeValue(Object value) + { + if (value == null) + { + return null; + } + else + { + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + + ") to null String."); + } + } + + public void writeValueImpl(Object value, ByteBuffer buffer) + { } + + public Object readValueFromBuffer(ByteBuffer buffer) + { + return null; + } + }, + + BINARY('x') + { + public int getEncodingSize(Object value) + { + return EncodingUtils.encodedLongstrLength((byte[]) value); + } + + public Object toNativeValue(Object value) + { + if ((value instanceof byte[]) || (value == null)) + { + return value; + } + else + { + throw new IllegalArgumentException("Value: " + value + " (" + value.getClass().getName() + + ") cannot be converted to byte[]"); + } + } + + public void writeValueImpl(Object value, ByteBuffer buffer) + { + EncodingUtils.writeLongstr(buffer, (byte[]) value); + } + + public Object readValueFromBuffer(ByteBuffer buffer) + { + return EncodingUtils.readLongstr(buffer); + } + }, + + ASCII_STRING('c') + { + public int getEncodingSize(Object value) + { + return EncodingUtils.encodedLongStringLength((String) value); + } + + public String toNativeValue(Object value) + { + if (value != null) + { + return value.toString(); + } + else + { + throw new NullPointerException("Cannot convert: null to String."); + } + } + + public void writeValueImpl(Object value, ByteBuffer buffer) + { + EncodingUtils.writeLongStringBytes(buffer, (String) value); + } + + public Object readValueFromBuffer(ByteBuffer buffer) + { + return EncodingUtils.readLongString(buffer); + } + }, + + WIDE_STRING('C') + { + public int getEncodingSize(Object value) + { + // FIXME: use proper charset encoder + return EncodingUtils.encodedLongStringLength((String) value); + } + + public String toNativeValue(Object value) + { + if (value != null) + { + return value.toString(); + } + else + { + throw new NullPointerException("Cannot convert: null to String."); + } + } + + public void writeValueImpl(Object value, ByteBuffer buffer) + { + EncodingUtils.writeLongStringBytes(buffer, (String) value); + } + + public Object readValueFromBuffer(ByteBuffer buffer) + { + return EncodingUtils.readLongString(buffer); + } + }, + + BOOLEAN('t') + { + public int getEncodingSize(Object value) + { + return EncodingUtils.encodedBooleanLength(); + } + + public Object toNativeValue(Object value) + { + if (value instanceof Boolean) + { + return (Boolean) value; + } + else if ((value instanceof String) || (value == null)) + { + return Boolean.valueOf((String) value); + } + else + { + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + + ") to boolean."); + } + } + + public void writeValueImpl(Object value, ByteBuffer buffer) + { + EncodingUtils.writeBoolean(buffer, (Boolean) value); + } + + public Object readValueFromBuffer(ByteBuffer buffer) + { + return EncodingUtils.readBoolean(buffer); + } + }, + + ASCII_CHARACTER('k') + { + public int getEncodingSize(Object value) + { + return EncodingUtils.encodedCharLength(); + } + + public Character toNativeValue(Object value) + { + if (value instanceof Character) + { + return (Character) value; + } + else if (value == null) + { + throw new NullPointerException("Cannot convert null into char"); + } + else + { + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + + ") to char."); + } + } + + public void writeValueImpl(Object value, ByteBuffer buffer) + { + EncodingUtils.writeChar(buffer, (Character) value); + } + + public Object readValueFromBuffer(ByteBuffer buffer) + { + return EncodingUtils.readChar(buffer); + } + }, + + BYTE('b') + { + public int getEncodingSize(Object value) + { + return EncodingUtils.encodedByteLength(); + } + + public Byte toNativeValue(Object value) + { + if (value instanceof Byte) + { + return (Byte) value; + } + else if ((value instanceof String) || (value == null)) + { + return Byte.valueOf((String) value); + } + else + { + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + + ") to byte."); + } + } + + public void writeValueImpl(Object value, ByteBuffer buffer) + { + EncodingUtils.writeByte(buffer, (Byte) value); + } + + public Object readValueFromBuffer(ByteBuffer buffer) + { + return EncodingUtils.readByte(buffer); + } + }, + + SHORT('s') + { + public int getEncodingSize(Object value) + { + return EncodingUtils.encodedShortLength(); + } + + public Short toNativeValue(Object value) + { + if (value instanceof Short) + { + return (Short) value; + } + else if (value instanceof Byte) + { + return ((Byte) value).shortValue(); + } + else if ((value instanceof String) || (value == null)) + { + return Short.valueOf((String) value); + } + else + { + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + + ") to short."); + } + } + + public void writeValueImpl(Object value, ByteBuffer buffer) + { + EncodingUtils.writeShort(buffer, (Short) value); + } + + public Object readValueFromBuffer(ByteBuffer buffer) + { + return EncodingUtils.readShort(buffer); + } + }, + + INT('I') + { + public int getEncodingSize(Object value) + { + return EncodingUtils.encodedIntegerLength(); + } + + public Integer toNativeValue(Object value) + { + if (value instanceof Integer) + { + return (Integer) value; + } + else if (value instanceof Short) + { + return ((Short) value).intValue(); + } + else if (value instanceof Byte) + { + return ((Byte) value).intValue(); + } + else if ((value instanceof String) || (value == null)) + { + return Integer.valueOf((String) value); + } + else + { + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + ") to int."); + } + } + + public void writeValueImpl(Object value, ByteBuffer buffer) + { + EncodingUtils.writeInteger(buffer, (Integer) value); + } + + public Object readValueFromBuffer(ByteBuffer buffer) + { + return EncodingUtils.readInteger(buffer); + } + }, + + LONG('l') + { + public int getEncodingSize(Object value) + { + return EncodingUtils.encodedLongLength(); + } + + public Object toNativeValue(Object value) + { + if (value instanceof Long) + { + return (Long) value; + } + else if (value instanceof Integer) + { + return ((Integer) value).longValue(); + } + else if (value instanceof Short) + { + return ((Short) value).longValue(); + } + else if (value instanceof Byte) + { + return ((Byte) value).longValue(); + } + else if ((value instanceof String) || (value == null)) + { + return Long.valueOf((String) value); + } + else + { + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + + ") to long."); + } + } + + public void writeValueImpl(Object value, ByteBuffer buffer) + { + EncodingUtils.writeLong(buffer, (Long) value); + } + + public Object readValueFromBuffer(ByteBuffer buffer) + { + return EncodingUtils.readLong(buffer); + } + }, + + FLOAT('f') + { + public int getEncodingSize(Object value) + { + return EncodingUtils.encodedFloatLength(); + } + + public Float toNativeValue(Object value) + { + if (value instanceof Float) + { + return (Float) value; + } + else if ((value instanceof String) || (value == null)) + { + return Float.valueOf((String) value); + } + else + { + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + + ") to float."); + } + } + + public void writeValueImpl(Object value, ByteBuffer buffer) + { + EncodingUtils.writeFloat(buffer, (Float) value); + } + + public Object readValueFromBuffer(ByteBuffer buffer) + { + return EncodingUtils.readFloat(buffer); + } + }, + + DOUBLE('d') + { + public int getEncodingSize(Object value) + { + return EncodingUtils.encodedDoubleLength(); + } + + public Double toNativeValue(Object value) + { + if (value instanceof Double) + { + return (Double) value; + } + else if (value instanceof Float) + { + return ((Float) value).doubleValue(); + } + else if ((value instanceof String) || (value == null)) + { + return Double.valueOf((String) value); + } + else + { + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + + ") to double."); + } + } + + public void writeValueImpl(Object value, ByteBuffer buffer) + { + EncodingUtils.writeDouble(buffer, (Double) value); + } + + public Object readValueFromBuffer(ByteBuffer buffer) + { + return EncodingUtils.readDouble(buffer); + } + }; + + /** Holds the defined one byte identifier for the type. */ + private final byte _identifier; + + /** + * Creates an instance of an AMQP type from its defined one byte identifier. + * + * @param identifier The one byte identifier for the type. + */ + AMQType(char identifier) + { + _identifier = (byte) identifier; + } + + /** + * Extracts the byte identifier for the typ. + * + * @return The byte identifier for the typ. + */ + public final byte identifier() + { + return _identifier; + } + + /** + * Calculates the size of an instance of the type in bytes. + * + * @param value An instance of the type. + * + * @return The size of the instance of the type in bytes. + */ + public abstract int getEncodingSize(Object value); + + /** + * Converts an instance of the type to an equivalent Java native representation. + * + * @param value An instance of the type. + * + * @return An equivalent Java native representation. + */ + public abstract Object toNativeValue(Object value); + + /** + * Converts an instance of the type to an equivalent Java native representation, packaged as an + * {@link AMQTypedValue} tagged with its AMQP type. + * + * @param value An instance of the type. + * + * @return An equivalent Java native representation, tagged with its AMQP type. + */ + public AMQTypedValue asTypedValue(Object value) + { + return new AMQTypedValue(this, toNativeValue(value)); + } + + /** + * Writes an instance of the type to a specified byte buffer, preceded by its one byte identifier. As the type and + * value are both written, this provides a fully encoded description of a parameters type and value. + * + * @param value An instance of the type. + * @param buffer The byte buffer to write it to. + */ + public void writeToBuffer(Object value, ByteBuffer buffer) + { + buffer.put(identifier()); + writeValueImpl(value, buffer); + } + + /** + * Writes an instance of the type to a specified byte buffer. + * + * @param value An instance of the type. + * @param buffer The byte buffer to write it to. + */ + abstract void writeValueImpl(Object value, ByteBuffer buffer); + + /** + * Reads an instance of the type from a specified byte buffer. + * + * @param buffer The byte buffer to write it to. + * + * @return An instance of the type. + */ + abstract Object readValueFromBuffer(ByteBuffer buffer); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQTypeMap.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQTypeMap.java new file mode 100644 index 0000000000..a07fd78c8c --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQTypeMap.java @@ -0,0 +1,48 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 java.util.HashMap; +import java.util.Map; + +public class AMQTypeMap +{ + public static final Map<Byte, AMQType> _reverseTypeMap = new HashMap<Byte, AMQType>(); + + static + { + for(AMQType type : AMQType.values()) + { + _reverseTypeMap.put(type.identifier(), type); + } + } + + public static AMQType getType(Byte identifier) + { + AMQType result = _reverseTypeMap.get(identifier); + if (result == null) { + throw new IllegalArgumentException + ("no such type code: " + Integer.toHexString(identifier.intValue())); + } + return result; + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQTypedValue.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQTypedValue.java new file mode 100644 index 0000000000..647d531476 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/AMQTypedValue.java @@ -0,0 +1,179 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 java.util.Date; +import java.util.Map; +import java.math.BigDecimal; + +/** + * AMQTypedValue combines together a native Java Object value, and an {@link AMQType}, as a fully typed AMQP parameter + * value. It provides the ability to read and write fully typed parameters to and from byte buffers. It also provides + * the ability to create such parameters from Java native value and a type tag or to extract the native value and type + * from one. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Create a fully typed AMQP value from a native type and a type tag. <td> {@link AMQType} + * <tr><td> Create a fully typed AMQP value from a binary representation in a byte buffer. <td> {@link AMQType} + * <tr><td> Write a fully typed AMQP value to a binary representation in a byte buffer. <td> {@link AMQType} + * <tr><td> Extract the type from a fully typed AMQP value. + * <tr><td> Extract the value from a fully typed AMQP value. + * </table> + */ +public class AMQTypedValue +{ + /** The type of the value. */ + private final AMQType _type; + + /** The Java native representation of the AMQP typed value. */ + private final Object _value; + + public AMQTypedValue(AMQType type, Object value) + { + if (type == null) + { + throw new NullPointerException("Cannot create a typed value with null type"); + } + + _type = type; + _value = type.toNativeValue(value); + } + + private AMQTypedValue(AMQType type, ByteBuffer buffer) + { + _type = type; + _value = type.readValueFromBuffer(buffer); + } + + public AMQType getType() + { + return _type; + } + + public Object getValue() + { + return _value; + } + + public void writeToBuffer(ByteBuffer buffer) + { + _type.writeToBuffer(_value, buffer); + } + + public int getEncodingSize() + { + return _type.getEncodingSize(_value); + } + + public static AMQTypedValue readFromBuffer(ByteBuffer buffer) + { + AMQType type = AMQTypeMap.getType(buffer.get()); + + return new AMQTypedValue(type, buffer); + } + + public String toString() + { + return "[" + getType() + ": " + getValue() + "]"; + } + + + public boolean equals(Object o) + { + if(o instanceof AMQTypedValue) + { + AMQTypedValue other = (AMQTypedValue) o; + return _type == other._type && (_value == null ? other._value == null : _value.equals(other._value)); + } + else + { + return false; + } + } + + public int hashCode() + { + return _type.hashCode() ^ (_value == null ? 0 : _value.hashCode()); + } + + + public static AMQTypedValue toTypedValue(Object val) + { + if(val == null) + { + return AMQType.VOID.asTypedValue(null); + } + + Class klass = val.getClass(); + if(klass == String.class) + { + return AMQType.ASCII_STRING.asTypedValue(val); + } + else if(klass == Character.class) + { + return AMQType.ASCII_CHARACTER.asTypedValue(val); + } + else if(klass == Integer.class) + { + return AMQType.INT.asTypedValue(val); + } + else if(klass == Long.class) + { + return AMQType.LONG.asTypedValue(val); + } + else if(klass == Float.class) + { + return AMQType.FLOAT.asTypedValue(val); + } + else if(klass == Double.class) + { + return AMQType.DOUBLE.asTypedValue(val); + } + else if(klass == Date.class) + { + return AMQType.TIMESTAMP.asTypedValue(val); + } + else if(klass == Byte.class) + { + return AMQType.BYTE.asTypedValue(val); + } + else if(klass == Boolean.class) + { + return AMQType.BOOLEAN.asTypedValue(val); + } + else if(klass == byte[].class) + { + return AMQType.BINARY.asTypedValue(val); + } + else if(klass == BigDecimal.class) + { + return AMQType.DECIMAL.asTypedValue(val); + } + else if(val instanceof Map) + { + return AMQType.FIELD_TABLE.asTypedValue(FieldTable.convertToFieldTable((Map)val)); + } + return null; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/BasicContentHeaderProperties.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/BasicContentHeaderProperties.java new file mode 100644 index 0000000000..c7d89a9927 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/BasicContentHeaderProperties.java @@ -0,0 +1,838 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BasicContentHeaderProperties implements CommonContentHeaderProperties +{ + //persistent & non-persistent constants, values as per JMS DeliveryMode + public static final int NON_PERSISTENT = 1; + public static final int PERSISTENT = 2; + + private static final Logger _logger = LoggerFactory.getLogger(BasicContentHeaderProperties.class); + + 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; + + private FieldTable _headers; + + private byte _deliveryMode; + + private byte _priority; + + private AMQShortString _correlationId; + + private AMQShortString _replyTo; + + private long _expiration; + + private AMQShortString _messageId; + + private long _timestamp; + + private AMQShortString _type; + + private AMQShortString _userId; + + private AMQShortString _appId; + + private AMQShortString _clusterId; + + private int _propertyFlags = 0; + private static final int CONTENT_TYPE_MASK = 1 << 15; + 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 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; + private static final int MESSAGE_ID_MASK = 1 << 7; + private static final int TIMESTAMP_MASK = 1 << 6; + private static final int TYPE_MASK = 1 << 5; + private static final int USER_ID_MASK = 1 << 4; + private static final int APPLICATION_ID_MASK = 1 << 3; + 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) + { + size += EncodingUtils.encodedShortStringLength(_contentType); + } + + if ((_propertyFlags & ENCONDING_MASK) > 0) + { + size += EncodingUtils.encodedShortStringLength(_encoding); + } + + if ((_propertyFlags & HEADERS_MASK) > 0) + { + size += EncodingUtils.encodedFieldTableLength(_headers); + } + + if ((_propertyFlags & DELIVERY_MODE_MASK) > 0) + { + size += 1; + } + + if ((_propertyFlags & PROPRITY_MASK) > 0) + { + size += 1; + } + + if ((_propertyFlags & CORRELATION_ID_MASK) > 0) + { + size += EncodingUtils.encodedShortStringLength(_correlationId); + } + + if ((_propertyFlags & REPLY_TO_MASK) > 0) + { + size += EncodingUtils.encodedShortStringLength(_replyTo); + } + + if ((_propertyFlags & EXPIRATION_MASK) > 0) + { + if (_expiration == 0L) + { + size += EncodingUtils.encodedShortStringLength(ZERO_STRING); + } + else + { + size += EncodingUtils.encodedShortStringLength(_expiration); + } + } + + if ((_propertyFlags & MESSAGE_ID_MASK) > 0) + { + size += EncodingUtils.encodedShortStringLength(_messageId); + } + + if ((_propertyFlags & TIMESTAMP_MASK) > 0) + { + size += 8; + } + + if ((_propertyFlags & TYPE_MASK) > 0) + { + size += EncodingUtils.encodedShortStringLength(_type); + } + + if ((_propertyFlags & USER_ID_MASK) > 0) + { + size += EncodingUtils.encodedShortStringLength(_userId); + } + + if ((_propertyFlags & APPLICATION_ID_MASK) > 0) + { + size += EncodingUtils.encodedShortStringLength(_appId); + } + + if ((_propertyFlags & CLUSTER_ID_MASK) > 0) + { + size += EncodingUtils.encodedShortStringLength(_clusterId); + } + + return size; + } + } + + private void clearEncodedForm() + { + if (!_decoded && (_encodedForm != null)) + { + // decode(); + } + + _encodedForm = null; + } + + public void setPropertyFlags(int propertyFlags) + { + _hasBeenUpdated = true; + clearEncodedForm(); + _propertyFlags = propertyFlags; + } + + public int getPropertyFlags() + { + return _propertyFlags; + } + + public void writePropertyListPayload(ByteBuffer buffer) + { + if (_encodedForm != null) + { + buffer.put(_encodedForm); + } + else + { + if ((_propertyFlags & (CONTENT_TYPE_MASK)) != 0) + { + EncodingUtils.writeShortStringBytes(buffer, _contentType); + } + + if ((_propertyFlags & ENCONDING_MASK) != 0) + { + EncodingUtils.writeShortStringBytes(buffer, _encoding); + } + + if ((_propertyFlags & HEADERS_MASK) != 0) + { + EncodingUtils.writeFieldTableBytes(buffer, _headers); + } + + if ((_propertyFlags & DELIVERY_MODE_MASK) != 0) + { + buffer.put(_deliveryMode); + } + + if ((_propertyFlags & PROPRITY_MASK) != 0) + { + buffer.put(_priority); + } + + if ((_propertyFlags & CORRELATION_ID_MASK) != 0) + { + EncodingUtils.writeShortStringBytes(buffer, _correlationId); + } + + if ((_propertyFlags & REPLY_TO_MASK) != 0) + { + EncodingUtils.writeShortStringBytes(buffer, _replyTo); + } + + if ((_propertyFlags & EXPIRATION_MASK) != 0) + { + 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 & TIMESTAMP_MASK) != 0) + { + EncodingUtils.writeTimestamp(buffer, _timestamp); + } + + if ((_propertyFlags & TYPE_MASK) != 0) + { + EncodingUtils.writeShortStringBytes(buffer, _type); + } + + if ((_propertyFlags & USER_ID_MASK) != 0) + { + EncodingUtils.writeShortStringBytes(buffer, _userId); + } + + if ((_propertyFlags & APPLICATION_ID_MASK) != 0) + { + EncodingUtils.writeShortStringBytes(buffer, _appId); + } + + if ((_propertyFlags & CLUSTER_ID_MASK) != 0) + { + EncodingUtils.writeShortStringBytes(buffer, _clusterId); + } + } + } + + public void populatePropertiesFromBuffer(ByteBuffer buffer, int propertyFlags, int size) throws AMQFrameDecodingException + { + _propertyFlags = propertyFlags; + + if (_logger.isDebugEnabled()) + { + _logger.debug("Property flags: " + _propertyFlags); + } + + decode(buffer); + /*_encodedForm = new byte[size]; + buffer.get(_encodedForm, 0, size); + _decoded = false; + _decodedHeaders = false; + _decodedContentType = false;*/ + } + + 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 & ENCONDING_MASK) != 0) + { + _encoding = EncodingUtils.readAMQShortString(buffer); + } + + if ((_propertyFlags & HEADERS_MASK) != 0) + { + _headers = EncodingUtils.readFieldTable(buffer); + } + + if ((_propertyFlags & DELIVERY_MODE_MASK) != 0) + { + _deliveryMode = buffer.get(); + } + + if ((_propertyFlags & PROPRITY_MASK) != 0) + { + _priority = buffer.get(); + } + + if ((_propertyFlags & CORRELATION_ID_MASK) != 0) + { + _correlationId = EncodingUtils.readAMQShortString(buffer); + } + + if ((_propertyFlags & REPLY_TO_MASK) != 0) + { + _replyTo = EncodingUtils.readAMQShortString(buffer); + } + + if ((_propertyFlags & EXPIRATION_MASK) != 0) + { + _expiration = EncodingUtils.readLongAsShortString(buffer); + } + + if ((_propertyFlags & MESSAGE_ID_MASK) != 0) + { + _messageId = EncodingUtils.readAMQShortString(buffer); + } + + if ((_propertyFlags & TIMESTAMP_MASK) != 0) + { + _timestamp = EncodingUtils.readTimestamp(buffer); + } + + if ((_propertyFlags & TYPE_MASK) != 0) + { + _type = EncodingUtils.readAMQShortString(buffer); + } + + if ((_propertyFlags & USER_ID_MASK) != 0) + { + _userId = EncodingUtils.readAMQShortString(buffer); + } + + if ((_propertyFlags & APPLICATION_ID_MASK) != 0) + { + _appId = EncodingUtils.readAMQShortString(buffer); + } + + if ((_propertyFlags & CLUSTER_ID_MASK) != 0) + { + _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)); + } + + public String getEncodingAsString() + { + + return (getEncoding() == null) ? null : getEncoding().toString(); + } + + public AMQShortString getEncoding() + { + decodeIfNecessary(); + + return _encoding; + } + + public void setEncoding(String encoding) + { + _hasBeenUpdated = true; + clearEncodedForm(); + _propertyFlags |= ENCONDING_MASK; + _encoding = (encoding == null) ? null : new AMQShortString(encoding); + } + + public void setEncoding(AMQShortString encoding) + { + _hasBeenUpdated = true; + clearEncodedForm(); + _propertyFlags |= ENCONDING_MASK; + _encoding = encoding; + } + + public FieldTable getHeaders() + { + decodeHeadersIfNecessary(); + + if (_headers == null) + { + setHeaders(FieldTableFactory.newFieldTable()); + } + + return _headers; + } + + 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) + { + 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; + } + + public void setUserId(String userId) + { + setUserId((userId == null) ? null : new AMQShortString(userId)); + } + + 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; + } + + public String toString() + { + return "reply-to = " + _replyTo + ",propertyFlags = " + _propertyFlags + ",ApplicationID = " + _appId + + ",ClusterID = " + _clusterId + ",UserId = " + _userId + ",JMSMessageID = " + _messageId + + ",JMSCorrelationID = " + _correlationId + ",JMSDeliveryMode = " + _deliveryMode + ",JMSExpiration = " + + _expiration + ",JMSPriority = " + _priority + ",JMSTimestamp = " + _timestamp + ",JMSType = " + _type; + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/BodyFactory.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/BodyFactory.java new file mode 100644 index 0000000000..59646577e1 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/BodyFactory.java @@ -0,0 +1,31 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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; + +/** + * Any class that is capable of turning a stream of bytes into an AMQ structure must implement this interface. + */ +public interface BodyFactory +{ + AMQBody createBody(ByteBuffer in, long bodySize) throws AMQFrameDecodingException; +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/CommonContentHeaderProperties.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/CommonContentHeaderProperties.java new file mode 100644 index 0000000000..7162c37062 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/CommonContentHeaderProperties.java @@ -0,0 +1,81 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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; + +public interface CommonContentHeaderProperties extends ContentHeaderProperties +{ + AMQShortString getContentType(); + + void setContentType(AMQShortString contentType); + + FieldTable getHeaders(); + + void setHeaders(FieldTable headers); + + byte getDeliveryMode(); + + void setDeliveryMode(byte deliveryMode); + + byte getPriority(); + + void setPriority(byte priority); + + AMQShortString getCorrelationId(); + + void setCorrelationId(AMQShortString correlationId); + + AMQShortString getReplyTo(); + + void setReplyTo(AMQShortString replyTo); + + long getExpiration(); + + void setExpiration(long expiration); + + AMQShortString getMessageId(); + + void setMessageId(AMQShortString messageId); + + long getTimestamp(); + + void setTimestamp(long timestamp); + + AMQShortString getType(); + + void setType(AMQShortString type); + + AMQShortString getUserId(); + + void setUserId(AMQShortString userId); + + AMQShortString getAppId(); + + void setAppId(AMQShortString appId); + + AMQShortString getClusterId(); + + void setClusterId(AMQShortString clusterId); + + AMQShortString getEncoding(); + + void setEncoding(AMQShortString encoding); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/CompositeAMQDataBlock.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/CompositeAMQDataBlock.java new file mode 100644 index 0000000000..94030f383e --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/CompositeAMQDataBlock.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.framing; + +import org.apache.mina.common.ByteBuffer; + +public class CompositeAMQDataBlock extends AMQDataBlock implements EncodableAMQDataBlock +{ + + private AMQDataBlock[] _blocks; + + public CompositeAMQDataBlock(AMQDataBlock[] blocks) + { + _blocks = blocks; + } + + + public AMQDataBlock[] getBlocks() + { + return _blocks; + } + + + public long getSize() + { + long frameSize = 0; + for (int i = 0; i < _blocks.length; i++) + { + frameSize += _blocks[i].getSize(); + } + return frameSize; + } + + public void writePayload(ByteBuffer buffer) + { + for (int i = 0; i < _blocks.length; i++) + { + _blocks[i].writePayload(buffer); + } + } + + public String toString() + { + if (_blocks == null) + { + return "No blocks contained in composite frame"; + } + else + { + StringBuilder buf = new StringBuilder(this.getClass().getName()); + buf.append("{"); + for (int i = 0 ; i < _blocks.length; i++) + { + buf.append(" ").append(i).append("=[").append(_blocks[i].toString()).append("]"); + } + buf.append("}"); + return buf.toString(); + } + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/Content.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/Content.java new file mode 100644 index 0000000000..e5feeec2a4 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/Content.java @@ -0,0 +1,26 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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; + +public interface Content +{ + // TODO: New Content class required for AMQP 0-9. +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/ContentBody.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/ContentBody.java new file mode 100644 index 0000000000..9d39f8aa86 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/ContentBody.java @@ -0,0 +1,121 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.qpid.protocol.AMQVersionAwareProtocolSession; +import org.apache.qpid.AMQException; + +public class ContentBody implements AMQBody +{ + public static final byte TYPE = 3; + + public ByteBuffer payload; + + public ContentBody() + { + } + + public ContentBody(ByteBuffer buffer, long size) throws AMQFrameDecodingException + { + if (size > 0) + { + payload = buffer.slice(); + payload.limit((int) size); + buffer.skip((int) size); + } + + } + + + public ContentBody(ByteBuffer payload) + { + this.payload = payload; + } + + public byte getFrameType() + { + return TYPE; + } + + public int getSize() + { + return (payload == null ? 0 : payload.limit()); + } + + public void writePayload(ByteBuffer buffer) + { + 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) + throws AMQException + { + session.contentBodyReceived(channelId, this); + } + + protected void populateFromBuffer(ByteBuffer buffer, long size) throws AMQFrameDecodingException + { + if (size > 0) + { + 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; + } + } + + + + public static AMQFrame createAMQFrame(int channelId, ContentBody body) + { + final AMQFrame frame = new AMQFrame(channelId, body); + return frame; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/ContentBodyFactory.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/ContentBodyFactory.java new file mode 100644 index 0000000000..c42995d148 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/ContentBodyFactory.java @@ -0,0 +1,48 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ContentBodyFactory implements BodyFactory +{ + private static final Logger _log = LoggerFactory.getLogger(AMQMethodBodyFactory.class); + + private static final ContentBodyFactory _instance = new ContentBodyFactory(); + + public static ContentBodyFactory getInstance() + { + return _instance; + } + + private ContentBodyFactory() + { + _log.debug("Creating content body factory"); + } + + public AMQBody createBody(ByteBuffer in, long bodySize) throws AMQFrameDecodingException + { + return new ContentBody(in, bodySize); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderBody.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderBody.java new file mode 100644 index 0000000000..30db3b8be7 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderBody.java @@ -0,0 +1,141 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.qpid.protocol.AMQVersionAwareProtocolSession; +import org.apache.qpid.AMQException; + +public class ContentHeaderBody implements AMQBody +{ + public static final byte TYPE = 2; + + public int classId; + + public int weight; + + /** unsigned long but java can't handle that anyway when allocating byte array */ + public long bodySize; + + /** must never be null */ + private ContentHeaderProperties properties; + + public ContentHeaderBody() + { + } + + public ContentHeaderBody(ByteBuffer buffer, long size) throws AMQFrameDecodingException + { + 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); + + } + + + public ContentHeaderBody(ContentHeaderProperties props, int classId) + { + properties = props; + this.classId = classId; + } + + public ContentHeaderBody(int classId, int weight, ContentHeaderProperties props, long bodySize) + { + this(props, classId); + this.weight = weight; + this.bodySize = bodySize; + } + + public byte getFrameType() + { + return TYPE; + } + + protected void populateFromBuffer(ByteBuffer buffer, long size) + throws AMQFrameDecodingException, AMQProtocolVersionException + { + 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); + } + + /** + * Helper method that is used currently by the persistence layer (by BDB at the moment). + * @param buffer + * @param size + * @return + * @throws AMQFrameDecodingException + */ + public static ContentHeaderBody createFromBuffer(ByteBuffer buffer, long size) + throws AMQFrameDecodingException, AMQProtocolVersionException + { + ContentHeaderBody body = new ContentHeaderBody(buffer, size); + + return body; + } + + public int getSize() + { + return 2 + 2 + 8 + 2 + properties.getPropertyListSize(); + } + + public void writePayload(ByteBuffer buffer) + { + EncodingUtils.writeUnsignedShort(buffer, classId); + EncodingUtils.writeUnsignedShort(buffer, weight); + buffer.putLong(bodySize); + EncodingUtils.writeUnsignedShort(buffer, properties.getPropertyFlags()); + properties.writePropertyListPayload(buffer); + } + + public void handle(final int channelId, final AMQVersionAwareProtocolSession session) + throws AMQException + { + session.contentHeaderReceived(channelId, this); + } + + public static AMQFrame createAMQFrame(int channelId, int classId, int weight, BasicContentHeaderProperties properties, + long bodySize) + { + return new AMQFrame(channelId, new ContentHeaderBody(classId, weight, properties, bodySize)); + } + + public static AMQFrame createAMQFrame(int channelId, ContentHeaderBody body) + { + return new AMQFrame(channelId, body); + } + + public ContentHeaderProperties getProperties() + { + return properties; + } + + public void setProperties(ContentHeaderProperties props) + { + properties = props; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderBodyFactory.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderBodyFactory.java new file mode 100644 index 0000000000..8d5e2f9fb4 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderBodyFactory.java @@ -0,0 +1,50 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ContentHeaderBodyFactory implements BodyFactory +{ + private static final Logger _log = LoggerFactory.getLogger(AMQMethodBodyFactory.class); + + private static final ContentHeaderBodyFactory _instance = new ContentHeaderBodyFactory(); + + public static ContentHeaderBodyFactory getInstance() + { + return _instance; + } + + private ContentHeaderBodyFactory() + { + _log.debug("Creating content header body factory"); + } + + 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 + return new ContentHeaderBody(in, bodySize); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderProperties.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderProperties.java new file mode 100644 index 0000000000..7ef538cfdc --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderProperties.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.framing; + +import org.apache.mina.common.ByteBuffer; + +/** + * There will be an implementation of this interface for each content type. All content types have associated + * header properties and this provides a way to encode and decode them. + */ +public interface ContentHeaderProperties +{ + /** + * Writes the property list to the buffer, in a suitably encoded form. + * @param buffer The buffer to write to + */ + void writePropertyListPayload(ByteBuffer buffer); + + /** + * Populates the properties from buffer. + * @param buffer The buffer to read from. + * @param propertyFlags he property flags. + * @throws AMQFrameDecodingException when the buffer does not contain valid data + */ + void populatePropertiesFromBuffer(ByteBuffer buffer, int propertyFlags, int size) + throws AMQFrameDecodingException; + + /** + * @return the size of the encoded property list in bytes. + */ + int getPropertyListSize(); + + /** + * Gets the property flags. Property flags indicate which properties are set in the list. The + * position and meaning of each flag is defined in the protocol specification for the particular + * content type with which these properties are associated. + * @return flags + */ + int getPropertyFlags(); + + void updated(); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderPropertiesFactory.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderPropertiesFactory.java new file mode 100644 index 0000000000..46189b63d7 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderPropertiesFactory.java @@ -0,0 +1,59 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.qpid.framing.amqp_8_0.BasicConsumeBodyImpl; + +public class ContentHeaderPropertiesFactory +{ + private static final ContentHeaderPropertiesFactory _instance = new ContentHeaderPropertiesFactory(); + + public static ContentHeaderPropertiesFactory getInstance() + { + return _instance; + } + + private ContentHeaderPropertiesFactory() + { + } + + public ContentHeaderProperties createContentHeaderProperties(int classId, int propertyFlags, + ByteBuffer buffer, int size) + throws AMQFrameDecodingException + { + ContentHeaderProperties properties; + // AMQP version change: "Hardwired" version to major=8, minor=0 + // TODO: Change so that the actual version is obtained from + // the ProtocolInitiation object for this session. + if (classId == BasicConsumeBodyImpl.CLASS_ID) + { + properties = new BasicContentHeaderProperties(); + } + else + { + throw new AMQFrameDecodingException(null, "Unsupport content header class id: " + classId, null); + } + properties.populatePropertiesFromBuffer(buffer, propertyFlags, size); + return properties; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/DeferredDataBlock.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/DeferredDataBlock.java new file mode 100644 index 0000000000..f6795ff200 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/qpid/framing/EncodableAMQDataBlock.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/EncodableAMQDataBlock.java new file mode 100644 index 0000000000..9cf96e698c --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/EncodableAMQDataBlock.java @@ -0,0 +1,35 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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; + +/** + * Marker interface to indicate to MINA that a data block should be encoded with the + * single encoder/decoder that we have defined. + * + * Note that due to a bug in MINA all classes must directly implement this interface, even if + * a superclass implements it. + * TODO: fix MINA so that this is not necessary + * + */ +public interface EncodableAMQDataBlock +{ + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/EncodingUtils.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/EncodingUtils.java new file mode 100644 index 0000000000..6425f8c591 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/EncodingUtils.java @@ -0,0 +1,1033 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.charset.Charset; + +public class EncodingUtils +{ + private static final Logger _logger = LoggerFactory.getLogger(EncodingUtils.class); + + private static final String STRING_ENCODING = "iso8859-15"; + + private static final Charset _charset = Charset.forName("iso8859-15"); + + public static final int SIZEOF_UNSIGNED_SHORT = 2; + public static final int SIZEOF_UNSIGNED_INT = 4; + private static final boolean[] ALL_FALSE_ARRAY = new boolean[8]; + + public static int encodedShortStringLength(String s) + { + if (s == null) + { + return 1; + } + else + { + return (short) (1 + s.length()); + } + } + + public static int encodedShortStringLength(short s) + { + if (s == 0) + { + return 1 + 1; + } + + int len = 0; + if (s < 0) + { + len = 1; + // sloppy - doesn't work of Integer.MIN_VALUE + s = (short) -s; + } + + if (s > 9999) + { + return 1 + 5; + } + else if (s > 999) + { + return 1 + 4; + } + else if (s > 99) + { + return 1 + 3; + } + else if (s > 9) + { + return 1 + 2; + } + else + { + return 1 + 1; + } + + } + + public static int encodedShortStringLength(int i) + { + if (i == 0) + { + return 1 + 1; + } + + int len = 0; + if (i < 0) + { + len = 1; + // sloppy - doesn't work of Integer.MIN_VALUE + i = -i; + } + + // range is now 1 - 2147483647 + if (i < Short.MAX_VALUE) + { + return len + encodedShortStringLength((short) i); + } + else if (i > 999999) + { + return len + 6 + encodedShortStringLength((short) (i / 1000000)); + } + else // if (i > 99999) + { + return len + 5 + encodedShortStringLength((short) (i / 100000)); + } + + } + + public static int encodedShortStringLength(long l) + { + if (l == 0) + { + return 1 + 1; + } + + int len = 0; + if (l < 0) + { + len = 1; + // sloppy - doesn't work of Long.MIN_VALUE + l = -l; + } + + if (l < Integer.MAX_VALUE) + { + return len + encodedShortStringLength((int) l); + } + else if (l > 9999999999L) + { + return len + 10 + encodedShortStringLength((int) (l / 10000000000L)); + } + else + { + return len + 1 + encodedShortStringLength((int) (l / 10L)); + } + + } + + public static int encodedShortStringLength(AMQShortString s) + { + if (s == null) + { + return 1; + } + else + { + return (1 + s.length()); + } + } + + public static int encodedLongStringLength(String s) + { + if (s == null) + { + return 4; + } + else + { + return 4 + s.length(); + } + } + + public static int encodedLongStringLength(char[] s) + { + if (s == null) + { + return 4; + } + else + { + return 4 + s.length; + } + } + + public static int encodedLongstrLength(byte[] bytes) + { + if (bytes == null) + { + return 4; + } + else + { + return 4 + bytes.length; + } + } + + public static int encodedFieldTableLength(FieldTable table) + { + if (table == null) + { + // length is encoded as 4 octets + return 4; + } + else + { + // length of the table plus 4 octets for the length + return (int) table.getEncodedSize() + 4; + } + } + + public static int encodedContentLength(Content table) + { + // TODO: New Content class required for AMQP 0-9. + return 0; + } + + public static void writeShortStringBytes(ByteBuffer buffer, String s) + { + if (s != null) + { + byte[] encodedString = new byte[s.length()]; + char[] cha = s.toCharArray(); + for (int i = 0; i < cha.length; i++) + { + encodedString[i] = (byte) cha[i]; + } + + // TODO: check length fits in an unsigned byte + writeUnsignedByte(buffer, (short)encodedString.length); + buffer.put(encodedString); + + + } + else + { + // really writing out unsigned byte + buffer.put((byte) 0); + } + } + + public static void writeShortStringBytes(ByteBuffer buffer, AMQShortString s) + { + if (s != null) + { + + s.writeToBuffer(buffer); + } + else + { + // really writing out unsigned byte + buffer.put((byte) 0); + } + } + + public static void writeLongStringBytes(ByteBuffer buffer, String s) + { + assert (s == null) || (s.length() <= 0xFFFE); + if (s != null) + { + int len = s.length(); + writeUnsignedInteger(buffer, s.length()); + byte[] encodedString = new byte[len]; + char[] cha = s.toCharArray(); + for (int i = 0; i < cha.length; i++) + { + encodedString[i] = (byte) cha[i]; + } + + buffer.put(encodedString); + } + else + { + writeUnsignedInteger(buffer, 0); + } + } + + public static void writeLongStringBytes(ByteBuffer buffer, char[] s) + { + assert (s == null) || (s.length <= 0xFFFE); + if (s != null) + { + int len = s.length; + writeUnsignedInteger(buffer, s.length); + byte[] encodedString = new byte[len]; + for (int i = 0; i < s.length; i++) + { + encodedString[i] = (byte) s[i]; + } + + buffer.put(encodedString); + } + else + { + writeUnsignedInteger(buffer, 0); + } + } + + public static void writeLongStringBytes(ByteBuffer buffer, byte[] bytes) + { + assert (bytes == null) || (bytes.length <= 0xFFFE); + if (bytes != null) + { + writeUnsignedInteger(buffer, bytes.length); + buffer.put(bytes); + } + else + { + writeUnsignedInteger(buffer, 0); + } + } + + public static void writeUnsignedByte(ByteBuffer buffer, short b) + { + byte bv = (byte) b; + buffer.put(bv); + } + + 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.putShort((short) s); + } + else + { + short sv = (short) s; + buffer.put((byte) (0xFF & (sv >> 8))); + buffer.put((byte) (0xFF & sv)); + } + } + + public static int unsignedIntegerLength() + { + return 4; + } + + 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.putInt((int) l); + } + else + { + int iv = (int) l; + + // 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.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(ByteBuffer buffer, FieldTable table) + { + if (table != null) + { + table.writeToBuffer(buffer); + } + else + { + EncodingUtils.writeUnsignedInteger(buffer, 0); + } + } + + public static void writeContentBytes(ByteBuffer buffer, Content content) + { + // TODO: New Content class required for AMQP 0-9. + } + + public static void writeBooleans(ByteBuffer buffer, boolean[] values) + { + byte packedValue = 0; + for (int i = 0; i < values.length; i++) + { + if (values[i]) + { + packedValue = (byte) (packedValue | (1 << i)); + } + } + + buffer.put(packedValue); + } + + public static void writeBooleans(ByteBuffer buffer, boolean value) + { + + buffer.put(value ? (byte) 1 : (byte) 0); + } + + public static void writeBooleans(ByteBuffer buffer, boolean value0, boolean value1) + { + byte packedValue = value0 ? (byte) 1 : (byte) 0; + + if (value1) + { + packedValue = (byte) (packedValue | (byte) (1 << 1)); + } + + buffer.put(packedValue); + } + + public static void writeBooleans(ByteBuffer buffer, boolean value0, boolean value1, boolean value2) + { + byte packedValue = value0 ? (byte) 1 : (byte) 0; + + if (value1) + { + packedValue = (byte) (packedValue | (byte) (1 << 1)); + } + + if (value2) + { + packedValue = (byte) (packedValue | (byte) (1 << 2)); + } + + buffer.put(packedValue); + } + + public static void writeBooleans(ByteBuffer buffer, boolean value0, boolean value1, boolean value2, boolean value3) + { + byte packedValue = value0 ? (byte) 1 : (byte) 0; + + if (value1) + { + packedValue = (byte) (packedValue | (byte) (1 << 1)); + } + + if (value2) + { + packedValue = (byte) (packedValue | (byte) (1 << 2)); + } + + if (value3) + { + packedValue = (byte) (packedValue | (byte) (1 << 3)); + } + + buffer.put(packedValue); + } + + public static void writeBooleans(ByteBuffer buffer, boolean value0, boolean value1, boolean value2, boolean value3, + boolean value4) + { + byte packedValue = value0 ? (byte) 1 : (byte) 0; + + if (value1) + { + packedValue = (byte) (packedValue | (byte) (1 << 1)); + } + + if (value2) + { + packedValue = (byte) (packedValue | (byte) (1 << 2)); + } + + if (value3) + { + packedValue = (byte) (packedValue | (byte) (1 << 3)); + } + + if (value4) + { + packedValue = (byte) (packedValue | (byte) (1 << 4)); + } + + buffer.put(packedValue); + } + + 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; + + if (value1) + { + packedValue = (byte) (packedValue | (byte) (1 << 1)); + } + + if (value2) + { + packedValue = (byte) (packedValue | (byte) (1 << 2)); + } + + if (value3) + { + packedValue = (byte) (packedValue | (byte) (1 << 3)); + } + + if (value4) + { + packedValue = (byte) (packedValue | (byte) (1 << 4)); + } + + if (value5) + { + packedValue = (byte) (packedValue | (byte) (1 << 5)); + } + + buffer.put(packedValue); + } + + 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; + + if (value1) + { + packedValue = (byte) (packedValue | (byte) (1 << 1)); + } + + if (value2) + { + packedValue = (byte) (packedValue | (byte) (1 << 2)); + } + + if (value3) + { + packedValue = (byte) (packedValue | (byte) (1 << 3)); + } + + if (value4) + { + packedValue = (byte) (packedValue | (byte) (1 << 4)); + } + + if (value5) + { + packedValue = (byte) (packedValue | (byte) (1 << 5)); + } + + if (value6) + { + packedValue = (byte) (packedValue | (byte) (1 << 6)); + } + + buffer.put(packedValue); + } + + 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; + + if (value1) + { + packedValue = (byte) (packedValue | (byte) (1 << 1)); + } + + if (value2) + { + packedValue = (byte) (packedValue | (byte) (1 << 2)); + } + + if (value3) + { + packedValue = (byte) (packedValue | (byte) (1 << 3)); + } + + if (value4) + { + packedValue = (byte) (packedValue | (byte) (1 << 4)); + } + + if (value5) + { + packedValue = (byte) (packedValue | (byte) (1 << 5)); + } + + if (value6) + { + packedValue = (byte) (packedValue | (byte) (1 << 6)); + } + + if (value7) + { + packedValue = (byte) (packedValue | (byte) (1 << 7)); + } + + buffer.put(packedValue); + } + + /** + * This is used for writing longstrs. + * + * @param buffer + * @param data + */ + public static void writeLongstr(ByteBuffer buffer, byte[] data) + { + if (data != null) + { + writeUnsignedInteger(buffer, data.length); + buffer.put(data); + } + else + { + writeUnsignedInteger(buffer, 0); + } + } + + public static void writeTimestamp(ByteBuffer buffer, long timestamp) + { + writeLong(buffer, timestamp); + } + + public static boolean[] readBooleans(ByteBuffer buffer) + { + final byte packedValue = buffer.get(); + if (packedValue == 0) + { + return ALL_FALSE_ARRAY; + } + + final boolean[] result = new boolean[8]; + + result[0] = ((packedValue & 1) != 0); + result[1] = ((packedValue & (1 << 1)) != 0); + result[2] = ((packedValue & (1 << 2)) != 0); + result[3] = ((packedValue & (1 << 3)) != 0); + if ((packedValue & 0xF0) == 0) + { + result[0] = ((packedValue & 1) != 0); + } + + result[4] = ((packedValue & (1 << 4)) != 0); + result[5] = ((packedValue & (1 << 5)) != 0); + result[6] = ((packedValue & (1 << 6)) != 0); + result[7] = ((packedValue & (1 << 7)) != 0); + + return result; + } + + public static FieldTable readFieldTable(ByteBuffer buffer) throws AMQFrameDecodingException + { + long length = buffer.getUnsignedInt(); + if (length == 0) + { + return null; + } + else + { + return FieldTableFactory.newFieldTable(buffer, length); + } + } + + public static Content readContent(ByteBuffer buffer) throws AMQFrameDecodingException + { + // TODO: New Content class required for AMQP 0-9. + return null; + } + + public static AMQShortString readAMQShortString(ByteBuffer buffer) + { + return AMQShortString.readFromBuffer(buffer); + + } + + public static String readShortString(ByteBuffer buffer) + { + short length = buffer.getUnsigned(); + if (length == 0) + { + return null; + } + else + { + // this may seem rather odd to declare two array but testing has shown + // that constructing a string from a byte array is 5 (five) times slower + // than constructing one from a char array. + // this approach here is valid since we know that all the chars are + // ASCII (0-127) + byte[] stringBytes = new byte[length]; + buffer.get(stringBytes, 0, length); + char[] stringChars = new char[length]; + for (int i = 0; i < stringChars.length; i++) + { + stringChars[i] = (char) stringBytes[i]; + } + + return new String(stringChars); + } + } + + public static String readLongString(ByteBuffer buffer) + { + long length = buffer.getUnsignedInt(); + if (length == 0) + { + return ""; + } + else + { + // this may seem rather odd to declare two array but testing has shown + // that constructing a string from a byte array is 5 (five) times slower + // than constructing one from a char array. + // this approach here is valid since we know that all the chars are + // ASCII (0-127) + byte[] stringBytes = new byte[(int) length]; + buffer.get(stringBytes, 0, (int) length); + char[] stringChars = new char[(int) length]; + for (int i = 0; i < stringChars.length; i++) + { + stringChars[i] = (char) stringBytes[i]; + } + + return new String(stringChars); + } + } + + public static byte[] readLongstr(ByteBuffer buffer) + { + long length = buffer.getUnsignedInt(); + if (length == 0) + { + return null; + } + else + { + byte[] result = new byte[(int) length]; + buffer.get(result); + + return result; + } + } + + public static long readTimestamp(ByteBuffer buffer) + { + // Discard msb from AMQ timestamp + // buffer.getUnsignedInt(); + return buffer.getLong(); + } + + static byte[] hexToByteArray(String id) + { + // Should check param for null, long enough for this check, upper-case and trailing char + String s = (id.charAt(1) == 'x') ? id.substring(2) : id; // strip 0x + + int len = s.length(); + int byte_len = len / 2; + byte[] b = new byte[byte_len]; + + for (int i = 0; i < byte_len; i++) + { + // fixme: refine these repetitive subscript calcs. + int ch = i * 2; + + byte b1 = Byte.parseByte(s.substring(ch, ch + 1), 16); + byte b2 = Byte.parseByte(s.substring(ch + 1, ch + 2), 16); + + b[i] = (byte) ((b1 * 16) + b2); + } + + return (b); + } + + public static char[] convertToHexCharArray(byte[] from) + { + int length = from.length; + char[] result_buff = new char[(length * 2) + 2]; + + result_buff[0] = '0'; + result_buff[1] = 'x'; + + int bite; + int dest = 2; + + for (int i = 0; i < length; i++) + { + bite = from[i]; + + if (bite < 0) + { + bite += 256; + } + + result_buff[dest++] = hex_chars[bite >> 4]; + result_buff[dest++] = hex_chars[bite & 0x0f]; + } + + return (result_buff); + } + + public static String convertToHexString(byte[] from) + { + return (new String(convertToHexCharArray(from))); + } + + public static String convertToHexString(ByteBuffer bb) + { + int size = bb.limit(); + + byte[] from = new byte[size]; + + // Is this not the same. + // bb.get(from, 0, length); + for (int i = 0; i < size; i++) + { + from[i] = bb.get(i); + } + + return (new String(convertToHexCharArray(from))); + } + + private static char[] hex_chars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + // **** new methods + + // AMQP_BOOLEAN_PROPERTY_PREFIX + + public static void writeBoolean(ByteBuffer buffer, Boolean aBoolean) + { + buffer.put((byte) (aBoolean ? 1 : 0)); + } + + public static boolean readBoolean(ByteBuffer buffer) + { + byte packedValue = buffer.get(); + + return (packedValue == 1); + } + + public static int encodedBooleanLength() + { + return 1; + } + + // AMQP_BYTE_PROPERTY_PREFIX + public static void writeByte(ByteBuffer buffer, Byte aByte) + { + buffer.put(aByte); + } + + public static byte readByte(ByteBuffer buffer) + { + return buffer.get(); + } + + public static int encodedByteLength() + { + return 1; + } + + // AMQP_SHORT_PROPERTY_PREFIX + public static void writeShort(ByteBuffer buffer, Short aShort) + { + buffer.putShort(aShort); + } + + public static short readShort(ByteBuffer buffer) + { + return buffer.getShort(); + } + + public static int encodedShortLength() + { + return 2; + } + + // INTEGER_PROPERTY_PREFIX + public static void writeInteger(ByteBuffer buffer, Integer aInteger) + { + buffer.putInt(aInteger); + } + + public static int readInteger(ByteBuffer buffer) + { + return buffer.getInt(); + } + + public static int encodedIntegerLength() + { + return 4; + } + + // AMQP_LONG_PROPERTY_PREFIX + public static void writeLong(ByteBuffer buffer, Long aLong) + { + buffer.putLong(aLong); + } + + public static long readLong(ByteBuffer buffer) + { + return buffer.getLong(); + } + + public static int encodedLongLength() + { + return 8; + } + + // Float_PROPERTY_PREFIX + public static void writeFloat(ByteBuffer buffer, Float aFloat) + { + buffer.putFloat(aFloat); + } + + public static float readFloat(ByteBuffer buffer) + { + return buffer.getFloat(); + } + + public static int encodedFloatLength() + { + return 4; + } + + // Double_PROPERTY_PREFIX + public static void writeDouble(ByteBuffer buffer, Double aDouble) + { + buffer.putDouble(aDouble); + } + + public static double readDouble(ByteBuffer buffer) + { + return buffer.getDouble(); + } + + public static int encodedDoubleLength() + { + return 8; + } + + public static byte[] readBytes(ByteBuffer buffer) + { + long length = buffer.getUnsignedInt(); + if (length == 0) + { + return null; + } + else + { + byte[] dataBytes = new byte[(int)length]; + buffer.get(dataBytes, 0, (int)length); + + return dataBytes; + } + } + + 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.put(data); + } + else + { + // really writing out unsigned byte + //buffer.put((byte) 0); + writeUnsignedInteger(buffer, 0L); + } + } + + // CHAR_PROPERTY + public static int encodedCharLength() + { + return encodedByteLength(); + } + + public static char readChar(ByteBuffer buffer) + { + // This is valid as we know that the Character is ASCII 0..127 + return (char) buffer.get(); + } + + 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(ByteBuffer buffer) + { + short length = buffer.getUnsigned(); + short pos = 0; + if (length == 0) + { + return 0L; + } + + byte digit = buffer.get(); + boolean isNegative; + long result = 0; + if (digit == (byte) '-') + { + isNegative = true; + pos++; + digit = buffer.get(); + } + else + { + isNegative = false; + } + + result = digit - (byte) '0'; + pos++; + + while (pos < length) + { + pos++; + digit = buffer.get(); + result = (result << 3) + (result << 1); + result += digit - (byte) '0'; + } + + return result; + } + + public static long readUnsignedInteger(ByteBuffer buffer) + { + long l = 0xFF & buffer.get(); + l <<= 8; + l = l | (0xFF & buffer.get()); + l <<= 8; + l = l | (0xFF & buffer.get()); + l <<= 8; + l = l | (0xFF & buffer.get()); + + return l; + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java new file mode 100644 index 0000000000..22205d49f8 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java @@ -0,0 +1,1246 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.qpid.AMQPInvalidClassException; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +// extends FieldTable +public class FieldTable +{ + private static final Logger _logger = LoggerFactory.getLogger(FieldTable.class); + private static final String STRICT_AMQP = "STRICT_AMQP"; + private final boolean _strictAMQP = Boolean.valueOf(System.getProperty(STRICT_AMQP, "false")); + + 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; + + public FieldTable() + { + super(); + // _encodedForm = ByteBuffer.allocate(INITIAL_ENCODED_FORM_SIZE); + // _encodedForm.setAutoExpand(true); + // _encodedForm.limit(0); + } + + /** + * Construct a new field table. + * + * @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(ByteBuffer buffer, long length) + { + this(); + 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) + { + checkPropertyName(string); + + synchronized (this) + { + if (_properties == null) + { + if (_encodedForm == null) + { + return null; + } + else + { + populateFromBuffer(); + } + } + } + + if (_properties == null) + { + return null; + } + else + { + return _properties.get(string); + } + } + + private void populateFromBuffer() + { + try + { + setFromBuffer(_encodedForm, _encodedSize); + } + catch (AMQFrameDecodingException e) + { + _logger.error("Error decoding FieldTable in deferred decoding mode ", e); + throw new IllegalArgumentException(e); + } + } + + private AMQTypedValue setProperty(AMQShortString key, AMQTypedValue val) + { + checkPropertyName(key); + initMapIfNecessary(); + if (_properties.containsKey(key)) + { + _encodedForm = null; + + if (val == null) + { + return removeKey(key); + } + } + else if ((_encodedForm != null) && (val != null)) + { + // We have updated data to store in the buffer + // So clear the _encodedForm to allow it to be rebuilt later + // this is safer than simply appending to any existing buffer. + _encodedForm = null; + } + else if (val == null) + { + return null; + } + + AMQTypedValue oldVal = _properties.put(key, val); + if (oldVal != null) + { + _encodedSize -= oldVal.getEncodingSize(); + } + else + { + _encodedSize += EncodingUtils.encodedShortStringLength(key) + 1; + } + + _encodedSize += val.getEncodingSize(); + + return oldVal; + } + + private void initMapIfNecessary() + { + synchronized (this) + { + if (_properties == null) + { + if ((_encodedForm == null) || (_encodedSize == 0)) + { + _properties = new LinkedHashMap<AMQShortString, AMQTypedValue>(); + } + else + { + populateFromBuffer(); + } + } + + } + } + + public Boolean getBoolean(String string) + { + return getBoolean(new AMQShortString(string)); + } + + public Boolean getBoolean(AMQShortString string) + { + AMQTypedValue value = getProperty(string); + if ((value != null) && (value.getType() == AMQType.BOOLEAN)) + { + return (Boolean) value.getValue(); + } + else + { + return null; + } + } + + public Byte getByte(String string) + { + return getByte(new AMQShortString(string)); + } + + public Byte getByte(AMQShortString string) + { + AMQTypedValue value = getProperty(string); + if ((value != null) && (value.getType() == AMQType.BYTE)) + { + return (Byte) value.getValue(); + } + else + { + return null; + } + } + + public Short getShort(String string) + { + return getShort(new AMQShortString(string)); + } + + public Short getShort(AMQShortString string) + { + AMQTypedValue value = getProperty(string); + if ((value != null) && (value.getType() == AMQType.SHORT)) + { + return (Short) value.getValue(); + } + else + { + return null; + } + } + + public Integer getInteger(String string) + { + return getInteger(new AMQShortString(string)); + } + + public Integer getInteger(AMQShortString string) + { + AMQTypedValue value = getProperty(string); + if ((value != null) && (value.getType() == AMQType.INT)) + { + return (Integer) value.getValue(); + } + else + { + return null; + } + } + + public Long getLong(String string) + { + return getLong(new AMQShortString(string)); + } + + public Long getLong(AMQShortString string) + { + AMQTypedValue value = getProperty(string); + if ((value != null) && (value.getType() == AMQType.LONG)) + { + return (Long) value.getValue(); + } + else + { + return null; + } + } + + public Float getFloat(String string) + { + return getFloat(new AMQShortString(string)); + } + + public Float getFloat(AMQShortString string) + { + AMQTypedValue value = getProperty(string); + if ((value != null) && (value.getType() == AMQType.FLOAT)) + { + return (Float) value.getValue(); + } + else + { + return null; + } + } + + public Double getDouble(String string) + { + return getDouble(new AMQShortString(string)); + } + + public Double getDouble(AMQShortString string) + { + AMQTypedValue value = getProperty(string); + if ((value != null) && (value.getType() == AMQType.DOUBLE)) + { + return (Double) value.getValue(); + } + else + { + return null; + } + } + + public String getString(String string) + { + return getString(new AMQShortString(string)); + } + + public String getString(AMQShortString string) + { + AMQTypedValue value = getProperty(string); + if ((value != null) && ((value.getType() == AMQType.WIDE_STRING) || (value.getType() == AMQType.ASCII_STRING))) + { + return (String) value.getValue(); + } + else if ((value != null) && (value.getValue() != null) && !(value.getValue() instanceof byte[])) + { + return String.valueOf(value.getValue()); + } + else + { + return null; + } + + } + + public Character getCharacter(String string) + { + return getCharacter(new AMQShortString(string)); + } + + public Character getCharacter(AMQShortString string) + { + AMQTypedValue value = getProperty(string); + if ((value != null) && (value.getType() == AMQType.ASCII_CHARACTER)) + { + return (Character) value.getValue(); + } + else + { + return null; + } + } + + public byte[] getBytes(String string) + { + return getBytes(new AMQShortString(string)); + } + + public byte[] getBytes(AMQShortString string) + { + AMQTypedValue value = getProperty(string); + if ((value != null) && (value.getType() == AMQType.BINARY)) + { + return (byte[]) value.getValue(); + } + else + { + return null; + } + } + + /** + * Extracts a value from the field table that is itself a FieldTable associated with the specified parameter name. + * + * @param string The name of the parameter to get the associated FieldTable value for. + * + * @return The associated FieldTable value, or <tt>null</tt> if the associated value is not of FieldTable type or + * not present in the field table at all. + */ + public FieldTable getFieldTable(String string) + { + return getFieldTable(new AMQShortString(string)); + } + + /** + * Extracts a value from the field table that is itself a FieldTable associated with the specified parameter name. + * + * @param string The name of the parameter to get the associated FieldTable value for. + * + * @return The associated FieldTable value, or <tt>null</tt> if the associated value is not of FieldTable type or + * not present in the field table at all. + */ + public FieldTable getFieldTable(AMQShortString string) + { + AMQTypedValue value = getProperty(string); + + if ((value != null) && (value.getType() == AMQType.FIELD_TABLE)) + { + return (FieldTable) value.getValue(); + } + else + { + return null; + } + } + + public Object getObject(String string) + { + return getObject(new AMQShortString(string)); + } + + public Object getObject(AMQShortString string) + { + AMQTypedValue value = getProperty(string); + if (value != null) + { + return value.getValue(); + } + else + { + return value; + } + + } + + public Long getTimestamp(AMQShortString name) + { + AMQTypedValue value = getProperty(name); + if ((value != null) && (value.getType() == AMQType.TIMESTAMP)) + { + return (Long) value.getValue(); + } + else + { + return null; + } + } + + public BigDecimal getDecimal(AMQShortString propertyName) + { + AMQTypedValue value = getProperty(propertyName); + if ((value != null) && (value.getType() == AMQType.DECIMAL)) + { + return (BigDecimal) value.getValue(); + } + else + { + return null; + } + } + + // ************ Setters + public Object setBoolean(String string, Boolean b) + { + return setBoolean(new AMQShortString(string), b); + } + + public Object setBoolean(AMQShortString string, Boolean b) + { + return setProperty(string, AMQType.BOOLEAN.asTypedValue(b)); + } + + public Object setByte(String string, Byte b) + { + return setByte(new AMQShortString(string), b); + } + + public Object setByte(AMQShortString string, Byte b) + { + return setProperty(string, AMQType.BYTE.asTypedValue(b)); + } + + public Object setShort(String string, Short i) + { + return setShort(new AMQShortString(string), i); + } + + public Object setShort(AMQShortString string, Short i) + { + return setProperty(string, AMQType.SHORT.asTypedValue(i)); + } + + public Object setInteger(String string, Integer i) + { + return setInteger(new AMQShortString(string), i); + } + + public Object setInteger(AMQShortString string, Integer i) + { + return setProperty(string, AMQType.INT.asTypedValue(i)); + } + + public Object setLong(String string, Long l) + { + return setLong(new AMQShortString(string), l); + } + + public Object setLong(AMQShortString string, Long l) + { + return setProperty(string, AMQType.LONG.asTypedValue(l)); + } + + public Object setFloat(String string, Float f) + { + return setFloat(new AMQShortString(string), f); + } + + public Object setFloat(AMQShortString string, Float v) + { + return setProperty(string, AMQType.FLOAT.asTypedValue(v)); + } + + public Object setDouble(String string, Double d) + { + return setDouble(new AMQShortString(string), d); + } + + public Object setDouble(AMQShortString string, Double v) + { + return setProperty(string, AMQType.DOUBLE.asTypedValue(v)); + } + + public Object setString(String string, String s) + { + return setString(new AMQShortString(string), s); + } + + public Object setAsciiString(AMQShortString string, String value) + { + if (value == null) + { + return setProperty(string, AMQType.VOID.asTypedValue(null)); + } + else + { + return setProperty(string, AMQType.ASCII_STRING.asTypedValue(value)); + } + } + + public Object setString(AMQShortString string, String value) + { + if (value == null) + { + return setProperty(string, AMQType.VOID.asTypedValue(null)); + } + else + { + return setProperty(string, AMQType.LONG_STRING.asTypedValue(value)); + } + } + + public Object setChar(String string, char c) + { + return setChar(new AMQShortString(string), c); + } + + public Object setChar(AMQShortString string, char c) + { + return setProperty(string, AMQType.ASCII_CHARACTER.asTypedValue(c)); + } + + public Object setBytes(String string, byte[] b) + { + return setBytes(new AMQShortString(string), b); + } + + public Object setBytes(AMQShortString string, byte[] bytes) + { + return setProperty(string, AMQType.BINARY.asTypedValue(bytes)); + } + + public Object setBytes(String string, byte[] bytes, int start, int length) + { + return setBytes(new AMQShortString(string), bytes, start, length); + } + + public Object setBytes(AMQShortString string, byte[] bytes, int start, int length) + { + byte[] newBytes = new byte[length]; + System.arraycopy(bytes, start, newBytes, 0, length); + + return setBytes(string, bytes); + } + + public Object setObject(String string, Object o) + { + return setObject(new AMQShortString(string), o); + } + + public Object setTimestamp(AMQShortString string, long datetime) + { + return setProperty(string, AMQType.TIMESTAMP.asTypedValue(datetime)); + } + + public Object setDecimal(AMQShortString string, BigDecimal decimal) + { + if (decimal.longValue() > Integer.MAX_VALUE) + { + throw new UnsupportedOperationException("AMQP doesnot support decimals larger than " + Integer.MAX_VALUE); + } + + if (decimal.scale() > Byte.MAX_VALUE) + { + throw new UnsupportedOperationException("AMQP doesnot support decimal scales larger than " + Byte.MAX_VALUE); + } + + return setProperty(string, AMQType.DECIMAL.asTypedValue(decimal)); + } + + public Object setVoid(AMQShortString string) + { + return setProperty(string, AMQType.VOID.asTypedValue(null)); + } + + /** + * Associates a nested field table with the specified parameter name. + * + * @param string The name of the parameter to store in the table. + * @param ftValue The field table value to associate with the parameter name. + * + * @return The stored value. + */ + public Object setFieldTable(String string, FieldTable ftValue) + { + return setFieldTable(new AMQShortString(string), ftValue); + } + + /** + * Associates a nested field table with the specified parameter name. + * + * @param string The name of the parameter to store in the table. + * @param ftValue The field table value to associate with the parameter name. + * + * @return The stored value. + */ + public Object setFieldTable(AMQShortString string, FieldTable ftValue) + { + return setProperty(string, AMQType.FIELD_TABLE.asTypedValue(ftValue)); + } + + public Object setObject(AMQShortString string, Object object) + { + if (object instanceof Boolean) + { + return setBoolean(string, (Boolean) object); + } + else if (object instanceof Byte) + { + return setByte(string, (Byte) object); + } + else if (object instanceof Short) + { + return setShort(string, (Short) object); + } + else if (object instanceof Integer) + { + return setInteger(string, (Integer) object); + } + else if (object instanceof Long) + { + return setLong(string, (Long) object); + } + else if (object instanceof Float) + { + return setFloat(string, (Float) object); + } + else if (object instanceof Double) + { + return setDouble(string, (Double) object); + } + else if (object instanceof String) + { + return setString(string, (String) object); + } + else if (object instanceof Character) + { + return setChar(string, (Character) object); + } + else if (object instanceof byte[]) + { + return setBytes(string, (byte[]) object); + } + + throw new AMQPInvalidClassException(AMQPInvalidClassException.INVALID_OBJECT_MSG + (object == null ? "null" : object.getClass())); + } + + public boolean isNullStringValue(String name) + { + AMQTypedValue value = getProperty(new AMQShortString(name)); + + return (value != null) && (value.getType() == AMQType.VOID); + } + + // ***** Methods + + public Enumeration getPropertyNames() + { + return Collections.enumeration(keys()); + } + + public boolean propertyExists(AMQShortString propertyName) + { + return itemExists(propertyName); + } + + public boolean propertyExists(String propertyName) + { + return itemExists(propertyName); + } + + public boolean itemExists(AMQShortString propertyName) + { + checkPropertyName(propertyName); + initMapIfNecessary(); + + return _properties.containsKey(propertyName); + } + + public boolean itemExists(String string) + { + return itemExists(new AMQShortString(string)); + } + + public String toString() + { + initMapIfNecessary(); + + return _properties.toString(); + } + + private void checkPropertyName(AMQShortString propertyName) + { + if (propertyName == null) + { + throw new IllegalArgumentException("Property name must not be null"); + } + else if (propertyName.length() == 0) + { + throw new IllegalArgumentException("Property name must not be the empty string"); + } + + if (_strictAMQP) + { + checkIdentiferFormat(propertyName); + } + } + + protected static void checkIdentiferFormat(AMQShortString propertyName) + { + // AMQP Spec: 4.2.5.5 Field Tables + // Guidelines for implementers: + // * Field names MUST start with a letter, '$' or '#' and may continue with + // letters, '$' or '#', digits, or underlines, to a maximum length of 128 + // characters. + // * The server SHOULD validate field names and upon receiving an invalid + // field name, it SHOULD signal a connection exception with reply code + // 503 (syntax error). Conformance test: amq_wlp_table_01. + // * A peer MUST handle duplicate fields by using only the first instance. + + // AMQP length limit + if (propertyName.length() > 128) + { + throw new IllegalArgumentException("AMQP limits property names to 128 characters"); + } + + // AMQ start character + if (!(Character.isLetter(propertyName.charAt(0)) || (propertyName.charAt(0) == '$') + || (propertyName.charAt(0) == '#') || (propertyName.charAt(0) == '_'))) // Not official AMQP added for JMS. + { + throw new IllegalArgumentException("Identifier '" + propertyName + + "' does not start with a valid AMQP start character"); + } + } + + // ************************* Byte Buffer Processing + + public void writeToBuffer(ByteBuffer buffer) + { + final boolean trace = _logger.isDebugEnabled(); + + if (trace) + { + _logger.debug("FieldTable::writeToBuffer: Writing encoded length of " + getEncodedSize() + "..."); + if (_properties != null) + { + _logger.debug(_properties.toString()); + } + } + + EncodingUtils.writeUnsignedInteger(buffer, getEncodedSize()); + + putDataInBuffer(buffer); + } + + public byte[] getDataAsBytes() + { + final int encodedSize = (int) getEncodedSize(); + final ByteBuffer buffer = ByteBuffer.allocate(encodedSize); // FIXME XXX: Is cast a problem? + + putDataInBuffer(buffer); + + final byte[] result = new byte[encodedSize]; + buffer.flip(); + buffer.get(result); + buffer.release(); + + return result; + } + + public long getEncodedSize() + { + return _encodedSize; + } + + private void recalculateEncodedSize() + { + + int encodedSize = 0; + if (_properties != null) + { + for (Map.Entry<AMQShortString, AMQTypedValue> e : _properties.entrySet()) + { + encodedSize += EncodingUtils.encodedShortStringLength(e.getKey()); + encodedSize++; // the byte for the encoding Type + encodedSize += e.getValue().getEncodingSize(); + + } + } + + _encodedSize = encodedSize; + } + + public void addAll(FieldTable fieldTable) + { + initMapIfNecessary(); + _encodedForm = null; + _properties.putAll(fieldTable._properties); + recalculateEncodedSize(); + } + + public static Map<String, Object> convertToMap(final FieldTable fieldTable) + { + final Map<String, Object> map = new HashMap<String, Object>(); + + if(fieldTable != null) + { + fieldTable.processOverElements( + new FieldTableElementProcessor() + { + + public boolean processElement(String propertyName, AMQTypedValue value) + { + Object val = value.getValue(); + if(val instanceof AMQShortString) + { + val = val.toString(); + } + map.put(propertyName, val); + return true; + } + + public Object getResult() + { + return map; + } + }); + } + return map; + } + + + public static interface FieldTableElementProcessor + { + public boolean processElement(String propertyName, AMQTypedValue value); + + public Object getResult(); + } + + public Object processOverElements(FieldTableElementProcessor processor) + { + initMapIfNecessary(); + if (_properties != null) + { + for (Map.Entry<AMQShortString, AMQTypedValue> e : _properties.entrySet()) + { + boolean result = processor.processElement(e.getKey().toString(), e.getValue()); + if (!result) + { + break; + } + } + } + + return processor.getResult(); + + } + + public int size() + { + initMapIfNecessary(); + + return _properties.size(); + + } + + public boolean isEmpty() + { + return size() == 0; + } + + public boolean containsKey(AMQShortString key) + { + initMapIfNecessary(); + + return _properties.containsKey(key); + } + + public boolean containsKey(String key) + { + return containsKey(new AMQShortString(key)); + } + + public Set<String> keys() + { + initMapIfNecessary(); + Set<String> keys = new LinkedHashSet<String>(); + for (AMQShortString key : _properties.keySet()) + { + keys.add(key.toString()); + } + + return keys; + } + + public Iterator<Map.Entry<AMQShortString, AMQTypedValue>> iterator() + { + if(_encodedForm != null) + { + return new FieldTableIterator(_encodedForm.duplicate().rewind(),(int)_encodedSize); + } + else + { + initMapIfNecessary(); + return _properties.entrySet().iterator(); + } + } + + public Object get(String key) + { + return get(new AMQShortString(key)); + } + + public Object get(AMQShortString key) + { + return getObject(key); + } + + public Object put(AMQShortString key, Object value) + { + return setObject(key, value); + } + + public Object remove(String key) + { + + return remove(new AMQShortString(key)); + + } + + public Object remove(AMQShortString key) + { + AMQTypedValue val = removeKey(key); + + return (val == null) ? null : val.getValue(); + + } + + public AMQTypedValue removeKey(AMQShortString key) + { + initMapIfNecessary(); + _encodedForm = null; + AMQTypedValue value = _properties.remove(key); + if (value == null) + { + return null; + } + else + { + _encodedSize -= EncodingUtils.encodedShortStringLength(key); + _encodedSize--; + _encodedSize -= value.getEncodingSize(); + + return value; + } + + } + + public void clear() + { + initMapIfNecessary(); + _encodedForm = null; + _properties.clear(); + _encodedSize = 0; + } + + public Set<AMQShortString> keySet() + { + initMapIfNecessary(); + + return _properties.keySet(); + } + + private void putDataInBuffer(ByteBuffer buffer) + { + + if (_encodedForm != null) + { + 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) + { + final Iterator<Map.Entry<AMQShortString, AMQTypedValue>> it = _properties.entrySet().iterator(); + + // If there are values then write out the encoded Size... could check _encodedSize != 0 + // write out the total length, which we have kept up to date as data is added + + while (it.hasNext()) + { + 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(ByteBuffer buffer, long length) throws AMQFrameDecodingException + { + + final boolean trace = _logger.isDebugEnabled(); + if (length > 0) + { + + final int expectedRemaining = buffer.remaining() - (int) length; + + _properties = new LinkedHashMap<AMQShortString, AMQTypedValue>(INITIAL_HASHMAP_CAPACITY); + + do + { + + 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 (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(); + + return _properties.hashCode(); + } + + public boolean equals(Object o) + { + if (o == this) + { + return true; + } + + if (o == null) + { + return false; + } + + if (!(o instanceof FieldTable)) + { + return false; + } + + initMapIfNecessary(); + + FieldTable f = (FieldTable) o; + f.initMapIfNecessary(); + + return _properties.equals(f._properties); + } + + public static FieldTable convertToFieldTable(Map<String, Object> map) + { + if (map != null) + { + FieldTable table = new FieldTable(); + for(Map.Entry<String,Object> entry : map.entrySet()) + { + table.put(new AMQShortString(entry.getKey()), entry.getValue()); + } + + return table; + } + else + { + return null; + } + } + + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/FieldTableFactory.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/FieldTableFactory.java new file mode 100644 index 0000000000..e9d75137ef --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/FieldTableFactory.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ +package org.apache.qpid.framing; + +import org.apache.mina.common.ByteBuffer; + +public class FieldTableFactory +{ + public static FieldTable newFieldTable() + { + return new FieldTable(); + } + + public static FieldTable newFieldTable(ByteBuffer byteBuffer, long length) throws AMQFrameDecodingException + { + return new FieldTable(byteBuffer, length); + } + + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/HeartbeatBody.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/HeartbeatBody.java new file mode 100644 index 0000000000..18ab05ffa1 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/HeartbeatBody.java @@ -0,0 +1,79 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.qpid.protocol.AMQVersionAwareProtocolSession; +import org.apache.qpid.AMQException; + +public class HeartbeatBody implements AMQBody +{ + public static final byte TYPE = 8; + public static final AMQFrame FRAME = new HeartbeatBody().toFrame(); + + public HeartbeatBody() + { + + } + + public HeartbeatBody(ByteBuffer buffer, long size) + { + if(size > 0) + { + //allow other implementations to have a payload, but ignore it: + buffer.skip((int) size); + } + } + + public byte getFrameType() + { + return TYPE; + } + + public int getSize() + { + return 0;//heartbeats we generate have no payload + } + + public void writePayload(ByteBuffer buffer) + { + } + + public void handle(final int channelId, final AMQVersionAwareProtocolSession session) + throws AMQException + { + session.heartbeatBodyReceived(channelId, this); + } + + protected void populateFromBuffer(ByteBuffer buffer, long size) throws AMQFrameDecodingException + { + if(size > 0) + { + //allow other implementations to have a payload, but ignore it: + buffer.skip((int) size); + } + } + + public AMQFrame toFrame() + { + return new AMQFrame(0, this); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/HeartbeatBodyFactory.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/HeartbeatBodyFactory.java new file mode 100644 index 0000000000..c7ada708dc --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/HeartbeatBodyFactory.java @@ -0,0 +1,31 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 class HeartbeatBodyFactory implements BodyFactory +{ + public AMQBody createBody(ByteBuffer in, long bodySize) throws AMQFrameDecodingException + { + return new HeartbeatBody(); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/ProtocolInitiation.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/ProtocolInitiation.java new file mode 100644 index 0000000000..fb3dd89717 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/ProtocolInitiation.java @@ -0,0 +1,223 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.qpid.AMQException; + +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.util.Arrays; + +public class ProtocolInitiation extends AMQDataBlock implements EncodableAMQDataBlock +{ + + // TODO: generate these constants automatically from the xml protocol spec file + private static final byte[] AMQP_HEADER = new byte[]{(byte)'A',(byte)'M',(byte)'Q',(byte)'P'}; + + private static final byte CURRENT_PROTOCOL_CLASS = 1; + private static final byte TCP_PROTOCOL_INSTANCE = 1; + + public final byte[] _protocolHeader; + public final byte _protocolClass; + public final byte _protocolInstance; + public final byte _protocolMajor; + public final byte _protocolMinor; + + +// public ProtocolInitiation() {} + + public ProtocolInitiation(byte[] protocolHeader, byte protocolClass, byte protocolInstance, byte protocolMajor, byte protocolMinor) + { + _protocolHeader = protocolHeader; + _protocolClass = protocolClass; + _protocolInstance = protocolInstance; + _protocolMajor = protocolMajor; + _protocolMinor = protocolMinor; + } + + public ProtocolInitiation(ProtocolVersion pv) + { + this(AMQP_HEADER, + pv.equals(ProtocolVersion.v0_91) ? 0 : CURRENT_PROTOCOL_CLASS, + pv.equals(ProtocolVersion.v0_91) ? 0 : TCP_PROTOCOL_INSTANCE, + pv.equals(ProtocolVersion.v0_91) ? 9 : pv.getMajorVersion(), + pv.equals(ProtocolVersion.v0_91) ? 1 : pv.getMinorVersion()); + } + + public ProtocolInitiation(ByteBuffer in) + { + _protocolHeader = new byte[4]; + in.get(_protocolHeader); + + _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(ByteBuffer buffer) + { + + buffer.put(_protocolHeader); + buffer.put(_protocolClass); + buffer.put(_protocolInstance); + buffer.put(_protocolMajor); + buffer.put(_protocolMinor); + } + + public boolean equals(Object o) + { + if (!(o instanceof ProtocolInitiation)) + { + return false; + } + + ProtocolInitiation pi = (ProtocolInitiation) o; + if (pi._protocolHeader == null) + { + return false; + } + + if (_protocolHeader.length != pi._protocolHeader.length) + { + return false; + } + + for (int i = 0; i < _protocolHeader.length; i++) + { + if (_protocolHeader[i] != pi._protocolHeader[i]) + { + return false; + } + } + + return (_protocolClass == pi._protocolClass && + _protocolInstance == pi._protocolInstance && + _protocolMajor == pi._protocolMajor && + _protocolMinor == pi._protocolMinor); + } + + @Override + public int hashCode() + { + int result = _protocolHeader != null ? Arrays.hashCode(_protocolHeader) : 0; + result = 31 * result + (int) _protocolClass; + result = 31 * result + (int) _protocolInstance; + result = 31 * result + (int) _protocolMajor; + result = 31 * result + (int) _protocolMinor; + return result; + } + + public static class Decoder //implements MessageDecoder + { + /** + * + * @param in input buffer + * @return true if we have enough data to decode the PI frame fully, false if more + * data is required + */ + public boolean decodable(ByteBuffer in) + { + return (in.remaining() >= 8); + } + + } + + public ProtocolVersion checkVersion() throws AMQException + { + + if(_protocolHeader.length != 4) + { + throw new AMQProtocolHeaderException("Protocol header should have exactly four octets", null); + } + for(int i = 0; i < 4; i++) + { + if(_protocolHeader[i] != AMQP_HEADER[i]) + { + try + { + throw new AMQProtocolHeaderException("Protocol header is not correct: Got " + new String(_protocolHeader,"ISO-8859-1") + " should be: " + new String(AMQP_HEADER, "ISO-8859-1"), null); + } + catch (UnsupportedEncodingException e) + { + + } + } + } + + ProtocolVersion pv; + + // Hack for 0-9-1 which changed how the header was defined + if(_protocolInstance == 0 && _protocolMajor == 9 && _protocolMinor == 1) + { + pv = ProtocolVersion.v0_91; + if (_protocolClass != 0) + { + throw new AMQProtocolClassException("Protocol class " + 0 + " was expected; received " + + _protocolClass, null); + } + } + else if (_protocolClass != CURRENT_PROTOCOL_CLASS) + { + throw new AMQProtocolClassException("Protocol class " + CURRENT_PROTOCOL_CLASS + " was expected; received " + + _protocolClass, null); + } + else if (_protocolInstance != TCP_PROTOCOL_INSTANCE) + { + throw new AMQProtocolInstanceException("Protocol instance " + TCP_PROTOCOL_INSTANCE + " was expected; received " + + _protocolInstance, null); + } + else + { + pv = new ProtocolVersion(_protocolMajor, _protocolMinor); + } + + + if (!pv.isSupported()) + { + // TODO: add list of available versions in list to msg... + throw new AMQProtocolVersionException("Protocol version " + + _protocolMajor + "." + _protocolMinor + " not suppoerted by this version of the Qpid broker.", null); + } + return pv; + } + + public String toString() + { + StringBuffer buffer = new StringBuffer(new String(_protocolHeader)); + buffer.append(Integer.toHexString(_protocolClass)); + buffer.append(Integer.toHexString(_protocolInstance)); + buffer.append(Integer.toHexString(_protocolMajor)); + buffer.append(Integer.toHexString(_protocolMinor)); + return buffer.toString(); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/SmallCompositeAMQDataBlock.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/SmallCompositeAMQDataBlock.java new file mode 100644 index 0000000000..bd763599b0 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/SmallCompositeAMQDataBlock.java @@ -0,0 +1,98 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.framing; + +import org.apache.mina.common.ByteBuffer; + +public class SmallCompositeAMQDataBlock extends AMQDataBlock implements EncodableAMQDataBlock +{ + private AMQDataBlock _firstFrame; + + private AMQDataBlock _block; + + public SmallCompositeAMQDataBlock(AMQDataBlock block) + { + _block = block; + } + + /** + * The encoded block will be logically first before the AMQDataBlocks which are encoded + * into the buffer afterwards. + * @param encodedBlock already-encoded data + * @param block a block to be encoded. + */ + public SmallCompositeAMQDataBlock(AMQDataBlock encodedBlock, AMQDataBlock block) + { + this(block); + _firstFrame = encodedBlock; + } + + public AMQDataBlock getBlock() + { + return _block; + } + + public AMQDataBlock getFirstFrame() + { + return _firstFrame; + } + + public long getSize() + { + long frameSize = _block.getSize(); + + if (_firstFrame != null) + { + + frameSize += _firstFrame.getSize(); + } + return frameSize; + } + + public void writePayload(ByteBuffer buffer) + { + if (_firstFrame != null) + { + _firstFrame.writePayload(buffer); + } + _block.writePayload(buffer); + + } + + public String toString() + { + if (_block == null) + { + return "No blocks contained in composite frame"; + } + else + { + StringBuilder buf = new StringBuilder(this.getClass().getName()); + buf.append("{encodedBlock=").append(_firstFrame); + + buf.append(" _block=[").append(_block.toString()).append("]"); + + buf.append("}"); + return buf.toString(); + } + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/VersionSpecificRegistry.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/VersionSpecificRegistry.java new file mode 100644 index 0000000000..76c154581d --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/VersionSpecificRegistry.java @@ -0,0 +1,198 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.qpid.framing.abstraction.ProtocolVersionMethodConverter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class VersionSpecificRegistry +{ + private static final Logger _log = LoggerFactory.getLogger(VersionSpecificRegistry.class); + + private final byte _protocolMajorVersion; + private final byte _protocolMinorVersion; + + private static final int DEFAULT_MAX_CLASS_ID = 200; + private static final int DEFAULT_MAX_METHOD_ID = 50; + + private AMQMethodBodyInstanceFactory[][] _registry = new AMQMethodBodyInstanceFactory[DEFAULT_MAX_CLASS_ID][]; + + private ProtocolVersionMethodConverter _protocolVersionConverter; + + public VersionSpecificRegistry(byte major, byte minor) + { + _protocolMajorVersion = major; + _protocolMinorVersion = minor; + + _protocolVersionConverter = loadProtocolVersionConverters(major, minor); + } + + private static ProtocolVersionMethodConverter loadProtocolVersionConverters(byte protocolMajorVersion, + byte protocolMinorVersion) + { + try + { + Class<ProtocolVersionMethodConverter> versionMethodConverterClass = + (Class<ProtocolVersionMethodConverter>) Class.forName("org.apache.qpid.framing.MethodConverter_" + + protocolMajorVersion + "_" + protocolMinorVersion); + + return versionMethodConverterClass.newInstance(); + + } + catch (ClassNotFoundException e) + { + _log.warn("Could not find protocol conversion classes for " + protocolMajorVersion + "-" + protocolMinorVersion); + if (protocolMinorVersion != 0) + { + protocolMinorVersion--; + + return loadProtocolVersionConverters(protocolMajorVersion, protocolMinorVersion); + } + else if (protocolMajorVersion != 0) + { + protocolMajorVersion--; + + return loadProtocolVersionConverters(protocolMajorVersion, protocolMinorVersion); + } + else + { + return null; + } + + } + catch (IllegalAccessException e) + { + throw new IllegalStateException("Unable to load protocol version converter: ", e); + } + catch (InstantiationException e) + { + throw new IllegalStateException("Unable to load protocol version converter: ", e); + } + } + + public byte getProtocolMajorVersion() + { + return _protocolMajorVersion; + } + + public byte getProtocolMinorVersion() + { + return _protocolMinorVersion; + } + + public AMQMethodBodyInstanceFactory getMethodBody(final short classID, final short methodID) + { + try + { + return _registry[classID][methodID]; + } + catch (IndexOutOfBoundsException e) + { + return null; + } + catch (NullPointerException e) + { + return null; + } + } + + public void registerMethod(final short classID, final short methodID, final AMQMethodBodyInstanceFactory instanceFactory) + { + if (_registry.length <= classID) + { + AMQMethodBodyInstanceFactory[][] oldRegistry = _registry; + _registry = new AMQMethodBodyInstanceFactory[classID + 1][]; + System.arraycopy(oldRegistry, 0, _registry, 0, oldRegistry.length); + } + + if (_registry[classID] == null) + { + _registry[classID] = + new AMQMethodBodyInstanceFactory[(methodID > DEFAULT_MAX_METHOD_ID) ? (methodID + 1) + : (DEFAULT_MAX_METHOD_ID + 1)]; + } + else if (_registry[classID].length <= methodID) + { + AMQMethodBodyInstanceFactory[] oldMethods = _registry[classID]; + _registry[classID] = new AMQMethodBodyInstanceFactory[methodID + 1]; + System.arraycopy(oldMethods, 0, _registry[classID], 0, oldMethods.length); + } + + _registry[classID][methodID] = instanceFactory; + + } + + public AMQMethodBody get(short classID, short methodID, ByteBuffer in, long size) throws AMQFrameDecodingException + { + AMQMethodBodyInstanceFactory bodyFactory; + try + { + bodyFactory = _registry[classID][methodID]; + } + catch (NullPointerException e) + { + throw new AMQFrameDecodingException(null, "Class " + classID + " unknown in AMQP version " + + _protocolMajorVersion + "-" + _protocolMinorVersion + " (while trying to decode class " + classID + + " method " + methodID + ".", e); + } + catch (IndexOutOfBoundsException e) + { + if (classID >= _registry.length) + { + throw new AMQFrameDecodingException(null, "Class " + classID + " unknown in AMQP version " + + _protocolMajorVersion + "-" + _protocolMinorVersion + " (while trying to decode class " + classID + + " method " + methodID + ".", e); + + } + else + { + throw new AMQFrameDecodingException(null, "Method " + methodID + " unknown in AMQP version " + + _protocolMajorVersion + "-" + _protocolMinorVersion + " (while trying to decode class " + classID + + " method " + methodID + ".", e); + + } + } + + if (bodyFactory == null) + { + throw new AMQFrameDecodingException(null, "Method " + methodID + " unknown in AMQP version " + + _protocolMajorVersion + "-" + _protocolMinorVersion + " (while trying to decode class " + classID + + " method " + methodID + ".", null); + } + + return bodyFactory.newInstance( in, size); + + } + + public ProtocolVersionMethodConverter getProtocolVersionMethodConverter() + { + return _protocolVersionConverter; + } + + public void configure() + { + _protocolVersionConverter.configure(); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/abstraction/AbstractMethodConverter.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/abstraction/AbstractMethodConverter.java new file mode 100644 index 0000000000..1d7c05e9cc --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/abstraction/AbstractMethodConverter.java @@ -0,0 +1,47 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.abstraction; + +public abstract class AbstractMethodConverter implements ProtocolVersionMethodConverter +{ + private final byte _protocolMajorVersion; + + + private final byte _protocolMinorVersion; + + public AbstractMethodConverter(byte major, byte minor) + { + _protocolMajorVersion = major; + _protocolMinorVersion = minor; + } + + + public final byte getProtocolMajorVersion() + { + return _protocolMajorVersion; + } + + public final byte getProtocolMinorVersion() + { + return _protocolMinorVersion; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/abstraction/ContentChunk.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/abstraction/ContentChunk.java new file mode 100644 index 0000000000..0695349f76 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/abstraction/ContentChunk.java @@ -0,0 +1,32 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.abstraction; + +import org.apache.mina.common.ByteBuffer; + +public interface ContentChunk +{ + int getSize(); + ByteBuffer getData(); + + void reduceToFit(); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/abstraction/MessagePublishInfo.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/abstraction/MessagePublishInfo.java new file mode 100644 index 0000000000..a96bdcc171 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/abstraction/MessagePublishInfo.java @@ -0,0 +1,38 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.framing.abstraction; + +import org.apache.qpid.framing.AMQShortString; + +public interface MessagePublishInfo +{ + + public AMQShortString getExchange(); + + public void setExchange(AMQShortString exchange); + + public boolean isImmediate(); + + public boolean isMandatory(); + + public AMQShortString getRoutingKey(); + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/abstraction/MessagePublishInfoConverter.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/abstraction/MessagePublishInfoConverter.java new file mode 100644 index 0000000000..01d1a8a17b --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/abstraction/MessagePublishInfoConverter.java @@ -0,0 +1,32 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.abstraction; + +import org.apache.qpid.framing.AMQMethodBody; + + +public interface MessagePublishInfoConverter +{ + public MessagePublishInfo convertToInfo(AMQMethodBody body); + public AMQMethodBody convertToBody(MessagePublishInfo info); + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/abstraction/MessagePublishInfoImpl.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/abstraction/MessagePublishInfoImpl.java new file mode 100644 index 0000000000..e3d5da73da --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/abstraction/MessagePublishInfoImpl.java @@ -0,0 +1,85 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.abstraction; + +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.framing.AMQShortString; + +public class MessagePublishInfoImpl implements MessagePublishInfo +{ + private AMQShortString _exchange; + private boolean _immediate; + private boolean _mandatory; + private AMQShortString _routingKey; + + public MessagePublishInfoImpl() + { + } + + public MessagePublishInfoImpl(AMQShortString exchange, boolean immediate, boolean mandatory, + AMQShortString routingKey) + { + _exchange = exchange; + _immediate = immediate; + _mandatory = mandatory; + _routingKey = routingKey; + } + + public AMQShortString getExchange() + { + return _exchange; + } + + public void setExchange(AMQShortString exchange) + { + _exchange = exchange; + } + + public boolean isImmediate() + { + return _immediate; + } + + public void setImmediate(boolean immedate) + { + _immediate = immedate; + } + + public boolean isMandatory() + { + return _mandatory; + } + + public void setMandatory(boolean mandatory) + { + _mandatory = mandatory; + } + + public AMQShortString getRoutingKey() + { + return _routingKey; + } + + public void setRoutingKey(AMQShortString routingKey) + { + _routingKey = routingKey; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/abstraction/ProtocolVersionMethodConverter.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/abstraction/ProtocolVersionMethodConverter.java new file mode 100644 index 0000000000..7544d9b7e7 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/abstraction/ProtocolVersionMethodConverter.java @@ -0,0 +1,36 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.abstraction; + +import org.apache.qpid.framing.AMQBody; + +import java.nio.ByteBuffer; + +public interface ProtocolVersionMethodConverter extends MessagePublishInfoConverter +{ + AMQBody convertToBody(ContentChunk contentBody); + ContentChunk convertToContentChunk(AMQBody body); + + void configure(); + + AMQBody convertToBody(ByteBuffer buf); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/AMQMethodBody_0_9.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/AMQMethodBody_0_9.java new file mode 100644 index 0000000000..8d51343507 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/AMQMethodBody_0_9.java @@ -0,0 +1,37 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.amqp_0_9; + +public abstract class AMQMethodBody_0_9 extends org.apache.qpid.framing.AMQMethodBodyImpl +{ + + public byte getMajor() + { + return 0; + } + + public byte getMinor() + { + return 9; + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/MethodConverter_0_9.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/MethodConverter_0_9.java new file mode 100644 index 0000000000..1c4a29b106 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/MethodConverter_0_9.java @@ -0,0 +1,134 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.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 +{ + private int _basicPublishClassId; + private int _basicPublishMethodId; + + public MethodConverter_0_9() + { + super((byte)0,(byte)9); + + + } + + public AMQBody convertToBody(ContentChunk contentChunk) + { + if(contentChunk instanceof ContentChunk_0_9) + { + return ((ContentChunk_0_9)contentChunk).toBody(); + } + else + { + return new ContentBody(contentChunk.getData()); + } + } + + public ContentChunk convertToContentChunk(AMQBody body) + { + final ContentBody contentBodyChunk = (ContentBody) body; + + return new ContentChunk_0_9(contentBodyChunk); + + } + + public void configure() + { + + _basicPublishClassId = org.apache.qpid.framing.amqp_0_9.BasicPublishBodyImpl.CLASS_ID; + _basicPublishMethodId = BasicPublishBodyImpl.METHOD_ID; + + } + + public AMQBody convertToBody(java.nio.ByteBuffer buf) + { + return new ContentBody(ByteBuffer.wrap(buf)); + } + + public MessagePublishInfo convertToInfo(AMQMethodBody methodBody) + { + final BasicPublishBody publishBody = ((BasicPublishBody) methodBody); + + final AMQShortString exchange = publishBody.getExchange(); + final AMQShortString routingKey = publishBody.getRoutingKey(); + + return new MessagePublishInfoImpl(exchange, + publishBody.getImmediate(), + publishBody.getMandatory(), + routingKey); + + } + + public AMQMethodBody convertToBody(MessagePublishInfo info) + { + + return new BasicPublishBodyImpl(0, + info.getExchange(), + info.getRoutingKey(), + info.isMandatory(), + info.isImmediate()) ; + + } + + private static class ContentChunk_0_9 implements ContentChunk + { + private final ContentBody _contentBodyChunk; + + public ContentChunk_0_9(final ContentBody contentBodyChunk) + { + _contentBodyChunk = contentBodyChunk; + } + + public int getSize() + { + return _contentBodyChunk.getSize(); + } + + public ByteBuffer getData() + { + return _contentBodyChunk.payload; + } + + public void reduceToFit() + { + _contentBodyChunk.reduceBufferToFit(); + } + + public AMQBody toBody() + { + return _contentBodyChunk; + } + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/amqp_0_91/AMQMethodBody_0_91.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/amqp_0_91/AMQMethodBody_0_91.java new file mode 100644 index 0000000000..60b8a7e1a6 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/amqp_0_91/AMQMethodBody_0_91.java @@ -0,0 +1,37 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.amqp_0_91; + +public abstract class AMQMethodBody_0_91 extends org.apache.qpid.framing.AMQMethodBodyImpl +{ + + public byte getMajor() + { + return 0; + } + + public byte getMinor() + { + return 91; + } + +}
\ No newline at end of file diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/amqp_0_91/MethodConverter_0_91.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/amqp_0_91/MethodConverter_0_91.java new file mode 100644 index 0000000000..6e330574bc --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/amqp_0_91/MethodConverter_0_91.java @@ -0,0 +1,132 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.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; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.framing.abstraction.MessagePublishInfoImpl; +import org.apache.qpid.framing.*; + +public class MethodConverter_0_91 extends AbstractMethodConverter implements ProtocolVersionMethodConverter +{ + private int _basicPublishClassId; + private int _basicPublishMethodId; + + public MethodConverter_0_91() + { + super((byte)0,(byte)9); + + + } + + public AMQBody convertToBody(ContentChunk contentChunk) + { + if(contentChunk instanceof ContentChunk_0_9) + { + return ((ContentChunk_0_9)contentChunk).toBody(); + } + else + { + return new ContentBody(contentChunk.getData()); + } + } + + public ContentChunk convertToContentChunk(AMQBody body) + { + final ContentBody contentBodyChunk = (ContentBody) body; + + return new ContentChunk_0_9(contentBodyChunk); + + } + + public void configure() + { + + _basicPublishClassId = BasicPublishBodyImpl.CLASS_ID; + _basicPublishMethodId = BasicPublishBodyImpl.METHOD_ID; + + } + + public AMQBody convertToBody(java.nio.ByteBuffer buf) + { + return new ContentBody(ByteBuffer.wrap(buf)); + } + + public MessagePublishInfo convertToInfo(AMQMethodBody methodBody) + { + final BasicPublishBody publishBody = ((BasicPublishBody) methodBody); + + final AMQShortString exchange = publishBody.getExchange(); + final AMQShortString routingKey = publishBody.getRoutingKey(); + + return new MessagePublishInfoImpl(exchange, + publishBody.getImmediate(), + publishBody.getMandatory(), + routingKey); + + } + + public AMQMethodBody convertToBody(MessagePublishInfo info) + { + + return new BasicPublishBodyImpl(0, + info.getExchange(), + info.getRoutingKey(), + info.isMandatory(), + info.isImmediate()) ; + + } + + private static class ContentChunk_0_9 implements ContentChunk + { + private final ContentBody _contentBodyChunk; + + public ContentChunk_0_9(final ContentBody contentBodyChunk) + { + _contentBodyChunk = contentBodyChunk; + } + + public int getSize() + { + return _contentBodyChunk.getSize(); + } + + public ByteBuffer getData() + { + return _contentBodyChunk.payload; + } + + public void reduceToFit() + { + _contentBodyChunk.reduceBufferToFit(); + } + + public AMQBody toBody() + { + return _contentBodyChunk; + } + } +}
\ No newline at end of file diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/AMQMethodBody_8_0.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/AMQMethodBody_8_0.java new file mode 100644 index 0000000000..35645854c0 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/AMQMethodBody_8_0.java @@ -0,0 +1,40 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.amqp_8_0; + +public abstract class AMQMethodBody_8_0 extends org.apache.qpid.framing.AMQMethodBodyImpl +{ + + public byte getMajor() + { + return 8; + } + + public byte getMinor() + { + return 0; + } + + + + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/MethodConverter_8_0.java b/qpid/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/MethodConverter_8_0.java new file mode 100644 index 0000000000..c87820b9b2 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/MethodConverter_8_0.java @@ -0,0 +1,113 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.amqp_8_0; + +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.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; + private int _basicPublishMethodId; + + public MethodConverter_8_0() + { + super((byte)8,(byte)0); + + + } + + public AMQBody convertToBody(ContentChunk contentChunk) + { + return new ContentBody(contentChunk.getData()); + } + + public ContentChunk convertToContentChunk(AMQBody body) + { + final ContentBody contentBodyChunk = (ContentBody) body; + + return new ContentChunk() + { + + public int getSize() + { + return contentBodyChunk.getSize(); + } + + public ByteBuffer getData() + { + return contentBodyChunk.payload; + } + + public void reduceToFit() + { + contentBodyChunk.reduceBufferToFit(); + } + }; + + } + + public void configure() + { + + _basicPublishClassId = BasicPublishBodyImpl.CLASS_ID; + _basicPublishMethodId = BasicPublishBodyImpl.METHOD_ID; + + } + + public AMQBody convertToBody(java.nio.ByteBuffer buf) + { + return new ContentBody(ByteBuffer.wrap(buf)); + } + + public MessagePublishInfo convertToInfo(AMQMethodBody methodBody) + { + final BasicPublishBody publishBody = ((BasicPublishBody) methodBody); + + final AMQShortString exchange = publishBody.getExchange(); + final AMQShortString routingKey = publishBody.getRoutingKey(); + + return new MessagePublishInfoImpl(exchange == null ? null : exchange.intern(), + publishBody.getImmediate(), + publishBody.getMandatory(), + routingKey == null ? null : routingKey.intern()); + + } + + public AMQMethodBody convertToBody(MessagePublishInfo info) + { + + return new BasicPublishBodyImpl(0, + info.getExchange(), + info.getRoutingKey(), + info.isMandatory(), + info.isImmediate()) ; + + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/messaging/Address.java b/qpid/java/common/src/main/java/org/apache/qpid/messaging/Address.java new file mode 100644 index 0000000000..2c7fe7b8ed --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/messaging/Address.java @@ -0,0 +1,75 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.messaging; + +import java.util.Map; + +import org.apache.qpid.messaging.util.AddressParser; + +import static org.apache.qpid.messaging.util.PyPrint.pprint; + + +/** + * Address + * + */ + +public class Address +{ + + public static Address parse(String address) + { + return new AddressParser(address).parse(); + } + + private String name; + private String subject; + private Map options; + + public Address(String name, String subject, Map options) + { + this.name = name; + this.subject = subject; + this.options = options; + } + + public String getName() + { + return name; + } + + public String getSubject() + { + return subject; + } + + public Map getOptions() + { + return options; + } + + public String toString() + { + return String.format("%s/%s; %s", pprint(name), pprint(subject), + pprint(options)); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/AddressParser.java b/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/AddressParser.java new file mode 100644 index 0000000000..7b31436ba0 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/AddressParser.java @@ -0,0 +1,380 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.messaging.util; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.qpid.messaging.Address; + + +/** + * AddressParser + * + */ + +public class AddressParser extends Parser +{ + + private static Lexicon lxi = new Lexicon(); + + private static Token.Type LBRACE = lxi.define("LBRACE", "\\{"); + private static Token.Type RBRACE = lxi.define("RBRACE", "\\}"); + private static Token.Type LBRACK = lxi.define("LBRACK", "\\["); + private static Token.Type RBRACK = lxi.define("RBRACK", "\\]"); + private static Token.Type COLON = lxi.define("COLON", ":"); + private static Token.Type SEMI = lxi.define("SEMI", ";"); + private static Token.Type SLASH = lxi.define("SLASH", "/"); + private static Token.Type COMMA = lxi.define("COMMA", ","); + private static Token.Type NUMBER = lxi.define("NUMBER", "[+-]?[0-9]*\\.?[0-9]+"); + private static Token.Type TRUE = lxi.define("TRUE", "True"); + private static Token.Type FALSE = lxi.define("FALSE", "False"); + private static Token.Type ID = lxi.define("ID", "[a-zA-Z_](?:[a-zA-Z0-9_-]*[a-zA-Z0-9_])?"); + private static Token.Type STRING = lxi.define("STRING", "\"(?:[^\\\"]|\\.)*\"|'(?:[^\\']|\\.)*'"); + private static Token.Type ESC = lxi.define("ESC", "\\\\[^ux]|\\\\x[0-9a-fA-F][0-9a-fA-F]|\\\\u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]"); + private static Token.Type SYM = lxi.define("SYM", "[.#*%@$^!+-]"); + private static Token.Type WSPACE = lxi.define("WSPACE", "[\\s]+"); + private static Token.Type EOF = lxi.eof("EOF"); + + private static Lexer LEXER = lxi.compile(); + + public static List<Token> lex(String input) + { + return LEXER.lex(input); + } + + static List<Token> wlex(String input) + { + List<Token> tokens = new ArrayList<Token>(); + for (Token t : lex(input)) + { + if (t.getType() != WSPACE) + { + tokens.add(t); + } + } + return tokens; + } + + static String unquote(String st, Token tok) + { + StringBuilder result = new StringBuilder(); + for (int i = 1; i < st.length() - 1; i++) + { + char ch = st.charAt(i); + if (ch == '\\') + { + char code = st.charAt(i+1); + switch (code) + { + case '\n': + break; + case '\\': + result.append('\\'); + break; + case '\'': + result.append('\''); + break; + case '"': + result.append('"'); + break; + case 'a': + result.append((char) 0x07); + break; + case 'b': + result.append((char) 0x08); + break; + case 'f': + result.append('\f'); + break; + case 'n': + result.append('\n'); + break; + case 'r': + result.append('\r'); + break; + case 't': + result.append('\t'); + break; + case 'u': + result.append(decode(st.substring(i+2, i+6))); + i += 4; + break; + case 'v': + result.append((char) 0x0b); + break; + case 'o': + result.append(decode(st.substring(i+2, i+4), 8)); + i += 2; + break; + case 'x': + result.append(decode(st.substring(i+2, i+4))); + i += 2; + break; + default: + throw new ParseError(tok); + } + i += 1; + } + else + { + result.append(ch); + } + } + + return result.toString(); + } + + static char[] decode(String hex) + { + return decode(hex, 16); + } + + static char[] decode(String code, int radix) + { + return Character.toChars(Integer.parseInt(code, radix)); + } + + static String tok2str(Token tok) + { + Token.Type type = tok.getType(); + String value = tok.getValue(); + + if (type == STRING) + { + return unquote(value, tok); + } + else if (type == ESC) + { + if (value.charAt(1) == 'x' || value.charAt(1) == 'u') + { + return new String(decode(value.substring(2))); + } + else + { + return value.substring(1); + } + } + else + { + return value; + } + } + + static Object tok2obj(Token tok) + { + Token.Type type = tok.getType(); + String value = tok.getValue(); + if (type == STRING) + { + return unquote(value, tok); + } + else if (type == NUMBER) + { + if (value.indexOf('.') >= 0) + { + return Double.valueOf(value); + } + else + { + return Integer.decode(value); + } + } + else if (type == TRUE) + { + return true; + } + else if (type == FALSE) + { + return false; + } + else + { + return value; + } + } + + static String toks2str(List<Token> toks) + { + if (toks.size() > 0) + { + StringBuilder result = new StringBuilder(); + for (Token t : toks) + { + result.append(tok2str(t)); + } + return result.toString(); + } + else + { + return null; + } + } + + public AddressParser(String input) + { + super(wlex(input)); + } + + public Address parse() + { + Address result = address(); + eat(EOF); + return result; + } + + public Address address() + { + String name = toks2str(eat_until(SLASH, SEMI, EOF)); + + if (name == null) + { + throw new ParseError(next()); + } + + String subject; + if (matches(SLASH)) + { + eat(SLASH); + subject = toks2str(eat_until(SEMI, EOF)); + } + else + { + subject = null; + } + + Map options; + if (matches(SEMI)) + { + eat(SEMI); + options = map(); + } + else + { + options = null; + } + + return new Address(name, subject, options); + } + + public Map<Object,Object> map() + { + eat(LBRACE); + + Map<Object,Object> result = new HashMap<Object,Object>(); + while (true) + { + if (matches(NUMBER, STRING, ID, LBRACE, LBRACK)) + { + keyval(result); + if (matches(COMMA)) + { + eat(COMMA); + } + else if (matches(RBRACE)) + { + break; + } + else + { + throw new ParseError(next(), COMMA, RBRACE); + } + } + else if (matches(RBRACE)) + { + break; + } + else + { + throw new ParseError(next(), NUMBER, STRING, ID, LBRACE, LBRACK, + RBRACE); + } + } + + eat(RBRACE); + return result; + } + + void keyval(Map<Object,Object> map) + { + Object key = value(); + eat(COLON); + Object val = value(); + map.put(key, val); + } + + Object value() + { + if (matches(NUMBER, STRING, ID, TRUE, FALSE)) + { + return tok2obj(eat()); + } + else if (matches(LBRACE)) + { + return map(); + } + else if (matches(LBRACK)) + { + return list(); + } + else + { + throw new ParseError(next(), NUMBER, STRING, ID, LBRACE, LBRACK); + } + } + + List<Object> list() + { + eat(LBRACK); + + List<Object> result = new ArrayList<Object>(); + + while (true) + { + if (matches(RBRACK)) + { + break; + } + else + { + result.add(value()); + if (matches(COMMA)) + { + eat(COMMA); + } + else if (matches(RBRACK)) + { + break; + } + else + { + throw new ParseError(next(), COMMA, RBRACK); + } + } + } + + eat(RBRACK); + return result; + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/JAddr.java b/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/JAddr.java new file mode 100644 index 0000000000..93df052af1 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/JAddr.java @@ -0,0 +1,97 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.messaging.util; + +import java.io.InputStreamReader; + +import java.util.List; + +import org.apache.qpid.messaging.Address; +import org.apache.qpid.messaging.util.ParseError; +import org.apache.qpid.messaging.util.Token; + +import static org.apache.qpid.messaging.util.PyPrint.pprint; + + +/** + * JAddr + * + */ + +public class JAddr +{ + + public static final void main(String[] args) throws Exception + { + StringBuilder addr = new StringBuilder(); + InputStreamReader reader = new InputStreamReader(System.in); + + char[] buf = new char[1024]; + while (true) + { + int n = reader.read(buf, 0, buf.length); + if (n < 0) + { + break; + } + addr.append(buf, 0, n); + } + + if ("parse".equals(args[0])) + { + try + { + Address address = Address.parse(addr.toString()); + System.out.println(pprint_address(address)); + } + catch (ParseError e) + { + System.out.println(String.format("ERROR: %s", e.getMessage())); + } + } + else + { + List<Token> tokens = AddressParser.lex(addr.toString()); + for (Token t : tokens) + { + String value = t.getValue(); + if (value != null) + { + value = value.replace("\\", "\\\\").replace("\n", "\\n"); + System.out.println(String.format("%s:%s:%s", t.getType(), t.getPosition(), value)); + } + else + { + System.out.println(String.format("%s:%s", t.getType(), t.getPosition())); + } + } + } + } + + private static String pprint_address(Address addr) + { + return String.format("NAME: %s\nSUBJECT: %s\nOPTIONS: %s", + pprint(addr.getName()), + pprint(addr.getSubject()), + pprint(addr.getOptions())); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/LexError.java b/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/LexError.java new file mode 100644 index 0000000000..b8d346dca4 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/LexError.java @@ -0,0 +1,37 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.messaging.util; + + +/** + * LexError + * + */ + +public class LexError extends RuntimeException +{ + + public LexError(String msg) + { + super(msg); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/Lexer.java b/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/Lexer.java new file mode 100644 index 0000000000..8226cc77cb --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/Lexer.java @@ -0,0 +1,84 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.messaging.util; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +/** + * Lexer + * + */ + +public class Lexer +{ + + private List<Token.Type> types; + private Token.Type eof; + private Pattern rexp; + + public Lexer(List<Token.Type> types, Token.Type eof, Pattern rexp) + { + this.types = types; + this.eof = eof; + this.rexp = rexp; + } + + public List<Token> lex(final String st) + { + List<Token> tokens = new ArrayList<Token>(); + + int position = 0; + Matcher m = rexp.matcher(st); + OUTER: while (position < st.length()) + { + m.region(position, st.length()); + if (m.lookingAt()) + { + for (int i = 1; i <= m.groupCount(); i++) + { + String value = m.group(i); + if (value != null) + { + tokens.add(new Token(types.get(i-1), value, st, m.start(i))); + position = m.end(i); + continue OUTER; + } + } + throw new RuntimeException("no group matched"); + } + else + { + throw new LexError("unrecognized characters line:" + LineInfo.get(st, position)); + } + } + + tokens.add(new Token(eof, null, st, position)); + + return tokens; + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/Lexicon.java b/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/Lexicon.java new file mode 100644 index 0000000000..9ab610f37a --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/Lexicon.java @@ -0,0 +1,103 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.messaging.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + + +/** + * Lexicon + * + */ + +public class Lexicon +{ + + private List<Token.Type> types; + private Token.Type eof; + + public Lexicon() + { + this.types = new ArrayList<Token.Type>(); + this.eof = null; + } + + public Token.Type define(String name, String pattern) + { + Token.Type t = new Token.Type(name, pattern); + types.add(t); + return t; + } + + public Token.Type eof(String name) + { + Token.Type t = new Token.Type(name, null); + eof = t; + return t; + } + + public Lexer compile() + { + StringBuilder joined = new StringBuilder(); + for (Token.Type t : types) + { + if (joined.length() > 0) + { + joined.append('|'); + } + joined.append('(').append(t.getPattern()).append(')'); + } + Pattern rexp = Pattern.compile(joined.toString()); + return new Lexer(new ArrayList<Token.Type>(types), eof, rexp); + } + + public static final void main(String[] args) + { + StringBuilder input = new StringBuilder(); + for (String a : args) + { + if (input.length() > 0) + { + input.append(" "); + } + + input.append(a); + } + + Lexicon lxi = new Lexicon(); + lxi.define("FOR", "for"); + lxi.define("IF", "if"); + lxi.define("LPAREN", "\\("); + lxi.define("RPAREN", "\\)"); + lxi.define("ID", "[\\S]+"); + lxi.define("WSPACE", "[\\s]+"); + lxi.eof("EOF"); + Lexer lx = lxi.compile(); + + for (Token t : lx.lex(input.toString())) + { + System.out.println(t); + } + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/LineInfo.java b/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/LineInfo.java new file mode 100644 index 0000000000..4952fc38a3 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/LineInfo.java @@ -0,0 +1,93 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.messaging.util; + + +/** + * LineInfo + * + */ + +public class LineInfo +{ + + public static LineInfo get(String st, int position) + { + int idx = 0; + int line = 1; + int column = 0; + int line_pos = 0; + while (idx < position) + { + if (st.charAt(idx) == '\n') + { + line += 1; + column = 0; + line_pos = idx; + } + + column += 1; + idx += 1; + } + + int end = st.indexOf('\n', line_pos); + if (end < 0) + { + end = st.length(); + } + + String text = st.substring(line_pos, end); + + return new LineInfo(line, column, text); + } + + private int line; + private int column; + private String text; + + public LineInfo(int line, int column, String text) + { + this.line = line; + this.column = column; + this.text = text; + } + + public int getLine() + { + return line; + } + + public int getColumn() + { + return column; + } + + public String getText() + { + return text; + } + + public String toString() + { + return String.format("%s,%s:%s", line, column, text); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/ParseError.java b/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/ParseError.java new file mode 100644 index 0000000000..ce758e15fa --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/ParseError.java @@ -0,0 +1,58 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.messaging.util; + +import org.apache.qpid.util.Strings; + + +/** + * ParseError + * + */ + +public class ParseError extends RuntimeException +{ + + private static String msg(Token token, Token.Type ... expected) + { + LineInfo li = token.getLineInfo(); + String exp = Strings.join(", ", expected); + if (expected.length > 1) + { + exp = String.format("(%s)", exp); + } + + if (expected.length > 0) + { + return String.format("expecting %s, got %s line:%s", exp, token, li); + } + else + { + return String.format("unexpected token %s line:%s", token, li); + } + } + + public ParseError(Token token, Token.Type ... expected) + { + super(msg(token, expected)); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/Parser.java b/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/Parser.java new file mode 100644 index 0000000000..2e983f5165 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/Parser.java @@ -0,0 +1,85 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.messaging.util; + +import java.util.ArrayList; +import java.util.List; + + +/** + * Parser + * + */ + +class Parser +{ + + private List<Token> tokens; + private int idx = 0; + + Parser(List<Token> tokens) + { + this.tokens = tokens; + this.idx = 0; + } + + Token next() + { + return tokens.get(idx); + } + + boolean matches(Token.Type ... types) + { + for (Token.Type t : types) + { + if (next().getType() == t) + { + return true; + } + } + return false; + } + + Token eat(Token.Type ... types) + { + if (types.length > 0 && !matches(types)) + { + throw new ParseError(next(), types); + } + else + { + Token t = next(); + idx += 1; + return t; + } + } + + List<Token> eat_until(Token.Type ... types) + { + List<Token> result = new ArrayList(); + while (!matches(types)) + { + result.add(eat()); + } + return result; + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/PyPrint.java b/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/PyPrint.java new file mode 100644 index 0000000000..ef6c724371 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/PyPrint.java @@ -0,0 +1,146 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.messaging.util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + + +/** + * PyPrint + * + */ + +public class PyPrint +{ + + public static String pprint(Object obj) + { + if (obj instanceof Map) + { + return pprint_map((Map) obj); + } + else if (obj instanceof List) + { + return pprint_list((List) obj); + } + else if (obj instanceof String) + { + return pprint_string((String) obj); + } + else if (obj instanceof Boolean) + { + return ((Boolean) obj).booleanValue() ? "True" : "False"; + } + else if (obj == null) + { + return "None"; + } + else + { + return obj.toString(); + } + } + + private static String indent(String st) + { + return " " + st.replace("\n", "\n "); + } + + private static String pprint_map(Map<Object,Object> map) + { + List<String> items = new ArrayList<String>(); + for (Map.Entry me : map.entrySet()) + { + items.add(String.format("%s: %s", pprint(me.getKey()), + pprint(me.getValue()))); + } + Collections.sort(items); + return pprint_items("{", items, "}"); + } + + private static String pprint_list(List list) + { + List<String> items = new ArrayList<String>(); + for (Object o : list) + { + items.add(pprint(o)); + } + return pprint_items("[", items, "]"); + } + + private static String pprint_items(String start, List<String> items, + String end) + { + StringBuilder result = new StringBuilder(); + for (String item : items) + { + if (result.length() > 0) + { + result.append(",\n"); + } + result.append(indent(item)); + } + + if (result.length() > 0) + { + return String.format("%s\n%s\n%s", start, result, end); + } + else + { + return String.format("%s%s", start, end); + } + } + + private static String pprint_string(String st) + { + StringBuilder result = new StringBuilder(); + result.append('\''); + for (int i = 0; i < st.length(); i++) + { + char c = st.charAt(i); + switch (c) + { + case '\'': + result.append("\\'"); + break; + case '\n': + result.append("\\n"); + break; + default: + if (c >= 0x80) + { + result.append(String.format("\\u%04x", (int)c)); + } + else + { + result.append(c); + } + break; + } + } + result.append('\''); + return result.toString(); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/Token.java b/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/Token.java new file mode 100644 index 0000000000..b9458d7997 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/messaging/util/Token.java @@ -0,0 +1,106 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.messaging.util; + + +/** + * Token + * + */ + +public class Token +{ + + public static class Type + { + + private String name; + private String pattern; + + Type(String name, String pattern) + { + this.name = name; + this.pattern = pattern; + } + + public String getName() + { + return name; + } + + public String getPattern() + { + return pattern; + } + + public String toString() + { + return name; + } + + } + + private Type type; + private String value; + private String input; + private int position; + + Token(Type type, String value, String input, int position) + { + this.type = type; + this.value = value; + this.input = input; + this.position = position; + } + + public Type getType() + { + return type; + } + + public String getValue() + { + return value; + } + + public int getPosition() + { + return position; + } + + public LineInfo getLineInfo() + { + return LineInfo.get(input, position); + } + + public String toString() + { + if (value == null) + { + return type.toString(); + } + else + { + return String.format("%s(%s)", type, value); + } + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/pool/Job.java b/qpid/java/common/src/main/java/org/apache/qpid/pool/Job.java new file mode 100644 index 0000000000..82b600de88 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/qpid/pool/ReadWriteJobQueue.java b/qpid/java/common/src/main/java/org/apache/qpid/pool/ReadWriteJobQueue.java new file mode 100644 index 0000000000..8de0f93ce9 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/pool/ReadWriteJobQueue.java @@ -0,0 +1,432 @@ +package org.apache.qpid.pool; + +import java.util.AbstractQueue; +import java.util.Iterator; +import java.util.Collection; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.atomic.AtomicInteger; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT 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 ReadWriteJobQueue extends AbstractQueue<Runnable> implements BlockingQueue<Runnable> +{ + + private final AtomicInteger _count = new AtomicInteger(0); + + private final ReentrantLock _takeLock = new ReentrantLock(); + + private final Condition _notEmpty = _takeLock.newCondition(); + + private final ReentrantLock _putLock = new ReentrantLock(); + + private final ConcurrentLinkedQueue<ReadWriteRunnable> _readJobQueue = new ConcurrentLinkedQueue<ReadWriteRunnable>(); + + private final ConcurrentLinkedQueue<ReadWriteRunnable> _writeJobQueue = new ConcurrentLinkedQueue<ReadWriteRunnable>(); + + + private class ReadWriteJobIterator implements Iterator<Runnable> + { + + private boolean _onReads; + private Iterator<ReadWriteRunnable> _iter = _writeJobQueue.iterator(); + + public boolean hasNext() + { + if(!_iter.hasNext()) + { + if(_onReads) + { + _iter = _readJobQueue.iterator(); + _onReads = true; + return _iter.hasNext(); + } + else + { + return false; + } + } + else + { + return true; + } + } + + public Runnable next() + { + if(_iter.hasNext()) + { + return _iter.next(); + } + else + { + return null; + } + } + + public void remove() + { + _takeLock.lock(); + try + { + _iter.remove(); + _count.decrementAndGet(); + } + finally + { + _takeLock.unlock(); + } + } + } + + public Iterator<Runnable> iterator() + { + return new ReadWriteJobIterator(); + } + + public int size() + { + return _count.get(); + } + + public boolean offer(final Runnable runnable) + { + final ReadWriteRunnable job = (ReadWriteRunnable) runnable; + final ReentrantLock putLock = _putLock; + putLock.lock(); + try + { + if(job.isRead()) + { + _readJobQueue.offer(job); + } + else + { + _writeJobQueue.offer(job); + } + if(_count.getAndIncrement() == 0) + { + _takeLock.lock(); + try + { + _notEmpty.signal(); + } + finally + { + _takeLock.unlock(); + } + } + return true; + } + finally + { + putLock.unlock(); + } + } + + public void put(final Runnable runnable) throws InterruptedException + { + final ReadWriteRunnable job = (ReadWriteRunnable) runnable; + final ReentrantLock putLock = _putLock; + putLock.lock(); + + try + { + if(job.isRead()) + { + _readJobQueue.offer(job); + } + else + { + _writeJobQueue.offer(job); + } + if(_count.getAndIncrement() == 0) + { + _takeLock.lock(); + try + { + _notEmpty.signal(); + } + finally + { + _takeLock.unlock(); + } + } + + } + finally + { + putLock.unlock(); + } + } + + + + public boolean offer(final Runnable runnable, final long timeout, final TimeUnit unit) throws InterruptedException + { + final ReadWriteRunnable job = (ReadWriteRunnable) runnable; + final ReentrantLock putLock = _putLock; + putLock.lock(); + + try + { + if(job.isRead()) + { + _readJobQueue.offer(job); + } + else + { + _writeJobQueue.offer(job); + } + if(_count.getAndIncrement() == 0) + { + _takeLock.lock(); + try + { + _notEmpty.signal(); + } + finally + { + _takeLock.unlock(); + } + } + + return true; + } + finally + { + putLock.unlock(); + } + + } + + public Runnable take() throws InterruptedException + { + final ReentrantLock takeLock = _takeLock; + takeLock.lockInterruptibly(); + try + { + try + { + while (_count.get() == 0) + { + _notEmpty.await(); + } + } + catch (InterruptedException ie) + { + _notEmpty.signal(); + throw ie; + } + + ReadWriteRunnable job = _writeJobQueue.poll(); + if(job == null) + { + job = _readJobQueue.poll(); + } + int c = _count.getAndDecrement(); + if (c > 1) + { + _notEmpty.signal(); + } + return job; + } + finally + { + takeLock.unlock(); + } + + + } + + public Runnable poll(final long timeout, final TimeUnit unit) throws InterruptedException + { + final ReentrantLock takeLock = _takeLock; + final AtomicInteger count = _count; + long nanos = unit.toNanos(timeout); + takeLock.lockInterruptibly(); + ReadWriteRunnable job = null; + try + { + + for (;;) + { + if (count.get() > 0) + { + job = _writeJobQueue.poll(); + if(job == null) + { + job = _readJobQueue.poll(); + } + int c = count.getAndDecrement(); + if (c > 1) + { + _notEmpty.signal(); + } + break; + } + if (nanos <= 0) + { + return null; + } + try + { + nanos = _notEmpty.awaitNanos(nanos); + } + catch (InterruptedException ie) + { + _notEmpty.signal(); + throw ie; + } + } + } + finally + { + takeLock.unlock(); + } + + return job; + } + + public int remainingCapacity() + { + return Integer.MAX_VALUE; + } + + public int drainTo(final Collection<? super Runnable> c) + { + int total = 0; + + _putLock.lock(); + _takeLock.lock(); + try + { + ReadWriteRunnable job; + while((job = _writeJobQueue.peek())!= null) + { + c.add(job); + _writeJobQueue.poll(); + _count.decrementAndGet(); + total++; + } + + while((job = _readJobQueue.peek())!= null) + { + c.add(job); + _readJobQueue.poll(); + _count.decrementAndGet(); + total++; + } + + } + finally + { + _takeLock.unlock(); + _putLock.unlock(); + } + return total; + } + + public int drainTo(final Collection<? super Runnable> c, final int maxElements) + { + int total = 0; + + _putLock.lock(); + _takeLock.lock(); + try + { + ReadWriteRunnable job; + while(total<=maxElements && (job = _writeJobQueue.peek())!= null) + { + c.add(job); + _writeJobQueue.poll(); + _count.decrementAndGet(); + total++; + } + + while(total<=maxElements && (job = _readJobQueue.peek())!= null) + { + c.add(job); + _readJobQueue.poll(); + _count.decrementAndGet(); + total++; + } + + } + finally + { + _takeLock.unlock(); + _putLock.unlock(); + } + return total; + + } + + public Runnable poll() + { + final ReentrantLock takeLock = _takeLock; + takeLock.lock(); + try + { + if(_count.get() > 0) + { + ReadWriteRunnable job = _writeJobQueue.poll(); + if(job == null) + { + job = _readJobQueue.poll(); + } + _count.decrementAndGet(); + return job; + } + else + { + return null; + } + } + finally + { + takeLock.unlock(); + } + + } + + public Runnable peek() + { + final ReentrantLock takeLock = _takeLock; + takeLock.lock(); + try + { + ReadWriteRunnable job = _writeJobQueue.peek(); + if(job == null) + { + job = _readJobQueue.peek(); + } + return job; + } + finally + { + takeLock.unlock(); + } + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/pool/ReadWriteRunnable.java b/qpid/java/common/src/main/java/org/apache/qpid/pool/ReadWriteRunnable.java new file mode 100644 index 0000000000..140c93ca8d --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/pool/ReadWriteRunnable.java @@ -0,0 +1,26 @@ +package org.apache.qpid.pool; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT 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 interface ReadWriteRunnable extends Runnable +{ + boolean isRead(); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/pool/ReferenceCountingExecutorService.java b/qpid/java/common/src/main/java/org/apache/qpid/pool/ReferenceCountingExecutorService.java new file mode 100644 index 0000000000..8152a1f5e9 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/pool/ReferenceCountingExecutorService.java @@ -0,0 +1,215 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.pool; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.LinkedBlockingQueue; + + +/** + * ReferenceCountingExecutorService wraps an ExecutorService in order to provide shared reference to it. It counts + * the references taken, instantiating the service on the first reference, and shutting it down when the last + * reference is released. + * + * <p/>It is important to ensure that an executor service is correctly shut down as failing to do so prevents the JVM + * from terminating due to the existence of non-daemon threads. + * + * <p/><table id="crc><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Provide a shared executor service. <td> {@link Executors} + * <tr><td> Shutdown the executor service when not needed. <td> {@link ExecutorService} + * <tr><td> Track references to the executor service. + * <tr><td> Provide configuration of the executor service. + * </table> + * + * @todo Might be more elegant to make this actually implement ExecutorService, providing better hiding of the + * implementation details. Also this class introduces a pattern (albeit specific to this usage) that could be + * generalized to reference count anything. That is, on first instance call a create method, on release of last + * instance call a destroy method. This could definitely be abstracted out as a re-usable piece of code; a + * reference counting factory. It could then be re-used to do reference counting in other places (such as + * messages). Countable objects have a simple create/destroy life cycle, capturable by an interface that the + * ref counting factory can call to manage the lifecycle. + * + * @todo {@link #_poolSize} should be static? + * + * @todo The {@link #getPool()} method breaks the encapsulation of the reference counter. Generally when getPool is used + * further checks are applied to ensure that the executor service has not been shutdown. This passes responsibility + * for managing the lifecycle of the reference counted object onto the caller rather than neatly encapsulating it + * here. Could think about adding more state to the lifecycle, to mark ref counted objects as invalid, and have an + * isValid method, or could make calling code deal with RejectedExecutionException raised by shutdown executors. + */ +public class ReferenceCountingExecutorService +{ + + + /** Defines the smallest thread pool that will be allocated, irrespective of the number of processors. */ + private static final int MINIMUM_POOL_SIZE = 4; + + /** Holds the number of processors on the machine. */ + private static final int NUM_CPUS = Runtime.getRuntime().availableProcessors(); + + /** Defines the thread pool size to use, which is the larger of the number of CPUs or the minimum size. */ + private static final int DEFAULT_POOL_SIZE = Math.max(NUM_CPUS, MINIMUM_POOL_SIZE); + + /** + * Holds the singleton instance of this reference counter. This is only created once, statically, so the + * {@link #getInstance()} method does not need to be synchronized. + */ + private static final ReferenceCountingExecutorService _instance = new ReferenceCountingExecutorService(); + + /** This lock is used to ensure that reference counts are updated atomically with create/destroy operations. */ + private final Object _lock = new Object(); + + /** The shared executor service that is reference counted. */ + private ExecutorService _pool; + + /** Holds the number of references given out to the executor service. */ + private int _refCount = 0; + + /** Holds the number of executor threads to create. */ + private int _poolSize = Integer.getInteger("amqj.read_write_pool_size", DEFAULT_POOL_SIZE); + + /** Thread Factory used to create thread of the pool. Uses the default implementation provided by + * {@link java.util.concurrent.Executors#defaultThreadFactory()} unless reset by the caller. + */ + private ThreadFactory _threadFactory = Executors.defaultThreadFactory(); + + private final boolean _useBiasedPool = Boolean.getBoolean("org.apache.qpid.use_write_biased_pool"); + + /** + * Retrieves the singleton instance of this reference counter. + * + * @return The singleton instance of this reference counter. + */ + public static ReferenceCountingExecutorService getInstance() + { + return _instance; + } + + /** + * Private constructor to ensure that only a singleton instance can be created. + */ + private ReferenceCountingExecutorService() + { } + + /** + * Provides a reference to a shared executor service, incrementing the reference count. + * + * @return An executor service. + */ + public ExecutorService acquireExecutorService() + { + synchronized (_lock) + { + if (_refCount++ == 0) + { + // Use a job queue that biases to writes + if(_useBiasedPool) + { + _pool = new ThreadPoolExecutor(_poolSize, _poolSize, + 0L, TimeUnit.MILLISECONDS, + new ReadWriteJobQueue(), + _threadFactory); + + } + else + { + _pool = new ThreadPoolExecutor(_poolSize, _poolSize, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<Runnable>(), + _threadFactory); + } + + } + + + return _pool; + } + } + + /** + * Releases a reference to a shared executor service, decrementing the reference count. If the reference count falls + * to zero, the executor service is shut down. + */ + public void releaseExecutorService() + { + synchronized (_lock) + { + if (--_refCount == 0) + { + _pool.shutdownNow(); + } + } + } + + /** + * Provides access to the executor service, without touching the reference count. + * + * @return The shared executor service, or <tt>null</tt> if none has been instantiated yet. + */ + public ExecutorService getPool() + { + return _pool; + } + + /** + * Return the ReferenceCount to this ExecutorService + * @return reference count + */ + public int getReferenceCount() + { + return _refCount; + } + + /** + * + * Return the thread factory used by the {@link ThreadPoolExecutor} to create new threads. + * + * @return thread factory + */ + public ThreadFactory getThreadFactory() + { + return _threadFactory; + } + + /** + * Sets the thread factory used by the {@link ThreadPoolExecutor} to create new threads. + * <p> + * If the pool has been already created, the change will have no effect until + * {@link #getReferenceCount()} reaches zero and the pool recreated. For this reason, + * callers must invoke this method <i>before</i> calling {@link #acquireExecutorService()}. + * + * @param threadFactory thread factory + */ + public void setThreadFactory(final ThreadFactory threadFactory) + { + if (threadFactory == null) + { + throw new NullPointerException("threadFactory cannot be null"); + } + _threadFactory = threadFactory; + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQConstant.java b/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQConstant.java new file mode 100644 index 0000000000..f0f2652ce3 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQConstant.java @@ -0,0 +1,236 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.protocol; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.qpid.framing.AMQShortString; + +/** + * Defines constants for AMQP codes and also acts as a factory for creating such constants from the raw codes. Each + * constant also defines a short human readable description of the constant. + * + * @todo Why would a constant be defined that is not in the map? Seems more natural that getConstant should raise an + * exception for an unknown constant. Or else provide an explanation of why this is so. Also, there is no way for + * callers to determine the unknown status of a code except by comparing its name to "unknown code", which would + * seem to render this scheme a little bit pointless? + * + * @todo Java has a nice enum construct for doing this sort of thing. Maybe this is done in the old style for Java 1.4 + * backward compatability? Now that is handled through retrotranslater it may be time to use enum. + * + * <p/><tabld id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Define the set of AMQP status codes. + * <tr><td> Provide a factory to lookup constants by their code. + * <tr><td> + */ +public final class AMQConstant +{ + /** Defines a map from codes to constants. */ + private static Map<Integer, AMQConstant> _codeMap = new HashMap<Integer, AMQConstant>(); + + /** Indicates that the method completed successfully. */ + public static final AMQConstant REPLY_SUCCESS = new AMQConstant(200, "reply success", true); + + public static final AMQConstant FRAME_END = new AMQConstant(206, "frame end", true); + + /** + * The client asked for a specific message that is no longer available. The message was delivered to another + * client, or was purged from the queue for some other reason. + */ + public static final AMQConstant NOT_DELIVERED = new AMQConstant(310, "not delivered", true); + + /** + * The client attempted to transfer content larger than the server could accept at the present time. The client + * may retry at a later time. + */ + public static final AMQConstant MESSAGE_TOO_LARGE = new AMQConstant(311, "message too large", true); + + /** + * When the exchange cannot route the result of a .Publish, most likely due to an invalid routing key. Only when + * the mandatory flag is set. + */ + public static final AMQConstant NO_ROUTE = new AMQConstant(312, "no route", true); + + /** + * When the exchange cannot deliver to a consumer when the immediate flag is set. As a result of pending data on + * the queue or the absence of any consumers of the queue. + */ + public static final AMQConstant NO_CONSUMERS = new AMQConstant(313, "no consumers", true); + + /** + * An operator intervened to close the connection for some reason. The client may retry at some later date. + */ + 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); + + /** The client attempted to work with a server entity to which it has no access due to security settings. */ + public static final AMQConstant ACCESS_REFUSED = new AMQConstant(403, "access refused", true); + + /** The client attempted to work with a server entity that does not exist. */ + public static final AMQConstant NOT_FOUND = new AMQConstant(404, "not found", true); + + /** + * The client attempted to work with a server entity to which it has no access because another client is + * working with it. + */ + public static final AMQConstant ALREADY_EXISTS = new AMQConstant(405, "Already exists", true); + + /** The client requested a method that was not allowed because some precondition failed. */ + public static final AMQConstant IN_USE = new AMQConstant(406, "In use", true); + + public static final AMQConstant INVALID_ROUTING_KEY = new AMQConstant(407, "routing key invalid", true); + + public static final AMQConstant REQUEST_TIMEOUT = new AMQConstant(408, "Request Timeout", 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 + * in the client. + */ + public static final AMQConstant FRAME_ERROR = new AMQConstant(501, "frame error", true); + + /** + * The client sent a frame that contained illegal values for one or more fields. This strongly implies a + * programming error in the client. + */ + public static final AMQConstant SYNTAX_ERROR = new AMQConstant(502, "syntax error", true); + + /** + * The client sent an invalid sequence of frames, attempting to perform an operation that was considered invalid + * by the server. This usually implies a programming error in the client. + */ + public static final AMQConstant COMMAND_INVALID = new AMQConstant(503, "command invalid", true); + + /** + * The client attempted to work with a channel that had not been correctly opened. This most likely indicates a + * fault in the client layer. + */ + public static final AMQConstant CHANNEL_ERROR = new AMQConstant(504, "channel error", true); + + /** + * The server could not complete the method because it lacked sufficient resources. This may be due to the client + * creating too many of some type of entity. + */ + public static final AMQConstant RESOURCE_ERROR = new AMQConstant(506, "resource error", true); + + /** + * The client tried to work with some entity in a manner that is prohibited by the server, due to security settings + * or by some other criteria. + */ + public static final AMQConstant NOT_ALLOWED = new AMQConstant(530, "not allowed", true); + + /** The client tried to use functionality that is not implemented in the server. */ + public static final AMQConstant NOT_IMPLEMENTED = new AMQConstant(540, "not implemented", true); + + /** + * The server could not complete the method because of an internal error. The server may require intervention by + * an operator in order to resume normal operations. + */ + public static final AMQConstant INTERNAL_ERROR = new AMQConstant(541, "internal error", true); + + public static final AMQConstant FRAME_MIN_SIZE = new AMQConstant(4096, "frame min size", 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 + */ + public static final AMQConstant UNSUPPORTED_CLIENT_PROTOCOL_ERROR = new AMQConstant(543, "client unsupported protocol", true); + + /** The AMQP status code. */ + private int _code; + + /** A short description of the status code. */ + private AMQShortString _name; + + /** + * Creates a new AMQP status code. + * + * @param code The code. + * @param name A short description of the code. + * @param map <tt>true</tt> to register the code as a known code, <tt>false</tt> otherwise. + */ + private AMQConstant(int code, String name, boolean map) + { + _code = code; + _name = new AMQShortString(name); + if (map) + { + _codeMap.put(Integer.valueOf(code), this); + } + } + + /** + * Creates a constant for a status code by looking up the code in the map of known codes. If the code is not known + * a constant is still created for it, but it is marked as unknown. + * + * @param code The AMQP status code. + * + * @return The AMQP status code encapsulated as a constant. + */ + public static AMQConstant getConstant(int code) + { + AMQConstant c = _codeMap.get(Integer.valueOf(code)); + if (c == null) + { + c = new AMQConstant(code, "unknown code", false); + } + + return c; + } + + /** + * Gets the underlying AMQP status code. + * + * @return The AMQP status code. + */ + public int getCode() + { + return _code; + } + + /** + * Gets a short description of the status code. + * + * @return A short description of the status code. + */ + public AMQShortString getName() + { + return _name; + } + + /** + * Renders the constant as a string, mainly for debugging purposes. + * + * @return The status code and its description. + */ + public String toString() + { + return _code + ": " + _name; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQMethodEvent.java b/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQMethodEvent.java new file mode 100644 index 0000000000..fd6907a152 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQMethodEvent.java @@ -0,0 +1,95 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.protocol; + +import org.apache.qpid.framing.AMQMethodBody; + +/** + * AMQMethodEvent encapsulates an AMQP method call, and the channel on which that method call occurred. + * + * <p/>Supplies the: + * <ul> + * <li>channel id</li> + * <li>protocol method</li> + * </ul> + * + * <p/>As the event contains the context in which it occurred, event listeners do not need to be statefull. + * to listeners. Events are often handled by {@link AMQMethodListener}s. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Encapsulate an AMQP method call and the channel as the context for the method call. + * </table> + */ +public class AMQMethodEvent<M extends AMQMethodBody> +{ + /** Holds the method call. */ + private final M _method; + + /** Holds the channel handle for the method call. */ + private final int _channelId; + + /** + * Creates a method event to encasulate a method call and channel. + * + * @param channelId The channel on which the method call occurred. + * @param method The method call. + */ + public AMQMethodEvent(int channelId, M method) + { + _channelId = channelId; + _method = method; + } + + /** + * Gets the method call. + * + * @return The method call. + */ + public M getMethod() + { + return _method; + } + + /** + * Gets the channel handle for the method call. + * + * @return The channel handle for the method call. + */ + public int getChannelId() + { + return _channelId; + } + + /** + * Prints the method call as a string, mainly for debugging purposes. + * + * @return The method call as a string, mainly for debugging purposes. + */ + public String toString() + { + StringBuilder buf = new StringBuilder("Method event: "); + buf.append("\nChannel id: ").append(_channelId); + buf.append("\nMethod: ").append(_method); + + return buf.toString(); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQMethodListener.java b/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQMethodListener.java new file mode 100644 index 0000000000..5a7679a972 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQMethodListener.java @@ -0,0 +1,70 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.protocol; + +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.AMQException; + +/** + * AMQMethodListener is a listener that receives notifications of AMQP methods. The methods are packaged as events in + * {@link AMQMethodEvent}. + * + * <p/>An event listener may be associated with a particular context, usually an AMQP channel, and in addition to + * receiving method events will be notified of errors on that context. This enables listeners to perform any clean + * up that they need to do before the context is closed or retried. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities + * <tr><td> Accept notification of AMQP method events. <td> {@link AMQMethodEvent} + * <tr><td> Accept notification of errors on the event context. + * </table> + * + * @todo Document why the exception is passed to the error method. Is it so that the exception can be passed + * from the event handling thread to another thread and rethown from there? It is unusual to pass exceptions as + * method arguments, because they have their own mechanism for propagating through the call stack, so some + * explanation ought to be provided. + */ +public interface AMQMethodListener +{ + /** + * Notifies the listener that an AMQP method event has occurred. + * + * @param evt The AMQP method event (contains the method and channel). + * + * @return <tt>true</tt> if the handler processes the method frame, <tt>false<tt> otherwise. Note that this does + * not prohibit the method event being delivered to subsequent listeners but can be used to determine if + * nobody has dealt with an incoming method frame. + * + * @throws Exception if an error has occurred. This exception may be delivered to all registered listeners using + * the error() method (see below) allowing them to perform cleanup if necessary. + * + * @todo Consider narrowing the exception. + */ + <B extends AMQMethodBody> boolean methodReceived(AMQMethodEvent<B> evt) throws AMQException; + + /** + * Notifies the listener of an error on the event context to which it is listening. The listener should perform + * any necessary clean-up for the context. + * + * @param e The underlying exception that is the source of the error. + */ + void error(Exception e); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQProtocolWriter.java b/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQProtocolWriter.java new file mode 100644 index 0000000000..65884e4950 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQProtocolWriter.java @@ -0,0 +1,43 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.protocol; + +import org.apache.qpid.framing.AMQDataBlock; + +/** + * AMQProtocolWriter provides a method to write a frame of data 'to the wire', in the context of the object + * that implements the method, usually some sort of session. The block of data, encapsulated by {@link AMQDataBlock}, + * will be encoded as it is written. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities + * <tr><td> Write an encoded block of data to the write, in the context of a session. + * </table> + */ +public interface AMQProtocolWriter +{ + /** + * Writes a frame to the wire, encoding it as necessary, for example, into a sequence of bytes. + * + * @param frame The frame to be encoded and written. + */ + public void writeFrame(AMQDataBlock frame); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQVersionAwareProtocolSession.java b/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQVersionAwareProtocolSession.java new file mode 100644 index 0000000000..b58e7d01dc --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQVersionAwareProtocolSession.java @@ -0,0 +1,64 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.protocol; + +import org.apache.qpid.framing.*; +import org.apache.qpid.transport.Sender; +import org.apache.qpid.AMQException; + +import java.nio.ByteBuffer; + + +/** + * AMQVersionAwareProtocolSession is implemented by all AMQP session classes, that need to provide an awareness to + * callers of the version of the AMQP protocol that they are able to work with. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities + * <tr><td> Provide the method registry for a specific version of the AMQP. + * </table> + * + * @todo Why is this a seperate interface to {@link ProtocolVersionAware}, could they be combined into a single + * interface and one of them eliminated? Move getRegistry method to ProtocolVersionAware, make the sessions + * implement AMQProtocolWriter directly and drop this interface. + */ +public interface AMQVersionAwareProtocolSession extends AMQProtocolWriter, ProtocolVersionAware +{ + /** + * Gets the method registry for a specific version of the AMQP. + * + * @return The method registry for a specific version of the AMQP. + */ +// public VersionSpecificRegistry getRegistry(); + + MethodRegistry getMethodRegistry(); + + + public void methodFrameReceived(int channelId, AMQMethodBody body) throws AMQException; + public void contentHeaderReceived(int channelId, ContentHeaderBody body) throws AMQException; + public void contentBodyReceived(int channelId, ContentBody body) throws AMQException; + public void heartbeatBodyReceived(int channelId, HeartbeatBody body) throws AMQException; + + + public void setSender(Sender<ByteBuffer> sender); + public void init(); + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/protocol/ProtocolEngine.java b/qpid/java/common/src/main/java/org/apache/qpid/protocol/ProtocolEngine.java new file mode 100644 index 0000000000..31953ea6ab --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/protocol/ProtocolEngine.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.protocol; + +import java.net.SocketAddress; + +import org.apache.qpid.framing.AMQDataBlock; +import org.apache.qpid.transport.NetworkDriver; +import org.apache.qpid.transport.Receiver; + +/** + * A ProtocolEngine is a Receiver for java.nio.ByteBuffers. It takes the data passed to it in the received + * decodes it and then process the result. + */ +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(); + + // Returns the local address of the NetworkDriver + SocketAddress getLocalAddress(); + + // Returns number of bytes written + long getWrittenBytes(); + + // Returns number of bytes read + long getReadBytes(); + + // Called by the NetworkDriver when the socket has been closed for reading + void closed(); + + // Called when the NetworkEngine has not written data for the specified period of time (will trigger a + // heartbeat) + void writerIdle(); + + // Called when the NetworkEngine has not read data for the specified period of time (will close the connection) + void readerIdle(); + + +}
\ No newline at end of file diff --git a/qpid/java/common/src/main/java/org/apache/qpid/protocol/ProtocolEngineFactory.java b/qpid/java/common/src/main/java/org/apache/qpid/protocol/ProtocolEngineFactory.java new file mode 100644 index 0000000000..9df84eef90 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/protocol/ProtocolEngineFactory.java @@ -0,0 +1,31 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.protocol; + +import org.apache.qpid.transport.NetworkDriver; + +public interface ProtocolEngineFactory +{ + + // Returns a new instance of a ProtocolEngine + ProtocolEngine newProtocolEngine(NetworkDriver networkDriver); + +}
\ No newline at end of file diff --git a/qpid/java/common/src/main/java/org/apache/qpid/protocol/ProtocolVersionAware.java b/qpid/java/common/src/main/java/org/apache/qpid/protocol/ProtocolVersionAware.java new file mode 100644 index 0000000000..56f950dd85 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/protocol/ProtocolVersionAware.java @@ -0,0 +1,53 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.protocol; + +import org.apache.qpid.framing.ProtocolVersion; + +/** + * ProtocolVersionAware is implemented by all AMQP handling classes, that need to provide an awareness to callers of + * the version of the AMQP protocol that they are able to handle. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities + * <tr><td> Report the major and minor AMQP version handled. + * </table> + */ +public interface ProtocolVersionAware +{ + /** + * @deprecated + * Reports the AMQP minor version, that the implementer can handle. + * + * @return The AMQP minor version. + */ + public byte getProtocolMinorVersion(); + + /** + * @deprecated + * Reports the AMQP major version, that the implementer can handle. + * + * @return The AMQP major version. + */ + public byte getProtocolMajorVersion(); + + public ProtocolVersion getProtocolVersion(); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/security/AMQPCallbackHandler.java b/qpid/java/common/src/main/java/org/apache/qpid/security/AMQPCallbackHandler.java new file mode 100644 index 0000000000..a3dad9acdc --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/security/AMQPCallbackHandler.java @@ -0,0 +1,28 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 javax.security.auth.callback.CallbackHandler; + +public interface AMQPCallbackHandler extends CallbackHandler +{ + void initialise(String username,String password); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/security/UsernamePasswordCallbackHandler.java b/qpid/java/common/src/main/java/org/apache/qpid/security/UsernamePasswordCallbackHandler.java new file mode 100644 index 0000000000..89a63abeab --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/qpid/ssl/SSLContextFactory.java b/qpid/java/common/src/main/java/org/apache/qpid/ssl/SSLContextFactory.java new file mode 100644 index 0000000000..702746b3da --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/ssl/SSLContextFactory.java @@ -0,0 +1,195 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.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.TrustManagerFactory; + +import org.apache.qpid.transport.network.security.ssl.SSLUtil; + +/** + * Factory used to create SSLContexts. SSL needs to be configured + * before this will work. + * + */ +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) + { + this(trustStorePath,trustStorePassword,trustStoreCertType, + trustStorePath,trustStorePassword,trustStoreCertType); + } + + /** + * 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) + { + + _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) + { + + _trustStorePath = trustStorePath; + _trustStorePassword = trustStorePassword; + + if (_trustStorePassword != null && _trustStorePassword.equals("none")) + { + _trustStorePassword = 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 + */ + + 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) + { + sslContext.init(new KeyManager[]{customKeyManager}, + tmf.getTrustManagers(), null); + + } + else + { + // 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(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/qpid/java/common/src/main/java/org/apache/qpid/thread/DefaultThreadFactory.java b/qpid/java/common/src/main/java/org/apache/qpid/thread/DefaultThreadFactory.java new file mode 100644 index 0000000000..a96dac4109 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/thread/DefaultThreadFactory.java @@ -0,0 +1,45 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.thread; + + + +public class DefaultThreadFactory implements ThreadFactory +{ + + private final LoggingUncaughtExceptionHandler _loggingUncaughtExceptionHandler = new LoggingUncaughtExceptionHandler(); + + public Thread createThread(Runnable r) + { + Thread t = new Thread(r); + t.setUncaughtExceptionHandler(_loggingUncaughtExceptionHandler); + return t; + } + + public Thread createThread(Runnable r, int priority) + { + Thread t = createThread(r); + t.setPriority(priority); + return t; + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/thread/LoggingUncaughtExceptionHandler.java b/qpid/java/common/src/main/java/org/apache/qpid/thread/LoggingUncaughtExceptionHandler.java new file mode 100644 index 0000000000..192675edcd --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/thread/LoggingUncaughtExceptionHandler.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.thread; + +import java.lang.Thread.UncaughtExceptionHandler; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * An {@link UncaughtExceptionHandler} that writes the exception to the application log via + * the SLF4J framework. Once registered with {@link Thread#setUncaughtExceptionHandler(UncaughtExceptionHandler)} + * it will be invoked by the JVM when a thread has been <i>abruptly</i> terminated due to an uncaught exception. + * Owing to the contract of {@link Runnable#run()}, the only possible exception types which can cause such a termination + * are instances of {@link RuntimeException} and {@link Error}. These exceptions are catastrophic and the client must + * restart the JVM. + * <p> + * The implementation also invokes {@link ThreadGroup#uncaughtException(Thread, Throwable)}. This + * is done to retain compatibility with any monitoring solutions (for example, log scraping of + * standard error) that existing users of older Qpid client libraries may have in place. + * + */ +public class LoggingUncaughtExceptionHandler implements UncaughtExceptionHandler +{ + private static final Logger _logger = LoggerFactory.getLogger(LoggingUncaughtExceptionHandler.class); + + @Override + public void uncaughtException(Thread t, Throwable e) + { + try + { + _logger.error("Uncaught exception in thread \"{}\"", t.getName(), e); + } + finally + { + // Invoke the thread group's handler too for compatibility with any + // existing clients who are already scraping stderr for such conditions. + t.getThreadGroup().uncaughtException(t, e); + } + } +}
\ No newline at end of file diff --git a/qpid/java/common/src/main/java/org/apache/qpid/thread/QpidThreadExecutor.java b/qpid/java/common/src/main/java/org/apache/qpid/thread/QpidThreadExecutor.java new file mode 100644 index 0000000000..38f60c04fe --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/thread/QpidThreadExecutor.java @@ -0,0 +1,42 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.thread; + +import org.apache.qpid.thread.Threading; + +import edu.emory.mathcs.backport.java.util.concurrent.Executor; + +public class QpidThreadExecutor implements Executor +{ + public void execute(Runnable command) + { + try + { + Threading.getThreadFactory().createThread(command).start(); + } + catch(Exception e) + { + throw new RuntimeException("Error creating a thread using Qpid thread factory",e); + } + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/thread/RealtimeThreadFactory.java b/qpid/java/common/src/main/java/org/apache/qpid/thread/RealtimeThreadFactory.java new file mode 100644 index 0000000000..95a8d192c5 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/thread/RealtimeThreadFactory.java @@ -0,0 +1,72 @@ +package org.apache.qpid.thread; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.lang.reflect.Constructor; + +public class RealtimeThreadFactory implements ThreadFactory +{ + private final LoggingUncaughtExceptionHandler _loggingUncaughtExceptionHandler = new LoggingUncaughtExceptionHandler(); + + private Class threadClass; + private Constructor threadConstructor; + private Constructor priorityParameterConstructor; + private int defaultRTThreadPriority = 20; + + public RealtimeThreadFactory() throws Exception + { + defaultRTThreadPriority = Integer.getInteger("qpid.rt_thread_priority",20); + threadClass = Class.forName("javax.realtime.RealtimeThread"); + + Class schedulingParametersClass = Class.forName("javax.realtime.SchedulingParameters"); + Class releaseParametersClass = Class.forName("javax.realtime.ReleaseParameters"); + Class memoryParametersClass = Class.forName("javax.realtime.MemoryParameters"); + Class memoryAreaClass = Class.forName("javax.realtime.MemoryArea"); + Class processingGroupParametersClass = Class.forName("javax.realtime.ProcessingGroupParameters"); + + Class[] paramTypes = new Class[]{schedulingParametersClass, + releaseParametersClass, + memoryParametersClass, + memoryAreaClass, + processingGroupParametersClass, + java.lang.Runnable.class}; + + threadConstructor = threadClass.getConstructor(paramTypes); + + Class priorityParameterClass = Class.forName("javax.realtime.PriorityParameters"); + priorityParameterConstructor = priorityParameterClass.getConstructor(new Class[]{int.class}); + } + + public Thread createThread(Runnable r) throws Exception + { + return createThread(r,defaultRTThreadPriority); + } + + public Thread createThread(Runnable r, int priority) throws Exception + { + Object priorityParams = priorityParameterConstructor.newInstance(priority); + Thread thread = (Thread)threadConstructor.newInstance(priorityParams,null,null,null,null,r); + thread.setUncaughtExceptionHandler(_loggingUncaughtExceptionHandler); + return thread; + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/thread/ThreadFactory.java b/qpid/java/common/src/main/java/org/apache/qpid/thread/ThreadFactory.java new file mode 100644 index 0000000000..4b8937acbd --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/thread/ThreadFactory.java @@ -0,0 +1,28 @@ +package org.apache.qpid.thread; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 interface ThreadFactory +{ + public Thread createThread(Runnable r) throws Exception; + public Thread createThread(Runnable r, int priority) throws Exception; +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/thread/Threading.java b/qpid/java/common/src/main/java/org/apache/qpid/thread/Threading.java new file mode 100644 index 0000000000..603e8a7441 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/thread/Threading.java @@ -0,0 +1,47 @@ +package org.apache.qpid.thread; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 final class Threading +{ + private static ThreadFactory threadFactory; + + static { + try + { + Class threadFactoryClass = + Class.forName(System.getProperty("qpid.thread_factory", + "org.apache.qpid.thread.DefaultThreadFactory")); + + threadFactory = (ThreadFactory)threadFactoryClass.newInstance(); + } + catch(Exception e) + { + throw new Error("Error occured while loading thread factory",e); + } + } + + public static ThreadFactory getThreadFactory() + { + return threadFactory; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/Binary.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/Binary.java new file mode 100644 index 0000000000..491a7ac218 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/Binary.java @@ -0,0 +1,154 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 static org.apache.qpid.transport.util.Functions.*; + + +/** + * Binary + * + */ + +public final class Binary +{ + + private byte[] bytes; + private int offset; + private int size; + private int hash = 0; + + public Binary(byte[] bytes, int offset, int size) + { + if (offset + size > bytes.length) + { + throw new ArrayIndexOutOfBoundsException(); + } + + this.bytes = bytes; + this.offset = offset; + this.size = size; + } + + public Binary(byte[] bytes) + { + this(bytes, 0, bytes.length); + } + + public final byte[] getBytes() + { + byte[] result = new byte[size]; + System.arraycopy(bytes, offset, result, 0, size); + return result; + } + + public final byte[] array() + { + return bytes; + } + + public final int offset() + { + return offset; + } + + public final int size() + { + return size; + } + + public final Binary slice(int low, int high) + { + int sz; + + if (high < 0) + { + sz = size + high; + } + else + { + sz = high - low; + } + + if (sz < 0) + { + sz = 0; + } + + return new Binary(bytes, offset + low, sz); + } + + public final int hashCode() + { + if (hash == 0) + { + int hc = 0; + for (int i = 0; i < size; i++) + { + hc = 31*hc + (0xFF & bytes[offset + i]); + } + hash = hc; + } + + return hash; + } + + public final boolean equals(Object o) + { + if (!(o instanceof Binary)) + { + return false; + } + + Binary buf = (Binary) o; + if (this.size != buf.size) + { + return false; + } + + for (int i = 0; i < size; i++) + { + if (bytes[offset + i] != buf.bytes[buf.offset + i]) + { + return false; + } + } + + return true; + } + + public String toString() + { + return str(ByteBuffer.wrap(bytes, offset, size)); + } + + public boolean hasExcessCapacity() + { + return size != bytes.length; + } + + public Binary copy() + { + return new Binary(getBytes()); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/Binding.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/Binding.java new file mode 100644 index 0000000000..8418c42189 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/Binding.java @@ -0,0 +1,36 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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; + + +/** + * Binding + * + */ + +public interface Binding<E,T> +{ + + E endpoint(Sender<T> sender); + + Receiver<T> receiver(E endpoint); + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/ClientDelegate.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/ClientDelegate.java new file mode 100644 index 0000000000..c8b7ad2a5e --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/ClientDelegate.java @@ -0,0 +1,320 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.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; + + +/** + * ClientDelegate + * + */ + +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; + } + + KRB5_OID = oid; + } + + private List<String> clientMechs; + private ConnectionSettings conSettings; + + public ClientDelegate(ConnectionSettings settings) + { + this.conSettings = settings; + this.clientMechs = Arrays.asList(settings.getSaslMechs().split(" ")); + } + + public void init(Connection conn, ProtocolHeader hdr) + { + if (!(hdr.getMajor() == 0 && hdr.getMinor() == 10)) + { + conn.exception(new ProtocolVersionException(hdr.getMajor(), hdr.getMinor())); + } + } + + @Override + public void connectionStart(Connection conn, ConnectionStart start) + { + Map<String,Object> clientProperties = new HashMap<String,Object>(); + + if(this.conSettings.getClientProperties() != null) + { + clientProperties.putAll(this.conSettings.getClientProperties()); + } + + clientProperties.put("qpid.session_flow", 1); + clientProperties.put("qpid.client_pid",getPID()); + clientProperties.put("qpid.client_process", + System.getProperty("qpid.client_process","Qpid Java Client")); + + List<Object> brokerMechs = start.getMechanisms(); + if (brokerMechs == null || brokerMechs.isEmpty()) + { + conn.connectionStartOk + (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 + { + 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() ? + sc.evaluateChallenge(new byte[0]) : null; + conn.connectionStartOk + (clientProperties, sc.getMechanismName(), response, + conn.getLocale()); + } + catch (SaslException e) + { + conn.exception(e); + } + } + + @Override + public void connectionSecure(Connection conn, ConnectionSecure secure) + { + SaslClient sc = conn.getSaslClient(); + try + { + byte[] response = sc.evaluateChallenge(secure.getChallenge()); + conn.connectionSecureOk(response); + } + catch (SaslException e) + { + conn.exception(e); + } + } + + @Override + public void connectionTune(Connection conn, ConnectionTune tune) + { + int hb_interval = calculateHeartbeatInterval(conSettings.getHeartbeatInterval(), + tune.getHeartbeatMin(), + tune.getHeartbeatMax() + ); + conn.connectionTuneOk(tune.getChannelMax(), + tune.getMaxFrameSize(), + hb_interval); + // The idle timeout is twice the heartbeat amount (in milisecs) + conn.setIdleTimeout(hb_interval*1000*2); + + int channelMax = tune.getChannelMax(); + //0 means no implied limit, except available server resources + //(or that forced by protocol limitations [0xFFFF]) + conn.setChannelMax(channelMax == 0 ? Connection.MAX_CHANNEL_MAX : channelMax); + + 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); + } + else + { + conn.setState(OPEN); + } + } + + @Override + public void connectionRedirect(Connection conn, ConnectionRedirect redir) + { + throw new UnsupportedOperationException(); + } + + @Override + public void connectionHeartbeat(Connection conn, ConnectionHeartbeat hearbeat) + { + conn.connectionHeartbeat(); + } + + /** + * Currently the spec specified the min and max for heartbeat using secs + */ + private int calculateHeartbeatInterval(int heartbeat,int min, int max) + { + int i = heartbeat; + if (i == 0) + { + log.info("Idle timeout is 0 sec. Heartbeats are disabled."); + return 0; // heartbeats are disabled. + } + else if (i >= min && i <= max) + { + return i; + } + 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); + return max; + } + } + + private int getPID() + { + RuntimeMXBean rtb = ManagementFactory.getRuntimeMXBean(); + String processName = rtb.getName(); + if (processName != null && processName.indexOf('@')>0) + { + try + { + return Integer.parseInt(processName.substring(0,processName.indexOf('@'))); + } + catch(Exception e) + { + log.warn("Unable to get the client PID due to error",e); + return -1; + } + } + else + { + log.warn("Unable to get the client PID due to unsupported format : " + processName); + return -1; + } + + } + + 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/qpid/java/common/src/main/java/org/apache/qpid/transport/Connection.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/Connection.java new file mode 100644 index 0000000000..dc32569ee8 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/Connection.java @@ -0,0 +1,703 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 static org.apache.qpid.transport.Connection.State.CLOSED; +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.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.transport.network.security.SecurityLayer; +import org.apache.qpid.transport.util.Logger; +import org.apache.qpid.transport.util.Waiter; +import org.apache.qpid.util.Strings; + + +/** + * Connection + * + * @author Rafael H. Schloming + * + * @todo the channels map should probably be replaced with something + * more efficient, e.g. an array or a map implementation that can use + * short instead of Short + */ + +public class Connection extends ConnectionInvoker + implements Receiver<ProtocolEvent>, Sender<ProtocolEvent> +{ + + protected static final Logger log = Logger.get(Connection.class); + + //Usable channels are numbered 0 to <ChannelMax> - 1 + 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 + { + public void opened(Connection conn) {} + public void exception(Connection conn, ConnectionException exception) + { + log.error(exception, "connection exception"); + } + public void closed(Connection conn) {} + } + + public static interface SessionFactory + { + Session newSession(Connection conn, Binary name, long expiry); + } + + private static final class DefaultSessionFactory implements SessionFactory + { + + public Session newSession(final Connection conn, final Binary name, final long expiry) + { + return new Session(conn, name, expiry); + } + } + + private static final SessionFactory DEFAULT_SESSION_FACTORY = new DefaultSessionFactory(); + + private SessionFactory _sessionFactory = DEFAULT_SESSION_FACTORY; + + private ConnectionDelegate delegate; + private Sender<ProtocolEvent> sender; + + final private Map<Binary,Session> sessions = new HashMap<Binary,Session>(); + final private Map<Integer,Session> channels = new HashMap<Integer,Session>(); + + private State state = NEW; + final private Object lock = new Object(); + private long timeout = 60000; + private List<ConnectionListener> listeners = new ArrayList<ConnectionListener>(); + private ConnectionException error = null; + + private int channelMax = 1; + private String locale; + 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) + { + this.delegate = delegate; + } + + public void addConnectionListener(ConnectionListener listener) + { + listeners.add(listener); + } + + public Sender<ProtocolEvent> getSender() + { + return sender; + } + + public void setSender(Sender<ProtocolEvent> sender) + { + this.sender = sender; + sender.setIdleTimeout(idleTimeout); + } + + protected void setState(State state) + { + synchronized (lock) + { + this.state = state; + lock.notifyAll(); + } + } + + public String getClientId() + { + return _clientId; + } + + public void setClientId(String id) + { + _clientId = id; + } + + void setLocale(String locale) + { + this.locale = locale; + } + + String getLocale() + { + return locale; + } + + void setSaslServer(SaslServer saslServer) + { + this.saslServer = saslServer; + } + + SaslServer getSaslServer() + { + return saslServer; + } + + void setSaslClient(SaslClient saslClient) + { + this.saslClient = saslClient; + } + + public SaslClient getSaslClient() + { + return saslClient; + } + + public void connect(String host, int port, String vhost, String username, String password) + { + connect(host, port, vhost, username, password, false); + } + + public void connect(String host, int port, String vhost, String username, String password, boolean ssl) + { + connect(host, port, vhost, username, password, ssl,"PLAIN"); + } + + public void connect(String host, int port, String vhost, String username, String password, boolean ssl,String saslMechs) + { + connect(host, port, vhost, username, password, ssl,saslMechs, Collections.EMPTY_MAP); + } + + + public void connect(String host, int port, String vhost, String username, String password, boolean ssl,String saslMechs,Map<String,Object> clientProps) + { + ConnectionSettings settings = new ConnectionSettings(); + settings.setHost(host); + settings.setPort(port); + settings.setVhost(vhost); + settings.setUsername(username); + settings.setPassword(password); + settings.setUseSSL(ssl); + settings.setSaslMechs(saslMechs); + settings.setClientProperties(clientProps); + connect(settings); + } + + public void connect(ConnectionSettings settings) + { + + synchronized (lock) + { + conSettings = settings; + state = OPENING; + userID = settings.getUsername(); + 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); + while (w.hasTime() && state == OPENING && error == null) + { + w.await(); + } + + if (error != null) + { + ConnectionException t = error; + error = null; + try + { + close(); + } + catch (ConnectionException ce) + { + if (!(t instanceof ProtocolVersionException)) + { + throw ce; + } + } + t.rethrow(); + } + + switch (state) + { + case OPENING: + close(); + throw new ConnectionException("connect() timed out"); + case OPEN: + case RESUMING: + connectionLost.set(false); + break; + case CLOSED: + throw new ConnectionException("connect() aborted"); + default: + throw new IllegalStateException(String.valueOf(state)); + } + } + + for (ConnectionListener listener: listeners) + { + listener.opened(this); + } + } + + public Session createSession() + { + return createSession(0); + } + + public Session createSession(long expiry) + { + return createSession(UUID.randomUUID().toString(), expiry); + } + + public Session createSession(String name) + { + return createSession(name, 0); + } + + public Session createSession(String name, long expiry) + { + return createSession(Strings.toUTF8(name), expiry); + } + + public Session createSession(byte[] name, long expiry) + { + return createSession(new Binary(name), expiry); + } + + public Session createSession(Binary name, long expiry) + { + synchronized (lock) + { + Waiter w = new Waiter(lock, timeout); + while (w.hasTime() && state != OPEN && error == null) + { + 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); + sessions.put(name, ssn); + map(ssn); + ssn.attach(); + return ssn; + } + } + + void removeSession(Session ssn) + { + synchronized (lock) + { + sessions.remove(ssn.getName()); + } + } + + public void setSessionFactory(SessionFactory sessionFactory) + { + assert sessionFactory != null; + + _sessionFactory = sessionFactory; + } + + public long getConnectionId() + { + return _connectionId; + } + + public ConnectionDelegate getConnectionDelegate() + { + return delegate; + } + + public void received(ProtocolEvent event) + { + log.debug("RECV: [%s] %s", this, event); + event.delegate(this, delegate); + } + + public void send(ProtocolEvent event) + { + log.debug("SEND: [%s] %s", this, event); + Sender s = sender; + if (s == null) + { + throw new ConnectionException("connection closed"); + } + s.send(event); + } + + public void flush() + { + log.debug("FLUSH: [%s]", this); + sender.flush(); + } + + protected void invoke(Method method) + { + method.setChannel(0); + send(method); + if (!method.isBatch()) + { + flush(); + } + } + + public void dispatch(Method method) + { + Session ssn = getSession(method.getChannel()); + if(ssn != null) + { + ssn.received(method); + } + else + { + throw new ProtocolViolationException( + "Received frames for an already dettached session", null); + } + } + + public int getChannelMax() + { + return channelMax; + } + + void setChannelMax(int max) + { + channelMax = max; + } + + private int map(Session ssn) + { + synchronized (lock) + { + //For a negotiated channelMax N, there are channels 0 to N-1 available. + for (int i = 0; i < getChannelMax(); i++) + { + if (!channels.containsKey(i)) + { + map(ssn, i); + return i; + } + } + + throw new RuntimeException("no more channels available"); + } + } + + void map(Session ssn, int channel) + { + synchronized (lock) + { + channels.put(channel, ssn); + ssn.setChannel(channel); + } + } + + void unmap(Session ssn) + { + synchronized (lock) + { + channels.remove(ssn.getChannel()); + } + } + + protected Session getSession(int channel) + { + synchronized (lock) + { + return channels.get(channel); + } + } + + public void resume() + { + synchronized (lock) + { + for (Session ssn : sessions.values()) + { + if (ssn.isTransacted()) + { + removeSession(ssn); + ssn.setState(Session.State.CLOSED); + } + else + { + map(ssn); + ssn.attach(); + ssn.resume(); + } + } + setState(OPEN); + } + } + + public void exception(ConnectionException e) + { + connectionLost.set(true); + synchronized (lock) + { + switch (state) + { + case OPENING: + case CLOSING: + error = e; + lock.notifyAll(); + return; + } + } + + for (ConnectionListener listener: listeners) + { + listener.exception(this, e); + } + + } + + public void exception(Throwable t) + { + exception(new ConnectionException(t)); + } + + void closeCode(ConnectionClose close) + { + synchronized (lock) + { + for (Session ssn : channels.values()) + { + ssn.closeCode(close); + } + ConnectionCloseCode code = close.getReplyCode(); + if (code != ConnectionCloseCode.NORMAL) + { + exception(new ConnectionException(close)); + } + } + } + + public void closed() + { + if (state == OPEN) + { + exception(new ConnectionException("connection aborted")); + } + + log.debug("connection closed: %s", this); + + synchronized (lock) + { + List<Session> values = new ArrayList<Session>(channels.values()); + for (Session ssn : values) + { + ssn.closed(); + } + + try + { + sender.close(); + } + catch(Exception e) + { + // ignore. + } + sender = null; + setState(CLOSED); + } + + for (ConnectionListener listener: listeners) + { + listener.closed(this); + } + } + + public void close() + { + 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) + { + switch (state) + { + case OPEN: + state = CLOSING; + connectionClose(replyCode, replyText, _options); + Waiter w = new Waiter(lock, timeout); + while (w.hasTime() && state == CLOSING && error == null) + { + w.await(); + } + + if (error != null) + { + close(replyCode, replyText, _options); + throw new ConnectionException(error); + } + + switch (state) + { + case CLOSING: + close(replyCode, replyText, _options); + throw new ConnectionException("close() timed out"); + case CLOSED: + break; + default: + throw new IllegalStateException(String.valueOf(state)); + } + break; + case CLOSED: + break; + default: + if (sender != null) + { + sender.close(); + w = new Waiter(lock, timeout); + while (w.hasTime() && sender != null && error == null) + { + w.await(); + } + + if (error != null) + { + throw new ConnectionException(error); + } + + if (sender != null) + { + throw new ConnectionException("close() timed out"); + } + } + break; + } + } + } + + public void setIdleTimeout(int i) + { + idleTimeout = i; + if (sender != null) + { + sender.setIdleTimeout(i); + } + } + + public int getIdleTimeout() + { + return idleTimeout; + } + + public void setAuthorizationID(String authorizationID) + { + _authorizationID = authorizationID; + } + + public String getAuthorizationID() + { + return _authorizationID; + } + + public String getUserID() + { + return userID; + } + + public void setUserID(String id) + { + userID = id; + } + + public void setServerProperties(final Map<String, Object> serverProperties) + { + _serverProperties = serverProperties == null ? Collections.EMPTY_MAP : serverProperties; + } + + public Map<String, Object> getServerProperties() + { + return _serverProperties; + } + + public String toString() + { + return String.format("conn:%x", System.identityHashCode(this)); + } + + public ConnectionSettings getConnectionSettings() + { + return conSettings; + } + + public SecurityLayer getSecurityLayer() + { + return securityLayer; + } + + public boolean isConnectionResuming() + { + return connectionLost.get(); + } + + protected Collection<Session> getChannels() + { + return channels.values(); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/ConnectionDelegate.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/ConnectionDelegate.java new file mode 100644 index 0000000000..88dd2d6afa --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/ConnectionDelegate.java @@ -0,0 +1,102 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.transport.util.Logger; + +import static org.apache.qpid.transport.Connection.State.*; + + +/** + * ConnectionDelegate + * + * @author Rafael H. Schloming + */ + +/** + * Currently only implemented client specific methods + * the server specific methods are dummy impls for testing + * + * the connectionClose is kind of different for both sides + */ +public abstract class ConnectionDelegate + extends MethodDelegate<Connection> + implements ProtocolDelegate<Connection> +{ + + private static final Logger log = Logger.get(ConnectionDelegate.class); + + public void control(Connection conn, Method method) + { + method.dispatch(conn, this); + } + + public void command(Connection conn, Method method) + { + method.dispatch(conn, this); + } + + public void error(Connection conn, ProtocolError error) + { + conn.exception(new ConnectionException(error.getMessage())); + } + + public void handle(Connection conn, Method method) + { + conn.dispatch(method); + } + + @Override public void connectionHeartbeat(Connection conn, ConnectionHeartbeat hearbeat) + { + // do nothing + } + + @Override public void connectionClose(Connection conn, ConnectionClose close) + { + conn.connectionCloseOk(); + conn.getSender().close(); + conn.closeCode(close); + conn.setState(CLOSE_RCVD); + } + + @Override public void connectionCloseOk(Connection conn, ConnectionCloseOk ok) + { + conn.getSender().close(); + } + + @Override public void sessionDetach(Connection conn, SessionDetach dtc) + { + Session ssn = conn.getSession(dtc.getChannel()); + ssn.sessionDetached(dtc.getName(), SessionDetachCode.NORMAL); + conn.unmap(ssn); + ssn.closed(); + } + + @Override public void sessionDetached(Connection conn, SessionDetached dtc) + { + Session ssn = conn.getSession(dtc.getChannel()); + if (ssn != null) + { + conn.unmap(ssn); + ssn.closed(); + } + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/ConnectionException.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/ConnectionException.java new file mode 100644 index 0000000000..6d3972eb43 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/ConnectionException.java @@ -0,0 +1,70 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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; + + +/** + * ConnectionException + * + */ + +public class ConnectionException extends TransportException +{ + + private ConnectionClose close; + + public ConnectionException(String message, ConnectionClose close, Throwable cause) + { + super(message, cause); + this.close = close; + } + + public ConnectionException(String message) + { + this(message, null, null); + } + + public ConnectionException(String message, Throwable cause) + { + this(message, null, cause); + } + + public ConnectionException(Throwable cause) + { + this(cause.getMessage(), null, cause); + } + + public ConnectionException(ConnectionClose close) + { + this(close.getReplyText(), close, null); + } + + public ConnectionClose getClose() + { + return close; + } + + @Override public void rethrow() + { + throw new ConnectionException(getMessage(), close, this); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/ConnectionListener.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/ConnectionListener.java new file mode 100644 index 0000000000..616e76825a --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/ConnectionListener.java @@ -0,0 +1,38 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.transport; + + +/** + * ConnectionListener + * + */ + +public interface ConnectionListener +{ + + void opened(Connection connection); + + void exception(Connection connection, ConnectionException exception); + + void closed(Connection connection); + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/ConnectionSettings.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/ConnectionSettings.java new file mode 100644 index 0000000000..08678b213b --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/ConnectionSettings.java @@ -0,0 +1,336 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util.Map; + +/** + * A ConnectionSettings object can only be associated with + * one Connection object. I have added an assertion that will + * throw an exception if it is used by more than on Connection + * + */ +public class ConnectionSettings +{ + String protocol = "tcp"; + String host = "localhost"; + String vhost; + String username = "guest"; + String password = "guest"; + int port = 5672; + boolean tcpNodelay = Boolean.getBoolean("amqj.tcp_nodelay"); + int maxChannelCount = 32767; + int maxFrameSize = 65535; + int heartbeatInterval; + int readBufferSize = 65535; + int writeBufferSize = 65535; + long transportTimeout = 60000; + + // SSL props + boolean useSSL; + String keyStorePath = System.getProperty("javax.net.ssl.keyStore"); + String keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword"); + String keyStoreCertType = System.getProperty("qpid.ssl.keyStoreCertType","SunX509");; + String trustStoreCertType = System.getProperty("qpid.ssl.trustStoreCertType","SunX509");; + String trustStorePath = System.getProperty("javax.net.ssl.trustStore");; + String trustStorePassword = System.getProperty("javax.net.ssl.trustStorePassword");; + String certAlias; + boolean verifyHostname; + + // SASL props + 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; + + private Map<String, Object> _clientProperties; + + public boolean isTcpNodelay() + { + return tcpNodelay; + } + + public void setTcpNodelay(boolean tcpNodelay) + { + this.tcpNodelay = tcpNodelay; + } + + public int getHeartbeatInterval() + { + return heartbeatInterval; + } + + public void setHeartbeatInterval(int heartbeatInterval) + { + this.heartbeatInterval = heartbeatInterval; + } + + public String getProtocol() + { + return protocol; + } + + public void setProtocol(String protocol) + { + this.protocol = protocol; + } + + public String getHost() + { + return host; + } + + public void setHost(String host) + { + this.host = host; + } + + public int getPort() + { + return port; + } + + public void setPort(int port) + { + this.port = port; + } + + public String getVhost() + { + return vhost; + } + + public void setVhost(String vhost) + { + this.vhost = vhost; + } + + public String getUsername() + { + return username; + } + + public void setUsername(String username) + { + this.username = username; + } + + public String getPassword() + { + return password; + } + + public void setPassword(String password) + { + this.password = password; + } + + public boolean isUseSSL() + { + return useSSL; + } + + public void setUseSSL(boolean useSSL) + { + this.useSSL = useSSL; + } + + public boolean isUseSASLEncryption() + { + return useSASLEncryption; + } + + public void setUseSASLEncryption(boolean useSASLEncryption) + { + this.useSASLEncryption = useSASLEncryption; + } + + public String getSaslMechs() + { + return saslMechs; + } + + public void setSaslMechs(String saslMechs) + { + this.saslMechs = saslMechs; + } + + public String getSaslProtocol() + { + return saslProtocol; + } + + public void setSaslProtocol(String saslProtocol) + { + this.saslProtocol = saslProtocol; + } + + public String getSaslServerName() + { + return saslServerName; + } + + public void setSaslServerName(String saslServerName) + { + this.saslServerName = saslServerName; + } + + public int getMaxChannelCount() + { + return maxChannelCount; + } + + public void setMaxChannelCount(int maxChannelCount) + { + this.maxChannelCount = maxChannelCount; + } + + public int getMaxFrameSize() + { + return maxFrameSize; + } + + public void setMaxFrameSize(int maxFrameSize) + { + this.maxFrameSize = maxFrameSize; + } + + public void setClientProperties(final Map<String, Object> clientProperties) + { + _clientProperties = clientProperties; + } + + public Map<String, Object> getClientProperties() + { + return _clientProperties; + } + + public String getKeyStorePath() + { + return keyStorePath; + } + + public void setKeyStorePath(String keyStorePath) + { + this.keyStorePath = keyStorePath; + } + + public String getKeyStorePassword() + { + return keyStorePassword; + } + + public void setKeyStorePassword(String keyStorePassword) + { + this.keyStorePassword = keyStorePassword; + } + + public String getTrustStorePath() + { + return trustStorePath; + } + + public void setTrustStorePath(String trustStorePath) + { + this.trustStorePath = trustStorePath; + } + + public String getTrustStorePassword() + { + return trustStorePassword; + } + + public void setTrustStorePassword(String trustStorePassword) + { + this.trustStorePassword = trustStorePassword; + } + + public String getCertAlias() + { + return certAlias; + } + + public void setCertAlias(String certAlias) + { + this.certAlias = certAlias; + } + + public boolean isVerifyHostname() + { + return verifyHostname; + } + + public void setVerifyHostname(boolean verifyHostname) + { + this.verifyHostname = verifyHostname; + } + + public String getKeyStoreCertType() + { + return keyStoreCertType; + } + + public void setKeyStoreCertType(String keyStoreCertType) + { + this.keyStoreCertType = keyStoreCertType; + } + + public String getTrustStoreCertType() + { + return trustStoreCertType; + } + + public void setTrustStoreCertType(String trustStoreCertType) + { + this.trustStoreCertType = trustStoreCertType; + } + + public int getReadBufferSize() + { + return readBufferSize; + } + + public void setReadBufferSize(int readBufferSize) + { + this.readBufferSize = readBufferSize; + } + + public int getWriteBufferSize() + { + return writeBufferSize; + } + + public void setWriteBufferSize(int writeBufferSize) + { + this.writeBufferSize = writeBufferSize; + } + + public long getTransportTimeout() + { + return transportTimeout; + } + + public void setTransportTimeout(long transportTimeout) + { + this.transportTimeout = transportTimeout; + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/Field.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/Field.java new file mode 100644 index 0000000000..bc6bf10041 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/Field.java @@ -0,0 +1,83 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.transport.codec.Decoder; +import org.apache.qpid.transport.codec.Encoder; + + +/** + * Field + * + */ + +public abstract class Field<C,T> +{ + + private final Class<C> container; + private final Class<T> type; + private final String name; + private final int index; + + Field(Class<C> container, Class<T> type, String name, int index) + { + this.container = container; + this.type = type; + this.name = name; + this.index = index; + } + + public final Class<C> getContainer() + { + return container; + } + + public final Class<T> getType() + { + return type; + } + + public final String getName() + { + return name; + } + + public final int getIndex() + { + return index; + } + + protected final C check(Object struct) + { + return container.cast(struct); + } + + public abstract boolean has(Object struct); + + public abstract void has(Object struct, boolean value); + + public abstract T get(Object struct); + + public abstract void read(Decoder dec, Object struct); + + public abstract void write(Encoder enc, Object struct); + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/Future.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/Future.java new file mode 100644 index 0000000000..d8cde61af5 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/Future.java @@ -0,0 +1,37 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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; + + +/** + * Future + * + * @author Rafael H. Schloming + */ + +public interface Future<T> +{ + + T get(); + + boolean isDone(); + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/Header.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/Header.java new file mode 100644 index 0000000000..9439e5e0de --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/Header.java @@ -0,0 +1,92 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.transport.network.Frame; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.LinkedHashMap; +import java.nio.ByteBuffer; + + +/** + * Header + * + * @author Rafael H. Schloming + */ + +public class Header { + + private final Struct[] structs; + + public Header(List<Struct> structs) + { + this(structs.toArray(new Struct[structs.size()])); + } + + public Header(Struct ... structs) + { + this.structs = structs; + } + + public Struct[] getStructs() + { + return structs; + } + + + public <T> T get(Class<T> klass) + { + for (Struct st : structs) + { + if (klass.isInstance(st)) + { + return (T) st; + } + } + + return null; + } + + public String toString() + { + StringBuffer str = new StringBuffer(); + str.append(" Header("); + boolean first = true; + for (Struct s : structs) + { + if (first) + { + first = false; + } + else + { + str.append(", "); + } + str.append(s); + } + str.append(")"); + return str.toString(); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/Method.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/Method.java new file mode 100644 index 0000000000..3c80180d0b --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/Method.java @@ -0,0 +1,236 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.transport.network.Frame; + +import java.nio.ByteBuffer; + +import static org.apache.qpid.transport.util.Functions.*; + +/** + * Method + * + * @author Rafael H. Schloming + */ + +public abstract class Method extends Struct implements ProtocolEvent +{ + + + public static final Method create(int type) + { + // XXX: should generate separate factories for separate + // namespaces + return (Method) StructFactory.createInstruction(type); + } + + // XXX: command subclass? + public static interface CompletionListener + { + public void onComplete(Method method); + } + + private int id; + private int channel; + private boolean idSet = false; + private boolean sync = false; + private boolean batch = false; + private boolean unreliable = false; + private CompletionListener completionListener; + + public final int getId() + { + return id; + } + + void setId(int id) + { + this.id = id; + this.idSet = true; + } + + boolean idSet() + { + return idSet; + } + + public final int getChannel() + { + return channel; + } + + public final void setChannel(int channel) + { + this.channel = channel; + } + + public final boolean isSync() + { + return sync; + } + + public final void setSync(boolean value) + { + this.sync = value; + } + + public final boolean isBatch() + { + return batch; + } + + final void setBatch(boolean value) + { + this.batch = value; + } + + public final boolean isUnreliable() + { + return unreliable; + } + + final void setUnreliable(boolean value) + { + this.unreliable = value; + } + + public abstract boolean hasPayload(); + + public Header getHeader() + { + return null; + } + + public void setHeader(Header header) + { + throw new UnsupportedOperationException(); + } + + public ByteBuffer getBody() + { + return null; + } + + public void setBody(ByteBuffer body) + { + throw new UnsupportedOperationException(); + } + + public int getBodySize() + { + ByteBuffer body = getBody(); + if (body == null) + { + return 0; + } + else + { + return body.remaining(); + } + } + + public abstract byte getEncodedTrack(); + + public abstract <C> void dispatch(C context, MethodDelegate<C> delegate); + + public <C> void delegate(C context, ProtocolDelegate<C> delegate) + { + if (getEncodedTrack() == Frame.L4) + { + delegate.command(context, this); + } + else + { + delegate.control(context, this); + } + } + + + public void setCompletionListener(CompletionListener completionListener) + { + this.completionListener = completionListener; + } + + public void complete() + { + if(completionListener!= null) + { + completionListener.onComplete(this); + completionListener = null; + } + } + + public boolean hasCompletionListener() + { + return completionListener != null; + } + + public String toString() + { + StringBuilder str = new StringBuilder(); + + str.append("ch="); + str.append(channel); + + if (getEncodedTrack() == Frame.L4 && idSet) + { + str.append(" id="); + str.append(id); + } + + if (sync || batch) + { + str.append(" "); + str.append("["); + if (sync) + { + str.append("S"); + } + if (batch) + { + str.append("B"); + } + str.append("]"); + } + + str.append(" "); + str.append(super.toString()); + Header hdr = getHeader(); + if (hdr != null) + { + for (Struct st : hdr.getStructs()) + { + str.append("\n "); + str.append(st); + } + } + ByteBuffer body = getBody(); + if (body != null) + { + str.append("\n body="); + str.append(str(body, 64)); + } + + return str.toString(); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/NetworkDriver.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/NetworkDriver.java new file mode 100644 index 0000000000..86af97bf7e --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/qpid/transport/NetworkDriverConfiguration.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/NetworkDriverConfiguration.java new file mode 100644 index 0000000000..c38afe5dd5 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/NetworkDriverConfiguration.java @@ -0,0 +1,44 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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; + +/** + * This interface provides a means for NetworkDrivers to configure TCP options such as incoming and outgoing + * 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 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(); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/OpenException.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/OpenException.java new file mode 100644 index 0000000000..68fbb5e8ec --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/OpenException.java @@ -0,0 +1,34 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.transport; + +import java.io.IOException; + +public class OpenException extends IOException +{ + + public OpenException(String string, Throwable lastException) + { + super(string, lastException); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/ProtocolDelegate.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/ProtocolDelegate.java new file mode 100644 index 0000000000..a90948fc1d --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/ProtocolDelegate.java @@ -0,0 +1,40 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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; + + +/** + * ProtocolDelegate + * + */ + +public interface ProtocolDelegate<C> +{ + + void init(C context, ProtocolHeader header); + + void control(C context, Method control); + + void command(C context, Method command); + + void error(C context, ProtocolError error); + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/ProtocolError.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/ProtocolError.java new file mode 100644 index 0000000000..8a5edc302e --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/ProtocolError.java @@ -0,0 +1,88 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.transport.network.NetworkDelegate; +import org.apache.qpid.transport.network.NetworkEvent; + + +/** + * ProtocolError + * + * @author Rafael H. Schloming + */ + +public final class ProtocolError implements NetworkEvent, ProtocolEvent +{ + + private int channel; + private final byte track; + private final String format; + private final Object[] args; + + public ProtocolError(byte track, String format, Object ... args) + { + this.track = track; + this.format = format; + this.args = args; + } + + public int getChannel() + { + return channel; + } + + public void setChannel(int channel) + { + this.channel = channel; + } + + public byte getEncodedTrack() + { + return track; + } + + public boolean isConnectionControl() + { + return false; + } + + public String getMessage() + { + return String.format(format, args); + } + + public <C> void delegate(C context, ProtocolDelegate<C> delegate) + { + delegate.error(context, this); + } + + public void delegate(NetworkDelegate delegate) + { + delegate.error(this); + } + + public String toString() + { + return String.format("protocol error: %s", getMessage()); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/ProtocolEvent.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/ProtocolEvent.java new file mode 100644 index 0000000000..b51a540701 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/ProtocolEvent.java @@ -0,0 +1,41 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.transport; + + +/** + * ProtocolEvent + * + */ + +public interface ProtocolEvent +{ + + int getChannel(); + + void setChannel(int channel); + + byte getEncodedTrack(); + + <C> void delegate(C context, ProtocolDelegate<C> delegate); + + boolean isConnectionControl(); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/ProtocolHeader.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/ProtocolHeader.java new file mode 100644 index 0000000000..e5b93e40a9 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/ProtocolHeader.java @@ -0,0 +1,123 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.NetworkDelegate; +import org.apache.qpid.transport.network.NetworkEvent; +import org.apache.qpid.transport.network.Frame; + + +/** + * ProtocolHeader + * + * @author Rafael H. Schloming + */ + +public final class ProtocolHeader implements NetworkEvent, ProtocolEvent +{ + + private static final byte[] AMQP = {'A', 'M', 'Q', 'P' }; + private static final byte CLASS = 1; + + final private byte protoClass; + final private byte instance; + final private byte major; + final private byte minor; + private int channel; + + public ProtocolHeader(byte protoClass, byte instance, byte major, byte minor) + { + this.protoClass = protoClass; + this.instance = instance; + this.major = major; + this.minor = minor; + } + + public ProtocolHeader(int instance, int major, int minor) + { + this(CLASS, (byte) instance, (byte) major, (byte) minor); + } + + public byte getInstance() + { + return instance; + } + + public byte getMajor() + { + return major; + } + + public byte getMinor() + { + return minor; + } + + public int getChannel() + { + return channel; + } + + public void setChannel(int channel) + { + this.channel = channel; + } + + public byte getEncodedTrack() + { + return Frame.L1; + } + + public boolean isConnectionControl() + { + return false; + } + + public ByteBuffer toByteBuffer() + { + ByteBuffer buf = ByteBuffer.allocate(8); + buf.put(AMQP); + buf.put(protoClass); + buf.put(instance); + buf.put(major); + buf.put(minor); + buf.flip(); + return buf; + } + + public <C> void delegate(C context, ProtocolDelegate<C> delegate) + { + delegate.init(context, this); + } + + public void delegate(NetworkDelegate delegate) + { + delegate.init(this); + } + + public String toString() + { + return String.format("AMQP.%d %d-%d", instance, major, minor); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/ProtocolVersionException.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/ProtocolVersionException.java new file mode 100644 index 0000000000..db8064268c --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/ProtocolVersionException.java @@ -0,0 +1,62 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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; + + +/** + * ProtocolVersionException + * + */ + +public final class ProtocolVersionException extends ConnectionException +{ + + private final byte major; + private final byte minor; + + public ProtocolVersionException(byte major, byte minor, Throwable cause) + { + super(String.format("version mismatch: %s-%s", major, minor), cause); + this.major = major; + this.minor = minor; + } + + public ProtocolVersionException(byte major, byte minor) + { + this(major, minor, null); + } + + public byte getMajor() + { + return this.major; + } + + public byte getMinor() + { + return this.minor; + } + + @Override public void rethrow() + { + throw new ProtocolVersionException(major, minor, this); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/ProtocolViolationException.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/ProtocolViolationException.java new file mode 100644 index 0000000000..6787157e8e --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/ProtocolViolationException.java @@ -0,0 +1,35 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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; + + +/** + * ProtocolViolationException + * + */ + +public final class ProtocolViolationException extends ConnectionException +{ + public ProtocolViolationException(String msg,Throwable cause) + { + super(msg, cause); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/Range.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/Range.java new file mode 100644 index 0000000000..f4335dc8a6 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/Range.java @@ -0,0 +1,125 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util.ArrayList; +import java.util.List; + +import static org.apache.qpid.util.Serial.*; + + +/** + * Range + * + * @author Rafael H. Schloming + */ + +public final class Range +{ + private final int lower; + private final int upper; + + public Range(int lower, int upper) + { + this.lower = lower; + this.upper = upper; + } + + public int getLower() + { + return lower; + } + + public int getUpper() + { + return upper; + } + + public boolean includes(int value) + { + return le(lower, value) && le(value, upper); + } + + public boolean includes(Range range) + { + return includes(range.lower) && includes(range.upper); + } + + public boolean intersects(Range range) + { + return (includes(range.lower) || includes(range.upper) || + range.includes(lower) || range.includes(upper)); + } + + public boolean touches(Range range) + { + return (intersects(range) || + includes(range.upper + 1) || includes(range.lower - 1) || + range.includes(upper + 1) || range.includes(lower - 1)); + } + + public Range span(Range range) + { + return new Range(min(lower, range.lower), max(upper, range.upper)); + } + + public List<Range> subtract(Range range) + { + List<Range> result = new ArrayList<Range>(); + + if (includes(range.lower) && le(lower, range.lower - 1)) + { + result.add(new Range(lower, range.lower - 1)); + } + + if (includes(range.upper) && le(range.upper + 1, upper)) + { + result.add(new Range(range.upper + 1, upper)); + } + + if (result.isEmpty() && !range.includes(this)) + { + result.add(this); + } + + return result; + } + + public Range intersect(Range range) + { + int l = max(lower, range.lower); + int r = min(upper, range.upper); + if (gt(l, r)) + { + return null; + } + else + { + return new Range(l, r); + } + } + + public String toString() + { + return "[" + lower + ", " + upper + "]"; + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/RangeSet.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/RangeSet.java new file mode 100644 index 0000000000..3850dc162b --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/RangeSet.java @@ -0,0 +1,152 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util.Iterator; +import java.util.ListIterator; +import java.util.LinkedList; + +import static org.apache.qpid.util.Serial.*; + +/** + * RangeSet + * + * @author Rafael H. Schloming + */ + +public final class RangeSet implements Iterable<Range> +{ + + private LinkedList<Range> ranges = new LinkedList<Range>(); + + public int size() + { + return ranges.size(); + } + + public Iterator<Range> iterator() + { + return ranges.iterator(); + } + + public Range getFirst() + { + return ranges.getFirst(); + } + + public Range getLast() + { + return ranges.getLast(); + } + + public boolean includes(Range range) + { + for (Range r : this) + { + if (r.includes(range)) + { + return true; + } + } + + return false; + } + + public boolean includes(int n) + { + for (Range r : this) + { + if (r.includes(n)) + { + return true; + } + } + + return false; + } + + public void add(Range range) + { + ListIterator<Range> it = ranges.listIterator(); + + while (it.hasNext()) + { + Range next = it.next(); + if (range.touches(next)) + { + it.remove(); + range = range.span(next); + } + else if (lt(range.getUpper(), next.getLower())) + { + it.previous(); + it.add(range); + return; + } + } + + it.add(range); + } + + public void add(int lower, int upper) + { + add(new Range(lower, upper)); + } + + public void add(int value) + { + add(value, value); + } + + public void clear() + { + ranges.clear(); + } + + public RangeSet copy() + { + RangeSet copy = new RangeSet(); + copy.ranges.addAll(ranges); + return copy; + } + + public String toString() + { + StringBuffer str = new StringBuffer(); + str.append("{"); + boolean first = true; + for (Range range : ranges) + { + if (first) + { + first = false; + } + else + { + str.append(", "); + } + str.append(range); + } + str.append("}"); + return str.toString(); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/Receiver.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/Receiver.java new file mode 100644 index 0000000000..2a994580dc --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/Receiver.java @@ -0,0 +1,38 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.transport; + + +/** + * Receiver + * + */ + +public interface Receiver<T> +{ + + void received(T msg); + + void exception(Throwable t); + + void closed(); + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/Sender.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/Sender.java new file mode 100644 index 0000000000..6519702c76 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/Sender.java @@ -0,0 +1,39 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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; + + +/** + * Sender + * + */ + +public interface Sender<T> +{ + void setIdleTimeout(int i); + + void send(T msg); + + void flush(); + + void close(); + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/SenderException.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/SenderException.java new file mode 100644 index 0000000000..a96079dc27 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/SenderException.java @@ -0,0 +1,52 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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; + + +/** + * SenderException + * + */ + +public class SenderException extends TransportException +{ + + public SenderException(String message, Throwable cause) + { + super(message, cause); + } + + public SenderException(String message) + { + super(message); + } + + public SenderException(Throwable cause) + { + super(cause); + } + + public void rethrow() + { + throw new SenderException(getMessage(), this); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/ServerDelegate.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/ServerDelegate.java new file mode 100644 index 0000000000..f21df251da --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/ServerDelegate.java @@ -0,0 +1,203 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 static org.apache.qpid.transport.Connection.State.*; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * ServerDelegate + */ +public class ServerDelegate extends ConnectionDelegate +{ + protected static final Logger _logger = LoggerFactory.getLogger(ServerDelegate.class); + + private List<Object> _locales; + private List<Object> _mechanisms; + private Map<String, Object> _clientProperties; + + + public ServerDelegate() + { + this(null, Collections.emptyList(), Collections.singletonList((Object)"utf8")); + } + + protected ServerDelegate(Map<String, Object> clientProperties, List<Object> mechanisms, List<Object> locales) + { + _clientProperties = clientProperties; + _mechanisms = mechanisms; + _locales = locales; + } + + public void init(Connection conn, ProtocolHeader hdr) + { + conn.send(new ProtocolHeader(1, 0, 10)); + + conn.connectionStart(_clientProperties, _mechanisms, _locales); + } + + @Override + public void connectionStartOk(Connection conn, ConnectionStartOk ok) + { + conn.setLocale(ok.getLocale()); + String mechanism = ok.getMechanism(); + + String clientName = (String) ok.getClientProperties().get("clientName"); + conn.setClientId(clientName); + + if (mechanism == null || mechanism.length() == 0) + { + conn.connectionTune + (getChannelMax(), + org.apache.qpid.transport.network.ConnectionBinding.MAX_FRAME_SIZE, + 0, getHeartbeatMax()); + return; + } + + try + { + + SaslServer ss = createSaslServer(mechanism); + if (ss == null) + { + conn.connectionClose(ConnectionCloseCode.CONNECTION_FORCED, + "null SASL mechanism: " + mechanism); + return; + } + conn.setSaslServer(ss); + secure(conn, ok.getResponse()); + } + catch (SaslException e) + { + conn.exception(e); + conn.connectionClose(ConnectionCloseCode.CONNECTION_FORCED, e.getMessage()); + } + } + + protected SaslServer createSaslServer(String mechanism) + throws SaslException + { + SaslServer ss = Sasl.createSaslServer(mechanism, "AMQP", "localhost", null, null); + return ss; + } + + private void secure(Connection conn, byte[] response) + { + SaslServer ss = conn.getSaslServer(); + try + { + byte[] challenge = ss.evaluateResponse(response); + if (ss.isComplete()) + { + ss.dispose(); + conn.connectionTune + (getChannelMax(), + org.apache.qpid.transport.network.ConnectionBinding.MAX_FRAME_SIZE, + 0, getHeartbeatMax()); + conn.setAuthorizationID(ss.getAuthorizationID()); + } + else + { + conn.connectionSecure(challenge); + } + } + catch (SaslException e) + { + conn.exception(e); + conn.connectionClose(ConnectionCloseCode.CONNECTION_FORCED, e.getMessage()); + } + } + + protected int getHeartbeatMax() + { + return 0xFFFF; + } + + protected int getChannelMax() + { + return 0xFFFF; + } + + @Override + public void connectionSecureOk(Connection conn, ConnectionSecureOk ok) + { + secure(conn, ok.getResponse()); + } + + @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 + public void connectionOpen(Connection conn, ConnectionOpen open) + { + conn.connectionOpenOk(Collections.emptyList()); + + conn.setState(OPEN); + } + + protected Session getSession(Connection conn, SessionDelegate delegate, SessionAttach atc) + { + return new Session(conn, delegate, new Binary(atc.getName()), 0); + } + + + public Session getSession(Connection conn, SessionAttach atc) + { + return new Session(conn, new Binary(atc.getName()), 0); + } + + @Override + public void sessionAttach(Connection conn, SessionAttach atc) + { + Session ssn = getSession(conn, atc); + conn.map(ssn, atc.getChannel()); + ssn.sessionAttached(atc.getName()); + ssn.setState(Session.State.OPEN); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/Session.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/Session.java new file mode 100644 index 0000000000..862c37283b --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/Session.java @@ -0,0 +1,1057 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 static org.apache.qpid.transport.Option.COMPLETED; +import static org.apache.qpid.transport.Option.SYNC; +import static org.apache.qpid.transport.Option.TIMELY_REPLY; +import static org.apache.qpid.transport.Session.State.CLOSED; +import static org.apache.qpid.transport.Session.State.CLOSING; +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.transport.network.Frame; +import static org.apache.qpid.transport.util.Functions.mod; +import org.apache.qpid.transport.util.Logger; +import org.apache.qpid.transport.util.Waiter; +import static org.apache.qpid.util.Serial.ge; +import static org.apache.qpid.util.Serial.gt; +import static org.apache.qpid.util.Serial.le; +import static org.apache.qpid.util.Serial.lt; +import static org.apache.qpid.util.Serial.max; +import static org.apache.qpid.util.Strings.toUTF8; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +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; + +/** + * Session + * + * @author Rafael H. Schloming + */ + +public class Session extends SessionInvoker +{ + + private static final Logger log = Logger.get(Session.class); + + public enum State { NEW, DETACHED, RESUMING, OPEN, CLOSING, CLOSED } + + static class DefaultSessionListener implements SessionListener + { + + public void opened(Session ssn) {} + + public void resumed(Session ssn) {} + + public void message(Session ssn, MessageTransfer xfr) + { + log.info("message: %s", xfr); + } + + public void exception(Session ssn, SessionException exc) + { + log.error(exc, "session exception"); + } + + public void closed(Session ssn) {} + } + + public static final int UNLIMITED_CREDIT = 0xFFFFFFFF; + + private Connection connection; + private Binary name; + private long expiry; + private boolean closing; + private int channel; + private SessionDelegate delegate; + private SessionListener listener = new DefaultSessionListener(); + private long timeout = 60000; + private boolean autoSync = false; + + private boolean incomingInit; + // incoming command count + private int commandsIn; + // completed incoming commands + private final Object processedLock = new Object(); + private RangeSet processed; + private int maxProcessed; + private int syncPoint; + + // outgoing command count + private int commandsOut = 0; + private Method[] commands = new Method[Integer.getInteger("qpid.session.command_limit", 64*1024)]; + private int commandBytes = 0; + private int byteLimit = Integer.getInteger("qpid.session.byte_limit", 1024*1024); + private int maxComplete = commandsOut - 1; + private boolean needSync = false; + + private State state = NEW; + + // transfer flow control + private volatile boolean flowControl = false; + private Semaphore credit = new Semaphore(0); + + private Thread resumer = null; + private boolean transacted = false; + + protected Session(Connection connection, Binary name, long expiry) + { + this(connection, new SessionDelegate(), name, expiry); + } + + protected Session(Connection connection, SessionDelegate delegate, Binary name, long expiry) + { + this.connection = connection; + this.delegate = delegate; + this.name = name; + this.expiry = expiry; + this.closing = false; + initReceiver(); + } + + public Connection getConnection() + { + return connection; + } + + public Binary getName() + { + return name; + } + + void setExpiry(long expiry) + { + this.expiry = expiry; + } + + void setClose(boolean close) + { + this.closing = close; + } + + public int getChannel() + { + return channel; + } + + void setChannel(int channel) + { + this.channel = channel; + } + + public void setSessionListener(SessionListener listener) + { + if (listener == null) + { + this.listener = new DefaultSessionListener(); + } + else + { + this.listener = listener; + } + } + + public SessionListener getSessionListener() + { + return listener; + } + + public void setAutoSync(boolean value) + { + synchronized (commands) + { + this.autoSync = value; + } + } + + protected void setState(State state) + { + synchronized (commands) + { + this.state = state; + commands.notifyAll(); + } + } + + void setFlowControl(boolean value) + { + flowControl = value; + } + + void addCredit(int value) + { + credit.release(value); + } + + void drainCredit() + { + credit.drainPermits(); + } + + void acquireCredit() + { + if (flowControl) + { + try + { + if (!credit.tryAcquire(timeout, TimeUnit.MILLISECONDS)) + { + throw new SessionException + ("timed out waiting for message credit"); + } + } + catch (InterruptedException e) + { + throw new SessionException + ("interrupted while waiting for credit", null, e); + } + } + } + + private void initReceiver() + { + synchronized (processedLock) + { + incomingInit = false; + processed = new RangeSet(); + } + } + + void attach() + { + initReceiver(); + sessionAttach(name.getBytes()); + sessionRequestTimeout(0);//use expiry here only if/when session resume is supported + } + + void resume() + { + synchronized (commands) + { + for (int i = maxComplete + 1; lt(i, commandsOut); i++) + { + Method m = commands[mod(i, commands.length)]; + if (m == null) + { + m = new ExecutionSync(); + m.setId(i); + } + 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)); + } + } + sessionCommandPoint(m.getId(), 0); + send(m); + } + + sessionCommandPoint(commandsOut, 0); + sessionFlush(COMPLETED); + resumer = Thread.currentThread(); + state = RESUMING; + listener.resumed(this); + resumer = null; + } + } + + void dump() + { + synchronized (commands) + { + for (Method m : commands) + { + if (m != null) + { + log.debug("%s", m); + } + } + } + } + + final void commandPoint(int id) + { + synchronized (processedLock) + { + this.commandsIn = id; + if (!incomingInit) + { + incomingInit = true; + maxProcessed = commandsIn - 1; + syncPoint = maxProcessed; + } + } + } + + public int getCommandsOut() + { + return commandsOut; + } + + public int getCommandsIn() + { + return commandsIn; + } + + public int nextCommandId() + { + return commandsIn++; + } + + final void identify(Method cmd) + { + if (!incomingInit) + { + throw new IllegalStateException(); + } + + int id = nextCommandId(); + cmd.setId(id); + + if(log.isDebugEnabled()) + { + log.debug("ID: [%s] %s", this.channel, id); + } + + //if ((id % 65536) == 0) + if ((id & 0xff) == 0) + { + flushProcessed(TIMELY_REPLY); + } + } + + public void processed(Method command) + { + processed(command.getId()); + } + + public void processed(int command) + { + processed(new Range(command, command)); + } + + public void processed(int lower, int upper) + { + + processed(new Range(lower, upper)); + } + + public void processed(Range range) + { + log.debug("%s processed(%s) %s %s", this, range, syncPoint, maxProcessed); + + boolean flush; + synchronized (processedLock) + { + log.debug("%s", processed); + + if (ge(range.getUpper(), commandsIn)) + { + throw new IllegalArgumentException + ("range exceeds max received command-id: " + range); + } + + processed.add(range); + Range first = processed.getFirst(); + int lower = first.getLower(); + int upper = first.getUpper(); + int old = maxProcessed; + if (le(lower, maxProcessed + 1)) + { + maxProcessed = max(maxProcessed, upper); + } + boolean synced = ge(maxProcessed, syncPoint); + flush = lt(old, syncPoint) && synced; + if (synced) + { + syncPoint = maxProcessed; + } + } + if (flush) + { + flushProcessed(); + } + } + + void flushExpected() + { + RangeSet rs = new RangeSet(); + synchronized (processedLock) + { + if (incomingInit) + { + rs.add(commandsIn); + } + } + sessionExpected(rs, null); + } + + public void flushProcessed(Option ... options) + { + RangeSet copy; + synchronized (processedLock) + { + copy = processed.copy(); + } + + synchronized (commands) + { + if (state == DETACHED || state == CLOSING) + { + return; + } + if (copy.size() > 0) + { + sessionCompleted(copy, options); + } + } + } + + void knownComplete(RangeSet kc) + { + synchronized (processedLock) + { + RangeSet newProcessed = new RangeSet(); + for (Range pr : processed) + { + for (Range kr : kc) + { + for (Range r : pr.subtract(kr)) + { + newProcessed.add(r); + } + } + } + this.processed = newProcessed; + } + } + + void syncPoint() + { + int id = getCommandsIn() - 1; + log.debug("%s synced to %d", this, id); + boolean flush; + synchronized (processedLock) + { + syncPoint = id; + flush = ge(maxProcessed, syncPoint); + } + if (flush) + { + flushProcessed(); + } + } + + protected boolean complete(int lower, int upper) + { + //avoid autoboxing + if(log.isDebugEnabled()) + { + log.debug("%s complete(%d, %d)", this, lower, upper); + } + synchronized (commands) + { + int old = maxComplete; + for (int id = max(maxComplete, lower); le(id, upper); id++) + { + int idx = mod(id, commands.length); + Method m = commands[idx]; + if (m != null) + { + commandBytes -= m.getBodySize(); + m.complete(); + commands[idx] = null; + } + } + if (le(lower, maxComplete + 1)) + { + maxComplete = max(maxComplete, upper); + } + log.debug("%s commands remaining: %s", this, commandsOut - maxComplete); + commands.notifyAll(); + return gt(maxComplete, old); + } + } + + void received(Method m) + { + m.delegate(this, delegate); + } + + private void send(Method m) + { + m.setChannel(channel); + connection.send(m); + + if (!m.isBatch()) + { + connection.flush(); + } + } + + protected boolean isFull(int id) + { + return isCommandsFull(id) || isBytesFull(); + } + + protected boolean isBytesFull() + { + return commandBytes >= byteLimit; + } + + protected boolean isCommandsFull(int id) + { + return id - maxComplete >= commands.length; + } + + public void invoke(Method m) + { + invoke(m,(Runnable)null); + } + + public void invoke(Method m, Runnable postIdSettingAction) + { + 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(); + } + + synchronized (commands) + { + if (state == DETACHED && m.isUnreliable()) + { + Thread current = Thread.currentThread(); + if (!current.equals(resumer)) + { + return; + } + } + + if (state != OPEN && state != CLOSED && state != CLOSING) + { + Thread current = Thread.currentThread(); + if (!current.equals(resumer)) + { + Waiter w = new Waiter(commands, timeout); + while (w.hasTime() && (state != OPEN && state != CLOSED)) + { + w.await(); + } + } + } + + switch (state) + { + case OPEN: + break; + case RESUMING: + Thread current = Thread.currentThread(); + if (!current.equals(resumer)) + { + throw new SessionException + ("timed out waiting for resume to finish"); + } + break; + case CLOSING: + case CLOSED: + ExecutionException exc = getException(); + if (exc != null) + { + throw new SessionException(exc); + } + else + { + throw new SessionClosedException(); + } + default: + throw new SessionException + (String.format + ("timed out waiting for session to become open " + + "(state=%s)", state)); + } + + int next; + next = commandsOut++; + m.setId(next); + if(postIdSettingAction != null) + { + postIdSettingAction.run(); + } + + if (isFull(next)) + { + Waiter w = new Waiter(commands, timeout); + while (w.hasTime() && isFull(next) && state != CLOSED) + { + if (state == OPEN || state == RESUMING) + { + try + { + sessionFlush(COMPLETED); + } + catch (SenderException e) + { + if (!closing) + { + // if expiry is > 0 then this will + // happen again on resume + log.error(e, "error sending flush (full replay buffer)"); + } + else + { + e.rethrow(); + } + } + } + w.await(); + } + } + + if (state == CLOSED) + { + ExecutionException exc = getException(); + if (exc != null) + { + throw new SessionException(exc); + } + else + { + throw new SessionClosedException(); + } + } + + if (isFull(next)) + { + throw new SessionException("timed out waiting for completion"); + } + + if (next == 0) + { + sessionCommandPoint(0, 0); + } + + boolean replayTransfer = !closing && !transacted && + m instanceof MessageTransfer && + ! m.isUnreliable(); + + if ((replayTransfer) || m.hasCompletionListener()) + { + commands[mod(next, commands.length)] = m; + commandBytes += m.getBodySize(); + } + if (autoSync) + { + m.setSync(true); + } + needSync = !m.isSync(); + + try + { + send(m); + } + catch (SenderException e) + { + if (!closing) + { + // if we are not closing then this will happen + // again on resume + log.error(e, "error sending command"); + } + else + { + e.rethrow(); + } + } + if (autoSync) + { + sync(); + } + + // flush every 64K commands to avoid ambiguity on + // wraparound + if (shouldIssueFlush(next)) + { + try + { + sessionFlush(COMPLETED); + } + catch (SenderException e) + { + if (!closing) + { + // if expiry is > 0 then this will happen + // again on resume + log.error(e, "error sending flush (periodic)"); + } + else + { + e.rethrow(); + } + } + } + } + } + else + { + send(m); + } + } + + protected boolean shouldIssueFlush(int next) + { + return (next % 65536) == 0; + } + + public void sync() + { + sync(timeout); + } + + public void sync(long timeout) + { + log.debug("%s sync()", this); + synchronized (commands) + { + int point = commandsOut - 1; + + if (needSync && lt(maxComplete, point)) + { + executionSync(SYNC); + } + + Waiter w = new Waiter(commands, timeout); + while (w.hasTime() && state != CLOSED && lt(maxComplete, point)) + { + log.debug("%s waiting for[%d]: %d, %s", this, point, maxComplete, commands); + w.await(); + } + + if (lt(maxComplete, point)) + { + if (state != CLOSED) + { + throw new SessionException( + String.format("timed out waiting for sync: complete = %s, point = %s", + maxComplete, point)); + } + else + { + ExecutionException ee = getException(); + if (ee != null) + { + throw new SessionException(ee); + } + } + } + } + } + + private Map<Integer,ResultFuture<?>> results = new HashMap<Integer,ResultFuture<?>>(); + private ExecutionException exception = null; + + void result(int command, Struct result) + { + ResultFuture<?> future; + synchronized (results) + { + future = results.remove(command); + } + + if (future != null) + { + future.set(result); + } + else + { + log.warn("Received a response to a command" + + " that's no longer valid on the client side." + + " [ command id : %s , result : %s ]",command, result); + } + } + + void setException(ExecutionException exc) + { + synchronized (results) + { + if (exception != null) + { + throw new IllegalStateException( + String.format("too many exceptions: %s, %s", exception, exc)); + } + exception = exc; + } + } + + private ConnectionClose close = null; + + void closeCode(ConnectionClose close) + { + this.close = close; + } + + ExecutionException getException() + { + synchronized (results) + { + return exception; + } + } + + protected <T> Future<T> invoke(Method m, Class<T> klass) + { + synchronized (commands) + { + int command = commandsOut; + ResultFuture<T> future = new ResultFuture<T>(klass); + synchronized (results) + { + results.put(command, future); + } + invoke(m); + return future; + } + } + + private class ResultFuture<T> implements Future<T> + { + + private final Class<T> klass; + private T result; + + private ResultFuture(Class<T> klass) + { + this.klass = klass; + } + + private void set(Struct result) + { + synchronized (this) + { + this.result = klass.cast(result); + notifyAll(); + } + } + + public T get(long timeout) + { + synchronized (this) + { + Waiter w = new Waiter(this, timeout); + while (w.hasTime() && state != CLOSED && !isDone()) + { + log.debug("%s waiting for result: %s", Session.this, this); + w.await(); + } + } + + if (isDone()) + { + return result; + } + else if (state == CLOSED) + { + throw new SessionException(getException()); + } + else + { + throw new SessionException( + String.format("%s timed out waiting for result: %s", + Session.this, this)); + } + } + + public T get() + { + return get(timeout); + } + + public boolean isDone() + { + return result != null; + } + + public String toString() + { + return String.format("Future(%s)", isDone() ? result : klass); + } + + } + + public final void messageTransfer(String destination, + MessageAcceptMode acceptMode, + MessageAcquireMode acquireMode, + Header header, + byte[] body, + Option ... _options) { + messageTransfer(destination, acceptMode, acquireMode, header, + ByteBuffer.wrap(body), _options); + } + + public final void messageTransfer(String destination, + MessageAcceptMode acceptMode, + MessageAcquireMode acquireMode, + Header header, + String body, + Option ... _options) { + messageTransfer(destination, acceptMode, acquireMode, header, + toUTF8(body), _options); + } + + public void close() + { + synchronized (commands) + { + state = CLOSING; + setClose(true); + sessionRequestTimeout(0); + sessionDetach(name.getBytes()); + + awaitClose(); + + + } + } + + protected void awaitClose() + { + Waiter w = new Waiter(commands, timeout); + while (w.hasTime() && state != CLOSED) + { + w.await(); + } + + if (state != CLOSED) + { + throw new SessionException("close() timed out"); + } + } + + public void exception(Throwable t) + { + log.error(t, "caught exception"); + } + + public void closed() + { + synchronized (commands) + { + if (closing || getException() != null) + { + state = CLOSED; + } + else + { + state = DETACHED; + } + + commands.notifyAll(); + + synchronized (results) + { + for (ResultFuture<?> result : results.values()) + { + synchronized(result) + { + result.notifyAll(); + } + } + } + if(state == CLOSED) + { + delegate.closed(this); + } + else + { + delegate.detached(this); + } + } + + if(state == CLOSED) + { + connection.removeSession(this); + listener.closed(this); + } + } + + public boolean isClosing() + { + return state == CLOSED || state == CLOSING; + } + + public String toString() + { + return String.format("ssn:%s", name); + } + + public void setTransacted(boolean b) { + this.transacted = b; + } + + public boolean isTransacted(){ + return transacted; + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/SessionClosedException.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/SessionClosedException.java new file mode 100644 index 0000000000..64f9039484 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/SessionClosedException.java @@ -0,0 +1,49 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util.Collections; + + +/** + * SessionClosedException + * + */ + +public class SessionClosedException extends SessionException +{ + + public SessionClosedException() + { + this(null); + } + + public SessionClosedException(Throwable cause) + { + super("session closed", null, cause); + } + + @Override public void rethrow() + { + throw new SessionClosedException(this); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/SessionDelegate.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/SessionDelegate.java new file mode 100644 index 0000000000..9a02961dc4 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/SessionDelegate.java @@ -0,0 +1,212 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.transport.util.Logger; + + +/** + * SessionDelegate + * + * @author Rafael H. Schloming + */ + +public class SessionDelegate + extends MethodDelegate<Session> + implements ProtocolDelegate<Session> +{ + protected static final Logger log = Logger.get(SessionDelegate.class); + + public void init(Session ssn, ProtocolHeader hdr) + { + log.warn("INIT: [%s] %s", ssn, hdr); + } + + public void control(Session ssn, Method method) + { + method.dispatch(ssn, this); + } + + public void command(Session ssn, Method method) { + ssn.identify(method); + method.dispatch(ssn, this); + if (!method.hasPayload()) + { + ssn.processed(method); + } + } + + public void error(Session ssn, ProtocolError error) + { + log.warn("ERROR: [%s] %s", ssn, error); + } + + public void handle(Session ssn, Method method) + { + log.warn("UNHANDLED: [%s] %s", ssn, method); + } + + @Override public void sessionRequestTimeout(Session ssn, SessionRequestTimeout t) + { + if (t.getTimeout() == 0) + { + ssn.setClose(true); + } + ssn.sessionTimeout(0); // Always report back an expiry of 0 until it is implemented + } + + @Override public void sessionAttached(Session ssn, SessionAttached atc) + { + ssn.setState(Session.State.OPEN); + } + + @Override public void sessionTimeout(Session ssn, SessionTimeout t) + { + // Setting of expiry is not implemented + } + + @Override public void sessionCompleted(Session ssn, SessionCompleted cmp) + { + RangeSet ranges = cmp.getCommands(); + RangeSet known = null; + if (cmp.getTimelyReply()) + { + known = new RangeSet(); + } + + if (ranges != null) + { + for (Range range : ranges) + { + boolean advanced = ssn.complete(range.getLower(), range.getUpper()); + if (advanced && known != null) + { + known.add(range); + } + } + } + + if (known != null) + { + ssn.sessionKnownCompleted(known); + } + } + + @Override public void sessionKnownCompleted(Session ssn, SessionKnownCompleted kcmp) + { + RangeSet kc = kcmp.getCommands(); + if (kc != null) + { + ssn.knownComplete(kc); + } + } + + @Override public void sessionFlush(Session ssn, SessionFlush flush) + { + if (flush.getCompleted()) + { + ssn.flushProcessed(); + } + if (flush.getConfirmed()) + { + ssn.flushProcessed(); + } + if (flush.getExpected()) + { + ssn.flushExpected(); + } + } + + @Override public void sessionCommandPoint(Session ssn, SessionCommandPoint scp) + { + ssn.commandPoint(scp.getCommandId()); + } + + @Override public void executionSync(Session ssn, ExecutionSync sync) + { + ssn.syncPoint(); + } + + @Override public void executionResult(Session ssn, ExecutionResult result) + { + ssn.result(result.getCommandId(), result.getValue()); + } + + @Override public void executionException(Session ssn, ExecutionException exc) + { + ssn.setException(exc); + ssn.getSessionListener().exception(ssn, new SessionException(exc)); + ssn.closed(); + } + + @Override public void messageTransfer(Session ssn, MessageTransfer xfr) + { + ssn.getSessionListener().message(ssn, xfr); + } + + @Override public void messageSetFlowMode(Session ssn, MessageSetFlowMode sfm) + { + if ("".equals(sfm.getDestination()) && + MessageFlowMode.CREDIT.equals(sfm.getFlowMode())) + { + ssn.setFlowControl(true); + } + else + { + super.messageSetFlowMode(ssn, sfm); + } + } + + @Override public void messageFlow(Session ssn, MessageFlow flow) + { + if ("".equals(flow.getDestination()) && + MessageCreditUnit.MESSAGE.equals(flow.getUnit())) + { + ssn.addCredit((int) flow.getValue()); + } + else + { + super.messageFlow(ssn, flow); + } + } + + @Override public void messageStop(Session ssn, MessageStop stop) + { + if ("".equals(stop.getDestination())) + { + ssn.drainCredit(); + } + else + { + super.messageStop(ssn, stop); + } + } + + public void closed(Session session) + { + log.debug("CLOSED: [%s]", session); + } + + public void detached(Session session) + { + log.debug("DETACHED: [%s]", session); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/SessionException.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/SessionException.java new file mode 100644 index 0000000000..c4fc9558a1 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/SessionException.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.transport; + +import java.util.List; + +/** + * SessionException + * + */ + +public class SessionException extends TransportException +{ + + private ExecutionException exception; + + public SessionException(String message, ExecutionException exception, Throwable cause) + { + super(message, cause); + this.exception = exception; + } + + public SessionException(ExecutionException exception) + { + this(String.valueOf(exception), exception, null); + } + + public SessionException(String message) + { + this(message, null, null); + } + + public ExecutionException getException() + { + return exception; + } + + @Override public void rethrow() + { + throw new SessionException(getMessage(), exception, this); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/SessionListener.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/SessionListener.java new file mode 100644 index 0000000000..eb650eb9ed --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/SessionListener.java @@ -0,0 +1,42 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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; + + +/** + * SessionListener + * + */ + +public interface SessionListener +{ + + void opened(Session session); + + void resumed(Session session); + + void message(Session ssn, MessageTransfer xfr); + + void exception(Session session, SessionException exception); + + void closed(Session session); + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/Struct.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/Struct.java new file mode 100644 index 0000000000..22bd9f34ad --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/Struct.java @@ -0,0 +1,142 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util.List; +import java.util.Map; + +import org.apache.qpid.transport.codec.Decoder; +import org.apache.qpid.transport.codec.Encodable; +import org.apache.qpid.transport.codec.Encoder; + + +/** + * Struct + * + * @author Rafael H. Schloming + */ + +public abstract class Struct implements Encodable +{ + + public static Struct create(int type) + { + return StructFactory.create(type); + } + + boolean dirty = true; + + public boolean isDirty() + { + return dirty; + } + + public void setDirty(boolean dirty) + { + this.dirty = dirty; + } + + public abstract int getStructType(); + + public abstract int getSizeWidth(); + + public abstract int getPackWidth(); + + public final int getEncodedType() + { + int type = getStructType(); + if (type < 0) + { + throw new UnsupportedOperationException(); + } + return type; + } + + private final boolean isBit(Field<?,?> f) + { + return f.getType().equals(Boolean.class); + } + + private final boolean packed() + { + return getPackWidth() > 0; + } + + private final boolean encoded(Field<?,?> f) + { + return !packed() || !isBit(f) && f.has(this); + } + + private final int getFlagWidth() + { + return (getFields().size() + 7)/8; + } + + private final int getPaddWidth() + { + int pw = getPackWidth() - getFlagWidth(); + assert pw > 0; + return pw; + } + + private final int getFlagCount() + { + return 8*getPackWidth(); + } + + private final int getReservedFlagCount() + { + return getFlagCount() - getFields().size(); + } + + public abstract void read(Decoder dec); + + public abstract void write(Encoder enc); + + public abstract Map<String,Object> getFields(); + + public String toString() + { + StringBuilder str = new StringBuilder(); + str.append(getClass().getSimpleName()); + + str.append("("); + boolean first = true; + for (Map.Entry<String,Object> me : getFields().entrySet()) + { + if (first) + { + first = false; + } + else + { + str.append(", "); + } + str.append(me.getKey()); + str.append("="); + str.append(me.getValue()); + } + str.append(")"); + + return str.toString(); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/TransportBuilder.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/TransportBuilder.java new file mode 100644 index 0000000000..c08909c6e4 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/qpid/transport/TransportException.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/TransportException.java new file mode 100644 index 0000000000..0de190dfad --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/TransportException.java @@ -0,0 +1,51 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.transport; + + +/** + * TransportException + */ + +public class TransportException extends RuntimeException +{ + + public TransportException(String msg) + { + super(msg); + } + + public TransportException(String msg, Throwable cause) + { + super(msg, cause); + } + + public TransportException(Throwable cause) + { + super(cause); + } + + public void rethrow() + { + throw new TransportException(getMessage(), this); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/codec/AbstractDecoder.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/codec/AbstractDecoder.java new file mode 100644 index 0000000000..09ce6a7eb1 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/codec/AbstractDecoder.java @@ -0,0 +1,478 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.codec; + +import java.io.UnsupportedEncodingException; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.transport.Binary; +import org.apache.qpid.transport.RangeSet; +import org.apache.qpid.transport.Struct; +import org.apache.qpid.transport.Type; + +import static org.apache.qpid.transport.util.Functions.*; + + +/** + * AbstractDecoder + * + * @author Rafael H. Schloming + */ + +abstract class AbstractDecoder implements Decoder +{ + + private final Map<Binary,String> str8cache = new LinkedHashMap<Binary,String>() + { + @Override protected boolean removeEldestEntry(Map.Entry<Binary,String> me) + { + return size() > 4*1024; + } + }; + + protected abstract byte doGet(); + + protected abstract void doGet(byte[] bytes); + + protected byte get() + { + return doGet(); + } + + protected void get(byte[] bytes) + { + doGet(bytes); + } + + protected Binary get(int size) + { + byte[] bytes = new byte[size]; + get(bytes); + return new Binary(bytes); + } + + protected short uget() + { + return (short) (0xFF & get()); + } + + public short readUint8() + { + return uget(); + } + + public int readUint16() + { + int i = uget() << 8; + i |= uget(); + return i; + } + + public long readUint32() + { + long l = uget() << 24; + l |= uget() << 16; + l |= uget() << 8; + l |= uget(); + return l; + } + + public int readSequenceNo() + { + return (int) readUint32(); + } + + public long readUint64() + { + long l = 0; + for (int i = 0; i < 8; i++) + { + l |= ((long) (0xFF & get())) << (56 - i*8); + } + return l; + } + + public long readDatetime() + { + return readUint64(); + } + + private static final String decode(byte[] bytes, int offset, int length, String charset) + { + try + { + return new String(bytes, offset, length, charset); + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + } + + private static final String decode(byte[] bytes, String charset) + { + return decode(bytes, 0, bytes.length, charset); + } + + public String readStr8() + { + short size = readUint8(); + Binary bin = get(size); + String str = str8cache.get(bin); + + if (str == null) + { + str = decode(bin.array(), bin.offset(), bin.size(), "UTF-8"); + if(bin.hasExcessCapacity()) + { + str8cache.put(bin.copy(), str); + } + else + { + str8cache.put(bin, str); + } + } + return str; + } + + public String readStr16() + { + int size = readUint16(); + byte[] bytes = new byte[size]; + get(bytes); + return decode(bytes, "UTF-8"); + } + + public byte[] readVbin8() + { + int size = readUint8(); + byte[] bytes = new byte[size]; + get(bytes); + return bytes; + } + + public byte[] readVbin16() + { + int size = readUint16(); + byte[] bytes = new byte[size]; + get(bytes); + return bytes; + } + + public byte[] readVbin32() + { + int size = (int) readUint32(); + byte[] bytes = new byte[size]; + get(bytes); + return bytes; + } + + public RangeSet readSequenceSet() + { + int count = readUint16()/8; + if (count == 0) + { + return null; + } + else + { + RangeSet ranges = new RangeSet(); + for (int i = 0; i < count; i++) + { + ranges.add(readSequenceNo(), readSequenceNo()); + } + return ranges; + } + } + + public RangeSet readByteRanges() + { + throw new Error("not implemented"); + } + + public UUID readUuid() + { + long msb = readUint64(); + long lsb = readUint64(); + return new UUID(msb, lsb); + } + + public String readContent() + { + throw new Error("Deprecated"); + } + + public Struct readStruct(int type) + { + Struct st = Struct.create(type); + int width = st.getSizeWidth(); + if (width > 0) + { + long size = readSize(width); + if (size == 0) + { + return null; + } + } + if (type > 0) + { + int code = readUint16(); + assert code == type; + } + st.read(this); + return st; + } + + public Struct readStruct32() + { + long size = readUint32(); + if (size == 0) + { + return null; + } + else + { + int type = readUint16(); + Struct result = Struct.create(type); + result.read(this); + return result; + } + } + + public Map<String,Object> readMap() + { + long size = readUint32(); + + if (size == 0) + { + return null; + } + + long count = readUint32(); + + if (count == 0) + { + return Collections.EMPTY_MAP; + } + + Map<String,Object> result = new LinkedHashMap(); + for (int i = 0; i < count; i++) + { + String key = readStr8(); + byte code = get(); + Type t = getType(code); + Object value = read(t); + result.put(key, value); + } + + return result; + } + + public List<Object> readList() + { + long size = readUint32(); + + if (size == 0) + { + return null; + } + + long count = readUint32(); + + if (count == 0) + { + return Collections.EMPTY_LIST; + } + + List<Object> result = new ArrayList(); + for (int i = 0; i < count; i++) + { + byte code = get(); + Type t = getType(code); + Object value = read(t); + result.add(value); + } + return result; + } + + public List<Object> readArray() + { + long size = readUint32(); + + if (size == 0) + { + return null; + } + + byte code = get(); + Type t = getType(code); + long count = readUint32(); + + if (count == 0) + { + return Collections.EMPTY_LIST; + } + + List<Object> result = new ArrayList<Object>(); + for (int i = 0; i < count; i++) + { + Object value = read(t); + result.add(value); + } + return result; + } + + private Type getType(byte code) + { + Type type = Type.get(code); + if (type == null) + { + throw new IllegalArgumentException("unknown code: " + code); + } + else + { + return type; + } + } + + private long readSize(Type t) + { + if (t.fixed) + { + return t.width; + } + else + { + return readSize(t.width); + } + } + + private long readSize(int width) + { + switch (width) + { + case 1: + return readUint8(); + case 2: + return readUint16(); + case 4: + return readUint32(); + default: + throw new IllegalStateException("illegal width: " + width); + } + } + + private byte[] readBytes(Type t) + { + long size = readSize(t); + byte[] result = new byte[(int) size]; + get(result); + return result; + } + + private Object read(Type t) + { + switch (t) + { + case BIN8: + case UINT8: + return readUint8(); + case INT8: + return get(); + case CHAR: + return (char) get(); + case BOOLEAN: + return get() > 0; + + case BIN16: + case UINT16: + return readUint16(); + + case INT16: + return (short) readUint16(); + + case BIN32: + case UINT32: + return readUint32(); + + case CHAR_UTF32: + case INT32: + return (int) readUint32(); + + case FLOAT: + return Float.intBitsToFloat((int) readUint32()); + + case BIN64: + case UINT64: + case INT64: + case DATETIME: + return readUint64(); + + case DOUBLE: + return Double.longBitsToDouble(readUint64()); + + case UUID: + return readUuid(); + + case STR8: + return readStr8(); + + case STR16: + return readStr16(); + + case STR8_LATIN: + case STR8_UTF16: + case STR16_LATIN: + case STR16_UTF16: + // XXX: need to do character conversion + return new String(readBytes(t)); + + case MAP: + return readMap(); + case LIST: + return readList(); + case ARRAY: + return readArray(); + case STRUCT32: + return readStruct32(); + + case BIN40: + case DEC32: + case BIN72: + case DEC64: + // XXX: what types are we supposed to use here? + return readBytes(t); + + case VOID: + return null; + + default: + return readBytes(t); + } + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/codec/AbstractEncoder.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/codec/AbstractEncoder.java new file mode 100644 index 0000000000..0ccfcfcb70 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/codec/AbstractEncoder.java @@ -0,0 +1,621 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.codec; + +import java.io.UnsupportedEncodingException; + +import java.nio.ByteBuffer; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.transport.Range; +import org.apache.qpid.transport.RangeSet; +import org.apache.qpid.transport.Struct; +import org.apache.qpid.transport.Type; + +import static org.apache.qpid.transport.util.Functions.*; + + +/** + * AbstractEncoder + * + * @author Rafael H. Schloming + */ + +abstract class AbstractEncoder implements Encoder +{ + + private static Map<Class<?>,Type> ENCODINGS = new HashMap<Class<?>,Type>(); + static + { + ENCODINGS.put(Boolean.class, Type.BOOLEAN); + ENCODINGS.put(String.class, Type.STR16); + ENCODINGS.put(Long.class, Type.INT64); + ENCODINGS.put(Integer.class, Type.INT32); + ENCODINGS.put(Short.class, Type.INT16); + ENCODINGS.put(Byte.class, Type.INT8); + ENCODINGS.put(Map.class, Type.MAP); + ENCODINGS.put(List.class, Type.LIST); + ENCODINGS.put(Float.class, Type.FLOAT); + 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[]>() + { + @Override protected boolean removeEldestEntry(Map.Entry<String,byte[]> me) + { + return size() > 4*1024; + } + }; + + protected abstract void doPut(byte b); + + protected abstract void doPut(ByteBuffer src); + + protected void put(byte b) + { + doPut(b); + } + + protected void put(ByteBuffer src) + { + doPut(src); + } + + protected void put(byte[] bytes) + { + put(ByteBuffer.wrap(bytes)); + } + + protected abstract int beginSize8(); + protected abstract void endSize8(int pos); + + protected abstract int beginSize16(); + protected abstract void endSize16(int pos); + + protected abstract int beginSize32(); + protected abstract void endSize32(int pos); + + public void writeUint8(short b) + { + assert b < 0x100; + + put((byte) b); + } + + public void writeUint16(int s) + { + assert s < 0x10000; + + put(lsb(s >>> 8)); + put(lsb(s)); + } + + public void writeUint32(long i) + { + assert i < 0x100000000L; + + put(lsb(i >>> 24)); + put(lsb(i >>> 16)); + put(lsb(i >>> 8)); + put(lsb(i)); + } + + public void writeSequenceNo(int i) + { + writeUint32(i); + } + + public void writeUint64(long l) + { + for (int i = 0; i < 8; i++) + { + put(lsb(l >> (56 - i*8))); + } + } + + + public void writeDatetime(long l) + { + writeUint64(l); + } + + private static final byte[] encode(String s, String charset) + { + try + { + return s.getBytes(charset); + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + } + + public void writeStr8(String s) + { + if (s == null) + { + s = ""; + } + + byte[] bytes = str8cache.get(s); + if (bytes == null) + { + bytes = encode(s, "UTF-8"); + str8cache.put(s, bytes); + } + writeUint8((short) bytes.length); + put(bytes); + } + + public void writeStr16(String s) + { + if (s == null) + { + s = ""; + } + + byte[] bytes = encode(s, "UTF-8"); + writeUint16(bytes.length); + put(bytes); + } + + public void writeVbin8(byte[] bytes) + { + if (bytes == null) { bytes = new byte[0]; } + if (bytes.length > 255) + { + throw new IllegalArgumentException("array too long: " + bytes.length); + } + writeUint8((short) bytes.length); + put(ByteBuffer.wrap(bytes)); + } + + public void writeVbin16(byte[] bytes) + { + if (bytes == null) { bytes = new byte[0]; } + writeUint16(bytes.length); + put(ByteBuffer.wrap(bytes)); + } + + public void writeVbin32(byte[] bytes) + { + if (bytes == null) { bytes = new byte[0]; } + writeUint32(bytes.length); + put(ByteBuffer.wrap(bytes)); + } + + public void writeSequenceSet(RangeSet ranges) + { + if (ranges == null) + { + writeUint16((short) 0); + } + else + { + writeUint16(ranges.size() * 8); + for (Range range : ranges) + { + writeSequenceNo(range.getLower()); + writeSequenceNo(range.getUpper()); + } + } + } + + public void writeByteRanges(RangeSet ranges) + { + throw new Error("not implemented"); + } + + public void writeUuid(UUID uuid) + { + long msb = 0; + long lsb = 0; + if (uuid != null) + { + msb = uuid.getMostSignificantBits(); + lsb = uuid.getLeastSignificantBits(); + } + writeUint64(msb); + writeUint64(lsb); + } + + public void writeStruct(int type, Struct s) + { + boolean empty = false; + if (s == null) + { + s = Struct.create(type); + empty = true; + } + + int width = s.getSizeWidth(); + int pos = -1; + if (width > 0) + { + pos = beginSize(width); + } + + if (type > 0) + { + writeUint16(type); + } + + s.write(this); + + if (width > 0) + { + endSize(width, pos); + } + } + + public void writeStruct32(Struct s) + { + if (s == null) + { + writeUint32(0); + } + else + { + int pos = beginSize32(); + writeUint16(s.getEncodedType()); + s.write(this); + endSize32(pos); + } + } + + private Type encoding(Object value) + { + if (value == null) + { + return Type.VOID; + } + + Class klass = value.getClass(); + Type type = resolve(klass); + + if (type == null) + { + throw new IllegalArgumentException + ("unable to resolve type: " + klass + ", " + value); + } + else + { + return type; + } + } + + static final Type resolve(Class klass) + { + Type type = ENCODINGS.get(klass); + if (type != null) + { + return type; + } + + Class sup = klass.getSuperclass(); + if (sup != null) + { + type = resolve(klass.getSuperclass()); + + if (type != null) + { + return type; + } + } + + for (Class iface : klass.getInterfaces()) + { + type = resolve(iface); + if (type != null) + { + return type; + } + } + + return null; + } + + public void writeMap(Map<String,Object> map) + { + int pos = beginSize32(); + if (map != null) + { + writeUint32(map.size()); + writeMapEntries(map); + } + endSize32(pos); + } + + protected void writeMapEntries(Map<String,Object> map) + { + for (Map.Entry<String,Object> entry : map.entrySet()) + { + String key = entry.getKey(); + Object value = entry.getValue(); + Type type = encoding(value); + writeStr8(key); + put(type.code); + write(type, value); + } + } + + public void writeList(List<Object> list) + { + int pos = beginSize32(); + if (list != null) + { + writeUint32(list.size()); + writeListEntries(list); + } + endSize32(pos); + } + + protected void writeListEntries(List<Object> list) + { + for (Object value : list) + { + Type type = encoding(value); + put(type.code); + write(type, value); + } + } + + public void writeArray(List<Object> array) + { + int pos = beginSize32(); + if (array != null) + { + writeArrayEntries(array); + } + endSize32(pos); + } + + protected void writeArrayEntries(List<Object> array) + { + Type type; + + if (array.isEmpty()) + { + return; + } + else + { + type = encoding(array.get(0)); + } + + put(type.code); + + writeUint32(array.size()); + + for (Object value : array) + { + write(type, value); + } + } + + private void writeSize(Type t, int size) + { + if (t.fixed) + { + if (size != t.width) + { + throw new IllegalArgumentException + ("size does not match fixed width " + t.width + ": " + + size); + } + } + else + { + writeSize(t.width, size); + } + } + + private void writeSize(int width, int size) + { + // XXX: should check lengths + switch (width) + { + case 1: + writeUint8((short) size); + break; + case 2: + writeUint16(size); + break; + case 4: + writeUint32(size); + break; + default: + throw new IllegalStateException("illegal width: " + width); + } + } + + private int beginSize(int width) + { + switch (width) + { + case 1: + return beginSize8(); + case 2: + return beginSize16(); + case 4: + return beginSize32(); + default: + throw new IllegalStateException("illegal width: " + width); + } + } + + private void endSize(int width, int pos) + { + switch (width) + { + case 1: + endSize8(pos); + break; + case 2: + endSize16(pos); + break; + case 4: + endSize32(pos); + break; + default: + throw new IllegalStateException("illegal width: " + width); + } + } + + private void writeBytes(Type t, byte[] bytes) + { + writeSize(t, bytes.length); + put(bytes); + } + + private <T> T coerce(Class<T> klass, Object value) + { + if (klass.isInstance(value)) + { + return klass.cast(value); + } + else + { + throw new IllegalArgumentException("" + value); + } + } + + private void write(Type t, Object value) + { + switch (t) + { + case BIN8: + case UINT8: + writeUint8(coerce(Short.class, value)); + break; + case INT8: + put(coerce(Byte.class, value)); + break; + case CHAR: + put((byte) ((char)coerce(Character.class, value))); + break; + case BOOLEAN: + if (coerce(Boolean.class, value)) + { + put((byte) 1); + } + else + { + put((byte) 0); + } + break; + + case BIN16: + case UINT16: + writeUint16(coerce(Integer.class, value)); + break; + + case INT16: + writeUint16(coerce(Short.class, value)); + break; + + case BIN32: + case UINT32: + writeUint32(coerce(Long.class, value)); + break; + + case CHAR_UTF32: + case INT32: + writeUint32(coerce(Integer.class, value)); + break; + + case FLOAT: + writeUint32(Float.floatToIntBits(coerce(Float.class, value))); + break; + + case BIN64: + case UINT64: + case INT64: + case DATETIME: + writeUint64(coerce(Long.class, value)); + break; + + case DOUBLE: + long bits = Double.doubleToLongBits(coerce(Double.class, value)); + writeUint64(bits); + break; + + case UUID: + writeUuid(coerce(UUID.class, value)); + break; + + case STR8: + writeStr8(coerce(String.class, value)); + break; + + case STR16: + writeStr16(coerce(String.class, value)); + break; + + case STR8_LATIN: + case STR8_UTF16: + case STR16_LATIN: + case STR16_UTF16: + // XXX: need to do character conversion + writeBytes(t, coerce(String.class, value).getBytes()); + break; + + case MAP: + writeMap((Map<String,Object>) coerce(Map.class, value)); + break; + case LIST: + writeList(coerce(List.class, value)); + break; + case ARRAY: + writeArray(coerce(List.class, value)); + break; + case STRUCT32: + writeStruct32(coerce(Struct.class, value)); + break; + + case BIN40: + case DEC32: + case BIN72: + case DEC64: + // XXX: what types are we supposed to use here? + writeBytes(t, coerce(byte[].class, value)); + break; + + case VOID: + break; + + default: + writeBytes(t, coerce(byte[].class, value)); + break; + } + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/codec/BBDecoder.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/codec/BBDecoder.java new file mode 100644 index 0000000000..10f67e1cd6 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/codec/BBDecoder.java @@ -0,0 +1,149 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.codec; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import org.apache.qpid.transport.Binary; + +/** + * Byte Buffer Decoder. + * Decoder concrete implementor using a backing byte buffer for decoding data. + * + * @author Rafael H. Schloming + */ +public final class BBDecoder extends AbstractDecoder +{ + private ByteBuffer in; + + public void init(ByteBuffer in) + { + this.in = in; + this.in.order(ByteOrder.BIG_ENDIAN); + } + + public void releaseBuffer() + { + in = null; + } + + protected byte doGet() + { + return in.get(); + } + + protected void doGet(byte[] bytes) + { + in.get(bytes); + } + + protected Binary get(int size) + { + if (in.hasArray()) + { + byte[] bytes = in.array(); + Binary bin = new Binary(bytes, in.arrayOffset() + in.position(), size); + in.position(in.position() + size); + return bin; + } + else + { + return super.get(size); + } + } + + public boolean hasRemaining() + { + return in.hasRemaining(); + } + + public short readUint8() + { + return (short) (0xFF & in.get()); + } + + public int readUint16() + { + return 0xFFFF & in.getShort(); + } + + public long readUint32() + { + return 0xFFFFFFFFL & in.getInt(); + } + + public long readUint64() + { + return in.getLong(); + } + + public byte[] readBin128() + { + byte[] result = new byte[16]; + get(result); + return result; + } + + public byte[] readBytes(int howManyBytes) + { + byte[] result = new byte[howManyBytes]; + get(result); + return result; + } + + public double readDouble() + { + return in.getDouble(); + } + + public float readFloat() + { + return in.getFloat(); + } + + public short readInt16() + { + return in.getShort(); + } + + public int readInt32() + { + return in.getInt(); + } + + public byte readInt8() + { + return in.get(); + } + + public byte[] readReaminingBytes() + { + byte[] result = new byte[in.limit() - in.position()]; + get(result); + return result; + } + + public long readInt64() + { + return in.getLong(); + } +}
\ No newline at end of file diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/codec/BBEncoder.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/codec/BBEncoder.java new file mode 100644 index 0000000000..4486b03a67 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/codec/BBEncoder.java @@ -0,0 +1,357 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.codec; + +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.UUID; + + +/** + * Byte Buffer Encoder. + * Encoder concrete implementor using a backing byte buffer for encoding data. + * + * @author Rafael H. Schloming + */ +public final class BBEncoder extends AbstractEncoder +{ + private ByteBuffer out; + private int segment; + + public BBEncoder(int capacity) { + out = ByteBuffer.allocate(capacity); + out.order(ByteOrder.BIG_ENDIAN); + segment = 0; + } + + public void init() + { + out.clear(); + segment = 0; + } + + public ByteBuffer segment() + { + int pos = out.position(); + out.position(segment); + ByteBuffer slice = out.slice(); + slice.limit(pos - segment); + out.position(pos); + segment = pos; + return slice; + } + + public ByteBuffer buffer() + { + int pos = out.position(); + out.position(segment); + ByteBuffer slice = out.slice(); + slice.limit(pos - segment); + out.position(pos); + return slice; + } + + private void grow(int size) + { + ByteBuffer old = out; + int capacity = old.capacity(); + out = ByteBuffer.allocate(Math.max(capacity + size, 2*capacity)); + out.order(ByteOrder.BIG_ENDIAN); + old.flip(); + out.put(old); + } + + protected void doPut(byte b) + { + try + { + out.put(b); + } + catch (BufferOverflowException e) + { + grow(1); + out.put(b); + } + } + + protected void doPut(ByteBuffer src) + { + try + { + out.put(src); + } + catch (BufferOverflowException e) + { + grow(src.remaining()); + out.put(src); + } + } + + protected void put(byte[] bytes) + { + try + { + out.put(bytes); + } + catch (BufferOverflowException e) + { + grow(bytes.length); + out.put(bytes); + } + } + + public void writeUint8(short b) + { + assert b < 0x100; + + try + { + out.put((byte) b); + } + catch (BufferOverflowException e) + { + grow(1); + out.put((byte) b); + } + } + + public void writeUint16(int s) + { + assert s < 0x10000; + + try + { + out.putShort((short) s); + } + catch (BufferOverflowException e) + { + grow(2); + out.putShort((short) s); + } + } + + public void writeUint32(long i) + { + assert i < 0x100000000L; + + try + { + out.putInt((int) i); + } + catch (BufferOverflowException e) + { + grow(4); + out.putInt((int) i); + } + } + + public void writeUint64(long l) + { + try + { + out.putLong(l); + } + catch (BufferOverflowException e) + { + grow(8); + out.putLong(l); + } + } + + public int beginSize8() + { + int pos = out.position(); + try + { + out.put((byte) 0); + } + catch (BufferOverflowException e) + { + grow(1); + out.put((byte) 0); + } + return pos; + } + + public void endSize8(int pos) + { + int cur = out.position(); + out.put(pos, (byte) (cur - pos - 1)); + } + + public int beginSize16() + { + int pos = out.position(); + try + { + out.putShort((short) 0); + } + catch (BufferOverflowException e) + { + grow(2); + out.putShort((short) 0); + } + return pos; + } + + public void endSize16(int pos) + { + int cur = out.position(); + out.putShort(pos, (short) (cur - pos - 2)); + } + + public int beginSize32() + { + int pos = out.position(); + try + { + out.putInt(0); + } + catch (BufferOverflowException e) + { + grow(4); + out.putInt(0); + } + return pos; + } + + public void endSize32(int pos) + { + int cur = out.position(); + out.putInt(pos, (cur - pos - 4)); + } + + public void writeDouble(double aDouble) + { + try + { + out.putDouble(aDouble); + } catch(BufferOverflowException exception) + { + grow(8); + out.putDouble(aDouble); + } + } + + public void writeInt16(short aShort) + { + try + { + out.putShort(aShort); + } catch(BufferOverflowException exception) + { + grow(2); + out.putShort(aShort); + } + } + + public void writeInt32(int anInt) + { + try + { + out.putInt(anInt); + } catch(BufferOverflowException exception) + { + grow(4); + out.putInt(anInt); + } + } + + public void writeInt64(long aLong) + { + try + { + out.putLong(aLong); + } catch(BufferOverflowException exception) + { + grow(8); + out.putLong(aLong); + } + } + + public void writeInt8(byte aByte) + { + try + { + out.put(aByte); + } catch(BufferOverflowException exception) + { + grow(1); + out.put(aByte); + } + } + + public void writeBin128(byte[] byteArray) + { + byteArray = (byteArray != null) ? byteArray : new byte [16]; + + assert byteArray.length == 16; + + try + { + out.put(byteArray); + } catch(BufferOverflowException exception) + { + grow(16); + out.put(byteArray); + } + } + + public void writeBin128(UUID id) + { + byte[] data = new byte[16]; + + long msb = id.getMostSignificantBits(); + long lsb = id.getLeastSignificantBits(); + + assert data.length == 16; + for (int i=7; i>=0; i--) + { + data[i] = (byte)(msb & 0xff); + msb = msb >> 8; + } + + for (int i=15; i>=8; i--) + { + data[i] = (byte)(lsb & 0xff); + lsb = (lsb >> 8); + } + writeBin128(data); + } + + public void writeFloat(float aFloat) + { + try + { + out.putFloat(aFloat); + } catch(BufferOverflowException exception) + { + grow(4); + out.putFloat(aFloat); + } + } + + public void writeMagicNumber() + { + out.put("AM2".getBytes()); + } +}
\ No newline at end of file diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/codec/Decoder.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/codec/Decoder.java new file mode 100644 index 0000000000..a4df5b5fcb --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/codec/Decoder.java @@ -0,0 +1,283 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.codec; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.transport.RangeSet; +import org.apache.qpid.transport.Struct; + + +/** + * Decoder interface. + * Each concrete implementor must specify how to decode given values. + * + * @author Rafael H. Schloming + */ +public interface Decoder +{ + /** + * Tells whether there are any remaining byte(s) to be read. + * + * @return true if there are remaining bytes, false otherwise. + */ + boolean hasRemaining(); + + /** + * The uint8 type is an 8-bit unsigned integral value. + * + * @return an 8-bit unsigned integral value. + */ + short readUint8(); + + /** + *The uint16 type is a 16-bit unsigned integral value encoded in network byte order. + * + * @return a 16-bit unsigned integral value encoded in network byte order. + */ + int readUint16(); + + /** + *The uint32 type is a 32-bit unsigned integral value encoded in network byte order. + * + * @return a 32-bit unsigned integral value encoded in network byte order. + */ + long readUint32(); + + /** + * The uint64 type is a 64-bit unsigned integral value encoded in network byte order. + * + * @return a 64-bit unsigned integral value encoded in network byte order. + */ + long readUint64(); + + /** + * The datetime type encodes a date and time using the 64 bit POSIX time_t format. + * + * @return a date and time using the 64 bit POSIX time_t format. + */ + long readDatetime(); + + /** + * The uuid type encodes a universally unique id as defined by RFC-4122. + * The format and operations for this type can be found in section 4.1.2 of RFC-4122. + * + * return a universally unique id as defined by RFC-4122. + */ + UUID readUuid(); + + /** +// *The sequence-no type encodes, in network byte order, a serial number as defined in RFC-1982. + * + * @return a serial number as defined in RFC-1982. + */ + int readSequenceNo(); + + RangeSet readSequenceSet(); // XXX + RangeSet readByteRanges(); // XXX + + /** + * The str8 type encodes up to 255 octets worth of UTF-8 unicode. + * The number of octets of unicode is first encoded as an 8-bit unsigned integral value. + * This is followed by the actual UTF-8 unicode. + * Note that the encoded size refers to the number of octets of unicode, not necessarily the number of characters since + * the UTF-8 unicode may include multi-byte character sequences. + * + * @return a string. + */ + String readStr8(); + + /** + * The str16 type encodes up to 65535 octets worth of UTF-8 unicode. + * The number of octets is first encoded as a 16-bit unsigned integral value in network byte order. + * This is followed by the actual UTF-8 unicode. + * Note that the encoded size refers to the number of octets of unicode, not necessarily the number of unicode + * characters since the UTF-8 unicode may include multi-byte character sequences. + * + * return a string. + */ + String readStr16(); + + /** + * The vbin8 type encodes up to 255 octets of opaque binary data. + * + * return a byte array. + */ + byte[] readVbin8(); + + /** + * The vbin16 type encodes up to 65535 octets of opaque binary data. + * + * @return the corresponding byte array. + */ + byte[] readVbin16(); + + /** + * The vbin32 type encodes up to 4294967295 octets of opaque binary data. + * + * @return the corresponding byte array. + */ + byte[] readVbin32(); + + /** + * The struct32 type describes any coded struct with a 32-bit (4 octet) size. + * The type is restricted to be only coded structs with a 32-bit size, consequently the first six octets of any encoded + * value for this type MUST always contain the size, class-code, and struct-code in that order. + * The size is encoded as a 32-bit unsigned integral value in network byte order that is equal to the size of the + * encoded field-data, packing-flags, class-code, and struct-code. The class-code is a single octet that may be set to any + * valid class code. + * The struct-code is a single octet that may be set to any valid struct code within the given class-code. + * The first six octets are then followed by the packing flags and encoded field data. + * The presence and quantity of packingflags, as well as the specific fields are determined by the struct definition + * identified with the encoded class-code and struct-code. + * + * @return the decoded struct. + */ + Struct readStruct32(); + + /** + * A map is a set of distinct keys where each key has an associated (type,value) pair. + * The triple of the key, type, and value, form an entry within a map. Each entry within a given map MUST have a + * distinct key. + * A map is encoded as a size in octets, a count of the number of entries, followed by the encoded entries themselves. + * An encoded map may contain up to (4294967295 - 4) octets worth of encoded entries. + * The size is encoded as a 32-bit unsigned integral value in network byte order equal to the number of octets worth of + * encoded entries plus 4. (The extra 4 octets is added for the entry count.) + * The size is then followed by the number of entries encoded as a 32-bit unsigned integral value in network byte order. + * Finally the entries are encoded sequentially. + * An entry is encoded as the key, followed by the type, and then the value. The key is always a string encoded as a str8. + * The type is a single octet that may contain any valid AMQP type code. + * The value is encoded according to the rules defined by the type code for that entry. + * + * @return the decoded map. + */ + Map<String,Object> readMap(); + + /** + * A list is an ordered sequence of (type, value) pairs. The (type, value) pair forms an item within the list. + * The list may contain items of many distinct types. A list is encoded as a size in octets, followed by a count of the + * number of items, followed by the items themselves encoded in their defined order. + * An encoded list may contain up to (4294967295 - 4) octets worth of encoded items. + * The size is encoded as a 32-bit unsigned integral value in network byte order equal to the number of octets worth + * of encoded items plus 4. (The extra4 octets is added for the item count.) + * The size is then followed by the number of items encoded as a 32-bit unsigned integral value in network byte order. + * Finally the items are encoded sequentially in their defined order. + * An item is encoded as the type followed by the value. The type is a single octet that may contain any valid AMQP type + * code. + * The value is encoded according to the rules defined by the type code for that item. + * + * @return the decoded list. + */ + List<Object> readList(); + + /** + * An array is an ordered sequence of values of the same type. + * The array is encoded in as a size in octets, followed by a type code, then a count of the number values in the array, + * and finally the values encoded in their defined order. + * An encoded array may contain up to (4294967295 - 5) octets worth of encoded values. + * The size is encoded as a 32-bit unsigned integral value in network byte order equal to the number of octets worth of + * encoded values plus 5. (The extra 5 octets consist of 4 octets for the count of the number of values, and one octet to + * hold the type code for the items inthe array.) + * The size is then followed by a single octet that may contain any valid AMQP type code. + * The type code is then followed by the number of values encoded as a 32-bit unsigned integral value in network byte + * order. + * Finally the values are encoded sequentially in their defined order according to the rules defined by the type code for + * the array. + * + * @return the decoded array. + */ + List<Object> readArray(); + + /** + * + * @param type the type of the struct. + * @return the decoded struct. + */ + Struct readStruct(int type); + + /** + * The float type encodes a single precision 32-bit floating point number. + * The format and operations are defined by the IEEE 754 standard for 32-bit single precision floating point numbers. + * + * @return the decoded float. + */ + float readFloat(); + + /** + * The double type encodes a double precision 64-bit floating point number. + * The format and operations are defined by the IEEE 754 standard for 64-bit double precision floating point numbers. + * + * @return the decoded double + */ + double readDouble(); + + /** + * The int8 type is a signed integral value encoded using an 8-bit two's complement representation. + * + * @return the decoded integer. + */ + byte readInt8(); + + /** + * The int16 type is a signed integral value encoded using a 16-bit two's complement representation in network byte order. + * + * @return the decoded integer. + */ + short readInt16(); + + /** + * The int32 type is a signed integral value encoded using a 32-bit two's complement representation in network byte order. + * + * @return the decoded integer. + */ + int readInt32(); + + /** + * The int64 type is a signed integral value encoded using a 64-bit two's complement representation in network byte order. + * + * @return the decoded integer (as long). + */ + long readInt64(); + + /** + * The bin128 type consists of 16 consecutive octets of opaque binary data. + * + * @return the decoded byte array. + */ + byte [] readBin128(); + + /** + * Reads the remaining bytes on the underlying buffer. + * + * @return the remaining bytes on the underlying buffer. + */ + byte[] readReaminingBytes (); + + /** + * Reads the given number of bytes. + * + * @param howManyBytes how many bytes need to be read? + * @return a byte array containing the requested data. + */ + byte[] readBytes (int howManyBytes); +}
\ No newline at end of file diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/codec/Encodable.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/codec/Encodable.java new file mode 100644 index 0000000000..37ce8a5cb7 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/codec/Encodable.java @@ -0,0 +1,44 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.codec; + + +/** + * Encodable + * + * @author Rafael H. Schloming + */ +public interface Encodable +{ + /** + * Encodes this encodable using the given encoder. + * + * @param encoder the encoder. + */ + void write(Encoder encoder); + + /** + * Decodes this encodable using the given decoder. + * + * @param decoder the decoder. + */ + void read(Decoder decoder); +}
\ No newline at end of file diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/codec/Encoder.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/codec/Encoder.java new file mode 100644 index 0000000000..7d4f02af31 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/codec/Encoder.java @@ -0,0 +1,282 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.codec; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.transport.RangeSet; +import org.apache.qpid.transport.Struct; + + +/** + * Encoder interface. + * Each concrete implementor must specify how to encode given values. + * + * @author Rafael H. Schloming + */ +public interface Encoder +{ + /** + * The uint8 type is an 8-bit unsigned integral value. + * + * @param b the unsigned integer to be encoded. + */ + void writeUint8(short b); + + /** + *The uint16 type is a 16-bit unsigned integral value encoded in network byte order. + * + * @param s the unsigned integer to be encoded. + */ + void writeUint16(int s); + + /** + *The uint32 type is a 32-bit unsigned integral value encoded in network byte order. + * + * @param i the unsigned integer to be encoded. + */ + void writeUint32(long i); + + /** + * The uint64 type is a 64-bit unsigned integral value encoded in network byte order. + * + * @param b the unsigned integer to be encoded. + */ + void writeUint64(long l); + + /** + * The datetime type encodes a date and time using the 64 bit POSIX time_t format. + * + * @param l the datetime (as long) to be encoded. + */ + void writeDatetime(long l); + + /** + * The uuid type encodes a universally unique id as defined by RFC-4122. + * The format and operations for this type can be found in section 4.1.2 of RFC-4122. + * + * @param uuid the uuid to be encoded. + */ + void writeUuid(UUID uuid); + + /** + *The sequence-no type encodes, in network byte order, a serial number as defined in RFC-1982. + * + * @param s the sequence number to be encoded. + */ + void writeSequenceNo(int s); + + void writeSequenceSet(RangeSet ranges); // XXX + void writeByteRanges(RangeSet ranges); // XXX + + /** + * The str8 type encodes up to 255 octets worth of UTF-8 unicode. + * The number of octets of unicode is first encoded as an 8-bit unsigned integral value. + * This is followed by the actual UTF-8 unicode. + * Note that the encoded size refers to the number of octets of unicode, not necessarily the number of characters since + * the UTF-8 unicode may include multi-byte character sequences. + * + * @param s the string to be encoded. + */ + void writeStr8(String s); + + /** + * The str16 type encodes up to 65535 octets worth of UTF-8 unicode. + * The number of octets is first encoded as a 16-bit unsigned integral value in network byte order. + * This is followed by the actual UTF-8 unicode. + * Note that the encoded size refers to the number of octets of unicode, not necessarily the number of unicode + * characters since the UTF-8 unicode may include multi-byte character sequences. + * + * @param s the string to be encoded. + */ + void writeStr16(String s); + + /** + * The vbin8 type encodes up to 255 octets of opaque binary data. + * The number of octets is first encoded as an 8-bit unsigned integral value. + * This is followed by the actual data. + * + * @param bytes the byte array to be encoded. + */ + void writeVbin8(byte[] bytes); + + /** + * The vbin16 type encodes up to 65535 octets of opaque binary data. + * The number of octets is first encoded as a 16-bit unsigned integral value in network byte order. + * This is followed by the actual data. + * + * @param bytes the byte array to be encoded. + */ + void writeVbin16(byte[] bytes); + + /** + * The vbin32 type encodes up to 4294967295 octets of opaque binary data. + * The number of octets is first encoded as a 32-bit unsigned integral value in network byte order. + * This is followed by the actual data. + * + * @param bytes the byte array to be encoded. + */ + void writeVbin32(byte[] bytes); + + /** + * The struct32 type describes any coded struct with a 32-bit (4 octet) size. + * The type is restricted to be only coded structs with a 32-bit size, consequently the first six octets of any encoded + * value for this type MUST always contain the size, class-code, and struct-code in that order. + * The size is encoded as a 32-bit unsigned integral value in network byte order that is equal to the size of the + * encoded field-data, packing-flags, class-code, and struct-code. The class-code is a single octet that may be set to any + * valid class code. + * The struct-code is a single octet that may be set to any valid struct code within the given class-code. + * The first six octets are then followed by the packing flags and encoded field data. + * The presence and quantity of packingflags, as well as the specific fields are determined by the struct definition + * identified with the encoded class-code and struct-code. + * + * @param struct the struct to be encoded. + */ + void writeStruct32(Struct struct); + + /** + * A map is a set of distinct keys where each key has an associated (type,value) pair. + * The triple of the key, type, and value, form an entry within a map. Each entry within a given map MUST have a + * distinct key. + * A map is encoded as a size in octets, a count of the number of entries, followed by the encoded entries themselves. + * An encoded map may contain up to (4294967295 - 4) octets worth of encoded entries. + * The size is encoded as a 32-bit unsigned integral value in network byte order equal to the number of octets worth of + * encoded entries plus 4. (The extra 4 octets is added for the entry count.) + * The size is then followed by the number of entries encoded as a 32-bit unsigned integral value in network byte order. + * Finally the entries are encoded sequentially. + * An entry is encoded as the key, followed by the type, and then the value. The key is always a string encoded as a str8. + * The type is a single octet that may contain any valid AMQP type code. + * The value is encoded according to the rules defined by the type code for that entry. + * + * @param map the map to be encoded. + */ + void writeMap(Map<String,Object> map); + + /** + * A list is an ordered sequence of (type, value) pairs. The (type, value) pair forms an item within the list. + * The list may contain items of many distinct types. A list is encoded as a size in octets, followed by a count of the + * number of items, followed by the items themselves encoded in their defined order. + * An encoded list may contain up to (4294967295 - 4) octets worth of encoded items. + * The size is encoded as a 32-bit unsigned integral value in network byte order equal to the number of octets worth + * of encoded items plus 4. (The extra4 octets is added for the item count.) + * The size is then followed by the number of items encoded as a 32-bit unsigned integral value in network byte order. + * Finally the items are encoded sequentially in their defined order. + * An item is encoded as the type followed by the value. The type is a single octet that may contain any valid AMQP type + * code. + * The value is encoded according to the rules defined by the type code for that item. + * + * @param list the list to be encoded. + */ + void writeList(List<Object> list); + + /** + * An array is an ordered sequence of values of the same type. + * The array is encoded in as a size in octets, followed by a type code, then a count of the number values in the array, + * and finally the values encoded in their defined order. + * An encoded array may contain up to (4294967295 - 5) octets worth of encoded values. + * The size is encoded as a 32-bit unsigned integral value in network byte order equal to the number of octets worth of + * encoded values plus 5. (The extra 5 octets consist of 4 octets for the count of the number of values, and one octet to + * hold the type code for the items inthe array.) + * The size is then followed by a single octet that may contain any valid AMQP type code. + * The type code is then followed by the number of values encoded as a 32-bit unsigned integral value in network byte + * order. + * Finally the values are encoded sequentially in their defined order according to the rules defined by the type code for + * the array. + * + * @param array the array to be encoded. + */ + void writeArray(List<Object> array); + + /** + * The struct32 type describes any coded struct with a 32-bit (4 octet) size. + * The type is restricted to be only coded structs with a 32-bit size, consequently the first six octets of any encoded + * value for this type MUST always contain the size, class-code, and struct-code in that order. + * The size is encoded as a 32-bit unsigned integral value in network byte order that is equal to the size of the + * encoded field-data, packing-flags, class-code, and struct-code. The class-code is a single octet that may be set to any + * valid class code. + * The struct-code is a single octet that may be set to any valid struct code within the given class-code. + * The first six octets are then followed by the packing flags and encoded field data. + * The presence and quantity of packingflags, as well as the specific fields are determined by the struct definition + * identified with the encoded class-code and struct-code. + * + * @param type the type of the struct. + * @param struct the struct to be encoded. + */ + void writeStruct(int type, Struct struct); + + /** + * The float type encodes a single precision 32-bit floating point number. + * The format and operations are defined by the IEEE 754 standard for 32-bit single precision floating point numbers. + * + * @param aFloat the float to be encoded. + */ + void writeFloat(float aFloat); + + /** + * The double type encodes a double precision 64-bit floating point number. + * The format and operations are defined by the IEEE 754 standard for 64-bit double precision floating point numbers. + * + * @param aDouble the double to be encoded. + */ + void writeDouble(double aDouble); + + /** + * The int8 type is a signed integral value encoded using an 8-bit two's complement representation. + * + * @param aByte the integer to be encoded. + */ + void writeInt8(byte aByte); + + /** + * The int16 type is a signed integral value encoded using a 16-bit two's complement representation in network byte order. + * + * @param aShort the integer to be encoded. + */ + void writeInt16(short aShort); + + /** + * The int32 type is a signed integral value encoded using a 32-bit two's complement representation in network byte order. + * + * @param anInt the integer to be encoded. + */ + void writeInt32(int anInt); + + /** + * The int64 type is a signed integral value encoded using a 64-bit two's complement representation in network byte order. + * + * @param aLong the integer to be encoded. + */ + void writeInt64(long aLong); + + /** + * The bin128 type consists of 16 consecutive octets of opaque binary data. + * + * @param bytes the bytes array to be encoded. + */ + void writeBin128(byte [] bytes); + + /** + * Encodes the AMQP magic number. + */ + void writeMagicNumber(); +}
\ No newline at end of file diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/Assembler.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/Assembler.java new file mode 100644 index 0000000000..1a85ab88a5 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/Assembler.java @@ -0,0 +1,256 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.qpid.transport.Header; +import org.apache.qpid.transport.Method; +import org.apache.qpid.transport.ProtocolError; +import org.apache.qpid.transport.ProtocolEvent; +import org.apache.qpid.transport.ProtocolHeader; +import org.apache.qpid.transport.Receiver; +import org.apache.qpid.transport.Struct; +import org.apache.qpid.transport.codec.BBDecoder; + +/** + * Assembler + * + */ +public class Assembler implements Receiver<NetworkEvent>, NetworkDelegate +{ + // Use a small array to store incomplete Methods for low-value channels, instead of allocating a huge + // array or always boxing the channelId and looking it up in the map. This value must be of the form 2^X - 1. + private static final int ARRAY_SIZE = 0xFF; + private final Method[] _incompleteMethodArray = new Method[ARRAY_SIZE + 1]; + private final Map<Integer, Method> _incompleteMethodMap = new HashMap<Integer, Method>(); + + private final Receiver<ProtocolEvent> receiver; + private final Map<Integer,List<Frame>> segments; + private static final ThreadLocal<BBDecoder> _decoder = new ThreadLocal<BBDecoder>() + { + public BBDecoder initialValue() + { + return new BBDecoder(); + } + }; + + public Assembler(Receiver<ProtocolEvent> receiver) + { + this.receiver = receiver; + segments = new HashMap<Integer,List<Frame>>(); + } + + private int segmentKey(Frame frame) + { + return (frame.getTrack() + 1) * frame.getChannel(); + } + + private List<Frame> getSegment(Frame frame) + { + return segments.get(segmentKey(frame)); + } + + private void setSegment(Frame frame, List<Frame> segment) + { + int key = segmentKey(frame); + if (segments.containsKey(key)) + { + error(new ProtocolError(Frame.L2, "segment in progress: %s", + frame)); + } + segments.put(segmentKey(frame), segment); + } + + private void clearSegment(Frame frame) + { + segments.remove(segmentKey(frame)); + } + + private void emit(int channel, ProtocolEvent event) + { + event.setChannel(channel); + receiver.received(event); + } + + public void received(NetworkEvent event) + { + event.delegate(this); + } + + public void exception(Throwable t) + { + this.receiver.exception(t); + } + + public void closed() + { + this.receiver.closed(); + } + + public void init(ProtocolHeader header) + { + emit(0, header); + } + + public void error(ProtocolError error) + { + emit(0, error); + } + + public void frame(Frame frame) + { + ByteBuffer segment; + if (frame.isFirstFrame() && frame.isLastFrame()) + { + segment = frame.getBody(); + assemble(frame, segment); + } + else + { + List<Frame> frames; + if (frame.isFirstFrame()) + { + frames = new ArrayList<Frame>(); + setSegment(frame, frames); + } + else + { + frames = getSegment(frame); + } + + frames.add(frame); + + if (frame.isLastFrame()) + { + clearSegment(frame); + + int size = 0; + for (Frame f : frames) + { + size += f.getSize(); + } + segment = ByteBuffer.allocate(size); + for (Frame f : frames) + { + segment.put(f.getBody()); + } + segment.flip(); + assemble(frame, segment); + } + } + + } + + private void assemble(Frame frame, ByteBuffer segment) + { + BBDecoder dec = _decoder.get(); + dec.init(segment); + + int channel = frame.getChannel(); + Method command; + + switch (frame.getType()) + { + case CONTROL: + int controlType = dec.readUint16(); + Method control = Method.create(controlType); + control.read(dec); + emit(channel, control); + break; + case COMMAND: + int commandType = dec.readUint16(); + // read in the session header, right now we don't use it + int hdr = dec.readUint16(); + command = Method.create(commandType); + command.setSync((0x0001 & hdr) != 0); + command.read(dec); + if (command.hasPayload()) + { + setIncompleteCommand(channel, command); + } + else + { + emit(channel, command); + } + break; + case HEADER: + command = getIncompleteCommand(channel); + List<Struct> structs = new ArrayList<Struct>(2); + while (dec.hasRemaining()) + { + structs.add(dec.readStruct32()); + } + command.setHeader(new Header(structs)); + if (frame.isLastSegment()) + { + setIncompleteCommand(channel, null); + emit(channel, command); + } + break; + case BODY: + command = getIncompleteCommand(channel); + command.setBody(segment); + setIncompleteCommand(channel, null); + emit(channel, command); + break; + default: + throw new IllegalStateException("unknown frame type: " + frame.getType()); + } + + dec.releaseBuffer(); + } + + private void setIncompleteCommand(int channelId, Method incomplete) + { + if ((channelId & ARRAY_SIZE) == channelId) + { + _incompleteMethodArray[channelId] = incomplete; + } + else + { + if(incomplete != null) + { + _incompleteMethodMap.put(channelId, incomplete); + } + else + { + _incompleteMethodMap.remove(channelId); + } + } + } + + private Method getIncompleteCommand(int channelId) + { + if ((channelId & ARRAY_SIZE) == channelId) + { + return _incompleteMethodArray[channelId]; + } + else + { + return _incompleteMethodMap.get(channelId); + } + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/ConnectionBinding.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/ConnectionBinding.java new file mode 100644 index 0000000000..1a8d277bba --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/ConnectionBinding.java @@ -0,0 +1,103 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 org.apache.qpid.transport.Binding; +import org.apache.qpid.transport.Connection; +import org.apache.qpid.transport.ConnectionDelegate; +import org.apache.qpid.transport.ConnectionListener; +import org.apache.qpid.transport.Receiver; +import org.apache.qpid.transport.Sender; +import org.apache.qpid.transport.network.security.sasl.SASLReceiver; +import org.apache.qpid.transport.network.security.sasl.SASLSender; + +/** + * ConnectionBinding + * + */ + +public abstract class ConnectionBinding + implements Binding<Connection,ByteBuffer> +{ + + public static Binding<Connection,ByteBuffer> get(final Connection connection) + { + return new ConnectionBinding() + { + public Connection connection() + { + return connection; + } + }; + } + + public static Binding<Connection,ByteBuffer> get(final ConnectionDelegate delegate) + { + return new ConnectionBinding() + { + public Connection connection() + { + Connection conn = new Connection(); + conn.setConnectionDelegate(delegate); + return conn; + } + }; + } + + public static final int MAX_FRAME_SIZE = 64 * 1024 - 1; + + public abstract Connection connection(); + + public Connection endpoint(Sender<ByteBuffer> sender) + { + Connection conn = connection(); + + if (conn.getConnectionSettings() != null && + conn.getConnectionSettings().isUseSASLEncryption()) + { + sender = new SASLSender(sender); + conn.addConnectionListener((ConnectionListener)sender); + } + + // XXX: hardcoded max-frame + Disassembler dis = new Disassembler(sender, MAX_FRAME_SIZE); + conn.setSender(dis); + return conn; + } + + public Receiver<ByteBuffer> receiver(Connection conn) + { + if (conn.getConnectionSettings() != null && + conn.getConnectionSettings().isUseSASLEncryption()) + { + SASLReceiver receiver = new SASLReceiver(new InputHandler(new Assembler(conn))); + conn.addConnectionListener((ConnectionListener)receiver); + return receiver; + } + else + { + return new InputHandler(new Assembler(conn)); + } + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/Disassembler.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/Disassembler.java new file mode 100644 index 0000000000..685034d1a9 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/Disassembler.java @@ -0,0 +1,233 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 org.apache.qpid.transport.Header; +import org.apache.qpid.transport.Method; +import org.apache.qpid.transport.ProtocolDelegate; +import org.apache.qpid.transport.ProtocolError; +import org.apache.qpid.transport.ProtocolEvent; +import org.apache.qpid.transport.ProtocolHeader; +import org.apache.qpid.transport.SegmentType; +import org.apache.qpid.transport.Sender; +import org.apache.qpid.transport.Struct; +import org.apache.qpid.transport.codec.BBEncoder; +import static org.apache.qpid.transport.network.Frame.FIRST_FRAME; +import static org.apache.qpid.transport.network.Frame.FIRST_SEG; +import static org.apache.qpid.transport.network.Frame.HEADER_SIZE; +import static org.apache.qpid.transport.network.Frame.LAST_FRAME; +import static org.apache.qpid.transport.network.Frame.LAST_SEG; + +import static java.lang.Math.min; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Disassembler + */ +public final class Disassembler implements Sender<ProtocolEvent>, ProtocolDelegate<Void> +{ + private final Sender<ByteBuffer> sender; + private final int maxPayload; + private final Object sendlock = new Object(); + private final static ThreadLocal<BBEncoder> _encoder = new ThreadLocal<BBEncoder>() + { + public BBEncoder initialValue() + { + return new BBEncoder(4*1024); + } + }; + + public Disassembler(Sender<ByteBuffer> sender, int maxFrame) + { + if (maxFrame <= HEADER_SIZE || maxFrame >= 64*1024) + { + throw new IllegalArgumentException("maxFrame must be > HEADER_SIZE and < 64K: " + maxFrame); + } + this.sender = sender; + this.maxPayload = maxFrame - HEADER_SIZE; + } + + public void send(ProtocolEvent event) + { + event.delegate(null, this); + } + + public void flush() + { + synchronized (sendlock) + { + sender.flush(); + } + } + + public void close() + { + synchronized (sendlock) + { + sender.close(); + } + } + + private void frame(byte flags, byte type, byte track, int channel, int size, ByteBuffer buf) + { + synchronized (sendlock) + { + ByteBuffer data = ByteBuffer.allocate(size + HEADER_SIZE); + data.order(ByteOrder.BIG_ENDIAN); + + data.put(0, flags); + data.put(1, type); + data.putShort(2, (short) (size + HEADER_SIZE)); + data.put(5, track); + data.putShort(6, (short) channel); + data.position(HEADER_SIZE); + + int limit = buf.limit(); + buf.limit(buf.position() + size); + data.put(buf); + buf.limit(limit); + + data.rewind(); + sender.send(data); + } + } + + private void fragment(byte flags, SegmentType type, ProtocolEvent event, ByteBuffer buf) + { + byte typeb = (byte) type.getValue(); + byte track = event.getEncodedTrack() == Frame.L4 ? (byte) 1 : (byte) 0; + + int remaining = buf.remaining(); + boolean first = true; + while (true) + { + int size = min(maxPayload, remaining); + remaining -= size; + + byte newflags = flags; + if (first) + { + newflags |= FIRST_FRAME; + first = false; + } + if (remaining == 0) + { + newflags |= LAST_FRAME; + } + + frame(newflags, typeb, track, event.getChannel(), size, buf); + + if (remaining == 0) + { + break; + } + } + } + + public void init(Void v, ProtocolHeader header) + { + synchronized (sendlock) + { + sender.send(header.toByteBuffer()); + sender.flush(); + } + } + + public void control(Void v, Method method) + { + method(method, SegmentType.CONTROL); + } + + public void command(Void v, Method method) + { + method(method, SegmentType.COMMAND); + } + + private void method(Method method, SegmentType type) + { + BBEncoder enc = _encoder.get(); + enc.init(); + enc.writeUint16(method.getEncodedType()); + if (type == SegmentType.COMMAND) + { + if (method.isSync()) + { + enc.writeUint16(0x0101); + } + else + { + enc.writeUint16(0x0100); + } + } + method.write(enc); + ByteBuffer methodSeg = enc.segment(); + + byte flags = FIRST_SEG; + + boolean payload = method.hasPayload(); + if (!payload) + { + flags |= LAST_SEG; + } + + ByteBuffer headerSeg = null; + if (payload) + { + final Header hdr = method.getHeader(); + if (hdr != null) + { + final Struct[] structs = hdr.getStructs(); + + for (Struct st : structs) + { + enc.writeStruct32(st); + } + } + headerSeg = enc.segment(); + } + + synchronized (sendlock) + { + fragment(flags, type, method, methodSeg); + if (payload) + { + ByteBuffer body = method.getBody(); + fragment(body == null ? LAST_SEG : 0x0, SegmentType.HEADER, method, headerSeg); + if (body != null) + { + fragment(LAST_SEG, SegmentType.BODY, method, body); + } + + } + } + } + + public void error(Void v, ProtocolError error) + { + throw new IllegalArgumentException(String.valueOf(error)); + } + + public void setIdleTimeout(int i) + { + sender.setIdleTimeout(i); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/Frame.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/Frame.java new file mode 100644 index 0000000000..849355276e --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/Frame.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.qpid.transport.network; + +import org.apache.qpid.transport.SegmentType; +import org.apache.qpid.transport.util.SliceIterator; + +import java.nio.ByteBuffer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Iterator; + +import static org.apache.qpid.transport.util.Functions.*; + + +/** + * Frame + * + * @author Rafael H. Schloming + */ + +public final class Frame implements NetworkEvent +{ + public static final int HEADER_SIZE = 12; + + // XXX: enums? + public static final byte L1 = 0; + public static final byte L2 = 1; + public static final byte L3 = 2; + public static final byte L4 = 3; + + public static final byte RESERVED = 0x0; + + public static final byte VERSION = 0x0; + + public static final byte FIRST_SEG = 0x8; + public static final byte LAST_SEG = 0x4; + public static final byte FIRST_FRAME = 0x2; + public static final byte LAST_FRAME = 0x1; + + final private byte flags; + final private SegmentType type; + final private byte track; + final private int channel; + final private ByteBuffer body; + + public Frame(byte flags, SegmentType type, byte track, int channel, + ByteBuffer body) + { + this.flags = flags; + this.type = type; + this.track = track; + this.channel = channel; + this.body = body; + } + + public ByteBuffer getBody() + { + return body.slice(); + } + + public byte getFlags() + { + return flags; + } + + public int getChannel() + { + return channel; + } + + public int getSize() + { + return body.remaining(); + } + + public SegmentType getType() + { + return type; + } + + public byte getTrack() + { + return track; + } + + private boolean flag(byte mask) + { + return (flags & mask) != 0; + } + + public boolean isFirstSegment() + { + return flag(FIRST_SEG); + } + + public boolean isLastSegment() + { + return flag(LAST_SEG); + } + + public boolean isFirstFrame() + { + return flag(FIRST_FRAME); + } + + public boolean isLastFrame() + { + return flag(LAST_FRAME); + } + + public void delegate(NetworkDelegate delegate) + { + delegate.frame(this); + } + + public String toString() + { + StringBuilder str = new StringBuilder(); + + str.append(String.format + ("[%05d %05d %1d %s %d%d%d%d] ", getChannel(), getSize(), + getTrack(), getType(), + isFirstSegment() ? 1 : 0, isLastSegment() ? 1 : 0, + isFirstFrame() ? 1 : 0, isLastFrame() ? 1 : 0)); + + str.append(str(body)); + + return str.toString(); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/InputHandler.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/InputHandler.java new file mode 100644 index 0000000000..a2885f97bc --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/InputHandler.java @@ -0,0 +1,205 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 java.nio.ByteOrder; + +import org.apache.qpid.transport.ProtocolError; +import org.apache.qpid.transport.ProtocolHeader; +import org.apache.qpid.transport.Receiver; +import org.apache.qpid.transport.SegmentType; + +import static org.apache.qpid.transport.util.Functions.*; + +import static org.apache.qpid.transport.network.InputHandler.State.*; + + +/** + * InputHandler + * + * @author Rafael H. Schloming + */ + +public class InputHandler implements Receiver<ByteBuffer> +{ + + public enum State + { + PROTO_HDR, + FRAME_HDR, + FRAME_BODY, + ERROR; + } + + private final Receiver<NetworkEvent> receiver; + private State state; + private ByteBuffer input = null; + private int needed; + + private byte flags; + private SegmentType type; + private byte track; + private int channel; + + public InputHandler(Receiver<NetworkEvent> receiver, State state) + { + this.receiver = receiver; + this.state = state; + + switch (state) + { + case PROTO_HDR: + needed = 8; + break; + case FRAME_HDR: + needed = Frame.HEADER_SIZE; + break; + } + } + + public InputHandler(Receiver<NetworkEvent> receiver) + { + this(receiver, PROTO_HDR); + } + + private void error(String fmt, Object ... args) + { + receiver.received(new ProtocolError(Frame.L1, fmt, args)); + } + + public void received(ByteBuffer buf) + { + int limit = buf.limit(); + int remaining = buf.remaining(); + while (remaining > 0) + { + if (remaining >= needed) + { + int consumed = needed; + int pos = buf.position(); + if (input == null) + { + buf.limit(pos + needed); + input = buf; + state = next(pos); + buf.limit(limit); + buf.position(pos + consumed); + } + else + { + buf.limit(pos + needed); + input.put(buf); + buf.limit(limit); + input.flip(); + state = next(0); + } + + remaining -= consumed; + input = null; + } + else + { + if (input == null) + { + input = ByteBuffer.allocate(needed); + } + input.put(buf); + needed -= remaining; + remaining = 0; + } + } + } + + private State next(int pos) + { + input.order(ByteOrder.BIG_ENDIAN); + + switch (state) { + case PROTO_HDR: + if (input.get(pos) != 'A' && + input.get(pos + 1) != 'M' && + input.get(pos + 2) != 'Q' && + input.get(pos + 3) != 'P') + { + error("bad protocol header: %s", str(input)); + return ERROR; + } + + byte protoClass = input.get(pos + 4); + byte instance = input.get(pos + 5); + byte major = input.get(pos + 6); + byte minor = input.get(pos + 7); + receiver.received(new ProtocolHeader(protoClass, instance, major, minor)); + needed = Frame.HEADER_SIZE; + return FRAME_HDR; + case FRAME_HDR: + flags = input.get(pos); + type = SegmentType.get(input.get(pos + 1)); + int size = (0xFFFF & input.getShort(pos + 2)); + size -= Frame.HEADER_SIZE; + if (size < 0 || size > (64*1024 - 12)) + { + error("bad frame size: %d", size); + return ERROR; + } + byte b = input.get(pos + 5); + if ((b & 0xF0) != 0) { + error("non-zero reserved bits in upper nibble of " + + "frame header byte 5: '%x'", b); + return ERROR; + } else { + track = (byte) (b & 0xF); + } + channel = (0xFFFF & input.getShort(pos + 6)); + if (size == 0) + { + Frame frame = new Frame(flags, type, track, channel, ByteBuffer.allocate(0)); + receiver.received(frame); + needed = Frame.HEADER_SIZE; + return FRAME_HDR; + } + else + { + needed = size; + return FRAME_BODY; + } + case FRAME_BODY: + Frame frame = new Frame(flags, type, track, channel, input.slice()); + receiver.received(frame); + needed = Frame.HEADER_SIZE; + return FRAME_HDR; + default: + throw new IllegalStateException(); + } + } + + public void exception(Throwable t) + { + receiver.exception(t); + } + + public void closed() + { + receiver.closed(); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/NetworkDelegate.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/NetworkDelegate.java new file mode 100644 index 0000000000..fbdfe6e84c --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/NetworkDelegate.java @@ -0,0 +1,42 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 org.apache.qpid.transport.ProtocolError; +import org.apache.qpid.transport.ProtocolHeader; + + +/** + * NetworkDelegate + * + * @author Rafael H. Schloming + */ + +public interface NetworkDelegate +{ + + void init(ProtocolHeader header); + + void frame(Frame frame); + + void error(ProtocolError error); + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/NetworkEvent.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/NetworkEvent.java new file mode 100644 index 0000000000..91314cd4ad --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/NetworkEvent.java @@ -0,0 +1,34 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.transport.network; + + +/** + * NetworkEvent + * + */ + +public interface NetworkEvent +{ + + void delegate(NetworkDelegate delegate); + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/NetworkTransport.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/NetworkTransport.java new file mode 100644 index 0000000000..5e12d7e7c6 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/NetworkTransport.java @@ -0,0 +1,38 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.transport.network; + +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/qpid/java/common/src/main/java/org/apache/qpid/transport/network/Transport.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/Transport.java new file mode 100644 index 0000000000..f0bf04d04f --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/Transport.java @@ -0,0 +1,56 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.transport.network; + +import org.apache.qpid.transport.TransportException; + +public class Transport +{ + private final static Class<?> transportClass; + + static + { + try + { + transportClass = + Class.forName(System.getProperty("qpid.transport", + "org.apache.qpid.transport.network.io.IoNetworkTransport")); + + } + catch(Exception e) + { + throw new Error("Error occured while loading Qpid Transport",e); + } + } + + public static NetworkTransport getTransport() throws TransportException + { + try + { + return (NetworkTransport)transportClass.newInstance(); + } + catch (Exception e) + { + throw new TransportException("Error while creating a new transport instance",e); + } + } +}
\ No newline at end of file diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/InputHandler_0_9.java b/qpid/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/qpid/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/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoAcceptor.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoAcceptor.java new file mode 100644 index 0000000000..8530240dcc --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoAcceptor.java @@ -0,0 +1,92 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 org.apache.qpid.transport.Binding; +import org.apache.qpid.transport.TransportException; + +import java.io.IOException; + +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; + +import java.nio.ByteBuffer; + + +/** + * IoAcceptor + * + */ + +public class IoAcceptor<E> extends Thread +{ + + + private ServerSocket socket; + private Binding<E,ByteBuffer> binding; + + public IoAcceptor(SocketAddress address, Binding<E,ByteBuffer> binding) + throws IOException + { + socket = new ServerSocket(); + socket.setReuseAddress(true); + socket.bind(address); + this.binding = binding; + + setName(String.format("IoAcceptor - %s", socket.getInetAddress())); + } + + /** + Close the underlying ServerSocket if it has not already been closed. + */ + public void close() throws IOException + { + if (!socket.isClosed()) + { + socket.close(); + } + } + + public IoAcceptor(String host, int port, Binding<E,ByteBuffer> binding) + throws IOException + { + this(new InetSocketAddress(host, port), binding); + } + + public void run() + { + while (true) + { + try + { + Socket sock = socket.accept(); + IoTransport<E> transport = new IoTransport<E>(sock, binding,false); + } + catch (IOException e) + { + throw new TransportException(e); + } + } + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoContext.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoContext.java new file mode 100644 index 0000000000..69b3a0ce45 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoContext.java @@ -0,0 +1,35 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.Sender; + +public interface IoContext +{ + Sender<ByteBuffer> getSender(); + + IoReceiver getReceiver(); + + Socket getSocket(); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoNetworkTransport.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoNetworkTransport.java new file mode 100644 index 0000000000..dd6a37eca2 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoNetworkTransport.java @@ -0,0 +1,116 @@ +/* +* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 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 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 log = Logger.get(IoNetworkTransport.class); + + private Socket socket; + private Sender<ByteBuffer> sender; + private IoReceiver receiver; + private long timeout = 60000; + private ConnectionSettings settings; + + public void init(ConnectionSettings settings) + { + try + { + this.settings = settings; + InetAddress address = InetAddress.getByName(settings.getHost()); + socket = new Socket(); + socket.setReuseAddress(true); + socket.setTcpNoDelay(settings.isTcpNodelay()); + + log.debug("default-SO_RCVBUF : %s", socket.getReceiveBufferSize()); + log.debug("default-SO_SNDBUF : %s", socket.getSendBufferSize()); + + 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())); + } + catch (SocketException e) + { + throw new TransportException("Error connecting to broker", e); + } + catch (IOException e) + { + throw new TransportException("Error connecting to broker", e); + } + } + + public void receiver(Receiver<ByteBuffer> delegate) + { + receiver = new IoReceiver(this, delegate, + 2*settings.getReadBufferSize() , timeout); + } + + public Sender<ByteBuffer> sender() + { + return new IoSender(this, 2*settings.getWriteBufferSize(), timeout); + } + + public void close() + { + + } + + public Sender<ByteBuffer> getSender() + { + return sender; + } + + public IoReceiver getReceiver() + { + return receiver; + } + + public Socket getSocket() + { + return socket; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoReceiver.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoReceiver.java new file mode 100644 index 0000000000..19a683d505 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoReceiver.java @@ -0,0 +1,162 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 org.apache.qpid.thread.Threading; +import org.apache.qpid.transport.Receiver; +import org.apache.qpid.transport.TransportException; +import org.apache.qpid.transport.util.Logger; + +import java.io.IOException; +import java.io.InputStream; +import java.net.Socket; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * IoReceiver + * + */ + +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 final boolean shutdownBroken = + ((String) System.getProperties().get("os.name")).matches("(?i).*windows.*"); + + public IoReceiver(IoContext ioCtx, Receiver<ByteBuffer> receiver, + int bufferSize, long timeout) + { + this.ioCtx = ioCtx; + this.receiver = receiver; + this.bufferSize = bufferSize; + this.socket = ioCtx.getSocket(); + this.timeout = timeout; + + try + { + receiverThread = Threading.getThreadFactory().createThread(this); + } + catch(Exception e) + { + throw new Error("Error creating IOReceiver thread",e); + } + receiverThread.setDaemon(true); + receiverThread.setName(String.format("IoReceiver - %s", socket.getRemoteSocketAddress())); + receiverThread.start(); + } + + void close(boolean block) + { + if (!closed.getAndSet(true)) + { + try + { + if (shutdownBroken) + { + socket.close(); + } + else + { + socket.shutdownInput(); + } + if (block && Thread.currentThread() != receiverThread) + { + receiverThread.join(timeout); + if (receiverThread.isAlive()) + { + throw new TransportException("join timed out"); + } + } + } + catch (InterruptedException e) + { + throw new TransportException(e); + } + catch (IOException e) + { + throw new TransportException(e); + } + } + } + + public void run() + { + final int threshold = bufferSize / 2; + + // I set the read buffer size simillar to SO_RCVBUF + // Haven't tested with a lower value to see if it's better or worse + byte[] buffer = new byte[bufferSize]; + try + { + InputStream in = socket.getInputStream(); + int read = 0; + int offset = 0; + while ((read = in.read(buffer, offset, bufferSize-offset)) != -1) + { + if (read > 0) + { + ByteBuffer b = ByteBuffer.wrap(buffer,offset,read); + receiver.received(b); + offset+=read; + if (offset > threshold) + { + offset = 0; + buffer = new byte[bufferSize]; + } + } + } + } + catch (Throwable t) + { + if (!(shutdownBroken && + t instanceof SocketException && + t.getMessage().equalsIgnoreCase("socket closed") && + closed.get())) + { + receiver.exception(t); + } + } + finally + { + receiver.closed(); + try + { + socket.close(); + } + catch(Exception e) + { + log.warn(e, "Error closing socket"); + } + } + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoSender.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoSender.java new file mode 100644 index 0000000000..66b97e8225 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoSender.java @@ -0,0 +1,307 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 static org.apache.qpid.transport.util.Functions.mod; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.qpid.thread.Threading; +import org.apache.qpid.transport.Sender; +import org.apache.qpid.transport.SenderException; +import org.apache.qpid.transport.TransportException; +import org.apache.qpid.transport.util.Logger; + + +public final class IoSender implements Runnable, Sender<ByteBuffer> +{ + + private static final Logger log = Logger.get(IoSender.class); + + // by starting here, we ensure that we always test the wraparound + // case, we should probably make this configurable somehow so that + // 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; + + private final byte[] buffer; + private volatile int head = START; + private volatile int tail = START; + private volatile boolean idle = true; + private final Object notFull = new Object(); + private final Object notEmpty = new Object(); + private final AtomicBoolean closed = new AtomicBoolean(false); + private final Thread senderThread; + + private volatile Throwable exception = null; + + + public IoSender(IoContext ioCtx, int bufferSize, long timeout) + { + this.ioCtx = ioCtx; + this.socket = ioCtx.getSocket(); + this.buffer = new byte[pof2(bufferSize)]; // buffer size must be a power of 2 + this.timeout = timeout; + + try + { + out = socket.getOutputStream(); + } + catch (IOException e) + { + throw new TransportException("Error getting output stream for socket", e); + } + + try + { + 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())); + senderThread.start(); + } + + private static final int pof2(int n) + { + int result = 1; + while (result < n) + { + result *= 2; + } + return result; + } + + public void send(ByteBuffer buf) + { + if (closed.get()) + { + throw new SenderException("sender is closed", exception); + } + + final int size = buffer.length; + int remaining = buf.remaining(); + + while (remaining > 0) + { + final int hd = head; + final int tl = tail; + + if (hd - tl >= size) + { + flush(); + synchronized (notFull) + { + long start = System.currentTimeMillis(); + long elapsed = 0; + while (!closed.get() && head - tail >= size && elapsed < timeout) + { + try + { + notFull.wait(timeout - elapsed); + } + catch (InterruptedException e) + { + // pass + } + elapsed += System.currentTimeMillis() - start; + } + + if (closed.get()) + { + throw new SenderException("sender is closed", exception); + } + + if (head - tail >= size) + { + throw new SenderException(String.format("write timed out: %s, %s", head, tail)); + } + } + continue; + } + + final int hd_idx = mod(hd, size); + final int tl_idx = mod(tl, size); + final int length; + + if (tl_idx > hd_idx) + { + length = Math.min(tl_idx - hd_idx, remaining); + } + else + { + length = Math.min(size - hd_idx, remaining); + } + + buf.get(buffer, hd_idx, length); + head += length; + remaining -= length; + } + } + + public void flush() + { + if (idle) + { + synchronized (notEmpty) + { + notEmpty.notify(); + } + } + } + + public void close() + { + close(true); + } + + void close(boolean reportException) + { + if (!closed.getAndSet(true)) + { + synchronized (notFull) + { + notFull.notify(); + } + + synchronized (notEmpty) + { + notEmpty.notify(); + } + + try + { + if (Thread.currentThread() != senderThread) + { + senderThread.join(timeout); + if (senderThread.isAlive()) + { + throw new SenderException("join timed out"); + } + } + ioCtx.getReceiver().close(false); + } + catch (InterruptedException e) + { + throw new SenderException(e); + } + + if (reportException && exception != null) + { + throw new SenderException(exception); + } + } + } + + public void run() + { + final int size = buffer.length; + while (true) + { + final int hd = head; + final int tl = tail; + + if (hd == tl) + { + if (closed.get()) + { + break; + } + + idle = true; + + synchronized (notEmpty) + { + while (head == tail && !closed.get()) + { + try + { + notEmpty.wait(); + } + catch (InterruptedException e) + { + // pass + } + } + } + + idle = false; + + continue; + } + + final int hd_idx = mod(hd, size); + final int tl_idx = mod(tl, size); + + final int length; + if (tl_idx < hd_idx) + { + length = hd_idx - tl_idx; + } + else + { + length = size - tl_idx; + } + + try + { + out.write(buffer, tl_idx, length); + } + catch (IOException e) + { + log.error(e, "error in write thread"); + exception = e; + close(false); + break; + } + tail += length; + if (head - tl >= size) + { + synchronized (notFull) + { + notFull.notify(); + } + } + } + } + + public void setIdleTimeout(int i) + { + try + { + socket.setSoTimeout(i); + } + catch (Exception e) + { + throw new SenderException(e); + } + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoTransport.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoTransport.java new file mode 100644 index 0000000000..bfdbb34978 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/qpid/transport/network/mina/MINANetworkDriver.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/mina/MINANetworkDriver.java new file mode 100644 index 0000000000..0f2c0d0226 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/qpid/transport/network/mina/MinaHandler.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/mina/MinaHandler.java new file mode 100644 index 0000000000..b89eed48b0 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/qpid/transport/network/mina/MinaSender.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/mina/MinaSender.java new file mode 100644 index 0000000000..22b9c5e784 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/qpid/transport/network/nio/NioHandler.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/nio/NioHandler.java new file mode 100644 index 0000000000..84e66c25bd --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/qpid/transport/network/nio/NioSender.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/nio/NioSender.java new file mode 100644 index 0000000000..2fa875f279 --- /dev/null +++ b/qpid/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/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/SecurityLayer.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/SecurityLayer.java new file mode 100644 index 0000000000..3f0966903d --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/SecurityLayer.java @@ -0,0 +1,185 @@ +/* +* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.nio.ByteBuffer; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + +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; +import org.apache.qpid.transport.TransportException; +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; + +public class SecurityLayer +{ + 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/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/sasl/SASLEncryptor.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/sasl/SASLEncryptor.java new file mode 100644 index 0000000000..7964239e31 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/sasl/SASLEncryptor.java @@ -0,0 +1,66 @@ +package org.apache.qpid.transport.network.security.sasl; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.concurrent.atomic.AtomicBoolean; + +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslClient; + +import org.apache.qpid.transport.Connection; +import org.apache.qpid.transport.ConnectionException; +import org.apache.qpid.transport.ConnectionListener; + +public abstract class SASLEncryptor implements ConnectionListener +{ + protected SaslClient saslClient; + protected boolean securityLayerEstablished = false; + protected int sendBuffSize; + protected int recvBuffSize; + + public boolean isSecurityLayerEstablished() + { + return securityLayerEstablished; + } + + public void opened(Connection conn) + { + if (conn.getSaslClient() != null) + { + saslClient = conn.getSaslClient(); + if (saslClient.isComplete() && saslClient.getNegotiatedProperty(Sasl.QOP) == "auth-conf") + { + sendBuffSize = Integer.parseInt( + (String)saslClient.getNegotiatedProperty(Sasl.RAW_SEND_SIZE)); + recvBuffSize = Integer.parseInt( + (String)saslClient.getNegotiatedProperty(Sasl.MAX_BUFFER)); + securityLayerEstablished(); + securityLayerEstablished = true; + } + } + } + + public void exception(Connection conn, ConnectionException exception){} + public void closed(Connection conn) {} + + public abstract void securityLayerEstablished(); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/sasl/SASLReceiver.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/sasl/SASLReceiver.java new file mode 100644 index 0000000000..86106318ef --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/sasl/SASLReceiver.java @@ -0,0 +1,86 @@ +package org.apache.qpid.transport.network.security.sasl; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 javax.security.sasl.SaslClient; +import javax.security.sasl.SaslException; + +import org.apache.qpid.transport.Receiver; +import org.apache.qpid.transport.SenderException; +import org.apache.qpid.transport.util.Logger; + +public class SASLReceiver extends SASLEncryptor implements Receiver<ByteBuffer> { + + Receiver<ByteBuffer> delegate; + private byte[] netData; + private static final Logger log = Logger.get(SASLReceiver.class); + + public SASLReceiver(Receiver<ByteBuffer> delegate) + { + this.delegate = delegate; + } + + public void closed() + { + delegate.closed(); + } + + + public void exception(Throwable t) + { + delegate.exception(t); + } + + public void received(ByteBuffer buf) + { + if (isSecurityLayerEstablished()) + { + while (buf.hasRemaining()) + { + int length = Math.min(buf.remaining(),recvBuffSize); + buf.get(netData, 0, length); + try + { + byte[] out = saslClient.unwrap(netData, 0, length); + delegate.received(ByteBuffer.wrap(out)); + } + catch (SaslException e) + { + throw new SenderException("SASL Sender, Error occurred while encrypting data",e); + } + } + } + else + { + delegate.received(buf); + } + } + + public void securityLayerEstablished() + { + netData = new byte[recvBuffSize]; + log.debug("SASL Security Layer Established"); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/sasl/SASLSender.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/sasl/SASLSender.java new file mode 100644 index 0000000000..27255f79f6 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/sasl/SASLSender.java @@ -0,0 +1,123 @@ +package org.apache.qpid.transport.network.security.sasl; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util.concurrent.atomic.AtomicBoolean; + +import javax.security.sasl.SaslClient; +import javax.security.sasl.SaslException; + +import org.apache.qpid.transport.Sender; +import org.apache.qpid.transport.SenderException; +import org.apache.qpid.transport.util.Logger; + +public class SASLSender extends SASLEncryptor implements Sender<ByteBuffer> { + + protected Sender<ByteBuffer> delegate; + private byte[] appData; + private final AtomicBoolean closed = new AtomicBoolean(false); + private static final Logger log = Logger.get(SASLSender.class); + + public SASLSender(Sender<ByteBuffer> delegate) + { + this.delegate = delegate; + log.debug("SASL Sender enabled"); + } + + @Override + public void close() + { + + if (!closed.getAndSet(true)) + { + delegate.close(); + if (isSecurityLayerEstablished()) + { + try + { + saslClient.dispose(); + } + catch (SaslException e) + { + throw new SenderException("Error closing SASL Sender",e); + } + } + } + } + + @Override + public void flush() + { + delegate.flush(); + } + + @Override + public void send(ByteBuffer buf) + { + if (closed.get()) + { + throw new SenderException("SSL Sender is closed"); + } + + if (isSecurityLayerEstablished()) + { + while (buf.hasRemaining()) + { + int length = Math.min(buf.remaining(),sendBuffSize); + log.debug("sendBuffSize %s", sendBuffSize); + log.debug("buf.remaining() %s", buf.remaining()); + + buf.get(appData, 0, length); + try + { + byte[] out = saslClient.wrap(appData, 0, length); + log.debug("out.length %s", out.length); + + delegate.send(ByteBuffer.wrap(out)); + } + catch (SaslException e) + { + log.error("Exception while encrypting data.",e); + throw new SenderException("SASL Sender, Error occurred while encrypting data",e); + } + } + } + else + { + delegate.send(buf); + } + } + + @Override + public void setIdleTimeout(int i) + { + delegate.setIdleTimeout(i); + } + + public void securityLayerEstablished() + { + appData = new byte[sendBuffSize]; + log.debug("SASL Security Layer Established"); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/QpidClientX509KeyManager.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/QpidClientX509KeyManager.java new file mode 100644 index 0000000000..14f28f8828 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/QpidClientX509KeyManager.java @@ -0,0 +1,100 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.ssl; + +import java.net.Socket; +import java.security.KeyStore; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.X509ExtendedKeyManager; + +import org.apache.qpid.transport.util.Logger; + +public class QpidClientX509KeyManager extends X509ExtendedKeyManager +{ + private static final Logger log = Logger.get(QpidClientX509KeyManager.class); + + X509ExtendedKeyManager delegate; + String alias; + + public QpidClientX509KeyManager(String alias, String keyStorePath, + String keyStorePassword,String keyStoreCertType) throws Exception + { + this.alias = alias; + KeyStore ks = SSLUtil.getInitializedKeyStore(keyStorePath,keyStorePassword); + KeyManagerFactory kmf = KeyManagerFactory.getInstance(keyStoreCertType); + 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/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/SSLReceiver.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/SSLReceiver.java new file mode 100644 index 0000000000..e227a51729 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/SSLReceiver.java @@ -0,0 +1,202 @@ +/* +* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.ssl; + +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 org.apache.qpid.transport.ConnectionSettings; +import org.apache.qpid.transport.Receiver; +import org.apache.qpid.transport.TransportException; +import org.apache.qpid.transport.util.Logger; + +public class SSLReceiver implements Receiver<ByteBuffer> +{ + 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); + + 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); + notificationToken = sender.getNotificationToken(); + } + + public void setConnectionSettings(ConnectionSettings settings) + { + this.settings = settings; + } + + public void closed() + { + delegate.closed(); + } + + public void exception(Throwable t) + { + delegate.exception(t); + } + + private ByteBuffer addPreviouslyUnreadData(ByteBuffer buf) + { + if (dataCached) + { + ByteBuffer b = ByteBuffer.allocate(localBuffer.remaining() + buf.remaining()); + b.put(localBuffer); + b.put(buf); + b.flip(); + dataCached = false; + return b; + } + else + { + return buf; + } + } + + public void received(ByteBuffer buf) + { + ByteBuffer netData = addPreviouslyUnreadData(buf); + + HandshakeStatus handshakeStatus; + Status status; + + while (netData.hasRemaining()) + { + try + { + SSLEngineResult result = engine.unwrap(netData, appData); + synchronized (notificationToken) + { + notificationToken.notifyAll(); + } + + int read = result.bytesProduced(); + status = result.getStatus(); + handshakeStatus = result.getHandshakeStatus(); + + if (read > 0) + { + int limit = appData.limit(); + appData.limit(appData.position()); + appData.position(appData.position() - read); + + ByteBuffer data = appData.slice(); + + appData.limit(limit); + appData.position(appData.position() + read); + + delegate.received(data); + } + + + switch(status) + { + case CLOSED: + synchronized(notificationToken) + { + notificationToken.notifyAll(); + } + return; + + case BUFFER_OVERFLOW: + appData = ByteBuffer.allocate(sslBufSize); + continue; + + case BUFFER_UNDERFLOW: + localBuffer.clear(); + localBuffer.put(netData); + localBuffer.flip(); + dataCached = true; + break; + + case OK: + break; // do nothing + + default: + throw new IllegalStateException("SSLReceiver: Invalid State " + status); + } + + switch (handshakeStatus) + { + case NEED_UNWRAP: + if (netData.hasRemaining()) + { + continue; + } + break; + + case NEED_TASK: + sender.doTasks(); + handshakeStatus = engine.getHandshakeStatus(); + + case FINISHED: + if (this.settings != null && this.settings.isVerifyHostname() ) + { + SSLUtil.verifyHostname(engine, this.settings.getHost()); + } + + case NEED_WRAP: + case NOT_HANDSHAKING: + synchronized(notificationToken) + { + notificationToken.notifyAll(); + } + break; + + default: + throw new IllegalStateException("SSLReceiver: Invalid State " + status); + } + + + } + catch(SSLException e) + { + log.error(e, "Error caught in SSLReceiver"); + sender.setErrorFlag(); + synchronized(notificationToken) + { + notificationToken.notifyAll(); + } + exception(new TransportException("Error in SSLReceiver",e)); + } + + } + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/SSLSender.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/SSLSender.java new file mode 100644 index 0000000000..cd47a11825 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/SSLSender.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.security.ssl; + +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicBoolean; + +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 org.apache.qpid.transport.ConnectionSettings; +import org.apache.qpid.transport.Sender; +import org.apache.qpid.transport.SenderException; +import org.apache.qpid.transport.util.Logger; + +public class SSLSender implements Sender<ByteBuffer> +{ + 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) + { + this.engine = engine; + this.delegate = delegate; + sslBufSize = engine.getSession().getPacketBufferSize(); + netData = ByteBuffer.allocate(sslBufSize); + timeout = Long.getLong("qpid.ssl_timeout", 60000); + } + + public void setConnectionSettings(ConnectionSettings settings) + { + this.settings = settings; + } + + public void close() + { + if (!closed.getAndSet(true)) + { + if (engine.isOutboundDone()) + { + return; + } + log.debug("Closing SSL connection"); + + engine.closeOutbound(); + try + { + tearDownSSLConnection(); + } + catch(Exception e) + { + throw new SenderException("Error closing SSL connection",e); + } + + + synchronized(engineState) + { + while (!engine.isOutboundDone()) + { + try + { + engineState.wait(); + } + catch(InterruptedException e) + { + // pass + } + + } + } + delegate.close(); + } + } + + private void tearDownSSLConnection() throws Exception + { + SSLEngineResult result = engine.wrap(ByteBuffer.allocate(0), netData); + Status status = result.getStatus(); + int read = result.bytesProduced(); + while (status != Status.CLOSED) + { + if (status == Status.BUFFER_OVERFLOW) + { + netData.clear(); + } + if(read > 0) + { + int limit = netData.limit(); + netData.limit(netData.position()); + netData.position(netData.position() - read); + + ByteBuffer data = netData.slice(); + + netData.limit(limit); + netData.position(netData.position() + read); + + delegate.send(data); + flush(); + } + result = engine.wrap(ByteBuffer.allocate(0), netData); + status = result.getStatus(); + read = result.bytesProduced(); + } + } + + public void flush() + { + delegate.flush(); + } + + public void send(ByteBuffer appData) + { + if (closed.get()) + { + throw new SenderException("SSL Sender is closed"); + } + + HandshakeStatus handshakeStatus; + Status status; + + while(appData.hasRemaining() && !error.get()) + { + int read = 0; + try + { + SSLEngineResult result = engine.wrap(appData, netData); + read = result.bytesProduced(); + status = result.getStatus(); + handshakeStatus = result.getHandshakeStatus(); + } + catch(SSLException e) + { + throw new SenderException("SSL, Error occurred while encrypting data",e); + } + + if(read > 0) + { + int limit = netData.limit(); + netData.limit(netData.position()); + netData.position(netData.position() - read); + + ByteBuffer data = netData.slice(); + + netData.limit(limit); + netData.position(netData.position() + read); + + delegate.send(data); + } + + switch(status) + { + case CLOSED: + throw new SenderException("SSLEngine is closed"); + + case BUFFER_OVERFLOW: + netData.clear(); + continue; + + case OK: + break; // do nothing + + default: + throw new IllegalStateException("SSLReceiver: Invalid State " + status); + } + + switch (handshakeStatus) + { + case NEED_WRAP: + if (netData.hasRemaining()) + { + continue; + } + + case NEED_TASK: + doTasks(); + break; + + case NEED_UNWRAP: + flush(); + synchronized(engineState) + { + switch (engine.getHandshakeStatus()) + { + case NEED_UNWRAP: + long start = System.currentTimeMillis(); + try + { + engineState.wait(timeout); + } + catch(InterruptedException e) + { + // pass + } + + if (System.currentTimeMillis()- start >= timeout) + { + throw new SenderException( + "SSL Engine timed out waiting for a response." + + "To get more info,run with -Djavax.net.debug=ssl"); + } + break; + } + } + break; + + case FINISHED: + if (this.settings != null && this.settings.isVerifyHostname() ) + { + SSLUtil.verifyHostname(engine, this.settings.getHost()); + } + + case NOT_HANDSHAKING: + break; //do nothing + + default: + throw new IllegalStateException("SSLSender: Invalid State " + status); + } + + } + } + + public void doTasks() + { + Runnable runnable; + while ((runnable = engine.getDelegatedTask()) != null) { + runnable.run(); + } + } + + public Object getNotificationToken() + { + return engineState; + } + + public void setErrorFlag() + { + error.set(true); + } + + public void setIdleTimeout(int i) + { + delegate.setIdleTimeout(i); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/SSLUtil.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/SSLUtil.java new file mode 100644 index 0000000000..fd73915b65 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/security/ssl/SSLUtil.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.qpid.transport.network.security.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 java.security.Principal; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLPeerUnverifiedException; + +import org.apache.qpid.ssl.SSLContextFactory; +import org.apache.qpid.transport.ConnectionSettings; +import org.apache.qpid.transport.TransportException; +import org.apache.qpid.transport.util.Logger; + +public class SSLUtil +{ + private static final Logger log = Logger.get(SSLUtil.class); + + public static void verifyHostname(SSLEngine engine,String hostnameExpected) + { + try + { + Certificate cert = engine.getSession().getPeerCertificates()[0]; + Principal p = ((X509Certificate)cert).getSubjectDN(); + String dn = p.getName(); + String hostname = null; + + if (dn.contains("CN=")) + { + hostname = dn.substring(3, + dn.indexOf(",") == -1? dn.length(): dn.indexOf(",")); + } + + if (log.isDebugEnabled()) + { + log.debug("Hostname expected : " + hostnameExpected); + log.debug("Distinguished Name for server certificate : " + dn); + log.debug("Host Name obtained from DN : " + hostname); + } + + if (hostname != null && !(hostname.equalsIgnoreCase(hostnameExpected) || + hostname.equalsIgnoreCase(hostnameExpected + ".localdomain"))) + { + throw new TransportException("SSL hostname verification failed." + + " Expected : " + hostnameExpected + + " Found in cert : " + hostname); + } + + } + catch(SSLPeerUnverifiedException e) + { + log.warn("Exception received while trying to verify hostname",e); + // For some reason the SSL engine sets the handshake status to FINISH twice + // in succession. The first time the peer certificate + // info is not available. The second time it works ! + // Therefore have no choice but to ignore the exception here. + } + } + + public static String retriveIdentity(SSLEngine engine) + { + StringBuffer id = new StringBuffer(); + try + { + Certificate cert = engine.getSession().getLocalCertificates()[0]; + Principal p = ((X509Certificate)cert).getSubjectDN(); + String dn = p.getName(); + + if (dn.contains("CN=")) + { + id.append(dn.substring(3, + dn.indexOf(",") == -1? dn.length(): dn.indexOf(","))); + } + + if (dn.contains("DC=")) + { + id.append("@"); + int c = 0; + for (String toks : dn.split(",")) + { + if (toks.contains("DC")) + { + if (c > 0) {id.append(".");} + id.append(toks.substring( + toks.indexOf("=")+1, + toks.indexOf(",") == -1? toks.length(): toks.indexOf(","))); + c++; + } + } + } + } + catch(Exception e) + { + log.info("Exception received while trying to retrive client identity from SSL cert",e); + } + + log.debug("Extracted Identity from client certificate : " + id); + 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"); + InputStream in = null; + try + { + File f = new File(storePath); + if (f.exists()) + { + in = new FileInputStream(f); + } + else + { + in = Thread.currentThread().getContextClassLoader().getResourceAsStream(storePath); + } + if (in == null) + { + throw new IOException("Unable to load keystore resource: " + storePath); + } + ks.load(in, storePassword.toCharArray()); + } + finally + { + if (in != null) + { + //noinspection EmptyCatchBlock + try + { + in.close(); + } + catch (IOException ignored) + { + } + } + } + return ks; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/util/Functions.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/util/Functions.java new file mode 100644 index 0000000000..5761228642 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/util/Functions.java @@ -0,0 +1,102 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util; + +import java.nio.ByteBuffer; + +import static java.lang.Math.*; + + +/** + * Functions + * + * @author Rafael H. Schloming + */ + +public class Functions +{ + + public static final int mod(int n, int m) + { + int r = n % m; + return r < 0 ? m + r : r; + } + + public static final byte lsb(int i) + { + return (byte) (0xFF & i); + } + + public static final byte lsb(long l) + { + return (byte) (0xFF & l); + } + + public static final String str(ByteBuffer buf) + { + return str(buf, buf.remaining()); + } + + public static final String str(ByteBuffer buf, int limit) + { + return str(buf, limit,buf.position()); + } + + public static final String str(ByteBuffer buf, int limit,int start) + { + StringBuilder str = new StringBuilder(); + str.append('"'); + + for (int i = start; i < min(buf.limit(), limit); i++) + { + byte c = buf.get(i); + + if (c > 31 && c < 127 && c != '\\') + { + str.append((char)c); + } + else + { + str.append(String.format("\\x%02x", c)); + } + } + + str.append('"'); + + if (limit < buf.remaining()) + { + str.append("..."); + } + + return str.toString(); + } + + public static final String str(byte[] bytes) + { + return str(ByteBuffer.wrap(bytes)); + } + + public static final String str(byte[] bytes, int limit) + { + return str(ByteBuffer.wrap(bytes), limit); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/util/Logger.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/util/Logger.java new file mode 100644 index 0000000000..8c4818df92 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/util/Logger.java @@ -0,0 +1,130 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util; + +import org.slf4j.LoggerFactory; + +/** + * Logger + * + */ + +public final class Logger +{ + + public static final Logger get(Class<?> klass) + { + return new Logger(LoggerFactory.getLogger(klass)); + } + + private final org.slf4j.Logger log; + + private Logger(org.slf4j.Logger log) + { + this.log = log; + } + + public boolean isDebugEnabled() + { + return log.isDebugEnabled(); + } + + public void debug(String message, Object ... args) + { + if (log.isDebugEnabled()) + { + log.debug(String.format(message, args)); + } + } + + public void debug(Throwable t, String message, Object ... args) + { + if (log.isDebugEnabled()) + { + log.debug(String.format(message, args), t); + } + } + + public void error(String message, Object ... args) + { + if (log.isErrorEnabled()) + { + log.error(String.format(message, args)); + } + } + + public void error(Throwable t, String message, Object ... args) + { + if (log.isErrorEnabled()) + { + log.error(String.format(message, args), t); + } + } + + public void warn(String message, Object ... args) + { + if (log.isWarnEnabled()) + { + log.warn(String.format(message, args)); + } + } + + public void warn(Throwable t, String message, Object ... args) + { + if (log.isWarnEnabled()) + { + log.warn(String.format(message, args), t); + } + } + + public void info(String message, Object ... args) + { + if (log.isInfoEnabled()) + { + log.info(String.format(message, args)); + } + } + + public void info(Throwable t, String message, Object ... args) + { + if (log.isInfoEnabled()) + { + log.info(String.format(message, args), t); + } + } + + public void trace(String message, Object ... args) + { + if (log.isTraceEnabled()) + { + log.trace(String.format(message, args)); + } + } + + public void trace(Throwable t, String message, Object ... args) + { + if (log.isTraceEnabled()) + { + log.trace(String.format(message, args), t); + } + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/util/SliceIterator.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/util/SliceIterator.java new file mode 100644 index 0000000000..3db29847b2 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/util/SliceIterator.java @@ -0,0 +1,59 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util; + +import java.nio.ByteBuffer; + +import java.util.Iterator; + + +/** + * SliceIterator + * + * @author Rafael H. Schloming + */ + +public class SliceIterator implements Iterator<ByteBuffer> +{ + + final private Iterator<ByteBuffer> iterator; + + public SliceIterator(Iterator<ByteBuffer> iterator) + { + this.iterator = iterator; + } + + public boolean hasNext() + { + return iterator.hasNext(); + } + + public ByteBuffer next() + { + return iterator.next().slice(); + } + + public void remove() + { + throw new UnsupportedOperationException(); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/util/Waiter.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/util/Waiter.java new file mode 100644 index 0000000000..e034d779ca --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/util/Waiter.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.util; + + +/** + * Waiter + * + */ + +public final class Waiter +{ + + private final Object lock; + private final long timeout; + private final long start; + private long elapsed; + + public Waiter(Object lock, long timeout) + { + this.lock = lock; + this.timeout = timeout; + this.start = System.currentTimeMillis(); + this.elapsed = 0; + } + + public boolean hasTime() + { + return elapsed < timeout; + } + + public void await() + { + try + { + lock.wait(timeout - elapsed); + } + catch (InterruptedException e) + { + // pass + } + elapsed = System.currentTimeMillis() - start; + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/url/AMQBindingURL.java b/qpid/java/common/src/main/java/org/apache/qpid/url/AMQBindingURL.java new file mode 100644 index 0000000000..26cb56ea97 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/url/AMQBindingURL.java @@ -0,0 +1,237 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.url; + +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AMQBindingURL implements BindingURL +{ + private static final Logger _logger = LoggerFactory.getLogger(AMQBindingURL.class); + + String _url; + AMQShortString _exchangeClass = ExchangeDefaults.DIRECT_EXCHANGE_CLASS; + AMQShortString _exchangeName = new AMQShortString(""); + AMQShortString _destinationName = new AMQShortString("");; + AMQShortString _queueName = new AMQShortString(""); + AMQShortString[] _bindingKeys = new AMQShortString[0]; + private HashMap<String, String> _options; + + public AMQBindingURL(String url) throws URISyntaxException + { + // format: + // <exch_class>://<exch_name>/[<destination>]/[<queue>]?<option>='<value>'[,<option>='<value>']* + _logger.debug("Parsing URL: " + url); + _url = url; + _options = new HashMap<String, String>(); + + parseBindingURL(); + } + + private void parseBindingURL() throws URISyntaxException + { + BindingURLParser parser = new BindingURLParser(); + parser.parse(_url,this); + processOptions(); + _logger.debug("URL Parsed: " + this); + } + + public void setExchangeClass(String exchangeClass) + { + setExchangeClass(new AMQShortString(exchangeClass)); + } + + public void setQueueName(String name) + { + setQueueName(new AMQShortString(name)); + } + + public void setDestinationName(String name) + { + setDestinationName(new AMQShortString(name)); + } + + public void setExchangeName(String exchangeName) + { + setExchangeName(new AMQShortString(exchangeName)); + } + + private void processOptions() throws URISyntaxException + { + } + + public String getURL() + { + return _url; + } + + public AMQShortString getExchangeClass() + { + return _exchangeClass; + } + + private void setExchangeClass(AMQShortString exchangeClass) + { + + _exchangeClass = exchangeClass; + if (exchangeClass.equals(ExchangeDefaults.TOPIC_EXCHANGE_CLASS)) + { + setOption(BindingURL.OPTION_EXCLUSIVE, "true"); + } + + } + + public AMQShortString getExchangeName() + { + return _exchangeName; + } + + private void setExchangeName(AMQShortString name) + { + _exchangeName = name; + } + + public AMQShortString getDestinationName() + { + return _destinationName; + } + + private void setDestinationName(AMQShortString name) + { + _destinationName = name; + } + + public AMQShortString getQueueName() + { + return _queueName; + } + + public void setQueueName(AMQShortString name) + { + _queueName = name; + } + + public String getOption(String key) + { + return _options.get(key); + } + + public void setOption(String key, String value) + { + _options.put(key, value); + } + + public boolean containsOption(String key) + { + return _options.containsKey(key); + } + + public AMQShortString getRoutingKey() + { + if (_exchangeClass.equals(ExchangeDefaults.DIRECT_EXCHANGE_CLASS)) + { + if (containsOption(BindingURL.OPTION_ROUTING_KEY)) + { + return new AMQShortString((String)getOption(OPTION_ROUTING_KEY)); + } + else + { + return getQueueName(); + } + } + + if (containsOption(BindingURL.OPTION_ROUTING_KEY)) + { + return new AMQShortString((String)getOption(OPTION_ROUTING_KEY)); + } + + return getDestinationName(); + } + + public AMQShortString[] getBindingKeys() + { + if (_bindingKeys != null && _bindingKeys.length>0) + { + return _bindingKeys; + } + else + { + return new AMQShortString[]{getRoutingKey()}; + } + } + + public void setBindingKeys(AMQShortString[] keys) + { + _bindingKeys = keys; + } + + public void setRoutingKey(AMQShortString key) + { + setOption(OPTION_ROUTING_KEY, key.toString()); + } + + public String toString() + { + StringBuffer sb = new StringBuffer(); + + sb.append(_exchangeClass); + sb.append("://"); + sb.append(_exchangeName); + sb.append('/'); + sb.append(_destinationName); + sb.append('/'); + sb.append(_queueName); + + sb.append(URLHelper.printOptions(_options)); + + // temp hack + if (getRoutingKey() == null || getRoutingKey().toString().equals("")) + { + + if (sb.toString().indexOf("?") == -1) + { + sb.append("?"); + } + else + { + sb.append("&"); + } + + for (AMQShortString key :_bindingKeys) + { + sb.append(BindingURL.OPTION_BINDING_KEY).append("='").append(key.toString()).append("'&"); + } + + return sb.toString().substring(0,sb.toString().length()-1); + } + else + { + return sb.toString(); + } + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/url/BindingURL.java b/qpid/java/common/src/main/java/org/apache/qpid/url/BindingURL.java new file mode 100644 index 0000000000..9996fff311 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/url/BindingURL.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.url; + +import org.apache.qpid.framing.AMQShortString; + +/* + Binding URL format: + <exch_class>://<exch_name>/[<destination>]/[<queue>]?<option>='<value>'[,<option>='<value>']* +*/ +public interface BindingURL +{ + public static final String OPTION_EXCLUSIVE = "exclusive"; + public static final String OPTION_AUTODELETE = "autodelete"; + public static final String OPTION_DURABLE = "durable"; + public static final String OPTION_BROWSE = "browse"; + public static final String OPTION_CLIENTID = "clientid"; + public static final String OPTION_SUBSCRIPTION = "subscription"; + public static final String OPTION_ROUTING_KEY = "routingkey"; + public static final String OPTION_BINDING_KEY = "bindingkey"; + + + String getURL(); + + AMQShortString getExchangeClass(); + + AMQShortString getExchangeName(); + + AMQShortString getDestinationName(); + + AMQShortString getQueueName(); + + String getOption(String key); + + boolean containsOption(String key); + + AMQShortString getRoutingKey(); + + AMQShortString[] getBindingKeys(); + + String toString(); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/url/BindingURLParser.java b/qpid/java/common/src/main/java/org/apache/qpid/url/BindingURLParser.java new file mode 100644 index 0000000000..0ebfe0e869 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/url/BindingURLParser.java @@ -0,0 +1,478 @@ +package org.apache.qpid.url; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BindingURLParser +{ + private static final char PROPERTY_EQUALS_CHAR = '='; + private static final char PROPERTY_SEPARATOR_CHAR = '&'; + private static final char ALTERNATIVE_PROPERTY_SEPARATOR_CHAR = ','; + private static final char FORWARD_SLASH_CHAR = '/'; + private static final char QUESTION_MARK_CHAR = '?'; + private static final char SINGLE_QUOTE_CHAR = '\''; + private static final char COLON_CHAR = ':'; + private static final char END_OF_URL_MARKER_CHAR = '%'; + + private static final Logger _logger = LoggerFactory.getLogger(BindingURLParser.class); + + private char[] _url; + private AMQBindingURL _bindingURL; + private BindingURLParserState _currentParserState; + private String _error; + private int _index = 0; + private String _currentPropName; + private Map<String,Object> _options; + + + public BindingURLParser() + { + } + + //<exch_class>://<exch_name>/[<destination>]/[<queue>]?<option>='<value>'[,<option>='<value>']* + public synchronized void parse(String url,AMQBindingURL bindingURL) throws URISyntaxException + { + _url = (url + END_OF_URL_MARKER_CHAR).toCharArray(); + _bindingURL = bindingURL; + _currentParserState = BindingURLParserState.BINDING_URL_START; + BindingURLParserState prevState = _currentParserState; + _index = 0; + _currentPropName = null; + _error = null; + _options = new HashMap<String,Object>(); + + try + { + while (_currentParserState != BindingURLParserState.ERROR && _currentParserState != BindingURLParserState.BINDING_URL_END) + { + prevState = _currentParserState; + _currentParserState = next(); + } + + if (_currentParserState == BindingURLParserState.ERROR) + { + _error = + "Invalid URL format [current_state = " + prevState + ", details parsed so far " + _bindingURL + " ] error at (" + _index + ") due to " + _error; + _logger.debug(_error); + URISyntaxException ex; + ex = new URISyntaxException(markErrorLocation(),"Error occured while parsing URL",_index); + throw ex; + } + + processOptions(); + } + catch (ArrayIndexOutOfBoundsException e) + { + _error = "Invalid URL format [current_state = " + prevState + ", details parsed so far " + _bindingURL + " ] error at (" + _index + ")"; + URISyntaxException ex = new URISyntaxException(markErrorLocation(),"Error occured while parsing URL",_index); + ex.initCause(e); + throw ex; + } + } + + enum BindingURLParserState + { + BINDING_URL_START, + EXCHANGE_CLASS, + COLON_CHAR, + DOUBLE_SEP, + EXCHANGE_NAME, + EXCHANGE_SEPERATOR_CHAR, + DESTINATION, + DESTINATION_SEPERATOR_CHAR, + QUEUE_NAME, + QUESTION_MARK_CHAR, + PROPERTY_NAME, + PROPERTY_EQUALS, + START_PROPERTY_VALUE, + PROPERTY_VALUE, + END_PROPERTY_VALUE, + PROPERTY_SEPARATOR, + BINDING_URL_END, + ERROR + } + + /** + * I am fully ware that there are few optimizations + * that can speed up things a wee bit. But I have opted + * for readability and maintainability at the expense of + * speed, as speed is not a critical factor here. + * + * One can understand the full parse logic by just looking at this method. + */ + private BindingURLParserState next() + { + switch (_currentParserState) + { + case BINDING_URL_START: + return extractExchangeClass(); + case COLON_CHAR: + _index++; //skip ":" + return BindingURLParserState.DOUBLE_SEP; + case DOUBLE_SEP: + _index = _index + 2; //skip "//" + return BindingURLParserState.EXCHANGE_NAME; + case EXCHANGE_NAME: + return extractExchangeName(); + case EXCHANGE_SEPERATOR_CHAR: + _index++; // skip '/' + return BindingURLParserState.DESTINATION; + case DESTINATION: + return extractDestination(); + case DESTINATION_SEPERATOR_CHAR: + _index++; // skip '/' + return BindingURLParserState.QUEUE_NAME; + case QUEUE_NAME: + return extractQueueName(); + case QUESTION_MARK_CHAR: + _index++; // skip '?' + return BindingURLParserState.PROPERTY_NAME; + case PROPERTY_NAME: + return extractPropertyName(); + case PROPERTY_EQUALS: + _index++; // skip the equal sign + return BindingURLParserState.START_PROPERTY_VALUE; + case START_PROPERTY_VALUE: + _index++; // skip the '\'' + return BindingURLParserState.PROPERTY_VALUE; + case PROPERTY_VALUE: + return extractPropertyValue(); + case END_PROPERTY_VALUE: + _index ++; + return checkEndOfURL(); + case PROPERTY_SEPARATOR: + _index++; // skip '&' + return BindingURLParserState.PROPERTY_NAME; + default: + return BindingURLParserState.ERROR; + } + } + + private BindingURLParserState extractExchangeClass() + { + char nextChar = _url[_index]; + + // check for the following special cases. + // "myQueue?durable='true'" or just "myQueue"; + + StringBuilder builder = new StringBuilder(); + while (nextChar != COLON_CHAR && nextChar != QUESTION_MARK_CHAR && nextChar != END_OF_URL_MARKER_CHAR) + { + builder.append(nextChar); + _index++; + nextChar = _url[_index]; + } + + // normal use case + if (nextChar == COLON_CHAR) + { + _bindingURL.setExchangeClass(builder.toString()); + return BindingURLParserState.COLON_CHAR; + } + // "myQueue?durable='true'" use case + else if (nextChar == QUESTION_MARK_CHAR) + { + _bindingURL.setExchangeClass(ExchangeDefaults.DIRECT_EXCHANGE_CLASS.asString()); + _bindingURL.setExchangeName(""); + _bindingURL.setQueueName(builder.toString()); + return BindingURLParserState.QUESTION_MARK_CHAR; + } + else + { + _bindingURL.setExchangeClass(ExchangeDefaults.DIRECT_EXCHANGE_CLASS.asString()); + _bindingURL.setExchangeName(""); + _bindingURL.setQueueName(builder.toString()); + return BindingURLParserState.BINDING_URL_END; + } + } + + private BindingURLParserState extractExchangeName() + { + char nextChar = _url[_index]; + StringBuilder builder = new StringBuilder(); + while (nextChar != FORWARD_SLASH_CHAR) + { + builder.append(nextChar); + _index++; + nextChar = _url[_index]; + } + + _bindingURL.setExchangeName(builder.toString()); + return BindingURLParserState.EXCHANGE_SEPERATOR_CHAR; + } + + private BindingURLParserState extractDestination() + { + char nextChar = _url[_index]; + + //The destination is and queue name are both optional + // This is checking for the case where both are not specified. + if (nextChar == QUESTION_MARK_CHAR) + { + return BindingURLParserState.QUESTION_MARK_CHAR; + } + + StringBuilder builder = new StringBuilder(); + while (nextChar != FORWARD_SLASH_CHAR && nextChar != QUESTION_MARK_CHAR) + { + builder.append(nextChar); + _index++; + nextChar = _url[_index]; + } + + // This is the case where the destination is explictily stated. + // ex direct://amq.direct/myDest/myQueue?option1='1' ... OR + // direct://amq.direct//myQueue?option1='1' ... + if (nextChar == FORWARD_SLASH_CHAR) + { + _bindingURL.setDestinationName(builder.toString()); + return BindingURLParserState.DESTINATION_SEPERATOR_CHAR; + } + // This is the case where destination is not explictly stated. + // ex direct://amq.direct/myQueue?option1='1' ... + else + { + _bindingURL.setQueueName(builder.toString()); + return BindingURLParserState.QUESTION_MARK_CHAR; + } + } + + private BindingURLParserState extractQueueName() + { + char nextChar = _url[_index]; + StringBuilder builder = new StringBuilder(); + while (nextChar != QUESTION_MARK_CHAR && nextChar != END_OF_URL_MARKER_CHAR) + { + builder.append(nextChar); + nextChar = _url[++_index]; + } + _bindingURL.setQueueName(builder.toString()); + + if(nextChar == QUESTION_MARK_CHAR) + { + return BindingURLParserState.QUESTION_MARK_CHAR; + } + else + { + return BindingURLParserState.BINDING_URL_END; + } + } + + private BindingURLParserState extractPropertyName() + { + StringBuilder builder = new StringBuilder(); + char next = _url[_index]; + while (next != PROPERTY_EQUALS_CHAR) + { + builder.append(next); + next = _url[++_index]; + } + _currentPropName = builder.toString(); + + if (_currentPropName.trim().equals("")) + { + _error = "Property name cannot be empty"; + return BindingURLParserState.ERROR; + } + + return BindingURLParserState.PROPERTY_EQUALS; + } + + private BindingURLParserState extractPropertyValue() + { + StringBuilder builder = new StringBuilder(); + char next = _url[_index]; + while (next != SINGLE_QUOTE_CHAR) + { + builder.append(next); + next = _url[++_index]; + } + String propValue = builder.toString(); + + if (propValue.trim().equals("")) + { + _error = "Property values cannot be empty"; + return BindingURLParserState.ERROR; + } + else + { + if (_options.containsKey(_currentPropName)) + { + Object obj = _options.get(_currentPropName); + if (obj instanceof List) + { + List list = (List)obj; + list.add(propValue); + } + else // it has to be a string + { + List<String> list = new ArrayList(); + list.add((String)obj); + list.add(propValue); + _options.put(_currentPropName, list); + } + } + else + { + _options.put(_currentPropName, propValue); + } + + + return BindingURLParserState.END_PROPERTY_VALUE; + } + } + + private BindingURLParserState checkEndOfURL() + { + char nextChar = _url[_index]; + if ( nextChar == END_OF_URL_MARKER_CHAR) + { + return BindingURLParserState.BINDING_URL_END; + } + else if (nextChar == PROPERTY_SEPARATOR_CHAR || nextChar == ALTERNATIVE_PROPERTY_SEPARATOR_CHAR) + { + return BindingURLParserState.PROPERTY_SEPARATOR; + } + else + { + return BindingURLParserState.ERROR; + } + } + + private String markErrorLocation() + { + String tmp = String.valueOf(_url); + // length -1 to remove ENDOF URL marker + return tmp.substring(0,_index) + "^" + tmp.substring(_index+1> tmp.length()-1?tmp.length()-1:_index+1,tmp.length()-1); + } + + private void processOptions() throws URISyntaxException + { +// check for bindingKey + if (_options.containsKey(BindingURL.OPTION_BINDING_KEY) && _options.get(BindingURL.OPTION_BINDING_KEY) != null) + { + Object obj = _options.get(BindingURL.OPTION_BINDING_KEY); + + if (obj instanceof String) + { + AMQShortString[] bindingKeys = new AMQShortString[]{new AMQShortString((String)obj)}; + _bindingURL.setBindingKeys(bindingKeys); + } + else // it would be a list + { + List list = (List)obj; + AMQShortString[] bindingKeys = new AMQShortString[list.size()]; + int i=0; + for (Iterator it = list.iterator(); it.hasNext();) + { + bindingKeys[i] = new AMQShortString((String)it.next()); + i++; + } + _bindingURL.setBindingKeys(bindingKeys); + } + + } + for (String key: _options.keySet()) + { + // We want to skip the bindingKey list + if (_options.get(key) instanceof String) + { + _bindingURL.setOption(key, (String)_options.get(key)); + } + } + + + // check if both a binding key and a routing key is specified. + if (_options.containsKey(BindingURL.OPTION_BINDING_KEY) && _options.containsKey(BindingURL.OPTION_ROUTING_KEY)) + { + throw new URISyntaxException(String.valueOf(_url),"It is illegal to specify both a routingKey and a bindingKey in the same URL",-1); + } + + // check for durable subscriptions + if (_bindingURL.getExchangeClass().equals(ExchangeDefaults.TOPIC_EXCHANGE_CLASS)) + { + String queueName = null; + if (Boolean.parseBoolean(_bindingURL.getOption(BindingURL.OPTION_DURABLE))) + { + if (_bindingURL.containsOption(BindingURL.OPTION_CLIENTID) && _bindingURL.containsOption(BindingURL.OPTION_SUBSCRIPTION)) + { + queueName = _bindingURL.getOption(BindingURL.OPTION_CLIENTID) + ":" + _bindingURL.getOption(BindingURL.OPTION_SUBSCRIPTION); + } + else + { + throw new URISyntaxException(String.valueOf(_url),"Durable subscription must have values for " + BindingURL.OPTION_CLIENTID + + " and " + BindingURL.OPTION_SUBSCRIPTION , -1); + + } + } + _bindingURL.setQueueName(queueName); + } + } + + public static void main(String[] args) + { + + String[] urls = new String[] + { + "topic://amq.topic//myTopic?routingkey='stocks.#'", + "topic://amq.topic/message_queue?bindingkey='usa.*'&bindingkey='control',exclusive='true'", + "topic://amq.topic//?bindingKey='usa.*',bindingkey='control',exclusive='true'", + "direct://amq.direct/dummyDest/myQueue?routingkey='abc.*'", + "exchange.Class://exchangeName/Destination/Queue", + "exchangeClass://exchangeName/Destination/?option='value',option2='value2'", + "IBMPerfQueue1?durable='true'", + "exchangeClass://exchangeName/Destination/?bindingkey='key1',bindingkey='key2'", + "exchangeClass://exchangeName/Destination/?bindingkey='key1'&routingkey='key2'" + }; + + try + { + BindingURLParser parser = new BindingURLParser(); + + for (String url: urls) + { + System.out.println("URL " + url); + AMQBindingURL bindingURL = new AMQBindingURL(url); + parser.parse(url,bindingURL); + System.out.println("\nX " + bindingURL.toString() + " \n"); + + } + + } + catch(Exception e) + { + e.printStackTrace(); + } + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/url/URLHelper.java b/qpid/java/common/src/main/java/org/apache/qpid/url/URLHelper.java new file mode 100644 index 0000000000..6f21c327e7 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/url/URLHelper.java @@ -0,0 +1,173 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.url; + +import java.util.HashMap; +import java.util.Map; + +public class URLHelper +{ + public static final char DEFAULT_OPTION_SEPERATOR = '&'; + public static final char ALTERNATIVE_OPTION_SEPARATOR = ','; + public static final char BROKER_SEPARATOR = ';'; + + 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; + } + + int optionIndex = options.indexOf('='); + + String option = options.substring(0, optionIndex); + + int length = options.length(); + + int nestedQuotes = 0; + + // to store index of final "'" + int valueIndex = optionIndex; + + // Walk remainder of url. + while ((nestedQuotes > 0) || (valueIndex < length)) + { + valueIndex++; + + if (valueIndex >= length) + { + break; + } + + if (options.charAt(valueIndex) == '\'') + { + if ((valueIndex + 1) < options.length()) + { + if ((options.charAt(valueIndex + 1) == DEFAULT_OPTION_SEPERATOR) + || (options.charAt(valueIndex + 1) == ALTERNATIVE_OPTION_SEPARATOR) + || (options.charAt(valueIndex + 1) == BROKER_SEPARATOR) + || (options.charAt(valueIndex + 1) == '\'')) + { + nestedQuotes--; + + if (nestedQuotes == 0) + { + // We've found the value of an option + break; + } + } + else + { + nestedQuotes++; + } + } + else + { + // We are at the end of the string + // Check to see if we are corectly closing quotes + if (options.charAt(valueIndex) == '\'') + { + nestedQuotes--; + } + + break; + } + } + } + + if ((nestedQuotes != 0) || (valueIndex < (optionIndex + 2))) + { + int sepIndex = 0; + + // Try and identify illegal separator character + if (nestedQuotes > 1) + { + for (int i = 0; i < nestedQuotes; i++) + { + sepIndex = options.indexOf('\'', sepIndex); + sepIndex++; + } + } + + if ((sepIndex >= options.length()) || (sepIndex == 0)) + { + throw parseError(valueIndex, "Unterminated option", options); + } + else + { + throw parseError(sepIndex, "Unterminated option. Possible illegal option separator:'" + + options.charAt(sepIndex) + "'", options); + } + } + + // optionIndex +2 to skip "='" + String value = options.substring(optionIndex + 2, valueIndex); + + optionMap.put(option, value); + + if (valueIndex < (options.length() - 1)) + { + // Recurse to get remaining options + parseOptions(optionMap, options.substring(valueIndex + 2)); + } + } + + public static URLSyntaxException parseError(int index, String error, String url) + { + return parseError(index, 1, error, url); + } + + public static URLSyntaxException parseError(int index, int length, String error, String url) + { + return new URLSyntaxException(url, error, index, length); + } + + public static String printOptions(Map<String, String> options) + { + if (options.isEmpty()) + { + return ""; + } + else + { + StringBuffer sb = new StringBuffer(); + sb.append('?'); + for (String key : options.keySet()) + { + sb.append(key); + + sb.append("='"); + + sb.append(options.get(key)); + + sb.append("'"); + sb.append(DEFAULT_OPTION_SEPERATOR); + } + + sb.deleteCharAt(sb.length() - 1); + + return sb.toString(); + } + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/url/URLSyntaxException.java b/qpid/java/common/src/main/java/org/apache/qpid/url/URLSyntaxException.java new file mode 100644 index 0000000000..3ff7195794 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/url/URLSyntaxException.java @@ -0,0 +1,97 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.url; + +import java.net.URISyntaxException; + +public class URLSyntaxException extends URISyntaxException +{ + private int _length; + + public URLSyntaxException(String url, String error, int index, int length) + { + super(url, error, index); + + _length = length; + } + + private static String getPositionString(int index, int length) + { + StringBuffer sb = new StringBuffer(index + 1); + + for (int i = 0; i < index; i++) + { + sb.append(" "); + } + + if (length > -1) + { + for (int i = 0; i < length; i++) + { + sb.append('^'); + } + } + + return sb.toString(); + } + + + public String toString() + { + StringBuffer sb = new StringBuffer(); + + sb.append(getReason()); + + if (getIndex() > -1) + { + if (_length != -1) + { + sb.append(" between indicies "); + sb.append(getIndex()); + sb.append(" and "); + sb.append(_length); + } + else + { + sb.append(" at index "); + sb.append(getIndex()); + } + } + + sb.append(" "); + if (getIndex() != -1) + { + sb.append("\n"); + } + + sb.append(getInput()); + + if (getIndex() != -1) + { + sb.append("\n"); + sb.append(getPositionString(getIndex(), _length)); + } + + return sb.toString(); + } + + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/CommandLineParser.java b/qpid/java/common/src/main/java/org/apache/qpid/util/CommandLineParser.java new file mode 100644 index 0000000000..09478d4157 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/CommandLineParser.java @@ -0,0 +1,695 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.regex.*; + +/** + * CommandLineParser provides a utility for specifying the format of a command line and parsing command lines to ensure + * that they fit their specified format. A command line is made up of flags and options, both may be refered to as + * options. A flag is an option that does not take an argument (specifying it means it has the value 'true' and not + * specifying it means it has the value 'false'). Options must take arguments but they can be set up with defaults so + * that they take a default value when not set. Options may be mandatory in wich case it is an error not to specify + * them on the command line. Flags are never mandatory because they are implicitly set to false when not specified. + * + * <p/>Some example command lines are: + * + * <ul> + * <li>This one has two options that expect arguments: + * <pre> + * cruisecontrol -configfile cruisecontrol.xml -port 9000 + * </pre> + * <li>This has one no-arg flag and two 'free' arguments: + * <pre> + * zip -r project.zip project/* + * </pre> + * <li>This one concatenates multiple flags into a single block with only one '-': + * <pre> + * jar -tvf mytar.tar + * </pre> + * + * <p/>The parsing rules are: + * + * <ol> + * <li>Flags may be combined after a single '-' because they never take arguments. Normally such flags are single letter + * flags but this is only a convention and not enforced. Flags of more than one letter are usually specified on their own. + * <li>Options expecting arguments must always be on their own. + * <li>The argument to an option may be seperated from it by whitespace or appended directly onto the option. + * <li>The argument to an option may never begin with a '-' character. + * <li>All other arguments not beginning with a '-' character are free arguments that do not belong to any option. + * <li>The second or later of a set of duplicate or repeated flags are ignored. + * <li>Options are matched up to the shortest matching option. This is because of the possibility of having no space + * between an option and its argument. This rules out the possibility of using two options where one is an opening + * substring of the other. For example, the options "foo" and "foobar" cannot be used on the same command line because + * it is not possible to distinguish the argument "-foobar" from being the "foobar" option or the "foo" option with + * the "bar" argument. + * </ol> + * + * <p/>By default, unknown options are simply ignored if specified on the command line. This behaviour may be changed + * so that the parser reports all unknowns as errors by using the {@link #setErrorsOnUnknowns} method. + * + * <p><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Accept a command line specification. + * <tr><td> Parse a command line into properties, validating it against its specification. + * <tr><td> Report all errors between a command line and its specification. + * <tr><td> Provide a formatted usage string for a command line. + * <tr><td> Provide a formatted options in force string for a command line. + * <tr><td> Allow errors on unknowns behaviour to be turned on or off. + * </table> + */ +public class CommandLineParser +{ + /** Holds a mapping from command line option names to detailed information about those options. */ + private Map<String, CommandLineOption> optionMap = new HashMap<String, CommandLineOption>(); + + /** Holds a list of parsing errors. */ + private List<String> parsingErrors = new ArrayList<String>(); + + /** Holds the regular expression matcher to match command line options with. */ + private Matcher optionMatcher = null; + + /** Holds the parsed command line properties after parsing. */ + private Properties parsedProperties = null; + + /** Flag used to indicate that errors should be created for unknown options. False by default. */ + private boolean errorsOnUnknowns = false; + + /** + * Creates a command line options parser from a command line specification. This is passed to this constructor + * as an array of arrays of strings. Each array of strings specifies the command line for a single option. A static + * array may therefore easily be used to configure the command line parser in a single method call with an easily + * readable format. + * + * <p/>Each array of strings must be 2, 3, 4 or 5 elements long. If any of the last three elements are missing they + * are assumed to be null. The elements specify the following parameters: + * <ol> + * <li>The name of the option without the leading '-'. For example, "file". To specify the format of the 'free' + * arguments use the option names "1", "2", ... and so on. + * <li>The option comment. A line of text describing the usage of the option. For example, "The file to be processed." + * <li>The options argument. This is a very short description of the argument to the option, often a single word + * or a reminder as to the arguments format. When this element is null the option is a flag and does not + * accept any arguments. For example, "filename" or "(unix | windows)" or null. The actual text specified + * is only used to print in the usage message to remind the user of the usage of the option. + * <li>The mandatory flag. When set to "true" an option must always be specified. Any other value, including null, + * means that the option is mandatory. Flags are always mandatory (see class javadoc for explanation of why) so + * this is ignored for flags. + * <li>A regular expression describing the format that the argument must take. Ignored if null. + * </ol> + * <p/>An example call to this constructor is: + * + * <pre> + * CommandLineParser commandLine = new CommandLineParser( + * new String[][] {{"file", "The file to be processed. ", "filename", "true"}, + * {"dir", "Directory to store results in. Current dir used if not set.", "out dir"}, + * {"os", "Operating system EOL format to use.", "(windows | unix)", null, "windows\|unix"}, + * {"v", "Verbose mode. Prints information about the processing as it goes."}, + * {"1", "The processing command to run.", "command", "true", "add\|remove\|list"}}); + * </pre> + * + * @param config The configuration as an array of arrays of strings. + */ + public CommandLineParser(String[][] config) + { + // Loop through all the command line option specifications creating details for each in the options map. + for (int i = 0; i < config.length; i++) + { + String[] nextOptionSpec = config[i]; + + addOption(nextOptionSpec[0], nextOptionSpec[1], (nextOptionSpec.length > 2) ? nextOptionSpec[2] : null, + (nextOptionSpec.length > 3) ? ("true".equals(nextOptionSpec[3]) ? true : false) : false, + (nextOptionSpec.length > 4) ? nextOptionSpec[4] : null); + } + } + + /** + * Lists all the parsing errors from the most recent parsing in a string. + * + * @return All the parsing errors from the most recent parsing. + */ + public String getErrors() + { + // Return the empty string if there are no errors. + if (parsingErrors.isEmpty()) + { + return ""; + } + + // Concatenate all the parsing errors together. + StringBuilder result = new StringBuilder(); + + for (String s : parsingErrors) + { + result.append(s); + } + + return result.toString(); + } + + /** + * Lists the properties set from the most recent parsing or an empty string if no parsing has been done yet. + * + * @return The properties set from the most recent parsing or an empty string if no parsing has been done yet. + */ + public String getOptionsInForce() + { + // Check if there are no properties to report and return and empty string if so. + if (parsedProperties == null) + { + return ""; + } + + // List all the properties. + StringBuilder result = new StringBuilder("Options in force:\n"); + + for (Map.Entry<Object, Object> property : parsedProperties.entrySet()) + { + result.append(property.getKey()) + .append(" = ") + .append(property.getValue()) + .append('\n'); + } + + return result.toString(); + } + + /** + * Generates a usage string consisting of the name of each option and each options argument description and + * comment. + * + * @return A usage string for all the options. + */ + public String getUsage() + { + String result = "Options:\n"; + + // Print usage on each of the command line options. + for (CommandLineOption optionInfo : optionMap.values()) + { + result += + "-" + optionInfo.option + " " + ((optionInfo.argument != null) ? (optionInfo.argument + " ") : "") + + optionInfo.comment + "\n"; + } + + return result; + } + + /** + * Control the behaviour of the errors on unkowns reporting. When turned on this reports all unkowns options + * as errors. When turned off, all unknowns are simply ignored. + * + * @param errors The setting of the errors on unkown flag. True to turn it on. + */ + public void setErrorsOnUnknowns(boolean errors) + { + errorsOnUnknowns = errors; + } + + /** + * Parses a set of command line arguments into a set of properties, keyed by the argument flag. The free arguments + * are keyed by integers as strings starting at "1" and then "2", ... and so on. + * + * <p/>See the class level comment for a description of the parsing rules. + * + * @param args The command line arguments. + * + * @return The arguments as a set of properties. + * + * @throws IllegalArgumentException If the command line cannot be parsed against its specification. If this exception + * is thrown a call to {@link #getErrors} will provide a diagnostic of the command + * line errors. + */ + public Properties parseCommandLine(String[] args) throws IllegalArgumentException + { + Properties options = new Properties(); + + // Used to keep count of the current 'free' argument. + int free = 1; + + // Used to indicate that the most recently parsed option is expecting arguments. + boolean expectingArgs = false; + + // The option that is expecting arguments from the next element of the command line. + String optionExpectingArgs = null; + + // Used to indicate that the most recently parsed option is a duplicate and should be ignored. + boolean ignore = false; + + // Create the regular expression matcher for the command line options. + StringBuilder regexp = new StringBuilder("^("); + int optionsAdded = 0; + + for (Iterator<String> i = optionMap.keySet().iterator(); i.hasNext();) + { + String nextOption = i.next(); + + // Check that the option is not a free argument definition. + boolean notFree = false; + + try + { + Integer.parseInt(nextOption); + } + catch (NumberFormatException e) + { + notFree = true; + } + + // Add the option to the regular expression matcher if it is not a free argument definition. + if (notFree) + { + regexp.append(nextOption) + .append(i.hasNext() ? "|" : ""); + optionsAdded++; + } + } + + // There has to be more that one option in the regular expression or else the compiler complains that the close + // cannot be nullable if the '?' token is used to make the matched option string optional. + regexp.append(')') + .append(((optionsAdded > 0) ? "?" : "")) + .append("(.*)"); + Pattern pattern = Pattern.compile(regexp.toString()); + + // Loop through all the command line arguments. + for (int i = 0; i < args.length; i++) + { + // Check if the next command line argument begins with a '-' character and is therefore the start of + // an option. + if (args[i].startsWith("-")) + { + // Extract the value of the option without the leading '-'. + String arg = args[i].substring(1); + + // Match up to the longest matching option. + optionMatcher = pattern.matcher(arg); + optionMatcher.matches(); + + String matchedOption = optionMatcher.group(1); + + // Match any argument directly appended onto the longest matching option. + String matchedArg = optionMatcher.group(2); + + // Check that a known option was matched. + if ((matchedOption != null) && !"".equals(matchedOption)) + { + // Get the command line option information for the matched option. + CommandLineOption optionInfo = optionMap.get(matchedOption); + + // Check if this option is expecting arguments. + if (optionInfo.expectsArgs) + { + // The option is expecting arguments so swallow the next command line argument as an + // argument to this option. + expectingArgs = true; + optionExpectingArgs = matchedOption; + + // In the mean time set this options argument to the empty string in case no argument is ever + // supplied. + // options.put(matchedOption, ""); + } + + // Check if the option was matched on its own and is a flag in which case set that flag. + if ("".equals(matchedArg) && !optionInfo.expectsArgs) + { + options.put(matchedOption, "true"); + } + // The option was matched as a substring with its argument appended to it or is a flag that is + // condensed together with other flags. + else if (!"".equals(matchedArg)) + { + // Check if the option is a flag and therefore is allowed to be condensed together + // with other flags. + if (!optionInfo.expectsArgs) + { + // Set the first matched flag. + options.put(matchedOption, "true"); + + // Repeat the longest matching process on the remainder but ensure that the remainder + // consists only of flags as only flags may be condensed together in this fashion. + do + { + // Match the remainder against the options. + optionMatcher = pattern.matcher(matchedArg); + optionMatcher.matches(); + + matchedOption = optionMatcher.group(1); + matchedArg = optionMatcher.group(2); + + // Check that an option was matched. + if (matchedOption != null) + { + // Get the command line option information for the next matched option. + optionInfo = optionMap.get(matchedOption); + + // Ensure that the next option is a flag or raise an error if not. + if (optionInfo.expectsArgs == true) + { + parsingErrors.add("Option " + matchedOption + " cannot be combined with flags.\n"); + } + + options.put(matchedOption, "true"); + } + // The remainder could not be matched against a flag it is either an unknown flag + // or an illegal argument to a flag. + else + { + parsingErrors.add("Illegal argument to a flag in the option " + arg + "\n"); + + break; + } + } + // Continue until the remainder of the argument has all been matched with flags. + while (!"".equals(matchedArg)); + } + // The option is expecting an argument, so store the unmatched portion against it + // as its argument. + else + { + // Check the arguments format is correct against any specified format. + checkArgumentFormat(optionInfo, matchedArg); + + // Store the argument against its option (regardless of its format). + options.put(matchedOption, matchedArg); + + // The argument to this flag has already been supplied to it. Do not swallow the + // next command line argument as an argument to this flag. + expectingArgs = false; + } + } + } + else // No matching option was found. + { + // Add this to the list of parsing errors if errors on unkowns is being used. + if (errorsOnUnknowns) + { + parsingErrors.add("Option " + matchedOption + " is not a recognized option.\n"); + } + } + } + // The command line argument did not being with a '-' so it is an argument to the previous flag or it + // is a free argument. + else + { + // Check if a previous flag is expecting to swallow this next argument as its argument. + if (expectingArgs) + { + // Get the option info for the option waiting for arguments. + CommandLineOption optionInfo = optionMap.get(optionExpectingArgs); + + // Check the arguments format is correct against any specified format. + checkArgumentFormat(optionInfo, args[i]); + + // Store the argument against its option (regardless of its format). + options.put(optionExpectingArgs, args[i]); + + // Clear the expecting args flag now that the argument has been swallowed. + expectingArgs = false; + optionExpectingArgs = null; + } + // This command line option is not an argument to any option. Add it to the set of 'free' options. + else + { + // Get the option info for the free option, if there is any. + CommandLineOption optionInfo = optionMap.get(Integer.toString(free)); + + if (optionInfo != null) + { + // Check the arguments format is correct against any specified format. + checkArgumentFormat(optionInfo, args[i]); + } + + // Add to the list of free options. + options.put(Integer.toString(free), args[i]); + + // Move on to the next free argument. + free++; + } + } + } + + // Scan through all the specified options to check that all mandatory options have been set and that all flags + // that were not set are set to false in the set of properties. + for (CommandLineOption optionInfo : optionMap.values()) + { + // Check if this is a flag. + if (!optionInfo.expectsArgs) + { + // Check if the flag is not set in the properties and set it to false if so. + if (!options.containsKey(optionInfo.option)) + { + options.put(optionInfo.option, "false"); + } + } + // Check if this is a mandatory option and was not set. + else if (optionInfo.mandatory && !options.containsKey(optionInfo.option)) + { + // Create an error for the missing option. + parsingErrors.add("Option -" + optionInfo.option + " is mandatory but not was not specified.\n"); + } + } + + // Check if there were any errors. + if (!parsingErrors.isEmpty()) + { + // Throw an illegal argument exception to signify that there were parsing errors. + throw new IllegalArgumentException(); + } + + // Convert any name/value pairs in the free arguments into properties in the parsed options. + options = takeFreeArgsAsProperties(options, 1); + + parsedProperties = options; + + return options; + } + + /** + * If a command line has been parsed, calling this method sets all of its parsed options into the specified properties. + */ + public void addCommandLineToProperties(Properties properties) + { + if (parsedProperties != null) + { + for (Object propKey : parsedProperties.keySet()) + { + String name = (String) propKey; + String value = parsedProperties.getProperty(name); + + properties.setProperty(name, value); + } + } + } + + /** + * Resets this command line parser after it has been used to parse a command line. This method will only need + * to be called to use this parser a second time which is not likely seeing as a command line is usually only + * specified once. However, it is exposed as a public method for the rare case where this may be done. + * + * <p/>Cleans the internal state of this parser, removing all stored errors and information about the options in + * force. + */ + public void reset() + { + parsingErrors = new ArrayList<String>(); + parsedProperties = null; + } + + /** + * Adds the option to list of available command line options. + * + * @param option The option to add as an available command line option. + * @param comment A comment for the option. + * @param argument The text that appears after the option in the usage string. + * @param mandatory When true, indicates that this option is mandatory. + * @param formatRegexp The format that the argument must take, defined as a regular expression. + */ + protected void addOption(String option, String comment, String argument, boolean mandatory, String formatRegexp) + { + // Check if usage text has been set in which case this option is expecting arguments. + boolean expectsArgs = ((argument == null) || argument.equals("")) ? false : true; + + // Add the option to the map of command line options. + CommandLineOption opt = new CommandLineOption(option, expectsArgs, comment, argument, mandatory, formatRegexp); + optionMap.put(option, opt); + } + + /** + * Converts the free arguments into property declarations. After parsing the command line the free arguments + * are numbered from 1, such that the parsed properties contain values for the keys "1", "2", ... This method + * converts any free arguments declared using the 'name=value' syntax into properties with key 'name', value + * 'value'. + * + * <p/>For example the comand line: + * <pre> + * ... debug=true + * </pre> + * + * <p/>After parsing has properties: + * <pre>[[1, debug=true]]</pre> + * + * <p/>After applying this method the properties are: + * <pre>[[1, debug=true], [debug, true]]</pre> + * + * @param properties The parsed command line properties. + * @param from The free argument index to convert to properties from. + * + * @return The parsed command line properties, with free argument name value pairs too. + */ + private Properties takeFreeArgsAsProperties(Properties properties, int from) + { + for (int i = from; true; i++) + { + String nextFreeArg = properties.getProperty(Integer.toString(i)); + + // Terminate the loop once all free arguments have been consumed. + if (nextFreeArg == null) + { + break; + } + + // Split it on the =, strip any whitespace and set it as a system property. + String[] nameValuePair = nextFreeArg.split("="); + + if (nameValuePair.length == 2) + { + properties.setProperty(nameValuePair[0], nameValuePair[1]); + } + } + + return properties; + } + + /** + * Checks the format of an argument to an option against its specified regular expression format if one has + * been set. Any errors are added to the list of parsing errors. + * + * @param optionInfo The command line option information for the option which is havings its argument checked. + * @param matchedArg The string argument to the option. + */ + private void checkArgumentFormat(CommandLineOption optionInfo, String matchedArg) + { + // Check if this option enforces a format for its argument. + if (optionInfo.argumentFormatRegexp != null) + { + Pattern pattern = Pattern.compile(optionInfo.argumentFormatRegexp); + Matcher argumentMatcher = pattern.matcher(matchedArg); + + // Check if the argument does not meet its required format. + if (!argumentMatcher.matches()) + { + // Create an error for this badly formed argument. + parsingErrors.add("The argument to option -" + optionInfo.option + " does not meet its required format.\n"); + } + } + } + + /** + * Extracts all name=value pairs from the command line, sets them all as system properties and also returns + * a map of properties containing them. + * + * @param args The command line. + * @param commandLine The command line parser. + * @param properties The properties object to inject all parsed properties into (optional may be <tt>null</tt>). + * + * @return A set of properties containing all name=value pairs from the command line. + */ + public static Properties processCommandLine(String[] args, CommandLineParser commandLine, Properties properties) + { + // Capture the command line arguments or display errors and correct usage and then exit. + Properties options = null; + + try + { + options = commandLine.parseCommandLine(args); + + // Add all the trailing command line options (name=value pairs) to system properties. They may be picked up + // from there. + commandLine.addCommandLineToProperties(properties); + } + catch (IllegalArgumentException e) + { + System.out.println(commandLine.getErrors()); + System.out.println(commandLine.getUsage()); + System.exit(1); + } + + return options; + } + + /** + * Holds information about a command line options. This includes what its name is, whether or not it is a flag, + * whether or not it is mandatory, what its user comment is, what its argument reminder text is and what its + * regular expression format is. + * + * <p><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Hold details of a command line option. + * </table> + */ + protected static class CommandLineOption + { + /** Holds the text for the flag to match this argument with. */ + public String option = null; + + /** Holds a string describing how to use this command line argument. */ + public String argument = null; + + /** Flag that determines whether or not this command line argument can take arguments. */ + public boolean expectsArgs = false; + + /** Holds a short comment describing what this command line argument is for. */ + public String comment = null; + + /** Flag that determines whether or not this is an mandatory command line argument. */ + public boolean mandatory = false; + + /** A regular expression describing what format the argument to this option muist have. */ + public String argumentFormatRegexp = null; + + /** + * Create a command line option object that holds specific information about a command line option. + * + * @param option The text that matches the option. + * @param expectsArgs Whether or not the option expects arguments. It is a flag if this is false. + * @param comment A comment explaining how to use this option. + * @param argument A short reminder of the format of the argument to this option/ + * @param mandatory Set to true if this option is mandatory. + * @param formatRegexp The regular expression that the argument to this option must meet to be valid. + */ + public CommandLineOption(String option, boolean expectsArgs, String comment, String argument, boolean mandatory, + String formatRegexp) + { + this.option = option; + this.expectsArgs = expectsArgs; + this.comment = comment; + this.argument = argument; + this.mandatory = mandatory; + this.argumentFormatRegexp = formatRegexp; + } + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/ConcurrentLinkedMessageQueueAtomicSize.java b/qpid/java/common/src/main/java/org/apache/qpid/util/ConcurrentLinkedMessageQueueAtomicSize.java new file mode 100644 index 0000000000..633cf4fe3a --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/ConcurrentLinkedMessageQueueAtomicSize.java @@ -0,0 +1,258 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicInteger; + +public class ConcurrentLinkedMessageQueueAtomicSize<E> extends ConcurrentLinkedQueueAtomicSize<E> implements MessageQueue<E> +{ + private static final Logger _logger = LoggerFactory.getLogger(ConcurrentLinkedMessageQueueAtomicSize.class); + + protected Queue<E> _messageHead = new ConcurrentLinkedQueueAtomicSize<E>(); + + protected AtomicInteger _messageHeadSize = new AtomicInteger(0); + + @Override + public int size() + { + return super.size() + _messageHeadSize.get(); + } + + public int headSize() + { + return _messageHeadSize.get(); + } + + @Override + public E poll() + { + if (_messageHead.isEmpty()) + { + return super.poll(); + } + else + { + E e = _messageHead.poll(); + + if (_logger.isDebugEnabled()) + { + _logger.debug("Providing item(" + e + ")from message head"); + } + + if (e != null) + { + _messageHeadSize.decrementAndGet(); + } + + return e; + } + } + + @Override + public boolean remove(Object o) + { + + if (_messageHead.isEmpty()) + { + return super.remove(o); + } + else + { + if (_messageHead.remove(o)) + { + _messageHeadSize.decrementAndGet(); + + return true; + } + + return super.remove(o); + } + } + + @Override + public boolean removeAll(Collection<?> c) + { + if (_messageHead.isEmpty()) + { + return super.removeAll(c); + } + else + { + // fixme this is super.removeAll but iterator here doesn't work + // we need to be able to correctly decrement _messageHeadSize + // boolean modified = false; + // Iterator<?> e = iterator(); + // while (e.hasNext()) + // { + // if (c.contains(e.next())) + // { + // e.remove(); + // modified = true; + // _size.decrementAndGet(); + // } + // } + // return modified; + + throw new RuntimeException("Not implemented"); + } + } + + @Override + public boolean isEmpty() + { + return (_messageHead.isEmpty() && super.isEmpty()); + } + + @Override + public void clear() + { + super.clear(); + _messageHead.clear(); + } + + @Override + public boolean contains(Object o) + { + return _messageHead.contains(o) || super.contains(o); + } + + @Override + public boolean containsAll(Collection<?> o) + { + return _messageHead.containsAll(o) || super.containsAll(o); + } + + @Override + public E element() + { + if (_messageHead.isEmpty()) + { + return super.element(); + } + else + { + return _messageHead.element(); + } + } + + @Override + public E peek() + { + if (_messageHead.isEmpty()) + { + return super.peek(); + } + else + { + E o = _messageHead.peek(); + if (_logger.isDebugEnabled()) + { + _logger.debug("Peeking item (" + o + ") from message head"); + } + + return o; + } + + } + + @Override + public Iterator<E> iterator() + { + final Iterator<E> mainMessageIterator = super.iterator(); + + return new Iterator<E>() + { + final Iterator<E> _headIterator = _messageHead.iterator(); + final Iterator<E> _mainIterator = mainMessageIterator; + + Iterator<E> last; + + public boolean hasNext() + { + return _headIterator.hasNext() || _mainIterator.hasNext(); + } + + public E next() + { + if (_headIterator.hasNext()) + { + last = _headIterator; + + return _headIterator.next(); + } + else + { + last = _mainIterator; + + return _mainIterator.next(); + } + } + + public void remove() + { + last.remove(); + if(last == _mainIterator) + { + _size.decrementAndGet(); + } + else + { + _messageHeadSize.decrementAndGet(); + } + } + }; + } + + @Override + public boolean retainAll(Collection<?> c) + { + throw new RuntimeException("Not Implemented"); + } + + @Override + public Object[] toArray() + { + throw new RuntimeException("Not Implemented"); + } + + public boolean pushHead(E o) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Adding item(" + o + ") to head of queue"); + } + + if (_messageHead.offer(o)) + { + _messageHeadSize.incrementAndGet(); + + return true; + } + + return false; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/ConcurrentLinkedQueueAtomicSize.java b/qpid/java/common/src/main/java/org/apache/qpid/util/ConcurrentLinkedQueueAtomicSize.java new file mode 100644 index 0000000000..c4d7683a02 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/ConcurrentLinkedQueueAtomicSize.java @@ -0,0 +1,70 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util; + +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; + +public class ConcurrentLinkedQueueAtomicSize<E> extends ConcurrentLinkedQueue<E> +{ + AtomicInteger _size = new AtomicInteger(0); + + public int size() + { + return _size.get(); + } + + public boolean offer(E o) + { + + if (super.offer(o)) + { + _size.incrementAndGet(); + return true; + } + + return false; + } + + public E poll() + { + E e = super.poll(); + + if (e != null) + { + _size.decrementAndGet(); + } + + return e; + } + + @Override + public boolean remove(Object o) + { + if (super.remove(o)) + { + _size.decrementAndGet(); + return true; + } + + return false; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/ConcurrentLinkedQueueNoSize.java b/qpid/java/common/src/main/java/org/apache/qpid/util/ConcurrentLinkedQueueNoSize.java new file mode 100644 index 0000000000..1f168345a1 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/ConcurrentLinkedQueueNoSize.java @@ -0,0 +1,38 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.util; + +import java.util.concurrent.ConcurrentLinkedQueue; + +public class ConcurrentLinkedQueueNoSize<E> extends ConcurrentLinkedQueue<E> +{ + public int size() + { + if (isEmpty()) + { + return 0; + } + else + { + return 1; + } + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/FileUtils.java b/qpid/java/common/src/main/java/org/apache/qpid/util/FileUtils.java new file mode 100644 index 0000000000..1a57af9bf7 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/FileUtils.java @@ -0,0 +1,395 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.LinkedList; +import java.util.List; + +/** + * FileUtils provides some simple helper methods for working with files. It follows the convention of wrapping all + * checked exceptions as runtimes, so code using these methods is free of try-catch blocks but does not expect to + * recover from errors. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Read a text file as a string. + * <tr><td> Open a file or default resource as an input stream. + * </table> + */ +public class FileUtils +{ + /** + * Reads a text file as a string. + * + * @param filename The name of the file. + * + * @return The contents of the file. + */ + public static String readFileAsString(String filename) + { + BufferedInputStream is = null; + + try + { + try + { + is = new BufferedInputStream(new FileInputStream(filename)); + } + catch (FileNotFoundException e) + { + throw new RuntimeException(e); + } + + return readStreamAsString(is); + } + finally + { + if (is != null) + { + try + { + is.close(); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + } + } + + /** + * Reads a text file as a string. + * + * @param file The file. + * + * @return The contents of the file. + */ + public static String readFileAsString(File file) + { + BufferedInputStream is = null; + + try + { + is = new BufferedInputStream(new FileInputStream(file)); + } + catch (FileNotFoundException e) + { + throw new RuntimeException(e); + } + + return readStreamAsString(is); + } + + /** + * Reads the contents of a reader, one line at a time until the end of stream is encountered, and returns all + * together as a string. + * + * @param is The reader. + * + * @return The contents of the reader. + */ + private static String readStreamAsString(BufferedInputStream is) + { + try + { + byte[] data = new byte[4096]; + + StringBuffer inBuffer = new StringBuffer(); + + String line; + int read; + + while ((read = is.read(data)) != -1) + { + String s = new String(data, 0, read); + inBuffer.append(s); + } + + return inBuffer.toString(); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + /** + * 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. + * @param cl The classloader to load the default resource with. + * + * @return An input stream for the file or resource, or null if one could not be opened. + */ + public static InputStream openFileOrDefaultResource(String filename, String defaultResource, ClassLoader cl) + { + 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 + { + is = new BufferedInputStream(new FileInputStream(new File(filename))); + + // Clear the default flag because the file was succesfully opened. + useDefault = false; + } + catch (FileNotFoundException e) + { + // 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 (useDefault) + { + is = cl.getResourceAsStream(defaultResource); + } + + return is; + } + + /** + * Copies the specified source file to the specified destintaion file. If the destinationst file does not exist, + * it is created. + * + * @param src The source file name. + * @param dst The destination file name. + */ + public static void copy(File src, File dst) + { + try + { + copyCheckedEx(src, dst); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + /** + * Copies the specified source file to the specified destination file. If the destination file does not exist, + * it is created. + * + * @param src The source file name. + * @param dst The destination file name. + * @throws IOException + */ + public static void copyCheckedEx(File src, File dst) throws IOException + { + InputStream in = new FileInputStream(src); + try + { + if (!dst.exists()) + { + dst.createNewFile(); + } + + OutputStream out = new FileOutputStream(dst); + + try + { + // Transfer bytes from in to out + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) + { + out.write(buf, 0, len); + } + } + finally + { + out.close(); + } + } + finally + { + in.close(); + } + } + + /* + * Deletes a given file + */ + public static boolean deleteFile(String filePath) + { + return delete(new File(filePath), false); + } + + /* + * Deletes a given empty directory + */ + public static boolean deleteDirectory(String directoryPath) + { + File directory = new File(directoryPath); + + if (directory.isDirectory()) + { + if (directory.listFiles().length == 0) + { + return delete(directory, true); + } + } + + return false; + } + + /** + * Delete a given file/directory, + * A directory will always require the recursive flag to be set. + * if a directory is specified and recursive set then delete the whole tree + * + * @param file the File object to start at + * @param recursive boolean to recurse if a directory is specified. + * + * @return <code>true</code> if and only if the file or directory is + * successfully deleted; <code>false</code> otherwise + */ + public static boolean delete(File file, boolean recursive) + { + boolean success = true; + + if (file.isDirectory()) + { + if (recursive) + { + File[] files = file.listFiles(); + + // This can occur if the file is deleted outside the JVM + if (files == null) + { + return false; + } + + for (int i = 0; i < files.length; i++) + { + success = delete(files[i], true) && success; + } + + return success && file.delete(); + } + + return false; + } + + return file.delete(); + } + + public static class UnableToCopyException extends Exception + { + UnableToCopyException(String msg) + { + super(msg); + } + } + + public static void copyRecursive(File source, File dst) throws FileNotFoundException, UnableToCopyException + { + + if (!source.exists()) + { + throw new FileNotFoundException("Unable to copy '" + source.toString() + "' as it does not exist."); + } + + if (dst.exists() && !dst.isDirectory()) + { + throw new IllegalArgumentException("Unable to copy '" + source.toString() + "' to '" + dst + "' a file with same name exists."); + } + + if (source.isFile()) + { + copy(source, dst); + } + + //else we have a source directory + if (!dst.isDirectory() && !dst.mkdirs()) + { + throw new UnableToCopyException("Unable to create destination directory"); + } + + for (File file : source.listFiles()) + { + if (file.isFile()) + { + copy(file, new File(dst.toString() + File.separator + file.getName())); + } + else + { + copyRecursive(file, new File(dst + File.separator + file.getName())); + } + } + + } + + /** + * Checks the specified file for instances of the search string. + * + * @param file the file to search + * @param search the search String + * + * @throws java.io.IOException + * @return the list of matching entries + */ + public static List<String> searchFile(File file, String search) + throws IOException + { + + List<String> results = new LinkedList<String>(); + + BufferedReader reader = new BufferedReader(new FileReader(file)); + try + { + while (reader.ready()) + { + String line = reader.readLine(); + if (line.contains(search)) + { + results.add(line); + } + } + } + finally + { + reader.close(); + } + + return results; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/MessageQueue.java b/qpid/java/common/src/main/java/org/apache/qpid/util/MessageQueue.java new file mode 100644 index 0000000000..b5efaa61b6 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/MessageQueue.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util; + +import java.util.Queue; + +/** + * Defines a queue that has a push operation to add an element to the head of the queue. + * + * @todo Seems like this may be pointless, the implementation uses this method to increment the message count + * then calls offer. Why not simply override offer and drop this interface? + */ +public interface MessageQueue<E> extends Queue<E> +{ + /** + * Inserts the specified element into this queue, if possible. When using queues that may impose insertion + * restrictions (for example capacity bounds), method offer is generally preferable to method Collection.add(E), + * which can fail to insert an element only by throwing an exception. + * + * @param o The element to insert. + * + * @return <tt>true</tt> if it was possible to add the element to this queue, else <tt>false</tt> + */ + boolean pushHead(E o); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/NameUUIDGen.java b/qpid/java/common/src/main/java/org/apache/qpid/util/NameUUIDGen.java new file mode 100644 index 0000000000..e764c8536b --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/NameUUIDGen.java @@ -0,0 +1,59 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util; + +import java.nio.ByteBuffer; +import java.util.UUID; + + +/** + * NameUUIDGen + * + */ + +public final class NameUUIDGen implements UUIDGen +{ + + private static final int WIDTH = 8; + + final private byte[] seed; + final private ByteBuffer seedBuf; + private long counter; + + public NameUUIDGen() + { + String namespace = UUID.randomUUID().toString(); + this.seed = new byte[namespace.length() + WIDTH]; + for (int i = WIDTH; i < seed.length; i++) + { + seed[i] = (byte) namespace.charAt(i - WIDTH); + } + this.seedBuf = ByteBuffer.wrap(seed); + this.counter = 0; + } + + public UUID generate() + { + seedBuf.putLong(0, counter++); + return UUID.nameUUIDFromBytes(seed); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/NetMatcher.java b/qpid/java/common/src/main/java/org/apache/qpid/util/NetMatcher.java new file mode 100644 index 0000000000..4c653e6ca0 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/NetMatcher.java @@ -0,0 +1,264 @@ +/*********************************************************************** + * Copyright (c) 2000-2006 The Apache Software Foundation. * + * All rights reserved. * + * ------------------------------------------------------------------- * + * 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.qpid.util; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +public class NetMatcher +{ + private ArrayList networks; + + public void initInetNetworks(final Collection nets) + { + networks = new ArrayList(); + for (Iterator iter = nets.iterator(); iter.hasNext(); ) try + { + InetNetwork net = InetNetwork.getFromString((String) iter.next()); + if (!networks.contains(net)) networks.add(net); + } + catch (java.net.UnknownHostException uhe) + { + log("Cannot resolve address: " + uhe.getMessage()); + } + networks.trimToSize(); + } + + public void initInetNetworks(final String[] nets) + { + networks = new ArrayList(); + for (int i = 0; i < nets.length; i++) try + { + InetNetwork net = InetNetwork.getFromString(nets[i]); + if (!networks.contains(net)) networks.add(net); + } + catch (java.net.UnknownHostException uhe) + { + log("Cannot resolve address: " + uhe.getMessage()); + } + networks.trimToSize(); + } + + public boolean matchInetNetwork(final String hostIP) + { + InetAddress ip = null; + + try + { + ip = InetAddress.getByName(hostIP); + } + catch (java.net.UnknownHostException uhe) + { + log("Cannot resolve address for " + hostIP + ": " + uhe.getMessage()); + } + + boolean sameNet = false; + + if (ip != null) for (Iterator iter = networks.iterator(); (!sameNet) && iter.hasNext(); ) + { + InetNetwork network = (InetNetwork) iter.next(); + sameNet = network.contains(ip); + } + return sameNet; + } + + public boolean matchInetNetwork(final InetAddress ip) + { + boolean sameNet = false; + + for (Iterator iter = networks.iterator(); (!sameNet) && iter.hasNext(); ) + { + InetNetwork network = (InetNetwork) iter.next(); + sameNet = network.contains(ip); + } + return sameNet; + } + + public NetMatcher() + { + } + + public NetMatcher(final String[] nets) + { + initInetNetworks(nets); + } + + public NetMatcher(final Collection nets) + { + initInetNetworks(nets); + } + + public String toString() { + return networks.toString(); + } + + protected void log(String s) { } +} + +class InetNetwork +{ + /* + * Implements network masking, and is compatible with RFC 1518 and + * RFC 1519, which describe CIDR: Classless Inter-Domain Routing. + */ + + private InetAddress network; + private InetAddress netmask; + + public InetNetwork(InetAddress ip, InetAddress netmask) + { + network = maskIP(ip, netmask); + this.netmask = netmask; + } + + public boolean contains(final String name) throws java.net.UnknownHostException + { + return network.equals(maskIP(InetAddress.getByName(name), netmask)); + } + + public boolean contains(final InetAddress ip) + { + return network.equals(maskIP(ip, netmask)); + } + + public String toString() + { + return network.getHostAddress() + "/" + netmask.getHostAddress(); + } + + public int hashCode() + { + return maskIP(network, netmask).hashCode(); + } + + public boolean equals(Object obj) + { + return (obj != null) && (obj instanceof InetNetwork) && + ((((InetNetwork)obj).network.equals(network)) && (((InetNetwork)obj).netmask.equals(netmask))); + } + + public static InetNetwork getFromString(String netspec) throws java.net.UnknownHostException + { + if (netspec.endsWith("*")) netspec = normalizeFromAsterisk(netspec); + else + { + int iSlash = netspec.indexOf('/'); + if (iSlash == -1) netspec += "/255.255.255.255"; + else if (netspec.indexOf('.', iSlash) == -1) netspec = normalizeFromCIDR(netspec); + } + + return new InetNetwork(InetAddress.getByName(netspec.substring(0, netspec.indexOf('/'))), + InetAddress.getByName(netspec.substring(netspec.indexOf('/') + 1))); + } + + public static InetAddress maskIP(final byte[] ip, final byte[] mask) + { + try + { + return getByAddress(new byte[] + { + (byte) (mask[0] & ip[0]), + (byte) (mask[1] & ip[1]), + (byte) (mask[2] & ip[2]), + (byte) (mask[3] & ip[3]) + }); + } + catch(Exception _) {} + { + return null; + } + } + + public static InetAddress maskIP(final InetAddress ip, final InetAddress mask) + { + return maskIP(ip.getAddress(), mask.getAddress()); + } + + /* + * This converts from an uncommon "wildcard" CIDR format + * to "address + mask" format: + * + * * => 000.000.000.0/000.000.000.0 + * xxx.* => xxx.000.000.0/255.000.000.0 + * xxx.xxx.* => xxx.xxx.000.0/255.255.000.0 + * xxx.xxx.xxx.* => xxx.xxx.xxx.0/255.255.255.0 + */ + static private String normalizeFromAsterisk(final String netspec) + { + String[] masks = { "0.0.0.0/0.0.0.0", "0.0.0/255.0.0.0", "0.0/255.255.0.0", "0/255.255.255.0" }; + char[] srcb = netspec.toCharArray(); + int octets = 0; + for (int i = 1; i < netspec.length(); i++) { + if (srcb[i] == '.') octets++; + } + return (octets == 0) ? masks[0] : netspec.substring(0, netspec.length() -1 ).concat(masks[octets]); + } + + /* + * RFC 1518, 1519 - Classless Inter-Domain Routing (CIDR) + * This converts from "prefix + prefix-length" format to + * "address + mask" format, e.g. from xxx.xxx.xxx.xxx/yy + * to xxx.xxx.xxx.xxx/yyy.yyy.yyy.yyy. + */ + static private String normalizeFromCIDR(final String netspec) + { + final int bits = 32 - Integer.parseInt(netspec.substring(netspec.indexOf('/')+1)); + final int mask = (bits == 32) ? 0 : 0xFFFFFFFF - ((1 << bits)-1); + + return netspec.substring(0, netspec.indexOf('/') + 1) + + Integer.toString(mask >> 24 & 0xFF, 10) + "." + + Integer.toString(mask >> 16 & 0xFF, 10) + "." + + Integer.toString(mask >> 8 & 0xFF, 10) + "." + + Integer.toString(mask >> 0 & 0xFF, 10); + } + + private static java.lang.reflect.Method getByAddress = null; + + static { + try { + Class inetAddressClass = Class.forName("java.net.InetAddress"); + Class[] parameterTypes = { byte[].class }; + getByAddress = inetAddressClass.getMethod("getByAddress", parameterTypes); + } catch (Exception e) { + getByAddress = null; + } + } + + private static InetAddress getByAddress(byte[] ip) throws java.net.UnknownHostException + { + InetAddress addr = null; + if (getByAddress != null) try { + addr = (InetAddress) getByAddress.invoke(null, new Object[] { ip }); + } catch (IllegalAccessException e) { + } catch (java.lang.reflect.InvocationTargetException e) { + } + + if (addr == null) { + addr = InetAddress.getByName + ( + Integer.toString(ip[0] & 0xFF, 10) + "." + + Integer.toString(ip[1] & 0xFF, 10) + "." + + Integer.toString(ip[2] & 0xFF, 10) + "." + + Integer.toString(ip[3] & 0xFF, 10) + ); + } + return addr; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/PrettyPrintingUtils.java b/qpid/java/common/src/main/java/org/apache/qpid/util/PrettyPrintingUtils.java new file mode 100644 index 0000000000..93266f2486 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/PrettyPrintingUtils.java @@ -0,0 +1,75 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util; + +/** + * Contains pretty printing convenienve methods for producing formatted logging output, mostly for debugging purposes. + * + * <p><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * </table> + * + * @todo Drop this. There are already array pretty printing methods it java.utils.Arrays. + */ +public class PrettyPrintingUtils +{ + /** + * Pretty prints an array of ints as a string. + * + * @param array The array to pretty print. + * + * @return The pretty printed string. + */ + public static String printArray(int[] array) + { + StringBuilder result = new StringBuilder("["); + for (int i = 0; i < array.length; i++) + { + result.append(array[i]) + .append((i < (array.length - 1)) ? ", " : ""); + } + + result.append(']'); + + return result.toString(); + } + + /** + * Pretty prints an array of strings as a string. + * + * @param array The array to pretty print. + * + * @return The pretty printed string. + */ + public static String printArray(String[] array) + { + String result = "["; + for (int i = 0; i < array.length; i++) + { + result += array[i]; + result += (i < (array.length - 1)) ? ", " : ""; + } + + result += "]"; + + return result; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/RandomUUIDGen.java b/qpid/java/common/src/main/java/org/apache/qpid/util/RandomUUIDGen.java new file mode 100644 index 0000000000..60b402a105 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/RandomUUIDGen.java @@ -0,0 +1,39 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util; + +import java.util.UUID; + + +/** + * RandomUUIDGen + * + */ + +public final class RandomUUIDGen implements UUIDGen +{ + + public UUID generate() + { + return UUID.randomUUID(); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/Serial.java b/qpid/java/common/src/main/java/org/apache/qpid/util/Serial.java new file mode 100644 index 0000000000..8ad9d00f54 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/Serial.java @@ -0,0 +1,108 @@ +package org.apache.qpid.util; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.Comparator; + +import org.apache.qpid.SerialException; + +/** + * This class provides basic serial number comparisons as defined in + * RFC 1982. + */ + +public class Serial +{ + + public static final Comparator<Integer> COMPARATOR = new Comparator<Integer>() + { + public int compare(Integer s1, Integer s2) + { + return Serial.compare(s1, s2); + } + }; + + /** + * Compares two numbers using serial arithmetic. + * + * @param s1 the first serial number + * @param s2 the second serial number + * + * @return a negative integer, zero, or a positive integer as the + * first argument is less than, equal to, or greater than the + * second + */ + public static final int compare(int s1, int s2) + { + return s1 - s2; + } + + public static final boolean lt(int s1, int s2) + { + return compare(s1, s2) < 0; + } + + public static final boolean le(int s1, int s2) + { + return compare(s1, s2) <= 0; + } + + public static final boolean gt(int s1, int s2) + { + return compare(s1, s2) > 0; + } + + public static final boolean ge(int s1, int s2) + { + return compare(s1, s2) >= 0; + } + + public static final boolean eq(int s1, int s2) + { + return s1 == s2; + } + + public static final int min(int s1, int s2) + { + if (lt(s1, s2)) + { + return s1; + } + else + { + return s2; + } + } + + public static final int max(int s1, int s2) + { + if (gt(s1, s2)) + { + return s1; + } + else + { + return s2; + } + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/Strings.java b/qpid/java/common/src/main/java/org/apache/qpid/util/Strings.java new file mode 100644 index 0000000000..a6a8b8beb4 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/Strings.java @@ -0,0 +1,260 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util; + +import java.io.UnsupportedEncodingException; + +import java.util.Arrays; +import java.util.Map; +import java.util.Properties; +import java.util.Stack; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +/** + * Strings + * + */ + +public final class Strings +{ + + private static final byte[] EMPTY = new byte[0]; + + private static final ThreadLocal<char[]> charbuf = new ThreadLocal<char[]>() + { + public char[] initialValue() + { + return new char[4096]; + } + }; + + public static final byte[] toUTF8(String str) + { + if (str == null) + { + return EMPTY; + } + else + { + final int size = str.length(); + char[] chars = charbuf.get(); + if (size > chars.length) + { + chars = new char[Math.max(size, 2*chars.length)]; + charbuf.set(chars); + } + + str.getChars(0, size, chars, 0); + final byte[] bytes = new byte[size]; + for (int i = 0; i < size; i++) + { + if (chars[i] > 127) + { + try + { + return str.getBytes("UTF-8"); + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + } + + bytes[i] = (byte) chars[i]; + } + return bytes; + } + } + + public static final String fromUTF8(byte[] bytes) + { + try + { + return new String(bytes, "UTF-8"); + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException(e); + } + } + + private static final Pattern VAR = Pattern.compile("(?:\\$\\{([^\\}]*)\\})|(?:\\$(\\$))"); + + public static interface Resolver + { + String resolve(String variable); + } + + public static class MapResolver implements Resolver + { + + private final Map<String,String> map; + + public MapResolver(Map<String,String> map) + { + this.map = map; + } + + public String resolve(String variable) + { + return map.get(variable); + } + } + + public static class PropertiesResolver implements Resolver + { + + private final Properties properties; + + public PropertiesResolver(Properties properties) + { + this.properties = properties; + } + + public String resolve(String variable) + { + return properties.getProperty(variable); + } + } + + public static class ChainedResolver implements Resolver + { + private final Resolver primary; + private final Resolver secondary; + + public ChainedResolver(Resolver primary, Resolver secondary) + { + this.primary = primary; + this.secondary = secondary; + } + + public String resolve(String variable) + { + String result = primary.resolve(variable); + if (result == null) + { + result = secondary.resolve(variable); + } + return result; + } + } + + public static final Resolver SYSTEM_RESOLVER = new Resolver() + { + public String resolve(String variable) + { + String result = System.getProperty(variable); + if (result == null) + { + result = System.getenv(variable); + } + return result; + } + }; + + public static final String expand(String input) + { + return expand(input, SYSTEM_RESOLVER); + } + + public static final String expand(String input, Resolver resolver) + { + return expand(input, resolver, new Stack<String>()); + } + + private static final String expand(String input, Resolver resolver, Stack<String> stack) + { + Matcher m = VAR.matcher(input); + StringBuffer result = new StringBuffer(); + while (m.find()) + { + String var = m.group(1); + if (var == null) + { + String esc = m.group(2); + if ("$".equals(esc)) + { + m.appendReplacement(result, Matcher.quoteReplacement("$")); + } + else + { + throw new IllegalArgumentException(esc); + } + } + else + { + m.appendReplacement(result, Matcher.quoteReplacement(resolve(var, resolver, stack))); + } + } + m.appendTail(result); + return result.toString(); + } + + private static final String resolve(String var, Resolver resolver, Stack<String> stack) + { + if (stack.contains(var)) + { + throw new IllegalArgumentException + (String.format("recursively defined variable: %s stack=%s", var, + stack)); + } + + String result = resolver.resolve(var); + if (result == null) + { + throw new IllegalArgumentException("no such variable: " + var); + } + + stack.push(var); + try + { + return expand(result, resolver, stack); + } + finally + { + stack.pop(); + } + } + + public static final String join(String sep, Iterable items) + { + StringBuilder result = new StringBuilder(); + + for (Object o : items) + { + if (result.length() > 0) + { + result.append(sep); + } + result.append(o.toString()); + } + + return result.toString(); + } + + public static final String join(String sep, Object[] items) + { + return join(sep, Arrays.asList(items)); + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/UUIDGen.java b/qpid/java/common/src/main/java/org/apache/qpid/util/UUIDGen.java new file mode 100644 index 0000000000..3cfe5afdac --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/UUIDGen.java @@ -0,0 +1,36 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util; + + +import java.util.UUID; + +/** + * UUIDGen + * + */ + +public interface UUIDGen +{ + + public UUID generate(); + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/UUIDs.java b/qpid/java/common/src/main/java/org/apache/qpid/util/UUIDs.java new file mode 100644 index 0000000000..4bf6b7f0a2 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/UUIDs.java @@ -0,0 +1,59 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util; + + +/** + * UUIDs + * + */ + +public final class UUIDs +{ + + public static final UUIDGen newGenerator() + { + return newGenerator(System.getProperty("qpid.uuid.generator", + NameUUIDGen.class.getName())); + } + + public static UUIDGen newGenerator(String name) + { + try + { + Class cls = Class.forName(name); + return (UUIDGen) cls.newInstance(); + } + catch (InstantiationException e) + { + throw new RuntimeException(e); + } + catch (IllegalAccessException e) + { + throw new RuntimeException(e); + } + catch (ClassNotFoundException e) + { + throw new RuntimeException(e); + } + } + +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/AlreadyUnblockedException.java b/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/AlreadyUnblockedException.java new file mode 100644 index 0000000000..e0c0337898 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/AlreadyUnblockedException.java @@ -0,0 +1,34 @@ +package org.apache.qpid.util.concurrent; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + + +/** + * Used to signal that a data element and its producer cannot be requeued or sent an error message when using a + * {@link BatchSynchQueue} because the producer has already been unblocked by an unblocking take on the queue. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Signal that an unblocking take has already occurred. + * </table> + */ +public class AlreadyUnblockedException extends RuntimeException +{ } diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/BatchSynchQueue.java b/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/BatchSynchQueue.java new file mode 100644 index 0000000000..63d8f77edb --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/BatchSynchQueue.java @@ -0,0 +1,122 @@ +package org.apache.qpid.util.concurrent; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.Collection; +import java.util.concurrent.BlockingQueue; + +/** + * BatchSynchQueue is an abstraction of the classic producer/consumer buffer pattern for thread interaction. In this + * pattern threads can deposit data onto a buffer whilst other threads take data from the buffer and perform usefull + * work with it. A BatchSynchQueue adds to this the possibility that producers can be blocked until their data is + * consumed or until a consumer chooses to release the producer some time after consuming the data from the queue. + * + * <p>There are a number of possible advantages to using this technique when compared with having the producers + * processing their own data: + * + * <ul> + * <li>Data may be deposited asynchronously in the buffer allowing the producers to continue running.</li> + * <li>Data may be deposited synchronously in the buffer so that producers wait until their data has been processed + * before being allowed to continue.</li> + * <li>Variable rates of production/consumption can be smoothed over by the buffer as it provides space in memory to + * hold data between production and consumption.</li> + * <li>Consumers may be able to batch data as they consume it leading to more efficient consumption over + * individual data item consumption where latency associated with the consume operation can be ammortized. + * For example, it may be possibly to ammortize the cost of a disk seek over many producers.</li> + * <li>Data from seperate threads can be combined together in the buffer, providing a convenient way of spreading work + * amongst many workers and gathering the results together again.</li> + * <li>Different types of queue can be used to hold the buffer, resulting in different processing orders. For example, + * lifo, fifo, priority heap, etc.</li> + * </ul> + * + * <p/>The asynchronous type of producer/consumer buffers is already well supported by the java.util.concurrent package + * (in Java 5) and there is also a synchronous queue implementation available there too. This interface extends the + * blocking queue with some more methods for controlling a synchronous blocking queue. In particular it adds additional + * take methods that can be used to take data from a queue without releasing producers, so that consumers have an + * opportunity to confirm correct processing of the data before producers are released. It also adds a put method with + * exceptions so that consumers can signal exception cases back to producers where there are errors in the data. + * + * <p/>This type of queue is usefull in situations where consumers can obtain an efficiency gain by batching data + * from many threads but where synchronous handling of that data is neccessary because producers need to know that + * their data has been processed before they continue. For example, sending a bundle of messages together, or writing + * many records to disk at once, may result in improved performance but the originators of the messages or disk records + * need confirmation that their data has really been sent or saved to disk. + * + * <p/>The consumer can put an element back onto the queue or send an error message to the elements producer using the + * {@link SynchRecord} interface. + * + * <p/>The {@link #take()}, {@link #drainTo(java.util.Collection<? super E>)} and + * {@link #drainTo(java.util.Collection<? super E>, int)} methods from {@link BlockingQueue} should behave as if they + * have been called with unblock set to false. That is they take elements from the queue but leave the producers + * blocked. These methods do not return collections of {@link SynchRecord}s so they do not supply an interface through + * which errors or re-queuings can be applied. If these methods are used then the consumer must succesfully process + * all the records it takes. + * + * <p/>The {@link #put} method should silently swallow any exceptions that consumers attempt to return to the caller. + * In order to handle exceptions the {@link #tryPut} method must be used. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Handle synchronous puts, with possible exceptions. + * <tr><td> Allow consumers to take many records from a queue in a batch. + * <tr><td> Allow consumers to decide when to unblock synchronous producers. + * </table> + */ +public interface BatchSynchQueue<E> extends BlockingQueue<E> +{ + /** + * Tries a synchronous put into the queue. If a consumer encounters an exception condition whilst processing the + * data that is put, then this is returned to the caller wrapped inside a {@link SynchException}. + * + * @param e The data element to put into the queue. + * + * @throws InterruptedException If the thread is interrupted whilst waiting to write to the queue or whilst waiting + * on its entry in the queue being consumed. + * @throws SynchException If a consumer encounters an error whilst processing the data element. + */ + public void tryPut(E e) throws InterruptedException, SynchException; + + /** + * Takes all available data items from the queue or blocks until some become available. The returned items + * are wrapped in a {@link SynchRecord} which provides an interface to requeue them or send errors to their + * producers, where the producers are still blocked. + * + * @param c The collection to drain the data items into. + * @param unblock If set to <tt>true</tt> the producers for the taken items will be immediately unblocked. + * + * @return A count of the number of elements that were drained from the queue. + */ + public SynchRef drainTo(Collection<SynchRecord<E>> c, boolean unblock); + + /** + * Takes up to maxElements available data items from the queue or blocks until some become available. The returned + * items are wrapped in a {@link SynchRecord} which provides an interface to requeue them or send errors to their + * producers, where the producers are still blocked. + * + * @param c The collection to drain the data items into. + * @param maxElements The maximum number of elements to drain. + * @param unblock If set to <tt>true</tt> the producers for the taken items will be immediately unblocked. + * + * @return A count of the number of elements that were drained from the queue. + */ + public SynchRef drainTo(Collection<SynchRecord<E>> c, int maxElements, boolean unblock); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/BatchSynchQueueBase.java b/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/BatchSynchQueueBase.java new file mode 100644 index 0000000000..4564b1d686 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/BatchSynchQueueBase.java @@ -0,0 +1,834 @@ +package org.apache.qpid.util.concurrent; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Synchronous/Asynchronous puts. Asynchronous is easiest, just wait till can write to queue and deposit data. + * Synchronous is harder. Deposit data, but then must wait until deposited element/elements are taken before being + * allowed to unblock and continue. Consumer needs some options here too. Can just get the data from the buffer and + * allow any producers unblocked as a result to continue, or can get data but continue blocking while the data is + * processed before sending a message to do the unblocking. Synch/Asynch mode to be controlled by a switch. + * Unblocking/not unblocking during consumer processing to be controlled by the consumers calls. + * + * <p/>Implementing sub-classes only need to supply an implementation of a queue to produce a valid concrete + * implementation of this. This queue is only accessed through the methods {@link #insert}, {@link #extract}, + * {@link #getBufferCapacity()}, {@link #peekAtBufferHead()}. An implementation can override these methods to implement + * the buffer other than by a queue, for example, by using an array. + * + * <p/>Normal queue methods to work asynchronously. + * <p/>Put, take and drain methods from the BlockingQueue interface work synchronously but unblock producers immediately + * when their data is taken. + * <p/>The additional put, take and drain methods from the BatchSynchQueue interface work synchronously and provide the + * option to keep producers blocked until the consumer decides to release them. + * + * <p/>Removed take method that keeps producers blocked as it is pointless. Essentially it reduces this class to + * synchronous processing of individual data items, which negates the point of the hand-off design. The efficiency + * gain of the hand off design comes in being able to batch consume requests, ammortizing latency (such as caused by io) + * accross many producers. The only advantage of the single blocking take method is that it did take advantage of the + * queue ordering, which ma be usefull, for example to apply a priority ordering amongst producers. This is also an + * advantage over the java.util.concurrent.SynchronousQueue which doesn't have a backing queue which can be used to + * apply orderings. If a single item take is really needed can just use the drainTo method with a maximum of one item. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * </table> + */ +public abstract class BatchSynchQueueBase<E> extends AbstractQueue<E> implements BatchSynchQueue<E> +{ + /** Used for logging. */ + private static final Logger log = LoggerFactory.getLogger(BatchSynchQueueBase.class); + + /** Holds a reference to the queue implementation that holds the buffer. */ + Queue<SynchRecordImpl<E>> buffer; + + /** Holds the number of items in the queue */ + private int count; + + /** Main lock guarding all access */ + private ReentrantLock lock; + + /** Condition for waiting takes */ + private Condition notEmpty; + + /** Condition for waiting puts */ + private Condition notFull; + + /** + * Creates a batch synch queue without fair thread scheduling. + */ + public BatchSynchQueueBase() + { + this(false); + } + + /** + * Ensures that the underlying buffer implementation is created. + * + * @param fair <tt>true</tt> if fairness is to be applied to threads waiting to access the buffer. + */ + public BatchSynchQueueBase(boolean fair) + { + buffer = this.createQueue(); + + // Create the buffer lock with the fairness flag set accordingly. + lock = new ReentrantLock(fair); + + // Create the non-empty and non-full condition monitors on the buffer lock. + notEmpty = lock.newCondition(); + notFull = lock.newCondition(); + } + + /** + * Returns an iterator over the elements contained in this collection. + * + * @return An iterator over the elements contained in this collection. + */ + public Iterator<E> iterator() + { + throw new RuntimeException("Not implemented."); + } + + /** + * Returns the number of elements in this collection. If the collection contains more than + * <tt>Integer.MAX_VALUE</tt> elements, returns <tt>Integer.MAX_VALUE</tt>. + * + * @return The number of elements in this collection. + */ + public int size() + { + final ReentrantLock lock = this.lock; + lock.lock(); + + try + { + return count; + } + finally + { + lock.unlock(); + } + } + + /** + * Inserts the specified element into this queue, if possible. When using queues that may impose insertion + * restrictions (for example capacity bounds), method <tt>offer</tt> is generally preferable to method + * {@link java.util.Collection#add}, which can fail to insert an element only by throwing an exception. + * + * @param e The element to insert. + * + * @return <tt>true</tt> if it was possible to add the element to this queue, else <tt>false</tt> + */ + public boolean offer(E e) + { + if (e == null) + { + throw new NullPointerException(); + } + + final ReentrantLock lock = this.lock; + lock.lock(); + + try + { + return insert(e, false); + } + finally + { + lock.unlock(); + } + } + + /** + * Inserts the specified element into this queue, waiting if necessary up to the specified wait time for space to + * become available. + * + * @param e The element to add. + * @param timeout How long to wait before giving up, in units of <tt>unit</tt> + * @param unit A <tt>TimeUnit</tt> determining how to interpret the <tt>timeout</tt> parameter. + * + * @return <tt>true</tt> if successful, or <tt>false</tt> if the specified waiting time elapses before space is + * available. + * + * @throws InterruptedException If interrupted while waiting. + * @throws NullPointerException If the specified element is <tt>null</tt>. + */ + public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException + { + if (e == null) + { + throw new NullPointerException(); + } + + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); + + long nanos = unit.toNanos(timeout); + + try + { + do + { + if (insert(e, false)) + { + return true; + } + + try + { + nanos = notFull.awaitNanos(nanos); + } + catch (InterruptedException ie) + { + notFull.signal(); // propagate to non-interrupted thread + throw ie; + } + } + while (nanos > 0); + + return false; + } + finally + { + lock.unlock(); + } + } + + /** + * Retrieves and removes the head of this queue, or <tt>null</tt> if this queue is empty. + * + * @return The head of this queue, or <tt>null</tt> if this queue is empty. + */ + public E poll() + { + final ReentrantLock lock = this.lock; + + lock.lock(); + try + { + if (count == 0) + { + return null; + } + + E x = extract(true, true).getElement(); + + return x; + } + finally + { + lock.unlock(); + } + } + + /** + * Retrieves and removes the head of this queue, waiting if necessary up to the specified wait time if no elements + * are present on this queue. + * + * @param timeout How long to wait before giving up, in units of <tt>unit</tt>. + * @param unit A <tt>TimeUnit</tt> determining how to interpret the <tt>timeout</tt> parameter. + * + * @return The head of this queue, or <tt>null</tt> if the specified waiting time elapses before an element is present. + * + * @throws InterruptedException If interrupted while waiting. + */ + public E poll(long timeout, TimeUnit unit) throws InterruptedException + { + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); + try + { + long nanos = unit.toNanos(timeout); + + do + { + if (count != 0) + { + E x = extract(true, true).getElement(); + + return x; + } + + try + { + nanos = notEmpty.awaitNanos(nanos); + } + catch (InterruptedException ie) + { + notEmpty.signal(); // propagate to non-interrupted thread + throw ie; + } + } + while (nanos > 0); + + return null; + } + finally + { + lock.unlock(); + } + } + + /** + * Retrieves, but does not remove, the head of this queue, returning <tt>null</tt> if this queue is empty. + * + * @return The head of this queue, or <tt>null</tt> if this queue is empty. + */ + public E peek() + { + final ReentrantLock lock = this.lock; + lock.lock(); + + try + { + return peekAtBufferHead(); + } + finally + { + lock.unlock(); + } + } + + /** + * Returns the number of elements that this queue can ideally (in the absence of memory or resource constraints) + * accept without blocking, or <tt>Integer.MAX_VALUE</tt> if there is no intrinsic limit. + * + * <p>Note that you <em>cannot</em> always tell if an attempt to <tt>add</tt> an element will succeed by + * inspecting <tt>remainingCapacity</tt> because it may be the case that another thread is about to <tt>put</tt> + * or <tt>take</tt> an element. + * + * @return The remaining capacity. + */ + public int remainingCapacity() + { + final ReentrantLock lock = this.lock; + lock.lock(); + + try + { + return getBufferCapacity() - count; + } + finally + { + lock.unlock(); + } + } + + /** + * Adds the specified element to this queue, waiting if necessary for space to become available. + * + * <p/>This method delegated to {@link #tryPut} which can raise {@link SynchException}s. If any are raised + * this method silently ignores them. Use the {@link #tryPut} method directly if you want to catch these + * exceptions. + * + * @param e The element to add. + * + * @throws InterruptedException If interrupted while waiting. + */ + public void put(E e) throws InterruptedException + { + try + { + tryPut(e); + } + catch (SynchException ex) + { + // This exception is deliberately ignored. See the method comment for information about this. + } + } + + /** + * Tries a synchronous put into the queue. If a consumer encounters an exception condition whilst processing the + * data that is put, then this is returned to the caller wrapped inside a {@link SynchException}. + * + * @param e The data element to put into the queue. Cannot be null. + * + * @throws InterruptedException If the thread is interrupted whilst waiting to write to the queue or whilst waiting + * on its entry in the queue being consumed. + * @throws SynchException If a consumer encounters an error whilst processing the data element. + */ + public void tryPut(E e) throws InterruptedException, SynchException + { + if (e == null) + { + throw new NullPointerException(); + } + + // final Queue<E> items = this.buffer; + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); + + try + { + while (count == getBufferCapacity()) + { + // Release the lock and wait until the queue is not full. + notFull.await(); + } + } + catch (InterruptedException ie) + { + notFull.signal(); // propagate to non-interrupted thread + throw ie; + } + + // There is room in the queue so insert must succeed. Insert into the queu, release the lock and block + // the producer until its data is taken. + insert(e, true); + } + + /** + * Retrieves and removes the head of this queue, waiting if no elements are present on this queue. + * Any producer that has its data element taken by this call will be immediately unblocked. To keep the + * producer blocked whilst taking just a single item, use the + * {@link #drainTo(java.util.Collection<org.apache.qpid.util.concurrent.SynchRecord<E>>, int, boolean)} + * method. There is no take method to do that because there is not usually any advantage in a synchronous hand + * off design that consumes data one item at a time. It is normal to consume data in chunks to ammortize consumption + * latencies accross many producers where possible. + * + * @return The head of this queue. + * + * @throws InterruptedException if interrupted while waiting. + */ + public E take() throws InterruptedException + { + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); + + try + { + try + { + while (count == 0) + { + // Release the lock and wait until the queue becomes non-empty. + notEmpty.await(); + } + } + catch (InterruptedException ie) + { + notEmpty.signal(); // propagate to non-interrupted thread + throw ie; + } + + // There is data in the queue so extraction must succeed. Notify any waiting threads that the queue is + // not full, and unblock the producer that owns the data item that is taken. + E x = extract(true, true).getElement(); + + return x; + } + finally + { + lock.unlock(); + } + } + + /** + * Removes all available elements from this queue and adds them into the given collection. This operation may be + * more efficient than repeatedly polling this queue. A failure encountered while attempting to <tt>add</tt> elements + * to collection <tt>c</tt> may result in elements being in neither, either or both collections when the associated + * exception is thrown. Attempts to drain a queue to itself result in <tt>IllegalArgumentException</tt>. Further, + * the behavior of this operation is undefined if the specified collection is modified while the operation is in + * progress. + * + * @param objects The collection to transfer elements into. + * + * @return The number of elements transferred. + * + * @throws NullPointerException If objects is null. + * @throws IllegalArgumentException If objects is this queue. + */ + public int drainTo(Collection<? super E> objects) + { + return drainTo(objects, -1); + } + + /** + * Removes at most the given number of available elements from this queue and adds them into the given collection. + * A failure encountered while attempting to <tt>add</tt> elements to collection <tt>c</tt> may result in elements + * being in neither, either or both collections when the associated exception is thrown. Attempts to drain a queue + * to itself result in <tt>IllegalArgumentException</tt>. Further, the behavior of this operation is undefined if + * the specified collection is modified while the operation is in progress. + * + * @param objects The collection to transfer elements into. + * @param maxElements The maximum number of elements to transfer. If this is -1 then that is interpreted as meaning + * all elements. + * + * @return The number of elements transferred. + * + * @throws NullPointerException If c is null. + * @throws IllegalArgumentException If c is this queue. + */ + public int drainTo(Collection<? super E> objects, int maxElements) + { + if (objects == null) + { + throw new NullPointerException(); + } + + if (objects == this) + { + throw new IllegalArgumentException(); + } + + // final Queue<E> items = this.buffer; + final ReentrantLock lock = this.lock; + lock.lock(); + + try + { + int n = 0; + + for (int max = ((maxElements >= count) || (maxElements < 0)) ? count : maxElements; n < max; n++) + { + // Take items from the queue, do unblock the producers, but don't send not full signals yet. + objects.add(extract(true, false).getElement()); + } + + if (n > 0) + { + // count -= n; + notFull.signalAll(); + } + + return n; + } + finally + { + lock.unlock(); + } + } + + /** + * Takes all available data items from the queue or blocks until some become available. The returned items + * are wrapped in a {@link SynchRecord} which provides an interface to requeue them or send errors to their + * producers, where the producers are still blocked. + * + * @param c The collection to drain the data items into. + * @param unblock If set to <tt>true</tt> the producers for the taken items will be immediately unblocked. + * + * @return A count of the number of elements that were drained from the queue. + */ + public SynchRef drainTo(Collection<SynchRecord<E>> c, boolean unblock) + { + return drainTo(c, -1, unblock); + } + + /** + * Takes up to maxElements available data items from the queue or blocks until some become available. The returned + * items are wrapped in a {@link SynchRecord} which provides an interface to requeue them or send errors to their + * producers, where the producers are still blocked. + * + * @param coll The collection to drain the data items into. + * @param maxElements The maximum number of elements to drain. + * @param unblock If set to <tt>true</tt> the producers for the taken items will be immediately unblocked. + * + * @return A count of the number of elements that were drained from the queue. + */ + public SynchRef drainTo(Collection<SynchRecord<E>> coll, int maxElements, boolean unblock) + { + if (coll == null) + { + throw new NullPointerException(); + } + + // final Queue<E> items = this.buffer; + final ReentrantLock lock = this.lock; + lock.lock(); + + try + { + int n = 0; + + for (int max = ((maxElements >= count) || (maxElements < 0)) ? count : maxElements; n < max; n++) + { + // Extract the next record from the queue, don't signall the not full condition yet and release + // producers depending on whether the caller wants to or not. + coll.add(extract(false, unblock)); + } + + if (n > 0) + { + // count -= n; + notFull.signalAll(); + } + + return new SynchRefImpl(n, coll); + } + finally + { + lock.unlock(); + } + } + + /** + * This abstract method should be overriden to return an empty queue. Different implementations of producer + * consumer buffers can control the order in which data is accessed using different queue implementations. + * This method allows the type of queue to be abstracted out of this class and to be supplied by concrete + * implementations. + * + * @return An empty queue. + */ + protected abstract <T> Queue<T> createQueue(); + + /** + * Insert element into the queue, then possibly signal that the queue is not empty and block the producer + * on the element until permission to procede is given. + * + * <p/>If the producer is to be blocked then the lock must be released first, otherwise no other process + * will be able to get access to the queue. Hence, unlock and block are always set together. + * + * <p/>Call only when holding the global lock. + * + * @param unlockAndBlock <tt>true</tt>If the global queue lock should be released and the producer should be blocked. + * + * @return <tt>true</tt> if the operation succeeded, <tt>false</tt> otherwise. If the result is <tt>true</tt> this + * method may not return straight away, but only after the producer is unblocked by having its data + * consumed if the unlockAndBlock flag is set. In the false case the method will return straight away, no + * matter what value the unlockAndBlock flag has, leaving the global lock on. + */ + protected boolean insert(E x, boolean unlockAndBlock) + { + // Create a new record for the data item. + SynchRecordImpl<E> record = new SynchRecordImpl<E>(x); + + boolean result = buffer.offer(record); + + if (result) + { + count++; + + // Tell any waiting consumers that the queue is not empty. + notEmpty.signal(); + + if (unlockAndBlock) + { + // Allow other threads to read/write the queue. + lock.unlock(); + + // Wait until a consumer takes this data item. + record.waitForConsumer(); + } + + return true; + } + else + { + return false; + } + } + + /** + * Extract element at current take position, advance, and signal. + * + * <p/>Call only when holding lock. + */ + protected SynchRecordImpl<E> extract(boolean unblock, boolean signal) + { + SynchRecordImpl<E> result = buffer.remove(); + count--; + + if (signal) + { + notFull.signal(); + } + + if (unblock) + { + result.releaseImmediately(); + } + + return result; + } + + /** + * Get the capacity of the buffer. If the buffer has no maximum capacity then Integer.MAX_VALUE is returned. + * + * <p/>Call only when holding lock. + * + * @return The maximum capacity of the buffer. + */ + protected int getBufferCapacity() + { + if (buffer instanceof Capacity) + { + return ((Capacity) buffer).getCapacity(); + } + else + { + return Integer.MAX_VALUE; + } + } + + /** + * Return the head element from the buffer. + * + * <p/>Call only when holding lock. + * + * @return The head element from the buffer. + */ + protected E peekAtBufferHead() + { + return buffer.peek().getElement(); + } + + public class SynchRefImpl implements SynchRef + { + /** Holds the number of synch records associated with this reference. */ + int numRecords; + + /** Holds a reference to the collection of synch records managed by this. */ + Collection<SynchRecord<E>> records; + + public SynchRefImpl(int n, Collection<SynchRecord<E>> records) + { + this.numRecords = n; + this.records = records; + } + + public int getNumRecords() + { + return numRecords; + } + + /** + * Any producers that have had their data elements taken from the queue but have not been unblocked are unblocked + * when this method is called. The exception to this is producers that have had their data put back onto the queue + * by a consumer. Producers that have had exceptions for their data items registered by consumers will be unblocked + * but will not return from their put call normally, but with an exception instead. + */ + public void unblockProducers() + { + log.debug("public void unblockProducers(): called"); + + if (records != null) + { + for (SynchRecord<E> record : records) + { + // This call takes account of items that have already been released, are to be requeued or are in + // error. + record.releaseImmediately(); + } + } + + records = null; + } + } + + /** + * A SynchRecordImpl is used by a {@link BatchSynchQueue} to pair together a producer with its data. This allows + * the producer of data to be identified so that it can be unblocked when its data is consumed or sent errors when + * its data cannot be consumed. + */ + public class SynchRecordImpl<E> implements SynchRecord<E> + { + /** A boolean latch that determines when the producer for this data item will be allowed to continue. */ + BooleanLatch latch = new BooleanLatch(); + + /** The data element associated with this item. */ + E element; + + /** + * Create a new synch record. + * + * @param e The data element that the record encapsulates. + */ + public SynchRecordImpl(E e) + { + // Keep the data element. + element = e; + } + + /** + * Waits until the producer is given permission to proceded by a consumer. + */ + public void waitForConsumer() + { + latch.await(); + } + + /** + * Gets the data element contained by this record. + * + * @return The data element contained by this record. + */ + public E getElement() + { + return element; + } + + /** + * Immediately releases the producer of this data record. Consumers can bring the synchronization time of + * producers to a minimum by using this method to release them at the earliest possible moment when batch + * consuming records from sychronized producers. + */ + public void releaseImmediately() + { + // Check that the record has not already been released, is in error or is to be requeued. + latch.signal(); + + // Propagate errors to the producer. + + // Requeue items to be requeued. + } + + /** + * Tells the synch queue to put this element back onto the queue instead of releasing its producer. + * The element is not requeued immediately but upon calling the {@link SynchRef#unblockProducers()} method or + * the {@link #releaseImmediately()} method. + * + * <p/>This method will raise a runtime exception {@link AlreadyUnblockedException} if the producer for this + * element has already been unblocked. + */ + public void reQueue() + { + throw new RuntimeException("Not implemented."); + } + + /** + * Tells the synch queue to raise an exception with this elements producer. The exception is not raised + * immediately but upon calling the {@link SynchRef#unblockProducers()} method or the + * {@link #releaseImmediately()} method. The exception will be wrapped in a {@link SynchException} before it is + * raised on the producer. + * + * <p/>This method is unusual in that it accepts an exception as an argument. This is non-standard but is used + * because the exception is to be passed onto a different thread. + * + * <p/>This method will raise a runtime exception {@link AlreadyUnblockedException} if the producer for this + * element has already been unblocked. + * + * @param e The exception to raise on the producer. + */ + public void inError(Exception e) + { + throw new RuntimeException("Not implemented."); + } + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/BooleanLatch.java b/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/BooleanLatch.java new file mode 100644 index 0000000000..0e4a07594f --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/BooleanLatch.java @@ -0,0 +1,128 @@ +package org.apache.qpid.util.concurrent; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.concurrent.locks.AbstractQueuedSynchronizer; + +/** + * A BooleanLatch is like a set of traffic lights, where threads can wait at a red light until another thread gives + * the green light. When threads arrive at the latch it is initially red. They queue up until the green signal is + * given, at which point they can all acquire the latch in shared mode and continue to run concurrently. Once the latch + * is signalled it cannot be reset to red again. + * + * <p/> The latch uses a {@link java.util.concurrent.locks.AbstractQueuedSynchronizer} to implement its synchronization. + * This has two internal states, 0 which means that the latch is blocked, and 1 which means that the latch is open. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Block threads until a go signal is given. + * </table> + * + * @todo Might be better to use a countdown latch to count down from 1. Its await method can throw interrupted + * exception which makes the possibility of interruption more explicit, and provides a reminder to recheck the + * latch condition before continuing. + */ +public class BooleanLatch +{ + /** Holds the synchronizer that provides the thread queueing synchronization. */ + private final Sync sync = new Sync(); + + /** + * Tests whether or not the latch has been signalled, that is to say that, the light is green. + * + * <p/>This method is non-blocking. + * + * @return <tt>true</tt> if the latch may be acquired; the light is green. + */ + public boolean isSignalled() + { + return sync.isSignalled(); + } + + /** + * Waits on the latch until the signal is given and the light is green. If the light is already green then the + * latch will be acquired and the thread will not have to wait. + * + * <p/>This method will block until the go signal is given or the thread is otherwise interrupted. Before carrying + * out any processing threads that return from this method should confirm that the go signal has really been given + * on this latch by calling the {@link #isSignalled()} method. + */ + public void await() + { + sync.acquireShared(1); + } + + /** + * Releases any threads currently waiting on the latch. This flips the light to green allowing any threads that + * were waiting for this condition to now run. + * + * <p/>This method is non-blocking. + */ + public void signal() + { + sync.releaseShared(1); + } + + /** + * Implements a thread queued synchronizer. The internal state 0 means that the queue is blocked and the internl + * state 1 means that the queue is released and that all waiting threads can acquire the synchronizer in shared + * mode. + */ + private static class Sync extends AbstractQueuedSynchronizer + { + /** + * Attempts to acquire this synchronizer in shared mode. It may be acquired once it has been released. + * + * @param ignore This parameter is ignored. + * + * @return 1 if the shared acquisition succeeds and -1 if it fails. + */ + protected int tryAcquireShared(int ignore) + { + return isSignalled() ? 1 : -1; + } + + /** + * Releases the synchronizer, setting its internal state to 1. + * + * @param ignore This parameter is ignored. + * + * @return <tt>true</tt> always. + */ + protected boolean tryReleaseShared(int ignore) + { + setState(1); + + return true; + } + + /** + * Tests if the synchronizer is signalled. It is signalled when its internal state it 1. + * + * @return <tt>true</tt> if the internal state is 1, <tt>false</tt> otherwise. + */ + boolean isSignalled() + { + return getState() != 0; + } + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/Capacity.java b/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/Capacity.java new file mode 100644 index 0000000000..a97ce0e172 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/Capacity.java @@ -0,0 +1,35 @@ +package org.apache.qpid.util.concurrent; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + + +/** + * An interface exposed by data structures that have a maximum capacity. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Report the maximum capacity. + * </table> + */ +public interface Capacity +{ + public int getCapacity(); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/SynchBuffer.java b/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/SynchBuffer.java new file mode 100644 index 0000000000..bc63eb0353 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/SynchBuffer.java @@ -0,0 +1,50 @@ +package org.apache.qpid.util.concurrent; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.Queue; + +/** + * SynchBuffer completes the {@link BatchSynchQueueBase} abstract class by providing an implementation of the underlying + * queue as an array. This uses FIFO ordering for the queue but restricts the maximum size of the queue to a fixed + * amount. It also has the advantage that, as the buffer does not grow and shrink dynamically, memory for the buffer + * is allocated up front and does not create garbage during the operation of the queue. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Provide array based FIFO queue to create a batch synched queue around. + * </table> + * + * @todo Write an array based buffer implementation that implements Queue. + */ +public class SynchBuffer<E> extends BatchSynchQueueBase<E> +{ + /** + * Returns an empty queue, implemented as an array. + * + * @return An empty queue, implemented as an array. + */ + protected <T> Queue<T> createQueue() + { + throw new RuntimeException("Not implemented."); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/SynchException.java b/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/SynchException.java new file mode 100644 index 0000000000..99a83f96cd --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/SynchException.java @@ -0,0 +1,52 @@ +package org.apache.qpid.util.concurrent; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + + +/** + * SynchException is used to encapsulate exceptions with the data elements that caused them in order to send exceptions + * back from the consumers of a {@link BatchSynchQueue} to producers. The underlying exception should be retrieved from + * the {@link #getCause} method. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Encapsulate a data element and exception. + * </table> + */ +public class SynchException extends Exception +{ + /** Holds the data element that is in error. */ + Object element; + + /** + * Creates a new BaseApplicationException object. + * + * @param message The exception message. + * @param cause The underlying throwable cause. This may be null. + */ + public SynchException(String message, Throwable cause, Object element) + { + super(message, cause); + + // Keep the data element that was in error. + this.element = element; + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/SynchQueue.java b/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/SynchQueue.java new file mode 100644 index 0000000000..95833f398a --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/SynchQueue.java @@ -0,0 +1,48 @@ +package org.apache.qpid.util.concurrent; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.LinkedList; +import java.util.Queue; + +/** + * SynchQueue completes the {@link BatchSynchQueueBase} abstract class by providing an implementation of the underlying + * queue as a linked list. This uses FIFO ordering for the queue and allows the queue to grow to accomodate more + * elements as needed. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Provide linked list FIFO queue to create a batch synched queue around. + * </table> + */ +public class SynchQueue<E> extends BatchSynchQueueBase<E> +{ + /** + * Returns an empty queue, implemented as a linked list. + * + * @return An empty queue, implemented as a linked list. + */ + protected <T> Queue<T> createQueue() + { + return new LinkedList<T>(); + } +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/SynchRecord.java b/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/SynchRecord.java new file mode 100644 index 0000000000..fd740c20cd --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/SynchRecord.java @@ -0,0 +1,74 @@ +package org.apache.qpid.util.concurrent; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + + +/** + * SynchRecord associates a data item from a {@link BatchSynchQueue} with its producer. This enables the data item data + * item to be put back on the queue without unblocking its producer, or to send exceptions to the producer. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Get the underlying data element. + * <tr><td> Put the data element back on the queue without unblocking its producer. + * <tr><td> Send and exception to the data elements producer. + * </table> + */ +public interface SynchRecord<E> +{ + /** + * Gets the data element contained by this record. + * + * @return The data element contained by this record. + */ + public E getElement(); + + /** + * Tells the synch queue to put this element back onto the queue instead of releasing its producer. + * The element is not requeued immediately but upon calling the {@link SynchRef#unblockProducers()} method. + * + * <p/>This method will raise a runtime exception {@link AlreadyUnblockedException} if the producer for this element + * has already been unblocked. + */ + public void reQueue(); + + /** + * Immediately releases the producer of this data record. Consumers can bring the synchronization time of + * producers to a minimum by using this method to release them at the earliest possible moment when batch + * consuming records from sychronized producers. + */ + public void releaseImmediately(); + + /** + * Tells the synch queue to raise an exception with this elements producer. The exception is not raised immediately + * but upon calling the {@link SynchRef#unblockProducers()} method. The exception will be wrapped in a + * {@link SynchException} before it is raised on the producer. + * + * <p/>This method is unusual in that it accepts an exception as an argument. This is non-standard but is used + * because the exception is to be passed onto a different thread. + * + * <p/>This method will raise a runtime exception {@link AlreadyUnblockedException} if the producer for this element + * has already been unblocked. + * + * @param e The exception to raise on the producer. + */ + public void inError(Exception e); +} diff --git a/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/SynchRef.java b/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/SynchRef.java new file mode 100644 index 0000000000..efe2344c06 --- /dev/null +++ b/qpid/java/common/src/main/java/org/apache/qpid/util/concurrent/SynchRef.java @@ -0,0 +1,51 @@ +package org.apache.qpid.util.concurrent; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + + +/** + * A SynchRef is an interface which is returned from the synchronous take and drain methods of {@link BatchSynchQueue}, + * allowing call-backs to be made against the synchronizing strucutre. It allows the consumer to communicate when it + * wants producers that have their data taken to be unblocked. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities + * <tr><td> Report number of records returned by a taking operation. + * <tr><td> Provide call-back to release producers of taken records. + * </table> + */ +public interface SynchRef +{ + /** + * Reports the number of records taken by the take or drain operation. + * + * @return The number of records taken by the take or drain operation. + */ + public int getNumRecords(); + + /** + * Any producers that have had their data elements taken from the queue but have not been unblocked are + * unblocked when this method is called. The exception to this is producers that have had their data put back + * onto the queue by a consumer. Producers that have had exceptions for their data items registered by consumers + * will be unblocked but will not return from their put call normally, but with an exception instead. + */ + public void unblockProducers(); +} diff --git a/qpid/java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterClient.java b/qpid/java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterClient.java new file mode 100644 index 0000000000..b93dc46741 --- /dev/null +++ b/qpid/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/qpid/java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterServer.java b/qpid/java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterServer.java new file mode 100644 index 0000000000..423e98c67b --- /dev/null +++ b/qpid/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/qpid/java/common/src/test/java/org/apache/qpid/AMQExceptionTest.java b/qpid/java/common/src/test/java/org/apache/qpid/AMQExceptionTest.java new file mode 100644 index 0000000000..ef6cd41492 --- /dev/null +++ b/qpid/java/common/src/test/java/org/apache/qpid/AMQExceptionTest.java @@ -0,0 +1,106 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 junit.framework.TestCase; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.framing.AMQFrameDecodingException; + +/** + * This test is to ensure that when an AMQException is rethrown that the specified exception is correctly wrapped up. + * + * There are three cases: + * Re-throwing an AMQException + * Re-throwing a Subclass of AMQException + * Re-throwing a Subclass of AMQException that does not have the default AMQException constructor which will force the + * creation of an AMQException. + */ +public class AMQExceptionTest extends TestCase +{ + /** + * Test that an AMQException will be correctly created and rethrown. + */ + public void testRethrowGeneric() + { + AMQException test = new AMQException(AMQConstant.ACCESS_REFUSED, "refused", new RuntimeException()); + + AMQException e = reThrowException(test); + + assertEquals("Exception not of correct class", AMQException.class, e.getClass()); + + } + + /** + * Test that a subclass of AMQException that has the default constructor will be correctly created and rethrown. + */ + public void testRethrowAMQESubclass() + { + AMQFrameDecodingException test = new AMQFrameDecodingException(AMQConstant.INTERNAL_ERROR, + "Error", + new Exception()); + AMQException e = reThrowException(test); + + assertEquals("Exception not of correct class", AMQFrameDecodingException.class, e.getClass()); + } + + /** + * Test that a subclass of AMQException that doesnot have the default constructor will be correctly rethrown as an + * AMQException + */ + public void testRethrowAMQESubclassNoConstructor() + { + AMQExceptionSubclass test = new AMQExceptionSubclass("Invalid Argument Exception"); + + AMQException e = reThrowException(test); + + assertEquals("Exception not of correct class", AMQException.class, e.getClass()); + } + + /** + * Private method to rethrown and validate the basic values of the rethrown + * @param test Exception to rethrow + * @throws AMQException the rethrown exception + */ + private AMQException reThrowException(AMQException test) + { + AMQException amqe = test.cloneForCurrentThread(); + + assertEquals("Error code does not match.", test.getErrorCode(), amqe.getErrorCode()); + assertTrue("Exception message does not start as expected.", amqe.getMessage().startsWith(test.getMessage())); + assertEquals("Test Exception is not set as the cause", test, amqe.getCause()); + assertEquals("Cause is not correct", test.getCause(), amqe.getCause().getCause()); + + return amqe; + } + + /** + * Private class that extends AMQException but does not have a default exception. + */ + private class AMQExceptionSubclass extends AMQException + { + + public AMQExceptionSubclass(String msg) + { + super(null, msg, null); + } + } +} + diff --git a/qpid/java/common/src/test/java/org/apache/qpid/codec/AMQDecoderTest.java b/qpid/java/common/src/test/java/org/apache/qpid/codec/AMQDecoderTest.java new file mode 100644 index 0000000000..62e25e7d79 --- /dev/null +++ b/qpid/java/common/src/test/java/org/apache/qpid/codec/AMQDecoderTest.java @@ -0,0 +1,151 @@ +package org.apache.qpid.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 java.nio.ByteBuffer; +import java.util.ArrayList; + +import junit.framework.TestCase; + +import org.apache.qpid.framing.AMQDataBlock; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.AMQFrameDecodingException; +import org.apache.qpid.framing.AMQProtocolVersionException; +import org.apache.qpid.framing.HeartbeatBody; + +public class AMQDecoderTest extends TestCase +{ + + private AMQCodecFactory _factory; + private AMQDecoder _decoder; + + + public void setUp() + { + _factory = new AMQCodecFactory(false, null); + _decoder = _factory.getDecoder(); + } + + + public void testSingleFrameDecode() throws AMQProtocolVersionException, AMQFrameDecodingException + { + ByteBuffer msg = HeartbeatBody.FRAME.toNioByteBuffer(); + ArrayList<AMQDataBlock> frames = _decoder.decodeBuffer(msg); + if (frames.get(0) instanceof AMQFrame) + { + assertEquals(HeartbeatBody.FRAME.getBodyFrame().getFrameType(), ((AMQFrame) frames.get(0)).getBodyFrame().getFrameType()); + } + else + { + fail("decode was not a frame"); + } + } + + public void testPartialFrameDecode() throws AMQProtocolVersionException, AMQFrameDecodingException + { + ByteBuffer msg = HeartbeatBody.FRAME.toNioByteBuffer(); + ByteBuffer msgA = msg.slice(); + int msgbPos = msg.remaining() / 2; + int msgaLimit = msg.remaining() - msgbPos; + msgA.limit(msgaLimit); + msg.position(msgbPos); + ByteBuffer msgB = msg.slice(); + ArrayList<AMQDataBlock> frames = _decoder.decodeBuffer(msgA); + assertEquals(0, frames.size()); + frames = _decoder.decodeBuffer(msgB); + assertEquals(1, frames.size()); + if (frames.get(0) instanceof AMQFrame) + { + assertEquals(HeartbeatBody.FRAME.getBodyFrame().getFrameType(), ((AMQFrame) frames.get(0)).getBodyFrame().getFrameType()); + } + else + { + fail("decode was not a frame"); + } + } + + public void testMultipleFrameDecode() throws AMQProtocolVersionException, AMQFrameDecodingException + { + ByteBuffer msgA = HeartbeatBody.FRAME.toNioByteBuffer(); + ByteBuffer msgB = HeartbeatBody.FRAME.toNioByteBuffer(); + ByteBuffer msg = ByteBuffer.allocate(msgA.remaining() + msgB.remaining()); + msg.put(msgA); + msg.put(msgB); + msg.flip(); + ArrayList<AMQDataBlock> frames = _decoder.decodeBuffer(msg); + assertEquals(2, frames.size()); + for (AMQDataBlock frame : frames) + { + if (frame instanceof AMQFrame) + { + assertEquals(HeartbeatBody.FRAME.getBodyFrame().getFrameType(), ((AMQFrame) frame).getBodyFrame().getFrameType()); + } + else + { + fail("decode was not a frame"); + } + } + } + + public void testMultiplePartialFrameDecode() throws AMQProtocolVersionException, AMQFrameDecodingException + { + 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); + int limit = msgB.limit(); + int pos = msgB.remaining() / 2; + msgB.limit(pos); + sliceA.put(msgB); + sliceA.flip(); + msgB.limit(limit); + msgB.position(pos); + + ByteBuffer sliceB = ByteBuffer.allocate(msgB.remaining() + pos); + sliceB.put(msgB); + msgC.limit(pos); + sliceB.put(msgC); + sliceB.flip(); + msgC.limit(limit); + + ArrayList<AMQDataBlock> frames = _decoder.decodeBuffer(sliceA); + assertEquals(1, frames.size()); + frames = _decoder.decodeBuffer(sliceB); + assertEquals(1, frames.size()); + frames = _decoder.decodeBuffer(msgC); + assertEquals(1, frames.size()); + for (AMQDataBlock frame : frames) + { + if (frame instanceof AMQFrame) + { + assertEquals(HeartbeatBody.FRAME.getBodyFrame().getFrameType(), ((AMQFrame) frame).getBodyFrame().getFrameType()); + } + else + { + fail("decode was not a frame"); + } + } + } + +} diff --git a/qpid/java/common/src/test/java/org/apache/qpid/codec/MockAMQVersionAwareProtocolSession.java b/qpid/java/common/src/test/java/org/apache/qpid/codec/MockAMQVersionAwareProtocolSession.java new file mode 100644 index 0000000000..401848c21d --- /dev/null +++ b/qpid/java/common/src/test/java/org/apache/qpid/codec/MockAMQVersionAwareProtocolSession.java @@ -0,0 +1,105 @@ +package org.apache.qpid.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 java.nio.ByteBuffer; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQDataBlock; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.HeartbeatBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.ProtocolVersion; +import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; +import org.apache.qpid.transport.Sender; + +public class MockAMQVersionAwareProtocolSession implements AMQVersionAwareProtocolSession +{ + + public void contentBodyReceived(int channelId, ContentBody body) throws AMQException + { + // TODO Auto-generated method stub + + } + + public void contentHeaderReceived(int channelId, ContentHeaderBody body) throws AMQException + { + // TODO Auto-generated method stub + + } + + public MethodRegistry getMethodRegistry() + { + return MethodRegistry.getMethodRegistry(ProtocolVersion.v0_9); + } + + public void heartbeatBodyReceived(int channelId, HeartbeatBody body) throws AMQException + { + // TODO Auto-generated method stub + + } + + public void init() + { + // TODO Auto-generated method stub + + } + + public void methodFrameReceived(int channelId, AMQMethodBody body) throws AMQException + { + // TODO Auto-generated method stub + + } + + public void setSender(Sender<ByteBuffer> sender) + { + // TODO Auto-generated method stub + + } + + public void writeFrame(AMQDataBlock frame) + { + // TODO Auto-generated method stub + + } + + public byte getProtocolMajorVersion() + { + // TODO Auto-generated method stub + return 0; + } + + public byte getProtocolMinorVersion() + { + // TODO Auto-generated method stub + return 0; + } + + public ProtocolVersion getProtocolVersion() + { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/qpid/java/common/src/test/java/org/apache/qpid/framing/AMQShortStringTest.java b/qpid/java/common/src/test/java/org/apache/qpid/framing/AMQShortStringTest.java new file mode 100644 index 0000000000..92e7ce0a80 --- /dev/null +++ b/qpid/java/common/src/test/java/org/apache/qpid/framing/AMQShortStringTest.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 junit.framework.TestCase; +public class AMQShortStringTest extends TestCase +{ + + public static final AMQShortString HELLO = new AMQShortString("Hello"); + public static final AMQShortString HELL = new AMQShortString("Hell"); + public static final AMQShortString GOODBYE = new AMQShortString("Goodbye"); + public static final AMQShortString GOOD = new AMQShortString("Good"); + public static final AMQShortString BYE = new AMQShortString("BYE"); + + public void testStartsWith() + { + assertTrue(HELLO.startsWith(HELL)); + + assertFalse(HELL.startsWith(HELLO)); + + assertTrue(GOODBYE.startsWith(GOOD)); + + assertFalse(GOOD.startsWith(GOODBYE)); + } + + public void testEndWith() + { + assertFalse(HELL.endsWith(HELLO)); + + assertTrue(GOODBYE.endsWith(new AMQShortString("bye"))); + + assertFalse(GOODBYE.endsWith(BYE)); + } + + + public void testTokenize() + { + AMQShortString dotSeparatedWords = new AMQShortString("this.is.a.test.with.1.2.3.-numbers-and-then--dashes-"); + AMQShortStringTokenizer dotTokenizer = dotSeparatedWords.tokenize((byte) '.'); + + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(new AMQShortString("this"),(dotTokenizer.nextToken())); + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(new AMQShortString("is"),(dotTokenizer.nextToken())); + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(new AMQShortString("a"),(dotTokenizer.nextToken())); + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(new AMQShortString("test"),(dotTokenizer.nextToken())); + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(new AMQShortString("with"),(dotTokenizer.nextToken())); + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(dotTokenizer.nextToken().toIntValue() , 1); + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(dotTokenizer.nextToken().toIntValue() , 2); + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(dotTokenizer.nextToken().toIntValue() , 3); + assertTrue(dotTokenizer.hasMoreTokens()); + AMQShortString dashString = dotTokenizer.nextToken(); + assertEquals(new AMQShortString("-numbers-and-then--dashes-"),(dashString)); + + AMQShortStringTokenizer dashTokenizer = dashString.tokenize((byte)'-'); + assertEquals(dashTokenizer.countTokens(), 7); + + AMQShortString[] expectedResults = new AMQShortString[] + { AMQShortString.EMPTY_STRING, + new AMQShortString("numbers"), + new AMQShortString("and"), + new AMQShortString("then"), + AMQShortString.EMPTY_STRING, + new AMQShortString("dashes"), + AMQShortString.EMPTY_STRING }; + + for(int i = 0; i < 7; i++) + { + assertTrue(dashTokenizer.hasMoreTokens()); + assertEquals(dashTokenizer.nextToken(), expectedResults[i]); + } + + assertFalse(dotTokenizer.hasMoreTokens()); + } + + + public void testEquals() + { + assertEquals(GOODBYE, new AMQShortString("Goodbye")); + assertEquals(new AMQShortString("A"), new AMQShortString("A")); + assertFalse(new AMQShortString("A").equals(new AMQShortString("a"))); + } + + +} diff --git a/qpid/java/common/src/test/java/org/apache/qpid/framing/BasicContentHeaderPropertiesTest.java b/qpid/java/common/src/test/java/org/apache/qpid/framing/BasicContentHeaderPropertiesTest.java new file mode 100644 index 0000000000..4fd1f60d69 --- /dev/null +++ b/qpid/java/common/src/test/java/org/apache/qpid/framing/BasicContentHeaderPropertiesTest.java @@ -0,0 +1,188 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 junit.framework.TestCase; + + +public class BasicContentHeaderPropertiesTest extends TestCase +{ + + BasicContentHeaderProperties _testProperties; + FieldTable _testTable; + String _testString = "This is a test string"; + int _testint = 666; + + /** + * Currently only test setting/getting String, int and boolean props + */ + public BasicContentHeaderPropertiesTest() + { + _testProperties = new BasicContentHeaderProperties(); + } + + public void setUp() + { + _testTable = new FieldTable(); + _testTable.setString("TestString", _testString); + _testTable.setInteger("Testint", _testint); + _testProperties = new BasicContentHeaderProperties(); + _testProperties.setHeaders(_testTable); + } + + public void testGetPropertyListSize() + { + //needs a better test but at least we're exercising the code ! + // FT length is encoded in an int + int expectedSize = EncodingUtils.encodedIntegerLength(); + + expectedSize += EncodingUtils.encodedShortStringLength("TestInt"); + // 1 is for the Encoding Letter. here an 'i' + expectedSize += 1 + EncodingUtils.encodedIntegerLength(); + + expectedSize += EncodingUtils.encodedShortStringLength("TestString"); + // 1 is for the Encoding Letter. here an 'S' + expectedSize += 1 + EncodingUtils.encodedLongStringLength(_testString); + + + int size = _testProperties.getPropertyListSize(); + + assertEquals(expectedSize, size); + } + + public void testGetSetPropertyFlags() + { + _testProperties.setPropertyFlags(99); + assertEquals(99, _testProperties.getPropertyFlags()); + } + + public void testWritePropertyListPayload() + { + ByteBuffer buf = ByteBuffer.allocate(300); + _testProperties.writePropertyListPayload(buf); + } + + public void testPopulatePropertiesFromBuffer() throws Exception + { + ByteBuffer buf = ByteBuffer.allocate(300); + _testProperties.populatePropertiesFromBuffer(buf, 99, 99); + } + + public void testSetGetContentType() + { + String contentType = "contentType"; + _testProperties.setContentType(contentType); + assertEquals(contentType, _testProperties.getContentTypeAsString()); + } + + public void testSetGetEncoding() + { + String encoding = "encoding"; + _testProperties.setEncoding(encoding); + assertEquals(encoding, _testProperties.getEncodingAsString()); + } + + public void testSetGetHeaders() + { + _testProperties.setHeaders(_testTable); + assertEquals(_testTable, _testProperties.getHeaders()); + } + + public void testSetGetDeliveryMode() + { + byte deliveryMode = 1; + _testProperties.setDeliveryMode(deliveryMode); + assertEquals(deliveryMode, _testProperties.getDeliveryMode()); + } + + public void testSetGetPriority() + { + byte priority = 1; + _testProperties.setPriority(priority); + assertEquals(priority, _testProperties.getPriority()); + } + + public void testSetGetCorrelationId() + { + String correlationId = "correlationId"; + _testProperties.setCorrelationId(correlationId); + assertEquals(correlationId, _testProperties.getCorrelationIdAsString()); + } + + public void testSetGetReplyTo() + { + String replyTo = "replyTo"; + _testProperties.setReplyTo(replyTo); + assertEquals(replyTo, _testProperties.getReplyToAsString()); + } + + public void testSetGetExpiration() + { + long expiration = 999999999; + _testProperties.setExpiration(expiration); + assertEquals(expiration, _testProperties.getExpiration()); + } + + public void testSetGetMessageId() + { + String messageId = "messageId"; + _testProperties.setMessageId(messageId); + assertEquals(messageId, _testProperties.getMessageIdAsString()); + } + + public void testSetGetTimestamp() + { + long timestamp = System.currentTimeMillis(); + _testProperties.setTimestamp(timestamp); + assertEquals(timestamp, _testProperties.getTimestamp()); + } + + public void testSetGetType() + { + String type = "type"; + _testProperties.setType(type); + assertEquals(type, _testProperties.getTypeAsString()); + } + + public void testSetGetUserId() + { + String userId = "userId"; + _testProperties.setUserId(userId); + assertEquals(userId, _testProperties.getUserIdAsString()); + } + + public void testSetGetAppId() + { + String appId = "appId"; + _testProperties.setAppId(appId); + assertEquals(appId, _testProperties.getAppIdAsString()); + } + + public void testSetGetClusterId() + { + String clusterId = "clusterId"; + _testProperties.setClusterId(clusterId); + assertEquals(clusterId, _testProperties.getClusterIdAsString()); + } + +} diff --git a/qpid/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java b/qpid/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java new file mode 100644 index 0000000000..d4691ba097 --- /dev/null +++ b/qpid/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java @@ -0,0 +1,974 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 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; + +public class PropertyFieldTableTest extends TestCase +{ + private static final Logger _logger = LoggerFactory.getLogger(PropertyFieldTableTest.class); + + /** + * Test that setting a similar named value replaces any previous value set on that name + */ + public void testReplacement() + { + FieldTable table1 = new FieldTable(); + // Set a boolean value + table1.setBoolean("value", true); + // Check length of table is correct (<Value length> + <type> + <Boolean length>) + int size = EncodingUtils.encodedShortStringLength("value") + 1 + EncodingUtils.encodedBooleanLength(); + Assert.assertEquals(size, table1.getEncodedSize()); + + // reset value to an integer + table1.setInteger("value", Integer.MAX_VALUE); + + // Check the length has changed accordingly (<Value length> + <type> + <Integer length>) + size = EncodingUtils.encodedShortStringLength("value") + 1 + EncodingUtils.encodedIntegerLength(); + Assert.assertEquals(size, table1.getEncodedSize()); + + // Check boolean value is null + Assert.assertEquals(null, table1.getBoolean("value")); + // ... and integer value is good + Assert.assertEquals((Integer) Integer.MAX_VALUE, table1.getInteger("value")); + } + + /** + * Set a boolean and check that we can only get it back as a boolean and a string + * Check that attempting to lookup a non existent value returns null + */ + public void testBoolean() + { + FieldTable table1 = new FieldTable(); + table1.setBoolean("value", true); + Assert.assertTrue(table1.propertyExists("value")); + + // Test Getting right value back + Assert.assertEquals((Boolean) true, table1.getBoolean("value")); + + // Check we don't get anything back for other gets + Assert.assertEquals(null, table1.getByte("value")); + Assert.assertEquals(null, table1.getByte("value")); + Assert.assertEquals(null, table1.getShort("value")); + Assert.assertEquals(null, table1.getCharacter("value")); + Assert.assertEquals(null, table1.getDouble("value")); + Assert.assertEquals(null, table1.getFloat("value")); + Assert.assertEquals(null, table1.getInteger("value")); + Assert.assertEquals(null, table1.getLong("value")); + Assert.assertEquals(null, table1.getBytes("value")); + + // except value as a string + Assert.assertEquals("true", table1.getString("value")); + + table1.remove("value"); + + // Table should now have zero length for encoding + checkEmpty(table1); + + // Looking up an invalid value returns null + Assert.assertEquals(null, table1.getBoolean("Rubbish")); + } + + /** + * Set a byte and check that we can only get it back as a byte and a string + * Check that attempting to lookup a non existent value returns null + */ + public void testByte() + { + FieldTable table1 = new FieldTable(); + table1.setByte("value", Byte.MAX_VALUE); + Assert.assertTrue(table1.propertyExists("value")); + + // Tets lookups we shouldn't get anything back for other gets + // we should get right value back for this type .... + Assert.assertEquals(null, table1.getBoolean("value")); + Assert.assertEquals(Byte.MAX_VALUE, (byte) table1.getByte("value")); + Assert.assertEquals(null, table1.getShort("value")); + Assert.assertEquals(null, table1.getCharacter("value")); + Assert.assertEquals(null, table1.getDouble("value")); + Assert.assertEquals(null, table1.getFloat("value")); + Assert.assertEquals(null, table1.getInteger("value")); + Assert.assertEquals(null, table1.getLong("value")); + Assert.assertEquals(null, table1.getBytes("value")); + + // ... and a the string value of it. + Assert.assertEquals("" + Byte.MAX_VALUE, table1.getString("value")); + + table1.remove("value"); + // Table should now have zero length for encoding + checkEmpty(table1); + + // Looking up an invalid value returns null + Assert.assertEquals(null, table1.getByte("Rubbish")); + } + + /** + * Set a short and check that we can only get it back as a short and a string + * Check that attempting to lookup a non existent value returns null + */ + public void testShort() + { + FieldTable table1 = new FieldTable(); + table1.setShort("value", Short.MAX_VALUE); + Assert.assertTrue(table1.propertyExists("value")); + + // Tets lookups we shouldn't get anything back for other gets + // we should get right value back for this type .... + Assert.assertEquals(null, table1.getBoolean("value")); + Assert.assertEquals(null, table1.getByte("value")); + Assert.assertEquals(Short.MAX_VALUE, (short) table1.getShort("value")); + Assert.assertEquals(null, table1.getCharacter("value")); + Assert.assertEquals(null, table1.getDouble("value")); + Assert.assertEquals(null, table1.getFloat("value")); + Assert.assertEquals(null, table1.getInteger("value")); + Assert.assertEquals(null, table1.getLong("value")); + Assert.assertEquals(null, table1.getBytes("value")); + + // ... and a the string value of it. + Assert.assertEquals("" + Short.MAX_VALUE, table1.getString("value")); + + table1.remove("value"); + // Table should now have zero length for encoding + checkEmpty(table1); + + // Looking up an invalid value returns null + Assert.assertEquals(null, table1.getShort("Rubbish")); + } + + /** + * Set a char and check that we can only get it back as a char + * Check that attempting to lookup a non existent value returns null + */ + public void testChar() + { + FieldTable table1 = new FieldTable(); + table1.setChar("value", 'c'); + Assert.assertTrue(table1.propertyExists("value")); + + // Tets lookups we shouldn't get anything back for other gets + // we should get right value back for this type .... + Assert.assertEquals(null, table1.getBoolean("value")); + Assert.assertEquals(null, table1.getByte("value")); + Assert.assertEquals(null, table1.getShort("value")); + Assert.assertEquals('c', (char) table1.getCharacter("value")); + Assert.assertEquals(null, table1.getDouble("value")); + Assert.assertEquals(null, table1.getFloat("value")); + Assert.assertEquals(null, table1.getInteger("value")); + Assert.assertEquals(null, table1.getLong("value")); + Assert.assertEquals(null, table1.getBytes("value")); + + // ... and a the string value of it. + Assert.assertEquals("c", table1.getString("value")); + + table1.remove("value"); + + // Table should now have zero length for encoding + checkEmpty(table1); + + // Looking up an invalid value returns null + Assert.assertEquals(null, table1.getCharacter("Rubbish")); + } + + /** + * Set a double and check that we can only get it back as a double + * Check that attempting to lookup a non existent value returns null + */ + public void testDouble() + { + FieldTable table1 = new FieldTable(); + table1.setDouble("value", Double.MAX_VALUE); + Assert.assertTrue(table1.propertyExists("value")); + + // Tets lookups we shouldn't get anything back for other gets + // we should get right value back for this type .... + Assert.assertEquals(null, table1.getBoolean("value")); + Assert.assertEquals(null, table1.getByte("value")); + Assert.assertEquals(null, table1.getShort("value")); + Assert.assertEquals(null, table1.getCharacter("value")); + Assert.assertEquals(Double.MAX_VALUE, (double) table1.getDouble("value")); + Assert.assertEquals(null, table1.getFloat("value")); + Assert.assertEquals(null, table1.getInteger("value")); + Assert.assertEquals(null, table1.getLong("value")); + Assert.assertEquals(null, table1.getBytes("value")); + + // ... and a the string value of it. + Assert.assertEquals("" + Double.MAX_VALUE, table1.getString("value")); + table1.remove("value"); + // but after a removeKey it doesn't + Assert.assertFalse(table1.containsKey("value")); + + // Table should now have zero length for encoding + checkEmpty(table1); + + // Looking up an invalid value returns null + Assert.assertEquals(null, table1.getDouble("Rubbish")); + } + + /** + * Set a float and check that we can only get it back as a float + * Check that attempting to lookup a non existent value returns null + */ + public void testFloat() + { + FieldTable table1 = new FieldTable(); + table1.setFloat("value", Float.MAX_VALUE); + Assert.assertTrue(table1.propertyExists("value")); + + // Tets lookups we shouldn't get anything back for other gets + // we should get right value back for this type .... + Assert.assertEquals(null, table1.getBoolean("value")); + Assert.assertEquals(null, table1.getByte("value")); + Assert.assertEquals(null, table1.getShort("value")); + Assert.assertEquals(null, table1.getCharacter("value")); + Assert.assertEquals(null, table1.getDouble("value")); + Assert.assertEquals(Float.MAX_VALUE, (float) table1.getFloat("value")); + Assert.assertEquals(null, table1.getInteger("value")); + Assert.assertEquals(null, table1.getLong("value")); + Assert.assertEquals(null, table1.getBytes("value")); + + // ... and a the string value of it. + Assert.assertEquals("" + Float.MAX_VALUE, table1.getString("value")); + + table1.remove("value"); + // but after a removeKey it doesn't + Assert.assertFalse(table1.containsKey("value")); + + // Table should now have zero length for encoding + checkEmpty(table1); + + // Looking up an invalid value returns null + Assert.assertEquals(null, table1.getFloat("Rubbish")); + } + + /** + * Set an int and check that we can only get it back as an int + * Check that attempting to lookup a non existent value returns null + */ + public void testInt() + { + FieldTable table1 = new FieldTable(); + table1.setInteger("value", Integer.MAX_VALUE); + Assert.assertTrue(table1.propertyExists("value")); + + // Tets lookups we shouldn't get anything back for other gets + // we should get right value back for this type .... + Assert.assertEquals(null, table1.getBoolean("value")); + Assert.assertEquals(null, table1.getByte("value")); + Assert.assertEquals(null, table1.getShort("value")); + Assert.assertEquals(null, table1.getCharacter("value")); + Assert.assertEquals(null, table1.getDouble("value")); + Assert.assertEquals(null, table1.getFloat("value")); + Assert.assertEquals(Integer.MAX_VALUE, (int) table1.getInteger("value")); + Assert.assertEquals(null, table1.getLong("value")); + Assert.assertEquals(null, table1.getBytes("value")); + + // ... and a the string value of it. + Assert.assertEquals("" + Integer.MAX_VALUE, table1.getString("value")); + + table1.remove("value"); + // but after a removeKey it doesn't + Assert.assertFalse(table1.containsKey("value")); + + // Table should now have zero length for encoding + checkEmpty(table1); + + // Looking up an invalid value returns null + Assert.assertEquals(null, table1.getInteger("Rubbish")); + } + + /** + * Set a long and check that we can only get it back as a long + * Check that attempting to lookup a non existent value returns null + */ + public void testLong() + { + FieldTable table1 = new FieldTable(); + table1.setLong("value", Long.MAX_VALUE); + Assert.assertTrue(table1.propertyExists("value")); + + // Tets lookups we shouldn't get anything back for other gets + // we should get right value back for this type .... + Assert.assertEquals(null, table1.getBoolean("value")); + Assert.assertEquals(null, table1.getByte("value")); + Assert.assertEquals(null, table1.getShort("value")); + Assert.assertEquals(null, table1.getCharacter("value")); + Assert.assertEquals(null, table1.getDouble("value")); + Assert.assertEquals(null, table1.getFloat("value")); + Assert.assertEquals(null, table1.getInteger("value")); + Assert.assertEquals(Long.MAX_VALUE, (long) table1.getLong("value")); + Assert.assertEquals(null, table1.getBytes("value")); + + // ... and a the string value of it. + Assert.assertEquals("" + Long.MAX_VALUE, table1.getString("value")); + + table1.remove("value"); + // but after a removeKey it doesn't + Assert.assertFalse(table1.containsKey("value")); + + // Table should now have zero length for encoding + checkEmpty(table1); + + // Looking up an invalid value returns null + Assert.assertEquals(null, table1.getLong("Rubbish")); + } + + /** + * Set a double and check that we can only get it back as a double + * Check that attempting to lookup a non existent value returns null + */ + public void testBytes() + { + byte[] bytes = { 99, 98, 97, 96, 95 }; + + FieldTable table1 = new FieldTable(); + table1.setBytes("value", bytes); + Assert.assertTrue(table1.propertyExists("value")); + + // Tets lookups we shouldn't get anything back for other gets + // we should get right value back for this type .... + Assert.assertEquals(null, table1.getBoolean("value")); + Assert.assertEquals(null, table1.getByte("value")); + Assert.assertEquals(null, table1.getShort("value")); + Assert.assertEquals(null, table1.getCharacter("value")); + Assert.assertEquals(null, table1.getDouble("value")); + Assert.assertEquals(null, table1.getFloat("value")); + Assert.assertEquals(null, table1.getInteger("value")); + Assert.assertEquals(null, table1.getLong("value")); + assertBytesEqual(bytes, table1.getBytes("value")); + + // ... and a the string value of it is null + Assert.assertEquals(null, table1.getString("value")); + + table1.remove("value"); + // but after a removeKey it doesn't + Assert.assertFalse(table1.containsKey("value")); + + // Table should now have zero length for encoding + checkEmpty(table1); + + // Looking up an invalid value returns null + Assert.assertEquals(null, table1.getBytes("Rubbish")); + } + + /** + * Calls all methods that can be used to check the table is empty + * - getEncodedSize + * - isEmpty + * - length + * + * @param table to check is empty + */ + private void checkEmpty(FieldTable table) + { + Assert.assertEquals(0, table.getEncodedSize()); + Assert.assertTrue(table.isEmpty()); + Assert.assertEquals(0, table.size()); + + Assert.assertEquals(0, table.keySet().size()); + } + + /** + * Set a String and check that we can only get it back as a String + * Check that attempting to lookup a non existent value returns null + */ + public void testString() + { + FieldTable table1 = new FieldTable(); + table1.setString("value", "Hello"); + Assert.assertTrue(table1.propertyExists("value")); + + // Tets lookups we shouldn't get anything back for other gets + // we should get right value back for this type .... + Assert.assertEquals(null, table1.getBoolean("value")); + Assert.assertEquals(null, table1.getByte("value")); + Assert.assertEquals(null, table1.getShort("value")); + Assert.assertEquals(null, table1.getCharacter("value")); + Assert.assertEquals(null, table1.getDouble("value")); + Assert.assertEquals(null, table1.getFloat("value")); + Assert.assertEquals(null, table1.getInteger("value")); + Assert.assertEquals(null, table1.getLong("value")); + Assert.assertEquals(null, table1.getBytes("value")); + Assert.assertEquals("Hello", table1.getString("value")); + + // Try setting a null value and read it back + table1.setString("value", null); + + Assert.assertEquals(null, table1.getString("value")); + + // but still contains the value + Assert.assertTrue(table1.containsKey("value")); + + table1.remove("value"); + // but after a removeKey it doesn't + Assert.assertFalse(table1.containsKey("value")); + + checkEmpty(table1); + + // Looking up an invalid value returns null + Assert.assertEquals(null, table1.getString("Rubbish")); + + // Additional Test that haven't been covered for string + table1.setObject("value", "Hello"); + // Check that it was set correctly + Assert.assertEquals("Hello", table1.getString("value")); + } + + /** Check that a nested field table parameter correctly encodes and decodes to a byte buffer. */ + public void testNestedFieldTable() + { + byte[] testBytes = new byte[] { 0, 1, 2, 3, 4, 5 }; + + FieldTable outerTable = new FieldTable(); + FieldTable innerTable = new FieldTable(); + + // Put some stuff in the inner table. + innerTable.setBoolean("bool", true); + innerTable.setByte("byte", Byte.MAX_VALUE); + innerTable.setBytes("bytes", testBytes); + innerTable.setChar("char", 'c'); + innerTable.setDouble("double", Double.MAX_VALUE); + innerTable.setFloat("float", Float.MAX_VALUE); + innerTable.setInteger("int", Integer.MAX_VALUE); + innerTable.setLong("long", Long.MAX_VALUE); + innerTable.setShort("short", Short.MAX_VALUE); + innerTable.setString("string", "hello"); + innerTable.setString("null-string", null); + + // Put the inner table in the outer one. + outerTable.setFieldTable("innerTable", innerTable); + + // Write the outer table into the buffer. + 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(buffer); + + FieldTable extractedTable = extractedOuterTable.getFieldTable("innerTable"); + + Assert.assertEquals((Boolean) true, extractedTable.getBoolean("bool")); + Assert.assertEquals((Byte) Byte.MAX_VALUE, extractedTable.getByte("byte")); + assertBytesEqual(testBytes, extractedTable.getBytes("bytes")); + Assert.assertEquals((Character) 'c', extractedTable.getCharacter("char")); + Assert.assertEquals(Double.MAX_VALUE, extractedTable.getDouble("double")); + Assert.assertEquals(Float.MAX_VALUE, extractedTable.getFloat("float")); + Assert.assertEquals((Integer) Integer.MAX_VALUE, extractedTable.getInteger("int")); + Assert.assertEquals((Long) Long.MAX_VALUE, extractedTable.getLong("long")); + Assert.assertEquals((Short) Short.MAX_VALUE, extractedTable.getShort("short")); + Assert.assertEquals("hello", extractedTable.getString("string")); + Assert.assertEquals(null, extractedTable.getString("null-string")); + } + catch (AMQFrameDecodingException e) + { + fail("Failed to decode field table with nested inner table."); + } + } + + public void testValues() + { + FieldTable table = new FieldTable(); + table.setBoolean("bool", true); + table.setByte("byte", Byte.MAX_VALUE); + byte[] bytes = { 99, 98, 97, 96, 95 }; + table.setBytes("bytes", bytes); + table.setChar("char", 'c'); + table.setDouble("double", Double.MAX_VALUE); + table.setFloat("float", Float.MAX_VALUE); + table.setInteger("int", Integer.MAX_VALUE); + table.setLong("long", Long.MAX_VALUE); + table.setShort("short", Short.MAX_VALUE); + table.setString("string", "Hello"); + table.setString("null-string", null); + + table.setObject("object-bool", true); + table.setObject("object-byte", Byte.MAX_VALUE); + table.setObject("object-bytes", bytes); + table.setObject("object-char", 'c'); + table.setObject("object-double", Double.MAX_VALUE); + table.setObject("object-float", Float.MAX_VALUE); + table.setObject("object-int", Integer.MAX_VALUE); + table.setObject("object-long", Long.MAX_VALUE); + table.setObject("object-short", Short.MAX_VALUE); + table.setObject("object-string", "Hello"); + + try + { + table.setObject("Null-object", null); + fail("null values are not allowed"); + } + catch (AMQPInvalidClassException aice) + { + assertEquals("Null values are not allowed to be set", + AMQPInvalidClassException.INVALID_OBJECT_MSG + "null", aice.getMessage()); + } + + try + { + table.setObject("Unsupported-object", new Exception()); + fail("Non primitive values are not allowed"); + } + catch (AMQPInvalidClassException aice) + { + assertEquals("Non primitive values are not allowed to be set", + AMQPInvalidClassException.INVALID_OBJECT_MSG + Exception.class, aice.getMessage()); + } + + Assert.assertEquals((Boolean) true, table.getBoolean("bool")); + Assert.assertEquals((Byte) Byte.MAX_VALUE, table.getByte("byte")); + assertBytesEqual(bytes, table.getBytes("bytes")); + Assert.assertEquals((Character) 'c', table.getCharacter("char")); + Assert.assertEquals(Double.MAX_VALUE, table.getDouble("double")); + Assert.assertEquals(Float.MAX_VALUE, table.getFloat("float")); + Assert.assertEquals((Integer) Integer.MAX_VALUE, table.getInteger("int")); + Assert.assertEquals((Long) Long.MAX_VALUE, table.getLong("long")); + Assert.assertEquals((Short) Short.MAX_VALUE, table.getShort("short")); + Assert.assertEquals("Hello", table.getString("string")); + Assert.assertEquals(null, table.getString("null-string")); + + Assert.assertEquals(true, table.getObject("object-bool")); + Assert.assertEquals(Byte.MAX_VALUE, table.getObject("object-byte")); + assertBytesEqual(bytes, (byte[]) table.getObject("object-bytes")); + Assert.assertEquals('c', table.getObject("object-char")); + Assert.assertEquals(Double.MAX_VALUE, table.getObject("object-double")); + Assert.assertEquals(Float.MAX_VALUE, table.getObject("object-float")); + Assert.assertEquals(Integer.MAX_VALUE, table.getObject("object-int")); + Assert.assertEquals(Long.MAX_VALUE, table.getObject("object-long")); + Assert.assertEquals(Short.MAX_VALUE, table.getObject("object-short")); + Assert.assertEquals("Hello", table.getObject("object-string")); + } + + public void testwriteBuffer() + { + byte[] bytes = { 99, 98, 97, 96, 95 }; + + FieldTable table = new FieldTable(); + table.setBoolean("bool", true); + table.setByte("byte", Byte.MAX_VALUE); + + table.setBytes("bytes", bytes); + table.setChar("char", 'c'); + table.setDouble("double", Double.MAX_VALUE); + table.setFloat("float", Float.MAX_VALUE); + table.setInteger("int", Integer.MAX_VALUE); + table.setLong("long", Long.MAX_VALUE); + table.setShort("short", Short.MAX_VALUE); + table.setString("string", "hello"); + table.setString("null-string", null); + + final ByteBuffer buffer = ByteBuffer.allocate((int) table.getEncodedSize() + 4); // FIXME XXX: Is cast a problem? + + table.writeToBuffer(buffer); + + buffer.flip(); + + long length = buffer.getUnsignedInt(); + + FieldTable table2 = new FieldTable(buffer, length); + + Assert.assertEquals((Boolean) true, table2.getBoolean("bool")); + Assert.assertEquals((Byte) Byte.MAX_VALUE, table2.getByte("byte")); + assertBytesEqual(bytes, table2.getBytes("bytes")); + Assert.assertEquals((Character) 'c', table2.getCharacter("char")); + Assert.assertEquals(Double.MAX_VALUE, table2.getDouble("double")); + Assert.assertEquals(Float.MAX_VALUE, table2.getFloat("float")); + Assert.assertEquals((Integer) Integer.MAX_VALUE, table2.getInteger("int")); + Assert.assertEquals((Long) Long.MAX_VALUE, table2.getLong("long")); + Assert.assertEquals((Short) Short.MAX_VALUE, table2.getShort("short")); + Assert.assertEquals("hello", table2.getString("string")); + Assert.assertEquals(null, table2.getString("null-string")); + } + + public void testEncodingSize() + { + FieldTable result = new FieldTable(); + int size = 0; + + result.setBoolean("boolean", true); + size += 1 + EncodingUtils.encodedShortStringLength("boolean") + EncodingUtils.encodedBooleanLength(); + Assert.assertEquals(size, result.getEncodedSize()); + + result.setByte("byte", (byte) Byte.MAX_VALUE); + size += 1 + EncodingUtils.encodedShortStringLength("byte") + EncodingUtils.encodedByteLength(); + Assert.assertEquals(size, result.getEncodedSize()); + + byte[] _bytes = { 99, 98, 97, 96, 95 }; + + result.setBytes("bytes", _bytes); + size += 1 + EncodingUtils.encodedShortStringLength("bytes") + 4 + _bytes.length; + Assert.assertEquals(size, result.getEncodedSize()); + + result.setChar("char", (char) 'c'); + size += 1 + EncodingUtils.encodedShortStringLength("char") + EncodingUtils.encodedCharLength(); + Assert.assertEquals(size, result.getEncodedSize()); + + result.setDouble("double", (double) Double.MAX_VALUE); + size += 1 + EncodingUtils.encodedShortStringLength("double") + EncodingUtils.encodedDoubleLength(); + Assert.assertEquals(size, result.getEncodedSize()); + + result.setFloat("float", (float) Float.MAX_VALUE); + size += 1 + EncodingUtils.encodedShortStringLength("float") + EncodingUtils.encodedFloatLength(); + Assert.assertEquals(size, result.getEncodedSize()); + + result.setInteger("int", (int) Integer.MAX_VALUE); + size += 1 + EncodingUtils.encodedShortStringLength("int") + EncodingUtils.encodedIntegerLength(); + Assert.assertEquals(size, result.getEncodedSize()); + + result.setLong("long", (long) Long.MAX_VALUE); + size += 1 + EncodingUtils.encodedShortStringLength("long") + EncodingUtils.encodedLongLength(); + Assert.assertEquals(size, result.getEncodedSize()); + + result.setShort("short", (short) Short.MAX_VALUE); + size += 1 + EncodingUtils.encodedShortStringLength("short") + EncodingUtils.encodedShortLength(); + Assert.assertEquals(size, result.getEncodedSize()); + + result.setString("result", "Hello"); + size += 1 + EncodingUtils.encodedShortStringLength("result") + EncodingUtils.encodedLongStringLength("Hello"); + Assert.assertEquals(size, result.getEncodedSize()); + + result.setObject("object-bool", true); + size += 1 + EncodingUtils.encodedShortStringLength("object-bool") + EncodingUtils.encodedBooleanLength(); + Assert.assertEquals(size, result.getEncodedSize()); + + result.setObject("object-byte", Byte.MAX_VALUE); + size += 1 + EncodingUtils.encodedShortStringLength("object-byte") + EncodingUtils.encodedByteLength(); + Assert.assertEquals(size, result.getEncodedSize()); + + result.setObject("object-bytes", _bytes); + size += 1 + EncodingUtils.encodedShortStringLength("object-bytes") + 4 + _bytes.length; + Assert.assertEquals(size, result.getEncodedSize()); + + result.setObject("object-char", 'c'); + size += 1 + EncodingUtils.encodedShortStringLength("object-char") + EncodingUtils.encodedCharLength(); + Assert.assertEquals(size, result.getEncodedSize()); + + result.setObject("object-double", Double.MAX_VALUE); + size += 1 + EncodingUtils.encodedShortStringLength("object-double") + EncodingUtils.encodedDoubleLength(); + Assert.assertEquals(size, result.getEncodedSize()); + + result.setObject("object-float", Float.MAX_VALUE); + size += 1 + EncodingUtils.encodedShortStringLength("object-float") + EncodingUtils.encodedFloatLength(); + Assert.assertEquals(size, result.getEncodedSize()); + + result.setObject("object-int", Integer.MAX_VALUE); + size += 1 + EncodingUtils.encodedShortStringLength("object-int") + EncodingUtils.encodedIntegerLength(); + Assert.assertEquals(size, result.getEncodedSize()); + + result.setObject("object-long", Long.MAX_VALUE); + size += 1 + EncodingUtils.encodedShortStringLength("object-long") + EncodingUtils.encodedLongLength(); + Assert.assertEquals(size, result.getEncodedSize()); + + result.setObject("object-short", Short.MAX_VALUE); + size += 1 + EncodingUtils.encodedShortStringLength("object-short") + EncodingUtils.encodedShortLength(); + Assert.assertEquals(size, result.getEncodedSize()); + + } + + // public void testEncodingSize1() + // { + // PropertyFieldTable table = new PropertyFieldTable(); + // int length = 0; + // result.put("one", 1L); + // length = EncodingUtils.encodedShortStringLength("one"); + // length += 1 + EncodingUtils.encodedLongLength(); + // assertEquals(length, result.getEncodedSize()); + // + // result.put("two", 2L); + // length += EncodingUtils.encodedShortStringLength("two"); + // length += 1 + EncodingUtils.encodedLongLength(); + // assertEquals(length, result.getEncodedSize()); + // + // result.put("three", 3L); + // length += EncodingUtils.encodedShortStringLength("three"); + // length += 1 + EncodingUtils.encodedLongLength(); + // assertEquals(length, result.getEncodedSize()); + // + // result.put("four", 4L); + // length += EncodingUtils.encodedShortStringLength("four"); + // length += 1 + EncodingUtils.encodedLongLength(); + // assertEquals(length, result.getEncodedSize()); + // + // result.put("five", 5L); + // length += EncodingUtils.encodedShortStringLength("five"); + // length += 1 + EncodingUtils.encodedLongLength(); + // assertEquals(length, result.getEncodedSize()); + // + // //fixme should perhaps be expanded to incorporate all types. + // + // final ByteBuffer buffer = ByteBuffer.allocate((int) result.getEncodedSize()); // FIXME XXX: Is cast a problem? + // + // result.writeToBuffer(buffer); + // + // buffer.flip(); + // + // long length = buffer.getUnsignedInt(); + // + // try + // { + // PropertyFieldTable table2 = new PropertyFieldTable(buffer, length); + // + // Assert.assertEquals((Long) 1L, table2.getLong("one")); + // Assert.assertEquals((Long) 2L, table2.getLong("two")); + // Assert.assertEquals((Long) 3L, table2.getLong("three")); + // Assert.assertEquals((Long) 4L, table2.getLong("four")); + // Assert.assertEquals((Long) 5L, table2.getLong("five")); + // } + // catch (AMQFrameDecodingException e) + // { + // e.printStackTrace(); + // fail("PFT should be instantiated from bytes." + e.getCause()); + // } + // + // } + + /** + * Additional test for setObject + */ + public void testSetObject() + { + FieldTable table = new FieldTable(); + + // Try setting a non primative object + + try + { + table.setObject("value", this); + fail("Only primative values allowed in setObject"); + } + catch (AMQPInvalidClassException iae) + { + // normal path + } + // so length should be zero + Assert.assertEquals(0, table.getEncodedSize()); + } + + /** + * Additional test checkPropertyName doesn't accept Null + */ + public void testCheckPropertyNameasNull() + { + FieldTable table = new FieldTable(); + + try + { + table.setObject((String) null, "String"); + fail("Null property name is not allowed"); + } + catch (IllegalArgumentException iae) + { + // normal path + } + // so length should be zero + Assert.assertEquals(0, table.getEncodedSize()); + } + + /** + * Additional test checkPropertyName doesn't accept an empty String + */ + public void testCheckPropertyNameasEmptyString() + { + FieldTable table = new FieldTable(); + + try + { + table.setObject("", "String"); + fail("empty property name is not allowed"); + } + catch (IllegalArgumentException iae) + { + // normal path + } + // so length should be zero + Assert.assertEquals(0, table.getEncodedSize()); + } + + /** + * Additional test checkPropertyName doesn't accept an empty String + */ + public void testCheckPropertyNamehasMaxLength() + { + String oldVal = System.getProperty("STRICT_AMQP"); + System.setProperty("STRICT_AMQP", "true"); + FieldTable table = new FieldTable(); + + StringBuffer longPropertyName = new StringBuffer(129); + + for (int i = 0; i < 129; i++) + { + longPropertyName.append("x"); + } + + try + { + table.setObject(longPropertyName.toString(), "String"); + fail("property name must be < 128 characters"); + } + catch (IllegalArgumentException iae) + { + // normal path + } + // so length should be zero + Assert.assertEquals(0, table.getEncodedSize()); + if (oldVal != null) + { + System.setProperty("STRICT_AMQP", oldVal); + } + else + { + System.clearProperty("STRICT_AMQP"); + } + } + + /** + * Additional test checkPropertyName starts with a letter + */ + public void testCheckPropertyNameStartCharacterIsLetter() + { + String oldVal = System.getProperty("STRICT_AMQP"); + System.setProperty("STRICT_AMQP", "true"); + FieldTable table = new FieldTable(); + + // Try a name that starts with a number + try + { + table.setObject("1", "String"); + fail("property name must start with a letter"); + } + catch (IllegalArgumentException iae) + { + // normal path + } + // so length should be zero + Assert.assertEquals(0, table.getEncodedSize()); + if (oldVal != null) + { + System.setProperty("STRICT_AMQP", oldVal); + } + else + { + System.clearProperty("STRICT_AMQP"); + } + } + + /** + * Additional test checkPropertyName starts with a hash or a dollar + */ + public void testCheckPropertyNameStartCharacterIsHashorDollar() + { + String oldVal = System.getProperty("STRICT_AMQP"); + System.setProperty("STRICT_AMQP", "true"); + FieldTable table = new FieldTable(); + + // Try a name that starts with a number + try + { + table.setObject("#", "String"); + table.setObject("$", "String"); + } + catch (IllegalArgumentException iae) + { + fail("property name are allowed to start with # and $s"); + } + + if (oldVal != null) + { + System.setProperty("STRICT_AMQP", oldVal); + } + else + { + System.clearProperty("STRICT_AMQP"); + } + } + + /** + * Additional test to test the contents of the table + */ + public void testContents() + { + FieldTable table = new FieldTable(); + + table.setObject("StringProperty", "String"); + + Assert.assertEquals("String", table.getString("StringProperty")); + + // Test Clear + + table.clear(); + + checkEmpty(table); + } + + /** + * Test the contents of the sets + */ + public void testSets() + { + + FieldTable table = new FieldTable(); + + table.setObject("n1", "1"); + table.setObject("n2", "2"); + table.setObject("n3", "3"); + + Assert.assertEquals("1", table.getObject("n1")); + Assert.assertEquals("2", table.getObject("n2")); + Assert.assertEquals("3", table.getObject("n3")); + + } + + private void assertBytesEqual(byte[] expected, byte[] actual) + { + Assert.assertEquals(expected.length, actual.length); + + for (int index = 0; index < expected.length; index++) + { + Assert.assertEquals(expected[index], actual[index]); + } + } + + private void assertBytesNotEqual(byte[] expected, byte[] actual) + { + Assert.assertEquals(expected.length, actual.length); + + for (int index = 0; index < expected.length; index++) + { + Assert.assertFalse(expected[index] == actual[index]); + } + } + + public static junit.framework.Test suite() + { + return new junit.framework.TestSuite(PropertyFieldTableTest.class); + } + +} diff --git a/qpid/java/common/src/test/java/org/apache/qpid/framing/abstraction/MessagePublishInfoImplTest.java b/qpid/java/common/src/test/java/org/apache/qpid/framing/abstraction/MessagePublishInfoImplTest.java new file mode 100644 index 0000000000..3243136287 --- /dev/null +++ b/qpid/java/common/src/test/java/org/apache/qpid/framing/abstraction/MessagePublishInfoImplTest.java @@ -0,0 +1,99 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.abstraction; + +import junit.framework.TestCase; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.abstraction.MessagePublishInfoImpl; + +public class MessagePublishInfoImplTest extends TestCase +{ + MessagePublishInfoImpl _mpi; + final AMQShortString _exchange = new AMQShortString("exchange"); + final AMQShortString _routingKey = new AMQShortString("routingKey"); + + public void setUp() + { + _mpi = new MessagePublishInfoImpl(_exchange, true, true, _routingKey); + } + + /** Test that we can update the exchange value. */ + public void testExchange() + { + assertEquals(_exchange, _mpi.getExchange()); + AMQShortString newExchange = new AMQShortString("newExchange"); + //Check we can update the exchange + _mpi.setExchange(newExchange); + assertEquals(newExchange, _mpi.getExchange()); + //Ensure that the new exchange doesn't equal the old one + assertFalse(_exchange.equals(_mpi.getExchange())); + } + + /** + * Check that the immedate value is set correctly and defaulted correctly + */ + public void testIsImmediate() + { + //Check that the set value is correct + assertTrue("Set value for immediate not as expected", _mpi.isImmediate()); + + MessagePublishInfoImpl mpi = new MessagePublishInfoImpl(); + + assertFalse("Default value for immediate should be false", mpi.isImmediate()); + + mpi.setImmediate(true); + + assertTrue("Updated value for immediate not as expected", mpi.isImmediate()); + + } + + /** + * Check that the mandatory value is set correctly and defaulted correctly + */ + public void testIsMandatory() + { + assertTrue("Set value for mandatory not as expected", _mpi.isMandatory()); + + MessagePublishInfoImpl mpi = new MessagePublishInfoImpl(); + + assertFalse("Default value for mandatory should be false", mpi.isMandatory()); + + mpi.setMandatory(true); + + assertTrue("Updated value for mandatory not as expected", mpi.isMandatory()); + } + + /** + * Check that the routingKey value is perserved + */ + public void testRoutingKey() + { + assertEquals(_routingKey, _mpi.getRoutingKey()); + AMQShortString newRoutingKey = new AMQShortString("newRoutingKey"); + + //Check we can update the routingKey + _mpi.setRoutingKey(newRoutingKey); + assertEquals(newRoutingKey, _mpi.getRoutingKey()); + //Ensure that the new routingKey doesn't equal the old one + assertFalse(_routingKey.equals(_mpi.getRoutingKey())); + + } +} diff --git a/qpid/java/common/src/test/java/org/apache/qpid/pool/ReferenceCountingExecutorServiceTest.java b/qpid/java/common/src/test/java/org/apache/qpid/pool/ReferenceCountingExecutorServiceTest.java new file mode 100644 index 0000000000..35998de3a1 --- /dev/null +++ b/qpid/java/common/src/test/java/org/apache/qpid/pool/ReferenceCountingExecutorServiceTest.java @@ -0,0 +1,159 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.HashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +import junit.framework.TestCase; + + +public class ReferenceCountingExecutorServiceTest extends TestCase +{ + + + private ReferenceCountingExecutorService _executorService = ReferenceCountingExecutorService.getInstance(); // Class under test + private ThreadFactory _beforeExecutorThreadFactory; + + + @Override + protected void setUp() throws Exception + { + super.setUp(); + _beforeExecutorThreadFactory = _executorService.getThreadFactory(); + } + + @Override + protected void tearDown() throws Exception + { + super.tearDown(); + _executorService.setThreadFactory(_beforeExecutorThreadFactory); + } + + + + /** + * Tests that the ReferenceCountingExecutorService correctly manages the reference count. + */ + public void testReferenceCounting() throws Exception + { + final int countBefore = _executorService.getReferenceCount(); + + try + { + _executorService.acquireExecutorService(); + _executorService.acquireExecutorService(); + + assertEquals("Reference count should now be +2", countBefore + 2, _executorService.getReferenceCount()); + } + finally + { + _executorService.releaseExecutorService(); + _executorService.releaseExecutorService(); + } + assertEquals("Reference count should have returned to the initial value", countBefore, _executorService.getReferenceCount()); + } + + /** + * Tests that the executor creates and executes a task using the default thread pool. + */ + public void testExecuteCommandWithDefaultExecutorThreadFactory() throws Exception + { + final CountDownLatch latch = new CountDownLatch(1); + final Set<ThreadGroup> threadGroups = new HashSet<ThreadGroup>(); + + _executorService.acquireExecutorService(); + + try + { + _executorService.getPool().execute(createRunnable(latch, threadGroups)); + + latch.await(3, TimeUnit.SECONDS); + + assertTrue("Expect that executor created a thread using default thread factory", + threadGroups.contains(Thread.currentThread().getThreadGroup())); + } + finally + { + _executorService.releaseExecutorService(); + } + } + + /** + * Tests that the executor creates and executes a task using an overridden thread pool. + */ + public void testExecuteCommandWithOverriddenExecutorThreadFactory() throws Exception + { + final CountDownLatch latch = new CountDownLatch(1); + final ThreadGroup expectedThreadGroup = new ThreadGroup("junit"); + _executorService.setThreadFactory(new ThreadGroupChangingThreadFactory(expectedThreadGroup)); + _executorService.acquireExecutorService(); + + final Set<ThreadGroup> threadGroups = new HashSet<ThreadGroup>(); + + try + { + _executorService.getPool().execute(createRunnable(latch, threadGroups)); + + latch.await(3, TimeUnit.SECONDS); + + assertTrue("Expect that executor created a thread using overridden thread factory", + threadGroups.contains(expectedThreadGroup)); + } + finally + { + _executorService.releaseExecutorService(); + } + } + + private Runnable createRunnable(final CountDownLatch latch, final Set<ThreadGroup> threadGroups) + { + return new Runnable() + { + + public void run() + { + threadGroups.add(Thread.currentThread().getThreadGroup()); + latch.countDown(); + } + + }; + } + + private final class ThreadGroupChangingThreadFactory implements ThreadFactory + { + private final ThreadGroup _newGroup; + + private ThreadGroupChangingThreadFactory(final ThreadGroup newGroup) + { + this._newGroup = newGroup; + } + + public Thread newThread(Runnable r) + { + return new Thread(_newGroup, r); + } + } + +} diff --git a/qpid/java/common/src/test/java/org/apache/qpid/session/TestSession.java b/qpid/java/common/src/test/java/org/apache/qpid/session/TestSession.java new file mode 100644 index 0000000000..aafc91b03b --- /dev/null +++ b/qpid/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/qpid/java/common/src/test/java/org/apache/qpid/test/utils/QpidTestCase.java b/qpid/java/common/src/test/java/org/apache/qpid/test/utils/QpidTestCase.java new file mode 100644 index 0000000000..8b470d555e --- /dev/null +++ b/qpid/java/common/src/test/java/org/apache/qpid/test/utils/QpidTestCase.java @@ -0,0 +1,130 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.test.utils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestCase; +import junit.framework.TestResult; + +import org.apache.log4j.Logger; + +public class QpidTestCase extends TestCase +{ + protected static final Logger _logger = Logger.getLogger(QpidTestCase.class); + + /** + * Some tests are excluded when the property test.excludes is set to true. + * An exclusion list is either a file (prop test.excludesfile) which contains one test name + * to be excluded per line or a String (prop test.excludeslist) where tests to be excluded are + * separated by " ". Excluded tests are specified following the format: + * className#testName where className is the class of the test to be + * excluded and testName is the name of the test to be excluded. + * className#* excludes all the tests of the specified class. + */ + static + { + if (Boolean.getBoolean("test.exclude")) + { + _logger.info("Some tests should be excluded, building the exclude list"); + String exclusionListURIs = System.getProperties().getProperty("test.excludefiles", ""); + String exclusionListString = System.getProperties().getProperty("test.excludelist", ""); + List<String> exclusionList = new ArrayList<String>(); + + for (String uri : exclusionListURIs.split("\\s+")) + { + File file = new File(uri); + if (file.exists()) + { + _logger.info("Using exclude file: " + uri); + try + { + BufferedReader in = new BufferedReader(new FileReader(file)); + String excludedTest = in.readLine(); + do + { + exclusionList.add(excludedTest); + excludedTest = in.readLine(); + } + while (excludedTest != null); + } + catch (IOException e) + { + _logger.warn("Exception when reading exclusion list", e); + } + } + } + + if (!exclusionListString.equals("")) + { + _logger.info("Using excludeslist: " + exclusionListString); + for (String test : exclusionListString.split("\\s+")) + { + exclusionList.add(test); + } + } + + _exclusionList = exclusionList; + } + } + + protected static final String MS_CLASS_NAME_KEY = "messagestore.class.name"; + protected static final String MEMORY_STORE_CLASS_NAME = "org.apache.qpid.server.store.MemoryMessageStore"; + + private static List<String> _exclusionList; + + public QpidTestCase() + { + this("QpidTestCase"); + } + + public QpidTestCase(String name) + { + super(name); + } + + public void run(TestResult testResult) + { + if (_exclusionList != null && (_exclusionList.contains(getClass().getPackage().getName() + ".*") || + _exclusionList.contains(getClass().getName() + "#*") || + _exclusionList.contains(getClass().getName() + "#" + getName()))) + { + _logger.info("Test: " + getName() + " is excluded"); + testResult.endTest(this); + } + else + { + super.run(testResult); + } + } + + public String getTestProfileMessageStoreClassName() + { + String storeClass = System.getProperty(MS_CLASS_NAME_KEY); + + return storeClass != null ? storeClass : MEMORY_STORE_CLASS_NAME ; + } +} diff --git a/qpid/java/common/src/test/java/org/apache/qpid/thread/ThreadFactoryTest.java b/qpid/java/common/src/test/java/org/apache/qpid/thread/ThreadFactoryTest.java new file mode 100644 index 0000000000..7b0f93700a --- /dev/null +++ b/qpid/java/common/src/test/java/org/apache/qpid/thread/ThreadFactoryTest.java @@ -0,0 +1,104 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.thread; + +import junit.framework.TestCase; + +/** + * Tests the ThreadFactory. + */ +public class ThreadFactoryTest extends TestCase +{ + public void testThreadFactory() + { + Class<? extends ThreadFactory> threadFactoryClass = null; + try + { + threadFactoryClass = Class.forName(System.getProperty("qpid.thread_factory", + "org.apache.qpid.thread.DefaultThreadFactory")).asSubclass(ThreadFactory.class); + } + // If the thread factory class was wrong it will flagged way before it gets here. + catch(Exception e) + { + fail("Invalid thread factory class"); + } + + assertEquals(threadFactoryClass, Threading.getThreadFactory().getClass()); + } + + /** + * Tests creating a thread without a priority. Also verifies that the factory sets the + * uncaught exception handler so uncaught exceptions are logged to SLF4J. + */ + public void testCreateThreadWithDefaultPriority() + { + Runnable r = createRunnable(); + + Thread t = null; + try + { + t = Threading.getThreadFactory().createThread(r); + } + catch(Exception e) + { + fail("Error creating thread using Qpid thread factory"); + } + + assertNotNull(t); + assertEquals(Thread.NORM_PRIORITY, t.getPriority()); + assertTrue(t.getUncaughtExceptionHandler() instanceof LoggingUncaughtExceptionHandler); + } + + /** + * Tests creating thread with a priority. Also verifies that the factory sets the + * uncaught exception handler so uncaught exceptions are logged to SLF4J. + */ + public void testCreateThreadWithSpecifiedPriority() + { + Runnable r = createRunnable(); + + Thread t = null; + try + { + t = Threading.getThreadFactory().createThread(r, 4); + } + catch(Exception e) + { + fail("Error creating thread using Qpid thread factory"); + } + + assertNotNull(t); + assertEquals(4, t.getPriority()); + assertTrue(t.getUncaughtExceptionHandler() instanceof LoggingUncaughtExceptionHandler); + } + + private Runnable createRunnable() + { + Runnable r = new Runnable(){ + + public void run(){ + + } + }; + return r; + } +} diff --git a/qpid/java/common/src/test/java/org/apache/qpid/transport/ConnectionTest.java b/qpid/java/common/src/test/java/org/apache/qpid/transport/ConnectionTest.java new file mode 100644 index 0000000000..375a326654 --- /dev/null +++ b/qpid/java/common/src/test/java/org/apache/qpid/transport/ConnectionTest.java @@ -0,0 +1,446 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.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.util.ArrayList; +import java.util.List; +import java.util.Collections; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.io.IOException; + +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>(); + private List<MessageTransfer> incoming = new ArrayList<MessageTransfer>(); + + private IoAcceptor _ioa = null; + + + protected void setUp() throws Exception + { + super.setUp(); + + port = AvailablePortFinder.getNextAvailable(12000); + } + + protected void tearDown() throws Exception + { + if (_ioa != null) + { + _ioa.close(); + } + + super.tearDown(); + } + + public void opened(Session ssn) {} + + public void resumed(Session ssn) {} + + public void message(final Session ssn, MessageTransfer xfr) + { + if (queue) + { + messages.add(xfr); + ssn.processed(xfr); + return; + } + + String body = xfr.getBodyString(); + + if (body.startsWith("CLOSE")) + { + ssn.getConnection().close(); + } + else if (body.startsWith("DELAYED_CLOSE")) + { + ssn.processed(xfr); + new Thread() + { + public void run() + { + try + { + sleep(3000); + } + catch (InterruptedException e) + { + throw new RuntimeException(e); + } + ssn.getConnection().close(); + } + }.start(); + } + else if (body.startsWith("ECHO")) + { + int id = xfr.getId(); + ssn.invoke(xfr); + ssn.processed(id); + } + else if (body.startsWith("SINK")) + { + ssn.processed(xfr); + } + else if (body.startsWith("DROP")) + { + // do nothing + } + else if (body.startsWith("EXCP")) + { + ExecutionException exc = new ExecutionException(); + exc.setDescription("intentional exception for testing"); + ssn.invoke(exc); + ssn.close(); + } + else + { + throw new IllegalArgumentException + ("unrecognized message: " + body); + } + } + + public void exception(Session ssn, SessionException exc) + { + throw exc; + } + + public void closed(Session ssn) {} + + private void send(Session ssn, String msg) + { + send(ssn, msg, false); + } + + private void send(Session ssn, String msg, boolean sync) + { + ssn.messageTransfer + ("xxx", MessageAcceptMode.NONE, MessageAcquireMode.PRE_ACQUIRED, + null, msg, sync ? SYNC : NONE); + } + + private Connection connect(final CountDownLatch closed) + { + Connection conn = new Connection(); + conn.addConnectionListener(new ConnectionListener() + { + public void opened(Connection conn) {} + public void exception(Connection conn, ConnectionException exc) + { + exc.printStackTrace(); + } + public void closed(Connection conn) + { + if (closed != null) + { + closed.countDown(); + } + } + }); + conn.connect("localhost", port, null, "guest", "guest", false); + return conn; + } + + public void testProtocolNegotiationExceptionOverridesCloseException() throws Exception + { + // Force os.name to be windows to exercise code in IoReceiver + // that looks for the value of os.name + System.setProperty("os.name","windows"); + + // Start server as 0-9 to froce a ProtocolVersionException + startServer(new ProtocolHeader(1, 0, 9)); + + CountDownLatch closed = new CountDownLatch(1); + + try + { + connect(closed); + fail("ProtocolVersionException expected"); + } + catch (ProtocolVersionException pve) + { + //Expected code path + } + catch (Exception e) + { + fail("ProtocolVersionException expected. Got:" + e.getMessage()); + } + } + + private void startServer() + { + startServer(new ProtocolHeader(1, 0, 10)); + } + + private void startServer(final ProtocolHeader protocolHeader) + { + ConnectionDelegate server = new ServerDelegate() + { + @Override + public void init(Connection conn, ProtocolHeader hdr) + { + conn.send(protocolHeader); + List<Object> utf8 = new ArrayList<Object>(); + utf8.add("utf8"); + conn.connectionStart(null, Collections.EMPTY_LIST, utf8); + } + + @Override + public Session getSession(Connection conn, SessionAttach atc) + { + Session ssn = super.getSession(conn, atc); + ssn.setSessionListener(ConnectionTest.this); + return ssn; + } + }; + + try + { + _ioa = new IoAcceptor("localhost", port, ConnectionBinding.get(server)); + } + catch (IOException e) + { + e.printStackTrace(); + fail("Unable to start Server for test due to:" + e.getMessage()); + } + + _ioa.start(); + } + + public void testClosedNotificationAndWriteToClosed() throws Exception + { + startServer(); + + CountDownLatch closed = new CountDownLatch(1); + Connection conn = connect(closed); + + Session ssn = conn.createSession(1); + send(ssn, "CLOSE"); + + if (!closed.await(3, TimeUnit.SECONDS)) + { + fail("never got notified of connection close"); + } + + try + { + conn.connectionCloseOk(); + fail("writing to a closed socket succeeded"); + } + catch (TransportException e) + { + // expected + } + } + + 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 + { + startServer(); + + Connection conn = new Connection(); + conn.addConnectionListener(new FailoverConnectionListener()); + conn.connect("localhost", port, null, "guest", "guest"); + Session ssn = conn.createSession(1); + ssn.setSessionListener(new TestSessionListener()); + + send(ssn, "SINK 0"); + send(ssn, "ECHO 1"); + send(ssn, "ECHO 2"); + + ssn.sync(); + + String[] msgs = { "DROP 3", "DROP 4", "DROP 5", "CLOSE 6", "SINK 7" }; + for (String m : msgs) + { + send(ssn, m); + } + + ssn.sync(); + + assertEquals(msgs.length, messages.size()); + for (int i = 0; i < msgs.length; i++) + { + assertEquals(msgs[i], messages.get(i).getBodyString()); + } + + queue = false; + + send(ssn, "ECHO 8"); + send(ssn, "ECHO 9"); + + synchronized (incoming) + { + Waiter w = new Waiter(incoming, 30000); + while (w.hasTime() && incoming.size() < 4) + { + w.await(); + } + + assertEquals(4, incoming.size()); + assertEquals("ECHO 1", incoming.get(0).getBodyString()); + assertEquals(0, incoming.get(0).getId()); + assertEquals("ECHO 2", incoming.get(1).getBodyString()); + assertEquals(1, incoming.get(1).getId()); + assertEquals("ECHO 8", incoming.get(2).getBodyString()); + assertEquals(0, incoming.get(0).getId()); + assertEquals("ECHO 9", incoming.get(3).getBodyString()); + assertEquals(1, incoming.get(1).getId()); + } + } + + public void testResumeEmptyReplayBuffer() throws InterruptedException + { + startServer(); + + Connection conn = new Connection(); + conn.addConnectionListener(new FailoverConnectionListener()); + conn.connect("localhost", port, null, "guest", "guest"); + Session ssn = conn.createSession(1); + ssn.setSessionListener(new TestSessionListener()); + + send(ssn, "SINK 0"); + send(ssn, "SINK 1"); + send(ssn, "DELAYED_CLOSE 2"); + ssn.sync(); + Thread.sleep(6000); + send(ssn, "SINK 3"); + ssn.sync(); + System.out.println(messages); + assertEquals(1, messages.size()); + assertEquals("SINK 3", messages.get(0).getBodyString()); + } + + public void testFlushExpected() throws InterruptedException + { + startServer(); + + Connection conn = new Connection(); + conn.connect("localhost", port, null, "guest", "guest"); + Session ssn = conn.createSession(); + ssn.sessionFlush(EXPECTED); + send(ssn, "SINK 0"); + ssn.sessionFlush(EXPECTED); + send(ssn, "SINK 1"); + ssn.sync(); + } + + public void testHeartbeat() + { + startServer(); + Connection conn = new Connection(); + conn.connect("localhost", port, null, "guest", "guest"); + conn.connectionHeartbeat(); + conn.close(); + } + + public void testExecutionExceptionInvoke() throws Exception + { + startServer(); + + Connection conn = new Connection(); + conn.connect("localhost", port, null, "guest", "guest"); + Session ssn = conn.createSession(); + send(ssn, "EXCP 0"); + Thread.sleep(3000); + try + { + send(ssn, "SINK 1"); + } + catch (SessionException exc) + { + assertNotNull(exc.getException()); + } + } + + public void testExecutionExceptionSync() throws Exception + { + startServer(); + + Connection conn = new Connection(); + conn.connect("localhost", port, null, "guest", "guest"); + Session ssn = conn.createSession(); + send(ssn, "EXCP 0", true); + try + { + ssn.sync(); + fail("this should have failed"); + } + catch (SessionException exc) + { + assertNotNull(exc.getException()); + } + } + +} diff --git a/qpid/java/common/src/test/java/org/apache/qpid/transport/GenTest.java b/qpid/java/common/src/test/java/org/apache/qpid/transport/GenTest.java new file mode 100644 index 0000000000..512a0a29a6 --- /dev/null +++ b/qpid/java/common/src/test/java/org/apache/qpid/transport/GenTest.java @@ -0,0 +1,44 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 junit.framework.TestCase; + +/** + * GenTest + * + */ + +public class GenTest extends TestCase +{ + + public void testBooleans() + { + QueueDeclare qd = new QueueDeclare().queue("test-queue").durable(false); + assertEquals(qd.getQueue(), "test-queue"); + assertFalse("durable should be false", qd.getDurable()); + qd.setDurable(true); + assertTrue("durable should be true", qd.getDurable()); + qd.setDurable(false); + assertFalse("durable should be false again", qd.getDurable()); + } + +} diff --git a/qpid/java/common/src/test/java/org/apache/qpid/transport/RangeSetTest.java b/qpid/java/common/src/test/java/org/apache/qpid/transport/RangeSetTest.java new file mode 100644 index 0000000000..ad45d00e46 --- /dev/null +++ b/qpid/java/common/src/test/java/org/apache/qpid/transport/RangeSetTest.java @@ -0,0 +1,238 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import junit.framework.TestCase; + +import static org.apache.qpid.util.Serial.*; + +/** + * RangeSetTest + * + */ + +public class RangeSetTest extends TestCase +{ + + private void check(RangeSet ranges) + { + List<Integer> posts = new ArrayList<Integer>(); + for (Range range : ranges) + { + posts.add(range.getLower()); + posts.add(range.getUpper()); + } + + List<Integer> sorted = new ArrayList<Integer>(posts); + Collections.sort(sorted, COMPARATOR); + + assertEquals(posts, sorted); + + int idx = 1; + while (idx + 1 < posts.size()) + { + assertTrue(!eq(posts.get(idx) + 1, posts.get(idx+1))); + idx += 2; + } + } + + public void test1() + { + RangeSet ranges = new RangeSet(); + ranges.add(5, 10); + check(ranges); + ranges.add(15, 20); + check(ranges); + ranges.add(23, 25); + check(ranges); + ranges.add(12, 14); + check(ranges); + ranges.add(0, 1); + check(ranges); + ranges.add(3, 11); + check(ranges); + } + + public void test2() + { + RangeSet rs = new RangeSet(); + check(rs); + + rs.add(1); + assertTrue(rs.includes(1)); + assertTrue(!rs.includes(2)); + assertTrue(!rs.includes(0)); + check(rs); + + rs.add(2); + assertTrue(!rs.includes(0)); + assertTrue(rs.includes(1)); + assertTrue(rs.includes(2)); + assertTrue(!rs.includes(3)); + check(rs); + + rs.add(0); + + assertTrue(!rs.includes(-1)); + assertTrue(rs.includes(0)); + assertTrue(rs.includes(1)); + assertTrue(rs.includes(2)); + assertTrue(!rs.includes(3)); + check(rs); + + rs.add(37); + + assertTrue(!rs.includes(-1)); + assertTrue(rs.includes(0)); + assertTrue(rs.includes(1)); + assertTrue(rs.includes(2)); + assertTrue(!rs.includes(3)); + assertTrue(!rs.includes(36)); + assertTrue(rs.includes(37)); + assertTrue(!rs.includes(38)); + check(rs); + + rs.add(-1); + check(rs); + + rs.add(-3); + check(rs); + + rs.add(1, 20); + assertTrue(!rs.includes(21)); + assertTrue(rs.includes(20)); + check(rs); + } + + public void testAddSelf() + { + RangeSet a = new RangeSet(); + a.add(0, 8); + check(a); + a.add(0, 8); + check(a); + assertEquals(a.size(), 1); + Range range = a.iterator().next(); + assertEquals(range.getLower(), 0); + assertEquals(range.getUpper(), 8); + } + + public void testIntersect1() + { + Range a = new Range(0, 10); + Range b = new Range(9, 20); + Range i1 = a.intersect(b); + Range i2 = b.intersect(a); + assertEquals(i1.getUpper(), 10); + assertEquals(i2.getUpper(), 10); + assertEquals(i1.getLower(), 9); + assertEquals(i2.getLower(), 9); + } + + public void testIntersect2() + { + Range a = new Range(0, 10); + Range b = new Range(11, 20); + assertNull(a.intersect(b)); + assertNull(b.intersect(a)); + } + + public void testIntersect3() + { + Range a = new Range(0, 10); + Range b = new Range(3, 5); + Range i1 = a.intersect(b); + Range i2 = b.intersect(a); + assertEquals(i1.getUpper(), 5); + assertEquals(i2.getUpper(), 5); + assertEquals(i1.getLower(), 3); + assertEquals(i2.getLower(), 3); + } + + public void testSubtract1() + { + Range a = new Range(0, 10); + assertTrue(a.subtract(a).isEmpty()); + } + + public void testSubtract2() + { + Range a = new Range(0, 10); + Range b = new Range(20, 30); + List<Range> ranges = a.subtract(b); + assertEquals(ranges.size(), 1); + Range d = ranges.get(0); + assertEquals(d.getLower(), a.getLower()); + assertEquals(d.getUpper(), a.getUpper()); + } + + public void testSubtract3() + { + Range a = new Range(20, 30); + Range b = new Range(0, 10); + List<Range> ranges = a.subtract(b); + assertEquals(ranges.size(), 1); + Range d = ranges.get(0); + assertEquals(d.getLower(), a.getLower()); + assertEquals(d.getUpper(), a.getUpper()); + } + + public void testSubtract4() + { + Range a = new Range(0, 10); + Range b = new Range(3, 5); + List<Range> ranges = a.subtract(b); + assertEquals(ranges.size(), 2); + Range low = ranges.get(0); + Range high = ranges.get(1); + assertEquals(low.getLower(), 0); + assertEquals(low.getUpper(), 2); + assertEquals(high.getLower(), 6); + assertEquals(high.getUpper(), 10); + } + + public void testSubtract5() + { + Range a = new Range(0, 10); + Range b = new Range(3, 20); + List<Range> ranges = a.subtract(b); + assertEquals(ranges.size(), 1); + Range d = ranges.get(0); + assertEquals(d.getLower(), 0); + assertEquals(d.getUpper(), 2); + } + + public void testSubtract6() + { + Range a = new Range(0, 10); + Range b = new Range(-10, 5); + List<Range> ranges = a.subtract(b); + assertEquals(ranges.size(), 1); + Range d = ranges.get(0); + assertEquals(d.getLower(), 6); + assertEquals(d.getUpper(), 10); + } + +} diff --git a/qpid/java/common/src/test/java/org/apache/qpid/transport/TestNetworkDriver.java b/qpid/java/common/src/test/java/org/apache/qpid/transport/TestNetworkDriver.java new file mode 100644 index 0000000000..957a7190ee --- /dev/null +++ b/qpid/java/common/src/test/java/org/apache/qpid/transport/TestNetworkDriver.java @@ -0,0 +1,133 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.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; + +/** + * 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 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; + + public TestNetworkDriver() + { + } + + public void bind(int port, InetAddress[] addresses, ProtocolEngineFactory protocolFactory, + NetworkDriverConfiguration config, SSLContextFactory sslFactory) throws BindException + { + + } + + public SocketAddress getLocalAddress() + { + return (_localAddress != null) ? _localAddress : new InetSocketAddress(_localHost, _port); + } + + public SocketAddress getRemoteAddress() + { + return (_remoteAddress != null) ? _remoteAddress : new InetSocketAddress(_remoteHost, _port); + } + + 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) + { + _port = port; + } + + public int getPort() + { + return _port; + } + + public void setLocalHost(String host) + { + _localHost = host; + } + + public void setRemoteHost(String host) + { + _remoteHost = host; + } + + public void setLocalAddress(SocketAddress address) + { + _localAddress = address; + } + + public void setRemoteAddress(SocketAddress address) + { + _remoteAddress = address; + } +} diff --git a/qpid/java/common/src/test/java/org/apache/qpid/transport/codec/BBEncoderTest.java b/qpid/java/common/src/test/java/org/apache/qpid/transport/codec/BBEncoderTest.java new file mode 100644 index 0000000000..79bf184fe2 --- /dev/null +++ b/qpid/java/common/src/test/java/org/apache/qpid/transport/codec/BBEncoderTest.java @@ -0,0 +1,47 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.codec; + +import junit.framework.TestCase; + +import java.nio.ByteBuffer; + +/** + * BBEncoderTest + * + */ + +public class BBEncoderTest extends TestCase +{ + + public void testGrow() + { + BBEncoder enc = new BBEncoder(4); + enc.writeInt32(0xDEADBEEF); + ByteBuffer buf = enc.buffer(); + assertEquals(0xDEADBEEF, buf.getInt(0)); + enc.writeInt32(0xBEEFDEAD); + buf = enc.buffer(); + assertEquals(0xDEADBEEF, buf.getInt(0)); + assertEquals(0xBEEFDEAD, buf.getInt(4)); + } + +} diff --git a/qpid/java/common/src/test/java/org/apache/qpid/transport/network/mina/MINANetworkDriverTest.java b/qpid/java/common/src/test/java/org/apache/qpid/transport/network/mina/MINANetworkDriverTest.java new file mode 100644 index 0000000000..fc8e689ca4 --- /dev/null +++ b/qpid/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/qpid/java/common/src/test/java/org/apache/qpid/util/CommandLineParserTest.java b/qpid/java/common/src/test/java/org/apache/qpid/util/CommandLineParserTest.java new file mode 100644 index 0000000000..942901f1c0 --- /dev/null +++ b/qpid/java/common/src/test/java/org/apache/qpid/util/CommandLineParserTest.java @@ -0,0 +1,554 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util; + +import junit.framework.*; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Properties; + +/** + * Unit tests the {@link CommandLineParser} class. + * + * <p><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Check that parsing a single flag works ok. + * <tr><td> Check that parsing multiple flags condensed together works ok. + * <tr><td> Check that parsing an option with a space between it and its argument works ok. + * <tr><td> Check that parsing an option with no space between it and its argument works ok. + * <tr><td> Check that parsing an option with specific argument format works ok. + * <tr><td> Check that parsing an option with specific argument format fails on bad argument. + * <tr><td> Check that parsing a flag condensed together with an option fails. + * <tr><td> Check that parsing a free argument works ok. + * <tr><td> Check that parsing a free argument with specific format works ok. + * <tr><td> Check that parsing a free argument with specific format fails on bad argument. + * <tr><td> Check that parsing a mandatory option works ok. + * <tr><td> Check that parsing a mandatory free argument works ok. + * <tr><td> Check that parsing a mandatory option fails when no option is set. + * <tr><td> Check that parsing a mandatory free argument fails when no argument is specified. + * <tr><td> Check that parsing an unknown option works when unknowns not errors. + * <tr><td> Check that parsing an unknown flag fails when unknowns are to be reported as errors. + * <tr><td> Check that parsing an unknown option fails when unknowns are to be reported as errors. + * <tr><td> Check that get errors returns a string on errors. + * <tr><td> Check that get errors returns an empty string on no errors. + * <tr><td> Check that get usage returns a string. + * <tr><td> Check that get options in force returns an empty string before parsing. + * <tr><td> Check that get options in force return a non-empty string after parsing. + * </table> + */ +public class CommandLineParserTest extends TestCase +{ + private static final Logger log = LoggerFactory.getLogger(CommandLineParserTest.class); + + public CommandLineParserTest(String name) + { + super(name); + } + + /** + * Compile all the tests for the default test implementation of a traversable state into a test suite. + */ + public static Test suite() + { + // Build a new test suite + TestSuite suite = new TestSuite("CommandLineParser Tests"); + + // Add all the tests defined in this class (using the default constructor) + suite.addTestSuite(CommandLineParserTest.class); + + return suite; + } + + /** Check that get errors returns an empty string on no errors. */ + public void testGetErrorsReturnsEmptyStringOnNoErrors() throws Exception + { + // Create a command line parser for some flags and options. + CommandLineParser parser = + new CommandLineParser( + new String[][] + { + { "t1", "Test Flag 1." }, + { "t2", "Test Option 2.", "test" }, + { "t3", "Test Option 3.", "test", "true" }, + { "t4", "Test Option 4.", "test", null, "^test$" } + }); + + // Do some legal parsing. + parser.parseCommandLine(new String[] { "-t1", "-t2test", "-t3test", "-t4test" }); + + // Check that the get errors message returns an empty string. + assertTrue("The errors method did not return an empty string.", "".equals(parser.getErrors())); + } + + /** Check that get errors returns a string on errors. */ + public void testGetErrorsReturnsStringOnErrors() throws Exception + { + // Create a command line parser for some flags and options. + CommandLineParser parser = + new CommandLineParser( + new String[][] + { + { "t1", "Test Flag 1." }, + { "t2", "Test Option 2.", "test" }, + { "t3", "Test Option 3.", "test", "true" }, + { "t4", "Test Option 4.", "test", null, "^test$" } + }); + + try + { + // Do some illegal parsing. + parser.parseCommandLine(new String[] { "-t1", "-t1t2test", "-t4fail" }); + } + catch (IllegalArgumentException e) + { } + + // Check that the get errors message returns a string. + assertTrue("The errors method returned an empty string.", + !((parser.getErrors() == null) || "".equals(parser.getErrors()))); + + } + + /** Check that get options in force returns an empty string before parsing. */ + public void testGetOptionsInForceReturnsEmptyStringBeforeParsing() throws Exception + { + // Create a command line parser for some flags and options. + CommandLineParser parser = + new CommandLineParser( + new String[][] + { + { "t1", "Test Flag 1." }, + { "t2", "Test Option 2.", "test" }, + { "t3", "Test Option 3.", "test", "true" }, + { "t4", "Test Option 4.", "test", null, "^test$" } + }); + + // Check that the options in force method returns an empty string. + assertTrue("The options in force method did not return an empty string.", "".equals(parser.getOptionsInForce())); + } + + /** Check that get options in force return a non-empty string after parsing. */ + public void testGetOptionsInForceReturnsNonEmptyStringAfterParsing() throws Exception + { + // Create a command line parser for some flags and options. + CommandLineParser parser = + new CommandLineParser( + new String[][] + { + { "t1", "Test Flag 1." }, + { "t2", "Test Option 2.", "test" }, + { "t3", "Test Option 3.", "test", "true" }, + { "t4", "Test Option 4.", "test", null, "^test$" } + }); + + // Do some parsing. + parser.parseCommandLine(new String[] { "-t1", "-t2test", "-t3test", "-t4test" }); + + // Check that the options in force method returns a string. + assertTrue("The options in force method did not return a non empty string.", + !((parser.getOptionsInForce() == null) || "".equals(parser.getOptionsInForce()))); + } + + /** Check that get usage returns a string. */ + public void testGetUsageReturnsString() throws Exception + { + // Create a command line parser for some flags and options. + CommandLineParser parser = + new CommandLineParser( + new String[][] + { + { "t1", "Test Flag 1." }, + { "t2", "Test Option 2.", "test" }, + { "t3", "Test Option 3.", "test", "true" }, + { "t4", "Test Option 4.", "test", null, "^test$" } + }); + + // Check that the usage method returns a string. + assertTrue("The usage method did not return a non empty string.", + !((parser.getUsage() == null) || "".equals(parser.getUsage()))); + } + + /** Check that parsing multiple flags condensed together works ok. */ + public void testParseCondensedFlagsOk() throws Exception + { + // Create a command line parser for multiple flags. + CommandLineParser parser = + new CommandLineParser( + new String[][] + { + { "t1", "Test Flag 1." }, + { "t2", "Test Flag 2." }, + { "t3", "Test Flag 3." } + }); + + // Parse a command line with the flags set and condensed together. + Properties testProps = parser.parseCommandLine(new String[] { "-t1t2t3" }); + + // Check that the flags were set in the parsed properties. + assertTrue("The t1 flag was not \"true\", it was: " + testProps.get("t1"), "true".equals(testProps.get("t1"))); + assertTrue("The t2 flag was not \"true\", it was: " + testProps.get("t2"), "true".equals(testProps.get("t2"))); + assertTrue("The t3 flag was not \"true\", it was: " + testProps.get("t3"), "true".equals(testProps.get("t3"))); + } + + /** Check that parsing a flag condensed together with an option fails. */ + public void testParseFlagCondensedWithOptionFails() throws Exception + { + // Create a command line parser for a flag and an option. + CommandLineParser parser = + new CommandLineParser(new String[][] + { + { "t1", "Test Flag 1." }, + { "t2", "Test Option 2.", "test" } + }); + + // Check that the parser reports an error. + boolean testPassed = false; + + try + { + // Parse a command line with the flag and option condensed together. + Properties testProps = parser.parseCommandLine(new String[] { "-t1t2" }); + } + catch (IllegalArgumentException e) + { + testPassed = true; + } + + assertTrue("IllegalArgumentException not thrown when a flag and option are condensed together.", testPassed); + } + + /** Check that parsing a free argument with specific format fails on bad argument. */ + public void testParseFormattedFreeArgumentFailsBadArgument() throws Exception + { + // Create a command line parser for a formatted free argument. + CommandLineParser parser = + new CommandLineParser(new String[][] + { + { "1", "Test Free Argument.", "test", null, "^test$" } + }); + + // Check that the parser signals an error for a badly formatted argument. + boolean testPassed = false; + + try + { + // Parse a command line with this option set incorrectly. + Properties testProps = parser.parseCommandLine(new String[] { "fail" }); + } + catch (IllegalArgumentException e) + { + testPassed = true; + } + + assertTrue("IllegalArgumentException not thrown when a badly formatted argument was set.", testPassed); + } + + /** Check that parsing a free argument with specific format works ok. */ + public void testParseFormattedFreeArgumentOk() throws Exception + { + // Create a command line parser for a formatted free argument. + CommandLineParser parser = + new CommandLineParser(new String[][] + { + { "1", "Test Free Argument.", "test", null, "^test$" } + }); + + // Parse a command line with this argument set correctly. + Properties testProps = parser.parseCommandLine(new String[] { "test" }); + + // Check that the resultant properties contains the correctly parsed option. + assertTrue("The first free argument was not equal to \"test\" but was: " + testProps.get("1"), + "test".equals(testProps.get("1"))); + } + + /** Check that parsing an option with specific argument format fails on bad argument. */ + public void testParseFormattedOptionArgumentFailsBadArgument() throws Exception + { + // Create a command line parser for a formatted option. + CommandLineParser parser = new CommandLineParser(new String[][] + { + { "t", "Test Option.", "test", null, "^test$" } + }); + + // Check that the parser signals an error for a badly formatted argument. + boolean testPassed = false; + + try + { + // Parse a command line with this option set incorrectly. + Properties testProps = parser.parseCommandLine(new String[] { "-t", "fail" }); + } + catch (IllegalArgumentException e) + { + testPassed = true; + } + + assertTrue("IllegalArgumentException not thrown when a badly formatted argument was set.", testPassed); + } + + /** Check that parsing an option with specific argument format works ok. */ + public void testParseFormattedOptionArgumentOk() throws Exception + { + // Create a command line parser for a formatted option. + CommandLineParser parser = new CommandLineParser(new String[][] + { + { "t", "Test Option.", "test", null, "^test$" } + }); + + // Parse a command line with this option set correctly. + Properties testProps = parser.parseCommandLine(new String[] { "-t", "test" }); + + // Check that the resultant properties contains the correctly parsed option. + assertTrue("The test option was not equal to \"test\" but was: " + testProps.get("t"), + "test".equals(testProps.get("t"))); + } + + /** Check that parsing a free argument works ok. */ + public void testParseFreeArgumentOk() throws Exception + { + // Create a command line parser for a free argument. + CommandLineParser parser = new CommandLineParser(new String[][] + { + { "1", "Test Free Argument.", "test" } + }); + + // Parse a command line with this argument set. + Properties testProps = parser.parseCommandLine(new String[] { "test" }); + + // Check that the resultant properties contains the correctly parsed option. + assertTrue("The first free argument was not equal to \"test\" but was: " + testProps.get("1"), + "test".equals(testProps.get("1"))); + } + + /** Check that parsing a mandatory option works ok. */ + public void testParseMandatoryOptionOk() throws Exception + { + // Create a command line parser for a mandatory option. + CommandLineParser parser = new CommandLineParser(new String[][] + { + { "t", "Test Option.", "test", "true" } + }); + + // Parse a command line with this option set correctly. + Properties testProps = parser.parseCommandLine(new String[] { "-t", "test" }); + + // Check that the resultant properties contains the correctly parsed option. + assertTrue("The test option was not equal to \"test\" but was: " + testProps.get("t"), + "test".equals(testProps.get("t"))); + } + + /** Check that parsing a mandatory free argument works ok. */ + public void testParseMandatoryFreeArgumentOk() throws Exception + { + // Create a command line parser for a mandatory free argument. + CommandLineParser parser = new CommandLineParser(new String[][] + { + { "1", "Test Option.", "test", "true" } + }); + + // Parse a command line with this argument set. + Properties testProps = parser.parseCommandLine(new String[] { "test" }); + + // Check that the resultant properties contains the correctly parsed option. + assertTrue("The first free argument was not equal to \"test\" but was: " + testProps.get("1"), + "test".equals(testProps.get("1"))); + } + + /** Check that parsing a mandatory free argument fails when no argument is specified. */ + public void testParseManadatoryFreeArgumentFailsNoArgument() throws Exception + { + // Create a command line parser for a mandatory free argument. + CommandLineParser parser = new CommandLineParser(new String[][] + { + { "1", "Test Option.", "test", "true" } + }); + + // Check that parsing fails when this mandatory free argument is missing. + boolean testPassed = false; + + try + { + // Parse a command line with this free argument not set. + Properties testProps = parser.parseCommandLine(new String[] {}); + } + catch (IllegalArgumentException e) + { + testPassed = true; + } + + // Check that the resultant properties contains the correctly parsed option. + assertTrue("IllegalArgumentException not thrown for a missing mandatory option.", testPassed); + } + + /** Check that parsing a mandatory option fails when no option is set. */ + public void testParseMandatoryFailsNoOption() throws Exception + { + // Create a command line parser for a mandatory option. + CommandLineParser parser = new CommandLineParser(new String[][] + { + { "t", "Test Option.", "test", "true" } + }); + + // Check that parsing fails when this mandatory option is missing. + boolean testPassed = false; + + try + { + // Parse a command line with this option not set. + Properties testProps = parser.parseCommandLine(new String[] {}); + } + catch (IllegalArgumentException e) + { + testPassed = true; + } + + // Check that the resultant properties contains the correctly parsed option. + assertTrue("IllegalArgumentException not thrown for a missing mandatory option.", testPassed); + } + + /** Check that parsing an option with no space between it and its argument works ok. */ + public void testParseOptionWithNoSpaceOk() throws Exception + { + // Create a command line parser for an option. + CommandLineParser parser = new CommandLineParser(new String[][] + { + { "t", "Test Option.", "test" } + }); + + // Parse a command line with this option set with no space. + Properties testProps = parser.parseCommandLine(new String[] { "-ttest" }); + + // Check that the resultant properties contains the correctly parsed option. + assertTrue("The test option was not equal to \"test\" but was: " + testProps.get("t"), + "test".equals(testProps.get("t"))); + } + + /** Check that parsing an option with a space between it and its argument works ok. */ + public void testParseOptionWithSpaceOk() throws Exception + { + // Create a command line parser for an option. + CommandLineParser parser = new CommandLineParser(new String[][] + { + { "t", "Test Option.", "test" } + }); + + // Parse a command line with this option set with a space. + Properties testProps = parser.parseCommandLine(new String[] { "-t", "test" }); + + // Check that the resultant properties contains the correctly parsed option. + assertTrue("The test option was not equal to \"test\" but was: " + testProps.get("t"), + "test".equals(testProps.get("t"))); + } + + /** Check that parsing a single flag works ok. */ + public void testParseSingleFlagOk() throws Exception + { + // Create a command line parser for a single flag. + CommandLineParser parser = new CommandLineParser(new String[][] + { + { "t", "Test Flag." } + }); + + // Parse a command line with the single flag set. + Properties testProps = parser.parseCommandLine(new String[] { "-t" }); + + // Check that the flag is set in the parsed properties. + assertTrue("The t flag was not \"true\", it was: " + testProps.get("t"), "true".equals(testProps.get("t"))); + + // Reset the parser. + parser.reset(); + + // Parse a command line with the single flag not set. + testProps = parser.parseCommandLine(new String[] {}); + + // Check that the flag is cleared in the parsed properties. + assertTrue("The t flag was not \"false\", it was: " + testProps.get("t"), "false".equals(testProps.get("t"))); + } + + /** Check that parsing an unknown option works when unknowns not errors. */ + public void testParseUnknownOptionOk() throws Exception + { + // Create a command line parser for no flags or options + CommandLineParser parser = new CommandLineParser(new String[][] {}); + + // Check that parsing does not fail on an unknown flag. + try + { + parser.parseCommandLine(new String[] { "-t" }); + } + catch (IllegalArgumentException e) + { + fail("The parser threw an IllegalArgumentException on an unknown flag when errors on unkowns is off."); + } + } + + /** Check that parsing an unknown flag fails when unknowns are to be reported as errors. */ + public void testParseUnknownFlagFailsWhenUnknownsAreErrors() throws Exception + { + // Create a command line parser for no flags or options + CommandLineParser parser = new CommandLineParser(new String[][] {}); + + // Turn on fail on unknowns mode. + parser.setErrorsOnUnknowns(true); + + // Check that parsing fails on an unknown flag. + boolean testPassed = false; + + try + { + parser.parseCommandLine(new String[] { "-t" }); + } + catch (IllegalArgumentException e) + { + testPassed = true; + } + + assertTrue("IllegalArgumentException not thrown for an unknown flag when errors on unknowns mode is on.", + testPassed); + } + + /** Check that parsing an unknown option fails when unknowns are to be reported as errors. */ + public void testParseUnknownOptionFailsWhenUnknownsAreErrors() throws Exception + { + // Create a command line parser for no flags or options + CommandLineParser parser = new CommandLineParser(new String[][] {}); + + // Turn on fail on unknowns mode. + parser.setErrorsOnUnknowns(true); + + // Check that parsing fails on an unknown flag. + boolean testPassed = false; + + try + { + parser.parseCommandLine(new String[] { "-t", "test" }); + } + catch (IllegalArgumentException e) + { + testPassed = true; + } + + assertTrue("IllegalArgumentException not thrown for an unknown option when errors on unknowns mode is on.", + testPassed); + } +} diff --git a/qpid/java/common/src/test/java/org/apache/qpid/util/FileUtilsTest.java b/qpid/java/common/src/test/java/org/apache/qpid/util/FileUtilsTest.java new file mode 100644 index 0000000000..7eba5f092e --- /dev/null +++ b/qpid/java/common/src/test/java/org/apache/qpid/util/FileUtilsTest.java @@ -0,0 +1,612 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util; + +import junit.framework.TestCase; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.List; + +public class FileUtilsTest extends TestCase +{ + private static final String COPY = "-Copy"; + private static final String SUB = "-Sub"; + + /** + * Additional test for the copy method. + * Ensures that the directory count did increase by more than 1 after the copy. + */ + public void testCopyFile() + { + final String TEST_DATA = "FileUtilsTest-testCopy-TestDataTestDataTestDataTestDataTestDataTestData"; + String fileName = "FileUtilsTest-testCopy"; + String fileNameCopy = fileName + COPY; + + File[] beforeCopyFileList = null; + + //Create initial file + File test = createTestFile(fileName, TEST_DATA); + + try + { + //Check number of files before copy + beforeCopyFileList = test.getAbsoluteFile().getParentFile().listFiles(); + int beforeCopy = beforeCopyFileList.length; + + //Perform Copy + File destination = new File(fileNameCopy); + FileUtils.copy(test, destination); + //Ensure the JVM cleans up if cleanup failues + destination.deleteOnExit(); + + //Retrieve counts after copy + int afterCopy = test.getAbsoluteFile().getParentFile().listFiles().length; + + int afterCopyFromCopy = new File(fileNameCopy).getAbsoluteFile().getParentFile().listFiles().length; + + // Validate the copy counts + assertEquals("The file listing from the original and the copy differ in length.", afterCopy, afterCopyFromCopy); + assertEquals("The number of files did not increase.", beforeCopy + 1, afterCopy); + assertEquals("The number of files did not increase.", beforeCopy + 1, afterCopyFromCopy); + + //Validate copy + // Load content + String copiedFileContent = FileUtils.readFileAsString(fileNameCopy); + assertEquals(TEST_DATA, copiedFileContent); + } + finally // Ensure clean + { + //Clean up + assertTrue("Unable to cleanup", FileUtils.deleteFile(fileNameCopy)); + + //Check file list after cleanup + File[] afterCleanup = new File(test.getAbsoluteFile().getParent()).listFiles(); + checkFileLists(beforeCopyFileList, afterCleanup); + + //Remove original file + assertTrue("Unable to cleanup", test.delete()); + } + } + + /** + * Create and Copy the following structure: + * + * testDirectory --+ + * +-- testSubDirectory --+ + * +-- testSubFile + * +-- File + * + * to testDirectory-Copy + * + * Validate that the file count in the copy is correct and contents of the copied files is correct. + */ + public void testCopyRecursive() + { + final String TEST_DATA = "FileUtilsTest-testDirectoryCopy-TestDataTestDataTestDataTestDataTestDataTestData"; + String fileName = "FileUtilsTest-testCopy"; + String TEST_DIR = "testDirectoryCopy"; + + //Create Initial Structure + File testDir = new File(TEST_DIR); + + //Check number of files before copy + File[] beforeCopyFileList = testDir.getAbsoluteFile().getParentFile().listFiles(); + + try + { + //Create Directories + assertTrue("Test directory already exists cannot test.", !testDir.exists()); + + if (!testDir.mkdir()) + { + fail("Unable to make test Directory"); + } + + File testSubDir = new File(TEST_DIR + File.separator + TEST_DIR + SUB); + if (!testSubDir.mkdir()) + { + fail("Unable to make test sub Directory"); + } + + //Create Files + createTestFile(testDir.toString() + File.separator + fileName, TEST_DATA); + createTestFile(testSubDir.toString() + File.separator + fileName + SUB, TEST_DATA); + + //Ensure the JVM cleans up if cleanup failues + testSubDir.deleteOnExit(); + testDir.deleteOnExit(); + + //Perform Copy + File copyDir = new File(testDir.toString() + COPY); + try + { + FileUtils.copyRecursive(testDir, copyDir); + } + catch (FileNotFoundException e) + { + fail(e.getMessage()); + } + catch (FileUtils.UnableToCopyException e) + { + fail(e.getMessage()); + } + + //Validate Copy + assertEquals("Copied directory should only have one file and one directory in it.", 2, copyDir.listFiles().length); + + //Validate Copy File Contents + String copiedFileContent = FileUtils.readFileAsString(copyDir.toString() + File.separator + fileName); + assertEquals(TEST_DATA, copiedFileContent); + + //Validate Name of Sub Directory + assertTrue("Expected subdirectory is not a directory", new File(copyDir.toString() + File.separator + TEST_DIR + SUB).isDirectory()); + + //Assert that it contains only one item + assertEquals("Copied sub directory should only have one directory in it.", 1, new File(copyDir.toString() + File.separator + TEST_DIR + SUB).listFiles().length); + + //Validate content of Sub file + copiedFileContent = FileUtils.readFileAsString(copyDir.toString() + File.separator + TEST_DIR + SUB + File.separator + fileName + SUB); + assertEquals(TEST_DATA, copiedFileContent); + } + finally + { + //Clean up source and copy directory. + assertTrue("Unable to cleanup", FileUtils.delete(testDir, true)); + assertTrue("Unable to cleanup", FileUtils.delete(new File(TEST_DIR + COPY), true)); + + //Check file list after cleanup + File[] afterCleanup = testDir.getAbsoluteFile().getParentFile().listFiles(); + checkFileLists(beforeCopyFileList, afterCleanup); + } + } + + /** + * Helper method to create a test file with a string content + * + * @param fileName The fileName to use in the creation + * @param test_data The data to store in the file + * + * @return The File reference + */ + private File createTestFile(String fileName, String test_data) + { + File test = new File(fileName); + + try + { + test.createNewFile(); + //Ensure the JVM cleans up if cleanup failues + test.deleteOnExit(); + } + catch (IOException e) + { + fail(e.getMessage()); + } + + BufferedWriter writer = null; + try + { + writer = new BufferedWriter(new FileWriter(test)); + try + { + writer.write(test_data); + } + catch (IOException e) + { + fail(e.getMessage()); + } + } + catch (IOException e) + { + fail(e.getMessage()); + } + finally + { + try + { + if (writer != null) + { + writer.close(); + } + } + catch (IOException e) + { + fail(e.getMessage()); + } + } + + return test; + } + + /** Test that deleteFile only deletes the specified file */ + public void testDeleteFile() + { + File test = new File("FileUtilsTest-testDelete"); + //Record file count in parent directory to check it is not changed by delete + String path = test.getAbsolutePath(); + File[] filesBefore = new File(path.substring(0, path.lastIndexOf(File.separator))).listFiles(); + int fileCountBefore = filesBefore.length; + + try + { + test.createNewFile(); + //Ensure the JVM cleans up if cleanup failues + test.deleteOnExit(); + } + catch (IOException e) + { + fail(e.getMessage()); + } + + assertTrue("File does not exists", test.exists()); + assertTrue("File is not a file", test.isFile()); + + //Check that file creation can be seen on disk + int fileCountCreated = new File(path.substring(0, path.lastIndexOf(File.separator))).listFiles().length; + assertEquals("File creation was no registered", fileCountBefore + 1, fileCountCreated); + + //Perform Delete + assertTrue("Unable to cleanup", FileUtils.deleteFile("FileUtilsTest-testDelete")); + + assertTrue("File exists after delete", !test.exists()); + + //Check that after deletion the file count is now accurate + File[] filesAfter = new File(path.substring(0, path.lastIndexOf(File.separator))).listFiles(); + int fileCountAfter = filesAfter.length; + assertEquals("File creation was no registered", fileCountBefore, fileCountAfter); + + checkFileLists(filesBefore, filesAfter); + } + + public void testDeleteNonExistentFile() + { + File test = new File("FileUtilsTest-testDelete-" + System.currentTimeMillis()); + + assertTrue("File exists", !test.exists()); + assertFalse("File is a directory", test.isDirectory()); + + assertTrue("Delete Succeeded ", !FileUtils.delete(test, true)); + } + + public void testDeleteNull() + { + try + { + FileUtils.delete(null, true); + fail("Delete with null value should throw NPE."); + } + catch (NullPointerException npe) + { + // expected path + } + } + + /** + * Given two lists of File arrays ensure they are the same length and all entries in Before are in After + * + * @param filesBefore File[] + * @param filesAfter File[] + */ + private void checkFileLists(File[] filesBefore, File[] filesAfter) + { + assertNotNull("Before file list cannot be null", filesBefore); + assertNotNull("After file list cannot be null", filesAfter); + + assertEquals("File lists are unequal", filesBefore.length, filesAfter.length); + + for (File fileBefore : filesBefore) + { + boolean found = false; + + for (File fileAfter : filesAfter) + { + if (fileBefore.getAbsolutePath().equals(fileAfter.getAbsolutePath())) + { + found = true; + break; + } + } + + assertTrue("File'" + fileBefore.getName() + "' was not in directory afterwards", found); + } + } + + public void testNonRecursiveNonEmptyDirectoryDeleteFails() + { + String directoryName = "FileUtilsTest-testRecursiveDelete"; + File test = new File(directoryName); + + //Record file count in parent directory to check it is not changed by delete + String path = test.getAbsolutePath(); + File[] filesBefore = new File(path.substring(0, path.lastIndexOf(File.separator))).listFiles(); + int fileCountBefore = filesBefore.length; + + assertTrue("Directory exists", !test.exists()); + + test.mkdir(); + + //Create a file in the directory + String fileName = test.getAbsolutePath() + File.separatorChar + "testFile"; + File subFile = new File(fileName); + try + { + subFile.createNewFile(); + //Ensure the JVM cleans up if cleanup failues + subFile.deleteOnExit(); + } + catch (IOException e) + { + fail(e.getMessage()); + } + //Ensure the JVM cleans up if cleanup failues + // This must be after the subFile as the directory must be empty before + // the delete is performed + test.deleteOnExit(); + + //Try and delete the non-empty directory + assertFalse("Non Empty Directory was successfully deleted.", FileUtils.deleteDirectory(directoryName)); + + //Check directory is still there + assertTrue("Directory was deleted.", test.exists()); + + // Clean up + assertTrue("Unable to cleanup", FileUtils.delete(test, true)); + + //Check that after deletion the file count is now accurate + File[] filesAfter = new File(path.substring(0, path.lastIndexOf(File.separator))).listFiles(); + int fileCountAfter = filesAfter.length; + assertEquals("File creation was no registered", fileCountBefore, fileCountAfter); + + checkFileLists(filesBefore, filesAfter); + } + + /** Test that an empty directory can be deleted with deleteDirectory */ + public void testEmptyDirectoryDelete() + { + String directoryName = "FileUtilsTest-testRecursiveDelete"; + File test = new File(directoryName); + + //Record file count in parent directory to check it is not changed by delete + String path = test.getAbsolutePath(); + File[] filesBefore = new File(path.substring(0, path.lastIndexOf(File.separator))).listFiles(); + int fileCountBefore = filesBefore.length; + + assertTrue("Directory exists", !test.exists()); + + test.mkdir(); + //Ensure the JVM cleans up if cleanup failues + test.deleteOnExit(); + + //Try and delete the empty directory + assertTrue("Non Empty Directory was successfully deleted.", FileUtils.deleteDirectory(directoryName)); + + //Check directory is still there + assertTrue("Directory was deleted.", !test.exists()); + + //Check that after deletion the file count is now accurate + File[] filesAfter = new File(path.substring(0, path.lastIndexOf(File.separator))).listFiles(); + int fileCountAfter = filesAfter.length; + assertEquals("File creation was no registered", fileCountBefore, fileCountAfter); + + checkFileLists(filesBefore, filesAfter); + + } + + /** Test that deleteDirectory on a non empty directory to complete */ + public void testNonEmptyDirectoryDelete() + { + String directoryName = "FileUtilsTest-testRecursiveDelete"; + File test = new File(directoryName); + + assertTrue("Directory exists", !test.exists()); + + //Record file count in parent directory to check it is not changed by delete + String path = test.getAbsolutePath(); + File[] filesBefore = new File(path.substring(0, path.lastIndexOf(File.separator))).listFiles(); + int fileCountBefore = filesBefore.length; + + test.mkdir(); + + //Create a file in the directory + String fileName = test.getAbsolutePath() + File.separatorChar + "testFile"; + File subFile = new File(fileName); + try + { + subFile.createNewFile(); + //Ensure the JVM cleans up if cleanup failues + subFile.deleteOnExit(); + } + catch (IOException e) + { + fail(e.getMessage()); + } + + // Ensure the JVM cleans up if cleanup failues + // This must be after the subFile as the directory must be empty before + // the delete is performed + test.deleteOnExit(); + + //Try and delete the non-empty directory non-recursively + assertFalse("Non Empty Directory was successfully deleted.", FileUtils.delete(test, false)); + + //Check directory is still there + assertTrue("Directory was deleted.", test.exists()); + + // Clean up + assertTrue("Unable to cleanup", FileUtils.delete(test, true)); + + //Check that after deletion the file count is now accurate + File[] filesAfter = new File(path.substring(0, path.lastIndexOf(File.separator))).listFiles(); + int fileCountAfter = filesAfter.length; + assertEquals("File creation was no registered", fileCountBefore, fileCountAfter); + + checkFileLists(filesBefore, filesAfter); + + } + + /** Test that a recursive delete successeds */ + public void testRecursiveDelete() + { + String directoryName = "FileUtilsTest-testRecursiveDelete"; + File test = new File(directoryName); + + assertTrue("Directory exists", !test.exists()); + + //Record file count in parent directory to check it is not changed by delete + String path = test.getAbsolutePath(); + File[] filesBefore = new File(path.substring(0, path.lastIndexOf(File.separator))).listFiles(); + int fileCountBefore = filesBefore.length; + + test.mkdir(); + + createSubDir(directoryName, 2, 4); + + //Ensure the JVM cleans up if cleanup failues + // This must be after the sub dir creation as the delete order is + // recorded and the directory must be empty to be deleted. + test.deleteOnExit(); + + assertFalse("Non recursive delete was able to directory", FileUtils.delete(test, false)); + + assertTrue("File does not exist after non recursive delete", test.exists()); + + assertTrue("Unable to cleanup", FileUtils.delete(test, true)); + + assertTrue("File exist after recursive delete", !test.exists()); + + //Check that after deletion the file count is now accurate + File[] filesAfter = new File(path.substring(0, path.lastIndexOf(File.separator))).listFiles(); + int fileCountAfter = filesAfter.length; + assertEquals("File creation was no registered", fileCountBefore, fileCountAfter); + + checkFileLists(filesBefore, filesAfter); + + } + + private void createSubDir(String path, int directories, int files) + { + File directory = new File(path); + + assertTrue("Directory" + path + " does not exists", directory.exists()); + + for (int dir = 0; dir < directories; dir++) + { + String subDirName = path + File.separatorChar + "sub" + dir; + File subDir = new File(subDirName); + + subDir.mkdir(); + + createSubDir(subDirName, directories - 1, files); + //Ensure the JVM cleans up if cleanup failues + // This must be after the sub dir creation as the delete order is + // recorded and the directory must be empty to be deleted. + subDir.deleteOnExit(); + } + + for (int file = 0; file < files; file++) + { + String subDirName = path + File.separatorChar + "file" + file; + File subFile = new File(subDirName); + try + { + subFile.createNewFile(); + //Ensure the JVM cleans up if cleanup failues + subFile.deleteOnExit(); + } + catch (IOException e) + { + fail(e.getMessage()); + } + } + } + + public static final String SEARCH_STRING = "testSearch"; + + /** + * Test searchFile(File file, String search) will find a match when it + * exists. + * + * @throws java.io.IOException if unable to perform test setup + */ + public void testSearchSucceed() throws IOException + { + File _logfile = File.createTempFile("FileUtilsTest-testSearchSucceed", ".out"); + + prepareFileForSearchTest(_logfile); + + List<String> results = FileUtils.searchFile(_logfile, SEARCH_STRING); + + assertNotNull("Null result set returned", results); + + assertEquals("Results do not contain expected count", 1, results.size()); + } + + /** + * Test searchFile(File file, String search) will not find a match when the + * test string does not exist. + * + * @throws java.io.IOException if unable to perform test setup + */ + public void testSearchFail() throws IOException + { + File _logfile = File.createTempFile("FileUtilsTest-testSearchFail", ".out"); + + prepareFileForSearchTest(_logfile); + + List<String> results = FileUtils.searchFile(_logfile, "Hello"); + + assertNotNull("Null result set returned", results); + + //Validate we only got one message + if (results.size() > 0) + { + System.err.println("Unexpected messages"); + + for (String msg : results) + { + System.err.println(msg); + } + } + + assertEquals("Results contains data when it was not expected", + 0, results.size()); + } + + /** + * Write the SEARCH_STRING in to the given file. + * + * @param logfile The file to write the SEARCH_STRING into + * + * @throws IOException if an error occurs + */ + private void prepareFileForSearchTest(File logfile) throws IOException + { + BufferedWriter writer = new BufferedWriter(new FileWriter(logfile)); + writer.append(SEARCH_STRING); + writer.flush(); + writer.close(); + } + +} diff --git a/qpid/java/common/src/test/java/org/apache/qpid/util/PropertyUtilsTest.java b/qpid/java/common/src/test/java/org/apache/qpid/util/PropertyUtilsTest.java new file mode 100644 index 0000000000..9fd18d461a --- /dev/null +++ b/qpid/java/common/src/test/java/org/apache/qpid/util/PropertyUtilsTest.java @@ -0,0 +1,48 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.util; + +import org.apache.qpid.configuration.PropertyException; +import org.apache.qpid.configuration.PropertyUtils; +import org.apache.qpid.test.utils.QpidTestCase; + +public class PropertyUtilsTest extends QpidTestCase +{ + public void testSimpleExpansion() throws PropertyException + { + System.setProperty("banana", "fruity"); + String expandedProperty = PropertyUtils.replaceProperties("${banana}"); + assertEquals(expandedProperty, "fruity"); + } + + public void testDualExpansion() throws PropertyException + { + System.setProperty("banana", "fruity"); + System.setProperty("concrete", "horrible"); + String expandedProperty = PropertyUtils.replaceProperties("${banana}xyz${concrete}"); + assertEquals(expandedProperty, "fruityxyzhorrible"); + } + + public static junit.framework.Test suite() + { + return new junit.framework.TestSuite(PropertyUtilsTest.class); + } +} diff --git a/qpid/java/common/src/test/java/org/apache/qpid/util/SerialTest.java b/qpid/java/common/src/test/java/org/apache/qpid/util/SerialTest.java new file mode 100644 index 0000000000..b2578563e0 --- /dev/null +++ b/qpid/java/common/src/test/java/org/apache/qpid/util/SerialTest.java @@ -0,0 +1,82 @@ +package org.apache.qpid.util; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 junit.framework.TestCase; + +import java.util.Random; + +import org.apache.qpid.SerialException; + +/** + *Junit tests for the Serial class + */ +public class SerialTest extends TestCase +{ + + /** + * Test the key boundaries where wraparound occurs. + */ + public void testBoundaries() + { + assertTrue(Serial.gt(1, 0)); + assertTrue(Serial.lt(0, 1)); + + assertTrue(Serial.gt(Integer.MAX_VALUE+1, Integer.MAX_VALUE)); + assertTrue(Serial.lt(Integer.MAX_VALUE, Integer.MAX_VALUE+1)); + + assertTrue(Serial.gt(0xFFFFFFFF + 1, 0xFFFFFFFF)); + assertTrue(Serial.lt(0xFFFFFFFF, 0xFFFFFFFF + 1)); + } + + /** + * Test the first Corollary of RFC 1982 + * For any sequence number s and any integer n such that addition of n + * to s is well defined, (s + n) >= s. Further (s + n) == s only when + * n == 0, in all other defined cases, (s + n) > s. + */ + public void testCorollary1() + { + int wrapcount = 0; + + int s = 0; + + for (int i = 0; i < 67108664; i++) + { + for (int n = 1; n < 4096; n += 512) + { + assertTrue(Serial.gt(s+n, s)); + assertTrue(Serial.lt(s, s+n)); + } + + s += 1024; + + if (s == 0) + { + wrapcount += 1; + } + } + + assertTrue(wrapcount > 0); + } + +} |