You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by de...@apache.org on 2013/07/04 16:54:29 UTC
svn commit: r1499777 [1/2] - in /activemq/trunk: activemq-http/
activemq-http/src/main/java/org/apache/activemq/transport/ws/
activemq-mqtt/src/main/java/org/apache/activemq/transport/mqtt/
activemq-web-demo/src/main/webapp/mqtt/ activemq-web-demo/src/...
Author: dejanb
Date: Thu Jul 4 14:54:29 2013
New Revision: 1499777
URL: http://svn.apache.org/r1499777
Log:
https://issues.apache.org/jira/browse/AMQ-4617 - mqtt over ws protocol
Added:
activemq/trunk/activemq-http/src/main/java/org/apache/activemq/transport/ws/MQTTSocket.java
activemq/trunk/activemq-http/src/main/java/org/apache/activemq/transport/ws/WSServlet.java
- copied, changed from r1498875, activemq/trunk/activemq-http/src/main/java/org/apache/activemq/transport/ws/StompServlet.java
activemq/trunk/activemq-web-demo/src/main/webapp/mqtt/
activemq/trunk/activemq-web-demo/src/main/webapp/mqtt/chat.css
activemq/trunk/activemq-web-demo/src/main/webapp/mqtt/chat.js
- copied, changed from r1498875, activemq/trunk/activemq-web-demo/src/main/webapp/websocket/chat.js
activemq/trunk/activemq-web-demo/src/main/webapp/mqtt/index.html
activemq/trunk/activemq-web-demo/src/main/webapp/mqtt/mqttws31.js
Removed:
activemq/trunk/activemq-http/src/main/java/org/apache/activemq/transport/ws/StompServlet.java
Modified:
activemq/trunk/activemq-http/pom.xml
activemq/trunk/activemq-http/src/main/java/org/apache/activemq/transport/ws/WSTransportServer.java
activemq/trunk/activemq-mqtt/src/main/java/org/apache/activemq/transport/mqtt/MQTTProtocolConverter.java
activemq/trunk/activemq-web-demo/src/main/webapp/websocket/chat.js
Modified: activemq/trunk/activemq-http/pom.xml
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-http/pom.xml?rev=1499777&r1=1499776&r2=1499777&view=diff
==============================================================================
--- activemq/trunk/activemq-http/pom.xml (original)
+++ activemq/trunk/activemq-http/pom.xml Thu Jul 4 14:54:29 2013
@@ -40,6 +40,10 @@
<artifactId>activemq-stomp</artifactId>
</dependency>
<dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>activemq-mqtt</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
Added: activemq/trunk/activemq-http/src/main/java/org/apache/activemq/transport/ws/MQTTSocket.java
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-http/src/main/java/org/apache/activemq/transport/ws/MQTTSocket.java?rev=1499777&view=auto
==============================================================================
--- activemq/trunk/activemq-http/src/main/java/org/apache/activemq/transport/ws/MQTTSocket.java (added)
+++ activemq/trunk/activemq-http/src/main/java/org/apache/activemq/transport/ws/MQTTSocket.java Thu Jul 4 14:54:29 2013
@@ -0,0 +1,135 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ws;
+
+import org.apache.activemq.command.Command;
+import org.apache.activemq.transport.TransportSupport;
+import org.apache.activemq.transport.mqtt.MQTTInactivityMonitor;
+import org.apache.activemq.transport.mqtt.MQTTProtocolConverter;
+import org.apache.activemq.transport.mqtt.MQTTTransport;
+import org.apache.activemq.transport.mqtt.MQTTWireFormat;
+import org.apache.activemq.util.ByteSequence;
+import org.apache.activemq.util.IOExceptionSupport;
+import org.apache.activemq.util.ServiceStopper;
+import org.eclipse.jetty.websocket.WebSocket;
+import org.fusesource.mqtt.codec.DISCONNECT;
+import org.fusesource.mqtt.codec.MQTTFrame;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+import java.util.concurrent.CountDownLatch;
+
+public class MQTTSocket extends TransportSupport implements WebSocket.OnBinaryMessage, MQTTTransport {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MQTTSocket.class);
+ Connection outbound;
+ MQTTProtocolConverter protocolConverter = new MQTTProtocolConverter(this, null);
+ MQTTWireFormat wireFormat = new MQTTWireFormat();
+ private final CountDownLatch socketTransportStarted = new CountDownLatch(1);
+
+ @Override
+ public void onMessage(byte[] bytes, int offset, int length) {
+ if (!transportStartedAtLeastOnce()) {
+ LOG.debug("Waiting for StompSocket to be properly started...");
+ try {
+ socketTransportStarted.await();
+ } catch (InterruptedException e) {
+ LOG.warn("While waiting for StompSocket to be properly started, we got interrupted!! Should be okay, but you could see race conditions...");
+ }
+ }
+
+ try {
+ MQTTFrame frame = (MQTTFrame)wireFormat.unmarshal(new ByteSequence(bytes, offset, length));
+ protocolConverter.onMQTTCommand(frame);
+ } catch (Exception e) {
+ onException(IOExceptionSupport.create(e));
+ }
+ }
+
+ @Override
+ public void onOpen(Connection connection) {
+ this.outbound = connection;
+ }
+
+ @Override
+ public void onClose(int closeCode, String message) {
+ try {
+ protocolConverter.onMQTTCommand(new DISCONNECT().encode());
+ } catch (Exception e) {
+ LOG.warn("Failed to close WebSocket", e);
+ }
+ }
+
+ protected void doStart() throws Exception {
+ socketTransportStarted.countDown();
+ }
+
+ @Override
+ protected void doStop(ServiceStopper stopper) throws Exception {
+ }
+
+ private boolean transportStartedAtLeastOnce() {
+ return socketTransportStarted.getCount() == 0;
+ }
+
+ @Override
+ public int getReceiveCounter() {
+ return 0;
+ }
+
+ @Override
+ public String getRemoteAddress() {
+ return "MQTTSocket_" + this.hashCode();
+ }
+
+ @Override
+ public void oneway(Object command) throws IOException {
+ try {
+ protocolConverter.onActiveMQCommand((Command)command);
+ } catch (Exception e) {
+ onException(IOExceptionSupport.create(e));
+ }
+ }
+
+ @Override
+ public void sendToActiveMQ(Command command) {
+ doConsume(command);
+ }
+
+ @Override
+ public void sendToMQTT(MQTTFrame command) throws IOException {
+ ByteSequence bytes = wireFormat.marshal(command);
+ outbound.sendMessage(bytes.getData(), 0, bytes.getLength());
+ }
+
+ @Override
+ public X509Certificate[] getPeerCertificates() {
+ return new X509Certificate[0];
+ }
+
+ @Override
+ public MQTTInactivityMonitor getInactivityMonitor() {
+ return null;
+ }
+
+ @Override
+ public MQTTWireFormat getWireFormat() {
+ return wireFormat;
+ }
+}
Copied: activemq/trunk/activemq-http/src/main/java/org/apache/activemq/transport/ws/WSServlet.java (from r1498875, activemq/trunk/activemq-http/src/main/java/org/apache/activemq/transport/ws/StompServlet.java)
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-http/src/main/java/org/apache/activemq/transport/ws/WSServlet.java?p2=activemq/trunk/activemq-http/src/main/java/org/apache/activemq/transport/ws/WSServlet.java&p1=activemq/trunk/activemq-http/src/main/java/org/apache/activemq/transport/ws/StompServlet.java&r1=1498875&r2=1499777&rev=1499777&view=diff
==============================================================================
--- activemq/trunk/activemq-http/src/main/java/org/apache/activemq/transport/ws/StompServlet.java (original)
+++ activemq/trunk/activemq-http/src/main/java/org/apache/activemq/transport/ws/WSServlet.java Thu Jul 4 14:54:29 2013
@@ -22,6 +22,7 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.apache.activemq.transport.Transport;
import org.apache.activemq.transport.TransportAcceptListener;
import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocketServlet;
@@ -29,7 +30,7 @@ import org.eclipse.jetty.websocket.WebSo
/**
* Handle connection upgrade requests and creates web sockets
*/
-public class StompServlet extends WebSocketServlet {
+public class WSServlet extends WebSocketServlet {
private static final long serialVersionUID = -4716657876092884139L;
private TransportAcceptListener listener;
@@ -49,8 +50,13 @@ public class StompServlet extends WebSoc
@Override
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
- StompSocket socket = new StompSocket();
- listener.onAccept(socket);
+ WebSocket socket;
+ if (protocol.startsWith("mqtt")) {
+ socket = new MQTTSocket();
+ } else {
+ socket = new StompSocket();
+ }
+ listener.onAccept((Transport)socket);
return socket;
}
}
Modified: activemq/trunk/activemq-http/src/main/java/org/apache/activemq/transport/ws/WSTransportServer.java
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-http/src/main/java/org/apache/activemq/transport/ws/WSTransportServer.java?rev=1499777&r1=1499776&r2=1499777&view=diff
==============================================================================
--- activemq/trunk/activemq-http/src/main/java/org/apache/activemq/transport/ws/WSTransportServer.java (original)
+++ activemq/trunk/activemq-http/src/main/java/org/apache/activemq/transport/ws/WSTransportServer.java Thu Jul 4 14:54:29 2013
@@ -65,7 +65,7 @@ public class WSTransportServer extends W
}
}
- holder.setServlet(new StompServlet());
+ holder.setServlet(new WSServlet());
contextHandler.addServlet(holder, "/");
contextHandler.setAttribute("acceptListener", getAcceptListener());
Modified: activemq/trunk/activemq-mqtt/src/main/java/org/apache/activemq/transport/mqtt/MQTTProtocolConverter.java
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-mqtt/src/main/java/org/apache/activemq/transport/mqtt/MQTTProtocolConverter.java?rev=1499777&r1=1499776&r2=1499777&view=diff
==============================================================================
--- activemq/trunk/activemq-mqtt/src/main/java/org/apache/activemq/transport/mqtt/MQTTProtocolConverter.java (original)
+++ activemq/trunk/activemq-mqtt/src/main/java/org/apache/activemq/transport/mqtt/MQTTProtocolConverter.java Thu Jul 4 14:54:29 2013
@@ -79,7 +79,7 @@ import org.fusesource.mqtt.codec.UNSUBSC
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-class MQTTProtocolConverter {
+public class MQTTProtocolConverter {
private static final Logger LOG = LoggerFactory.getLogger(MQTTProtocolConverter.class);
Added: activemq/trunk/activemq-web-demo/src/main/webapp/mqtt/chat.css
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-web-demo/src/main/webapp/mqtt/chat.css?rev=1499777&view=auto
==============================================================================
--- activemq/trunk/activemq-web-demo/src/main/webapp/mqtt/chat.css (added)
+++ activemq/trunk/activemq-web-demo/src/main/webapp/mqtt/chat.css Thu Jul 4 14:54:29 2013
@@ -0,0 +1,112 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+* {
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ font-family: 'Helvetica Neue', Helvetica, Verdana, Arial, sans-serif;
+ padding: 10px;
+}
+
+#disconnect {
+ display: none;
+}
+#subscribe {
+ display: none;
+}
+
+#debug {
+ background-color: #F0F0F0;
+ font-size: 12px;
+ width: 800px;
+ margin-left: 0px;
+ margin-top: 10px;
+ margin-right: 0px;
+ padding: 10px;
+ border: 1px solid #CCC;
+}
+
+#send_form {
+ width: 800px;
+ bottom: 10px;
+}
+
+#send_form #send_form_input {
+ border: 1px solid #CCC;
+ font-size: 16px;
+ height: 20px;
+ padding: 10px;
+ width: 800px;
+}
+
+#send_form input[disabled] {
+ background-color: #EEE;
+}
+
+#messages {
+ bottom: 25px;
+ left: 0;
+ overflow: auto;
+ padding: 5px;
+ right: 0;
+ top: 2em;
+ z-index: -1;
+}
+
+.message {
+ width: 95%;
+}
+
+.timestamp {
+ font-size: 12px;
+}
+
+.me, .nick {
+ float: left;
+ width: 100px;
+}
+
+.me {
+ color: #F99;
+}
+
+.nick {
+ color: #99F;
+}
+
+.status {
+ background-color: #DDD;
+}
+
+form dt {
+ clear:both;
+ width:19%;
+ float:left;
+ text-align:right;
+}
+
+form dd {
+ float:left;
+ width:80%;
+ margin:0 0 0.5em 0.25em;
+}
+
+input {
+ width: 320px;
+}
\ No newline at end of file
Copied: activemq/trunk/activemq-web-demo/src/main/webapp/mqtt/chat.js (from r1498875, activemq/trunk/activemq-web-demo/src/main/webapp/websocket/chat.js)
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-web-demo/src/main/webapp/mqtt/chat.js?p2=activemq/trunk/activemq-web-demo/src/main/webapp/mqtt/chat.js&p1=activemq/trunk/activemq-web-demo/src/main/webapp/websocket/chat.js&r1=1498875&r2=1499777&rev=1499777&view=diff
==============================================================================
--- activemq/trunk/activemq-web-demo/src/main/webapp/websocket/chat.js (original)
+++ activemq/trunk/activemq-web-demo/src/main/webapp/mqtt/chat.js Thu Jul 4 14:54:29 2013
@@ -16,53 +16,76 @@
* limitations under the License.
*/
$(document).ready(function(){
-
var client, destination;
$('#connect_form').submit(function() {
- var url = $("#connect_url").val();
- var login = $("#connect_login").val();
- var passcode = $("#connect_passcode").val();
+
+ var host = $("#connect_host").val();
+ var port = $("#connect_port").val();
+ var clientId = $("#connect_clientId").val();
destination = $("#destination").val();
- client = Stomp.client(url);
+
+ client = new Messaging.Client(host, Number(port), clientId);
+
+ client.onConnect = onConnect;
+
+ client.onMessageArrived = onMessageArrived;
+ client.onConnectionLost = onConnectionLost;
+
+ client.connect({onSuccess:onConnect, onFailure:onFailure});
+ return false;
+ });
- // this allows to display debug logs directly on the web page
- client.debug = function(str) {
- $("#debug").append(document.createTextNode(str + "\n"));
- };
// the client is notified when it is connected to the server.
- var onconnect = function(frame) {
- client.debug("connected to Stomp");
+ var onConnect = function(frame) {
+ debug("connected to MQTT");
$('#connect').fadeOut({ duration: 'fast' });
$('#disconnect').fadeIn();
$('#send_form_input').removeAttr('disabled');
+ client.subscribe(destination);
+ };
- client.subscribe(destination, function(message) {
- $("#messages").append(document.createTextNode("<p>" + message.body + "</p>\n"));
- });
- };
- client.connect(login, passcode, onconnect);
-
- return false;
- });
+ // this allows to display debug logs directly on the web page
+ var debug = function(str) {
+ $("#debug").append(document.createTextNode(str + "\n"));
+ };
$('#disconnect_form').submit(function() {
- client.disconnect(function() {
- $('#disconnect').fadeOut({ duration: 'fast' });
- $('#connect').fadeIn();
- $('#send_form_input').addAttr('disabled');
- });
+ client.disconnect();
+ $('#disconnect').fadeOut({ duration: 'fast' });
+ $('#connect').fadeIn();
+ $('#send_form_input').addAttr('disabled');
return false;
});
$('#send_form').submit(function() {
var text = $('#send_form_input').val();
if (text) {
- client.send(destination, {foo: 1}, text);
+ message = new Messaging.Message(text);
+ message.destinationName = destination;
+ client.send(message);
$('#send_form_input').val("");
}
return false;
});
+ function onFailure(failure) {
+ debug("failure");
+ debug(failure.errorMessage);
+ }
+
+ function onMessageArrived(message) {
+ var p = document.createElement("p");
+ var t = document.createTextNode(message.payloadString);
+ p.appendChild(t);
+ $("#messages").append(p);
+ }
+
+ function onConnectionLost(responseObject) {
+ if (responseObject.errorCode !== 0) {
+ debug(client.clientId + ": " + responseObject.errorCode + "\n");
+ }
+ }
+
});
\ No newline at end of file
Added: activemq/trunk/activemq-web-demo/src/main/webapp/mqtt/index.html
URL: http://svn.apache.org/viewvc/activemq/trunk/activemq-web-demo/src/main/webapp/mqtt/index.html?rev=1499777&view=auto
==============================================================================
--- activemq/trunk/activemq-web-demo/src/main/webapp/mqtt/index.html (added)
+++ activemq/trunk/activemq-web-demo/src/main/webapp/mqtt/index.html Thu Jul 4 14:54:29 2013
@@ -0,0 +1,174 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+ <link rel="stylesheet" href="chat.css" type="text/css">
+ <style type="text/css" media="screen">
+ @import url(../styles/sorttable.css);
+ @import url(../styles/type-settings.css);
+ @import url(../styles/site.css);
+ @import url(../styles/prettify.css);
+ </style>
+ <title>Chat Example Using Stomp Over WebSocket</title>
+ <link rel="stylesheet" type="text/css" href="chat.css"></link>
+ <script type="text/javascript" src="../js/jquery-1.4.2.min.js"></script>
+ <script src='mqttws31.js'></script>
+ <script src='chat.js'></script>
+ <script>
+ $(document).ready(function() {
+ var supported = ("WebSocket" in window);
+ if(!supported) {
+ var msg = "Your browser does not support Web Sockets. This example will not work properly.<br>";
+ msg += "Please use a Web Browser with Web Sockets support (WebKit or Google Chrome).";
+ $("#connect").html(msg);
+ }
+ });
+ </script>
+</head>
+<body>
+
+<div class="white_box">
+ <div class="header">
+ <div class="header_l">
+ <div class="header_r">
+ </div>
+ </div>
+ </div>
+ <div class="content">
+ <div class="content_l">
+ <div class="content_r">
+
+ <div>
+
+ <!-- Banner -->
+ <div id="asf_logo">
+ <div id="activemq_logo">
+ <a style="float:left; width:280px;display:block;text-indent:-5000px;text-decoration:none;line-height:60px; margin-top:10px; margin-left:100px;"
+ href="http://activemq.apache.org/"
+ title="The most popular and powerful open source Message Broker">ActiveMQ</a>
+ <a style="float:right; width:210px;display:block;text-indent:-5000px;text-decoration:none;line-height:60px; margin-top:15px; margin-right:10px;"
+ href="http://www.apache.org/" title="The Apache Software Foundation">ASF</a>
+ </div>
+ </div>
+
+
+ <div class="top_red_bar">
+ <div id="site-breadcrumbs">
+ <a href="../index.html" title="Home">Home</a>
+ </div>
+ <div id="site-quicklinks"><P>
+ <a href="http://activemq.apache.org/support.html"
+ title="Get help and support using Apache ActiveMQ">Support</a></p>
+ </div>
+ </div>
+
+ <table border="0">
+ <tbody>
+ <tr>
+ <td valign="top" width="100%" style="overflow:hidden;">
+ <div class="body-content">
+
+<div id='connect'>
+ <form id='connect_form'>
+ <dl>
+ <dt><label for=connect_url>Server Host</label></dt>
+ <dd><input name=url id='connect_host' value='localhost'></dd>
+ <dt><label for=connect_port>Server Port</label></dt>
+ <dd><input id='connect_port' placeholder="Server Port" value="61614"></dd>
+ <dt><label for=connect_clientId>Client ID</label></dt>
+ <dd><input id='connect_clientId' placeholder="Client ID" value="clientId"></dd>
+ <dt><label for=destination>Destination</label></dt>
+ <dd><input id='destination' placeholder="Destination" value="/test"></dd>
+ <dt> </dt>
+ <dd><input type=submit id='connect_submit' value="Connect"></dd>
+ </dl>
+ </form>
+
+ <p><b>Make sure you have enabled <a href="http://activemq.apache.org/websockets.html">websockets transport</a> before running this demo</b></p>
+ <p>This is adapted <a href="https://github.com/jmesnil/stomp-websocket">stomp-websocket</a> library demo</p>
+ <p>Use the form above to connect to the Stomp server and subscribe to the destination.</p>
+ <p>Once connected, you can send messages to the destination with the text field at the bottom of this page</p>
+
+
+</div>
+<div id="disconnect">
+ <form id='disconnect_form'>
+ <input type=submit id='disconnect_submit' value="Disconnect">
+ </form>
+</div>
+<div id="messages">
+</div>
+
+<form id='send_form'>
+ <input id='send_form_input' placeholder="Type your message here" disabled />
+</form>
+
+<pre id="debug"></pre>
+
+ </div>
+ </td>
+ <td valign="top">
+
+ <div class="navigation">
+ <div class="navigation_top">
+ <div class="navigation_bottom">
+ <H3>Useful Links</H3>
+
+ <ul class="alternate" type="square">
+ <li><a href="http://activemq.apache.org/"
+ title="The most popular and powerful open source Message Broker">Documentation</a></li>
+ <li><a href="http://activemq.apache.org/faq.html">FAQ</a></li>
+ <li><a href="http://activemq.apache.org/download.html">Downloads</a>
+ </li>
+ <li><a href="http://activemq.apache.org/discussion-forums.html">Forums</a>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+
+ <div class="bottom_red_bar"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="black_box">
+ <div class="footer">
+ <div class="footer_l">
+ <div class="footer_r">
+ <div>
+ Copyright 2005-2012 The Apache Software Foundation.
+
+ (<a href="?printable=true">printable version</a>)
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+<div class="design_attribution"><a href="http://hiramchirino.com/">Graphic Design By Hiram</a></div>
+
+</body>
+</html>
\ No newline at end of file