git-svn-id: https://svn.apache.org/repos/asf/activemq/trunk@1157238 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Timothy A. Bish 2011-08-12 20:29:29 +00:00
parent 9026274532
commit 0885c60c4d
34 changed files with 2079 additions and 947 deletions

View File

@ -6,9 +6,9 @@
The ASF licenses this file to You under the Apache License, Version 2.0 The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -98,7 +98,7 @@
<groupId>org.osgi</groupId> <groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId> <artifactId>org.osgi.core</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.activemq</groupId> <groupId>org.apache.activemq</groupId>
@ -133,7 +133,7 @@
<groupId>com.thoughtworks.xstream</groupId> <groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId> <artifactId>xstream</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.codehaus.jettison</groupId> <groupId>org.codehaus.jettison</groupId>
<artifactId>jettison</artifactId> <artifactId>jettison</artifactId>
@ -150,7 +150,7 @@
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId> <artifactId>spring-context</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.derby</groupId> <groupId>org.apache.derby</groupId>
@ -213,7 +213,7 @@
<artifactId>spring-test</artifactId> <artifactId>spring-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>commons-io</groupId> <groupId>commons-io</groupId>
<artifactId>commons-io</artifactId> <artifactId>commons-io</artifactId>
@ -224,7 +224,7 @@
<artifactId>activemq-jmdns_1.0</artifactId> <artifactId>activemq-jmdns_1.0</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jasypt</groupId> <groupId>org.jasypt</groupId>
<artifactId>jasypt</artifactId> <artifactId>jasypt</artifactId>
@ -372,8 +372,25 @@
</plugin> </plugin>
</plugins> </plugins>
</reporting> </reporting>
<build> <build>
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
</resource>
<resource>
<directory>${project.basedir}/src/main/filtered-resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.felix</groupId> <groupId>org.apache.felix</groupId>
@ -384,7 +401,7 @@
</instructions> </instructions>
</configuration> </configuration>
</plugin> </plugin>
<!-- Configure which tests are included/excuded --> <!-- Configure which tests are included/excuded -->
<plugin> <plugin>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
@ -405,7 +422,7 @@
Note: if you want to see log messages on the console window remove Note: if you want to see log messages on the console window remove
"redirectTestOutputToFile" from the parent pom "redirectTestOutputToFile" from the parent pom
--> -->
<!-- <!--
<property> <property>
<name>log4j.configuration</name> <name>log4j.configuration</name>
<value>file:target/test-classes/log4j.properties</value> <value>file:target/test-classes/log4j.properties</value>
@ -426,7 +443,7 @@
<!-- These are performance tests so take too long to run --> <!-- These are performance tests so take too long to run -->
<exclude>**/perf/*</exclude> <exclude>**/perf/*</exclude>
<!-- These are load tests so take too long to run --> <!-- These are load tests so take too long to run -->
<exclude>**/load/*</exclude> <exclude>**/load/*</exclude>
@ -470,19 +487,19 @@
<!-- A test used for memory profiling only. --> <!-- A test used for memory profiling only. -->
<exclude>**/NetworkConnectionsCleanedupTest.*/**</exclude> <exclude>**/NetworkConnectionsCleanedupTest.*/**</exclude>
<exclude>**/NetworkConnectionsCleanedupTest.*/**</exclude> <exclude>**/NetworkConnectionsCleanedupTest.*/**</exclude>
<!-- used just to test potential memory leaks manually --> <!-- used just to test potential memory leaks manually -->
<exclude>**/JDBCTestMemory.*</exclude> <exclude>**/JDBCTestMemory.*</exclude>
<exclude>**/amq1490/*</exclude> <exclude>**/amq1490/*</exclude>
<exclude>**/archive/*</exclude> <exclude>**/archive/*</exclude>
<exclude>**/NetworkFailoverTest.*/**</exclude> <exclude>**/NetworkFailoverTest.*/**</exclude>
<exclude>**/vm/VMTransportBrokerTest.*</exclude> <exclude>**/vm/VMTransportBrokerTest.*</exclude>
<exclude>**/broker/MarshallingBrokerTest.*</exclude> <exclude>**/broker/MarshallingBrokerTest.*</exclude>
<exclude>**/AMQDeadlockTest3.*</exclude> <exclude>**/AMQDeadlockTest3.*</exclude>
@ -491,7 +508,7 @@
<!-- breaks hudson: disable till we get a chance to give it the time that it needs - http://hudson.zones.apache.org/hudson/job/ActiveMQ/org.apache.activemq$activemq-core/199/testReport/org.apache.activemq.network/BrokerNetworkWithStuckMessagesTest/testBrokerNetworkWithStuckMessages/ --> <!-- breaks hudson: disable till we get a chance to give it the time that it needs - http://hudson.zones.apache.org/hudson/job/ActiveMQ/org.apache.activemq$activemq-core/199/testReport/org.apache.activemq.network/BrokerNetworkWithStuckMessagesTest/testBrokerNetworkWithStuckMessages/ -->
<exclude>**/BrokerNetworkWithStuckMessagesTest.*</exclude> <exclude>**/BrokerNetworkWithStuckMessagesTest.*</exclude>
</excludes> </excludes>
</configuration> </configuration>
</plugin> </plugin>
@ -519,28 +536,28 @@
</filesets> </filesets>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId> <artifactId>maven-antrun-plugin</artifactId>
<executions> <executions>
<execution> <execution>
<id>package</id> <id>package</id>
<phase>package</phase> <phase>package</phase>
<configuration> <configuration>
<tasks> <tasks>
<echo>Deleting unwanted resources from the test-jar</echo> <echo>Deleting unwanted resources from the test-jar</echo>
<delete dir="${project.build.directory}/test-classes" verbose="true"> <delete dir="${project.build.directory}/test-classes" verbose="true">
<include name="*.*" /> <include name="*.*" />
</delete> </delete>
</tasks> </tasks>
</configuration> </configuration>
<goals> <goals>
<goal>run</goal> <goal>run</goal>
</goals> </goals>
</execution> </execution>
<execution> <execution>
<id>site</id> <id>site</id>
<phase>site</phase> <phase>site</phase>
@ -626,7 +643,7 @@
<artifactId>cobertura-maven-plugin</artifactId> <artifactId>cobertura-maven-plugin</artifactId>
<version>2.0</version> <version>2.0</version>
<configuration> <configuration>
<check> <check>
<branchRate>50</branchRate> <branchRate>50</branchRate>
<lineRate>50</lineRate> <lineRate>50</lineRate>
<haltOnFailure>true</haltOnFailure> <haltOnFailure>true</haltOnFailure>
@ -634,12 +651,12 @@
<totalLineRate>50</totalLineRate> <totalLineRate>50</totalLineRate>
</check> </check>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
<profiles> <profiles>
<profile> <profile>
<id>openwire-generate</id> <id>openwire-generate</id>
<dependencies> <dependencies>
@ -675,7 +692,7 @@
</plugins> </plugins>
</build> </build>
</profile> </profile>
</profiles> </profiles>
</project> </project>

View File

@ -0,0 +1 @@
${project.version}

View File

@ -0,0 +1,372 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.transport;
import java.io.IOException;
import java.util.Timer;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.activemq.command.KeepAliveInfo;
import org.apache.activemq.command.WireFormatInfo;
import org.apache.activemq.thread.SchedulerTimerTask;
import org.apache.activemq.wireformat.WireFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Used to make sure that commands are arriving periodically from the peer of
* the transport.
*/
public abstract class AbstractInactivityMonitor extends TransportFilter {
private static final Logger LOG = LoggerFactory.getLogger(AbstractInactivityMonitor.class);
private static ThreadPoolExecutor ASYNC_TASKS;
private static int CHECKER_COUNTER;
private static long DEFAULT_CHECK_TIME_MILLS = 30000;
private static Timer READ_CHECK_TIMER;
private static Timer WRITE_CHECK_TIMER;
private final AtomicBoolean monitorStarted = new AtomicBoolean(false);
private final AtomicBoolean commandSent = new AtomicBoolean(false);
private final AtomicBoolean inSend = new AtomicBoolean(false);
private final AtomicBoolean failed = new AtomicBoolean(false);
private final AtomicBoolean commandReceived = new AtomicBoolean(true);
private final AtomicBoolean inReceive = new AtomicBoolean(false);
private final AtomicInteger lastReceiveCounter = new AtomicInteger(0);
private SchedulerTimerTask writeCheckerTask;
private SchedulerTimerTask readCheckerTask;
private long readCheckTime = DEFAULT_CHECK_TIME_MILLS;
private long writeCheckTime = DEFAULT_CHECK_TIME_MILLS;
private long initialDelayTime = DEFAULT_CHECK_TIME_MILLS;
private boolean useKeepAlive = true;
private boolean keepAliveResponseRequired;
protected WireFormat wireFormat;
private final Runnable readChecker = new Runnable() {
long lastRunTime;
public void run() {
long now = System.currentTimeMillis();
long elapsed = (now-lastRunTime);
if( lastRunTime != 0 && LOG.isDebugEnabled() ) {
LOG.debug(""+elapsed+" ms elapsed since last read check.");
}
// Perhaps the timer executed a read check late.. and then executes
// the next read check on time which causes the time elapsed between
// read checks to be small..
// If less than 90% of the read check Time elapsed then abort this readcheck.
if( !allowReadCheck(elapsed) ) { // FUNKY qdox bug does not allow me to inline this expression.
LOG.debug("Aborting read check.. Not enough time elapsed since last read check.");
return;
}
lastRunTime = now;
readCheck();
}
};
private boolean allowReadCheck(long elapsed) {
return elapsed > (readCheckTime * 9 / 10);
}
private final Runnable writeChecker = new Runnable() {
long lastRunTime;
public void run() {
long now = System.currentTimeMillis();
if( lastRunTime != 0 && LOG.isDebugEnabled() ) {
LOG.debug(this + " "+(now-lastRunTime)+" ms elapsed since last write check.");
}
lastRunTime = now;
writeCheck();
}
};
public AbstractInactivityMonitor(Transport next, WireFormat wireFormat) {
super(next);
this.wireFormat = wireFormat;
}
public void start() throws Exception {
next.start();
startMonitorThreads();
}
public void stop() throws Exception {
stopMonitorThreads();
next.stop();
}
final void writeCheck() {
if (inSend.get()) {
if (LOG.isTraceEnabled()) {
LOG.trace("A send is in progress");
}
return;
}
if (!commandSent.get() && useKeepAlive) {
if (LOG.isTraceEnabled()) {
LOG.trace(this + " no message sent since last write check, sending a KeepAliveInfo");
}
ASYNC_TASKS.execute(new Runnable() {
public void run() {
if (monitorStarted.get()) {
try {
KeepAliveInfo info = new KeepAliveInfo();
info.setResponseRequired(keepAliveResponseRequired);
oneway(info);
} catch (IOException e) {
onException(e);
}
}
};
});
} else {
if (LOG.isTraceEnabled()) {
LOG.trace(this + " message sent since last write check, resetting flag");
}
}
commandSent.set(false);
}
final void readCheck() {
int currentCounter = next.getReceiveCounter();
int previousCounter = lastReceiveCounter.getAndSet(currentCounter);
if (inReceive.get() || currentCounter!=previousCounter ) {
if (LOG.isTraceEnabled()) {
LOG.trace("A receive is in progress");
}
return;
}
if (!commandReceived.get()) {
if (LOG.isDebugEnabled()) {
LOG.debug("No message received since last read check for " + toString() + "! Throwing InactivityIOException.");
}
ASYNC_TASKS.execute(new Runnable() {
public void run() {
onException(new InactivityIOException("Channel was inactive for too (>" + readCheckTime + ") long: "+next.getRemoteAddress()));
};
});
} else {
if (LOG.isTraceEnabled()) {
LOG.trace("Message received since last read check, resetting flag: ");
}
}
commandReceived.set(false);
}
protected abstract void processInboundWireFormatInfo(WireFormatInfo info) throws IOException;
protected abstract void processOutboundWireFormatInfo(WireFormatInfo info) throws IOException;
public void onCommand(Object command) {
commandReceived.set(true);
inReceive.set(true);
try {
if (command.getClass() == KeepAliveInfo.class) {
KeepAliveInfo info = (KeepAliveInfo) command;
if (info.isResponseRequired()) {
try {
info.setResponseRequired(false);
oneway(info);
} catch (IOException e) {
onException(e);
}
}
} else {
if (command.getClass() == WireFormatInfo.class) {
synchronized (this) {
try {
processInboundWireFormatInfo((WireFormatInfo) command);
} catch (IOException e) {
onException(e);
}
}
}
synchronized (readChecker) {
transportListener.onCommand(command);
}
}
} finally {
inReceive.set(false);
}
}
public void oneway(Object o) throws IOException {
// Disable inactivity monitoring while processing a command.
// synchronize this method - its not synchronized
// further down the transport stack and gets called by more
// than one thread by this class
synchronized(inSend) {
inSend.set(true);
try {
if( failed.get() ) {
throw new InactivityIOException("Cannot send, channel has already failed: "+next.getRemoteAddress());
}
if (o.getClass() == WireFormatInfo.class) {
synchronized (this) {
processOutboundWireFormatInfo((WireFormatInfo) o);
}
}
next.oneway(o);
} finally {
commandSent.set(true);
inSend.set(false);
}
}
}
public void onException(IOException error) {
if (failed.compareAndSet(false, true)) {
stopMonitorThreads();
transportListener.onException(error);
}
}
public void setUseKeepAlive(boolean val) {
useKeepAlive = val;
}
public long getReadCheckTime() {
return readCheckTime;
}
public void setReadCheckTime(long readCheckTime) {
this.readCheckTime = readCheckTime;
}
public long getWriteCheckTime() {
return writeCheckTime;
}
public void setWriteCheckTime(long writeCheckTime) {
this.writeCheckTime = writeCheckTime;
}
public long getInitialDelayTime() {
return initialDelayTime;
}
public void setInitialDelayTime(long initialDelayTime) {
this.initialDelayTime = initialDelayTime;
}
public boolean isKeepAliveResponseRequired() {
return this.keepAliveResponseRequired;
}
public void setKeepAliveResponseRequired(boolean value) {
this.keepAliveResponseRequired = value;
}
public boolean isMonitorStarted() {
return this.monitorStarted.get();
}
protected synchronized void startMonitorThreads() throws IOException {
if (monitorStarted.get()) {
return;
}
if (!configuredOk()) {
return;
}
if (readCheckTime > 0) {
readCheckerTask = new SchedulerTimerTask(readChecker);
}
if (writeCheckTime > 0) {
writeCheckerTask = new SchedulerTimerTask(writeChecker);
}
if (writeCheckTime > 0 || readCheckTime > 0) {
monitorStarted.set(true);
synchronized(AbstractInactivityMonitor.class) {
if( CHECKER_COUNTER == 0 ) {
ASYNC_TASKS = createExecutor();
READ_CHECK_TIMER = new Timer("InactivityMonitor ReadCheck",true);
WRITE_CHECK_TIMER = new Timer("InactivityMonitor WriteCheck",true);
}
CHECKER_COUNTER++;
if (readCheckTime > 0) {
READ_CHECK_TIMER.schedule(readCheckerTask, initialDelayTime, readCheckTime);
}
if (writeCheckTime > 0) {
WRITE_CHECK_TIMER.schedule(writeCheckerTask, initialDelayTime, writeCheckTime);
}
}
}
}
abstract protected boolean configuredOk() throws IOException;
protected synchronized void stopMonitorThreads() {
if (monitorStarted.compareAndSet(true, false)) {
if (readCheckerTask != null) {
readCheckerTask.cancel();
}
if (writeCheckerTask != null) {
writeCheckerTask.cancel();
}
synchronized( AbstractInactivityMonitor.class ) {
WRITE_CHECK_TIMER.purge();
READ_CHECK_TIMER.purge();
CHECKER_COUNTER--;
if(CHECKER_COUNTER==0) {
WRITE_CHECK_TIMER.cancel();
READ_CHECK_TIMER.cancel();
WRITE_CHECK_TIMER = null;
READ_CHECK_TIMER = null;
ASYNC_TASKS.shutdownNow();
ASYNC_TASKS = null;
}
}
}
}
private ThreadFactory factory = new ThreadFactory() {
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(runnable, "InactivityMonitor Async Task: "+runnable);
thread.setDaemon(true);
return thread;
}
};
private ThreadPoolExecutor createExecutor() {
ThreadPoolExecutor exec = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 10, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), factory);
exec.allowCoreThreadTimeOut(true);
return exec;
}
}

View File

