mirror of https://github.com/apache/activemq.git
git-svn-id: https://svn.apache.org/repos/asf/activemq/trunk@1157238 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
9026274532
commit
0885c60c4d
|
@ -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>
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
${project.version}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 ) {
|
||||||
|
|
|
@ -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>
|
||||||
* <transportConnector
|
* <transportConnector
|
||||||
* name="tcp1"
|
* name="tcp1"
|
||||||
* uri="tcp://127.0.0.1:61616?transport.soTimeout=10000&transport.soWriteTimeout=15000"
|
* uri="tcp://127.0.0.1:61616?transport.soTimeout=10000&transport.soWriteTimeout=15000"
|
||||||
* />
|
* />
|
||||||
* </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>
|
||||||
* <transportConnector
|
* <transportConnector
|
||||||
* name="tcp1"
|
* name="tcp1"
|
||||||
* uri="tcp://127.0.0.1:61616?transport.soTimeout=10000&transport.soWriteTimeout=15000"
|
* uri="tcp://127.0.0.1:61616?transport.soTimeout=10000&transport.soWriteTimeout=15000"
|
||||||
* />
|
* />
|
||||||
* </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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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/");
|
||||||
|
|
|
@ -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 + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue