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 "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
@ -98,7 +98,7 @@
|
|||
<groupId>org.osgi</groupId>
|
||||
<artifactId>org.osgi.core</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.activemq</groupId>
|
||||
|
@ -133,7 +133,7 @@
|
|||
<groupId>com.thoughtworks.xstream</groupId>
|
||||
<artifactId>xstream</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jettison</groupId>
|
||||
<artifactId>jettison</artifactId>
|
||||
|
@ -150,7 +150,7 @@
|
|||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
</dependency>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.derby</groupId>
|
||||
|
@ -213,7 +213,7 @@
|
|||
<artifactId>spring-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
|
@ -224,7 +224,7 @@
|
|||
<artifactId>activemq-jmdns_1.0</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jasypt</groupId>
|
||||
<artifactId>jasypt</artifactId>
|
||||
|
@ -372,8 +372,25 @@
|
|||
</plugin>
|
||||
</plugins>
|
||||
</reporting>
|
||||
|
||||
|
||||
<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>
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
|
@ -384,7 +401,7 @@
|
|||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
|
||||
<!-- Configure which tests are included/excuded -->
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
|
@ -405,7 +422,7 @@
|
|||
Note: if you want to see log messages on the console window remove
|
||||
"redirectTestOutputToFile" from the parent pom
|
||||
-->
|
||||
<!--
|
||||
<!--
|
||||
<property>
|
||||
<name>log4j.configuration</name>
|
||||
<value>file:target/test-classes/log4j.properties</value>
|
||||
|
@ -426,7 +443,7 @@
|
|||
|
||||
<!-- These are performance tests so take too long to run -->
|
||||
<exclude>**/perf/*</exclude>
|
||||
|
||||
|
||||
<!-- These are load tests so take too long to run -->
|
||||
<exclude>**/load/*</exclude>
|
||||
|
||||
|
@ -470,19 +487,19 @@
|
|||
|
||||
<!-- A test used for memory profiling only. -->
|
||||
<exclude>**/NetworkConnectionsCleanedupTest.*/**</exclude>
|
||||
|
||||
|
||||
<exclude>**/NetworkConnectionsCleanedupTest.*/**</exclude>
|
||||
|
||||
|
||||
<!-- used just to test potential memory leaks manually -->
|
||||
<exclude>**/JDBCTestMemory.*</exclude>
|
||||
|
||||
|
||||
<exclude>**/amq1490/*</exclude>
|
||||
<exclude>**/archive/*</exclude>
|
||||
<exclude>**/NetworkFailoverTest.*/**</exclude>
|
||||
|
||||
|
||||
<exclude>**/vm/VMTransportBrokerTest.*</exclude>
|
||||
<exclude>**/broker/MarshallingBrokerTest.*</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/ -->
|
||||
<exclude>**/BrokerNetworkWithStuckMessagesTest.*</exclude>
|
||||
|
||||
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
@ -519,28 +536,28 @@
|
|||
</filesets>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
|
||||
<executions>
|
||||
<execution>
|
||||
<execution>
|
||||
<id>package</id>
|
||||
<phase>package</phase>
|
||||
<configuration>
|
||||
<tasks>
|
||||
<echo>Deleting unwanted resources from the test-jar</echo>
|
||||
<delete dir="${project.build.directory}/test-classes" verbose="true">
|
||||
<include name="*.*" />
|
||||
</delete>
|
||||
<include name="*.*" />
|
||||
</delete>
|
||||
</tasks>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
|
||||
|
||||
<execution>
|
||||
<id>site</id>
|
||||
<phase>site</phase>
|
||||
|
@ -626,7 +643,7 @@
|
|||
<artifactId>cobertura-maven-plugin</artifactId>
|
||||
<version>2.0</version>
|
||||
<configuration>
|
||||
<check>
|
||||
<check>
|
||||
<branchRate>50</branchRate>
|
||||
<lineRate>50</lineRate>
|
||||
<haltOnFailure>true</haltOnFailure>
|
||||
|
@ -634,12 +651,12 @@
|
|||
<totalLineRate>50</totalLineRate>
|
||||
</check>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
|
||||
|
||||
<profile>
|
||||
<id>openwire-generate</id>
|
||||
<dependencies>
|
||||
|
@ -675,7 +692,7 @@
|
|||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
|
||||
</profiles>
|
||||
|
||||
</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;
|
||||
|
||||
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;
|
||||
|
@ -35,293 +26,59 @@ import org.slf4j.LoggerFactory;
|
|||
/**
|
||||
* Used to make sure that commands are arriving periodically from the peer of
|
||||
* the transport.
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class InactivityMonitor extends TransportFilter {
|
||||
public class InactivityMonitor extends AbstractInactivityMonitor {
|
||||
|
||||
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 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 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) {
|
||||
super(next);
|
||||
this.wireFormat = wireFormat;
|
||||
super(next, wireFormat);
|
||||
if (this.wireFormat == null) {
|
||||
this.ignoreAllWireFormatInfo = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void start() throws Exception {
|
||||
next.start();
|
||||
protected void processInboundWireFormatInfo(WireFormatInfo info) throws IOException {
|
||||
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();
|
||||
}
|
||||
|
||||
public void stop() throws Exception {
|
||||
stopMonitorThreads();
|
||||
next.stop();
|
||||
}
|
||||
|
||||
final void writeCheck() {
|
||||
if (inSend.get()) {
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("A send is in progress");
|
||||
}
|
||||
@Override
|
||||
protected synchronized void startMonitorThreads() throws IOException {
|
||||
if (isMonitorStarted()) {
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
long readCheckTime = getReadCheckTime();
|
||||
|
||||
if (readCheckTime > 0) {
|
||||
monitorStarted.set(true);
|
||||
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);
|
||||
}
|
||||
setWriteCheckTime(readCheckTime>3 ? readCheckTime/3 : readCheckTime);
|
||||
}
|
||||
|
||||
super.startMonitorThreads();
|
||||
}
|
||||
|
||||
private boolean configuredOk() throws IOException {
|
||||
@Override
|
||||
protected boolean configuredOk() throws IOException {
|
||||
boolean configured = false;
|
||||
if (ignoreAllWireFormatInfo) {
|
||||
configured = true;
|
||||
|
@ -330,54 +87,29 @@ public class InactivityMonitor extends TransportFilter {
|
|||
if (LOG.isDebugEnabled()) {
|
||||
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 {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
* synchronously, asynchronously and consumed.
|
||||
*
|
||||
*
|
||||
*/
|
||||
public interface Transport extends Service {
|
||||
|
||||
/**
|
||||
* A one way asynchronous send
|
||||
*
|
||||
*
|
||||
* @param command
|
||||
* @throws IOException
|
||||
*/
|
||||
|
@ -40,7 +38,7 @@ public interface Transport extends Service {
|
|||
* 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
|
||||
|
@ -50,7 +48,7 @@ public interface Transport extends Service {
|
|||
|
||||
/**
|
||||
* A synchronous request response
|
||||
*
|
||||
*
|
||||
* @param command
|
||||
* @return the response
|
||||
* @throws IOException
|
||||
|
@ -59,7 +57,7 @@ public interface Transport extends Service {
|
|||
|
||||
/**
|
||||
* A synchronous request response
|
||||
*
|
||||
*
|
||||
* @param command
|
||||
* @param 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;
|
||||
|
||||
// /**
|
||||
// * 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
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
TransportListener getTransportListener();
|
||||
|
||||
/**
|
||||
* Registers an inbound command listener
|
||||
*
|
||||
*
|
||||
* @param commandListener
|
||||
*/
|
||||
void setTransportListener(TransportListener commandListener);
|
||||
|
@ -131,26 +92,26 @@ public interface Transport extends Service {
|
|||
|
||||
/**
|
||||
* Indicates if the transport can handle faults
|
||||
*
|
||||
*
|
||||
* @return true if fault tolerant
|
||||
*/
|
||||
boolean isFaultTolerant();
|
||||
|
||||
|
||||
/**
|
||||
* @return true if the transport is disposed
|
||||
*/
|
||||
boolean isDisposed();
|
||||
|
||||
|
||||
/**
|
||||
* @return true if the transport is connected
|
||||
*/
|
||||
boolean isConnected();
|
||||
|
||||
|
||||
/**
|
||||
* @return true if reconnect is supported
|
||||
*/
|
||||
boolean isReconnectSupported();
|
||||
|
||||
|
||||
/**
|
||||
* @return true if updating uris is supported
|
||||
*/
|
||||
|
@ -161,10 +122,10 @@ public interface Transport extends Service {
|
|||
* @throws IOException on failure of if not supported
|
||||
*/
|
||||
void reconnect(URI uri) throws IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Provide a list of available alternative locations
|
||||
* @param rebalance
|
||||
* @param rebalance
|
||||
* @param uris
|
||||
* @throws IOException
|
||||
*/
|
||||
|
@ -172,10 +133,10 @@ public interface Transport extends Service {
|
|||
|
||||
/**
|
||||
* 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.
|
||||
* The value may wrap into the negative numbers.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @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 THREAD_NAME_FILTER = "threadName";
|
||||
|
||||
|
||||
public abstract TransportServer doBind(URI location) throws IOException;
|
||||
|
||||
public Transport doConnect(URI location, Executor ex) throws Exception {
|
||||
|
@ -57,7 +57,7 @@ public abstract class TransportFactory {
|
|||
|
||||
/**
|
||||
* Creates a normal transport.
|
||||
*
|
||||
*
|
||||
* @param location
|
||||
* @return the transport
|
||||
* @throws Exception
|
||||
|
@ -69,7 +69,7 @@ public abstract class TransportFactory {
|
|||
|
||||
/**
|
||||
* Creates a normal transport.
|
||||
*
|
||||
*
|
||||
* @param location
|
||||
* @param ex
|
||||
* @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
|
||||
* used by composite transports like reliable and HA.
|
||||
*
|
||||
*
|
||||
* @param location
|
||||
* @return the Transport
|
||||
* @throws Exception
|
||||
|
@ -96,7 +96,7 @@ public abstract class TransportFactory {
|
|||
/**
|
||||
* Creates a slimmed down transport that is more efficient so that it can be
|
||||
* used by composite transports like reliable and HA.
|
||||
*
|
||||
*
|
||||
* @param location
|
||||
* @param ex
|
||||
* @return the Transport
|
||||
|
@ -113,12 +113,12 @@ public abstract class TransportFactory {
|
|||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @deprecated
|
||||
*/
|
||||
public static TransportServer bind(String brokerId, URI location) throws IOException {
|
||||
return bind(location);
|
||||
}
|
||||
|
||||
|
||||
public static TransportServer bind(BrokerService brokerService, URI location) throws IOException {
|
||||
TransportFactory tf = findTransportFactory(location);
|
||||
if( brokerService!=null && tf instanceof BrokerServiceAware ) {
|
||||
|
@ -132,7 +132,7 @@ public abstract class TransportFactory {
|
|||
} finally {
|
||||
SslContext.setCurrentSslContext(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Transport doConnect(URI location) throws Exception {
|
||||
try {
|
||||
|
@ -164,7 +164,7 @@ public abstract class TransportFactory {
|
|||
throw IOExceptionSupport.create(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Allow registration of a transport factory without wiring via META-INF classes
|
||||
* @param scheme
|
||||
|
@ -176,7 +176,7 @@ public abstract class TransportFactory {
|
|||
|
||||
/**
|
||||
* Factory method to create a new transport
|
||||
*
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws UnknownHostException
|
||||
*/
|
||||
|
@ -235,13 +235,14 @@ public abstract class TransportFactory {
|
|||
/**
|
||||
* Fully configures and adds all need transport filters so that the
|
||||
* transport can be used by the JMS client.
|
||||
*
|
||||
*
|
||||
* @param transport
|
||||
* @param wf
|
||||
* @param options
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Transport configure(Transport transport, WireFormat wf, Map options) throws Exception {
|
||||
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
|
||||
* between this and the configure() method is that the broker does not issue
|
||||
* requests to the client so the ResponseCorrelator is not needed.
|
||||
*
|
||||
*
|
||||
* @param transport
|
||||
* @param format
|
||||
* @param options
|
||||
* @return
|
||||
* @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)) {
|
||||
transport = new ThreadNameFilter(transport);
|
||||
}
|
||||
|
@ -276,12 +278,13 @@ public abstract class TransportFactory {
|
|||
* Similar to configure(...) but this avoid adding in the MutexTransport and
|
||||
* ResponseCorrelator transport layers so that the resulting transport can
|
||||
* more efficiently be used as part of a composite transport.
|
||||
*
|
||||
*
|
||||
* @param transport
|
||||
* @param format
|
||||
* @param options
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Transport compositeConfigure(Transport transport, WireFormat format, Map options) {
|
||||
if (options.containsKey(WRITE_TIMEOUT_FILTER)) {
|
||||
transport = new WriteTimeoutFilter(transport);
|
||||
|
@ -294,6 +297,7 @@ public abstract class TransportFactory {
|
|||
return transport;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
protected String getOption(Map options, String key, String def) {
|
||||
String rc = (String) options.remove(key);
|
||||
if( rc == null ) {
|
||||
|
|
|
@ -22,10 +22,7 @@ import java.net.Socket;
|
|||
import java.util.Iterator;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -39,15 +36,15 @@ import org.slf4j.LoggerFactory;
|
|||
* <code>transport.soWriteTimeout=<value in millis></code>.<br/>
|
||||
* For example (15 second timeout on write operations to the socket):</br>
|
||||
* <pre><code>
|
||||
* <transportConnector
|
||||
* name="tcp1"
|
||||
* <transportConnector
|
||||
* name="tcp1"
|
||||
* uri="tcp://127.0.0.1:61616?transport.soTimeout=10000&transport.soWriteTimeout=15000"
|
||||
* />
|
||||
* </code></pre><br/>
|
||||
* For example (enable default timeout on the socket):</br>
|
||||
* <pre><code>
|
||||
* <transportConnector
|
||||
* name="tcp1"
|
||||
* <transportConnector
|
||||
* name="tcp1"
|
||||
* uri="tcp://127.0.0.1:61616?transport.soTimeout=10000&transport.soWriteTimeout=15000"
|
||||
* />
|
||||
* </code></pre>
|
||||
|
@ -59,12 +56,12 @@ public class WriteTimeoutFilter extends TransportFilter {
|
|||
private static final Logger LOG = LoggerFactory.getLogger(WriteTimeoutFilter.class);
|
||||
protected static ConcurrentLinkedQueue<WriteTimeoutFilter> writers = new ConcurrentLinkedQueue<WriteTimeoutFilter>();
|
||||
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 long writeTimeout = -1;
|
||||
|
||||
|
||||
public WriteTimeoutFilter(Transport next) {
|
||||
super(next);
|
||||
}
|
||||
|
@ -80,7 +77,7 @@ public class WriteTimeoutFilter extends TransportFilter {
|
|||
deRegisterWrite(this,false,null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public long getWriteTimeout() {
|
||||
return writeTimeout;
|
||||
}
|
||||
|
@ -88,7 +85,7 @@ public class WriteTimeoutFilter extends TransportFilter {
|
|||
public void setWriteTimeout(long writeTimeout) {
|
||||
this.writeTimeout = writeTimeout;
|
||||
}
|
||||
|
||||
|
||||
public static long getSleep() {
|
||||
return sleep;
|
||||
}
|
||||
|
@ -97,21 +94,21 @@ public class WriteTimeoutFilter extends TransportFilter {
|
|||
WriteTimeoutFilter.sleep = sleep;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected TimeStampStream getWriter() {
|
||||
return next.narrow(TimeStampStream.class);
|
||||
}
|
||||
|
||||
|
||||
protected Socket getSocket() {
|
||||
return next.narrow(Socket.class);
|
||||
}
|
||||
|
||||
|
||||
protected static void registerWrite(WriteTimeoutFilter filter) {
|
||||
writers.add(filter);
|
||||
}
|
||||
|
||||
|
||||
protected static boolean deRegisterWrite(WriteTimeoutFilter filter, boolean fail, IOException iox) {
|
||||
boolean result = writers.remove(filter);
|
||||
boolean result = writers.remove(filter);
|
||||
if (result) {
|
||||
if (fail) {
|
||||
String message = "Forced write timeout for:"+filter.getNext().getRemoteAddress();
|
||||
|
@ -129,17 +126,17 @@ public class WriteTimeoutFilter extends TransportFilter {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void start() throws Exception {
|
||||
super.start();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
super.stop();
|
||||
}
|
||||
|
||||
|
||||
protected static class TimeoutThread extends Thread {
|
||||
static AtomicInteger instance = new AtomicInteger(0);
|
||||
boolean run = true;
|
||||
|
@ -150,14 +147,14 @@ public class WriteTimeoutFilter extends TransportFilter {
|
|||
start();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void run() {
|
||||
while (run) {
|
||||
boolean error = false;
|
||||
boolean error = false;
|
||||
try {
|
||||
if (!interrupted()) {
|
||||
Iterator<WriteTimeoutFilter> filters = writers.iterator();
|
||||
while (run && filters.hasNext()) {
|
||||
if (!interrupted()) {
|
||||
Iterator<WriteTimeoutFilter> filters = writers.iterator();
|
||||
while (run && filters.hasNext()) {
|
||||
WriteTimeoutFilter filter = filters.next();
|
||||
if (filter.getWriteTimeout()<=0) continue; //no timeout set
|
||||
long writeStart = filter.getWriter().getWriteTimestamp();
|
||||
|
|
|
@ -22,12 +22,9 @@ import java.util.Map;
|
|||
|
||||
import javax.jms.Destination;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.MessageListener;
|
||||
import javax.jms.MessageProducer;
|
||||
|
||||
import org.apache.activemq.command.ActiveMQDestination;
|
||||
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
|
||||
|
|
|
@ -34,8 +34,6 @@ import org.apache.activemq.command.ActiveMQObjectMessage;
|
|||
import org.apache.activemq.command.DataStructure;
|
||||
import org.apache.activemq.util.JettisonMappedXmlDriver;
|
||||
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.io.HierarchicalStreamReader;
|
||||
|
@ -50,206 +48,208 @@ import com.thoughtworks.xstream.io.xml.XppReader;
|
|||
* @author <a href="mailto:dejan@nighttale.net">Dejan Bosanac</a>
|
||||
*/
|
||||
public class JmsFrameTranslator extends LegacyFrameTranslator implements
|
||||
BrokerContextAware {
|
||||
BrokerContextAware {
|
||||
|
||||
XStream xStream = null;
|
||||
BrokerContext brokerContext;
|
||||
XStream xStream = null;
|
||||
BrokerContext brokerContext;
|
||||
|
||||
public ActiveMQMessage convertFrame(ProtocolConverter converter,
|
||||
StompFrame command) throws JMSException, ProtocolException {
|
||||
Map headers = command.getHeaders();
|
||||
ActiveMQMessage msg;
|
||||
String transformation = (String) headers.get(Stomp.Headers.TRANSFORMATION);
|
||||
if (headers.containsKey(Stomp.Headers.CONTENT_LENGTH) || transformation.equals(Stomp.Transformations.JMS_BYTE.toString())) {
|
||||
msg = super.convertFrame(converter, command);
|
||||
} else {
|
||||
HierarchicalStreamReader in;
|
||||
public ActiveMQMessage convertFrame(ProtocolConverter converter,
|
||||
StompFrame command) throws JMSException, ProtocolException {
|
||||
Map<String, String> headers = command.getHeaders();
|
||||
ActiveMQMessage msg;
|
||||
String transformation = (String) headers.get(Stomp.Headers.TRANSFORMATION);
|
||||
if (headers.containsKey(Stomp.Headers.CONTENT_LENGTH) || transformation.equals(Stomp.Transformations.JMS_BYTE.toString())) {
|
||||
msg = super.convertFrame(converter, command);
|
||||
} else {
|
||||
HierarchicalStreamReader in;
|
||||
|
||||
try {
|
||||
String text = new String(command.getContent(), "UTF-8");
|
||||
switch (Stomp.Transformations.getValue(transformation)) {
|
||||
case JMS_OBJECT_XML:
|
||||
in = new XppReader(new StringReader(text));
|
||||
msg = createObjectMessage(in);
|
||||
break;
|
||||
case JMS_OBJECT_JSON:
|
||||
in = new JettisonMappedXmlDriver().createReader(new StringReader(text));
|
||||
msg = createObjectMessage(in);
|
||||
break;
|
||||
case JMS_MAP_XML:
|
||||
in = new XppReader(new StringReader(text));
|
||||
msg = createMapMessage(in);
|
||||
break;
|
||||
case JMS_MAP_JSON:
|
||||
in = new JettisonMappedXmlDriver().createReader(new StringReader(text));
|
||||
msg = createMapMessage(in);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unkown transformation: " + transformation);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
command.getHeaders().put(Stomp.Headers.TRANSFORMATION_ERROR, e.getMessage());
|
||||
msg = super.convertFrame(converter, command);
|
||||
}
|
||||
}
|
||||
FrameTranslator.Helper.copyStandardHeadersFromFrameToMessage(converter, command, msg, this);
|
||||
return msg;
|
||||
}
|
||||
try {
|
||||
String text = new String(command.getContent(), "UTF-8");
|
||||
switch (Stomp.Transformations.getValue(transformation)) {
|
||||
case JMS_OBJECT_XML:
|
||||
in = new XppReader(new StringReader(text));
|
||||
msg = createObjectMessage(in);
|
||||
break;
|
||||
case JMS_OBJECT_JSON:
|
||||
in = new JettisonMappedXmlDriver().createReader(new StringReader(text));
|
||||
msg = createObjectMessage(in);
|
||||
break;
|
||||
case JMS_MAP_XML:
|
||||
in = new XppReader(new StringReader(text));
|
||||
msg = createMapMessage(in);
|
||||
break;
|
||||
case JMS_MAP_JSON:
|
||||
in = new JettisonMappedXmlDriver().createReader(new StringReader(text));
|
||||
msg = createMapMessage(in);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unkown transformation: " + transformation);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
command.getHeaders().put(Stomp.Headers.TRANSFORMATION_ERROR, e.getMessage());
|
||||
msg = super.convertFrame(converter, command);
|
||||
}
|
||||
}
|
||||
FrameTranslator.Helper.copyStandardHeadersFromFrameToMessage(converter, command, msg, this);
|
||||
return msg;
|
||||
}
|
||||
|
||||
public StompFrame convertMessage(ProtocolConverter converter,
|
||||
ActiveMQMessage message) throws IOException, JMSException {
|
||||
if (message.getDataStructureType() == ActiveMQObjectMessage.DATA_STRUCTURE_TYPE) {
|
||||
StompFrame command = new StompFrame();
|
||||
command.setAction(Stomp.Responses.MESSAGE);
|
||||
Map<String, String> headers = new HashMap<String, String>(25);
|
||||
command.setHeaders(headers);
|
||||
public StompFrame convertMessage(ProtocolConverter converter,
|
||||
ActiveMQMessage message) throws IOException, JMSException {
|
||||
if (message.getDataStructureType() == ActiveMQObjectMessage.DATA_STRUCTURE_TYPE) {
|
||||
StompFrame command = new StompFrame();
|
||||
command.setAction(Stomp.Responses.MESSAGE);
|
||||
Map<String, String> headers = new HashMap<String, String>(25);
|
||||
command.setHeaders(headers);
|
||||
|
||||
FrameTranslator.Helper.copyStandardHeadersFromMessageToFrame(
|
||||
converter, message, command, this);
|
||||
FrameTranslator.Helper.copyStandardHeadersFromMessageToFrame(
|
||||
converter, message, command, this);
|
||||
|
||||
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())) {
|
||||
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();
|
||||
command.setContent(marshall(msg.getObject(),
|
||||
headers.get(Stomp.Headers.TRANSFORMATION))
|
||||
.getBytes("UTF-8"));
|
||||
return command;
|
||||
command.setContent(marshall(msg.getObject(),
|
||||
headers.get(Stomp.Headers.TRANSFORMATION))
|
||||
.getBytes("UTF-8"));
|
||||
return command;
|
||||
|
||||
} else if (message.getDataStructureType() == ActiveMQMapMessage.DATA_STRUCTURE_TYPE) {
|
||||
StompFrame command = new StompFrame();
|
||||
command.setAction(Stomp.Responses.MESSAGE);
|
||||
Map<String, String> headers = new HashMap<String, String>(25);
|
||||
command.setHeaders(headers);
|
||||
} else if (message.getDataStructureType() == ActiveMQMapMessage.DATA_STRUCTURE_TYPE) {
|
||||
StompFrame command = new StompFrame();
|
||||
command.setAction(Stomp.Responses.MESSAGE);
|
||||
Map<String, String> headers = new HashMap<String, String>(25);
|
||||
command.setHeaders(headers);
|
||||
|
||||
FrameTranslator.Helper.copyStandardHeadersFromMessageToFrame(
|
||||
converter, message, command, this);
|
||||
FrameTranslator.Helper.copyStandardHeadersFromMessageToFrame(
|
||||
converter, message, command, this);
|
||||
|
||||
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())) {
|
||||
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();
|
||||
command.setContent(marshall((Serializable)msg.getContentMap(),
|
||||
headers.get(Stomp.Headers.TRANSFORMATION))
|
||||
.getBytes("UTF-8"));
|
||||
return command;
|
||||
ActiveMQMapMessage msg = (ActiveMQMapMessage) message.copy();
|
||||
command.setContent(marshall((Serializable)msg.getContentMap(),
|
||||
headers.get(Stomp.Headers.TRANSFORMATION))
|
||||
.getBytes("UTF-8"));
|
||||
return command;
|
||||
} else if (message.getDataStructureType() == ActiveMQMessage.DATA_STRUCTURE_TYPE &&
|
||||
AdvisorySupport.ADIVSORY_MESSAGE_TYPE.equals(message.getType())) {
|
||||
|
||||
StompFrame command = new StompFrame();
|
||||
command.setAction(Stomp.Responses.MESSAGE);
|
||||
Map<String, String> headers = new HashMap<String, String>(25);
|
||||
command.setHeaders(headers);
|
||||
StompFrame command = new StompFrame();
|
||||
command.setAction(Stomp.Responses.MESSAGE);
|
||||
Map<String, String> headers = new HashMap<String, String>(25);
|
||||
command.setHeaders(headers);
|
||||
|
||||
FrameTranslator.Helper.copyStandardHeadersFromMessageToFrame(
|
||||
converter, message, command, this);
|
||||
converter, message, command, this);
|
||||
|
||||
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())) {
|
||||
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(),
|
||||
headers.get(Stomp.Headers.TRANSFORMATION));
|
||||
headers.get(Stomp.Headers.TRANSFORMATION));
|
||||
command.setContent(body.getBytes("UTF-8"));
|
||||
return command;
|
||||
} else {
|
||||
return super.convertMessage(converter, message);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return super.convertMessage(converter, message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marshalls the Object to a string using XML or JSON encoding
|
||||
*/
|
||||
protected String marshall(Serializable object, String transformation)
|
||||
throws JMSException {
|
||||
StringWriter buffer = new StringWriter();
|
||||
HierarchicalStreamWriter out;
|
||||
if (transformation.toLowerCase().endsWith("json")) {
|
||||
out = new JettisonMappedXmlDriver(new Configuration(), false).createWriter(buffer);
|
||||
} else {
|
||||
out = new PrettyPrintWriter(buffer);
|
||||
}
|
||||
getXStream().marshal(object, out);
|
||||
return buffer.toString();
|
||||
}
|
||||
/**
|
||||
* Marshalls the Object to a string using XML or JSON encoding
|
||||
*/
|
||||
protected String marshall(Serializable object, String transformation)
|
||||
throws JMSException {
|
||||
StringWriter buffer = new StringWriter();
|
||||
HierarchicalStreamWriter out;
|
||||
if (transformation.toLowerCase().endsWith("json")) {
|
||||
out = new JettisonMappedXmlDriver(new Configuration(), false).createWriter(buffer);
|
||||
} else {
|
||||
out = new PrettyPrintWriter(buffer);
|
||||
}
|
||||
getXStream().marshal(object, out);
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
protected ActiveMQObjectMessage createObjectMessage(HierarchicalStreamReader in) throws JMSException {
|
||||
ActiveMQObjectMessage objMsg = new ActiveMQObjectMessage();
|
||||
Object obj = getXStream().unmarshal(in);
|
||||
objMsg.setObject((Serializable) obj);
|
||||
return objMsg;
|
||||
}
|
||||
protected ActiveMQObjectMessage createObjectMessage(HierarchicalStreamReader in) throws JMSException {
|
||||
ActiveMQObjectMessage objMsg = new ActiveMQObjectMessage();
|
||||
Object obj = getXStream().unmarshal(in);
|
||||
objMsg.setObject((Serializable) obj);
|
||||
return objMsg;
|
||||
}
|
||||
|
||||
protected ActiveMQMapMessage createMapMessage(HierarchicalStreamReader in) throws JMSException {
|
||||
ActiveMQMapMessage mapMsg = new ActiveMQMapMessage();
|
||||
Map<String, Object> map = (Map<String, Object>)getXStream().unmarshal(in);
|
||||
for (String key : map.keySet()) {
|
||||
mapMsg.setObject(key, map.get(key));
|
||||
}
|
||||
return mapMsg;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
protected ActiveMQMapMessage createMapMessage(HierarchicalStreamReader in) throws JMSException {
|
||||
ActiveMQMapMessage mapMsg = new ActiveMQMapMessage();
|
||||
Map<String, Object> map = (Map<String, Object>)getXStream().unmarshal(in);
|
||||
for (String key : map.keySet()) {
|
||||
mapMsg.setObject(key, map.get(key));
|
||||
}
|
||||
return mapMsg;
|
||||
}
|
||||
|
||||
protected String marshallAdvisory(final DataStructure ds, String transformation) {
|
||||
|
||||
StringWriter buffer = new StringWriter();
|
||||
HierarchicalStreamWriter out;
|
||||
if (transformation.toLowerCase().endsWith("json")) {
|
||||
out = new JettisonMappedXmlDriver().createWriter(buffer);
|
||||
} else {
|
||||
out = new PrettyPrintWriter(buffer);
|
||||
}
|
||||
StringWriter buffer = new StringWriter();
|
||||
HierarchicalStreamWriter out;
|
||||
if (transformation.toLowerCase().endsWith("json")) {
|
||||
out = new JettisonMappedXmlDriver().createWriter(buffer);
|
||||
} else {
|
||||
out = new PrettyPrintWriter(buffer);
|
||||
}
|
||||
|
||||
XStream xstream = getXStream();
|
||||
XStream xstream = getXStream();
|
||||
xstream.setMode(XStream.NO_REFERENCES);
|
||||
xstream.aliasPackage("", "org.apache.activemq.command");
|
||||
xstream.marshal(ds, out);
|
||||
return buffer.toString();
|
||||
xstream.marshal(ds, out);
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
// Properties
|
||||
// -------------------------------------------------------------------------
|
||||
public XStream getXStream() {
|
||||
if (xStream == null) {
|
||||
xStream = createXStream();
|
||||
}
|
||||
return xStream;
|
||||
}
|
||||
// Properties
|
||||
// -------------------------------------------------------------------------
|
||||
public XStream getXStream() {
|
||||
if (xStream == null) {
|
||||
xStream = createXStream();
|
||||
}
|
||||
return xStream;
|
||||
}
|
||||
|
||||
public void setXStream(XStream xStream) {
|
||||
this.xStream = xStream;
|
||||
}
|
||||
public void setXStream(XStream xStream) {
|
||||
this.xStream = xStream;
|
||||
}
|
||||
|
||||
// Implementation methods
|
||||
// -------------------------------------------------------------------------
|
||||
protected XStream createXStream() {
|
||||
XStream xstream = null;
|
||||
if (brokerContext != null) {
|
||||
Map<String, XStream> beans = brokerContext.getBeansOfType(XStream.class);
|
||||
for (XStream bean : beans.values()) {
|
||||
if (bean != null) {
|
||||
xstream = bean;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Implementation methods
|
||||
// -------------------------------------------------------------------------
|
||||
@SuppressWarnings("unchecked")
|
||||
protected XStream createXStream() {
|
||||
XStream xstream = null;
|
||||
if (brokerContext != null) {
|
||||
Map<String, XStream> beans = brokerContext.getBeansOfType(XStream.class);
|
||||
for (XStream bean : beans.values()) {
|
||||
if (bean != null) {
|
||||
xstream = bean;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (xstream == null) {
|
||||
xstream = new XStream();
|
||||
}
|
||||
return xstream;
|
||||
if (xstream == null) {
|
||||
xstream = new XStream();
|
||||
}
|
||||
return xstream;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void setBrokerContext(BrokerContext brokerContext) {
|
||||
this.brokerContext = brokerContext;
|
||||
}
|
||||
public void setBrokerContext(BrokerContext brokerContext) {
|
||||
this.brokerContext = brokerContext;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
package org.apache.activemq.transport.stomp;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
@ -23,10 +24,17 @@ import java.util.Map;
|
|||
import javax.jms.Destination;
|
||||
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.io.json.JsonHierarchicalStreamDriver;
|
||||
import org.apache.activemq.advisory.AdvisorySupport;
|
||||
import org.apache.activemq.command.*;
|
||||
|
||||
/**
|
||||
* Implements ActiveMQ 4.0 translations
|
||||
|
@ -35,7 +43,7 @@ public class LegacyFrameTranslator implements FrameTranslator {
|
|||
|
||||
|
||||
public ActiveMQMessage convertFrame(ProtocolConverter converter, StompFrame command) throws JMSException, ProtocolException {
|
||||
final Map headers = command.getHeaders();
|
||||
final Map<?, ?> headers = command.getHeaders();
|
||||
final ActiveMQMessage msg;
|
||||
/*
|
||||
* 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")){
|
||||
ActiveMQTextMessage text = new ActiveMQTextMessage();
|
||||
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) {
|
||||
throw new ProtocolException("Text could not bet set: " + e, false, e);
|
||||
}
|
||||
|
@ -66,7 +79,12 @@ public class LegacyFrameTranslator implements FrameTranslator {
|
|||
} else {
|
||||
ActiveMQTextMessage text = new ActiveMQTextMessage();
|
||||
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) {
|
||||
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) {
|
||||
|
||||
ActiveMQTextMessage msg = (ActiveMQTextMessage)message.copy();
|
||||
command.setContent(msg.getText().getBytes("UTF-8"));
|
||||
if (!message.isCompressed() && message.getContent() != null) {
|
||||
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) {
|
||||
|
||||
|
@ -96,13 +123,13 @@ public class LegacyFrameTranslator implements FrameTranslator {
|
|||
byte[] data = new byte[(int)msg.getBodyLength()];
|
||||
msg.readBytes(data);
|
||||
|
||||
headers.put(Stomp.Headers.CONTENT_LENGTH, "" + data.length);
|
||||
headers.put(Stomp.Headers.CONTENT_LENGTH, Integer.toString(data.length));
|
||||
command.setContent(data);
|
||||
} else if (message.getDataStructureType() == ActiveMQMessage.DATA_STRUCTURE_TYPE &&
|
||||
AdvisorySupport.ADIVSORY_MESSAGE_TYPE.equals(message.getType())) {
|
||||
|
||||
FrameTranslator.Helper.copyStandardHeadersFromMessageToFrame(
|
||||
converter, message, command, this);
|
||||
converter, message, command, this);
|
||||
|
||||
String body = marshallAdvisory(message.getDataStructure());
|
||||
command.setContent(body.getBytes("UTF-8"));
|
||||
|
@ -119,10 +146,10 @@ public class LegacyFrameTranslator implements FrameTranslator {
|
|||
|
||||
String rc = converter.getCreatedTempDestinationName(activeMQDestination);
|
||||
if( rc!=null ) {
|
||||
return rc;
|
||||
return rc;
|
||||
}
|
||||
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
if (activeMQDestination.isQueue()) {
|
||||
if (activeMQDestination.isTemporary()) {
|
||||
buffer.append("/remote-temp-queue/");
|
||||
|
|
|
@ -16,10 +16,16 @@
|
|||
*/
|
||||
package org.apache.activemq.transport.stomp;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
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.ActiveMQTempTopic;
|
||||
import org.apache.activemq.command.Command;
|
||||
import org.apache.activemq.command.CommandTypes;
|
||||
import org.apache.activemq.command.ConnectionError;
|
||||
import org.apache.activemq.command.ConnectionId;
|
||||
import org.apache.activemq.command.ConnectionInfo;
|
||||
|
@ -62,7 +69,6 @@ import org.apache.activemq.util.IntrospectionSupport;
|
|||
import org.apache.activemq.util.LongSequenceGenerator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
|
||||
/**
|
||||
* @author <a href="http://hiramchirino.com">chirino</a>
|
||||
|
@ -70,9 +76,29 @@ import org.springframework.context.ApplicationContextAware;
|
|||
public class ProtocolConverter {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ProtocolConverter.class);
|
||||
|
||||
|
||||
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 SessionId sessionId = new SessionId(connectionId, -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<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, String> tempDestinationAmqToStompMap = new ConcurrentHashMap<String, String>();
|
||||
private final Map<String, LocalTransactionId> transactions = new ConcurrentHashMap<String, LocalTransactionId>();
|
||||
|
@ -92,13 +119,15 @@ public class ProtocolConverter {
|
|||
private final Object commnadIdMutex = new Object();
|
||||
private int lastCommandId;
|
||||
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 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.frameTranslator = translator;
|
||||
this.brokerContext = brokerContext;
|
||||
}
|
||||
|
||||
|
@ -178,6 +207,8 @@ public class ProtocolConverter {
|
|||
onStompSend(command);
|
||||
} else if (action.startsWith(Stomp.Commands.ACK)) {
|
||||
onStompAck(command);
|
||||
} else if (action.startsWith(Stomp.Commands.NACK)) {
|
||||
onStompNack(command);
|
||||
} else if (action.startsWith(Stomp.Commands.BEGIN)) {
|
||||
onStompBegin(command);
|
||||
} else if (action.startsWith(Stomp.Commands.COMMIT)) {
|
||||
|
@ -188,7 +219,8 @@ public class ProtocolConverter {
|
|||
onStompSubscribe(command);
|
||||
} else if (action.startsWith(Stomp.Commands.UNSUBSCRIBE)) {
|
||||
onStompUnsubscribe(command);
|
||||
} else if (action.startsWith(Stomp.Commands.CONNECT)) {
|
||||
} else if (action.startsWith(Stomp.Commands.CONNECT) ||
|
||||
action.startsWith(Stomp.Commands.STOMP)) {
|
||||
onStompConnect(command);
|
||||
} else if (action.startsWith(Stomp.Commands.DISCONNECT)) {
|
||||
onStompDisconnect(command);
|
||||
|
@ -199,7 +231,7 @@ public class ProtocolConverter {
|
|||
} catch (ProtocolException e) {
|
||||
handleException(e, command);
|
||||
// Some protocol errors can cause the connection to get closed.
|
||||
if( e.isFatal() ) {
|
||||
if (e.isFatal()) {
|
||||
getStompTransport().onException(e);
|
||||
}
|
||||
}
|
||||
|
@ -219,6 +251,7 @@ public class ProtocolConverter {
|
|||
|
||||
HashMap<String, String> headers = new HashMap<String, String>();
|
||||
headers.put(Stomp.Headers.Error.MESSAGE, exception.getMessage());
|
||||
headers.put(Stomp.Headers.CONTENT_TYPE, "text/plain");
|
||||
|
||||
if (command != null) {
|
||||
final String receiptId = command.getHeaders().get(Stomp.Headers.RECEIPT_REQUESTED);
|
||||
|
@ -235,6 +268,11 @@ public class ProtocolConverter {
|
|||
checkConnected();
|
||||
|
||||
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);
|
||||
headers.remove("transaction");
|
||||
|
||||
|
@ -255,24 +293,64 @@ public class ProtocolConverter {
|
|||
|
||||
message.onSend();
|
||||
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 {
|
||||
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();
|
||||
String messageId = headers.get(Stomp.Headers.Ack.MESSAGE_ID);
|
||||
if (messageId == null) {
|
||||
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;
|
||||
String stompTx = headers.get(Stomp.Headers.TRANSACTION);
|
||||
if (stompTx != null) {
|
||||
|
@ -283,21 +361,37 @@ public class ProtocolConverter {
|
|||
}
|
||||
|
||||
boolean acked = false;
|
||||
for (Iterator<StompSubscription> iter = subscriptionsByConsumerId.values().iterator(); iter.hasNext();) {
|
||||
StompSubscription sub = iter.next();
|
||||
MessageAck ack = sub.onStompMessageAck(messageId, activemqTx);
|
||||
if (ack != null) {
|
||||
ack.setTransactionId(activemqTx);
|
||||
sendToActiveMQ(ack, createResponseHandler(command));
|
||||
acked = true;
|
||||
break;
|
||||
|
||||
if (subscriptionId != null) {
|
||||
|
||||
StompSubscription sub = this.subscriptions.get(subscriptionId);
|
||||
if (sub != null) {
|
||||
MessageAck ack = sub.onStompMessageAck(messageId, activemqTx);
|
||||
if (ack != null) {
|
||||
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) {
|
||||
throw new ProtocolException("Unexpected ACK received for message-id [" + messageId + "]");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void onStompBegin(StompFrame command) throws ProtocolException {
|
||||
|
@ -324,7 +418,6 @@ public class ProtocolConverter {
|
|||
tx.setType(TransactionInfo.BEGIN);
|
||||
|
||||
sendToActiveMQ(tx, createResponseHandler(command));
|
||||
|
||||
}
|
||||
|
||||
protected void onStompCommit(StompFrame command) throws ProtocolException {
|
||||
|
@ -342,8 +435,7 @@ public class ProtocolConverter {
|
|||
throw new ProtocolException("Invalid transaction id: " + stompTx);
|
||||
}
|
||||
|
||||
for (Iterator<StompSubscription> iter = subscriptionsByConsumerId.values().iterator(); iter.hasNext();) {
|
||||
StompSubscription sub = iter.next();
|
||||
for (StompSubscription sub : subscriptionsByConsumerId.values()) {
|
||||
sub.onStompCommit(activemqTx);
|
||||
}
|
||||
|
||||
|
@ -353,7 +445,6 @@ public class ProtocolConverter {
|
|||
tx.setType(TransactionInfo.COMMIT_ONE_PHASE);
|
||||
|
||||
sendToActiveMQ(tx, createResponseHandler(command));
|
||||
|
||||
}
|
||||
|
||||
protected void onStompAbort(StompFrame command) throws ProtocolException {
|
||||
|
@ -369,8 +460,7 @@ public class ProtocolConverter {
|
|||
if (activemqTx == null) {
|
||||
throw new ProtocolException("Invalid transaction id: " + stompTx);
|
||||
}
|
||||
for (Iterator<StompSubscription> iter = subscriptionsByConsumerId.values().iterator(); iter.hasNext();) {
|
||||
StompSubscription sub = iter.next();
|
||||
for (StompSubscription sub : subscriptionsByConsumerId.values()) {
|
||||
try {
|
||||
sub.onStompAbort(activemqTx);
|
||||
} catch (Exception e) {
|
||||
|
@ -384,7 +474,6 @@ public class ProtocolConverter {
|
|||
tx.setType(TransactionInfo.ROLLBACK);
|
||||
|
||||
sendToActiveMQ(tx, createResponseHandler(command));
|
||||
|
||||
}
|
||||
|
||||
protected void onStompSubscribe(StompFrame command) throws ProtocolException {
|
||||
|
@ -395,6 +484,10 @@ public class ProtocolConverter {
|
|||
String subscriptionId = headers.get(Stomp.Headers.Subscribe.ID);
|
||||
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);
|
||||
|
||||
if (actualDest == null) {
|
||||
|
@ -406,6 +499,16 @@ public class ProtocolConverter {
|
|||
consumerInfo.setPrefetchSize(1000);
|
||||
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);
|
||||
consumerInfo.setSelector(selector);
|
||||
|
||||
|
@ -413,7 +516,12 @@ public class ProtocolConverter {
|
|||
|
||||
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);
|
||||
|
||||
String ackMode = headers.get(Stomp.Headers.Subscribe.ACK_MODE);
|
||||
|
@ -426,8 +534,11 @@ public class ProtocolConverter {
|
|||
}
|
||||
|
||||
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));
|
||||
|
||||
}
|
||||
|
||||
protected void onStompUnsubscribe(StompFrame command) throws ProtocolException {
|
||||
|
@ -441,6 +552,9 @@ public class ProtocolConverter {
|
|||
}
|
||||
|
||||
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) {
|
||||
throw new ProtocolException("Must specify the subscriptionId or the destination you are unsubscribing from");
|
||||
|
@ -457,18 +571,26 @@ public class ProtocolConverter {
|
|||
return;
|
||||
}
|
||||
|
||||
// TODO: Unsubscribing using a destination is a bit wierd if multiple
|
||||
// subscriptions
|
||||
// are created with the same destination. Perhaps this should be
|
||||
// removed.
|
||||
//
|
||||
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()))) {
|
||||
if (subscriptionId != null) {
|
||||
|
||||
StompSubscription sub = this.subscriptions.remove(subscriptionId);
|
||||
if (sub != null) {
|
||||
sendToActiveMQ(sub.getConsumerInfo().createRemoveCommand(), createResponseHandler(command));
|
||||
iter.remove();
|
||||
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.");
|
||||
|
@ -488,10 +610,28 @@ public class ProtocolConverter {
|
|||
String login = headers.get(Stomp.Headers.Connect.LOGIN);
|
||||
String passcode = headers.get(Stomp.Headers.Connect.PASSCODE);
|
||||
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.");
|
||||
|
||||
connectionInfo.setConnectionId(connectionId);
|
||||
if (clientId != null) {
|
||||
connectionInfo.setClientId(clientId);
|
||||
|
@ -544,10 +684,22 @@ public class ProtocolConverter {
|
|||
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();
|
||||
sc.setAction(Stomp.Responses.CONNECTED);
|
||||
sc.setHeaders(responseHeaders);
|
||||
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 {
|
||||
if (command.isResponse()) {
|
||||
|
||||
Response response = (Response)command;
|
||||
ResponseHandler rh = resposeHandlers.remove(Integer.valueOf(response.getCorrelationId()));
|
||||
if (rh != null) {
|
||||
|
@ -589,12 +740,13 @@ public class ProtocolConverter {
|
|||
}
|
||||
}
|
||||
} else if (command.isMessageDispatch()) {
|
||||
|
||||
MessageDispatch md = (MessageDispatch)command;
|
||||
StompSubscription sub = subscriptionsByConsumerId.get(md.getConsumerId());
|
||||
if (sub != null) {
|
||||
sub.onMessageDispatch(md);
|
||||
}
|
||||
} else if (command.getDataStructureType() == CommandTypes.KEEP_ALIVE_INFO) {
|
||||
stompTransport.sendToStomp(ping);
|
||||
} else if (command.getDataStructureType() == ConnectionError.DATA_STRUCTURE_TYPE) {
|
||||
// Pass down any unexpected async errors. Should this close the connection?
|
||||
Throwable exception = ((ConnectionError)command).getException();
|
||||
|
@ -643,4 +795,49 @@ public class ProtocolConverter {
|
|||
public String getCreatedTempDestinationName(ActiveMQDestination destination) {
|
||||
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 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 {
|
||||
String STOMP = "STOMP";
|
||||
String CONNECT = "CONNECT";
|
||||
String SEND = "SEND";
|
||||
String DISCONNECT = "DISCONNECT";
|
||||
|
@ -34,6 +56,8 @@ public interface Stomp {
|
|||
String COMMIT = "COMMIT";
|
||||
String ABORT = "ABORT";
|
||||
String ACK = "ACK";
|
||||
String NACK = "NACK";
|
||||
String KEEPALIVE = "KEEPALIVE";
|
||||
}
|
||||
|
||||
public interface Responses {
|
||||
|
@ -48,8 +72,10 @@ public interface Stomp {
|
|||
String RECEIPT_REQUESTED = "receipt";
|
||||
String TRANSACTION = "transaction";
|
||||
String CONTENT_LENGTH = "content-length";
|
||||
String CONTENT_TYPE = "content-type";
|
||||
String TRANSFORMATION = "transformation";
|
||||
String TRANSFORMATION_ERROR = "transformation-error";
|
||||
|
||||
/**
|
||||
* This header is used to instruct ActiveMQ to construct the message
|
||||
* based with a specific type.
|
||||
|
@ -81,6 +107,7 @@ public interface Stomp {
|
|||
String TIMESTAMP = "timestamp";
|
||||
String TYPE = "type";
|
||||
String SUBSCRIPTION = "subscription";
|
||||
String BROWSER = "browser";
|
||||
String USERID = "JMSXUserID";
|
||||
String ORIGINAL_DESTINATION = "original-destination";
|
||||
}
|
||||
|
@ -90,6 +117,7 @@ public interface Stomp {
|
|||
String ACK_MODE = "ack";
|
||||
String ID = "id";
|
||||
String SELECTOR = "selector";
|
||||
String BROWSER = "browser";
|
||||
|
||||
public interface AckModeValues {
|
||||
String AUTO = "auto";
|
||||
|
@ -108,6 +136,9 @@ public interface Stomp {
|
|||
String PASSCODE = "passcode";
|
||||
String CLIENT_ID = "client-id";
|
||||
String REQUEST_ID = "request-id";
|
||||
String ACCEPT_VERSION = "accept-version";
|
||||
String HOST = "host";
|
||||
String HEART_BEAT = "heart-beat";
|
||||
}
|
||||
|
||||
public interface Error {
|
||||
|
@ -117,30 +148,34 @@ public interface Stomp {
|
|||
public interface Connected {
|
||||
String SESSION = "session";
|
||||
String RESPONSE_ID = "response-id";
|
||||
String SERVER = "server";
|
||||
String VERSION = "version";
|
||||
String HEART_BEAT = "heart-beat";
|
||||
}
|
||||
|
||||
public interface Ack {
|
||||
String MESSAGE_ID = "message-id";
|
||||
String SUBSCRIPTION = "subscription";
|
||||
}
|
||||
}
|
||||
|
||||
public enum Transformations {
|
||||
JMS_BYTE,
|
||||
JMS_XML,
|
||||
JMS_JSON,
|
||||
JMS_OBJECT_XML,
|
||||
JMS_OBJECT_JSON,
|
||||
JMS_MAP_XML,
|
||||
JMS_MAP_JSON,
|
||||
JMS_ADVISORY_XML,
|
||||
JMS_ADVISORY_JSON;
|
||||
public enum Transformations {
|
||||
JMS_BYTE,
|
||||
JMS_XML,
|
||||
JMS_JSON,
|
||||
JMS_OBJECT_XML,
|
||||
JMS_OBJECT_JSON,
|
||||
JMS_MAP_XML,
|
||||
JMS_MAP_JSON,
|
||||
JMS_ADVISORY_XML,
|
||||
JMS_ADVISORY_JSON;
|
||||
|
||||
public String toString() {
|
||||
return name().replaceAll("_", "-").toLowerCase();
|
||||
}
|
||||
public String toString() {
|
||||
return name().replaceAll("_", "-").toLowerCase();
|
||||
}
|
||||
|
||||
public static Transformations getValue(String value) {
|
||||
return valueOf(value.replaceAll("-", "_").toUpperCase());
|
||||
}
|
||||
}
|
||||
public static Transformations getValue(String value) {
|
||||
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.util.ByteArrayOutputStream;
|
||||
import org.apache.activemq.util.DataByteArrayInputStream;
|
||||
import org.apache.activemq.wireformat.WireFormat;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.HashMap;
|
||||
|
@ -41,55 +40,55 @@ public class StompCodec {
|
|||
}
|
||||
|
||||
public void parse(ByteArrayInputStream input, int readSize) throws Exception {
|
||||
int i = 0;
|
||||
int b;
|
||||
while(i++ < readSize) {
|
||||
b = input.read();
|
||||
// skip repeating nulls
|
||||
if (!processedHeaders && previousByte == 0 && b == 0) {
|
||||
continue;
|
||||
}
|
||||
int i = 0;
|
||||
int b;
|
||||
while(i++ < readSize) {
|
||||
b = input.read();
|
||||
// skip repeating nulls
|
||||
if (!processedHeaders && previousByte == 0 && b == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!processedHeaders) {
|
||||
currentCommand.write(b);
|
||||
// end of headers section, parse action and header
|
||||
if (previousByte == '\n' && b == '\n') {
|
||||
if (transport.getWireFormat() instanceof StompWireFormat) {
|
||||
DataByteArrayInputStream data = new DataByteArrayInputStream(currentCommand.toByteArray());
|
||||
action = ((StompWireFormat)transport.getWireFormat()).parseAction(data);
|
||||
headers = ((StompWireFormat)transport.getWireFormat()).parseHeaders(data);
|
||||
String contentLengthHeader = headers.get(Stomp.Headers.CONTENT_LENGTH);
|
||||
if (contentLengthHeader != null) {
|
||||
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);
|
||||
}
|
||||
if (!processedHeaders) {
|
||||
currentCommand.write(b);
|
||||
// end of headers section, parse action and header
|
||||
if (previousByte == '\n' && b == '\n') {
|
||||
if (transport.getWireFormat() instanceof StompWireFormat) {
|
||||
DataByteArrayInputStream data = new DataByteArrayInputStream(currentCommand.toByteArray());
|
||||
action = ((StompWireFormat)transport.getWireFormat()).parseAction(data);
|
||||
headers = ((StompWireFormat)transport.getWireFormat()).parseHeaders(data);
|
||||
String contentLengthHeader = headers.get(Stomp.Headers.CONTENT_LENGTH);
|
||||
if (contentLengthHeader != null) {
|
||||
contentLength = ((StompWireFormat)transport.getWireFormat()).parseContentLength(contentLengthHeader);
|
||||
} else {
|
||||
// read desired content length
|
||||
if (readLength++ == contentLength) {
|
||||
processCommand();
|
||||
readLength = 0;
|
||||
} else {
|
||||
currentCommand.write(b);
|
||||
}
|
||||
contentLength = -1;
|
||||
}
|
||||
}
|
||||
|
||||
previousByte = b;
|
||||
processedHeaders = true;
|
||||
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 {
|
||||
|
|
|
@ -38,7 +38,7 @@ public class StompConnection {
|
|||
}
|
||||
|
||||
public void open(Socket socket) {
|
||||
stompSocket = socket;
|
||||
stompSocket = socket;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
|
@ -70,8 +70,8 @@ public class StompConnection {
|
|||
}
|
||||
|
||||
public StompFrame receive(long timeOut) throws Exception {
|
||||
stompSocket.setSoTimeout((int)timeOut);
|
||||
InputStream is = stompSocket.getInputStream();
|
||||
stompSocket.setSoTimeout((int)timeOut);
|
||||
InputStream is = stompSocket.getInputStream();
|
||||
StompWireFormat wf = new StompWireFormat();
|
||||
DataInputStream dis = new DataInputStream(is);
|
||||
return (StompFrame)wf.unmarshal(dis);
|
||||
|
@ -104,143 +104,143 @@ public class StompConnection {
|
|||
}
|
||||
}
|
||||
|
||||
private String stringFromBuffer(ByteArrayOutputStream inputBuffer) throws Exception {
|
||||
byte[] ba = inputBuffer.toByteArray();
|
||||
private String stringFromBuffer(ByteArrayOutputStream inputBuffer) throws Exception {
|
||||
byte[] ba = inputBuffer.toByteArray();
|
||||
inputBuffer.reset();
|
||||
return new String(ba, "UTF-8");
|
||||
}
|
||||
|
||||
public Socket getStompSocket() {
|
||||
return stompSocket;
|
||||
}
|
||||
return stompSocket;
|
||||
}
|
||||
|
||||
public void setStompSocket(Socket stompSocket) {
|
||||
this.stompSocket = stompSocket;
|
||||
}
|
||||
public void setStompSocket(Socket stompSocket) {
|
||||
this.stompSocket = stompSocket;
|
||||
}
|
||||
|
||||
public void connect(String username, String password) throws Exception {
|
||||
connect(username, password, null);
|
||||
}
|
||||
|
||||
public void connect(String username, String password, String client) throws Exception {
|
||||
HashMap<String, String> headers = new HashMap();
|
||||
headers.put("login", username);
|
||||
headers.put("passcode", password);
|
||||
if (client != null) {
|
||||
headers.put("client-id", client);
|
||||
}
|
||||
StompFrame frame = new StompFrame("CONNECT", headers);
|
||||
HashMap<String, String> headers = new HashMap<String, String>();
|
||||
headers.put("login", username);
|
||||
headers.put("passcode", password);
|
||||
if (client != null) {
|
||||
headers.put("client-id", client);
|
||||
}
|
||||
StompFrame frame = new StompFrame("CONNECT", headers);
|
||||
sendFrame(frame.format());
|
||||
|
||||
StompFrame connect = receive();
|
||||
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 {
|
||||
StompFrame frame = new StompFrame("DISCONNECT");
|
||||
StompFrame frame = new StompFrame("DISCONNECT");
|
||||
sendFrame(frame.format());
|
||||
}
|
||||
|
||||
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 {
|
||||
if (headers == null) {
|
||||
headers = new HashMap<String, String>();
|
||||
}
|
||||
headers.put("destination", destination);
|
||||
if (transaction != null) {
|
||||
headers.put("transaction", transaction);
|
||||
}
|
||||
StompFrame frame = new StompFrame("SEND", headers, message.getBytes());
|
||||
if (headers == null) {
|
||||
headers = new HashMap<String, String>();
|
||||
}
|
||||
headers.put("destination", destination);
|
||||
if (transaction != null) {
|
||||
headers.put("transaction", transaction);
|
||||
}
|
||||
StompFrame frame = new StompFrame("SEND", headers, message.getBytes());
|
||||
sendFrame(frame.format());
|
||||
}
|
||||
|
||||
public void subscribe(String destination) throws Exception {
|
||||
subscribe(destination, null, null);
|
||||
subscribe(destination, null, null);
|
||||
}
|
||||
|
||||
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 {
|
||||
if (headers == null) {
|
||||
headers = new HashMap<String, String>();
|
||||
}
|
||||
headers.put("destination", destination);
|
||||
if (ack != null) {
|
||||
headers.put("ack", ack);
|
||||
}
|
||||
StompFrame frame = new StompFrame("SUBSCRIBE", headers);
|
||||
if (headers == null) {
|
||||
headers = new HashMap<String, String>();
|
||||
}
|
||||
headers.put("destination", destination);
|
||||
if (ack != null) {
|
||||
headers.put("ack", ack);
|
||||
}
|
||||
StompFrame frame = new StompFrame("SUBSCRIBE", headers);
|
||||
sendFrame(frame.format());
|
||||
}
|
||||
|
||||
public void unsubscribe(String destination) throws Exception {
|
||||
unsubscribe(destination, null);
|
||||
unsubscribe(destination, null);
|
||||
}
|
||||
|
||||
public void unsubscribe(String destination, HashMap<String, String> headers) throws Exception {
|
||||
if (headers == null) {
|
||||
headers = new HashMap<String, String>();
|
||||
}
|
||||
headers.put("destination", destination);
|
||||
StompFrame frame = new StompFrame("UNSUBSCRIBE", headers);
|
||||
if (headers == null) {
|
||||
headers = new HashMap<String, String>();
|
||||
}
|
||||
headers.put("destination", destination);
|
||||
StompFrame frame = new StompFrame("UNSUBSCRIBE", headers);
|
||||
sendFrame(frame.format());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void begin(String transaction) throws Exception {
|
||||
HashMap<String, String> headers = new HashMap<String, String>();
|
||||
headers.put("transaction", transaction);
|
||||
StompFrame frame = new StompFrame("BEGIN", headers);
|
||||
sendFrame(frame.format());
|
||||
HashMap<String, String> headers = new HashMap<String, String>();
|
||||
headers.put("transaction", transaction);
|
||||
StompFrame frame = new StompFrame("BEGIN", headers);
|
||||
sendFrame(frame.format());
|
||||
}
|
||||
|
||||
public void abort(String transaction) throws Exception {
|
||||
HashMap<String, String> headers = new HashMap<String, String>();
|
||||
headers.put("transaction", transaction);
|
||||
StompFrame frame = new StompFrame("ABORT", headers);
|
||||
sendFrame(frame.format());
|
||||
HashMap<String, String> headers = new HashMap<String, String>();
|
||||
headers.put("transaction", transaction);
|
||||
StompFrame frame = new StompFrame("ABORT", headers);
|
||||
sendFrame(frame.format());
|
||||
}
|
||||
|
||||
public void commit(String transaction) throws Exception {
|
||||
HashMap<String, String> headers = new HashMap<String, String>();
|
||||
headers.put("transaction", transaction);
|
||||
StompFrame frame = new StompFrame("COMMIT", headers);
|
||||
sendFrame(frame.format());
|
||||
HashMap<String, String> headers = new HashMap<String, String>();
|
||||
headers.put("transaction", transaction);
|
||||
StompFrame frame = new StompFrame("COMMIT", headers);
|
||||
sendFrame(frame.format());
|
||||
}
|
||||
|
||||
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 {
|
||||
ack(frame.getHeaders().get("message-id"), transaction);
|
||||
ack(frame.getHeaders().get("message-id"), transaction);
|
||||
}
|
||||
|
||||
public void ack(String messageId) throws Exception {
|
||||
ack(messageId, null);
|
||||
ack(messageId, null);
|
||||
}
|
||||
|
||||
public void ack(String messageId, String transaction) throws Exception {
|
||||
HashMap<String, String> headers = new HashMap<String, String>();
|
||||
headers.put("message-id", messageId);
|
||||
if (transaction != null)
|
||||
headers.put("transaction", transaction);
|
||||
StompFrame frame = new StompFrame("ACK", headers);
|
||||
sendFrame(frame.format());
|
||||
HashMap<String, String> headers = new HashMap<String, String>();
|
||||
headers.put("message-id", messageId);
|
||||
if (transaction != null)
|
||||
headers.put("transaction", transaction);
|
||||
StompFrame frame = new StompFrame("ACK", headers);
|
||||
sendFrame(frame.format());
|
||||
}
|
||||
|
||||
protected String appendHeaders(HashMap<String, Object> headers) {
|
||||
StringBuffer result = new StringBuffer();
|
||||
for (String key : headers.keySet()) {
|
||||
result.append(key + ":" + headers.get(key) + "\n");
|
||||
}
|
||||
result.append("\n");
|
||||
return result.toString();
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (String key : headers.keySet()) {
|
||||
result.append(key + ":" + headers.get(key) + "\n");
|
||||
}
|
||||
result.append("\n");
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ package org.apache.activemq.transport.stomp;
|
|||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.activemq.command.Command;
|
||||
|
@ -180,12 +179,11 @@ public class StompFrame implements Command {
|
|||
}
|
||||
|
||||
public String format(boolean forLogging) {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
buffer.append(getAction());
|
||||
buffer.append("\n");
|
||||
Map headers = getHeaders();
|
||||
for (Iterator iter = headers.entrySet().iterator(); iter.hasNext();) {
|
||||
Map.Entry entry = (Map.Entry)iter.next();
|
||||
Map<String, String> headers = getHeaders();
|
||||
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
||||
buffer.append(entry.getKey());
|
||||
buffer.append(":");
|
||||
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;
|
||||
|
||||
import org.apache.activemq.transport.nio.NIOSSLTransport;
|
||||
import org.apache.activemq.util.IOExceptionSupport;
|
||||
import org.apache.activemq.wireformat.WireFormat;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.net.URI;
|
||||
|
|
|
@ -54,15 +54,15 @@ public class StompNIOSSLTransportFactory extends StompNIOTransportFactory {
|
|||
return new StompNIOSSLTransport(wf, socketFactory, location, localLocation);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public TransportServer doBind(URI location) throws IOException {
|
||||
if (SslContext.getCurrentSslContext() != null) {
|
||||
try {
|
||||
context = SslContext.getCurrentSslContext().getSSLContext();
|
||||
} catch (Exception e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
if (SslContext.getCurrentSslContext() != null) {
|
||||
try {
|
||||
context = SslContext.getCurrentSslContext().getSSLContext();
|
||||
} catch (Exception e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
return super.doBind(location);
|
||||
}
|
||||
|
||||
|
|
|
@ -26,19 +26,14 @@ import java.net.UnknownHostException;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.HashMap;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import org.apache.activemq.command.Command;
|
||||
import org.apache.activemq.transport.Transport;
|
||||
import org.apache.activemq.transport.nio.NIOOutputStream;
|
||||
import org.apache.activemq.transport.nio.SelectorManager;
|
||||
import org.apache.activemq.transport.nio.SelectorSelection;
|
||||
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.ServiceStopper;
|
||||
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
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class StompNIOTransport extends TcpTransport {
|
||||
|
||||
|
@ -133,7 +128,7 @@ public class StompNIOTransport extends TcpTransport {
|
|||
try {
|
||||
selection.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
e.printStackTrace();
|
||||
}
|
||||
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.util.IntrospectionSupport;
|
||||
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
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
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 {
|
||||
return new StompNIOTransport(wf, socketFactory, location, localLocation);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
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);
|
||||
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) {
|
||||
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
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class StompSslTransportFactory extends SslTransportFactory implements BrokerServiceAware {
|
||||
|
||||
|
@ -39,8 +39,9 @@ public class StompSslTransportFactory extends SslTransportFactory implements Bro
|
|||
return "stomp";
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
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);
|
||||
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 INDIVIDUAL_ACK = Stomp.Headers.Subscribe.AckModeValues.INDIVIDUAL;
|
||||
|
||||
private final ProtocolConverter protocolConverter;
|
||||
private final String subscriptionId;
|
||||
private final ConsumerInfo consumerInfo;
|
||||
protected final ProtocolConverter protocolConverter;
|
||||
protected final String subscriptionId;
|
||||
protected final ConsumerInfo consumerInfo;
|
||||
|
||||
private final LinkedHashMap<MessageId, MessageDispatch> dispatchedMessage = new LinkedHashMap<MessageId, MessageDispatch>();
|
||||
private final LinkedList<MessageDispatch> unconsumedMessage = new LinkedList<MessageDispatch>();
|
||||
|
||||
private String ackMode = AUTO_ACK;
|
||||
private ActiveMQDestination destination;
|
||||
private String transformation;
|
||||
protected final LinkedHashMap<MessageId, MessageDispatch> dispatchedMessage = new LinkedHashMap<MessageId, MessageDispatch>();
|
||||
protected final LinkedList<MessageDispatch> unconsumedMessage = new LinkedList<MessageDispatch>();
|
||||
|
||||
protected String ackMode = AUTO_ACK;
|
||||
protected ActiveMQDestination destination;
|
||||
protected String transformation;
|
||||
|
||||
public StompSubscription(ProtocolConverter stompTransport, String subscriptionId, ConsumerInfo consumerInfo, String transformation) {
|
||||
this.protocolConverter = stompTransport;
|
||||
|
@ -82,12 +81,12 @@ public class StompSubscription {
|
|||
boolean ignoreTransformation = false;
|
||||
|
||||
if (transformation != null && !( message instanceof ActiveMQBytesMessage ) ) {
|
||||
message.setReadOnlyProperties(false);
|
||||
message.setStringProperty(Stomp.Headers.TRANSFORMATION, transformation);
|
||||
message.setReadOnlyProperties(false);
|
||||
message.setStringProperty(Stomp.Headers.TRANSFORMATION, transformation);
|
||||
} else {
|
||||
if (message.getStringProperty(Stomp.Headers.TRANSFORMATION) != null) {
|
||||
ignoreTransformation = true;
|
||||
}
|
||||
if (message.getStringProperty(Stomp.Headers.TRANSFORMATION) != null) {
|
||||
ignoreTransformation = true;
|
||||
}
|
||||
}
|
||||
|
||||
StompFrame command = protocolConverter.convertMessage(message, ignoreTransformation);
|
||||
|
@ -101,24 +100,25 @@ public class StompSubscription {
|
|||
}
|
||||
|
||||
synchronized void onStompAbort(TransactionId transactionId) {
|
||||
unconsumedMessage.clear();
|
||||
unconsumedMessage.clear();
|
||||
}
|
||||
|
||||
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();
|
||||
MessageId id = (MessageId)entry.getKey();
|
||||
MessageDispatch msg = (MessageDispatch)entry.getValue();
|
||||
if (unconsumedMessage.contains(msg)) {
|
||||
iter.remove();
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
unconsumedMessage.clear();
|
||||
}
|
||||
|
||||
unconsumedMessage.clear();
|
||||
}
|
||||
|
||||
synchronized MessageAck onStompMessageAck(String messageId, TransactionId transactionId) {
|
||||
|
||||
MessageId msgId = new MessageId(messageId);
|
||||
MessageId msgId = new MessageId(messageId);
|
||||
|
||||
if (!dispatchedMessage.containsKey(msgId)) {
|
||||
return null;
|
||||
|
@ -129,10 +129,11 @@ public class StompSubscription {
|
|||
ack.setConsumerId(consumerInfo.getConsumerId());
|
||||
|
||||
if (ackMode == CLIENT_ACK) {
|
||||
ack.setAckType(MessageAck.STANDARD_ACK_TYPE);
|
||||
ack.setAckType(MessageAck.STANDARD_ACK_TYPE);
|
||||
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();
|
||||
MessageId id = (MessageId)entry.getKey();
|
||||
MessageDispatch msg = (MessageDispatch)entry.getValue();
|
||||
|
@ -142,39 +143,59 @@ public class StompSubscription {
|
|||
}
|
||||
|
||||
if (transactionId != null) {
|
||||
if (!unconsumedMessage.contains(msg)) {
|
||||
unconsumedMessage.add(msg);
|
||||
}
|
||||
if (!unconsumedMessage.contains(msg)) {
|
||||
unconsumedMessage.add(msg);
|
||||
}
|
||||
} else {
|
||||
iter.remove();
|
||||
iter.remove();
|
||||
}
|
||||
|
||||
|
||||
count++;
|
||||
|
||||
if (id.equals(msgId)) {
|
||||
ack.setLastMessageId(id);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
ack.setMessageCount(count);
|
||||
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.setMessageID(msgId);
|
||||
if (transactionId != null) {
|
||||
unconsumedMessage.add(dispatchedMessage.get(msgId));
|
||||
ack.setTransactionId(transactionId);
|
||||
unconsumedMessage.add(dispatchedMessage.get(msgId));
|
||||
ack.setTransactionId(transactionId);
|
||||
}
|
||||
dispatchedMessage.remove(msgId);
|
||||
}
|
||||
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() {
|
||||
return ackMode;
|
||||
}
|
||||
|
@ -198,5 +219,4 @@ public class StompSubscription {
|
|||
public ConsumerInfo getConsumerInfo() {
|
||||
return consumerInfo;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,11 +28,14 @@ import org.apache.activemq.command.Command;
|
|||
public interface StompTransport {
|
||||
|
||||
public void sendToActiveMQ(Command command);
|
||||
|
||||
|
||||
public void sendToStomp(StompFrame command) throws IOException;
|
||||
|
||||
|
||||
public X509Certificate[] getPeerCertificates();
|
||||
|
||||
|
||||
public void onException(IOException error);
|
||||
|
||||
|
||||
public StompInactivityMonitor getInactivityMonitor();
|
||||
|
||||
public StompWireFormat getWireFormat();
|
||||
}
|
||||
|
|
|
@ -16,48 +16,47 @@
|
|||
*/
|
||||
package org.apache.activemq.transport.stomp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ServerSocketFactory;
|
||||
import org.apache.activemq.broker.BrokerContext;
|
||||
import org.apache.activemq.broker.BrokerService;
|
||||
import org.apache.activemq.broker.BrokerServiceAware;
|
||||
import org.apache.activemq.transport.Transport;
|
||||
import org.apache.activemq.transport.tcp.TcpTransportFactory;
|
||||
import org.apache.activemq.transport.tcp.TcpTransportServer;
|
||||
import org.apache.activemq.util.IntrospectionSupport;
|
||||
import org.apache.activemq.wireformat.WireFormat;
|
||||
import org.apache.activemq.xbean.XBeanBrokerService;
|
||||
|
||||
/**
|
||||
* A <a href="http://stomp.codehaus.org/">STOMP</a> transport factory
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class StompTransportFactory extends TcpTransportFactory implements BrokerServiceAware {
|
||||
|
||||
private BrokerContext brokerContext = null;
|
||||
|
||||
private BrokerContext brokerContext = null;
|
||||
|
||||
protected String getDefaultWireFormatType() {
|
||||
return "stomp";
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
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);
|
||||
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) {
|
||||
this.brokerContext = brokerService.getBrokerContext();
|
||||
}
|
||||
|
||||
public void setBrokerService(BrokerService brokerService) {
|
||||
this.brokerContext = brokerService.getBrokerContext();
|
||||
}
|
||||
@Override
|
||||
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.tcp.SslTransport;
|
||||
import org.apache.activemq.util.IOExceptionSupport;
|
||||
import org.apache.activemq.wireformat.WireFormat;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -36,20 +37,24 @@ import org.slf4j.LoggerFactory;
|
|||
* 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
|
||||
* ProtocolConverter.
|
||||
*
|
||||
*
|
||||
* @author <a href="http://hiramchirino.com">chirino</a>
|
||||
*/
|
||||
public class StompTransportFilter extends TransportFilter implements StompTransport {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(StompTransportFilter.class);
|
||||
private final ProtocolConverter protocolConverter;
|
||||
private final FrameTranslator frameTranslator;
|
||||
private StompInactivityMonitor monitor;
|
||||
private StompWireFormat wireFormat;
|
||||
|
||||
private boolean trace;
|
||||
|
||||
public StompTransportFilter(Transport next, FrameTranslator translator, BrokerContext brokerContext) {
|
||||
public StompTransportFilter(Transport next, WireFormat wireFormat, BrokerContext brokerContext) {
|
||||
super(next);
|
||||
this.frameTranslator = translator;
|
||||
this.protocolConverter = new ProtocolConverter(this, translator, brokerContext);
|
||||
this.protocolConverter = new ProtocolConverter(this, brokerContext);
|
||||
|
||||
if (wireFormat instanceof StompWireFormat) {
|
||||
this.wireFormat = (StompWireFormat) wireFormat;
|
||||
}
|
||||
}
|
||||
|
||||
public void oneway(Object o) throws IOException {
|
||||
|
@ -66,7 +71,7 @@ public class StompTransportFilter extends TransportFilter implements StompTransp
|
|||
if (trace) {
|
||||
LOG.trace("Received: \n" + command);
|
||||
}
|
||||
|
||||
|
||||
protocolConverter.onStompCommand((StompFrame)command);
|
||||
} catch (IOException e) {
|
||||
onException(e);
|
||||
|
@ -92,21 +97,17 @@ public class StompTransportFilter extends TransportFilter implements StompTransp
|
|||
}
|
||||
}
|
||||
|
||||
public FrameTranslator getFrameTranslator() {
|
||||
return frameTranslator;
|
||||
}
|
||||
|
||||
public X509Certificate[] getPeerCertificates() {
|
||||
if(next instanceof SslTransport) {
|
||||
X509Certificate[] peerCerts = ((SslTransport)next).getPeerCertificates();
|
||||
if (trace && peerCerts != null) {
|
||||
if(next instanceof SslTransport) {
|
||||
X509Certificate[] peerCerts = ((SslTransport)next).getPeerCertificates();
|
||||
if (trace && peerCerts != null) {
|
||||
LOG.debug("Peer Identity has been verified\n");
|
||||
}
|
||||
return peerCerts;
|
||||
}
|
||||
return null;
|
||||
return peerCerts;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public boolean isTrace() {
|
||||
return trace;
|
||||
}
|
||||
|
@ -114,4 +115,18 @@ public class StompTransportFilter extends TransportFilter implements StompTransp
|
|||
public void setTrace(boolean 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.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PushbackInputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
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_DATA_LENGTH = 1024 * 1024 * 100;
|
||||
|
||||
private boolean encodingEnabled = false;
|
||||
private int version = 1;
|
||||
|
||||
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 {
|
||||
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.NEWLINE);
|
||||
|
||||
// Output the headers.
|
||||
for (Iterator iter = stomp.getHeaders().entrySet().iterator(); iter.hasNext();) {
|
||||
Map.Entry entry = (Map.Entry)iter.next();
|
||||
for (Map.Entry<String, String> entry : stomp.getHeaders().entrySet()) {
|
||||
buffer.append(entry.getKey());
|
||||
buffer.append(Stomp.Headers.SEPERATOR);
|
||||
buffer.append(entry.getValue());
|
||||
buffer.append(encodeHeader(entry.getValue()));
|
||||
buffer.append(Stomp.NEWLINE);
|
||||
}
|
||||
|
||||
|
@ -87,7 +93,7 @@ public class StompWireFormat implements WireFormat {
|
|||
public Object unmarshal(DataInput in) throws IOException {
|
||||
|
||||
try {
|
||||
|
||||
|
||||
// parse action
|
||||
String action = parseAction(in);
|
||||
|
||||
|
@ -129,7 +135,6 @@ public class StompWireFormat implements WireFormat {
|
|||
baos.close();
|
||||
data = baos.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new StompFrame(action, headers, data);
|
||||
|
@ -137,10 +142,14 @@ public class StompWireFormat implements WireFormat {
|
|||
} catch (ProtocolException e) {
|
||||
return new StompFrameError(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(maxLength);
|
||||
while ((b = in.readByte()) != '\n') {
|
||||
|
@ -150,10 +159,9 @@ public class StompWireFormat implements WireFormat {
|
|||
baos.write(b);
|
||||
}
|
||||
baos.close();
|
||||
ByteSequence sequence = baos.toByteSequence();
|
||||
return new String(sequence.getData(), sequence.getOffset(), sequence.getLength(), "UTF-8");
|
||||
return baos.toByteSequence();
|
||||
}
|
||||
|
||||
|
||||
protected String parseAction(DataInput in) throws IOException {
|
||||
String action = null;
|
||||
|
||||
|
@ -171,21 +179,35 @@ public class StompWireFormat implements WireFormat {
|
|||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
|
||||
protected HashMap<String, String> parseHeaders(DataInput in) throws IOException {
|
||||
HashMap<String, String> headers = new HashMap<String, String>(25);
|
||||
while (true) {
|
||||
String line = readLine(in, MAX_HEADER_LENGTH, "The maximum header length was exceeded");
|
||||
if (line != null && line.trim().length() > 0) {
|
||||
ByteSequence line = readHeaderLine(in, MAX_HEADER_LENGTH, "The maximum header length was exceeded");
|
||||
if (line != null && line.length > 0) {
|
||||
|
||||
if (headers.size() > MAX_HEADERS) {
|
||||
throw new ProtocolException("The maximum number of headers was exceeded", true);
|
||||
}
|
||||
|
||||
try {
|
||||
int seperatorIndex = line.indexOf(Stomp.Headers.SEPERATOR);
|
||||
String name = line.substring(0, seperatorIndex).trim();
|
||||
String value = line.substring(seperatorIndex + 1, line.length()).trim();
|
||||
|
||||
ByteArrayInputStream headerLine = new ByteArrayInputStream(line);
|
||||
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);
|
||||
} catch (Exception e) {
|
||||
throw new ProtocolException("Unable to parser header line [" + line + "]", true);
|
||||
|
@ -193,10 +215,10 @@ public class StompWireFormat implements WireFormat {
|
|||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
|
||||
protected int parseContentLength(String contentLength) throws ProtocolException {
|
||||
int length;
|
||||
try {
|
||||
|
@ -208,10 +230,71 @@ public class StompWireFormat implements WireFormat {
|
|||
if (length > MAX_DATA_LENGTH) {
|
||||
throw new ProtocolException("The maximum data length was exceeded", true);
|
||||
}
|
||||
|
||||
|
||||
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() {
|
||||
return version;
|
||||
}
|
||||
|
@ -220,4 +303,12 @@ public class StompWireFormat implements WireFormat {
|
|||
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.URISyntaxException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
@ -30,21 +27,13 @@ import java.util.Map;
|
|||
import javax.net.ServerSocketFactory;
|
||||
import javax.net.SocketFactory;
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLServerSocketFactory;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
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.openwire.OpenWireFormat;
|
||||
import org.apache.activemq.transport.InactivityMonitor;
|
||||
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.WireFormatNegotiator;
|
||||
import org.apache.activemq.util.IOExceptionSupport;
|
||||
import org.apache.activemq.util.IntrospectionSupport;
|
||||
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
|
||||
* SslTransport classes. All Transports and TransportServers created from this
|
||||
* factory will have their needClientAuth option set to false.
|
||||
*
|
||||
*
|
||||
* @author sepandm@gmail.com (Sepand)
|
||||
* @author David Martin Clavo david(dot)martin(dot)clavo(at)gmail.com (logging improvement modifications)
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class SslTransportFactory extends TcpTransportFactory {
|
||||
// The log this uses.,
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SslTransportFactory.class);
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
* configuration
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Transport compositeConfigure(Transport transport, WireFormat format, Map options) {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new SSL ServerSocketFactory. The given factory will use
|
||||
* user-provided key and trust managers (if the user provided them).
|
||||
*
|
||||
*
|
||||
* @return Newly created (Ssl)ServerSocketFactory.
|
||||
* @throws IOException
|
||||
* @throws IOException
|
||||
*/
|
||||
protected ServerSocketFactory createServerSocketFactory() throws IOException {
|
||||
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
|
||||
* key and trust managers (if the user provided them).
|
||||
*
|
||||
*
|
||||
* @return Newly created (Ssl)SocketFactory.
|
||||
* @throws IOException
|
||||
* @throws IOException
|
||||
*/
|
||||
protected SocketFactory createSocketFactory() throws IOException {
|
||||
|
||||
|
||||
if( SslContext.getCurrentSslContext()!=null ) {
|
||||
SslContext ctx = SslContext.getCurrentSslContext();
|
||||
try {
|
||||
|
@ -161,11 +149,10 @@ public class SslTransportFactory extends TcpTransportFactory {
|
|||
} else {
|
||||
return SSLSocketFactory.getDefault();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param km
|
||||
* @param tm
|
||||
* @param random
|
||||
|
|
|
@ -20,16 +20,22 @@ import java.io.DataInputStream;
|
|||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
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.Map;
|
||||
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.atomic.AtomicReference;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import org.apache.activemq.Service;
|
||||
import org.apache.activemq.thread.DefaultThreadPools;
|
||||
import org.apache.activemq.transport.Transport;
|
||||
|
@ -64,7 +70,6 @@ public class TcpTransport extends TransportThreadSupport implements Transport, S
|
|||
protected DataInputStream dataIn;
|
||||
protected TimeStampStream buffOut = null;
|
||||
|
||||
|
||||
/**
|
||||
* The Traffic Class to be set on the socket.
|
||||
*/
|
||||
|
@ -636,7 +641,6 @@ public class TcpTransport extends TransportThreadSupport implements Transport, S
|
|||
return receiveCounter;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param sock The socket on which to set the Traffic Class.
|
||||
* @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);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Transport compositeConfigure(Transport transport, WireFormat format, Map options) {
|
||||
|
||||
TcpTransport tcpTransport = (TcpTransport)transport.narrow(TcpTransport.class);
|
||||
|
@ -98,11 +99,10 @@ public class TcpTransportFactory extends TransportFactory {
|
|||
|
||||
boolean useInactivityMonitor = "true".equals(getOption(options, "useInactivityMonitor", "true"));
|
||||
if (useInactivityMonitor && isUseInactivityMonitor(transport)) {
|
||||
transport = new InactivityMonitor(transport, format);
|
||||
transport = createInactivityMonitor(transport, format);
|
||||
IntrospectionSupport.setProperties(transport, options);
|
||||
}
|
||||
|
||||
|
||||
// Only need the WireFormatNegotiator if using openwire
|
||||
if (format instanceof OpenWireFormat) {
|
||||
transport = new WireFormatNegotiator(transport, (OpenWireFormat)format, tcpTransport.getMinmumWireFormatVersion());
|
||||
|
@ -162,4 +162,8 @@ public class TcpTransportFactory extends TransportFactory {
|
|||
protected SocketFactory createSocketFactory() throws IOException {
|
||||
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.UnknownHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
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.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.jms.BytesMessage;
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.JMSException;
|
||||
|
@ -36,6 +37,7 @@ import javax.jms.ObjectMessage;
|
|||
import javax.jms.Session;
|
||||
import javax.jms.TextMessage;
|
||||
import javax.management.ObjectName;
|
||||
|
||||
import org.apache.activemq.ActiveMQConnectionFactory;
|
||||
import org.apache.activemq.CombinationTestSupport;
|
||||
import org.apache.activemq.broker.BrokerFactory;
|
||||
|
@ -318,7 +320,7 @@ public class StompTest extends CombinationTestSupport {
|
|||
assertEquals("Hello World", message.getText());
|
||||
assertEquals("getJMSPriority", 4, message.getJMSPriority());
|
||||
}
|
||||
|
||||
|
||||
public void testReceipts() throws Exception {
|
||||
|
||||
StompConnection receiver = new StompConnection();
|
||||
|
@ -449,7 +451,7 @@ public class StompTest extends CombinationTestSupport {
|
|||
|
||||
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;
|
||||
stompConnection.sendFrame(frame);
|
||||
|
|
|
@ -21,9 +21,9 @@ import java.security.cert.X509Certificate;
|
|||
|
||||
import org.apache.activemq.command.Command;
|
||||
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.StompFrame;
|
||||
import org.apache.activemq.transport.stomp.StompInactivityMonitor;
|
||||
import org.apache.activemq.transport.stomp.StompTransport;
|
||||
import org.apache.activemq.transport.stomp.StompWireFormat;
|
||||
import org.apache.activemq.util.ByteSequence;
|
||||
|
@ -32,19 +32,19 @@ import org.apache.activemq.util.ServiceStopper;
|
|||
import org.eclipse.jetty.websocket.WebSocket;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Implements web socket and mediates between servlet and the broker
|
||||
*
|
||||
*/
|
||||
class StompSocket extends TransportSupport implements WebSocket, StompTransport {
|
||||
Outbound outbound;
|
||||
ProtocolConverter protocolConverter = new ProtocolConverter(this, new LegacyFrameTranslator(), null);
|
||||
ProtocolConverter protocolConverter = new ProtocolConverter(this, null);
|
||||
StompWireFormat wireFormat = new StompWireFormat();
|
||||
|
||||
public void onConnect(Outbound outbound) {
|
||||
this.outbound=outbound;
|
||||
}
|
||||
|
||||
|
||||
public void onMessage(byte frame, byte[] data,int offset, int length) {}
|
||||
|
||||
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 {
|
||||
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