@ -17,17 +17,8 @@
package org.apache.activemq.transport; package org.apache.activemq.transport;
import java.io.IOException; import java.io.IOException;
import java.util.Timer;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.activemq.command.KeepAliveInfo;
import org.apache.activemq.command.WireFormatInfo; import org.apache.activemq.command.WireFormatInfo;
import org.apache.activemq.thread.SchedulerTimerTask;
import org.apache.activemq.wireformat.WireFormat; import org.apache.activemq.wireformat.WireFormat;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -35,293 +26,59 @@ import org.slf4j.LoggerFactory;
/** /**
* Used to make sure that commands are arriving periodically from the peer of * Used to make sure that commands are arriving periodically from the peer of
* the transport. * the transport.
*
*
*/ */
public class InactivityMonitor extends TransportFilter { public class InactivityMonitor extends AbstractInactivityMonitor {
private static final Logger LOG = LoggerFactory.getLogger(InactivityMonitor.class); private static final Logger LOG = LoggerFactory.getLogger(InactivityMonitor.class);
private static ThreadPoolExecutor ASYNC_TASKS;
private static int CHECKER_COUNTER;
private static long DEFAULT_CHECK_TIME_MILLS = 30000;
private static Timer READ_CHECK_TIMER;
private static Timer WRITE_CHECK_TIMER;
private WireFormatInfo localWireFormatInfo; private WireFormatInfo localWireFormatInfo;
private WireFormatInfo remoteWireFormatInfo; private WireFormatInfo remoteWireFormatInfo;
private final AtomicBoolean monitorStarted = new AtomicBoolean(false);
private final AtomicBoolean commandSent = new AtomicBoolean(false);
private final AtomicBoolean inSend = new AtomicBoolean(false);
private final AtomicBoolean failed = new AtomicBoolean(false);
private final AtomicBoolean commandReceived = new AtomicBoolean(true);
private final AtomicBoolean inReceive = new AtomicBoolean(false);
private final AtomicInteger lastReceiveCounter = new AtomicInteger(0);
private SchedulerTimerTask writeCheckerTask;
private SchedulerTimerTask readCheckerTask;
private boolean ignoreRemoteWireFormat = false; private boolean ignoreRemoteWireFormat = false;
private boolean ignoreAllWireFormatInfo = false; private boolean ignoreAllWireFormatInfo = false;
private long readCheckTime = DEFAULT_CHECK_TIME_MILLS;
private long writeCheckTime = DEFAULT_CHECK_TIME_MILLS;
private long initialDelayTime = DEFAULT_CHECK_TIME_MILLS;
private boolean useKeepAlive = true;
private boolean keepAliveResponseRequired;
private WireFormat wireFormat;
private final Runnable readChecker = new Runnable() {
long lastRunTime;
public void run() {
long now = System.currentTimeMillis();
long elapsed = (now-lastRunTime);
if( lastRunTime != 0 && LOG.isDebugEnabled() ) {
LOG.debug(""+elapsed+" ms elapsed since last read check.");
}
// Perhaps the timer executed a read check late.. and then executes
// the next read check on time which causes the time elapsed between
// read checks to be small..
// If less than 90% of the read check Time elapsed then abort this readcheck.
if( !allowReadCheck(elapsed) ) { // FUNKY qdox bug does not allow me to inline this expression.
LOG.debug("Aborting read check.. Not enough time elapsed since last read check.");
return;
}
lastRunTime = now;
readCheck();
}
};
private boolean allowReadCheck(long elapsed) {
return elapsed > (readCheckTime * 9 / 10);
}
private final Runnable writeChecker = new Runnable() {
long lastRunTime;
public void run() {
long now = System.currentTimeMillis();
if( lastRunTime != 0 && LOG.isDebugEnabled() ) {
LOG.debug(this + " "+(now-lastRunTime)+" ms elapsed since last write check.");
}
lastRunTime = now;
writeCheck();
}
};
public InactivityMonitor(Transport next, WireFormat wireFormat) { public InactivityMonitor(Transport next, WireFormat wireFormat) {
super(next); super(next, wireFormat);
this.wireFormat = wireFormat;
if (this.wireFormat == null) { if (this.wireFormat == null) {
this.ignoreAllWireFormatInfo = true; this.ignoreAllWireFormatInfo = true;
} }
} }
public void start() throws Exception { protected void processInboundWireFormatInfo(WireFormatInfo info) throws IOException {
next.start(); IOException error = null;
remoteWireFormatInfo = info;
try {
startMonitorThreads();
} catch (IOException e) {
error = e;
}
if (error != null) {
onException(error);
}
}
protected void processOutboundWireFormatInfo(WireFormatInfo info) throws IOException{
localWireFormatInfo = info;
startMonitorThreads(); startMonitorThreads();
} }
public void stop() throws Exception { @Override
stopMonitorThreads(); protected synchronized void startMonitorThreads() throws IOException {
next.stop(); if (isMonitorStarted()) {
}
final void writeCheck() {
if (inSend.get()) {
if (LOG.isTraceEnabled()) {
LOG.trace("A send is in progress");
}
return; return;
} }
if (!commandSent.get() && useKeepAlive) { long readCheckTime = getReadCheckTime();
if (LOG.isTraceEnabled()) {
LOG.trace(this + " no message sent since last write check, sending a KeepAliveInfo");
}
ASYNC_TASKS.execute(new Runnable() {
public void run() {
if (monitorStarted.get()) {
try {
KeepAliveInfo info = new KeepAliveInfo();
info.setResponseRequired(keepAliveResponseRequired);
oneway(info);
} catch (IOException e) {
onException(e);
}
}
};
});
} else {
if (LOG.isTraceEnabled()) {
LOG.trace(this + " message sent since last write check, resetting flag");
}
}
commandSent.set(false);
}
final void readCheck() {
int currentCounter = next.getReceiveCounter();
int previousCounter = lastReceiveCounter.getAndSet(currentCounter);
if (inReceive.get() || currentCounter!=previousCounter ) {
if (LOG.isTraceEnabled()) {
LOG.trace("A receive is in progress");
}
return;
}
if (!commandReceived.get()) {
if (LOG.isDebugEnabled()) {
LOG.debug("No message received since last read check for " + toString() + "! Throwing InactivityIOException.");
}
ASYNC_TASKS.execute(new Runnable() {
public void run() {
onException(new InactivityIOException("Channel was inactive for too (>" + readCheckTime + ") long: "+next.getRemoteAddress()));
};
});
} else {
if (LOG.isTraceEnabled()) {
LOG.trace("Message received since last read check, resetting flag: ");
}
}
commandReceived.set(false);
}
public void onCommand(Object command) {
commandReceived.set(true);
inReceive.set(true);
try {
if (command.getClass() == KeepAliveInfo.class) {
KeepAliveInfo info = (KeepAliveInfo) command;
if (info.isResponseRequired()) {
try {
info.setResponseRequired(false);
oneway(info);
} catch (IOException e) {
onException(e);
}
}
} else {
if (command.getClass() == WireFormatInfo.class) {
synchronized (this) {
IOException error = null;
remoteWireFormatInfo = (WireFormatInfo) command;
try {
startMonitorThreads();
} catch (IOException e) {
error = e;
}
if (error != null) {
onException(error);
}
}
}
synchronized (readChecker) {
transportListener.onCommand(command);
}
}
} finally {
inReceive.set(false);
}
}
public void oneway(Object o) throws IOException {
// Disable inactivity monitoring while processing a command.
//synchronize this method - its not synchronized
//further down the transport stack and gets called by more
//than one thread by this class
synchronized(inSend) {
inSend.set(true);
try {
if( failed.get() ) {
throw new InactivityIOException("Cannot send, channel has already failed: "+next.getRemoteAddress());
}
if (o.getClass() == WireFormatInfo.class) {
synchronized (this) {
localWireFormatInfo = (WireFormatInfo)o;
startMonitorThreads();
}
}
next.oneway(o);
} finally {
commandSent.set(true);
inSend.set(false);
}
}
}
public void onException(IOException error) {
if (failed.compareAndSet(false, true)) {
stopMonitorThreads();
transportListener.onException(error);
}
}
public void setKeepAliveResponseRequired(boolean val) {
keepAliveResponseRequired = val;
}
public void setUseKeepAlive(boolean val) {
useKeepAlive = val;
}
public void setIgnoreRemoteWireFormat(boolean val) {
ignoreRemoteWireFormat = val;
}
public long getReadCheckTime() {
return readCheckTime;
}
public void setReadCheckTime(long readCheckTime) {
this.readCheckTime = readCheckTime;
}
public long getInitialDelayTime() {
return initialDelayTime;
}
public void setInitialDelayTime(long initialDelayTime) {
this.initialDelayTime = initialDelayTime;
}
private synchronized void startMonitorThreads() throws IOException {
if (monitorStarted.get()) {
return;
}
if (!configuredOk()) {
return;
}
if (readCheckTime > 0) { if (readCheckTime > 0) {
monitorStarted.set(true); setWriteCheckTime(readCheckTime>3 ? readCheckTime/3 : readCheckTime);
writeCheckerTask = new SchedulerTimerTask(writeChecker);
readCheckerTask = new SchedulerTimerTask(readChecker);
writeCheckTime = readCheckTime>3 ? readCheckTime/3 : readCheckTime;
synchronized( InactivityMonitor.class ) {
if( CHECKER_COUNTER == 0 ) {
ASYNC_TASKS = createExecutor();
READ_CHECK_TIMER = new Timer("InactivityMonitor ReadCheck",true);
WRITE_CHECK_TIMER = new Timer("InactivityMonitor WriteCheck",true);
}
CHECKER_COUNTER++;
WRITE_CHECK_TIMER.schedule(writeCheckerTask, initialDelayTime, writeCheckTime);
READ_CHECK_TIMER.schedule(readCheckerTask, initialDelayTime, readCheckTime);
}
} }
super.startMonitorThreads();
} }
private boolean configuredOk() throws IOException { @Override
protected boolean configuredOk() throws IOException {
boolean configured = false; boolean configured = false;
if (ignoreAllWireFormatInfo) { if (ignoreAllWireFormatInfo) {
configured = true; configured = true;
@ -330,54 +87,29 @@ public class InactivityMonitor extends TransportFilter {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("Using min of local: " + localWireFormatInfo + " and remote: " + remoteWireFormatInfo); LOG.debug("Using min of local: " + localWireFormatInfo + " and remote: " + remoteWireFormatInfo);
} }
readCheckTime = Math.min(localWireFormatInfo.getMaxInactivityDuration(), remoteWireFormatInfo.getMaxInactivityDuration());
initialDelayTime = Math.min(localWireFormatInfo.getMaxInactivityDurationInitalDelay(), remoteWireFormatInfo.getMaxInactivityDurationInitalDelay()); long readCheckTime = Math.min(localWireFormatInfo.getMaxInactivityDuration(), remoteWireFormatInfo.getMaxInactivityDuration());
long writeCheckTime = readCheckTime>3 ? readCheckTime/3 : readCheckTime;
setReadCheckTime(readCheckTime);
setInitialDelayTime(Math.min(localWireFormatInfo.getMaxInactivityDurationInitalDelay(), remoteWireFormatInfo.getMaxInactivityDurationInitalDelay()));
setWriteCheckTime(writeCheckTime);
} else { } else {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("Using local: " + localWireFormatInfo); LOG.debug("Using local: " + localWireFormatInfo);
} }
readCheckTime = localWireFormatInfo.getMaxInactivityDuration();
initialDelayTime = localWireFormatInfo.getMaxInactivityDurationInitalDelay(); long readCheckTime = localWireFormatInfo.getMaxInactivityDuration();
long writeCheckTime = readCheckTime>3 ? readCheckTime/3 : readCheckTime;
setReadCheckTime(readCheckTime);
setInitialDelayTime(localWireFormatInfo.getMaxInactivityDurationInitalDelay());
setWriteCheckTime(writeCheckTime);
} }
configured = true; configured = true;
} }
return configured; return configured;
} }
/**
*
*/
private synchronized void stopMonitorThreads() {
if (monitorStarted.compareAndSet(true, false)) {
readCheckerTask.cancel();
writeCheckerTask.cancel();
synchronized( InactivityMonitor.class ) {
WRITE_CHECK_TIMER.purge();
READ_CHECK_TIMER.purge();
CHECKER_COUNTER--;
if(CHECKER_COUNTER==0) {
WRITE_CHECK_TIMER.cancel();
READ_CHECK_TIMER.cancel();
WRITE_CHECK_TIMER = null;
READ_CHECK_TIMER = null;
ASYNC_TASKS.shutdownNow();
ASYNC_TASKS = null;
}
}
}
}
private ThreadFactory factory = new ThreadFactory() {
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(runnable, "InactivityMonitor Async Task: "+runnable);
thread.setDaemon(true);
return thread;
}
};
private ThreadPoolExecutor createExecutor() {
ThreadPoolExecutor exec = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 10, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), factory);
exec.allowCoreThreadTimeOut(true);
return exec;
}
} }

View File

@ -23,14 +23,12 @@ import org.apache.activemq.Service;
/** /**
* Represents the client side of a transport allowing messages to be sent * Represents the client side of a transport allowing messages to be sent
* synchronously, asynchronously and consumed. * synchronously, asynchronously and consumed.
*
*
*/ */
public interface Transport extends Service { public interface Transport extends Service {
/** /**
* A one way asynchronous send * A one way asynchronous send
* *
* @param command * @param command
* @throws IOException * @throws IOException
*/ */
@ -40,7 +38,7 @@ public interface Transport extends Service {
* An asynchronous request response where the Receipt will be returned in * An asynchronous request response where the Receipt will be returned in
* the future. If responseCallback is not null, then it will be called when * the future. If responseCallback is not null, then it will be called when
* the response has been completed. * the response has been completed.
* *
* @param command * @param command
* @param responseCallback TODO * @param responseCallback TODO
* @return the FutureResponse * @return the FutureResponse
@ -50,7 +48,7 @@ public interface Transport extends Service {
/** /**
* A synchronous request response * A synchronous request response
* *
* @param command * @param command
* @return the response * @return the response
* @throws IOException * @throws IOException
@ -59,7 +57,7 @@ public interface Transport extends Service {
/** /**
* A synchronous request response * A synchronous request response
* *
* @param command * @param command
* @param timeout * @param timeout
* @return the repsonse or null if timeout * @return the repsonse or null if timeout
@ -67,53 +65,16 @@ public interface Transport extends Service {
*/ */
Object request(Object command, int timeout) throws IOException; Object request(Object command, int timeout) throws IOException;
// /**
// * A one way asynchronous send
// * @param command
// * @throws IOException
// */
// void oneway(Command command) throws IOException;
//
// /**
// * An asynchronous request response where the Receipt will be returned
// * in the future. If responseCallback is not null, then it will be called
// * when the response has been completed.
// *
// * @param command
// * @param responseCallback TODO
// * @return the FutureResponse
// * @throws IOException
// */
// FutureResponse asyncRequest(Command command, ResponseCallback
// responseCallback) throws IOException;
//
// /**
// * A synchronous request response
// * @param command
// * @return the response
// * @throws IOException
// */
// Response request(Command command) throws IOException;
//
// /**
// * A synchronous request response
// * @param command
// * @param timeout
// * @return the repsonse or null if timeout
// * @throws IOException
// */
// Response request(Command command, int timeout) throws IOException;
/** /**
* Returns the current transport listener * Returns the current transport listener
* *
* @return * @return
*/ */
TransportListener getTransportListener(); TransportListener getTransportListener();
/** /**
* Registers an inbound command listener * Registers an inbound command listener
* *
* @param commandListener * @param commandListener
*/ */
void setTransportListener(TransportListener commandListener); void setTransportListener(TransportListener commandListener);
@ -131,26 +92,26 @@ public interface Transport extends Service {
/** /**
* Indicates if the transport can handle faults * Indicates if the transport can handle faults
* *
* @return true if fault tolerant * @return true if fault tolerant
*/ */
boolean isFaultTolerant(); boolean isFaultTolerant();
/** /**
* @return true if the transport is disposed * @return true if the transport is disposed
*/ */
boolean isDisposed(); boolean isDisposed();
/** /**
* @return true if the transport is connected * @return true if the transport is connected
*/ */
boolean isConnected(); boolean isConnected();
/** /**
* @return true if reconnect is supported * @return true if reconnect is supported
*/ */
boolean isReconnectSupported(); boolean isReconnectSupported();
/** /**
* @return true if updating uris is supported * @return true if updating uris is supported
*/ */
@ -161,10 +122,10 @@ public interface Transport extends Service {
* @throws IOException on failure of if not supported * @throws IOException on failure of if not supported
*/ */
void reconnect(URI uri) throws IOException; void reconnect(URI uri) throws IOException;
/** /**
* Provide a list of available alternative locations * Provide a list of available alternative locations
* @param rebalance * @param rebalance
* @param uris * @param uris
* @throws IOException * @throws IOException
*/ */
@ -172,10 +133,10 @@ public interface Transport extends Service {
/** /**
* Returns a counter which gets incremented as data is read from the transport. * Returns a counter which gets incremented as data is read from the transport.
* It should only be used to determine if there is progress being made in reading the next command from the transport. * It should only be used to determine if there is progress being made in reading the next command from the transport.
* The value may wrap into the negative numbers. * The value may wrap into the negative numbers.
* *
* @return a counter which gets incremented as data is read from the transport. * @return a counter which gets incremented as data is read from the transport.
*/ */
int getReceiveCounter(); int getReceiveCounter();
} }

View File

@ -44,7 +44,7 @@ public abstract class TransportFactory {
private static final String WRITE_TIMEOUT_FILTER = "soWriteTimeout"; private static final String WRITE_TIMEOUT_FILTER = "soWriteTimeout";
private static final String THREAD_NAME_FILTER = "threadName"; private static final String THREAD_NAME_FILTER = "threadName";
public abstract TransportServer doBind(URI location) throws IOException; public abstract TransportServer doBind(URI location) throws IOException;
public Transport doConnect(URI location, Executor ex) throws Exception { public Transport doConnect(URI location, Executor ex) throws Exception {
@ -57,7 +57,7 @@ public abstract class TransportFactory {
/** /**
* Creates a normal transport. * Creates a normal transport.
* *
* @param location * @param location
* @return the transport * @return the transport
* @throws Exception * @throws Exception
@ -69,7 +69,7 @@ public abstract class TransportFactory {
/** /**
* Creates a normal transport. * Creates a normal transport.
* *
* @param location * @param location
* @param ex * @param ex
* @return the transport * @return the transport
@ -83,7 +83,7 @@ public abstract class TransportFactory {
/** /**
* Creates a slimmed down transport that is more efficient so that it can be * Creates a slimmed down transport that is more efficient so that it can be
* used by composite transports like reliable and HA. * used by composite transports like reliable and HA.
* *
* @param location * @param location
* @return the Transport * @return the Transport
* @throws Exception * @throws Exception
@ -96,7 +96,7 @@ public abstract class TransportFactory {
/** /**
* Creates a slimmed down transport that is more efficient so that it can be * Creates a slimmed down transport that is more efficient so that it can be
* used by composite transports like reliable and HA. * used by composite transports like reliable and HA.
* *
* @param location * @param location
* @param ex * @param ex
* @return the Transport * @return the Transport
@ -113,12 +113,12 @@ public abstract class TransportFactory {
} }
/** /**
* @deprecated * @deprecated
*/ */
public static TransportServer bind(String brokerId, URI location) throws IOException { public static TransportServer bind(String brokerId, URI location) throws IOException {
return bind(location); return bind(location);
} }
public static TransportServer bind(BrokerService brokerService, URI location) throws IOException { public static TransportServer bind(BrokerService brokerService, URI location) throws IOException {
TransportFactory tf = findTransportFactory(location); TransportFactory tf = findTransportFactory(location);
if( brokerService!=null && tf instanceof BrokerServiceAware ) { if( brokerService!=null && tf instanceof BrokerServiceAware ) {
@ -132,7 +132,7 @@ public abstract class TransportFactory {
} finally { } finally {
SslContext.setCurrentSslContext(null); SslContext.setCurrentSslContext(null);
} }
} }
public Transport doConnect(URI location) throws Exception { public Transport doConnect(URI location) throws Exception {
try { try {
@ -164,7 +164,7 @@ public abstract class TransportFactory {
throw IOExceptionSupport.create(e); throw IOExceptionSupport.create(e);
} }
} }
/** /**
* Allow registration of a transport factory without wiring via META-INF classes * Allow registration of a transport factory without wiring via META-INF classes
* @param scheme * @param scheme
@ -176,7 +176,7 @@ public abstract class TransportFactory {
/** /**
* Factory method to create a new transport * Factory method to create a new transport
* *
* @throws IOException * @throws IOException
* @throws UnknownHostException * @throws UnknownHostException
*/ */
@ -235,13 +235,14 @@ public abstract class TransportFactory {
/** /**
* Fully configures and adds all need transport filters so that the * Fully configures and adds all need transport filters so that the
* transport can be used by the JMS client. * transport can be used by the JMS client.
* *
* @param transport * @param transport
* @param wf * @param wf
* @param options * @param options
* @return * @return
* @throws Exception * @throws Exception
*/ */
@SuppressWarnings("rawtypes")
public Transport configure(Transport transport, WireFormat wf, Map options) throws Exception { public Transport configure(Transport transport, WireFormat wf, Map options) throws Exception {
transport = compositeConfigure(transport, wf, options); transport = compositeConfigure(transport, wf, options);
@ -256,14 +257,15 @@ public abstract class TransportFactory {
* transport can be used by the ActiveMQ message broker. The main difference * transport can be used by the ActiveMQ message broker. The main difference
* between this and the configure() method is that the broker does not issue * between this and the configure() method is that the broker does not issue
* requests to the client so the ResponseCorrelator is not needed. * requests to the client so the ResponseCorrelator is not needed.
* *
* @param transport * @param transport
* @param format * @param format
* @param options * @param options
* @return * @return
* @throws Exception * @throws Exception
*/ */
public Transport serverConfigure(Transport transport, WireFormat format, HashMap options) throws Exception { @SuppressWarnings("rawtypes")
public Transport serverConfigure(Transport transport, WireFormat format, HashMap options) throws Exception {
if (options.containsKey(THREAD_NAME_FILTER)) { if (options.containsKey(THREAD_NAME_FILTER)) {
transport = new ThreadNameFilter(transport); transport = new ThreadNameFilter(transport);
} }
@ -276,12 +278,13 @@ public abstract class TransportFactory {
* Similar to configure(...) but this avoid adding in the MutexTransport and * Similar to configure(...) but this avoid adding in the MutexTransport and
* ResponseCorrelator transport layers so that the resulting transport can * ResponseCorrelator transport layers so that the resulting transport can
* more efficiently be used as part of a composite transport. * more efficiently be used as part of a composite transport.
* *
* @param transport * @param transport
* @param format * @param format
* @param options * @param options
* @return * @return
*/ */
@SuppressWarnings("rawtypes")
public Transport compositeConfigure(Transport transport, WireFormat format, Map options) { public Transport compositeConfigure(Transport transport, WireFormat format, Map options) {
if (options.containsKey(WRITE_TIMEOUT_FILTER)) { if (options.containsKey(WRITE_TIMEOUT_FILTER)) {
transport = new WriteTimeoutFilter(transport); transport = new WriteTimeoutFilter(transport);
@ -294,6 +297,7 @@ public abstract class TransportFactory {
return transport; return transport;
} }
@SuppressWarnings("rawtypes")
protected String getOption(Map options, String key, String def) { protected String getOption(Map options, String key, String def) {
String rc = (String) options.remove(key); String rc = (String) options.remove(key);
if( rc == null ) { if( rc == null ) {

View File

@ -22,10 +22,7 @@ import java.net.Socket;
import java.util.Iterator; import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.activemq.transport.tcp.TcpBufferedOutputStream;
import org.apache.activemq.transport.tcp.TimeStampStream; import org.apache.activemq.transport.tcp.TimeStampStream;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -39,15 +36,15 @@ import org.slf4j.LoggerFactory;
* <code>transport.soWriteTimeout=<value in millis></code>.<br/> * <code>transport.soWriteTimeout=<value in millis></code>.<br/>
* For example (15 second timeout on write operations to the socket):</br> * For example (15 second timeout on write operations to the socket):</br>
* <pre><code> * <pre><code>
* &lt;transportConnector * &lt;transportConnector
* name=&quot;tcp1&quot; * name=&quot;tcp1&quot;
* uri=&quot;tcp://127.0.0.1:61616?transport.soTimeout=10000&amp;transport.soWriteTimeout=15000" * uri=&quot;tcp://127.0.0.1:61616?transport.soTimeout=10000&amp;transport.soWriteTimeout=15000"
* /&gt; * /&gt;
* </code></pre><br/> * </code></pre><br/>
* For example (enable default timeout on the socket):</br> * For example (enable default timeout on the socket):</br>
* <pre><code> * <pre><code>
* &lt;transportConnector * &lt;transportConnector
* name=&quot;tcp1&quot; * name=&quot;tcp1&quot;
* uri=&quot;tcp://127.0.0.1:61616?transport.soTimeout=10000&amp;transport.soWriteTimeout=15000" * uri=&quot;tcp://127.0.0.1:61616?transport.soTimeout=10000&amp;transport.soWriteTimeout=15000"
* /&gt; * /&gt;
* </code></pre> * </code></pre>
@ -59,12 +56,12 @@ public class WriteTimeoutFilter extends TransportFilter {
private static final Logger LOG = LoggerFactory.getLogger(WriteTimeoutFilter.class); private static final Logger LOG = LoggerFactory.getLogger(WriteTimeoutFilter.class);
protected static ConcurrentLinkedQueue<WriteTimeoutFilter> writers = new ConcurrentLinkedQueue<WriteTimeoutFilter>(); protected static ConcurrentLinkedQueue<WriteTimeoutFilter> writers = new ConcurrentLinkedQueue<WriteTimeoutFilter>();
protected static AtomicInteger messageCounter = new AtomicInteger(0); protected static AtomicInteger messageCounter = new AtomicInteger(0);
protected static TimeoutThread timeoutThread = new TimeoutThread(); protected static TimeoutThread timeoutThread = new TimeoutThread();
protected static long sleep = 5000l; protected static long sleep = 5000l;
protected long writeTimeout = -1; protected long writeTimeout = -1;
public WriteTimeoutFilter(Transport next) { public WriteTimeoutFilter(Transport next) {
super(next); super(next);
} }
@ -80,7 +77,7 @@ public class WriteTimeoutFilter extends TransportFilter {
deRegisterWrite(this,false,null); deRegisterWrite(this,false,null);
} }
} }
public long getWriteTimeout() { public long getWriteTimeout() {
return writeTimeout; return writeTimeout;
} }
@ -88,7 +85,7 @@ public class WriteTimeoutFilter extends TransportFilter {
public void setWriteTimeout(long writeTimeout) { public void setWriteTimeout(long writeTimeout) {
this.writeTimeout = writeTimeout; this.writeTimeout = writeTimeout;
} }
public static long getSleep() { public static long getSleep() {
return sleep; return sleep;
} }
@ -97,21 +94,21 @@ public class WriteTimeoutFilter extends TransportFilter {
WriteTimeoutFilter.sleep = sleep; WriteTimeoutFilter.sleep = sleep;
} }
protected TimeStampStream getWriter() { protected TimeStampStream getWriter() {
return next.narrow(TimeStampStream.class); return next.narrow(TimeStampStream.class);
} }
protected Socket getSocket() { protected Socket getSocket() {
return next.narrow(Socket.class); return next.narrow(Socket.class);
} }
protected static void registerWrite(WriteTimeoutFilter filter) { protected static void registerWrite(WriteTimeoutFilter filter) {
writers.add(filter); writers.add(filter);
} }
protected static boolean deRegisterWrite(WriteTimeoutFilter filter, boolean fail, IOException iox) { protected static boolean deRegisterWrite(WriteTimeoutFilter filter, boolean fail, IOException iox) {
boolean result = writers.remove(filter); boolean result = writers.remove(filter);
if (result) { if (result) {
if (fail) { if (fail) {
String message = "Forced write timeout for:"+filter.getNext().getRemoteAddress(); String message = "Forced write timeout for:"+filter.getNext().getRemoteAddress();
@ -129,17 +126,17 @@ public class WriteTimeoutFilter extends TransportFilter {
} }
return result; return result;
} }
@Override @Override
public void start() throws Exception { public void start() throws Exception {
super.start(); super.start();
} }
@Override @Override
public void stop() throws Exception { public void stop() throws Exception {
super.stop(); super.stop();
} }
protected static class TimeoutThread extends Thread { protected static class TimeoutThread extends Thread {
static AtomicInteger instance = new AtomicInteger(0); static AtomicInteger instance = new AtomicInteger(0);
boolean run = true; boolean run = true;
@ -150,14 +147,14 @@ public class WriteTimeoutFilter extends TransportFilter {
start(); start();
} }
public void run() { public void run() {
while (run) { while (run) {
boolean error = false; boolean error = false;
try { try {
if (!interrupted()) { if (!interrupted()) {
Iterator<WriteTimeoutFilter> filters = writers.iterator(); Iterator<WriteTimeoutFilter> filters = writers.iterator();
while (run && filters.hasNext()) { while (run && filters.hasNext()) {
WriteTimeoutFilter filter = filters.next(); WriteTimeoutFilter filter = filters.next();
if (filter.getWriteTimeout()<=0) continue; //no timeout set if (filter.getWriteTimeout()<=0) continue; //no timeout set
long writeStart = filter.getWriter().getWriteTimestamp(); long writeStart = filter.getWriter().getWriteTimestamp();

View File

@ -22,12 +22,9 @@ import java.util.Map;
import javax.jms.Destination; import javax.jms.Destination;
import javax.jms.JMSException; import javax.jms.JMSException;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import org.apache.activemq.command.ActiveMQDestination; import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.command.ActiveMQMessage; import org.apache.activemq.command.ActiveMQMessage;
import org.apache.activemq.command.Message;
/** /**
* Implementations of this interface are used to map back and forth from Stomp * Implementations of this interface are used to map back and forth from Stomp

View File

@ -34,8 +34,6 @@ import org.apache.activemq.command.ActiveMQObjectMessage;
import org.apache.activemq.command.DataStructure; import org.apache.activemq.command.DataStructure;
import org.apache.activemq.util.JettisonMappedXmlDriver; import org.apache.activemq.util.JettisonMappedXmlDriver;
import org.codehaus.jettison.mapped.Configuration; import org.codehaus.jettison.mapped.Configuration;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamReader;
@ -50,206 +48,208 @@ import com.thoughtworks.xstream.io.xml.XppReader;
* @author <a href="mailto:dejan@nighttale.net">Dejan Bosanac</a> * @author <a href="mailto:dejan@nighttale.net">Dejan Bosanac</a>
*/ */
public class JmsFrameTranslator extends LegacyFrameTranslator implements public class JmsFrameTranslator extends LegacyFrameTranslator implements
BrokerContextAware { BrokerContextAware {
XStream xStream = null; XStream xStream = null;
BrokerContext brokerContext; BrokerContext brokerContext;
public ActiveMQMessage convertFrame(ProtocolConverter converter, public ActiveMQMessage convertFrame(ProtocolConverter converter,
StompFrame command) throws JMSException, ProtocolException { StompFrame command) throws JMSException, ProtocolException {
Map headers = command.getHeaders(); Map<String, String> headers = command.getHeaders();
ActiveMQMessage msg; ActiveMQMessage msg;
String transformation = (String) headers.get(Stomp.Headers.TRANSFORMATION); String transformation = (String) headers.get(Stomp.Headers.TRANSFORMATION);
if (headers.containsKey(Stomp.Headers.CONTENT_LENGTH) || transformation.equals(Stomp.Transformations.JMS_BYTE.toString())) { if (headers.containsKey(Stomp.Headers.CONTENT_LENGTH) || transformation.equals(Stomp.Transformations.JMS_BYTE.toString())) {
msg = super.convertFrame(converter, command); msg = super.convertFrame(converter, command);
} else { } else {
HierarchicalStreamReader in; HierarchicalStreamReader in;
try { try {
String text = new String(command.getContent(), "UTF-8"); String text = new String(command.getContent(), "UTF-8");
switch (Stomp.Transformations.getValue(transformation)) { switch (Stomp.Transformations.getValue(transformation)) {
case JMS_OBJECT_XML: case JMS_OBJECT_XML:
in = new XppReader(new StringReader(text)); in = new XppReader(new StringReader(text));
msg = createObjectMessage(in); msg = createObjectMessage(in);
break; break;
case JMS_OBJECT_JSON: case JMS_OBJECT_JSON:
in = new JettisonMappedXmlDriver().createReader(new StringReader(text)); in = new JettisonMappedXmlDriver().createReader(new StringReader(text));
msg = createObjectMessage(in); msg = createObjectMessage(in);
break; break;
case JMS_MAP_XML: case JMS_MAP_XML:
in = new XppReader(new StringReader(text)); in = new XppReader(new StringReader(text));
msg = createMapMessage(in); msg = createMapMessage(in);
break; break;
case JMS_MAP_JSON: case JMS_MAP_JSON:
in = new JettisonMappedXmlDriver().createReader(new StringReader(text)); in = new JettisonMappedXmlDriver().createReader(new StringReader(text));
msg = createMapMessage(in); msg = createMapMessage(in);
break; break;
default: default:
throw new Exception("Unkown transformation: " + transformation); throw new Exception("Unkown transformation: " + transformation);
} }
} catch (Throwable e) { } catch (Throwable e) {
command.getHeaders().put(Stomp.Headers.TRANSFORMATION_ERROR, e.getMessage()); command.getHeaders().put(Stomp.Headers.TRANSFORMATION_ERROR, e.getMessage());
msg = super.convertFrame(converter, command); msg = super.convertFrame(converter, command);
} }
} }
FrameTranslator.Helper.copyStandardHeadersFromFrameToMessage(converter, command, msg, this); FrameTranslator.Helper.copyStandardHeadersFromFrameToMessage(converter, command, msg, this);
return msg; return msg;
} }
public StompFrame convertMessage(ProtocolConverter converter, public StompFrame convertMessage(ProtocolConverter converter,
ActiveMQMessage message) throws IOException, JMSException { ActiveMQMessage message) throws IOException, JMSException {
if (message.getDataStructureType() == ActiveMQObjectMessage.DATA_STRUCTURE_TYPE) { if (message.getDataStructureType() == ActiveMQObjectMessage.DATA_STRUCTURE_TYPE) {
StompFrame command = new StompFrame(); StompFrame command = new StompFrame();
command.setAction(Stomp.Responses.MESSAGE); command.setAction(Stomp.Responses.MESSAGE);
Map<String, String> headers = new HashMap<String, String>(25); Map<String, String> headers = new HashMap<String, String>(25);
command.setHeaders(headers); command.setHeaders(headers);
FrameTranslator.Helper.copyStandardHeadersFromMessageToFrame( FrameTranslator.Helper.copyStandardHeadersFromMessageToFrame(
converter, message, command, this); converter, message, command, this);
if (headers.get(Stomp.Headers.TRANSFORMATION).equals(Stomp.Transformations.JMS_XML.toString())) { if (headers.get(Stomp.Headers.TRANSFORMATION).equals(Stomp.Transformations.JMS_XML.toString())) {
headers.put(Stomp.Headers.TRANSFORMATION, Stomp.Transformations.JMS_OBJECT_XML.toString()); headers.put(Stomp.Headers.TRANSFORMATION, Stomp.Transformations.JMS_OBJECT_XML.toString());
} else if (headers.get(Stomp.Headers.TRANSFORMATION).equals(Stomp.Transformations.JMS_JSON.toString())) { } else if (headers.get(Stomp.Headers.TRANSFORMATION).equals(Stomp.Transformations.JMS_JSON.toString())) {
headers.put(Stomp.Headers.TRANSFORMATION, Stomp.Transformations.JMS_OBJECT_JSON.toString()); headers.put(Stomp.Headers.TRANSFORMATION, Stomp.Transformations.JMS_OBJECT_JSON.toString());
} }
ActiveMQObjectMessage msg = (ActiveMQObjectMessage) message.copy(); ActiveMQObjectMessage msg = (ActiveMQObjectMessage) message.copy();
command.setContent(marshall(msg.getObject(), command.setContent(marshall(msg.getObject(),
headers.get(Stomp.Headers.TRANSFORMATION)) headers.get(Stomp.Headers.TRANSFORMATION))
.getBytes("UTF-8")); .getBytes("UTF-8"));
return command; return command;
} else if (message.getDataStructureType() == ActiveMQMapMessage.DATA_STRUCTURE_TYPE) { } else if (message.getDataStructureType() == ActiveMQMapMessage.DATA_STRUCTURE_TYPE) {
StompFrame command = new StompFrame(); StompFrame command = new StompFrame();
command.setAction(Stomp.Responses.MESSAGE); command.setAction(Stomp.Responses.MESSAGE);
Map<String, String> headers = new HashMap<String, String>(25); Map<String, String> headers = new HashMap<String, String>(25);
command.setHeaders(headers); command.setHeaders(headers);
FrameTranslator.Helper.copyStandardHeadersFromMessageToFrame( FrameTranslator.Helper.copyStandardHeadersFromMessageToFrame(
converter, message, command, this); converter, message, command, this);
if (headers.get(Stomp.Headers.TRANSFORMATION).equals(Stomp.Transformations.JMS_XML.toString())) { if (headers.get(Stomp.Headers.TRANSFORMATION).equals(Stomp.Transformations.JMS_XML.toString())) {
headers.put(Stomp.Headers.TRANSFORMATION, Stomp.Transformations.JMS_MAP_XML.toString()); headers.put(Stomp.Headers.TRANSFORMATION, Stomp.Transformations.JMS_MAP_XML.toString());
} else if (headers.get(Stomp.Headers.TRANSFORMATION).equals(Stomp.Transformations.JMS_JSON.toString())) { } else if (headers.get(Stomp.Headers.TRANSFORMATION).equals(Stomp.Transformations.JMS_JSON.toString())) {
headers.put(Stomp.Headers.TRANSFORMATION, Stomp.Transformations.JMS_MAP_JSON.toString()); headers.put(Stomp.Headers.TRANSFORMATION, Stomp.Transformations.JMS_MAP_JSON.toString());
} }
ActiveMQMapMessage msg = (ActiveMQMapMessage) message.copy(); ActiveMQMapMessage msg = (ActiveMQMapMessage) message.copy();
command.setContent(marshall((Serializable)msg.getContentMap(), command.setContent(marshall((Serializable)msg.getContentMap(),
headers.get(Stomp.Headers.TRANSFORMATION)) headers.get(Stomp.Headers.TRANSFORMATION))
.getBytes("UTF-8")); .getBytes("UTF-8"));
return command; return command;
} else if (message.getDataStructureType() == ActiveMQMessage.DATA_STRUCTURE_TYPE && } else if (message.getDataStructureType() == ActiveMQMessage.DATA_STRUCTURE_TYPE &&
AdvisorySupport.ADIVSORY_MESSAGE_TYPE.equals(message.getType())) { AdvisorySupport.ADIVSORY_MESSAGE_TYPE.equals(message.getType())) {
StompFrame command = new StompFrame(); StompFrame command = new StompFrame();
command.setAction(Stomp.Responses.MESSAGE); command.setAction(Stomp.Responses.MESSAGE);
Map<String, String> headers = new HashMap<String, String>(25); Map<String, String> headers = new HashMap<String, String>(25);
command.setHeaders(headers); command.setHeaders(headers);
FrameTranslator.Helper.copyStandardHeadersFromMessageToFrame( FrameTranslator.Helper.copyStandardHeadersFromMessageToFrame(
converter, message, command, this); converter, message, command, this);
if (headers.get(Stomp.Headers.TRANSFORMATION).equals(Stomp.Transformations.JMS_XML.toString())) { if (headers.get(Stomp.Headers.TRANSFORMATION).equals(Stomp.Transformations.JMS_XML.toString())) {
headers.put(Stomp.Headers.TRANSFORMATION, Stomp.Transformations.JMS_ADVISORY_XML.toString()); headers.put(Stomp.Headers.TRANSFORMATION, Stomp.Transformations.JMS_ADVISORY_XML.toString());
} else if (headers.get(Stomp.Headers.TRANSFORMATION).equals(Stomp.Transformations.JMS_JSON.toString())) { } else if (headers.get(Stomp.Headers.TRANSFORMATION).equals(Stomp.Transformations.JMS_JSON.toString())) {
headers.put(Stomp.Headers.TRANSFORMATION, Stomp.Transformations.JMS_ADVISORY_JSON.toString()); headers.put(Stomp.Headers.TRANSFORMATION, Stomp.Transformations.JMS_ADVISORY_JSON.toString());
} }
String body = marshallAdvisory(message.getDataStructure(), String body = marshallAdvisory(message.getDataStructure(),
headers.get(Stomp.Headers.TRANSFORMATION)); headers.get(Stomp.Headers.TRANSFORMATION));
command.setContent(body.getBytes("UTF-8")); command.setContent(body.getBytes("UTF-8"));
return command; return command;
} else { } else {
return super.convertMessage(converter, message); return super.convertMessage(converter, message);
} }
} }
/** /**
* Marshalls the Object to a string using XML or JSON encoding * Marshalls the Object to a string using XML or JSON encoding
*/ */
protected String marshall(Serializable object, String transformation) protected String marshall(Serializable object, String transformation)
throws JMSException { throws JMSException {
StringWriter buffer = new StringWriter(); StringWriter buffer = new StringWriter();
HierarchicalStreamWriter out; HierarchicalStreamWriter out;
if (transformation.toLowerCase().endsWith("json")) { if (transformation.toLowerCase().endsWith("json")) {
out = new JettisonMappedXmlDriver(new Configuration(), false).createWriter(buffer); out = new JettisonMappedXmlDriver(new Configuration(), false).createWriter(buffer);
} else { } else {
out = new PrettyPrintWriter(buffer); out = new PrettyPrintWriter(buffer);
} }
getXStream().marshal(object, out); getXStream().marshal(object, out);
return buffer.toString(); return buffer.toString();
} }
protected ActiveMQObjectMessage createObjectMessage(HierarchicalStreamReader in) throws JMSException { protected ActiveMQObjectMessage createObjectMessage(HierarchicalStreamReader in) throws JMSException {
ActiveMQObjectMessage objMsg = new ActiveMQObjectMessage(); ActiveMQObjectMessage objMsg = new ActiveMQObjectMessage();
Object obj = getXStream().unmarshal(in); Object obj = getXStream().unmarshal(in);
objMsg.setObject((Serializable) obj); objMsg.setObject((Serializable) obj);
return objMsg; return objMsg;
} }
protected ActiveMQMapMessage createMapMessage(HierarchicalStreamReader in) throws JMSException { @SuppressWarnings("unchecked")
ActiveMQMapMessage mapMsg = new ActiveMQMapMessage(); protected ActiveMQMapMessage createMapMessage(HierarchicalStreamReader in) throws JMSException {
Map<String, Object> map = (Map<String, Object>)getXStream().unmarshal(in); ActiveMQMapMessage mapMsg = new ActiveMQMapMessage();
for (String key : map.keySet()) { Map<String, Object> map = (Map<String, Object>)getXStream().unmarshal(in);
mapMsg.setObject(key, map.get(key)); for (String key : map.keySet()) {
} mapMsg.setObject(key, map.get(key));
return mapMsg; }
} return mapMsg;
}
protected String marshallAdvisory(final DataStructure ds, String transformation) { protected String marshallAdvisory(final DataStructure ds, String transformation) {
StringWriter buffer = new StringWriter(); StringWriter buffer = new StringWriter();
HierarchicalStreamWriter out; HierarchicalStreamWriter out;
if (transformation.toLowerCase().endsWith("json")) { if (transformation.toLowerCase().endsWith("json")) {
out = new JettisonMappedXmlDriver().createWriter(buffer); out = new JettisonMappedXmlDriver().createWriter(buffer);
} else { } else {
out = new PrettyPrintWriter(buffer); out = new PrettyPrintWriter(buffer);
} }
XStream xstream = getXStream(); XStream xstream = getXStream();
xstream.setMode(XStream.NO_REFERENCES); xstream.setMode(XStream.NO_REFERENCES);
xstream.aliasPackage("", "org.apache.activemq.command"); xstream.aliasPackage("", "org.apache.activemq.command");
xstream.marshal(ds, out); xstream.marshal(ds, out);
return buffer.toString(); return buffer.toString();
} }
// Properties // Properties
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
public XStream getXStream() { public XStream getXStream() {
if (xStream == null) { if (xStream == null) {
xStream = createXStream(); xStream = createXStream();
} }
return xStream; return xStream;
} }
public void setXStream(XStream xStream) { public void setXStream(XStream xStream) {
this.xStream = xStream; this.xStream = xStream;
} }
// Implementation methods // Implementation methods
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
protected XStream createXStream() { @SuppressWarnings("unchecked")
XStream xstream = null; protected XStream createXStream() {
if (brokerContext != null) { XStream xstream = null;
Map<String, XStream> beans = brokerContext.getBeansOfType(XStream.class); if (brokerContext != null) {
for (XStream bean : beans.values()) { Map<String, XStream> beans = brokerContext.getBeansOfType(XStream.class);
if (bean != null) { for (XStream bean : beans.values()) {
xstream = bean; if (bean != null) {
break; xstream = bean;
} break;
} }
} }
}
if (xstream == null) { if (xstream == null) {
xstream = new XStream(); xstream = new XStream();
} }
return xstream; return xstream;
} }
public void setBrokerContext(BrokerContext brokerContext) { public void setBrokerContext(BrokerContext brokerContext) {
this.brokerContext = brokerContext; this.brokerContext = brokerContext;
} }
} }

View File

@ -16,6 +16,7 @@
*/ */
package org.apache.activemq.transport.stomp; package org.apache.activemq.transport.stomp;
import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -23,10 +24,17 @@ import java.util.Map;
import javax.jms.Destination; import javax.jms.Destination;
import javax.jms.JMSException; import javax.jms.JMSException;
import org.apache.activemq.advisory.AdvisorySupport;
import org.apache.activemq.command.ActiveMQBytesMessage;
import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.command.ActiveMQMessage;
import org.apache.activemq.command.ActiveMQTextMessage;
import org.apache.activemq.command.DataStructure;
import org.apache.activemq.util.ByteArrayOutputStream;
import org.apache.activemq.util.ByteSequence;
import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver; import com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver;
import org.apache.activemq.advisory.AdvisorySupport;
import org.apache.activemq.command.*;
/** /**
* Implements ActiveMQ 4.0 translations * Implements ActiveMQ 4.0 translations
@ -35,7 +43,7 @@ public class LegacyFrameTranslator implements FrameTranslator {
public ActiveMQMessage convertFrame(ProtocolConverter converter, StompFrame command) throws JMSException, ProtocolException { public ActiveMQMessage convertFrame(ProtocolConverter converter, StompFrame command) throws JMSException, ProtocolException {
final Map headers = command.getHeaders(); final Map<?, ?> headers = command.getHeaders();
final ActiveMQMessage msg; final ActiveMQMessage msg;
/* /*
* To reduce the complexity of this method perhaps a Chain of Responsibility * To reduce the complexity of this method perhaps a Chain of Responsibility
@ -46,7 +54,12 @@ public class LegacyFrameTranslator implements FrameTranslator {
if(intendedType.equalsIgnoreCase("text")){ if(intendedType.equalsIgnoreCase("text")){
ActiveMQTextMessage text = new ActiveMQTextMessage(); ActiveMQTextMessage text = new ActiveMQTextMessage();
try { try {
text.setText(new String(command.getContent(), "UTF-8")); //text.setText(new String(command.getContent(), "UTF-8"));
ByteArrayOutputStream bytes = new ByteArrayOutputStream(command.getContent().length + 4);
DataOutputStream data = new DataOutputStream(bytes);
data.writeInt(command.getContent().length);
data.write(command.getContent());
text.setContent(bytes.toByteSequence());
} catch (Throwable e) { } catch (Throwable e) {
throw new ProtocolException("Text could not bet set: " + e, false, e); throw new ProtocolException("Text could not bet set: " + e, false, e);
} }
@ -66,7 +79,12 @@ public class LegacyFrameTranslator implements FrameTranslator {
} else { } else {
ActiveMQTextMessage text = new ActiveMQTextMessage(); ActiveMQTextMessage text = new ActiveMQTextMessage();
try { try {
text.setText(new String(command.getContent(), "UTF-8")); //text.setText(new String(command.getContent(), "UTF-8"));
ByteArrayOutputStream bytes = new ByteArrayOutputStream(command.getContent().length + 4);
DataOutputStream data = new DataOutputStream(bytes);
data.writeInt(command.getContent().length);
data.write(command.getContent());
text.setContent(bytes.toByteSequence());
} catch (Throwable e) { } catch (Throwable e) {
throw new ProtocolException("Text could not bet set: " + e, false, e); throw new ProtocolException("Text could not bet set: " + e, false, e);
} }
@ -86,8 +104,17 @@ public class LegacyFrameTranslator implements FrameTranslator {
if (message.getDataStructureType() == ActiveMQTextMessage.DATA_STRUCTURE_TYPE) { if (message.getDataStructureType() == ActiveMQTextMessage.DATA_STRUCTURE_TYPE) {
ActiveMQTextMessage msg = (ActiveMQTextMessage)message.copy(); if (!message.isCompressed() && message.getContent() != null) {
command.setContent(msg.getText().getBytes("UTF-8")); ByteSequence msgContent = message.getContent();
if (msgContent.getLength() > 4) {
byte[] content = new byte[msgContent.getLength() - 4];
System.arraycopy(msgContent.data, 4, content, 0, content.length);
command.setContent(content);
}
} else {
ActiveMQTextMessage msg = (ActiveMQTextMessage)message.copy();
command.setContent(msg.getText().getBytes("UTF-8"));
}
} else if (message.getDataStructureType() == ActiveMQBytesMessage.DATA_STRUCTURE_TYPE) { } else if (message.getDataStructureType() == ActiveMQBytesMessage.DATA_STRUCTURE_TYPE) {
@ -96,13 +123,13 @@ public class LegacyFrameTranslator implements FrameTranslator {
byte[] data = new byte[(int)msg.getBodyLength()]; byte[] data = new byte[(int)msg.getBodyLength()];
msg.readBytes(data); msg.readBytes(data);
headers.put(Stomp.Headers.CONTENT_LENGTH, "" + data.length); headers.put(Stomp.Headers.CONTENT_LENGTH, Integer.toString(data.length));
command.setContent(data); command.setContent(data);
} else if (message.getDataStructureType() == ActiveMQMessage.DATA_STRUCTURE_TYPE && } else if (message.getDataStructureType() == ActiveMQMessage.DATA_STRUCTURE_TYPE &&
AdvisorySupport.ADIVSORY_MESSAGE_TYPE.equals(message.getType())) { AdvisorySupport.ADIVSORY_MESSAGE_TYPE.equals(message.getType())) {
FrameTranslator.Helper.copyStandardHeadersFromMessageToFrame( FrameTranslator.Helper.copyStandardHeadersFromMessageToFrame(
converter, message, command, this); converter, message, command, this);
String body = marshallAdvisory(message.getDataStructure()); String body = marshallAdvisory(message.getDataStructure());
command.setContent(body.getBytes("UTF-8")); command.setContent(body.getBytes("UTF-8"));
@ -119,10 +146,10 @@ public class LegacyFrameTranslator implements FrameTranslator {
String rc = converter.getCreatedTempDestinationName(activeMQDestination); String rc = converter.getCreatedTempDestinationName(activeMQDestination);
if( rc!=null ) { if( rc!=null ) {
return rc; return rc;
} }
StringBuffer buffer = new StringBuffer(); StringBuilder buffer = new StringBuilder();
if (activeMQDestination.isQueue()) { if (activeMQDestination.isQueue()) {
if (activeMQDestination.isTemporary()) { if (activeMQDestination.isTemporary()) {
buffer.append("/remote-temp-queue/"); buffer.append("/remote-temp-queue/");

View File

@ -16,10 +16,16 @@
*/ */
package org.apache.activemq.transport.stomp; package org.apache.activemq.transport.stomp;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -34,6 +40,7 @@ import org.apache.activemq.command.ActiveMQMessage;
import org.apache.activemq.command.ActiveMQTempQueue; import org.apache.activemq.command.ActiveMQTempQueue;
import org.apache.activemq.command.ActiveMQTempTopic; import org.apache.activemq.command.ActiveMQTempTopic;
import org.apache.activemq.command.Command; import org.apache.activemq.command.Command;
import org.apache.activemq.command.CommandTypes;
import org.apache.activemq.command.ConnectionError; import org.apache.activemq.command.ConnectionError;
import org.apache.activemq.command.ConnectionId; import org.apache.activemq.command.ConnectionId;
import org.apache.activemq.command.ConnectionInfo; import org.apache.activemq.command.ConnectionInfo;
@ -62,7 +69,6 @@ import org.apache.activemq.util.IntrospectionSupport;
import org.apache.activemq.util.LongSequenceGenerator; import org.apache.activemq.util.LongSequenceGenerator;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContextAware;
/** /**
* @author <a href="http://hiramchirino.com">chirino</a> * @author <a href="http://hiramchirino.com">chirino</a>
@ -70,9 +76,29 @@ import org.springframework.context.ApplicationContextAware;
public class ProtocolConverter { public class ProtocolConverter {
private static final Logger LOG = LoggerFactory.getLogger(ProtocolConverter.class); private static final Logger LOG = LoggerFactory.getLogger(ProtocolConverter.class);
private static final IdGenerator CONNECTION_ID_GENERATOR = new IdGenerator(); private static final IdGenerator CONNECTION_ID_GENERATOR = new IdGenerator();
private static final String BROKER_VERSION;
private static final StompFrame ping = new StompFrame(Stomp.Commands.KEEPALIVE);
private static final long DEFAULT_OUTBOUND_HEARTBEAT = 100;
private static final long DEFAULT_INBOUND_HEARTBEAT = 1000;
private static final long DEFAULT_INITIAL_HEARTBEAT_DELAY = 1000;
static {
InputStream in = null;
String version = "5.6.0";
if ((in = ProtocolConverter.class.getResourceAsStream("/org/apache/activemq/version.txt")) != null) {
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
try {
version = reader.readLine();
} catch(Exception e) {
}
}
BROKER_VERSION = version;
}
private final ConnectionId connectionId = new ConnectionId(CONNECTION_ID_GENERATOR.generateId()); private final ConnectionId connectionId = new ConnectionId(CONNECTION_ID_GENERATOR.generateId());
private final SessionId sessionId = new SessionId(connectionId, -1); private final SessionId sessionId = new SessionId(connectionId, -1);
private final ProducerId producerId = new ProducerId(sessionId, 1); private final ProducerId producerId = new ProducerId(sessionId, 1);
@ -84,6 +110,7 @@ public class ProtocolConverter {
private final ConcurrentHashMap<Integer, ResponseHandler> resposeHandlers = new ConcurrentHashMap<Integer, ResponseHandler>(); private final ConcurrentHashMap<Integer, ResponseHandler> resposeHandlers = new ConcurrentHashMap<Integer, ResponseHandler>();
private final ConcurrentHashMap<ConsumerId, StompSubscription> subscriptionsByConsumerId = new ConcurrentHashMap<ConsumerId, StompSubscription>(); private final ConcurrentHashMap<ConsumerId, StompSubscription> subscriptionsByConsumerId = new ConcurrentHashMap<ConsumerId, StompSubscription>();
private final ConcurrentHashMap<String, StompSubscription> subscriptions = new ConcurrentHashMap<String, StompSubscription>();
private final ConcurrentHashMap<String, ActiveMQDestination> tempDestinations = new ConcurrentHashMap<String, ActiveMQDestination>(); private final ConcurrentHashMap<String, ActiveMQDestination> tempDestinations = new ConcurrentHashMap<String, ActiveMQDestination>();
private final ConcurrentHashMap<String, String> tempDestinationAmqToStompMap = new ConcurrentHashMap<String, String>(); private final ConcurrentHashMap<String, String> tempDestinationAmqToStompMap = new ConcurrentHashMap<String, String>();
private final Map<String, LocalTransactionId> transactions = new ConcurrentHashMap<String, LocalTransactionId>(); private final Map<String, LocalTransactionId> transactions = new ConcurrentHashMap<String, LocalTransactionId>();
@ -92,13 +119,15 @@ public class ProtocolConverter {
private final Object commnadIdMutex = new Object(); private final Object commnadIdMutex = new Object();
private int lastCommandId; private int lastCommandId;
private final AtomicBoolean connected = new AtomicBoolean(false); private final AtomicBoolean connected = new AtomicBoolean(false);
private final FrameTranslator frameTranslator; private final FrameTranslator frameTranslator = new LegacyFrameTranslator();
private final FactoryFinder FRAME_TRANSLATOR_FINDER = new FactoryFinder("META-INF/services/org/apache/activemq/transport/frametranslator/"); private final FactoryFinder FRAME_TRANSLATOR_FINDER = new FactoryFinder("META-INF/services/org/apache/activemq/transport/frametranslator/");
private final BrokerContext brokerContext; private final BrokerContext brokerContext;
private String version = "1.0";
private long hbReadInterval = DEFAULT_INBOUND_HEARTBEAT;
private long hbWriteInterval = DEFAULT_OUTBOUND_HEARTBEAT;
public ProtocolConverter(StompTransport stompTransport, FrameTranslator translator, BrokerContext brokerContext) { public ProtocolConverter(StompTransport stompTransport, BrokerContext brokerContext) {
this.stompTransport = stompTransport; this.stompTransport = stompTransport;
this.frameTranslator = translator;
this.brokerContext = brokerContext; this.brokerContext = brokerContext;
} }
@ -178,6 +207,8 @@ public class ProtocolConverter {
onStompSend(command); onStompSend(command);
} else if (action.startsWith(Stomp.Commands.ACK)) { } else if (action.startsWith(Stomp.Commands.ACK)) {
onStompAck(command); onStompAck(command);
} else if (action.startsWith(Stomp.Commands.NACK)) {
onStompNack(command);
} else if (action.startsWith(Stomp.Commands.BEGIN)) { } else if (action.startsWith(Stomp.Commands.BEGIN)) {
onStompBegin(command); onStompBegin(command);
} else if (action.startsWith(Stomp.Commands.COMMIT)) { } else if (action.startsWith(Stomp.Commands.COMMIT)) {
@ -188,7 +219,8 @@ public class ProtocolConverter {
onStompSubscribe(command); onStompSubscribe(command);
} else if (action.startsWith(Stomp.Commands.UNSUBSCRIBE)) { } else if (action.startsWith(Stomp.Commands.UNSUBSCRIBE)) {
onStompUnsubscribe(command); onStompUnsubscribe(command);
} else if (action.startsWith(Stomp.Commands.CONNECT)) { } else if (action.startsWith(Stomp.Commands.CONNECT) ||
action.startsWith(Stomp.Commands.STOMP)) {
onStompConnect(command); onStompConnect(command);
} else if (action.startsWith(Stomp.Commands.DISCONNECT)) { } else if (action.startsWith(Stomp.Commands.DISCONNECT)) {
onStompDisconnect(command); onStompDisconnect(command);
@ -199,7 +231,7 @@ public class ProtocolConverter {
} catch (ProtocolException e) { } catch (ProtocolException e) {
handleException(e, command); handleException(e, command);
// Some protocol errors can cause the connection to get closed. // Some protocol errors can cause the connection to get closed.
if( e.isFatal() ) { if (e.isFatal()) {
getStompTransport().onException(e); getStompTransport().onException(e);
} }
} }
@ -219,6 +251,7 @@ public class ProtocolConverter {
HashMap<String, String> headers = new HashMap<String, String>(); HashMap<String, String> headers = new HashMap<String, String>();
headers.put(Stomp.Headers.Error.MESSAGE, exception.getMessage()); headers.put(Stomp.Headers.Error.MESSAGE, exception.getMessage());
headers.put(Stomp.Headers.CONTENT_TYPE, "text/plain");
if (command != null) { if (command != null) {
final String receiptId = command.getHeaders().get(Stomp.Headers.RECEIPT_REQUESTED); final String receiptId = command.getHeaders().get(Stomp.Headers.RECEIPT_REQUESTED);
@ -235,6 +268,11 @@ public class ProtocolConverter {
checkConnected(); checkConnected();
Map<String, String> headers = command.getHeaders(); Map<String, String> headers = command.getHeaders();
String destination = headers.get(Stomp.Headers.Send.DESTINATION);
if (destination == null) {
throw new ProtocolException("SEND received without a Destination specified!");
}
String stompTx = headers.get(Stomp.Headers.TRANSACTION); String stompTx = headers.get(Stomp.Headers.TRANSACTION);
headers.remove("transaction"); headers.remove("transaction");
@ -255,24 +293,64 @@ public class ProtocolConverter {
message.onSend(); message.onSend();
sendToActiveMQ(message, createResponseHandler(command)); sendToActiveMQ(message, createResponseHandler(command));
}
protected void onStompNack(StompFrame command) throws ProtocolException {
checkConnected();
if (this.version.equals(Stomp.V1_1)) {
throw new ProtocolException("NACK received but connection is in v1.0 mode.");
}
Map<String, String> headers = command.getHeaders();
String subscriptionId = headers.get(Stomp.Headers.Ack.SUBSCRIPTION);
if (subscriptionId == null) {
throw new ProtocolException("NACK received without a subscription id for acknowledge!");
}
String messageId = headers.get(Stomp.Headers.Ack.MESSAGE_ID);
if (messageId == null) {
throw new ProtocolException("NACK received without a message-id to acknowledge!");
}
TransactionId activemqTx = null;
String stompTx = headers.get(Stomp.Headers.TRANSACTION);
if (stompTx != null) {
activemqTx = transactions.get(stompTx);
if (activemqTx == null) {
throw new ProtocolException("Invalid transaction id: " + stompTx);
}
}
if (subscriptionId != null) {
StompSubscription sub = this.subscriptions.get(subscriptionId);
if (sub != null) {
MessageAck ack = sub.onStompMessageNack(messageId, activemqTx);
if (ack != null) {
sendToActiveMQ(ack, createResponseHandler(command));
} else {
throw new ProtocolException("Unexpected NACK received for message-id [" + messageId + "]");
}
}
}
} }
protected void onStompAck(StompFrame command) throws ProtocolException { protected void onStompAck(StompFrame command) throws ProtocolException {
checkConnected(); checkConnected();
// TODO: acking with just a message id is very bogus
// since the same message id could have been sent to 2 different
// subscriptions
// on the same stomp connection. For example, when 2 subs are created on
// the same topic.
Map<String, String> headers = command.getHeaders(); Map<String, String> headers = command.getHeaders();
String messageId = headers.get(Stomp.Headers.Ack.MESSAGE_ID); String messageId = headers.get(Stomp.Headers.Ack.MESSAGE_ID);
if (messageId == null) { if (messageId == null) {
throw new ProtocolException("ACK received without a message-id to acknowledge!"); throw new ProtocolException("ACK received without a message-id to acknowledge!");
} }
String subscriptionId = headers.get(Stomp.Headers.Ack.SUBSCRIPTION);
if (this.version.equals(Stomp.V1_1) && subscriptionId == null) {
throw new ProtocolException("ACK received without a subscription id for acknowledge!");
}
TransactionId activemqTx = null; TransactionId activemqTx = null;
String stompTx = headers.get(Stomp.Headers.TRANSACTION); String stompTx = headers.get(Stomp.Headers.TRANSACTION);
if (stompTx != null) { if (stompTx != null) {
@ -283,21 +361,37 @@ public class ProtocolConverter {
} }
boolean acked = false; boolean acked = false;
for (Iterator<StompSubscription> iter = subscriptionsByConsumerId.values().iterator(); iter.hasNext();) {
StompSubscription sub = iter.next(); if (subscriptionId != null) {
MessageAck ack = sub.onStompMessageAck(messageId, activemqTx);
if (ack != null) { StompSubscription sub = this.subscriptions.get(subscriptionId);
ack.setTransactionId(activemqTx); if (sub != null) {
sendToActiveMQ(ack, createResponseHandler(command)); MessageAck ack = sub.onStompMessageAck(messageId, activemqTx);
acked = true; if (ack != null) {
break; sendToActiveMQ(ack, createResponseHandler(command));
acked = true;
}
}
} else {
// TODO: acking with just a message id is very bogus since the same message id
// could have been sent to 2 different subscriptions on the same Stomp connection.
// For example, when 2 subs are created on the same topic.
for (StompSubscription sub : subscriptionsByConsumerId.values()) {
MessageAck ack = sub.onStompMessageAck(messageId, activemqTx);
if (ack != null) {
sendToActiveMQ(ack, createResponseHandler(command));
acked = true;
break;
}
} }
} }
if (!acked) { if (!acked) {
throw new ProtocolException("Unexpected ACK received for message-id [" + messageId + "]"); throw new ProtocolException("Unexpected ACK received for message-id [" + messageId + "]");
} }
} }
protected void onStompBegin(StompFrame command) throws ProtocolException { protected void onStompBegin(StompFrame command) throws ProtocolException {
@ -324,7 +418,6 @@ public class ProtocolConverter {
tx.setType(TransactionInfo.BEGIN); tx.setType(TransactionInfo.BEGIN);
sendToActiveMQ(tx, createResponseHandler(command)); sendToActiveMQ(tx, createResponseHandler(command));
} }
protected void onStompCommit(StompFrame command) throws ProtocolException { protected void onStompCommit(StompFrame command) throws ProtocolException {
@ -342,8 +435,7 @@ public class ProtocolConverter {
throw new ProtocolException("Invalid transaction id: " + stompTx); throw new ProtocolException("Invalid transaction id: " + stompTx);
} }
for (Iterator<StompSubscription> iter = subscriptionsByConsumerId.values().iterator(); iter.hasNext();) { for (StompSubscription sub : subscriptionsByConsumerId.values()) {
StompSubscription sub = iter.next();
sub.onStompCommit(activemqTx); sub.onStompCommit(activemqTx);
} }
@ -353,7 +445,6 @@ public class ProtocolConverter {
tx.setType(TransactionInfo.COMMIT_ONE_PHASE); tx.setType(TransactionInfo.COMMIT_ONE_PHASE);
sendToActiveMQ(tx, createResponseHandler(command)); sendToActiveMQ(tx, createResponseHandler(command));
} }
protected void onStompAbort(StompFrame command) throws ProtocolException { protected void onStompAbort(StompFrame command) throws ProtocolException {
@ -369,8 +460,7 @@ public class ProtocolConverter {
if (activemqTx == null) { if (activemqTx == null) {
throw new ProtocolException("Invalid transaction id: " + stompTx); throw new ProtocolException("Invalid transaction id: " + stompTx);
} }
for (Iterator<StompSubscription> iter = subscriptionsByConsumerId.values().iterator(); iter.hasNext();) { for (StompSubscription sub : subscriptionsByConsumerId.values()) {
StompSubscription sub = iter.next();
try { try {
sub.onStompAbort(activemqTx); sub.onStompAbort(activemqTx);
} catch (Exception e) { } catch (Exception e) {
@ -384,7 +474,6 @@ public class ProtocolConverter {
tx.setType(TransactionInfo.ROLLBACK); tx.setType(TransactionInfo.ROLLBACK);
sendToActiveMQ(tx, createResponseHandler(command)); sendToActiveMQ(tx, createResponseHandler(command));
} }
protected void onStompSubscribe(StompFrame command) throws ProtocolException { protected void onStompSubscribe(StompFrame command) throws ProtocolException {
@ -395,6 +484,10 @@ public class ProtocolConverter {
String subscriptionId = headers.get(Stomp.Headers.Subscribe.ID); String subscriptionId = headers.get(Stomp.Headers.Subscribe.ID);
String destination = headers.get(Stomp.Headers.Subscribe.DESTINATION); String destination = headers.get(Stomp.Headers.Subscribe.DESTINATION);
if (this.version.equals(Stomp.V1_1) && subscriptionId == null) {
throw new ProtocolException("SUBSCRIBE received without a subscription id!");
}
ActiveMQDestination actualDest = translator.convertDestination(this, destination); ActiveMQDestination actualDest = translator.convertDestination(this, destination);
if (actualDest == null) { if (actualDest == null) {
@ -406,6 +499,16 @@ public class ProtocolConverter {
consumerInfo.setPrefetchSize(1000); consumerInfo.setPrefetchSize(1000);
consumerInfo.setDispatchAsync(true); consumerInfo.setDispatchAsync(true);
String browser = headers.get(Stomp.Headers.Subscribe.BROWSER);
if (browser != null && browser.equals(Stomp.TRUE)) {
if (!this.version.equals(Stomp.V1_1)) {
throw new ProtocolException("Queue Browser feature only valid for Stomp v1.1 clients!");
}
consumerInfo.setBrowser(true);
}
String selector = headers.remove(Stomp.Headers.Subscribe.SELECTOR); String selector = headers.remove(Stomp.Headers.Subscribe.SELECTOR);
consumerInfo.setSelector(selector); consumerInfo.setSelector(selector);
@ -413,7 +516,12 @@ public class ProtocolConverter {
consumerInfo.setDestination(translator.convertDestination(this, destination)); consumerInfo.setDestination(translator.convertDestination(this, destination));
StompSubscription stompSubscription = new StompSubscription(this, subscriptionId, consumerInfo, headers.get(Stomp.Headers.TRANSFORMATION)); StompSubscription stompSubscription;
if (!consumerInfo.isBrowser()) {
stompSubscription = new StompSubscription(this, subscriptionId, consumerInfo, headers.get(Stomp.Headers.TRANSFORMATION));
} else {
stompSubscription = new StompQueueBrowserSubscription(this, subscriptionId, consumerInfo, headers.get(Stomp.Headers.TRANSFORMATION));
}
stompSubscription.setDestination(actualDest); stompSubscription.setDestination(actualDest);
String ackMode = headers.get(Stomp.Headers.Subscribe.ACK_MODE); String ackMode = headers.get(Stomp.Headers.Subscribe.ACK_MODE);
@ -426,8 +534,11 @@ public class ProtocolConverter {
} }
subscriptionsByConsumerId.put(id, stompSubscription); subscriptionsByConsumerId.put(id, stompSubscription);
// Stomp v1.0 doesn't need to set this header so we avoid an NPE if not set.
if (subscriptionId != null) {
subscriptions.put(subscriptionId, stompSubscription);
}
sendToActiveMQ(consumerInfo, createResponseHandler(command)); sendToActiveMQ(consumerInfo, createResponseHandler(command));
} }
protected void onStompUnsubscribe(StompFrame command) throws ProtocolException { protected void onStompUnsubscribe(StompFrame command) throws ProtocolException {
@ -441,6 +552,9 @@ public class ProtocolConverter {
} }
String subscriptionId = headers.get(Stomp.Headers.Unsubscribe.ID); String subscriptionId = headers.get(Stomp.Headers.Unsubscribe.ID);
if (this.version.equals(Stomp.V1_1) && subscriptionId == null) {
throw new ProtocolException("UNSUBSCRIBE received without a subscription id!");
}
if (subscriptionId == null && destination == null) { if (subscriptionId == null && destination == null) {
throw new ProtocolException("Must specify the subscriptionId or the destination you are unsubscribing from"); throw new ProtocolException("Must specify the subscriptionId or the destination you are unsubscribing from");
@ -457,18 +571,26 @@ public class ProtocolConverter {
return; return;
} }
// TODO: Unsubscribing using a destination is a bit wierd if multiple if (subscriptionId != null) {
// subscriptions
// are created with the same destination. Perhaps this should be StompSubscription sub = this.subscriptions.remove(subscriptionId);
// removed. if (sub != null) {
//
for (Iterator<StompSubscription> iter = subscriptionsByConsumerId.values().iterator(); iter.hasNext();) {
StompSubscription sub = iter.next();
if ((subscriptionId != null && subscriptionId.equals(sub.getSubscriptionId())) || (destination != null && destination.equals(sub.getDestination()))) {
sendToActiveMQ(sub.getConsumerInfo().createRemoveCommand(), createResponseHandler(command)); sendToActiveMQ(sub.getConsumerInfo().createRemoveCommand(), createResponseHandler(command));
iter.remove();
return; return;
} }
} else {
// Unsubscribing using a destination is a bit weird if multiple subscriptions
// are created with the same destination.
for (Iterator<StompSubscription> iter = subscriptionsByConsumerId.values().iterator(); iter.hasNext();) {
StompSubscription sub = iter.next();
if (destination != null && destination.equals(sub.getDestination())) {
sendToActiveMQ(sub.getConsumerInfo().createRemoveCommand(), createResponseHandler(command));
iter.remove();
return;
}
}
} }
throw new ProtocolException("No subscription matched."); throw new ProtocolException("No subscription matched.");
@ -488,10 +610,28 @@ public class ProtocolConverter {
String login = headers.get(Stomp.Headers.Connect.LOGIN); String login = headers.get(Stomp.Headers.Connect.LOGIN);
String passcode = headers.get(Stomp.Headers.Connect.PASSCODE); String passcode = headers.get(Stomp.Headers.Connect.PASSCODE);
String clientId = headers.get(Stomp.Headers.Connect.CLIENT_ID); String clientId = headers.get(Stomp.Headers.Connect.CLIENT_ID);
String heartBeat = headers.get(Stomp.Headers.Connect.HEART_BEAT);
String accepts = headers.get(Stomp.Headers.Connect.ACCEPT_VERSION);
if (accepts == null) {
accepts = Stomp.DEFAULT_VERSION;
}
if (heartBeat == null) {
heartBeat = Stomp.DEFAULT_HEART_BEAT;
}
HashSet<String> acceptsVersions = new HashSet<String>(Arrays.asList(accepts.split(Stomp.COMMA)));
acceptsVersions.retainAll(Arrays.asList(Stomp.SUPPORTED_PROTOCOL_VERSIONS));
if (acceptsVersions.isEmpty()) {
throw new ProtocolException("Invlid Protocol version, supported versions are: " +
Arrays.toString(Stomp.SUPPORTED_PROTOCOL_VERSIONS), true);
} else {
this.version = Collections.max(acceptsVersions);
}
configureInactivityMonitor(heartBeat);
IntrospectionSupport.setProperties(connectionInfo, headers, "activemq."); IntrospectionSupport.setProperties(connectionInfo, headers, "activemq.");
connectionInfo.setConnectionId(connectionId); connectionInfo.setConnectionId(connectionId);
if (clientId != null) { if (clientId != null) {
connectionInfo.setClientId(clientId); connectionInfo.setClientId(clientId);
@ -544,10 +684,22 @@ public class ProtocolConverter {
responseHeaders.put(Stomp.Headers.Response.RECEIPT_ID, requestId); responseHeaders.put(Stomp.Headers.Response.RECEIPT_ID, requestId);
} }
responseHeaders.put(Stomp.Headers.Connected.VERSION, version);
responseHeaders.put(Stomp.Headers.Connected.HEART_BEAT,
String.format("%d,%d", hbWriteInterval, hbReadInterval));
responseHeaders.put(Stomp.Headers.Connected.SERVER, "ActiveMQ/"+BROKER_VERSION);
StompFrame sc = new StompFrame(); StompFrame sc = new StompFrame();
sc.setAction(Stomp.Responses.CONNECTED); sc.setAction(Stomp.Responses.CONNECTED);
sc.setHeaders(responseHeaders); sc.setHeaders(responseHeaders);
sendToStomp(sc); sendToStomp(sc);
if (version.equals(Stomp.V1_1)) {
StompWireFormat format = stompTransport.getWireFormat();
if (format != null) {
format.setEncodingEnabled(true);
}
}
} }
}); });
@ -576,7 +728,6 @@ public class ProtocolConverter {
*/ */
public void onActiveMQCommand(Command command) throws IOException, JMSException { public void onActiveMQCommand(Command command) throws IOException, JMSException {
if (command.isResponse()) { if (command.isResponse()) {
Response response = (Response)command; Response response = (Response)command;
ResponseHandler rh = resposeHandlers.remove(Integer.valueOf(response.getCorrelationId())); ResponseHandler rh = resposeHandlers.remove(Integer.valueOf(response.getCorrelationId()));
if (rh != null) { if (rh != null) {
@ -589,12 +740,13 @@ public class ProtocolConverter {
} }
} }
} else if (command.isMessageDispatch()) { } else if (command.isMessageDispatch()) {
MessageDispatch md = (MessageDispatch)command; MessageDispatch md = (MessageDispatch)command;
StompSubscription sub = subscriptionsByConsumerId.get(md.getConsumerId()); StompSubscription sub = subscriptionsByConsumerId.get(md.getConsumerId());
if (sub != null) { if (sub != null) {
sub.onMessageDispatch(md); sub.onMessageDispatch(md);
} }
} else if (command.getDataStructureType() == CommandTypes.KEEP_ALIVE_INFO) {
stompTransport.sendToStomp(ping);
} else if (command.getDataStructureType() == ConnectionError.DATA_STRUCTURE_TYPE) { } else if (command.getDataStructureType() == ConnectionError.DATA_STRUCTURE_TYPE) {
// Pass down any unexpected async errors. Should this close the connection? // Pass down any unexpected async errors. Should this close the connection?
Throwable exception = ((ConnectionError)command).getException(); Throwable exception = ((ConnectionError)command).getException();
@ -643,4 +795,49 @@ public class ProtocolConverter {
public String getCreatedTempDestinationName(ActiveMQDestination destination) { public String getCreatedTempDestinationName(ActiveMQDestination destination) {
return tempDestinationAmqToStompMap.get(destination.getQualifiedName()); return tempDestinationAmqToStompMap.get(destination.getQualifiedName());
} }
protected void configureInactivityMonitor(String heartBeatConfig) throws ProtocolException {
String[] keepAliveOpts = heartBeatConfig.split(Stomp.COMMA);
if (keepAliveOpts == null || keepAliveOpts.length != 2) {
throw new ProtocolException("Invlid heart-beat header:" + heartBeatConfig, true);
} else {
try {
hbReadInterval = Long.parseLong(keepAliveOpts[0]);
hbWriteInterval = Long.parseLong(keepAliveOpts[1]);
} catch(NumberFormatException e) {
throw new ProtocolException("Invlid heart-beat header:" + heartBeatConfig, true);
}
if (hbReadInterval > 0) {
hbReadInterval = Math.max(DEFAULT_INBOUND_HEARTBEAT, hbReadInterval);
hbReadInterval += Math.min(hbReadInterval, 5000);
}
if (hbWriteInterval > 0) {
hbWriteInterval = Math.max(DEFAULT_OUTBOUND_HEARTBEAT, hbWriteInterval);
}
try {
StompInactivityMonitor monitor = this.stompTransport.getInactivityMonitor();
monitor.setReadCheckTime(hbReadInterval);
monitor.setInitialDelayTime(DEFAULT_INITIAL_HEARTBEAT_DELAY);
monitor.setWriteCheckTime(hbWriteInterval);
monitor.startMonitoring();
} catch(Exception ex) {
hbReadInterval = 0;
hbWriteInterval = 0;
}
if (LOG.isDebugEnabled()) {
LOG.debug("Stomp Connect heartbeat conf RW[" + hbReadInterval + "," + hbWriteInterval + "]");
}
}
}
} }

View File

@ -20,7 +20,29 @@ public interface Stomp {
String NULL = "\u0000"; String NULL = "\u0000";
String NEWLINE = "\n"; String NEWLINE = "\n";
byte BREAK = '\n';
byte COLON = ':';
byte ESCAPE = '\\';
byte[] ESCAPE_ESCAPE_SEQ = { 92, 92 };
byte[] COLON_ESCAPE_SEQ = { 92, 99 };
byte[] NEWLINE_ESCAPE_SEQ = { 92, 110 };
String COMMA = ",";
String V1_0 = "1.0";
String V1_1 = "1.1";
String DEFAULT_HEART_BEAT = "0,0";
String DEFAULT_VERSION = "1.0";
String EMPTY = "";
String[] SUPPORTED_PROTOCOL_VERSIONS = {"1.1", "1.0"};
String TEXT_PLAIN = "text/plain";
String TRUE = "true";
String FALSE = "false";
String END = "end";
public static interface Commands { public static interface Commands {
String STOMP = "STOMP";
String CONNECT = "CONNECT"; String CONNECT = "CONNECT";
String SEND = "SEND"; String SEND = "SEND";
String DISCONNECT = "DISCONNECT"; String DISCONNECT = "DISCONNECT";
@ -34,6 +56,8 @@ public interface Stomp {
String COMMIT = "COMMIT"; String COMMIT = "COMMIT";
String ABORT = "ABORT"; String ABORT = "ABORT";
String ACK = "ACK"; String ACK = "ACK";
String NACK = "NACK";
String KEEPALIVE = "KEEPALIVE";
} }
public interface Responses { public interface Responses {
@ -48,8 +72,10 @@ public interface Stomp {
String RECEIPT_REQUESTED = "receipt"; String RECEIPT_REQUESTED = "receipt";
String TRANSACTION = "transaction"; String TRANSACTION = "transaction";
String CONTENT_LENGTH = "content-length"; String CONTENT_LENGTH = "content-length";
String CONTENT_TYPE = "content-type";
String TRANSFORMATION = "transformation"; String TRANSFORMATION = "transformation";
String TRANSFORMATION_ERROR = "transformation-error"; String TRANSFORMATION_ERROR = "transformation-error";
/** /**
* This header is used to instruct ActiveMQ to construct the message * This header is used to instruct ActiveMQ to construct the message
* based with a specific type. * based with a specific type.
@ -81,6 +107,7 @@ public interface Stomp {
String TIMESTAMP = "timestamp"; String TIMESTAMP = "timestamp";
String TYPE = "type"; String TYPE = "type";
String SUBSCRIPTION = "subscription"; String SUBSCRIPTION = "subscription";
String BROWSER = "browser";
String USERID = "JMSXUserID"; String USERID = "JMSXUserID";
String ORIGINAL_DESTINATION = "original-destination"; String ORIGINAL_DESTINATION = "original-destination";
} }
@ -90,6 +117,7 @@ public interface Stomp {
String ACK_MODE = "ack"; String ACK_MODE = "ack";
String ID = "id"; String ID = "id";
String SELECTOR = "selector"; String SELECTOR = "selector";
String BROWSER = "browser";
public interface AckModeValues { public interface AckModeValues {
String AUTO = "auto"; String AUTO = "auto";
@ -108,6 +136,9 @@ public interface Stomp {
String PASSCODE = "passcode"; String PASSCODE = "passcode";
String CLIENT_ID = "client-id"; String CLIENT_ID = "client-id";
String REQUEST_ID = "request-id"; String REQUEST_ID = "request-id";
String ACCEPT_VERSION = "accept-version";
String HOST = "host";
String HEART_BEAT = "heart-beat";
} }
public interface Error { public interface Error {
@ -117,30 +148,34 @@ public interface Stomp {
public interface Connected { public interface Connected {
String SESSION = "session"; String SESSION = "session";
String RESPONSE_ID = "response-id"; String RESPONSE_ID = "response-id";
String SERVER = "server";
String VERSION = "version";
String HEART_BEAT = "heart-beat";
} }
public interface Ack { public interface Ack {
String MESSAGE_ID = "message-id"; String MESSAGE_ID = "message-id";
String SUBSCRIPTION = "subscription";
} }
} }
public enum Transformations { public enum Transformations {
JMS_BYTE, JMS_BYTE,
JMS_XML, JMS_XML,
JMS_JSON, JMS_JSON,
JMS_OBJECT_XML, JMS_OBJECT_XML,
JMS_OBJECT_JSON, JMS_OBJECT_JSON,
JMS_MAP_XML, JMS_MAP_XML,
JMS_MAP_JSON, JMS_MAP_JSON,
JMS_ADVISORY_XML, JMS_ADVISORY_XML,
JMS_ADVISORY_JSON; JMS_ADVISORY_JSON;
public String toString() { public String toString() {
return name().replaceAll("_", "-").toLowerCase(); return name().replaceAll("_", "-").toLowerCase();
} }
public static Transformations getValue(String value) { public static Transformations getValue(String value) {
return valueOf(value.replaceAll("-", "_").toUpperCase()); return valueOf(value.replaceAll("-", "_").toUpperCase());
} }
} }
} }

View File

@ -19,7 +19,6 @@ package org.apache.activemq.transport.stomp;
import org.apache.activemq.transport.tcp.TcpTransport; import org.apache.activemq.transport.tcp.TcpTransport;
import org.apache.activemq.util.ByteArrayOutputStream; import org.apache.activemq.util.ByteArrayOutputStream;
import org.apache.activemq.util.DataByteArrayInputStream; import org.apache.activemq.util.DataByteArrayInputStream;
import org.apache.activemq.wireformat.WireFormat;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.util.HashMap; import java.util.HashMap;
@ -41,55 +40,55 @@ public class StompCodec {
} }
public void parse(ByteArrayInputStream input, int readSize) throws Exception { public void parse(ByteArrayInputStream input, int readSize) throws Exception {
int i = 0; int i = 0;
int b; int b;
while(i++ < readSize) { while(i++ < readSize) {
b = input.read(); b = input.read();
// skip repeating nulls // skip repeating nulls
if (!processedHeaders && previousByte == 0 && b == 0) { if (!processedHeaders && previousByte == 0 && b == 0) {
continue; continue;
} }
if (!processedHeaders) { if (!processedHeaders) {
currentCommand.write(b); currentCommand.write(b);
// end of headers section, parse action and header // end of headers section, parse action and header
if (previousByte == '\n' && b == '\n') { if (previousByte == '\n' && b == '\n') {
if (transport.getWireFormat() instanceof StompWireFormat) { if (transport.getWireFormat() instanceof StompWireFormat) {
DataByteArrayInputStream data = new DataByteArrayInputStream(currentCommand.toByteArray()); DataByteArrayInputStream data = new DataByteArrayInputStream(currentCommand.toByteArray());
action = ((StompWireFormat)transport.getWireFormat()).parseAction(data); action = ((StompWireFormat)transport.getWireFormat()).parseAction(data);
headers = ((StompWireFormat)transport.getWireFormat()).parseHeaders(data); headers = ((StompWireFormat)transport.getWireFormat()).parseHeaders(data);
String contentLengthHeader = headers.get(Stomp.Headers.CONTENT_LENGTH); String contentLengthHeader = headers.get(Stomp.Headers.CONTENT_LENGTH);
if (contentLengthHeader != null) { if (contentLengthHeader != null) {
contentLength = ((StompWireFormat)transport.getWireFormat()).parseContentLength(contentLengthHeader); contentLength = ((StompWireFormat)transport.getWireFormat()).parseContentLength(contentLengthHeader);
} else {
contentLength = -1;
}
}
processedHeaders = true;
currentCommand.reset();
}
} else {
if (contentLength == -1) {
// end of command reached, unmarshal
if (b == 0) {
processCommand();
} else {
currentCommand.write(b);
}
} else { } else {
// read desired content length contentLength = -1;
if (readLength++ == contentLength) {
processCommand();
readLength = 0;
} else {
currentCommand.write(b);
}
} }
} }
processedHeaders = true;
previousByte = b; currentCommand.reset();
} }
} else {
if (contentLength == -1) {
// end of command reached, unmarshal
if (b == 0) {
processCommand();
} else {
currentCommand.write(b);
}
} else {
// read desired content length
if (readLength++ == contentLength) {
processCommand();
readLength = 0;
} else {
currentCommand.write(b);
}
}
}
previousByte = b;
}
} }
protected void processCommand() throws Exception { protected void processCommand() throws Exception {

View File

@ -38,7 +38,7 @@ public class StompConnection {
} }
public void open(Socket socket) { public void open(Socket socket) {
stompSocket = socket; stompSocket = socket;
} }
public void close() throws IOException { public void close() throws IOException {
@ -70,8 +70,8 @@ public class StompConnection {
} }
public StompFrame receive(long timeOut) throws Exception { public StompFrame receive(long timeOut) throws Exception {
stompSocket.setSoTimeout((int)timeOut); stompSocket.setSoTimeout((int)timeOut);
InputStream is = stompSocket.getInputStream(); InputStream is = stompSocket.getInputStream();
StompWireFormat wf = new StompWireFormat(); StompWireFormat wf = new StompWireFormat();
DataInputStream dis = new DataInputStream(is); DataInputStream dis = new DataInputStream(is);
return (StompFrame)wf.unmarshal(dis); return (StompFrame)wf.unmarshal(dis);
@ -104,143 +104,143 @@ public class StompConnection {
} }
} }
private String stringFromBuffer(ByteArrayOutputStream inputBuffer) throws Exception { private String stringFromBuffer(ByteArrayOutputStream inputBuffer) throws Exception {
byte[] ba = inputBuffer.toByteArray(); byte[] ba = inputBuffer.toByteArray();
inputBuffer.reset(); inputBuffer.reset();
return new String(ba, "UTF-8"); return new String(ba, "UTF-8");
} }
public Socket getStompSocket() { public Socket getStompSocket() {
return stompSocket; return stompSocket;
} }
public void setStompSocket(Socket stompSocket) { public void setStompSocket(Socket stompSocket) {
this.stompSocket = stompSocket; this.stompSocket = stompSocket;
} }
public void connect(String username, String password) throws Exception { public void connect(String username, String password) throws Exception {
connect(username, password, null); connect(username, password, null);
} }
public void connect(String username, String password, String client) throws Exception { public void connect(String username, String password, String client) throws Exception {
HashMap<String, String> headers = new HashMap(); HashMap<String, String> headers = new HashMap<String, String>();
headers.put("login", username); headers.put("login", username);
headers.put("passcode", password); headers.put("passcode", password);
if (client != null) { if (client != null) {
headers.put("client-id", client); headers.put("client-id", client);
} }
StompFrame frame = new StompFrame("CONNECT", headers); StompFrame frame = new StompFrame("CONNECT", headers);
sendFrame(frame.format()); sendFrame(frame.format());
StompFrame connect = receive(); StompFrame connect = receive();
if (!connect.getAction().equals(Stomp.Responses.CONNECTED)) { if (!connect.getAction().equals(Stomp.Responses.CONNECTED)) {
throw new Exception ("Not connected: " + connect.getBody()); throw new Exception ("Not connected: " + connect.getBody());
} }
} }
public void disconnect() throws Exception { public void disconnect() throws Exception {
StompFrame frame = new StompFrame("DISCONNECT"); StompFrame frame = new StompFrame("DISCONNECT");
sendFrame(frame.format()); sendFrame(frame.format());
} }
public void send(String destination, String message) throws Exception { public void send(String destination, String message) throws Exception {
send(destination, message, null, null); send(destination, message, null, null);
} }
public void send(String destination, String message, String transaction, HashMap<String, String> headers) throws Exception { public void send(String destination, String message, String transaction, HashMap<String, String> headers) throws Exception {
if (headers == null) { if (headers == null) {
headers = new HashMap<String, String>(); headers = new HashMap<String, String>();
} }
headers.put("destination", destination); headers.put("destination", destination);
if (transaction != null) { if (transaction != null) {
headers.put("transaction", transaction); headers.put("transaction", transaction);
} }
StompFrame frame = new StompFrame("SEND", headers, message.getBytes()); StompFrame frame = new StompFrame("SEND", headers, message.getBytes());
sendFrame(frame.format()); sendFrame(frame.format());
} }
public void subscribe(String destination) throws Exception { public void subscribe(String destination) throws Exception {
subscribe(destination, null, null); subscribe(destination, null, null);
} }
public void subscribe(String destination, String ack) throws Exception { public void subscribe(String destination, String ack) throws Exception {
subscribe(destination, ack, new HashMap<String, String>()); subscribe(destination, ack, new HashMap<String, String>());
} }
public void subscribe(String destination, String ack, HashMap<String, String> headers) throws Exception { public void subscribe(String destination, String ack, HashMap<String, String> headers) throws Exception {
if (headers == null) { if (headers == null) {
headers = new HashMap<String, String>(); headers = new HashMap<String, String>();
} }
headers.put("destination", destination); headers.put("destination", destination);
if (ack != null) { if (ack != null) {
headers.put("ack", ack); headers.put("ack", ack);
} }
StompFrame frame = new StompFrame("SUBSCRIBE", headers); StompFrame frame = new StompFrame("SUBSCRIBE", headers);
sendFrame(frame.format()); sendFrame(frame.format());
} }
public void unsubscribe(String destination) throws Exception { public void unsubscribe(String destination) throws Exception {
unsubscribe(destination, null); unsubscribe(destination, null);
} }
public void unsubscribe(String destination, HashMap<String, String> headers) throws Exception { public void unsubscribe(String destination, HashMap<String, String> headers) throws Exception {
if (headers == null) { if (headers == null) {
headers = new HashMap<String, String>(); headers = new HashMap<String, String>();
} }
headers.put("destination", destination); headers.put("destination", destination);
StompFrame frame = new StompFrame("UNSUBSCRIBE", headers); StompFrame frame = new StompFrame("UNSUBSCRIBE", headers);
sendFrame(frame.format()); sendFrame(frame.format());
} }
public void begin(String transaction) throws Exception { public void begin(String transaction) throws Exception {
HashMap<String, String> headers = new HashMap<String, String>(); HashMap<String, String> headers = new HashMap<String, String>();
headers.put("transaction", transaction); headers.put("transaction", transaction);
StompFrame frame = new StompFrame("BEGIN", headers); StompFrame frame = new StompFrame("BEGIN", headers);
sendFrame(frame.format()); sendFrame(frame.format());
} }
public void abort(String transaction) throws Exception { public void abort(String transaction) throws Exception {
HashMap<String, String> headers = new HashMap<String, String>(); HashMap<String, String> headers = new HashMap<String, String>();
headers.put("transaction", transaction); headers.put("transaction", transaction);
StompFrame frame = new StompFrame("ABORT", headers); StompFrame frame = new StompFrame("ABORT", headers);
sendFrame(frame.format()); sendFrame(frame.format());
} }
public void commit(String transaction) throws Exception { public void commit(String transaction) throws Exception {
HashMap<String, String> headers = new HashMap<String, String>(); HashMap<String, String> headers = new HashMap<String, String>();
headers.put("transaction", transaction); headers.put("transaction", transaction);
StompFrame frame = new StompFrame("COMMIT", headers); StompFrame frame = new StompFrame("COMMIT", headers);
sendFrame(frame.format()); sendFrame(frame.format());
} }
public void ack(StompFrame frame) throws Exception { public void ack(StompFrame frame) throws Exception {
ack(frame.getHeaders().get("message-id"), null); ack(frame.getHeaders().get("message-id"), null);
} }
public void ack(StompFrame frame, String transaction) throws Exception { public void ack(StompFrame frame, String transaction) throws Exception {
ack(frame.getHeaders().get("message-id"), transaction); ack(frame.getHeaders().get("message-id"), transaction);
} }
public void ack(String messageId) throws Exception { public void ack(String messageId) throws Exception {
ack(messageId, null); ack(messageId, null);
} }
public void ack(String messageId, String transaction) throws Exception { public void ack(String messageId, String transaction) throws Exception {
HashMap<String, String> headers = new HashMap<String, String>(); HashMap<String, String> headers = new HashMap<String, String>();
headers.put("message-id", messageId); headers.put("message-id", messageId);
if (transaction != null) if (transaction != null)
headers.put("transaction", transaction); headers.put("transaction", transaction);
StompFrame frame = new StompFrame("ACK", headers); StompFrame frame = new StompFrame("ACK", headers);
sendFrame(frame.format()); sendFrame(frame.format());
} }
protected String appendHeaders(HashMap<String, Object> headers) { protected String appendHeaders(HashMap<String, Object> headers) {
StringBuffer result = new StringBuffer(); StringBuilder result = new StringBuilder();
for (String key : headers.keySet()) { for (String key : headers.keySet()) {
result.append(key + ":" + headers.get(key) + "\n"); result.append(key + ":" + headers.get(key) + "\n");
} }
result.append("\n"); result.append("\n");
return result.toString(); return result.toString();
} }
} }

View File

@ -19,7 +19,6 @@ package org.apache.activemq.transport.stomp;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import org.apache.activemq.command.Command; import org.apache.activemq.command.Command;
@ -180,12 +179,11 @@ public class StompFrame implements Command {
} }
public String format(boolean forLogging) { public String format(boolean forLogging) {
StringBuffer buffer = new StringBuffer(); StringBuilder buffer = new StringBuilder();
buffer.append(getAction()); buffer.append(getAction());
buffer.append("\n"); buffer.append("\n");
Map headers = getHeaders(); Map<String, String> headers = getHeaders();
for (Iterator iter = headers.entrySet().iterator(); iter.hasNext();) { for (Map.Entry<String, String> entry : headers.entrySet()) {
Map.Entry entry = (Map.Entry)iter.next();
buffer.append(entry.getKey()); buffer.append(entry.getKey());
buffer.append(":"); buffer.append(":");
if (forLogging && entry.getKey().toString().toLowerCase().contains(Stomp.Headers.Connect.PASSCODE)) { if (forLogging && entry.getKey().toString().toLowerCase().contains(Stomp.Headers.Connect.PASSCODE)) {

View File

@ -0,0 +1,71 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.transport.stomp;
import java.io.IOException;
import org.apache.activemq.command.WireFormatInfo;
import org.apache.activemq.transport.AbstractInactivityMonitor;
import org.apache.activemq.transport.Transport;
import org.apache.activemq.wireformat.WireFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Used to make sure that commands are arriving periodically from the peer of
* the transport.
*/
public class StompInactivityMonitor extends AbstractInactivityMonitor {
private static final Logger LOG = LoggerFactory.getLogger(StompInactivityMonitor.class);
private boolean isConfigured = false;
public StompInactivityMonitor(Transport next, WireFormat wireFormat) {
super(next, wireFormat);
}
public void startMonitoring() throws IOException {
this.isConfigured = true;
this.startMonitorThreads();
}
@Override
protected void processInboundWireFormatInfo(WireFormatInfo info) throws IOException {
}
@Override
protected void processOutboundWireFormatInfo(WireFormatInfo info) throws IOException{
}
@Override
protected boolean configuredOk() throws IOException {
if (!isConfigured) {
return false;
}
LOG.debug("Stomp Inactivity Monitor read check: " + getReadCheckTime() +
", write check: " + getWriteCheckTime());
if (this.getReadCheckTime() >= 0 && this.getWriteCheckTime() >= 0) {
return true;
}
return false;
}
}

View File

@ -17,12 +17,10 @@
package org.apache.activemq.transport.stomp; package org.apache.activemq.transport.stomp;
import org.apache.activemq.transport.nio.NIOSSLTransport; import org.apache.activemq.transport.nio.NIOSSLTransport;
import org.apache.activemq.util.IOExceptionSupport;
import org.apache.activemq.wireformat.WireFormat; import org.apache.activemq.wireformat.WireFormat;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.net.Socket; import java.net.Socket;
import java.net.URI; import java.net.URI;

View File

@ -54,15 +54,15 @@ public class StompNIOSSLTransportFactory extends StompNIOTransportFactory {
return new StompNIOSSLTransport(wf, socketFactory, location, localLocation); return new StompNIOSSLTransport(wf, socketFactory, location, localLocation);
} }
@Override @Override
public TransportServer doBind(URI location) throws IOException { public TransportServer doBind(URI location) throws IOException {
if (SslContext.getCurrentSslContext() != null) { if (SslContext.getCurrentSslContext() != null) {
try { try {
context = SslContext.getCurrentSslContext().getSSLContext(); context = SslContext.getCurrentSslContext().getSSLContext();
} catch (Exception e) { } catch (Exception e) {
throw new IOException(e); throw new IOException(e);
} }
} }
return super.doBind(location); return super.doBind(location);
} }

View File

@ -26,19 +26,14 @@ import java.net.UnknownHostException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey; import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel; import java.nio.channels.SocketChannel;
import java.util.HashMap;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import org.apache.activemq.command.Command;
import org.apache.activemq.transport.Transport; import org.apache.activemq.transport.Transport;
import org.apache.activemq.transport.nio.NIOOutputStream; import org.apache.activemq.transport.nio.NIOOutputStream;
import org.apache.activemq.transport.nio.SelectorManager; import org.apache.activemq.transport.nio.SelectorManager;
import org.apache.activemq.transport.nio.SelectorSelection; import org.apache.activemq.transport.nio.SelectorSelection;
import org.apache.activemq.transport.tcp.TcpTransport; import org.apache.activemq.transport.tcp.TcpTransport;
import org.apache.activemq.util.ByteArrayOutputStream;
import org.apache.activemq.util.ByteSequence;
import org.apache.activemq.util.DataByteArrayInputStream;
import org.apache.activemq.util.IOExceptionSupport; import org.apache.activemq.util.IOExceptionSupport;
import org.apache.activemq.util.ServiceStopper; import org.apache.activemq.util.ServiceStopper;
import org.apache.activemq.wireformat.WireFormat; import org.apache.activemq.wireformat.WireFormat;
@ -46,7 +41,7 @@ import org.apache.activemq.wireformat.WireFormat;
/** /**
* An implementation of the {@link Transport} interface for using Stomp over NIO * An implementation of the {@link Transport} interface for using Stomp over NIO
* *
* *
*/ */
public class StompNIOTransport extends TcpTransport { public class StompNIOTransport extends TcpTransport {
@ -133,7 +128,7 @@ public class StompNIOTransport extends TcpTransport {
try { try {
selection.close(); selection.close();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
super.doStop(stopper); super.doStop(stopper);
} }

View File

@ -35,12 +35,11 @@ import org.apache.activemq.transport.tcp.TcpTransport;
import org.apache.activemq.transport.tcp.TcpTransportServer; import org.apache.activemq.transport.tcp.TcpTransportServer;
import org.apache.activemq.util.IntrospectionSupport; import org.apache.activemq.util.IntrospectionSupport;
import org.apache.activemq.wireformat.WireFormat; import org.apache.activemq.wireformat.WireFormat;
import org.apache.activemq.xbean.XBeanBrokerService;
/** /**
* A <a href="http://stomp.codehaus.org/">STOMP</a> over NIO transport factory * A <a href="http://stomp.codehaus.org/">STOMP</a> over NIO transport factory
* *
* *
*/ */
public class StompNIOTransportFactory extends NIOTransportFactory implements BrokerServiceAware { public class StompNIOTransportFactory extends NIOTransportFactory implements BrokerServiceAware {
@ -60,20 +59,15 @@ public class StompNIOTransportFactory extends NIOTransportFactory implements Bro
protected TcpTransport createTcpTransport(WireFormat wf, SocketFactory socketFactory, URI location, URI localLocation) throws UnknownHostException, IOException { protected TcpTransport createTcpTransport(WireFormat wf, SocketFactory socketFactory, URI location, URI localLocation) throws UnknownHostException, IOException {
return new StompNIOTransport(wf, socketFactory, location, localLocation); return new StompNIOTransport(wf, socketFactory, location, localLocation);
} }
@SuppressWarnings("rawtypes")
public Transport compositeConfigure(Transport transport, WireFormat format, Map options) { public Transport compositeConfigure(Transport transport, WireFormat format, Map options) {
transport = new StompTransportFilter(transport, new LegacyFrameTranslator(), brokerContext); transport = new StompTransportFilter(transport, format, brokerContext);
IntrospectionSupport.setProperties(transport, options); IntrospectionSupport.setProperties(transport, options);
return super.compositeConfigure(transport, format, options); return super.compositeConfigure(transport, format, options);
} }
protected boolean isUseInactivityMonitor(Transport transport) {
// lets disable the inactivity monitor as stomp does not use keep alive
// packets
return false;
}
public void setBrokerService(BrokerService brokerService) { public void setBrokerService(BrokerService brokerService) {
this.brokerContext = brokerService.getBrokerContext(); this.brokerContext = brokerService.getBrokerContext();
} }

View File

@ -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.activemq.transport.stomp;
import java.io.IOException;
import javax.jms.JMSException;
import org.apache.activemq.command.ConsumerInfo;
import org.apache.activemq.command.MessageAck;
import org.apache.activemq.command.MessageDispatch;
import org.apache.activemq.command.TransactionId;
public class StompQueueBrowserSubscription extends StompSubscription {
public StompQueueBrowserSubscription(ProtocolConverter stompTransport, String subscriptionId, ConsumerInfo consumerInfo, String transformation) {
super(stompTransport, subscriptionId, consumerInfo, transformation);
}
@Override
void onMessageDispatch(MessageDispatch md) throws IOException, JMSException {
if (md.getMessage() != null) {
super.onMessageDispatch(md);
} else {
StompFrame browseDone = new StompFrame(Stomp.Responses.MESSAGE);
browseDone.getHeaders().put(Stomp.Headers.Message.SUBSCRIPTION, this.getSubscriptionId());
browseDone.getHeaders().put(Stomp.Headers.Message.BROWSER, "end");
browseDone.getHeaders().put(Stomp.Headers.Message.DESTINATION,
protocolConverter.findTranslator(null).convertDestination(protocolConverter, this.destination));
browseDone.getHeaders().put(Stomp.Headers.Message.MESSAGE_ID, "0");
protocolConverter.sendToStomp(browseDone);
}
}
@Override
public MessageAck onStompMessageNack(String messageId, TransactionId transactionId) throws ProtocolException {
throw new ProtocolException("Cannot Nack a message on a Queue Browser Subscription.");
}
}

View File

@ -28,8 +28,8 @@ import org.apache.activemq.wireformat.WireFormat;
/** /**
* A <a href="http://stomp.codehaus.org/">STOMP</a> over SSL transport factory * A <a href="http://stomp.codehaus.org/">STOMP</a> over SSL transport factory
* *
* *
*/ */
public class StompSslTransportFactory extends SslTransportFactory implements BrokerServiceAware { public class StompSslTransportFactory extends SslTransportFactory implements BrokerServiceAware {
@ -39,8 +39,9 @@ public class StompSslTransportFactory extends SslTransportFactory implements Bro
return "stomp"; return "stomp";
} }
@SuppressWarnings("rawtypes")
public Transport compositeConfigure(Transport transport, WireFormat format, Map options) { public Transport compositeConfigure(Transport transport, WireFormat format, Map options) {
transport = new StompTransportFilter(transport, new LegacyFrameTranslator(), brokerContext); transport = new StompTransportFilter(transport, format, brokerContext);
IntrospectionSupport.setProperties(transport, options); IntrospectionSupport.setProperties(transport, options);
return super.compositeConfigure(transport, format, options); return super.compositeConfigure(transport, format, options);
} }

View File

@ -45,17 +45,16 @@ public class StompSubscription {
public static final String CLIENT_ACK = Stomp.Headers.Subscribe.AckModeValues.CLIENT; public static final String CLIENT_ACK = Stomp.Headers.Subscribe.AckModeValues.CLIENT;
public static final String INDIVIDUAL_ACK = Stomp.Headers.Subscribe.AckModeValues.INDIVIDUAL; public static final String INDIVIDUAL_ACK = Stomp.Headers.Subscribe.AckModeValues.INDIVIDUAL;
private final ProtocolConverter protocolConverter; protected final ProtocolConverter protocolConverter;
private final String subscriptionId; protected final String subscriptionId;
private final ConsumerInfo consumerInfo; protected final ConsumerInfo consumerInfo;
private final LinkedHashMap<MessageId, MessageDispatch> dispatchedMessage = new LinkedHashMap<MessageId, MessageDispatch>(); protected final LinkedHashMap<MessageId, MessageDispatch> dispatchedMessage = new LinkedHashMap<MessageId, MessageDispatch>();
private final LinkedList<MessageDispatch> unconsumedMessage = new LinkedList<MessageDispatch>(); protected final LinkedList<MessageDispatch> unconsumedMessage = new LinkedList<MessageDispatch>();
private String ackMode = AUTO_ACK;
private ActiveMQDestination destination;
private String transformation;
protected String ackMode = AUTO_ACK;
protected ActiveMQDestination destination;
protected String transformation;
public StompSubscription(ProtocolConverter stompTransport, String subscriptionId, ConsumerInfo consumerInfo, String transformation) { public StompSubscription(ProtocolConverter stompTransport, String subscriptionId, ConsumerInfo consumerInfo, String transformation) {
this.protocolConverter = stompTransport; this.protocolConverter = stompTransport;
@ -82,12 +81,12 @@ public class StompSubscription {
boolean ignoreTransformation = false; boolean ignoreTransformation = false;
if (transformation != null && !( message instanceof ActiveMQBytesMessage ) ) { if (transformation != null && !( message instanceof ActiveMQBytesMessage ) ) {
message.setReadOnlyProperties(false); message.setReadOnlyProperties(false);
message.setStringProperty(Stomp.Headers.TRANSFORMATION, transformation); message.setStringProperty(Stomp.Headers.TRANSFORMATION, transformation);
} else { } else {
if (message.getStringProperty(Stomp.Headers.TRANSFORMATION) != null) { if (message.getStringProperty(Stomp.Headers.TRANSFORMATION) != null) {
ignoreTransformation = true; ignoreTransformation = true;
} }
} }
StompFrame command = protocolConverter.convertMessage(message, ignoreTransformation); StompFrame command = protocolConverter.convertMessage(message, ignoreTransformation);
@ -101,24 +100,25 @@ public class StompSubscription {
} }
synchronized void onStompAbort(TransactionId transactionId) { synchronized void onStompAbort(TransactionId transactionId) {
unconsumedMessage.clear(); unconsumedMessage.clear();
} }
synchronized void onStompCommit(TransactionId transactionId) { synchronized void onStompCommit(TransactionId transactionId) {
for (Iterator iter = dispatchedMessage.entrySet().iterator(); iter.hasNext();) { for (Iterator<?> iter = dispatchedMessage.entrySet().iterator(); iter.hasNext();) {
@SuppressWarnings("rawtypes")
Map.Entry entry = (Entry)iter.next(); Map.Entry entry = (Entry)iter.next();
MessageId id = (MessageId)entry.getKey();
MessageDispatch msg = (MessageDispatch)entry.getValue(); MessageDispatch msg = (MessageDispatch)entry.getValue();
if (unconsumedMessage.contains(msg)) { if (unconsumedMessage.contains(msg)) {
iter.remove(); iter.remove();
} }
} }
unconsumedMessage.clear();
unconsumedMessage.clear();
} }
synchronized MessageAck onStompMessageAck(String messageId, TransactionId transactionId) { synchronized MessageAck onStompMessageAck(String messageId, TransactionId transactionId) {
MessageId msgId = new MessageId(messageId); MessageId msgId = new MessageId(messageId);
if (!dispatchedMessage.containsKey(msgId)) { if (!dispatchedMessage.containsKey(msgId)) {
return null; return null;
@ -129,10 +129,11 @@ public class StompSubscription {
ack.setConsumerId(consumerInfo.getConsumerId()); ack.setConsumerId(consumerInfo.getConsumerId());
if (ackMode == CLIENT_ACK) { if (ackMode == CLIENT_ACK) {
ack.setAckType(MessageAck.STANDARD_ACK_TYPE); ack.setAckType(MessageAck.STANDARD_ACK_TYPE);
int count = 0; int count = 0;
for (Iterator iter = dispatchedMessage.entrySet().iterator(); iter.hasNext();) { for (Iterator<?> iter = dispatchedMessage.entrySet().iterator(); iter.hasNext();) {
@SuppressWarnings("rawtypes")
Map.Entry entry = (Entry)iter.next(); Map.Entry entry = (Entry)iter.next();
MessageId id = (MessageId)entry.getKey(); MessageId id = (MessageId)entry.getKey();
MessageDispatch msg = (MessageDispatch)entry.getValue(); MessageDispatch msg = (MessageDispatch)entry.getValue();
@ -142,39 +143,59 @@ public class StompSubscription {
} }
if (transactionId != null) { if (transactionId != null) {
if (!unconsumedMessage.contains(msg)) { if (!unconsumedMessage.contains(msg)) {
unconsumedMessage.add(msg); unconsumedMessage.add(msg);
} }
} else { } else {
iter.remove(); iter.remove();
} }
count++; count++;
if (id.equals(msgId)) { if (id.equals(msgId)) {
ack.setLastMessageId(id); ack.setLastMessageId(id);
break; break;
} }
} }
ack.setMessageCount(count); ack.setMessageCount(count);
if (transactionId != null) { if (transactionId != null) {
ack.setTransactionId(transactionId); ack.setTransactionId(transactionId);
} }
}
else if (ackMode == INDIVIDUAL_ACK) { } else if (ackMode == INDIVIDUAL_ACK) {
ack.setAckType(MessageAck.INDIVIDUAL_ACK_TYPE); ack.setAckType(MessageAck.INDIVIDUAL_ACK_TYPE);
ack.setMessageID(msgId); ack.setMessageID(msgId);
if (transactionId != null) { if (transactionId != null) {
unconsumedMessage.add(dispatchedMessage.get(msgId)); unconsumedMessage.add(dispatchedMessage.get(msgId));
ack.setTransactionId(transactionId); ack.setTransactionId(transactionId);
} }
dispatchedMessage.remove(msgId); dispatchedMessage.remove(msgId);
} }
return ack; return ack;
} }
public MessageAck onStompMessageNack(String messageId, TransactionId transactionId) throws ProtocolException {
MessageId msgId = new MessageId(messageId);
if (!dispatchedMessage.containsKey(msgId)) {
return null;
}
MessageAck ack = new MessageAck();
ack.setDestination(consumerInfo.getDestination());
ack.setConsumerId(consumerInfo.getConsumerId());
ack.setAckType(MessageAck.POSION_ACK_TYPE);
ack.setMessageID(msgId);
if (transactionId != null) {
unconsumedMessage.add(dispatchedMessage.get(msgId));
ack.setTransactionId(transactionId);
}
dispatchedMessage.remove(msgId);
return null;
}
public String getAckMode() { public String getAckMode() {
return ackMode; return ackMode;
} }
@ -198,5 +219,4 @@ public class StompSubscription {
public ConsumerInfo getConsumerInfo() { public ConsumerInfo getConsumerInfo() {
return consumerInfo; return consumerInfo;
} }
} }

View File

@ -28,11 +28,14 @@ import org.apache.activemq.command.Command;
public interface StompTransport { public interface StompTransport {
public void sendToActiveMQ(Command command); public void sendToActiveMQ(Command command);
public void sendToStomp(StompFrame command) throws IOException; public void sendToStomp(StompFrame command) throws IOException;
public X509Certificate[] getPeerCertificates(); public X509Certificate[] getPeerCertificates();
public void onException(IOException error); public void onException(IOException error);
public StompInactivityMonitor getInactivityMonitor();
public StompWireFormat getWireFormat();
} }

View File

@ -16,48 +16,47 @@
*/ */
package org.apache.activemq.transport.stomp; package org.apache.activemq.transport.stomp;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map; import java.util.Map;
import javax.net.ServerSocketFactory;
import org.apache.activemq.broker.BrokerContext; import org.apache.activemq.broker.BrokerContext;
import org.apache.activemq.broker.BrokerService; import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.broker.BrokerServiceAware; import org.apache.activemq.broker.BrokerServiceAware;
import org.apache.activemq.transport.Transport; import org.apache.activemq.transport.Transport;
import org.apache.activemq.transport.tcp.TcpTransportFactory; import org.apache.activemq.transport.tcp.TcpTransportFactory;
import org.apache.activemq.transport.tcp.TcpTransportServer;
import org.apache.activemq.util.IntrospectionSupport; import org.apache.activemq.util.IntrospectionSupport;
import org.apache.activemq.wireformat.WireFormat; import org.apache.activemq.wireformat.WireFormat;
import org.apache.activemq.xbean.XBeanBrokerService;
/** /**
* A <a href="http://stomp.codehaus.org/">STOMP</a> transport factory * A <a href="http://stomp.codehaus.org/">STOMP</a> transport factory
* *
* *
*/ */
public class StompTransportFactory extends TcpTransportFactory implements BrokerServiceAware { public class StompTransportFactory extends TcpTransportFactory implements BrokerServiceAware {
private BrokerContext brokerContext = null; private BrokerContext brokerContext = null;
protected String getDefaultWireFormatType() { protected String getDefaultWireFormatType() {
return "stomp"; return "stomp";
} }
@SuppressWarnings("rawtypes")
public Transport compositeConfigure(Transport transport, WireFormat format, Map options) { public Transport compositeConfigure(Transport transport, WireFormat format, Map options) {
transport = new StompTransportFilter(transport, new LegacyFrameTranslator(), brokerContext); transport = new StompTransportFilter(transport, format, brokerContext);
IntrospectionSupport.setProperties(transport, options); IntrospectionSupport.setProperties(transport, options);
return super.compositeConfigure(transport, format, options); return super.compositeConfigure(transport, format, options);
} }
protected boolean isUseInactivityMonitor(Transport transport) { public void setBrokerService(BrokerService brokerService) {
// lets disable the inactivity monitor as stomp does not use keep alive this.brokerContext = brokerService.getBrokerContext();
// packets
return false;
} }
public void setBrokerService(BrokerService brokerService) { @Override
this.brokerContext = brokerService.getBrokerContext(); protected Transport createInactivityMonitor(Transport transport, WireFormat format) {
} StompInactivityMonitor monitor = new StompInactivityMonitor(transport, format);
StompTransportFilter filter = (StompTransportFilter) transport.narrow(StompTransportFilter.class);
filter.setInactivityMonitor(monitor);
return monitor;
}
} }

View File

@ -28,6 +28,7 @@ import org.apache.activemq.transport.TransportFilter;
import org.apache.activemq.transport.TransportListener; import org.apache.activemq.transport.TransportListener;
import org.apache.activemq.transport.tcp.SslTransport; import org.apache.activemq.transport.tcp.SslTransport;
import org.apache.activemq.util.IOExceptionSupport; import org.apache.activemq.util.IOExceptionSupport;
import org.apache.activemq.wireformat.WireFormat;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -36,20 +37,24 @@ import org.slf4j.LoggerFactory;
* configured with the StompWireFormat and is used to convert STOMP commands to * configured with the StompWireFormat and is used to convert STOMP commands to
* ActiveMQ commands. All of the conversion work is done by delegating to the * ActiveMQ commands. All of the conversion work is done by delegating to the
* ProtocolConverter. * ProtocolConverter.
* *
* @author <a href="http://hiramchirino.com">chirino</a> * @author <a href="http://hiramchirino.com">chirino</a>
*/ */
public class StompTransportFilter extends TransportFilter implements StompTransport { public class StompTransportFilter extends TransportFilter implements StompTransport {
private static final Logger LOG = LoggerFactory.getLogger(StompTransportFilter.class); private static final Logger LOG = LoggerFactory.getLogger(StompTransportFilter.class);
private final ProtocolConverter protocolConverter; private final ProtocolConverter protocolConverter;
private final FrameTranslator frameTranslator; private StompInactivityMonitor monitor;
private StompWireFormat wireFormat;
private boolean trace; private boolean trace;
public StompTransportFilter(Transport next, FrameTranslator translator, BrokerContext brokerContext) { public StompTransportFilter(Transport next, WireFormat wireFormat, BrokerContext brokerContext) {
super(next); super(next);
this.frameTranslator = translator; this.protocolConverter = new ProtocolConverter(this, brokerContext);
this.protocolConverter = new ProtocolConverter(this, translator, brokerContext);
if (wireFormat instanceof StompWireFormat) {
this.wireFormat = (StompWireFormat) wireFormat;
}
} }
public void oneway(Object o) throws IOException { public void oneway(Object o) throws IOException {
@ -66,7 +71,7 @@ public class StompTransportFilter extends TransportFilter implements StompTransp
if (trace) { if (trace) {
LOG.trace("Received: \n" + command); LOG.trace("Received: \n" + command);
} }
protocolConverter.onStompCommand((StompFrame)command); protocolConverter.onStompCommand((StompFrame)command);
} catch (IOException e) { } catch (IOException e) {
onException(e); onException(e);
@ -92,21 +97,17 @@ public class StompTransportFilter extends TransportFilter implements StompTransp
} }
} }
public FrameTranslator getFrameTranslator() {
return frameTranslator;
}
public X509Certificate[] getPeerCertificates() { public X509Certificate[] getPeerCertificates() {
if(next instanceof SslTransport) { if(next instanceof SslTransport) {
X509Certificate[] peerCerts = ((SslTransport)next).getPeerCertificates(); X509Certificate[] peerCerts = ((SslTransport)next).getPeerCertificates();
if (trace && peerCerts != null) { if (trace && peerCerts != null) {
LOG.debug("Peer Identity has been verified\n"); LOG.debug("Peer Identity has been verified\n");
} }
return peerCerts; return peerCerts;
} }
return null; return null;
} }
public boolean isTrace() { public boolean isTrace() {
return trace; return trace;
} }
@ -114,4 +115,18 @@ public class StompTransportFilter extends TransportFilter implements StompTransp
public void setTrace(boolean trace) { public void setTrace(boolean trace) {
this.trace = trace; this.trace = trace;
} }
@Override
public StompInactivityMonitor getInactivityMonitor() {
return monitor;
}
public void setInactivityMonitor(StompInactivityMonitor monitor) {
this.monitor = monitor;
}
@Override
public StompWireFormat getWireFormat() {
return this.wireFormat;
}
} }

View File

@ -21,8 +21,9 @@ import java.io.DataInputStream;
import java.io.DataOutput; import java.io.DataOutput;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import org.apache.activemq.util.ByteArrayInputStream; import org.apache.activemq.util.ByteArrayInputStream;
@ -44,6 +45,7 @@ public class StompWireFormat implements WireFormat {
private static final int MAX_HEADERS = 1000; private static final int MAX_HEADERS = 1000;
private static final int MAX_DATA_LENGTH = 1024 * 1024 * 100; private static final int MAX_DATA_LENGTH = 1024 * 1024 * 100;
private boolean encodingEnabled = false;
private int version = 1; private int version = 1;
public ByteSequence marshal(Object command) throws IOException { public ByteSequence marshal(Object command) throws IOException {
@ -63,16 +65,20 @@ public class StompWireFormat implements WireFormat {
public void marshal(Object command, DataOutput os) throws IOException { public void marshal(Object command, DataOutput os) throws IOException {
StompFrame stomp = (org.apache.activemq.transport.stomp.StompFrame)command; StompFrame stomp = (org.apache.activemq.transport.stomp.StompFrame)command;
StringBuffer buffer = new StringBuffer(); if (stomp.getAction().equals(Stomp.Commands.KEEPALIVE)) {
os.write(Stomp.BREAK);
return;
}
StringBuilder buffer = new StringBuilder();
buffer.append(stomp.getAction()); buffer.append(stomp.getAction());
buffer.append(Stomp.NEWLINE); buffer.append(Stomp.NEWLINE);
// Output the headers. // Output the headers.
for (Iterator iter = stomp.getHeaders().entrySet().iterator(); iter.hasNext();) { for (Map.Entry<String, String> entry : stomp.getHeaders().entrySet()) {
Map.Entry entry = (Map.Entry)iter.next();
buffer.append(entry.getKey()); buffer.append(entry.getKey());
buffer.append(Stomp.Headers.SEPERATOR); buffer.append(Stomp.Headers.SEPERATOR);
buffer.append(entry.getValue()); buffer.append(encodeHeader(entry.getValue()));
buffer.append(Stomp.NEWLINE); buffer.append(Stomp.NEWLINE);
} }
@ -87,7 +93,7 @@ public class StompWireFormat implements WireFormat {
public Object unmarshal(DataInput in) throws IOException { public Object unmarshal(DataInput in) throws IOException {
try { try {
// parse action // parse action
String action = parseAction(in); String action = parseAction(in);
@ -129,7 +135,6 @@ public class StompWireFormat implements WireFormat {
baos.close(); baos.close();
data = baos.toByteArray(); data = baos.toByteArray();
} }
} }
return new StompFrame(action, headers, data); return new StompFrame(action, headers, data);
@ -137,10 +142,14 @@ public class StompWireFormat implements WireFormat {
} catch (ProtocolException e) { } catch (ProtocolException e) {
return new StompFrameError(e); return new StompFrameError(e);
} }
} }
private String readLine(DataInput in, int maxLength, String errorMessage) throws IOException { private String readLine(DataInput in, int maxLength, String errorMessage) throws IOException {
ByteSequence sequence = readHeaderLine(in, maxLength, errorMessage);
return new String(sequence.getData(), sequence.getOffset(), sequence.getLength(), "UTF-8").trim();
}
private ByteSequence readHeaderLine(DataInput in, int maxLength, String errorMessage) throws IOException {
byte b; byte b;
ByteArrayOutputStream baos = new ByteArrayOutputStream(maxLength); ByteArrayOutputStream baos = new ByteArrayOutputStream(maxLength);
while ((b = in.readByte()) != '\n') { while ((b = in.readByte()) != '\n') {
@ -150,10 +159,9 @@ public class StompWireFormat implements WireFormat {
baos.write(b); baos.write(b);
} }
baos.close(); baos.close();
ByteSequence sequence = baos.toByteSequence(); return baos.toByteSequence();
return new String(sequence.getData(), sequence.getOffset(), sequence.getLength(), "UTF-8");
} }
protected String parseAction(DataInput in) throws IOException { protected String parseAction(DataInput in) throws IOException {
String action = null; String action = null;
@ -171,21 +179,35 @@ public class StompWireFormat implements WireFormat {
} }
return action; return action;
} }
protected HashMap<String, String> parseHeaders(DataInput in) throws IOException { protected HashMap<String, String> parseHeaders(DataInput in) throws IOException {
HashMap<String, String> headers = new HashMap<String, String>(25); HashMap<String, String> headers = new HashMap<String, String>(25);
while (true) { while (true) {
String line = readLine(in, MAX_HEADER_LENGTH, "The maximum header length was exceeded"); ByteSequence line = readHeaderLine(in, MAX_HEADER_LENGTH, "The maximum header length was exceeded");
if (line != null && line.trim().length() > 0) { if (line != null && line.length > 0) {
if (headers.size() > MAX_HEADERS) { if (headers.size() > MAX_HEADERS) {
throw new ProtocolException("The maximum number of headers was exceeded", true); throw new ProtocolException("The maximum number of headers was exceeded", true);
} }
try { try {
int seperatorIndex = line.indexOf(Stomp.Headers.SEPERATOR);
String name = line.substring(0, seperatorIndex).trim(); ByteArrayInputStream headerLine = new ByteArrayInputStream(line);
String value = line.substring(seperatorIndex + 1, line.length()).trim(); ByteArrayOutputStream stream = new ByteArrayOutputStream(line.length);
// First complete the name
int result = -1;
while ((result = headerLine.read()) != -1) {
if (result != ':') {
stream.write(result);
} else {
break;
}
}
ByteSequence nameSeq = stream.toByteSequence();
String name = new String(nameSeq.getData(), nameSeq.getOffset(), nameSeq.getLength(), "UTF-8").trim();
String value = decodeHeader(headerLine).trim();
headers.put(name, value); headers.put(name, value);
} catch (Exception e) { } catch (Exception e) {
throw new ProtocolException("Unable to parser header line [" + line + "]", true); throw new ProtocolException("Unable to parser header line [" + line + "]", true);
@ -193,10 +215,10 @@ public class StompWireFormat implements WireFormat {
} else { } else {
break; break;
} }
} }
return headers; return headers;
} }
protected int parseContentLength(String contentLength) throws ProtocolException { protected int parseContentLength(String contentLength) throws ProtocolException {
int length; int length;
try { try {
@ -208,10 +230,71 @@ public class StompWireFormat implements WireFormat {
if (length > MAX_DATA_LENGTH) { if (length > MAX_DATA_LENGTH) {
throw new ProtocolException("The maximum data length was exceeded", true); throw new ProtocolException("The maximum data length was exceeded", true);
} }
return length; return length;
} }
private String encodeHeader(String header) throws IOException {
String result = header;
if (this.encodingEnabled) {
byte[] utf8buf = header.getBytes("UTF-8");
ByteArrayOutputStream stream = new ByteArrayOutputStream(utf8buf.length);
for(byte val : utf8buf) {
switch(val) {
case Stomp.ESCAPE:
stream.write(Stomp.ESCAPE_ESCAPE_SEQ);
break;
case Stomp.BREAK:
stream.write(Stomp.NEWLINE_ESCAPE_SEQ);
break;
case Stomp.COLON:
stream.write(Stomp.COLON_ESCAPE_SEQ);
break;
default:
stream.write(val);
}
}
}
return result;
}
private String decodeHeader(InputStream header) throws IOException {
ByteArrayOutputStream decoded = new ByteArrayOutputStream();
PushbackInputStream stream = new PushbackInputStream(header);
int value = -1;
while( (value = stream.read()) != -1) {
if (value == 92) {
int next = stream.read();
if (next != -1) {
switch(next) {
case 110:
decoded.write(Stomp.BREAK);
break;
case 99:
decoded.write(Stomp.COLON);
break;
case 92:
decoded.write(Stomp.ESCAPE);
break;
default:
stream.unread(next);
decoded.write(value);
}
} else {
decoded.write(value);
}
} else {
decoded.write(value);
}
}
return new String(decoded.toByteArray(), "UTF-8");
}
public int getVersion() { public int getVersion() {
return version; return version;
} }
@ -220,4 +303,12 @@ public class StompWireFormat implements WireFormat {
this.version = version; this.version = version;
} }
public boolean isEncodingEnabled() {
return this.encodingEnabled;
}
public void setEncodingEnabled(boolean value) {
this.encodingEnabled = value;
}
} }

View File

@ -20,9 +20,6 @@ import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -30,21 +27,13 @@ import java.util.Map;
import javax.net.ServerSocketFactory; import javax.net.ServerSocketFactory;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.broker.BrokerServiceAware;
import org.apache.activemq.broker.SslContext; import org.apache.activemq.broker.SslContext;
import org.apache.activemq.openwire.OpenWireFormat;
import org.apache.activemq.transport.InactivityMonitor;
import org.apache.activemq.transport.Transport; import org.apache.activemq.transport.Transport;
import org.apache.activemq.transport.TransportFactory;
import org.apache.activemq.transport.TransportLoggerFactory;
import org.apache.activemq.transport.TransportServer; import org.apache.activemq.transport.TransportServer;
import org.apache.activemq.transport.WireFormatNegotiator;
import org.apache.activemq.util.IOExceptionSupport; import org.apache.activemq.util.IOExceptionSupport;
import org.apache.activemq.util.IntrospectionSupport; import org.apache.activemq.util.IntrospectionSupport;
import org.apache.activemq.util.URISupport; import org.apache.activemq.util.URISupport;
@ -57,15 +46,15 @@ import org.slf4j.LoggerFactory;
* contribution from this class is that it is aware of SslTransportServer and * contribution from this class is that it is aware of SslTransportServer and
* SslTransport classes. All Transports and TransportServers created from this * SslTransport classes. All Transports and TransportServers created from this
* factory will have their needClientAuth option set to false. * factory will have their needClientAuth option set to false.
* *
* @author sepandm@gmail.com (Sepand) * @author sepandm@gmail.com (Sepand)
* @author David Martin Clavo david(dot)martin(dot)clavo(at)gmail.com (logging improvement modifications) * @author David Martin Clavo david(dot)martin(dot)clavo(at)gmail.com (logging improvement modifications)
* *
*/ */
public class SslTransportFactory extends TcpTransportFactory { public class SslTransportFactory extends TcpTransportFactory {
// The log this uses., // The log this uses.,
private static final Logger LOG = LoggerFactory.getLogger(SslTransportFactory.class); private static final Logger LOG = LoggerFactory.getLogger(SslTransportFactory.class);
/** /**
* Overriding to use SslTransportServer and allow for proper reflection. * Overriding to use SslTransportServer and allow for proper reflection.
*/ */
@ -91,6 +80,7 @@ public class SslTransportFactory extends TcpTransportFactory {
* Overriding to allow for proper configuration through reflection but delegate to get common * Overriding to allow for proper configuration through reflection but delegate to get common
* configuration * configuration
*/ */
@SuppressWarnings("rawtypes")
public Transport compositeConfigure(Transport transport, WireFormat format, Map options) { public Transport compositeConfigure(Transport transport, WireFormat format, Map options) {
SslTransport sslTransport = (SslTransport)transport.narrow(SslTransport.class); SslTransport sslTransport = (SslTransport)transport.narrow(SslTransport.class);
@ -120,14 +110,12 @@ public class SslTransportFactory extends TcpTransportFactory {
return new SslTransport(wf, (SSLSocketFactory)socketFactory, location, localLocation, false); return new SslTransport(wf, (SSLSocketFactory)socketFactory, location, localLocation, false);
} }
/** /**
* Creates a new SSL ServerSocketFactory. The given factory will use * Creates a new SSL ServerSocketFactory. The given factory will use
* user-provided key and trust managers (if the user provided them). * user-provided key and trust managers (if the user provided them).
* *
* @return Newly created (Ssl)ServerSocketFactory. * @return Newly created (Ssl)ServerSocketFactory.
* @throws IOException * @throws IOException
*/ */
protected ServerSocketFactory createServerSocketFactory() throws IOException { protected ServerSocketFactory createServerSocketFactory() throws IOException {
if( SslContext.getCurrentSslContext()!=null ) { if( SslContext.getCurrentSslContext()!=null ) {
@ -145,12 +133,12 @@ public class SslTransportFactory extends TcpTransportFactory {
/** /**
* Creates a new SSL SocketFactory. The given factory will use user-provided * Creates a new SSL SocketFactory. The given factory will use user-provided
* key and trust managers (if the user provided them). * key and trust managers (if the user provided them).
* *
* @return Newly created (Ssl)SocketFactory. * @return Newly created (Ssl)SocketFactory.
* @throws IOException * @throws IOException
*/ */
protected SocketFactory createSocketFactory() throws IOException { protected SocketFactory createSocketFactory() throws IOException {
if( SslContext.getCurrentSslContext()!=null ) { if( SslContext.getCurrentSslContext()!=null ) {
SslContext ctx = SslContext.getCurrentSslContext(); SslContext ctx = SslContext.getCurrentSslContext();
try { try {
@ -161,11 +149,10 @@ public class SslTransportFactory extends TcpTransportFactory {
} else { } else {
return SSLSocketFactory.getDefault(); return SSLSocketFactory.getDefault();
} }
} }
/** /**
* *
* @param km * @param km
* @param tm * @param tm
* @param random * @param random

View File

@ -20,16 +20,22 @@ import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.net.*; import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import org.apache.activemq.Service; import org.apache.activemq.Service;
import org.apache.activemq.thread.DefaultThreadPools; import org.apache.activemq.thread.DefaultThreadPools;
import org.apache.activemq.transport.Transport; import org.apache.activemq.transport.Transport;
@ -64,7 +70,6 @@ public class TcpTransport extends TransportThreadSupport implements Transport, S
protected DataInputStream dataIn; protected DataInputStream dataIn;
protected TimeStampStream buffOut = null; protected TimeStampStream buffOut = null;
/** /**
* The Traffic Class to be set on the socket. * The Traffic Class to be set on the socket.
*/ */
@ -636,7 +641,6 @@ public class TcpTransport extends TransportThreadSupport implements Transport, S
return receiveCounter; return receiveCounter;
} }
/** /**
* @param sock The socket on which to set the Traffic Class. * @param sock The socket on which to set the Traffic Class.
* @return Whether or not the Traffic Class was set on the given socket. * @return Whether or not the Traffic Class was set on the given socket.

View File

@ -79,6 +79,7 @@ public class TcpTransportFactory extends TransportFactory {
return new TcpTransportServer(this, location, serverSocketFactory); return new TcpTransportServer(this, location, serverSocketFactory);
} }
@SuppressWarnings("rawtypes")
public Transport compositeConfigure(Transport transport, WireFormat format, Map options) { public Transport compositeConfigure(Transport transport, WireFormat format, Map options) {
TcpTransport tcpTransport = (TcpTransport)transport.narrow(TcpTransport.class); TcpTransport tcpTransport = (TcpTransport)transport.narrow(TcpTransport.class);
@ -98,11 +99,10 @@ public class TcpTransportFactory extends TransportFactory {
boolean useInactivityMonitor = "true".equals(getOption(options, "useInactivityMonitor", "true")); boolean useInactivityMonitor = "true".equals(getOption(options, "useInactivityMonitor", "true"));
if (useInactivityMonitor && isUseInactivityMonitor(transport)) { if (useInactivityMonitor && isUseInactivityMonitor(transport)) {
transport = new InactivityMonitor(transport, format); transport = createInactivityMonitor(transport, format);
IntrospectionSupport.setProperties(transport, options); IntrospectionSupport.setProperties(transport, options);
} }
// Only need the WireFormatNegotiator if using openwire // Only need the WireFormatNegotiator if using openwire
if (format instanceof OpenWireFormat) { if (format instanceof OpenWireFormat) {
transport = new WireFormatNegotiator(transport, (OpenWireFormat)format, tcpTransport.getMinmumWireFormatVersion()); transport = new WireFormatNegotiator(transport, (OpenWireFormat)format, tcpTransport.getMinmumWireFormatVersion());
@ -162,4 +162,8 @@ public class TcpTransportFactory extends TransportFactory {
protected SocketFactory createSocketFactory() throws IOException { protected SocketFactory createSocketFactory() throws IOException {
return SocketFactory.getDefault(); return SocketFactory.getDefault();
} }
protected Transport createInactivityMonitor(Transport transport, WireFormat format) {
return new InactivityMonitor(transport, format);
}
} }

View File

@ -27,7 +27,6 @@ import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;

View File

@ -0,0 +1,546 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.transport.stomp;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import org.apache.activemq.CombinationTestSupport;
import org.apache.activemq.broker.BrokerFactory;
import org.apache.activemq.broker.BrokerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Stomp11Test extends CombinationTestSupport {
private static final Logger LOG = LoggerFactory.getLogger(StompTest.class);
protected String bindAddress = "stomp://localhost:61613";
protected String confUri = "xbean:org/apache/activemq/transport/stomp/stomp-auth-broker.xml";
protected String jmsUri = "vm://localhost";
private BrokerService broker;
private StompConnection stompConnection = new StompConnection();
@Override
protected void setUp() throws Exception {
broker = BrokerFactory.createBroker(new URI(confUri));
broker.start();
broker.waitUntilStarted();
stompConnect();
}
private void stompConnect() throws IOException, URISyntaxException, UnknownHostException {
URI connectUri = new URI(bindAddress);
stompConnection.open(createSocket(connectUri));
}
protected Socket createSocket(URI connectUri) throws IOException {
return new Socket("127.0.0.1", connectUri.getPort());
}
protected String getQueueName() {
return getClass().getName() + "." + getName();
}
@Override
protected void tearDown() throws Exception {
try {
stompDisconnect();
} catch(Exception e) {
// Some tests explicitly disconnect from stomp so can ignore
} finally {
broker.stop();
broker.waitUntilStopped();
}
}
private void stompDisconnect() throws IOException {
if (stompConnection != null) {
stompConnection.close();
stompConnection = null;
}
}
public void testConnect() throws Exception {
String connectFrame = "STOMP\n" +
"login: system\n" +
"passcode: manager\n" +
"accept-version:1.1\n" +
"host:localhost\n" +
"request-id: 1\n" +
"\n" + Stomp.NULL;
stompConnection.sendFrame(connectFrame);
String f = stompConnection.receiveFrame();
LOG.debug("Broker sent: " + f);
assertTrue(f.startsWith("CONNECTED"));
assertTrue(f.indexOf("response-id:1") >= 0);
assertTrue(f.indexOf("version:1.1") >= 0);
assertTrue(f.indexOf("session:") >= 0);
String frame = "DISCONNECT\n" + "\n\n" + Stomp.NULL;
stompConnection.sendFrame(frame);
}
public void testConnectWithVersionOptions() throws Exception {
String connectFrame = "STOMP\n" +
"login: system\n" +
"passcode: manager\n" +
"accept-version:1.0,1.1\n" +
"host:localhost\n" +
"\n" + Stomp.NULL;
stompConnection.sendFrame(connectFrame);
String f = stompConnection.receiveFrame();
LOG.debug("Broker sent: " + f);
assertTrue(f.startsWith("CONNECTED"));
assertTrue(f.indexOf("version:1.1") >= 0);
assertTrue(f.indexOf("session:") >= 0);
String frame = "DISCONNECT\n" + "\n\n" + Stomp.NULL;
stompConnection.sendFrame(frame);
}
public void testConnectWithValidFallback() throws Exception {
String connectFrame = "STOMP\n" +
"login: system\n" +
"passcode: manager\n" +
"accept-version:1.0,10.1\n" +
"host:localhost\n" +
"\n" + Stomp.NULL;
stompConnection.sendFrame(connectFrame);
String f = stompConnection.receiveFrame();
LOG.debug("Broker sent: " + f);
assertTrue(f.startsWith("CONNECTED"));
assertTrue(f.indexOf("version:1.0") >= 0);
assertTrue(f.indexOf("session:") >= 0);
String frame = "DISCONNECT\n" + "\n\n" + Stomp.NULL;
stompConnection.sendFrame(frame);
}
public void testConnectWithInvalidFallback() throws Exception {
String connectFrame = "STOMP\n" +
"login: system\n" +
"passcode: manager\n" +
"accept-version:9.0,10.1\n" +
"host:localhost\n" +
"\n" + Stomp.NULL;
stompConnection.sendFrame(connectFrame);
String f = stompConnection.receiveFrame();
LOG.debug("Broker sent: " + f);
assertTrue(f.startsWith("ERROR"));
assertTrue(f.indexOf("version") >= 0);
assertTrue(f.indexOf("message:") >= 0);
}
public void testHeartbeats() throws Exception {
String connectFrame = "STOMP\n" +
"login: system\n" +
"passcode: manager\n" +
"accept-version:1.1\n" +
"heart-beat:0,1000\n" +
"host:localhost\n" +
"\n" + Stomp.NULL;
stompConnection.sendFrame(connectFrame);
String f = stompConnection.receiveFrame();
assertTrue(f.startsWith("CONNECTED"));
assertTrue(f.indexOf("version:1.1") >= 0);
assertTrue(f.indexOf("heart-beat:") >= 0);
assertTrue(f.indexOf("session:") >= 0);
LOG.debug("Broker sent: " + f);
stompConnection.getStompSocket().getOutputStream().write('\n');
DataInputStream in = new DataInputStream(stompConnection.getStompSocket().getInputStream());
in.read();
{
long startTime = System.currentTimeMillis();
int input = in.read();
assertEquals("did not receive the correct hear beat value", '\n', input);
long endTime = System.currentTimeMillis();
assertTrue("Broker did not send KeepAlive in time", (endTime - startTime) >= 900);
}
{
long startTime = System.currentTimeMillis();
int input = in.read();
assertEquals("did not receive the correct hear beat value", '\n', input);
long endTime = System.currentTimeMillis();
assertTrue("Broker did not send KeepAlive in time", (endTime - startTime) >= 900);
}
String frame = "DISCONNECT\n" + "\n\n" + Stomp.NULL;
stompConnection.sendFrame(frame);
}
public void testHeartbeatsDropsIdleConnection() throws Exception {
String connectFrame = "STOMP\n" +
"login: system\n" +
"passcode: manager\n" +
"accept-version:1.1\n" +
"heart-beat:1000,0\n" +
"host:localhost\n" +
"\n" + Stomp.NULL;
stompConnection.sendFrame(connectFrame);
String f = stompConnection.receiveFrame();
assertTrue(f.startsWith("CONNECTED"));
assertTrue(f.indexOf("version:1.1") >= 0);
assertTrue(f.indexOf("heart-beat:") >= 0);
assertTrue(f.indexOf("session:") >= 0);
LOG.debug("Broker sent: " + f);
long startTime = System.currentTimeMillis();
try {
f = stompConnection.receiveFrame();
LOG.debug("Broker sent: " + f);
fail();
} catch(Exception e) {
}
long endTime = System.currentTimeMillis();
assertTrue("Broker did close idle connection in time.", (endTime - startTime) >= 1000);
}
public void testRejectInvalidHeartbeats1() throws Exception {
String connectFrame = "STOMP\n" +
"login: system\n" +
"passcode: manager\n" +
"accept-version:1.1\n" +
"heart-beat:0\n" +
"host:localhost\n" +
"\n" + Stomp.NULL;
stompConnection.sendFrame(connectFrame);
String f = stompConnection.receiveFrame();
LOG.debug("Broker sent: " + f);
assertTrue(f.startsWith("ERROR"));
assertTrue(f.indexOf("heart-beat") >= 0);
assertTrue(f.indexOf("message:") >= 0);
}
public void testRejectInvalidHeartbeats2() throws Exception {
String connectFrame = "STOMP\n" +
"login: system\n" +
"passcode: manager\n" +
"accept-version:1.1\n" +
"heart-beat:T,0\n" +
"host:localhost\n" +
"\n" + Stomp.NULL;
stompConnection.sendFrame(connectFrame);
String f = stompConnection.receiveFrame();
LOG.debug("Broker sent: " + f);
assertTrue(f.startsWith("ERROR"));
assertTrue(f.indexOf("heart-beat") >= 0);
assertTrue(f.indexOf("message:") >= 0);
}
public void testRejectInvalidHeartbeats3() throws Exception {
String connectFrame = "STOMP\n" +
"login: system\n" +
"passcode: manager\n" +
"accept-version:1.1\n" +
"heart-beat:100,10,50\n" +
"host:localhost\n" +
"\n" + Stomp.NULL;
stompConnection.sendFrame(connectFrame);
String f = stompConnection.receiveFrame();
LOG.debug("Broker sent: " + f);
assertTrue(f.startsWith("ERROR"));
assertTrue(f.indexOf("heart-beat") >= 0);
assertTrue(f.indexOf("message:") >= 0);
}
public void testSubscribeAndUnsubscribe() throws Exception {
String connectFrame = "STOMP\n" +
"login: system\n" +
"passcode: manager\n" +
"accept-version:1.1\n" +
"host:localhost\n" +
"\n" + Stomp.NULL;
stompConnection.sendFrame(connectFrame);
String f = stompConnection.receiveFrame();
LOG.debug("Broker sent: " + f);
assertTrue(f.startsWith("CONNECTED"));
String message = "SEND\n" + "destination:/queue/" + getQueueName() + "\n\n" + "Hello World" + Stomp.NULL;
stompConnection.sendFrame(message);
String frame = "SUBSCRIBE\n" + "destination:/queue/" + getQueueName() + "\n" +
"id:12345\n" + "ack:auto\n\n" + Stomp.NULL;
stompConnection.sendFrame(frame);
frame = stompConnection.receiveFrame();
assertTrue(frame.startsWith("MESSAGE"));
frame = "UNSUBSCRIBE\n" + "destination:/queue/" + getQueueName() + "\n" +
"id:12345\n\n" + Stomp.NULL;
stompConnection.sendFrame(frame);
Thread.sleep(2000);
stompConnection.sendFrame(message);
try {
frame = stompConnection.receiveFrame();
LOG.info("Received frame: " + frame);
fail("No message should have been received since subscription was removed");
} catch (SocketTimeoutException e) {
}
frame = "DISCONNECT\n" + "\n\n" + Stomp.NULL;
stompConnection.sendFrame(frame);
}
public void testSubscribeWithNoId() throws Exception {
String connectFrame = "STOMP\n" +
"login: system\n" +
"passcode: manager\n" +
"accept-version:1.1\n" +
"host:localhost\n" +
"\n" + Stomp.NULL;
stompConnection.sendFrame(connectFrame);
String f = stompConnection.receiveFrame();
LOG.debug("Broker sent: " + f);
assertTrue(f.startsWith("CONNECTED"));
String frame = "SUBSCRIBE\n" + "destination:/queue/" + getQueueName() + "\n" +
"ack:auto\n\n" + Stomp.NULL;
stompConnection.sendFrame(frame);
frame = stompConnection.receiveFrame();
assertTrue(frame.startsWith("ERROR"));
frame = "DISCONNECT\n" + "\n\n" + Stomp.NULL;
stompConnection.sendFrame(frame);
}
public void testUnsubscribeWithNoId() throws Exception {
String connectFrame = "STOMP\n" +
"login: system\n" +
"passcode: manager\n" +
"accept-version:1.1\n" +
"host:localhost\n" +
"\n" + Stomp.NULL;
stompConnection.sendFrame(connectFrame);
String f = stompConnection.receiveFrame();
LOG.debug("Broker sent: " + f);
assertTrue(f.startsWith("CONNECTED"));
String frame = "SUBSCRIBE\n" + "destination:/queue/" + getQueueName() + "\n" +
"id:12345\n" + "ack:auto\n\n" + Stomp.NULL;
stompConnection.sendFrame(frame);
Thread.sleep(2000);
frame = "UNSUBSCRIBE\n" + "destination:/queue/" + getQueueName() + "\n" + "\n\n" + Stomp.NULL;
stompConnection.sendFrame(frame);
frame = stompConnection.receiveFrame();
assertTrue(frame.startsWith("ERROR"));
frame = "DISCONNECT\n" + "\n\n" + Stomp.NULL;
stompConnection.sendFrame(frame);
}
public void testAckMessageWithId() throws Exception {
String connectFrame = "STOMP\n" +
"login: system\n" +
"passcode: manager\n" +
"accept-version:1.1\n" +
"host:localhost\n" +
"\n" + Stomp.NULL;
stompConnection.sendFrame(connectFrame);
String f = stompConnection.receiveFrame();
LOG.debug("Broker sent: " + f);
assertTrue(f.startsWith("CONNECTED"));
String message = "SEND\n" + "destination:/queue/" + getQueueName() + "\n\n" + "Hello World" + Stomp.NULL;
stompConnection.sendFrame(message);
String frame = "SUBSCRIBE\n" + "destination:/queue/" + getQueueName() + "\n" +
"id:12345\n" + "ack:client\n\n" + Stomp.NULL;
stompConnection.sendFrame(frame);
StompFrame received = stompConnection.receive();
assertTrue(received.getAction().equals("MESSAGE"));
frame = "ACK\n" + "subscription:12345\n" + "message-id:" +
received.getHeaders().get("message-id") + "\n\n" + Stomp.NULL;
stompConnection.sendFrame(frame);
frame = "UNSUBSCRIBE\n" + "destination:/queue/" + getQueueName() + "\n" +
"id:12345\n\n" + Stomp.NULL;
stompConnection.sendFrame(frame);
frame = "DISCONNECT\n" + "\n\n" + Stomp.NULL;
stompConnection.sendFrame(frame);
}
public void testAckMessageWithNoId() throws Exception {
String connectFrame = "STOMP\n" +
"login: system\n" +
"passcode: manager\n" +
"accept-version:1.1\n" +
"host:localhost\n" +
"\n" + Stomp.NULL;
stompConnection.sendFrame(connectFrame);
String f = stompConnection.receiveFrame();
LOG.debug("Broker sent: " + f);
assertTrue(f.startsWith("CONNECTED"));
String message = "SEND\n" + "destination:/queue/" + getQueueName() + "\n\n" + "Hello World" + Stomp.NULL;
stompConnection.sendFrame(message);
String subscribe = "SUBSCRIBE\n" + "destination:/queue/" + getQueueName() + "\n" +
"id:12345\n" + "ack:client\n\n" + Stomp.NULL;
stompConnection.sendFrame(subscribe);
StompFrame received = stompConnection.receive();
assertTrue(received.getAction().equals("MESSAGE"));
String ack = "ACK\n" + "message-id:" +
received.getHeaders().get("message-id") + "\n\n" + Stomp.NULL;
stompConnection.sendFrame(ack);
StompFrame error = stompConnection.receive();
assertTrue(error.getAction().equals("ERROR"));
String unsub = "UNSUBSCRIBE\n" + "destination:/queue/" + getQueueName() + "\n" +
"id:12345\n\n" + Stomp.NULL;
stompConnection.sendFrame(unsub);
String frame = "DISCONNECT\n" + "\n\n" + Stomp.NULL;
stompConnection.sendFrame(frame);
}
public void testQueueBrowerSubscription() throws Exception {
final int MSG_COUNT = 10;
String connectFrame = "STOMP\n" +
"login: system\n" +
"passcode: manager\n" +
"accept-version:1.1\n" +
"host:localhost\n" +
"\n" + Stomp.NULL;
stompConnection.sendFrame(connectFrame);
String f = stompConnection.receiveFrame();
LOG.debug("Broker sent: " + f);
assertTrue(f.startsWith("CONNECTED"));
for(int i = 0; i < MSG_COUNT; ++i) {
String message = "SEND\n" + "destination:/queue/" + getQueueName() + "\n" +
"receipt:0\n" +
"\n" + "Hello World {" + i + "}" + Stomp.NULL;
stompConnection.sendFrame(message);
StompFrame repsonse = stompConnection.receive();
assertEquals("0", repsonse.getHeaders().get(Stomp.Headers.Response.RECEIPT_ID));
}
String subscribe = "SUBSCRIBE\n" + "destination:/queue/" + getQueueName() + "\n" +
"id:12345\n" + "browser:true\n\n" + Stomp.NULL;
stompConnection.sendFrame(subscribe);
for(int i = 0; i < MSG_COUNT; ++i) {
StompFrame message = stompConnection.receive();
assertEquals(Stomp.Responses.MESSAGE, message.getAction());
assertEquals("12345", message.getHeaders().get(Stomp.Headers.Message.SUBSCRIPTION));
}
// We should now get a browse done message
StompFrame browseDone = stompConnection.receive();
LOG.debug("Browse Done: " + browseDone.toString());
assertEquals(Stomp.Responses.MESSAGE, browseDone.getAction());
assertEquals("12345", browseDone.getHeaders().get(Stomp.Headers.Message.SUBSCRIPTION));
assertEquals("end", browseDone.getHeaders().get(Stomp.Headers.Message.BROWSER));
assertTrue(browseDone.getHeaders().get(Stomp.Headers.Message.DESTINATION) != null);
String unsub = "UNSUBSCRIBE\n" + "destination:/queue/" + getQueueName() + "\n" +
"id:12345\n\n" + Stomp.NULL;
stompConnection.sendFrame(unsub);
Thread.sleep(2000);
subscribe = "SUBSCRIBE\n" + "destination:/queue/" + getQueueName() + "\n" + "id:12345\n\n" + Stomp.NULL;
stompConnection.sendFrame(subscribe);
for(int i = 0; i < MSG_COUNT; ++i) {
StompFrame message = stompConnection.receive();
assertEquals(Stomp.Responses.MESSAGE, message.getAction());
assertEquals("12345", message.getHeaders().get(Stomp.Headers.Message.SUBSCRIPTION));
}
stompConnection.sendFrame(unsub);
String frame = "DISCONNECT\n" + "\n\n" + Stomp.NULL;
stompConnection.sendFrame(frame);
}
}

View File

@ -26,6 +26,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.jms.BytesMessage; import javax.jms.BytesMessage;
import javax.jms.Connection; import javax.jms.Connection;
import javax.jms.JMSException; import javax.jms.JMSException;
@ -36,6 +37,7 @@ import javax.jms.ObjectMessage;
import javax.jms.Session; import javax.jms.Session;
import javax.jms.TextMessage; import javax.jms.TextMessage;
import javax.management.ObjectName; import javax.management.ObjectName;
import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.CombinationTestSupport; import org.apache.activemq.CombinationTestSupport;
import org.apache.activemq.broker.BrokerFactory; import org.apache.activemq.broker.BrokerFactory;
@ -318,7 +320,7 @@ public class StompTest extends CombinationTestSupport {
assertEquals("Hello World", message.getText()); assertEquals("Hello World", message.getText());
assertEquals("getJMSPriority", 4, message.getJMSPriority()); assertEquals("getJMSPriority", 4, message.getJMSPriority());
} }
public void testReceipts() throws Exception { public void testReceipts() throws Exception {
StompConnection receiver = new StompConnection(); StompConnection receiver = new StompConnection();
@ -449,7 +451,7 @@ public class StompTest extends CombinationTestSupport {
public void testSendMultipleBytesMessages() throws Exception { public void testSendMultipleBytesMessages() throws Exception {
final int MSG_COUNT = 50; final int MSG_COUNT = 50;
String frame = "CONNECT\n" + "login: system\n" + "passcode: manager\n\n" + Stomp.NULL; String frame = "CONNECT\n" + "login: system\n" + "passcode: manager\n\n" + Stomp.NULL;
stompConnection.sendFrame(frame); stompConnection.sendFrame(frame);

View File

@ -21,9 +21,9 @@ import java.security.cert.X509Certificate;
import org.apache.activemq.command.Command; import org.apache.activemq.command.Command;
import org.apache.activemq.transport.TransportSupport; import org.apache.activemq.transport.TransportSupport;
import org.apache.activemq.transport.stomp.LegacyFrameTranslator;
import org.apache.activemq.transport.stomp.ProtocolConverter; import org.apache.activemq.transport.stomp.ProtocolConverter;
import org.apache.activemq.transport.stomp.StompFrame; import org.apache.activemq.transport.stomp.StompFrame;
import org.apache.activemq.transport.stomp.StompInactivityMonitor;
import org.apache.activemq.transport.stomp.StompTransport; import org.apache.activemq.transport.stomp.StompTransport;
import org.apache.activemq.transport.stomp.StompWireFormat; import org.apache.activemq.transport.stomp.StompWireFormat;
import org.apache.activemq.util.ByteSequence; import org.apache.activemq.util.ByteSequence;
@ -32,19 +32,19 @@ import org.apache.activemq.util.ServiceStopper;
import org.eclipse.jetty.websocket.WebSocket; import org.eclipse.jetty.websocket.WebSocket;
/** /**
* *
* Implements web socket and mediates between servlet and the broker * Implements web socket and mediates between servlet and the broker
* *
*/ */
class StompSocket extends TransportSupport implements WebSocket, StompTransport { class StompSocket extends TransportSupport implements WebSocket, StompTransport {
Outbound outbound; Outbound outbound;
ProtocolConverter protocolConverter = new ProtocolConverter(this, new LegacyFrameTranslator(), null); ProtocolConverter protocolConverter = new ProtocolConverter(this, null);
StompWireFormat wireFormat = new StompWireFormat(); StompWireFormat wireFormat = new StompWireFormat();
public void onConnect(Outbound outbound) { public void onConnect(Outbound outbound) {
this.outbound=outbound; this.outbound=outbound;
} }
public void onMessage(byte frame, byte[] data,int offset, int length) {} public void onMessage(byte frame, byte[] data,int offset, int length) {}
public void onMessage(byte frame, String data) { public void onMessage(byte frame, String data) {
@ -91,4 +91,14 @@ class StompSocket extends TransportSupport implements WebSocket, StompTransport
public void sendToStomp(StompFrame command) throws IOException { public void sendToStomp(StompFrame command) throws IOException {
outbound.sendMessage(WebSocket.SENTINEL_FRAME, command.format()); outbound.sendMessage(WebSocket.SENTINEL_FRAME, command.format());
} }
@Override
public StompInactivityMonitor getInactivityMonitor() {
return null;
}
@Override
public StompWireFormat getWireFormat() {
return this.wireFormat;
}
} }