summaryrefslogtreecommitdiff
path: root/qpid/tools/src/java/qpid-qmf2/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'qpid/tools/src/java/qpid-qmf2/src/main/java')
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/Agent.java1465
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/AgentExternal.java259
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/MethodCallParams.java123
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/MethodCallWorkItem.java64
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/QmfAgentData.java364
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/QueryWorkItem.java85
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/ResubscribeParams.java78
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/ResubscribeRequestWorkItem.java66
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/SubscribableAgent.java70
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/SubscribeRequestWorkItem.java69
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/Subscription.java280
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/SubscriptionParams.java102
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/UnsubscribeRequestWorkItem.java65
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/QmfData.pngbin0 -> 3876 bytes
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/QmfEventListenerModel.pngbin0 -> 5412 bytes
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/QmfQuery.pngbin0 -> 8444 bytes
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/Schema.pngbin0 -> 4911 bytes
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/Subscriptions.pngbin0 -> 3365 bytes
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/WorkQueueEventModel.pngbin0 -> 7521 bytes
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/AMQPMessage.java309
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BlockingNotifier.java65
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanEquals.java79
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanExists.java72
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanExpression.java221
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanFalse.java60
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanGreaterEqual.java89
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanGreaterThan.java89
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanLessEqual.java89
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanLessThan.java89
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanNotEquals.java79
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanRegexMatch.java95
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanTrue.java60
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/Expression.java77
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/Handle.java107
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/LogicalAnd.java62
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/LogicalExpression.java64
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/LogicalNot.java62
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/LogicalOr.java62
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/Notifier.java67
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/NotifierWrapper.java57
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/NullQmfEventListener.java43
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/ObjectId.java159
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfCallback.java33
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfData.java443
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfDescribed.java84
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfEvent.java224
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfEventListener.java51
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfException.java42
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfManaged.java83
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfQuery.java343
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfQueryTarget.java36
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfType.java39
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaClass.java135
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaClassId.java202
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaEventClass.java266
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaMethod.java275
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaObjectClass.java378
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaProperty.java364
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/WorkItem.java400
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/WorkQueue.java106
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/Console.pngbin0 -> 6234 bytes
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/QmfData.pngbin0 -> 3876 bytes
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/QmfEventListenerModel.pngbin0 -> 5412 bytes
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/QmfQuery.pngbin0 -> 8444 bytes
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/Schema.pngbin0 -> 4911 bytes
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/Subscriptions.pngbin0 -> 5382 bytes
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/WorkItem.pngbin0 -> 9007 bytes
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/WorkQueueEventModel.pngbin0 -> 7521 bytes
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/Agent.java482
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentAccessWorkItem.java80
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentAddedWorkItem.java49
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentDeletedWorkItem.java50
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentHeartbeatWorkItem.java48
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentProxy.java85
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentRestartedWorkItem.java46
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/Console.java2237
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/EventReceivedWorkItem.java63
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/MethodResponseWorkItem.java65
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/MethodResult.java151
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/ObjectUpdateWorkItem.java65
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/QmfConsoleData.java277
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscribeIndication.java66
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscribeParams.java130
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscribeResponseWorkItem.java80
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscriptionIndicationWorkItem.java62
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscriptionManager.java255
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/doc-files/Console.pngbin0 -> 6234 bytes
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/doc-files/QmfEventListenerModel.pngbin0 -> 5412 bytes
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/doc-files/Subscriptions.pngbin0 -> 2789 bytes
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/doc-files/WorkQueueEventModel.pngbin0 -> 7521 bytes
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/util/ConnectionHelper.java862
-rw-r--r--qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/util/GetOpt.java209
92 files changed, 14112 insertions, 0 deletions
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/Agent.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/Agent.java
new file mode 100644
index 0000000000..80acf93e55
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/Agent.java
@@ -0,0 +1,1465 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.agent;
+
+// JMS Imports
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.MapMessage;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.MessageListener;
+import javax.jms.Session;
+
+// Simple Logging Facade 4 Java
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+// Misc Imports
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.AMQPMessage;
+import org.apache.qpid.qmf2.common.Handle;
+import org.apache.qpid.qmf2.common.Notifier;
+import org.apache.qpid.qmf2.common.NotifierWrapper;
+import org.apache.qpid.qmf2.common.NullQmfEventListener;
+import org.apache.qpid.qmf2.common.ObjectId;
+import org.apache.qpid.qmf2.common.QmfCallback;
+import org.apache.qpid.qmf2.common.QmfData;
+import org.apache.qpid.qmf2.common.QmfEvent;
+import org.apache.qpid.qmf2.common.QmfEventListener;
+import org.apache.qpid.qmf2.common.QmfException;
+import org.apache.qpid.qmf2.common.QmfQuery;
+import org.apache.qpid.qmf2.common.QmfQueryTarget;
+import org.apache.qpid.qmf2.common.SchemaClass;
+import org.apache.qpid.qmf2.common.SchemaClassId;
+import org.apache.qpid.qmf2.common.SchemaEventClass;
+import org.apache.qpid.qmf2.common.SchemaObjectClass;
+import org.apache.qpid.qmf2.common.WorkItem;
+import org.apache.qpid.qmf2.common.WorkQueue;
+
+/**
+ * A QMF agent component is represented by a instance of the Agent class. This class is the topmost object
+ * of the Agent application's object model. Associated with a particular agent are:
+ * <pre>
+ * * The set of objects managed by that agent
+ * * The set of schema that describes the structured objects owned by the agent
+ * * A collection of Consoles that are interfacing with the agent
+ * </pre>
+ * The Agent class communicates with the application using the same work-queue model as the Console.
+ * The agent maintains a work-queue of pending requests. Each pending request is associated with a handle.
+ * When the application is done servicing the work request, it passes the response to the agent along with
+ * the handle associated with the originating request.
+ * <p>
+ * The base class for the Agent object is the Agent class. This base class represents a single agent
+ * implementing internal store.
+ *
+ * <h3>Subscriptions</h3>
+ * This implementation of the QMF2 API has full support for QMF2 Subscriptions.
+ * <p>
+ * The diagram below shows the relationship between the Agent, the Subscription and SubscribableAgent interface.
+ * <p>
+ * <img alt="" src="doc-files/Subscriptions.png">
+ * <p>
+ * <h3>Receiving Asynchronous Notifications</h3>
+ * This implementation of the QMF2 Agent actually supports two independent APIs to enable clients to receive
+ * Asynchronous notifications.
+ * <p>
+ * A QmfEventListener object is used to receive asynchronously delivered WorkItems.
+ * <p>
+ * This provides an alternative (simpler) API to the official QMF2 WorkQueue API that some (including the Author)
+ * may prefer over the official API.
+ * <p>
+ * The following diagram illustrates the QmfEventListener Event model.
+ * <p>
+ * Notes
+ * <ol>
+ * <li>This is provided as an alternative to the official QMF2 WorkQueue and Notifier Event model.</li>
+ * <li>Agent and Console methods are sufficiently thread safe that it is possible to call them from a callback fired
+ * from the onEvent() method that may have been called from the JMS MessageListener. Internally the synchronous
+ * and asynchronous calls are processed on different JMS Sessions to facilitate this</li>
+ * </ol>
+ * <p>
+ * <img alt="" src="doc-files/QmfEventListenerModel.png">
+ * <p>
+ * The QMF2 API has a work-queue Callback approach. All asynchronous events are represented by a WorkItem object.
+ * When a QMF event occurs it is translated into a WorkItem object and placed in a FIFO queue. It is left to the
+ * application to drain this queue as needed.
+ * <p>
+ * This new API does require the application to provide a single callback. The callback is used to notify the
+ * application that WorkItem object(s) are pending on the work queue. This callback is invoked by QMF when one or
+ * more new WorkItem objects are added to the queue. To avoid any potential threading issues, the application is
+ * not allowed to call any QMF API from within the context of the callback. The purpose of the callback is to
+ * notify the application to schedule itself to drain the work queue at the next available opportunity.
+ * <p>
+ * For example, a console application may be designed using a select() loop. The application waits in the select()
+ * for any of a number of different descriptors to become ready. In this case, the callback could be written to
+ * simply make one of the descriptors ready, and then return. This would cause the application to exit the wait state,
+ * and start processing pending events.
+ * <p>
+ * The callback is represented by the Notifier virtual base class. This base class contains a single method. An
+ * application derives a custom notification handler from this class, and makes it available to the Console or Agent object.
+ * <p>
+ * The following diagram illustrates the Notifier and WorkQueue QMF2 API Event model.
+ * <p>
+ * Notes
+ * <ol>
+ * <li>There is an alternative (simpler but not officially QMF2) API based on implementing the QmfEventListener as
+ * described previously.</li>
+ * <li>BlockingNotifier is not part of QMF2 either but is how most people would probably write a Notifier.</li>
+ * <li>It's generally not necessary to use a Notifier as the Console provides a blocking getNextWorkitem() method.</li>
+ * </ol>
+ * <p>
+ * <img alt="" src="doc-files/WorkQueueEventModel.png">
+ * <h3>Potential Issues with Qpid versions earlier than 0.12</h3>
+ * Note 1: This uses QMF2 so requires that the "--mgmt-qmf2 yes" option is applied to the broker (this is the default
+ * from Qpid 0.10).
+ * <p>
+ * Note 2: In order to use QMF2 the app-id field needs to be set. There appears to be no way to set the AMQP 0-10
+ * specific app-id field on a message which the brokers QMFv2 implementation currently requires.
+ * <p>
+ * Gordon Sim has put together a patch for org.apache.qpid.client.message.AMQMessageDelegate_0_10
+ * Found in client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate_0_10.java
+ * <pre>
+ * public void setStringProperty(String propertyName, String value) throws JMSException
+ * {
+ * checkPropertyName(propertyName);
+ * checkWritableProperties();
+ * setApplicationHeader(propertyName, value);
+ *
+ * if ("x-amqp-0-10.app-id".equals(propertyName))
+ * {
+ * _messageProps.setAppId(value.getBytes());
+ * }
+ * }
+ * </pre>
+ * This gets things working.
+ * <p>
+ * A jira <a href=https://issues.apache.org/jira/browse/QPID-3302>QPID-3302</a> has been raised.
+ * This is fixed in Qpid 0.12.
+ *
+ * @author Fraser Adams
+ */
+public class Agent extends QmfData implements MessageListener, SubscribableAgent
+{
+ private static final Logger _log = LoggerFactory.getLogger(Agent.class);
+
+ /**
+ * This TimerTask causes the Agent to sent a Hearbeat when it gets scheduled
+ */
+ private final class Heartbeat extends TimerTask
+ {
+ public void run()
+ {
+ try
+ {
+ String vendorKey = _vendor.replace(".", "_");
+ String productKey = _product.replace(".", "_");
+ String instanceKey = _instance.replace(".", "_");
+ String subject = "agent.ind.heartbeat." + vendorKey + "." + productKey + "." + instanceKey;
+
+ MapMessage response = _syncSession.createMapMessage();
+ response.setStringProperty("x-amqp-0-10.app-id", "qmf2");
+ response.setStringProperty("method", "indication");
+ response.setStringProperty("qmf.opcode", "_agent_heartbeat_indication");
+ response.setStringProperty("qmf.agent", _name);
+ response.setStringProperty("qpid.subject", subject);
+ setValue("_timestamp", System.currentTimeMillis()*1000000l);
+ response.setObject("_values", mapEncode());
+
+ // Send heartbeat messages with a Time To Live (in msecs) set to two times the _heartbeatInterval
+ // to prevent stale heartbeats from getting to the consoles.
+ _producer.send(_topicAddress, response, Message.DEFAULT_DELIVERY_MODE,
+ Message.DEFAULT_PRIORITY, _heartbeatInterval*2000);
+ }
+ catch (JMSException jmse)
+ {
+ _log.info("JMSException {} caught in sendHeartbeat()", jmse.getMessage());
+ }
+
+ // Reap any QmfAgentData Objects that have been marked as Deleted
+ // Use the iterator approach rather than foreach as we may want to call iterator.remove() to zap an entry
+ Iterator<QmfAgentData> i = _objectIndex.values().iterator();
+ while (i.hasNext())
+ {
+ QmfAgentData object = i.next();
+ if (object.isDeleted())
+ {
+ _log.debug("Removing deleted QmfAgentData Object from store");
+ i.remove();
+ }
+ }
+ }
+ }
+
+ // Attributes
+ // ********************************************************************************************************
+
+ /**
+ * The _eventListener may be a real application QmfEventListener, a NullQmfEventListener or an application
+ * Notifier wrapped in a QmfEventListener. In all cases the Agent may call _eventListener.onEvent() at
+ * various places to pass a WorkItem to an asynchronous receiver.
+ */
+ private QmfEventListener _eventListener;
+
+ /**
+ * _schemaCache holds references to the Schema objects for easy lookup so we can return the info to
+ * the Console if necessary
+ */
+ private Map<SchemaClassId, SchemaClass> _schemaCache = new ConcurrentHashMap<SchemaClassId, SchemaClass>();
+
+ /**
+ * _objectIndex is the global index of QmfAgentData objects registered with this Agent.
+ * The capacity of 100 is pretty arbitrary but the default of 16 seems too low for most Agents.
+ */
+ private Map<ObjectId, QmfAgentData> _objectIndex = new ConcurrentHashMap<ObjectId, QmfAgentData>(100);
+
+ /**
+ * This Map is used to look up Subscriptions by SubscriptionId
+ */
+ private Map<String, Subscription> _subscriptions = new ConcurrentHashMap<String, Subscription>();
+
+ /**
+ * Used to implement a thread safe queue of WorkItem objects used to implement the Notifier API
+ */
+ private WorkQueue _workQueue = new WorkQueue();
+
+ /**
+ * If a name is supplied, it must be unique across all attached to the AMQP bus under the given domain.
+ * The name must comprise three parts separated by colons: <vendor>:<product>[:<instance>], where the
+ * vendor is the Agent vendor name, the product is the Agent product itself and the instance is a UUID
+ * representing the running instance. If the instance is not supplied then a random UUID will be generated
+ */
+ private String _name;
+
+ /**
+ * The Agent vendor name
+ */
+ private String _vendor;
+
+ /**
+ * The Agent product name
+ */
+ private String _product;
+
+ /**
+ * A UUID representing the running instance
+ */
+ private String _instance = UUID.randomUUID().toString();
+
+ /**
+ * The epoch may be used to maintain a count of the number of times an agent has been restarted. By
+ * incrementing this value and keeping a constant instance value an Agent can indicate to a client
+ * that it is a persistent Agent and has been restarted. The Broker Management Agent behaves in this way.
+ */
+ private int _epoch = 1;
+
+ /**
+ * The interval that this Agent waits between sending out hearbeat messages
+ */
+ private int _heartbeatInterval = 30;
+
+ /**
+ * The domain string is used to construct the name of the AMQP exchange to which the component's
+ * name string will be bound. If not supplied, the value of the domain defaults to "default". Both
+ * Agents and Components must belong to the same domain in order to communicate.
+ */
+ private String _domain;
+
+ /**
+ * This timer is used to schedule periodic events such as sending Heartbeats and subscription updates
+ */
+ private Timer _timer;
+
+ /**
+ * Various JMS related fields
+ */
+ private Connection _connection = null;
+ private Session _asyncSession;
+ private Session _syncSession;
+ private MessageConsumer _locateConsumer;
+ private MessageConsumer _mainConsumer;
+ // _aliasConsumer is used for the alias address if the Agent is a broker Agent (used in Java Broker QMF plugin)
+ private MessageConsumer _aliasConsumer;
+
+ private MessageProducer _producer;
+
+ private String _quotedDirectBase;
+ private Destination _directAddress;
+
+ private String _quotedTopicBase;
+ private Destination _topicAddress;
+
+ // private implementation methods
+ // ********************************************************************************************************
+
+
+ /**
+ * There's some slight "hackery" below. The Agent clearly needs to respond
+ * to requests and quite possibly using the JMS replyTo is the correct thing
+ * to do, however in older versions of Qpid invoking send() on the replyTo
+ * causes spurious exchangeDeclares to occur and the caching of replyTo wasn't
+ * as good as it might be. To get around this the Agent uses exchange name
+ * as the core address and sets the Message "qpid.subject" property with an
+ * appropriate Routing Key.
+ * @param handle the reply handle that contains the replyTo Address.
+ * @param message the JMS Message to be sent.
+ */
+ private final void sendResponse(final Handle handle, final Message message) throws JMSException
+ {
+ // Just in case the replyTo issues still exist check if the replyTo starts
+ // with qmf.default.topic or qmf.default.direct and if so send to the
+ // main topic or direct Destinations, if not fall back to using the real
+ // replyTo Destination. TODO check if original replyTo issue still exists.
+ String replyTo = handle.getReplyTo().toString();
+ if (replyTo.startsWith(_quotedTopicBase))
+ {
+ _producer.send(_topicAddress, message);
+ }
+ else if (replyTo.startsWith(_quotedDirectBase))
+ {
+ _producer.send(_directAddress, message);
+ }
+ else
+ {
+ _producer.send(handle.getReplyTo(), message);
+ }
+ }
+
+ /**
+ * Send an _agent_locate_response back to the Console that requested the locate.
+ * @param handle the reply handle that contains the replyTo Address.
+ */
+ private final void handleLocateRequest(final Handle handle)
+ {
+ try
+ {
+ MapMessage response = _syncSession.createMapMessage();
+ response.setStringProperty("x-amqp-0-10.app-id", "qmf2");
+ response.setStringProperty("method", "indication");
+ response.setStringProperty("qmf.opcode", "_agent_locate_response");
+ response.setStringProperty("qmf.agent", _name);
+ response.setStringProperty("qpid.subject", handle.getRoutingKey());
+ setValue("_timestamp", System.currentTimeMillis()*1000000l);
+ response.setObject("_values", mapEncode());
+ sendResponse(handle, response);
+ }
+ catch (JMSException jmse)
+ {
+ _log.info("JMSException {} caught in handleLocateRequest()", jmse.getMessage());
+ }
+ }
+
+ /**
+ * Handle the query request and send the response back to the Console.
+ * @param handle the reply handle that contains the replyTo Address.
+ * @param query the inbound query from the Console.
+ */
+ @SuppressWarnings("unchecked")
+ private final void handleQueryRequest(final Handle handle, final QmfQuery query)
+ {
+ QmfQueryTarget target = query.getTarget();
+
+ if (target == QmfQueryTarget.SCHEMA_ID)
+ {
+ List<Map> results = new ArrayList<Map>(_schemaCache.size());
+ // Look up all SchemaClassId objects
+ for (SchemaClassId classId : _schemaCache.keySet())
+ {
+ results.add(classId.mapEncode());
+ }
+ queryResponse(handle, results, "_schema_id"); // Send the response back to the Console.
+ }
+ else if (target == QmfQueryTarget.SCHEMA)
+ {
+ List<Map> results = new ArrayList<Map>(1);
+ // Look up a SchemaClass object by the SchemaClassId obtained from the query
+ SchemaClassId classId = query.getSchemaClassId();
+ SchemaClass schema = _schemaCache.get(classId);
+ if (schema != null)
+ {
+ results.add(schema.mapEncode());
+ }
+ queryResponse(handle, results, "_schema"); // Send the response back to the Console.
+ }
+ else if (target == QmfQueryTarget.OBJECT_ID)
+ {
+ List<Map> results = new ArrayList<Map>(_objectIndex.size());
+ // Look up all ObjectId objects
+ for (ObjectId objectId : _objectIndex.keySet())
+ {
+ results.add(objectId.mapEncode());
+ }
+ queryResponse(handle, results, "_object_id"); // Send the response back to the Console.
+ }
+ else if (target == QmfQueryTarget.OBJECT)
+ {
+ // If this is implementing the AgentExternal model we pass the QmfQuery on in a QueryWorkItem
+ if (this instanceof AgentExternal)
+ {
+ _eventListener.onEvent(new QueryWorkItem(handle, query));
+ return;
+ }
+ else
+ { // If not implementing the AgentExternal model we handle the Query ourself.
+ //qmfContentType = "_data";
+ if (query.getObjectId() != null)
+ {
+ List<Map> results = new ArrayList<Map>(1);
+ // Look up a QmfAgentData object by the ObjectId obtained from the query
+ ObjectId objectId = query.getObjectId();
+ QmfAgentData object = _objectIndex.get(objectId);
+ if (object != null && !object.isDeleted())
+ {
+ results.add(object.mapEncode());
+ }
+ queryResponse(handle, results, "_data"); // Send the response back to the Console.
+ }
+ else
+ {
+ // Look up QmfAgentData objects by the SchemaClassId obtained from the query
+ // This is implemented by a linear search and allows searches with only the className specified.
+ // Linear searches clearly don't scale brilliantly, but the number of QmfAgentData objects managed
+ // by an Agent is generally fairly small, so it should be OK. Note that this is the same approach
+ // taken by the C++ broker ManagementAgent, so if it's a problem here........
+
+ // N.B. the results list declared here is a generic List of Objects. We *must* only pass a List of
+ // Map to queryResponse(), but conversely if the response items are sortable we need to sort them
+ // before doing mapEncode(). Unfortunately we don't know if the items are sortable a priori so
+ // we either add a Map or we add a QmfAgentData, then sort then mapEncode() each item. I'm not
+ // sure of a more elegant way to do this without creating two lists, which might not be so bad
+ // but we don't know the size of the list a priori either.
+ List results = new ArrayList(_objectIndex.size());
+ // It's unlikely that evaluating this query will return a mixture of sortable and notSortable
+ // QmfAgentData objects, but it's best to check if that has occurred as accidentally passing a
+ // List of QmfAgentData instead of a List of Map to queryResponse() will break things.
+ boolean sortable = false;
+ boolean notSortable = false;
+ for (QmfAgentData object : _objectIndex.values())
+ {
+ if (!object.isDeleted() && query.evaluate(object))
+ {
+ if (object.isSortable())
+ { // If QmfAgentData is marked sortable we add the QmfAgentData object to the List
+ // so we can sort first before mapEncoding.
+ results.add(object);
+ sortable = true;
+ }
+ else
+ { // If QmfAgentData is not marked sortable we mapEncode immediately and add the Map to List.
+ results.add(object.mapEncode());
+ notSortable = true;
+ }
+ }
+ }
+
+ // If both flags have been set something has gone a bit weird, so we log an error and clear the
+ // results List to avoid sending unconvertable data. Hopefully this condition should never occur.
+ if (sortable && notSortable)
+ {
+ _log.info("Query resulted in inconsistent mixture of sortable and non-sortable data.");
+ results.clear();
+ }
+ else if (sortable)
+ {
+ Collections.sort(results);
+ int length = results.size();
+ for (int i = 0; i < length; i++)
+ {
+ QmfAgentData object = (QmfAgentData)results.get(i);
+ results.set(i, object.mapEncode());
+ }
+ }
+ queryResponse(handle, results, "_data"); // Send the response back to the Console.
+ }
+ }
+ }
+ else
+ {
+ raiseException(handle, "Query for _what => '" + target + "' not supported");
+ return;
+ }
+ } // end of handleQueryRequest()
+
+ /**
+ * Return a QmfAgentData from the internal Object store given its ObjectId.
+ * N.B. This method isn't part of the *official* QMF2 public API, however it is pretty useful and probably
+ * should be (as should evaluateQuery()). Given that wi.getType() == METHOD_CALL primarily uses the pattern:
+ * <pre>
+ * MethodCallWorkItem item = (MethodCallWorkItem)wi;
+ * MethodCallParams methodCallParams = item.getMethodCallParams();
+ * String methodName = methodCallParams.getName();
+ * ObjectId objectId = methodCallParams.getObjectId();
+ * </pre>
+ * to identify the method name and Object instance it seems odd not to have a public API method to look up
+ * said Object by ObjectId. Clearly a separate Map could be maintained in client code but that seems pointless.
+ */
+ public final QmfAgentData getObject(ObjectId objectId)
+ {
+ return _objectIndex.get(objectId);
+ }
+
+ /**
+ * Send an exception back to the Console.
+ * @param handle the reply handle that contains the replyTo Address.
+ * @param message the exception message.
+ */
+ public final void raiseException(final Handle handle, final String message)
+ {
+ try
+ {
+ MapMessage response = _syncSession.createMapMessage();
+ response.setJMSCorrelationID(handle.getCorrelationId());
+ response.setStringProperty("x-amqp-0-10.app-id", "qmf2");
+ response.setStringProperty("method", "response");
+ response.setStringProperty("qmf.opcode", "_exception");
+ response.setStringProperty("qmf.agent", _name);
+ response.setStringProperty("qpid.subject", handle.getRoutingKey());
+
+ QmfData exception = new QmfData();
+ exception.setValue("error_text", message);
+ response.setObject("_values", exception.mapEncode());
+ sendResponse(handle, response);
+ }
+ catch (JMSException jmse)
+ {
+ _log.info("JMSException {} caught in handleLocateRequest()", jmse.getMessage());
+ }
+ }
+
+ // methods implementing SubscribableAgent interface
+ // ********************************************************************************************************
+
+ /**
+ * Send a list of updated subscribed data to the Console.
+ *
+ * @param handle the console reply handle.
+ * @param results a list of subscribed data in Map encoded form.
+ */
+ public final void sendSubscriptionIndicate(final Handle handle, final List<Map> results)
+ {
+ try
+ {
+ Message response = AMQPMessage.createListMessage(_syncSession);
+ response.setJMSCorrelationID(handle.getCorrelationId());
+ response.setStringProperty("x-amqp-0-10.app-id", "qmf2");
+ response.setStringProperty("method", "indication");
+ response.setStringProperty("qmf.opcode", "_data_indication");
+ response.setStringProperty("qmf.content", "_data");
+ response.setStringProperty("qmf.agent", _name);
+ response.setStringProperty("qpid.subject", handle.getRoutingKey());
+ AMQPMessage.setList(response, results);
+ sendResponse(handle, response);
+ }
+ catch (JMSException jmse)
+ {
+ _log.info("JMSException {} caught in sendSubscriptionIndicate()", jmse.getMessage());
+ }
+ }
+
+ /**
+ * This method evaluates a QmfQuery over the Agent's data on behalf of a Subscription.
+ *
+ * @param query the QmfQuery that the Subscription wants to be evaluated over the Agent's data.
+ * @return a List of QmfAgentData objects that match the specified QmfQuery.
+ */
+ public final List<QmfAgentData> evaluateQuery(final QmfQuery query)
+ {
+ List<QmfAgentData> results = new ArrayList<QmfAgentData>(_objectIndex.size());
+ if (query.getTarget() == QmfQueryTarget.OBJECT)
+ { // Note that we don't include objects marked as deleted in the results here, because if an object gets
+ // destroyed we asynchronously publish its new state to subscribers, see QmfAgentData.destroy() method.
+ if (query.getObjectId() != null)
+ {
+ // Look up a QmfAgentData object by the ObjectId obtained from the query
+ ObjectId objectId = query.getObjectId();
+ QmfAgentData object = _objectIndex.get(objectId);
+ if (object != null && !object.isDeleted())
+ {
+ results.add(object);
+ }
+ }
+ else
+ {
+ // Look up QmfAgentData objects evaluating the query
+ for (QmfAgentData object : _objectIndex.values())
+ {
+ if (!object.isDeleted() && query.evaluate(object))
+ {
+ results.add(object);
+ }
+ }
+ }
+ }
+ return results;
+ }
+
+ /**
+ * This method is called by the Subscription to tell the SubscribableAgent that the Subscription has been cancelled.
+ *
+ * @param subscription the Subscription that has been cancelled and is requesting removal.
+ */
+ public final void removeSubscription(final Subscription subscription)
+ {
+ _subscriptions.remove(subscription.getSubscriptionId());
+ }
+
+ // MessageListener
+ // ********************************************************************************************************
+
+ /**
+ * MessageListener for QMF2 Console requests.
+ *
+ * @param message the JMS Message passed to the listener.
+ */
+ public final void onMessage(final Message message)
+ {
+ try
+ {
+ String agentName = QmfData.getString(message.getObjectProperty("qmf.agent"));
+ String content = QmfData.getString(message.getObjectProperty("qmf.content"));
+ String opcode = QmfData.getString(message.getObjectProperty("qmf.opcode"));
+ //String routingKey = ((javax.jms.Topic)message.getJMSDestination()).getTopicName();
+ //String contentType = ((org.apache.qpid.client.message.AbstractJMSMessage)message).getContentType();
+
+//System.out.println();
+//System.out.println("agentName = " + agentName);
+//System.out.println("content = " + content);
+//System.out.println("opcode = " + opcode);
+//System.out.println("routingKey = " + routingKey);
+//System.out.println("contentType = " + contentType);
+
+ Handle handle = new Handle(message.getJMSCorrelationID(), message.getJMSReplyTo());
+
+ if (opcode.equals("_agent_locate_request"))
+ {
+ handleLocateRequest(handle);
+ }
+ else if (opcode.equals("_method_request"))
+ {
+ if (AMQPMessage.isAMQPMap(message))
+ {
+ _eventListener.onEvent
+ (
+ new MethodCallWorkItem(handle, new MethodCallParams(AMQPMessage.getMap(message)))
+ );
+ }
+ else
+ {
+ _log.info("onMessage() Received Method Request message in incorrect format");
+ }
+ }
+ else if (opcode.equals("_query_request"))
+ {
+ if (AMQPMessage.isAMQPMap(message))
+ {
+ try
+ {
+ QmfQuery query = new QmfQuery(AMQPMessage.getMap(message));
+ handleQueryRequest(handle, query);
+ }
+ catch (QmfException qmfe)
+ {
+ raiseException(handle, "Query Request failed, invalid Query: " + qmfe.getMessage());
+ }
+ }
+ else
+ {
+ _log.info("onMessage() Received Query Request message in incorrect format");
+ }
+ }
+ else if (opcode.equals("_subscribe_request"))
+ {
+ if (AMQPMessage.isAMQPMap(message))
+ {
+ try
+ {
+ SubscriptionParams subscriptionParams =
+ new SubscriptionParams(handle, AMQPMessage.getMap(message));
+ if (this instanceof AgentExternal)
+ {
+ _eventListener.onEvent(new SubscribeRequestWorkItem(handle, subscriptionParams));
+ }
+ else
+ {
+ Subscription subscription = new Subscription(this, subscriptionParams);
+ String subscriptionId = subscription.getSubscriptionId();
+ _subscriptions.put(subscriptionId, subscription);
+ _timer.schedule(subscription, 0, subscriptionParams.getPublishInterval());
+ subscriptionResponse(handle, subscription.getConsoleHandle(), subscriptionId,
+ subscription.getDuration(), subscription.getInterval(), null);
+ }
+ }
+ catch (QmfException qmfe)
+ {
+ raiseException(handle, "Subscribe Request failed, invalid Query: " + qmfe.getMessage());
+ }
+ }
+ else
+ {
+ _log.info("onMessage() Received Subscribe Request message in incorrect format");
+ }
+ }
+ else if (opcode.equals("_subscribe_refresh_indication"))
+ {
+ if (AMQPMessage.isAMQPMap(message))
+ {
+ ResubscribeParams resubscribeParams = new ResubscribeParams(AMQPMessage.getMap(message));
+ if (this instanceof AgentExternal)
+ {
+ _eventListener.onEvent(new ResubscribeRequestWorkItem(handle, resubscribeParams));
+ }
+ else
+ {
+ String subscriptionId = resubscribeParams.getSubscriptionId();
+ Subscription subscription = _subscriptions.get(subscriptionId);
+ if (subscription != null)
+ {
+ subscription.refresh(resubscribeParams);
+ subscriptionResponse(handle,
+ subscription.getConsoleHandle(), subscription.getSubscriptionId(),
+ subscription.getDuration(), subscription.getInterval(), null);
+ }
+ }
+ }
+ else
+ {
+ _log.info("onMessage() Received Resubscribe Request message in incorrect format");
+ }
+ }
+ else if (opcode.equals("_subscribe_cancel_indication"))
+ {
+ if (AMQPMessage.isAMQPMap(message))
+ {
+ QmfData qmfSubscribe = new QmfData(AMQPMessage.getMap(message));
+ String subscriptionId = qmfSubscribe.getStringValue("_subscription_id");
+ if (this instanceof AgentExternal)
+ {
+ _eventListener.onEvent(new UnsubscribeRequestWorkItem(subscriptionId));
+ }
+ else
+ {
+ Subscription subscription = _subscriptions.get(subscriptionId);
+ if (subscription != null)
+ {
+ subscription.cancel();
+ }
+ }
+ }
+ else
+ {
+ _log.info("onMessage() Received Subscribe Cancel Request message in incorrect format");
+ }
+ }
+ }
+ catch (JMSException jmse)
+ {
+ _log.info("JMSException {} caught in onMessage()", jmse.getMessage());
+ }
+ } // end of onMessage()
+
+ // QMF API Methods
+ // ********************************************************************************************************
+
+ /**
+ * Constructor that provides defaults for name, domain and heartbeat interval and takes a Notifier/Listener.
+ *
+ * @param notifier this may be either a QMF2 API Notifier object OR a QMFEventListener.
+ * <p>
+ * The latter is an alternative API that avoids the need for an explicit Notifier thread to be created the
+ * EventListener is called from the JMS MessageListener thread.
+ * <p>
+ * This API may be simpler and more convenient than the QMF2 Notifier API for many applications.
+ */
+ public Agent(final QmfCallback notifier) throws QmfException
+ {
+ this(null, null, notifier, 30);
+ }
+
+ /**
+ * Constructor that provides defaults for name and domain and takes a Notifier/Listener
+ *
+ * @param notifier this may be either a QMF2 API Notifier object OR a QMFEventListener.
+ * <p>
+ * The latter is an alternative API that avoids the need for an explicit Notifier thread to be created the
+ * EventListener is called from the JMS MessageListener thread.
+ * <p>
+ * This API may be simpler and more convenient than the QMF2 Notifier API for many applications.
+ * @param interval is the heartbeat interval in seconds.
+ */
+ public Agent(final QmfCallback notifier, final int interval) throws QmfException
+ {
+ this(null, null, notifier, interval);
+ }
+
+ /**
+ * Main constructor, creates a Agent, but does NOT start it, that requires us to do setConnection()
+ *
+ * @param name If a name is supplied, it must be unique across all agents attached to the AMQP bus under the
+ * given domain.
+ * <p>
+ * The name must comprise three parts separated by colons: <pre>&lt;vendor&gt;:&lt;product&gt;[:&lt;instance&gt;]</pre>
+ * where the vendor is the Agent vendor name, the product is the Agent product itself and the instance is a UUID
+ * representing the running instance.
+ * <p>
+ * If the instance is not supplied then a random UUID will be generated.
+ * @param domain the QMF "domain".
+ * <p>
+ * A QMF address is composed of two parts - an optional domain string, and a mandatory name string
+ * <pre>"qmf.&lt;domain-string&gt;.direct/&lt;name-string&gt;"</pre>
+ * The domain string is used to construct the name of the AMQP exchange to which the component's name string will
+ * be bound. If not supplied, the value of the domain defaults to "default".
+ * <p>
+ * Both Agents and Consoles must belong to the same domain in order to communicate.
+ * @param notifier this may be either a QMF2 API Notifier object OR a QMFEventListener.
+ * <p>
+ * The latter is an alternative API that avoids the need for an explicit Notifier thread to be created the
+ * EventListener is called from the JMS MessageListener thread.
+ * <p>
+ * This API may be simpler and more convenient than the QMF2 Notifier API for many applications.
+ * @param interval is the heartbeat interval in seconds.
+ */
+ public Agent(final String name, final String domain,
+ final QmfCallback notifier, final int interval) throws QmfException
+ {
+ if (name != null)
+ {
+ String[] split = name.split(":");
+ if (split.length < 2 || split.length > 3)
+ {
+ throw new QmfException("Agent name must be in the format <vendor>:<product>[:<instance>]");
+ }
+
+ _vendor = split[0];
+ _product = split[1];
+
+ if (split.length == 3)
+ {
+ _instance = split[2];
+ }
+
+ _name = _vendor + ":" + _product + ":" + _instance;
+ }
+
+ _domain = (domain == null) ? "default" : domain;
+
+ if (notifier == null)
+ {
+ _eventListener = new NullQmfEventListener();
+ }
+ else if (notifier instanceof Notifier)
+ {
+ _eventListener = new NotifierWrapper((Notifier)notifier, _workQueue);
+ }
+ else if (notifier instanceof QmfEventListener)
+ {
+ _eventListener = (QmfEventListener)notifier;
+ }
+ else
+ {
+ throw new QmfException("QmfCallback listener must be either a Notifier or QmfEventListener");
+ }
+
+ if (interval > 0)
+ {
+ _heartbeatInterval = interval;
+ }
+ }
+
+ /**
+ * Returns the name string of the agent.
+ * @return the name string of the agent.
+ */
+ public final String getName()
+ {
+ return _name;
+ }
+
+ /**
+ * Set the vendor String, must be called before setConnection().
+ * @param vendor the vendor name.
+ */
+ public final void setVendor(final String vendor)
+ {
+ _vendor = vendor;
+ _name = _vendor + ":" + _product + ":" + _instance;
+ }
+
+ /**
+ * Set the product String, must be called before setConnection().
+ * @param product the product name.
+ */
+ public final void setProduct(final String product)
+ {
+ _product = product;
+ _name = _vendor + ":" + _product + ":" + _instance;
+ }
+
+ /**
+ * Set the instance String, must be called before setConnection().
+ * @param instance the instance value.
+ */
+ public final void setInstance(final String instance)
+ {
+ _instance = instance;
+ _name = _vendor + ":" + _product + ":" + _instance;
+ }
+
+ /**
+ * Returns the current epoch value.
+ * @return the current epoch value.
+ */
+ public final int getEpoch()
+ {
+ return _epoch;
+ }
+
+ /**
+ * Set the new epoch value.
+ * @param epoch the new epoch value.
+ */
+ public final void setEpoch(final int epoch)
+ {
+ _epoch = epoch;
+ }
+
+ /**
+ * Releases Agent's resources.
+ */
+ public final void destroy()
+ {
+ try
+ {
+ if (_connection != null)
+ {
+ removeConnection(_connection);
+ }
+ }
+ catch (QmfException qmfe)
+ {
+ // Ignore as we've already tested for _connection != null this should never occur
+ }
+ }
+
+ /**
+ * Connect the Agent to the AMQP cloud.
+ *
+ * @param conn a javax.jms.Connection.
+ */
+ public final void setConnection(final Connection conn) throws QmfException
+ {
+ setConnection(conn, "");
+ }
+
+ /**
+ * Connect the Agent to the AMQP cloud.
+ * <p>
+ * This is an extension to the standard QMF2 API allowing the user to specify address options in order to allow
+ * finer control over the Agent's ingest queue, such as an explicit name, non-default size or durability.
+ *
+ * @param conn a javax.jms.Connection.
+ * @param addressOptions options String giving finer grained control of the receiver queue.
+ * <p>
+ * As an example the following gives the Agent's ingest queue the name test-agent, size = 500000000 and ring policy.
+ * <pre>
+ * " ; {link: {name:'test-agent', x-declare: {arguments: {'qpid.policy_type': ring, 'qpid.max_size': 500000000}}}}"
+ * </pre>
+ */
+ public final void setConnection(final Connection conn, final String addressOptions) throws QmfException
+ {
+ // Make the test and set of _connection synchronized just in case multiple threads attempt to add a _connection
+ // to the same Agent instance at the same time.
+ synchronized(this)
+ {
+ if (_connection != null)
+ {
+ throw new QmfException("Multiple connections per Agent is not supported");
+ }
+ _connection = conn;
+ }
+
+ if (_name == null || _vendor == null || _product == null)
+ {
+ throw new QmfException("The vendor, product or name is not set");
+ }
+
+ setValue("_epoch", _epoch);
+ setValue("_heartbeat_interval", _heartbeatInterval);
+ setValue("_name", _name);
+ setValue("_product", _product);
+ setValue("_vendor", _vendor);
+ setValue("_instance", _instance);
+
+ try
+ {
+ _asyncSession = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ _syncSession = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ // Create a Destination for the QMF direct address, mainly used for request/response
+ String directBase = "qmf." + _domain + ".direct";
+ _quotedDirectBase = "'" + directBase + "'";
+ _directAddress = _syncSession.createQueue(directBase);
+
+ // Create a Destination for the QMF topic address used to broadcast Events & Heartbeats.
+ String topicBase = "qmf." + _domain + ".topic";
+ _quotedTopicBase = "'" + topicBase + "'";
+ _topicAddress = _syncSession.createQueue(topicBase);
+
+ // Create an unidentified MessageProducer for sending to various destinations.
+ _producer = _syncSession.createProducer(null);
+
+ // TODO it should be possible to bind _locateConsumer, _mainConsumer and _aliasConsumer to the
+ // same queue if I can figure out the correct AddressString to use, probably not a big deal though.
+
+ // Set up MessageListener on the Agent Locate Address
+ Destination locateAddress = _asyncSession.createQueue(topicBase + "/console.request.agent_locate");
+ _locateConsumer = _asyncSession.createConsumer(locateAddress);
+ _locateConsumer.setMessageListener(this);
+
+ // Set up MessageListener on the Agent address
+ String address = directBase + "/" + _name + addressOptions;
+ Destination agentAddress = _asyncSession.createQueue(address);
+ _mainConsumer = _asyncSession.createConsumer(agentAddress);
+ _mainConsumer.setMessageListener(this);
+
+ // If the product name has been set to qpidd we create an additional consumer address of
+ // "qmf.default.direct/broker" in addition to the main address so that Consoles can talk to the
+ // broker Agent without needing to do Agent discovery. This is only really needed when the Agent
+ // class has been used to create the QmfManagementAgent for the Java broker QmfManagementPlugin.
+ // It's important to do this as many tools (such as qpid-config) and demo code tend to use the
+ // alias address rather than the discovered address when talking to the broker ManagementAgent.
+ if (_product.equals("qpidd"))
+ {
+ String alias = directBase + "/broker";
+ _log.info("Creating address {} as an alias address for the broker Agent", alias);
+ Destination aliasAddress = _asyncSession.createQueue(alias);
+ _aliasConsumer = _asyncSession.createConsumer(aliasAddress);
+ _aliasConsumer.setMessageListener(this);
+ }
+
+ _connection.start();
+
+ // Schedule a Heartbeat every _heartbeatInterval seconds sending the first one immediately
+ _timer = new Timer(true);
+ _timer.schedule(new Heartbeat(), 0, _heartbeatInterval*1000);
+ }
+ catch (JMSException jmse)
+ {
+ // If we can't create the QMF Destinations there's not much else we can do
+ _log.info("JMSException {} caught in setConnection()", jmse.getMessage());
+ throw new QmfException("Failed to create sessions or destinations " + jmse.getMessage());
+ }
+ } // end of setConnection()
+
+ /**
+ * Remove the AMQP connection from the Agent. Un-does the setConnection() operation.
+ *
+ * @param conn a javax.jms.Connection.
+ */
+ public final void removeConnection(final Connection conn) throws QmfException
+ {
+ if (conn != _connection)
+ {
+ throw new QmfException("Attempt to delete unknown connection");
+ }
+
+ try
+ {
+ _timer.cancel();
+ _connection.close();
+ }
+ catch (JMSException jmse)
+ {
+ throw new QmfException("Failed to remove connection, caught JMSException " + jmse.getMessage());
+ }
+ _connection = null;
+ }
+
+ /**
+ * Register a schema for an object class with the Agent.
+ * <p>
+ * The Agent must have a registered schema for an object class before it can handle objects of that class.
+ *
+ * @param schema the SchemaObjectClass to be registered
+ */
+ public final void registerObjectClass(final SchemaObjectClass schema)
+ {
+ SchemaClassId classId = schema.getClassId();
+ _schemaCache.put(classId, schema);
+ }
+
+ /**
+ * Register a schema for an event class with the Agent.
+ * <p>
+ * The Agent must have a registered schema for an event class before it can handle events of that class.
+ *
+ * @param schema the SchemaEventClass to be registered
+ */
+ public final void registerEventClass(final SchemaEventClass schema)
+ {
+ SchemaClassId classId = schema.getClassId();
+ _schemaCache.put(classId, schema);
+ }
+
+ /**
+ * Cause the agent to raise the given event.
+ *
+ * @param event the QmfEvent to be raised
+ */
+ public final void raiseEvent(final QmfEvent event)
+ {
+ try
+ {
+ String packageKey = event.getSchemaClassId().getPackageName().replace(".", "_");
+ String nameKey = event.getSchemaClassId().getClassName().replace(".", "_");
+ String severity = event.getSeverity();
+ String vendorKey = _vendor.replace(".", "_");
+ String productKey = _product.replace(".", "_");
+ String instanceKey = _instance.replace(".", "_");
+
+ String subject = "agent.ind.event." + packageKey + "." + nameKey + "." + severity + "." + vendorKey + "." +
+ productKey + "." + instanceKey;
+
+ Message response = AMQPMessage.createListMessage(_syncSession);
+ response.setStringProperty("x-amqp-0-10.app-id", "qmf2");
+ response.setStringProperty("method", "indication");
+ response.setStringProperty("qmf.opcode", "_data_indication");
+ response.setStringProperty("qmf.content", "_event");
+ response.setStringProperty("qmf.agent", _name);
+ response.setStringProperty("qpid.subject", subject);
+
+ List<Map> results = new ArrayList<Map>();
+ results.add(event.mapEncode());
+ AMQPMessage.setList(response, results);
+ _producer.send(_topicAddress, response);
+ }
+ catch (JMSException jmse)
+ {
+ _log.info("JMSException {} caught in raiseEvent()", jmse.getMessage());
+ }
+ }
+
+ /**
+ * Passes a reference to an instance of a managed QMF object to the Agent.
+ * <p>
+ * The object's name must uniquely identify this object among all objects known to this Agent.
+ * <p>
+ * This method creates an ObjectId for the QmfAgentData being added, it does this by first checking
+ * the schema.
+ * <p>
+ * If an associated schema exists we look for the set of property names that have been
+ * specified as idNames. If idNames exists we look for their values within the object and use that
+ * to create the objectName. If we can't create a sensible name we use a randomUUID.
+ * @param object the QmfAgentData object to be added
+ */
+ public void addObject(final QmfAgentData object) throws QmfException
+ {
+ // There are some cases where a QmfAgentData Object might have already set its ObjectId, for example where
+ // it may need to have a "well known" ObjectId. This is the case with the Java Broker Management Agent
+ // where tools such as qpid-config might have made assumptions about its ObjectId rather than doing "discovery".
+ ObjectId addr = object.getObjectId();
+ if (addr == null)
+ {
+ SchemaClassId classId = object.getSchemaClassId();
+ SchemaClass schema = _schemaCache.get(classId);
+
+ // Try to create an objectName using the property names that have been specified as idNames in the schema
+ StringBuilder buf = new StringBuilder();
+ // Initialise idNames as an empty array as we want to check if a key has been used to construct the name.
+ String[] idNames = {};
+ if (schema != null && schema instanceof SchemaObjectClass)
+ {
+ idNames = ((SchemaObjectClass)schema).getIdNames();
+ for (String property : idNames)
+ {
+ buf.append(object.getStringValue(property));
+ }
+ }
+ String objectName = buf.toString();
+
+ // If the schema hasn't given any help we use a UUID. Note that we check the length of idNames too
+ // as a given named key property might legitimately be an empty string (e.g. the default direct
+ // exchange has name == "")
+ if (objectName.length() == 0 && idNames.length == 0) objectName = UUID.randomUUID().toString();
+
+ // Finish up the name by incorporating package and class names
+ objectName = classId.getPackageName() + ":" + classId.getClassName() + ":" + objectName;
+
+ // Now we've got a good name for the object we create its ObjectId and add that to the object
+ addr = new ObjectId(_name, objectName, _epoch);
+
+ object.setObjectId(addr);
+ }
+
+ QmfAgentData foundObject = _objectIndex.get(addr);
+ if (foundObject != null)
+ {
+ // If a duplicate object has actually been Deleted we can reuse the address.
+ if (!foundObject.isDeleted())
+ {
+ throw new QmfException("Duplicate QmfAgentData Address");
+ }
+ }
+
+ _objectIndex.put(addr, object);
+
+ // Does the new object match any Subscriptions? If so add a reference to the matching Subscription and publish.
+ for (Subscription subscription : _subscriptions.values())
+ {
+ QmfQuery query = subscription.getQuery();
+ if (query.getObjectId() != null)
+ {
+ if (query.getObjectId().equals(addr))
+ {
+ object.addSubscription(subscription.getSubscriptionId(), subscription);
+ object.publish();
+ }
+ }
+ else if (query.evaluate(object))
+ {
+ object.addSubscription(subscription.getSubscriptionId(), subscription);
+ object.publish();
+ }
+ }
+ } // end of addObject()
+
+ /**
+ * Returns the count of pending WorkItems that can be retrieved.
+ * @return the count of pending WorkItems that can be retrieved.
+ */
+ public final int getWorkitemCount()
+ {
+ return _workQueue.size();
+ }
+
+ /**
+ * Obtains the next pending work item - blocking version.
+ * <p>
+ * The blocking getNextWorkitem() can be used without the need for a Notifier as it will block until
+ * a new item gets added to the work queue e.g. the following usage pattern.
+ * <pre>
+ * while ((wi = agent.getNextWorkitem()) != null)
+ * {
+ * System.out.println("WorkItem type: " + wi.getType());
+ * }
+ * </pre>
+ * @return the next pending work item, or null if none available.
+ */
+ public final WorkItem getNextWorkitem()
+ {
+ return _workQueue.getNextWorkitem();
+ }
+
+ /**
+ * Obtains the next pending work item - balking version.
+ * <p>
+ * The balking getNextWorkitem() is generally used with a Notifier which can be used as a gate to determine
+ * if any work items are available e.g. the following usage pattern.
+ * <pre>
+ * while (true)
+ * {
+ * notifier.waitForWorkItem(); // Assuming a BlockingNotifier has been used here
+ * System.out.println("WorkItem available, count = " + agent.getWorkitemCount());
+ *
+ * WorkItem wi;
+ * while ((wi = agent.getNextWorkitem(0)) != null)
+ * {
+ * System.out.println("WorkItem type: " + wi.getType());
+ * }
+ * }
+ * </pre>
+ * Note that it is possible for the getNextWorkitem() loop to retrieve multiple items from the _workQueue
+ * and for the Agent to add new items as the loop is looping thus when it finally exits and goes
+ * back to the outer loop notifier.waitForWorkItems() may return immediately as it had been notified
+ * whilst we were in the getNextWorkitem() loop. This will be evident by a getWorkitemCount() of 0
+ * after returning from waitForWorkItem().
+ * <p>
+ * This is the expected behaviour, but illustrates the need to check for nullness of the value returned
+ * by getNextWorkitem() - or alternatively to use getWorkitemCount() to put getNextWorkitem() in a
+ * bounded loop.
+ *
+ * @param timeout the timeout in seconds. If timeout = 0 it returns immediately with either a WorkItem or null
+ * @return the next pending work item, or null if none available.
+ */
+ public final WorkItem getNextWorkitem(final long timeout)
+ {
+ return _workQueue.getNextWorkitem(timeout);
+ }
+
+ /**
+ * Releases a WorkItem instance obtained by getNextWorkItem(). Called when the application has finished
+ * processing the WorkItem.
+ */
+ public final void releaseWorkitem()
+ {
+ // To be honest I'm not clear what the intent of this method actually is. One thought is that it's here
+ // to support equivalent behaviour to the Python Queue.task_done() which is used by queue consumer threads.
+ // For each get() used to fetch a task, a subsequent call to task_done() tells the queue that the processing
+ // on the task is complete.
+ //
+ // The problem with that theory is there is no equivalent QMF2 API call that would invoke the
+ // Queue.join() which is used in conjunction with Queue.task_done() to enable a synchronisation gate to
+ // be implemented to wait for completion of all worker thread.
+ //
+ // I'm a bit stumped and there's no obvious Java equivalent on BlockingQueue, so for now this does nothing.
+ }
+
+ /**
+ * Indicate to the Agent that the application has completed processing a method request.
+ * <p>
+ * See the description of the METHOD_CALL WorkItem.
+ * @param methodName the method's name.
+ * @param handle the reply handle from WorkItem.
+ * @param outArgs the output argument map.
+ * @param error the error object that was created if the method failed in any way, otherwise null.
+ */
+ public final void methodResponse(final String methodName, final Handle handle,
+ final QmfData outArgs, final QmfData error)
+ {
+ try
+ {
+ MapMessage response = _syncSession.createMapMessage();
+ response.setJMSCorrelationID(handle.getCorrelationId());
+ response.setStringProperty("x-amqp-0-10.app-id", "qmf2");
+ response.setStringProperty("method", "response");
+ response.setStringProperty("qmf.opcode", "_method_response");
+ response.setStringProperty("qmf.agent", _name);
+ response.setStringProperty("qpid.subject", handle.getRoutingKey());
+
+ if (error == null)
+ {
+ if (outArgs != null)
+ {
+ response.setObject("_arguments", outArgs.mapEncode());
+ if (outArgs.getSubtypes() != null)
+ {
+ response.setObject("_subtypes", outArgs.getSubtypes());
+ }
+ }
+ }
+ else
+ {
+ Map<String, Object> errorMap = error.mapEncode();
+ for (Map.Entry<String, Object> entry : errorMap.entrySet())
+ {
+ response.setObject(entry.getKey(), entry.getValue());
+ }
+ }
+ sendResponse(handle, response);
+ }
+ catch (JMSException jmse)
+ {
+ _log.info("JMSException {} caught in methodResponse()", jmse.getMessage());
+ }
+ }
+
+ /**
+ * Send the query response back to the Console.
+ * @param handle the reply handle that contains the replyTo Address.
+ * @param results the list of mapEncoded query results.
+ * @param qmfContentType the value to be passed to the qmf.content Header.
+ */
+ protected final void queryResponse(final Handle handle, List<Map> results, final String qmfContentType)
+ {
+ try
+ {
+ Message response = AMQPMessage.createListMessage(_syncSession);
+ response.setJMSCorrelationID(handle.getCorrelationId());
+ response.setStringProperty("x-amqp-0-10.app-id", "qmf2");
+ response.setStringProperty("method", "response");
+ response.setStringProperty("qmf.opcode", "_query_response");
+ response.setStringProperty("qmf.agent", _name);
+ response.setStringProperty("qmf.content", qmfContentType);
+ response.setStringProperty("qpid.subject", handle.getRoutingKey());
+ AMQPMessage.setList(response, results);
+ sendResponse(handle, response);
+ }
+ catch (JMSException jmse)
+ {
+ _log.info("JMSException {} caught in queryResponse()", jmse.getMessage());
+ }
+ }
+
+ /**
+ * If the subscription request is successful, the Agent application must provide a unique subscriptionId.
+ * <p>
+ * If replying to a sucessful subscription refresh, the original subscriptionId must be supplied.
+ * <p>
+ * If the subscription or refresh fails, the subscriptionId should be set to null and error may be set to
+ * an application-specific QmfData instance that describes the error.
+ * <p>
+ * Should a refresh request fail, the consoleHandle may be set to null if unknown.
+ *
+ * @param handle the handle from the WorkItem.
+ * @param consoleHandle the console reply handle.
+ * @param subscriptionId a unique handle for the subscription supplied by the Agent.
+ * @param lifetime should be set to the duration of the subscription in seconds.
+ * @param publishInterval should be set to the time interval in seconds between successive publications
+ * on this subscription.
+ * @param error an application-specific QmfData instance that describes the error.
+ */
+ public final void subscriptionResponse(final Handle handle, final Handle consoleHandle, final String subscriptionId,
+ final long lifetime, final long publishInterval, final QmfData error)
+ {
+ try
+ {
+ MapMessage response = _syncSession.createMapMessage();
+ response.setJMSCorrelationID(handle.getCorrelationId());
+ response.setStringProperty("x-amqp-0-10.app-id", "qmf2");
+ response.setStringProperty("method", "response");
+ response.setStringProperty("qmf.opcode", "_subscribe_response");
+ response.setStringProperty("qmf.agent", _name);
+ response.setStringProperty("qpid.subject", handle.getRoutingKey());
+
+ if (error == null)
+ {
+ response.setObject("_subscription_id", subscriptionId);
+ response.setObject("_duration", lifetime);
+ response.setObject("_interval", publishInterval);
+ }
+ else
+ {
+ Map<String, Object> errorMap = error.mapEncode();
+ for (Map.Entry<String, Object> entry : errorMap.entrySet())
+ {
+ response.setObject(entry.getKey(), entry.getValue());
+ }
+ }
+ sendResponse(handle, response);
+ }
+ catch (JMSException jmse)
+ {
+ _log.info("JMSException {} caught in subscriptionResponse()", jmse.getMessage());
+ }
+ }
+}
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/AgentExternal.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/AgentExternal.java
new file mode 100644
index 0000000000..25b2db3d69
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/AgentExternal.java
@@ -0,0 +1,259 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.agent;
+
+// Misc Imports
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.Handle;
+import org.apache.qpid.qmf2.common.ObjectId;
+import org.apache.qpid.qmf2.common.QmfCallback;
+import org.apache.qpid.qmf2.common.QmfException;
+
+/**
+ * The AgentExternal class must be used by those applications that implement the external store model described in
+ * <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API Proposal</a>.
+ * <p>
+ * The AgentExternal class extends the Agent class by adding interfaces that notify the application when it needs to
+ * service a request for management operations from the agent.
+ * <p>
+ * N.B. The author is not convinced that there is any particular advantage of the AgentExternal model over the
+ * basic Agent model and indeed the API forces some constructs that are actually likely to be less efficient, as an
+ * example sending a separate queryResponse() for each object forces a look up of a List of QmfAgentData objects
+ * keyed by the consoleHandle for each call. There is also the need to separately iterate through the List of
+ * QmfAgentData objects thus created to create the mapEncoded list needed for sending via the QMF2 protocol.
+ * There are similar inefficiencies imposed in the subscriptionIndicate() method that are not present in the
+ * Subscription code implemented in the Agent class for the "Internal Store" Agent model.
+ * <p>
+ * To be honest the author only bothered to implement AgentExternal for completeness and is unlikely to use it himself.
+ *
+ * @author Fraser Adams
+ */
+public final class AgentExternal extends Agent
+{
+ /**
+ * This Map is used to hold query results. This is necessary as the API has each queryResponse() call send
+ * back an individual QmfAgentData, so we need to maintain these in a list keyed by the consoleHandle until
+ * the queryComplete() gets sent.
+ */
+ private Map<String, List<QmfAgentData>> _queryResults = new ConcurrentHashMap<String, List<QmfAgentData>>();
+
+ // QMF API Methods
+ // ********************************************************************************************************
+
+ /**
+ * Constructor that provides defaults for name, domain and heartbeat interval and takes a Notifier/Listener.
+ *
+ * @param notifier this may be either a QMF2 API Notifier object OR a QMFEventListener.
+ * <p>
+ * The latter is an alternative API that avoids the need for an explicit Notifier thread to be created the
+ * EventListener is called from the JMS MessageListener thread.
+ * <p>
+ * This API may be simpler and more convenient than the QMF2 Notifier API for many applications.
+ */
+ public AgentExternal(final QmfCallback notifier) throws QmfException
+ {
+ super(null, null, notifier, 30);
+ }
+
+ /**
+ * Constructor that provides defaults for name and domain and takes a Notifier/Listener
+ *
+ * @param notifier this may be either a QMF2 API Notifier object OR a QMFEventListener.
+ * <p>
+ * The latter is an alternative API that avoids the need for an explicit Notifier thread to be created the
+ * EventListener is called from the JMS MessageListener thread.
+ * <p>
+ * This API may be simpler and more convenient than the QMF2 Notifier API for many applications.
+ * @param interval is the heartbeat interval in seconds.
+ */
+ public AgentExternal(final QmfCallback notifier, final int interval) throws QmfException
+ {
+ super(null, null, notifier, interval);
+ }
+
+ /**
+ * Main constructor, creates a Agent, but does NOT start it, that requires us to do setConnection()
+ *
+ * @param name If a name is supplied, it must be unique across all attached to the AMQP bus under the given domain.
+ * <p>
+ * The name must comprise three parts separated by colons: <pre>&lt;vendor&gt;:&lt;product&gt;[:&lt;instance&gt;]</pre>
+ * where the vendor is the Agent vendor name, the product is the Agent product itself and the instance is a UUID
+ * representing the running instance.
+ * <p>
+ * If the instance is not supplied then a random UUID will be generated.
+ * @param domain the QMF "domain".
+ * <p>
+ * A QMF address is composed of two parts - an optional domain string, and a mandatory name string
+ * <pre>"qmf.&lt;domain-string&gt;.direct/&lt;name-string&gt;"</pre>
+ * The domain string is used to construct the name of the AMQP exchange to which the component's name string will
+ * be bound. If not supplied, the value of the domain defaults to "default".
+ * <p>
+ * Both Agents and Components must belong to the same domain in order to communicate.
+ * @param notifier this may be either a QMF2 API Notifier object OR a QMFEventListener.
+ * <p>
+ * The latter is an alternative API that avoids the need for an explicit Notifier thread to be created the
+ * EventListener is called from the JMS MessageListener thread.
+ * <p>
+ * This API may be simpler and more convenient than the QMF2 Notifier API for many applications.
+ * @param interval is the heartbeat interval in seconds.
+ */
+ public AgentExternal(final String name, final String domain,
+ final QmfCallback notifier, final int interval) throws QmfException
+ {
+ super(name, domain, notifier, interval);
+ }
+
+ /**
+ * We override the base Class addObject() to throw an Exception as addObject() is used to populate the
+ * <b>internal</b> store.
+ */
+ @Override
+ public void addObject(final QmfAgentData object) throws QmfException
+ {
+ throw new QmfException("Cannot call addObject() on AgentExternal as this method is used to populate the internal object store");
+ }
+
+ /**
+ * Indicate to QMF that the named object is available to be managed. Once this method returns, the agent will
+ * service requests from consoles referencing this data.
+ *
+ * @param objectName the name of the QmfAgentData being managed.
+ * @return a new ObjectId based on the objectName passed as a parameter.
+ */
+ public ObjectId allocObjectId(final String objectName)
+ {
+ return new ObjectId(getName(), objectName, getEpoch());
+ }
+
+ /**
+ * Indicate to QMF that the named object is no longer available to be managed.
+ *
+ * @param objectName the name of the QmfAgentData being managed.
+ */
+ public void freeObjectId(final String objectName)
+ {
+ // Null implementation. It's not really clear that there's anything useful that the Agent needs to do here
+ }
+
+ /**
+ * Send a managed object in reply to a received query. Note that ownership of the object instance is returned to
+ * the caller on return from this call.
+ *
+ * @param handle the handle from the WorkItem.
+ * @param object a managed QmfAgentData object.
+ */
+ public void queryResponse(final Handle handle, final QmfAgentData object)
+ {
+ String index = handle.getCorrelationId();
+ List<QmfAgentData> objects = _queryResults.get(index);
+ if (objects == null)
+ {
+ objects = new ArrayList<QmfAgentData>();
+ _queryResults.put(index, objects);
+ }
+ objects.add(object);
+ }
+
+ /**
+ * Indicate to the agent that the application has completed processing a query request.
+ * Zero or more calls to the queryResponse() method should be invoked before calling query_complete().
+ * If the query should fail - for example, due to authentication error - the result should be set to a
+ * non-zero error code ?TBD?.
+ *
+ * @param handle the handle from the WorkItem.
+ * @param statusCode if this is non zero it indicates that the query failed.
+ */
+ public void queryComplete(final Handle handle, final int statusCode)
+ {
+ String index = handle.getCorrelationId();
+ List<QmfAgentData> objects = _queryResults.get(index);
+ if (objects != null)
+ {
+ List<Map> results = new ArrayList<Map>(objects.size());
+ for (QmfAgentData object : objects)
+ {
+ results.add(object.mapEncode());
+ }
+
+ // Send the response back to the Console
+ queryResponse(handle, results, "_data");
+ _queryResults.remove(index);
+ }
+ }
+
+ /**
+ * This has actually been implemented in the base Agent class as it's useful there too.
+ *
+ * If the subscription request is successful, the Agent application must provide a unique subscriptionId.
+ * If replying to a sucessful subscription refresh, the original subscriptionId must be supplied.
+ * If the subscription or refresh fails, the subscriptionId should be set to null and error may be set to
+ * an application-specific QmfData instance that describes the error.
+ * Should a refresh request fail, the consoleHandle may be set to null if unknown.
+ *
+ * @param handle the handle from the WorkItem
+ * @param consoleHandle the console reply handle
+ * @param subscriptionId a unique handle for the subscription supplied by the Agent
+ * @param lifetime should be set to the duration of the subscription in seconds.
+ * @param publishInterval should be set to the time interval in seconds between successive publications
+ * on this subscription.
+ * @param error an application-specific QmfData instance that describes the error.
+ */
+ //public void subscriptionResponse(Handle handle, Handle consoleHandle, String subscriptionId, long lifetime, long
+ // publishInterval, QmfData error)
+
+ /**
+ * Send a list of updated subscribed data to the Console.
+ *
+ * Note that the base Agent class contains a sendSubscriptionIndicate() for data that is already mapEncoded()
+ * To be honest AgentExternal Agents that happen to implement the SubscriptionProxy interface which reuses the
+ * Internal Agent Subscription code will almost certainly call the base class sendSubscriptionIndicate() as the
+ * SubscriptionProxy sendSubscriptionIndicate() has the same signature.
+ *
+ * @param handle the console reply handle.
+ * @param objects a list of subscribed data in QmfAgentData encoded form.
+ */
+ public void subscriptionIndicate(final Handle handle, final List<QmfAgentData> objects)
+ {
+ List<Map> results = new ArrayList<Map>(objects.size());
+ for (QmfAgentData object : objects)
+ {
+ results.add(object.mapEncode());
+ }
+ sendSubscriptionIndicate(handle, results);
+ }
+
+ /**
+ * Acknowledge a Subscription Cancel WorkItem.
+ *
+ * @param handle the handle from the WorkItem.
+ * @param consoleHandle the console reply handle.
+ */
+ public void subscriptionCancel(final Handle handle, final String consoleHandle)
+ {
+ // Null implementation, there isn't really much that needs to be done after cancelling a Subscription
+ }
+}
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/MethodCallParams.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/MethodCallParams.java
new file mode 100644
index 0000000000..1f69709225
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/MethodCallParams.java
@@ -0,0 +1,123 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.agent;
+
+import java.util.Map;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.ObjectId;
+import org.apache.qpid.qmf2.common.QmfData;
+
+/**
+ * This class contains the values passed by the Agent class to the Agent implementation within the MethodCallWorkItem.
+ * <p>
+ * It contains information that is needed by the Agent implementation to identify the object on which the method.
+ * is to be called via getObjectId(), the name of the method to be invoked via getName() and the arguments of the
+ * method via getArgs().
+ *
+ * @author Fraser Adams
+ */
+
+public final class MethodCallParams
+{
+ private final String _name;
+ private final ObjectId _objectId;
+ private final QmfData _args;
+ private final String _userId;
+
+ /**
+ * Construct MethodCallParams.
+ *
+ * @param m the Map used to populate the WorkItem's parameters.
+ */
+ public MethodCallParams(final Map m)
+ {
+ _name = QmfData.getString(m.get("_method_name"));
+
+ Map oid = (Map)m.get("_object_id");
+ _objectId = (oid == null) ? null : new ObjectId(oid);
+
+ Map args = (Map)m.get("_arguments");
+ if (args == null)
+ {
+ _args = null;
+ }
+ else
+ {
+ _args = new QmfData(args);
+ _args.setSubtypes((Map)m.get("_subtypes"));
+ }
+ _userId = QmfData.getString(m.get("_user_id"));
+ }
+
+ /**
+ * Return a string containing the name of the method call.
+ * @return a string containing the name of the method call.
+ */
+ public String getName()
+ {
+ return _name;
+ }
+
+ /**
+ * Return the identifier for the object on which this method needs to be invoked.
+ * @return the identifier for the object on which this method needs to be invoked.
+ * <p>
+ * Returns null iff there is no associated object (a method call against the agent itself).
+ */
+ public ObjectId getObjectId()
+ {
+ return _objectId;
+ }
+
+ /**
+ * Return a map of input arguments for the method.
+ * @return a map of input arguments for the method.
+ * <p>
+ * Arguments are in "name"=&lt;value&gt; pairs. Returns null if no arguments are supplied.
+ */
+ public QmfData getArgs()
+ {
+ return _args;
+ }
+
+ /**
+ * Return authenticated user id of caller if present, else null.
+ * @return authenticated user id of caller if present, else null.
+ */
+ public String getUserId()
+ {
+ return _userId;
+ }
+
+ /**
+ * Helper/debug method to list the properties and their type.
+ */
+ public void listValues()
+ {
+ System.out.println("MethodCallParams:");
+ System.out.println("name: " + _name);
+ System.out.println("objectId: " + _objectId);
+ System.out.println("args: " + _args);
+ System.out.println("userId: " + _userId);
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/MethodCallWorkItem.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/MethodCallWorkItem.java
new file mode 100644
index 0000000000..cde10902e6
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/MethodCallWorkItem.java
@@ -0,0 +1,64 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.agent;
+
+import java.util.Map;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.Handle;
+import org.apache.qpid.qmf2.common.WorkItem;
+
+/**
+ * Descriptions below are taken from <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API Proposal</a>
+ * <pre>
+ * METHOD_CALL: The METHOD_CALL WorkItem describes a method call that must be serviced by the application on
+ * behalf of this Agent.
+ *
+ * The getParams() method of a METHOD_CALL WorkItem will return an instance of the MethodCallParams class.
+ *
+ * Use MethodCallWorkItem to enable neater access
+ * </pre>
+ * @author Fraser Adams
+ */
+
+public final class MethodCallWorkItem extends WorkItem
+{
+ /**
+ * Construct a MethodCallWorkItem. Convenience constructor not in API
+ *
+ * @param handle the reply handle.
+ * @param params the MethodCallParams used to populate the WorkItem's param.
+ */
+ public MethodCallWorkItem(final Handle handle, final MethodCallParams params)
+ {
+ super(WorkItemType.METHOD_CALL, handle, params);
+ }
+
+ /**
+ * Return the MethodCallParams stored in the params Map.
+ * @return the MethodCallParams stored in the params Map.
+ */
+ public MethodCallParams getMethodCallParams()
+ {
+ return (MethodCallParams)getParams();
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/QmfAgentData.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/QmfAgentData.java
new file mode 100644
index 0000000000..974c5746e2
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/QmfAgentData.java
@@ -0,0 +1,364 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.agent;
+
+// Misc Imports
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.ObjectId;
+import org.apache.qpid.qmf2.common.QmfException;
+import org.apache.qpid.qmf2.common.QmfManaged;
+import org.apache.qpid.qmf2.common.SchemaObjectClass;
+
+/**
+ * The Agent manages the data it represents by the QmfAgentData class - a derivative of the QmfData class.
+ * <p>
+ * The Agent is responsible for managing the values of the properties within the object, as well as servicing
+ * the object's method calls. Unlike the Console, the Agent has full control of the state of the object.
+ * <p>
+ * In most cases, for efficiency, it is expected that Agents would <i>actually</i> manage objects that are subclasses of
+ * QmfAgentData and maintain subclass specific properties as primitives, only actually explicitly setting the
+ * underlying Map properties via setValue() etc. when the object needs to be "serialised". This would most
+ * obviously be done by extending the mapEncode() method (noting that it's important to call QmfAgentData's mapEncode()
+ * first via super.mapEncode(); as this will set the state of the underlying QmfData).
+ * <p>
+ * This class provides a number of methods aren't in the QMF2 API per se, but they are used to manage the association
+ * between a managed object and any subscriptions that might be interested in it.
+ * <p>
+ * The diagram below shows the relationship between the Subscription and QmfAgentData.
+ * <p>
+ * <img alt="" src="doc-files/Subscriptions.png">
+ * <p>
+ * In particular the QmfAgentData maintains references to active subscriptions to allow agents to asynchronously
+ * push data to subscribing Consoles immediately that data becomes available.
+ * <p>
+ * The update() method indicates that the object's state has changed and the publish() method <b>immediately</b> sends
+ * the new state to any subscription.
+ * <p>
+ * The original intention was to "auto update" by calling these from the setValue() method. Upon reflection this
+ * seems a bad idea, as in many cases there may be several properties that an Agent may wish to change which would
+ * lead to unnecessary calls to currentTimeMillis(), but also as theSubscription update is run via a TimerTask it is
+ * possible that an update indication could get sent part way through setting an object's overall state.
+ * Similarly calling the publish() method directly from setValue() would force an update indication on partial changes
+ * of state, which is generally not the desired behaviour.
+ * @author Fraser Adams
+ */
+public class QmfAgentData extends QmfManaged implements Comparable<QmfAgentData>
+{
+ private long _updateTimestamp;
+ private long _createTimestamp;
+ private long _deleteTimestamp;
+ private String _compareKey = null;
+
+ /**
+ * This Map is used to look up Subscriptions that are interested in this data by SubscriptionId
+ */
+ private Map<String, Subscription> _subscriptions = new ConcurrentHashMap<String, Subscription>();
+
+ /**
+ * Construct a QmfAgentData object of the type described by the given SchemaObjectClass.
+ *
+ * @param schema the schema describing the type of this QmfAgentData object.
+ */
+ public QmfAgentData(final SchemaObjectClass schema)
+ {
+ long currentTime = System.currentTimeMillis()*1000000l;
+ _updateTimestamp = currentTime;
+ _createTimestamp = currentTime;
+ _deleteTimestamp = 0;
+ setSchemaClassId(schema.getClassId());
+ }
+
+ /**
+ * Return the creation timestamp.
+ * @return the creation timestamp. Timestamps are recorded in nanoseconds since the epoch
+ */
+ public final long getCreateTime()
+ {
+ return _createTimestamp;
+ }
+
+ /**
+ * Return the update timestamp.
+ * @return the update timestamp. Timestamps are recorded in nanoseconds since the epoch
+ */
+ public final long getUpdateTime()
+ {
+ return _updateTimestamp;
+ }
+
+ /**
+ * Return the deletion timestamp.
+ * @return the deletion timestamp, or zero if not deleted. Timestamps are recorded in nanoseconds since the epoch
+ */
+ public final long getDeleteTime()
+ {
+ return _deleteTimestamp;
+ }
+
+ /**
+ * Return true if deletion timestamp not zero.
+ * @return true if deletion timestamp not zero.
+ */
+ public final boolean isDeleted()
+ {
+ return getDeleteTime() != 0;
+ }
+
+ /**
+ * Mark the object as deleted by setting the deletion timestamp to the current time.
+ * <p>
+ * This method alse publishes the deleted object to any listening Subscription then removes references to the
+ * Subscription.
+ * <p>
+ * When this method returns the object should be ready for reaping.
+ */
+ public final void destroy()
+ {
+ _deleteTimestamp = System.currentTimeMillis()*1000000l;
+ _updateTimestamp = System.currentTimeMillis()*1000000l;
+ publish();
+ _subscriptions.clear();
+ }
+
+ /**
+ * Add the delta to the property.
+ *
+ * @param name the name of the property being modified.
+ * @param delta the value being added to the property.
+ */
+ public final synchronized void incValue(final String name, final long delta)
+ {
+ long value = getLongValue(name);
+ value += delta;
+ setValue(name, value);
+ }
+
+ /**
+ * Add the delta to the property.
+ *
+ * @param name the name of the property being modified.
+ * @param delta the value being added to the property.
+ */
+ public final synchronized void incValue(final String name, final double delta)
+ {
+ double value = getDoubleValue(name);
+ value += delta;
+ setValue(name, value);
+ }
+
+ /**
+ * Subtract the delta from the property.
+ *
+ * @param name the name of the property being modified.
+ * @param delta the value being subtracted from the property.
+ */
+ public final synchronized void decValue(final String name, final long delta)
+ {
+ long value = getLongValue(name);
+ value -= delta;
+ setValue(name, value);
+ }
+
+ /**
+ * Subtract the delta from the property.
+ *
+ * @param name the name of the property being modified.
+ * @param delta the value being subtracted from the property.
+ */
+ public final synchronized void decValue(final String name, final double delta)
+ {
+ double value = getDoubleValue(name);
+ value -= delta;
+ setValue(name, value);
+ }
+
+ // The following methods aren't in the QMF2 API per se, but they are used to manage the association between
+ // a managed object and any subscriptions that might be interested in it.
+
+ /**
+ * Return the Subscription with the specified ID.
+ * @return the Subscription with the specified ID.
+ */
+ public final Subscription getSubscription(final String subscriptionId)
+ {
+ return _subscriptions.get(subscriptionId);
+ }
+
+ /**
+ * Add a new Subscription reference.
+ * @param subscriptionId the ID of the Subscription being added.
+ * @param subscription the Subscription being added.
+ */
+ public final void addSubscription(final String subscriptionId, final Subscription subscription)
+ {
+ _subscriptions.put(subscriptionId, subscription);
+ }
+
+ /**
+ * Remove a Subscription reference.
+ * @param subscriptionId the ID of the Subscription being removed.
+ */
+ public final void removeSubscription(final String subscriptionId)
+ {
+ _subscriptions.remove(subscriptionId);
+ }
+
+
+ /**
+ * Set the _updateTimestamp to indicate (particularly to subscriptions) that the managed object has changed.
+ * <p>
+ * The update() method indicates that the object's state has changed and the publish() method <b>immediately</b> sends
+ * the new state to any subscription.
+ * <p>
+ * The original intention was to "auto update" by calling these from the setValue() method. Upon reflection this
+ * seems a bad idea, as in many cases there may be several properties that an Agent may wish to change which would
+ * lead to unnecessary calls to currentTimeMillis(), but also as the Subscription update is run via a TimerTask it
+ * is possible that an update indication could get sent part way through setting an object's overall state.
+ * Similarly calling the publish() method directly from setValue() would force an update indication on partial
+ * changes of state, which is generally not the desired behaviour.
+ */
+ public final void update()
+ {
+ _updateTimestamp = System.currentTimeMillis()*1000000l;
+ }
+
+ /**
+ * Iterate through any Subscriptions associated with this Object and force them to republish the Object's new state.
+ * <p>
+ * The update() method indicates that the object's state has changed and the publish() method <b>immediately</b> sends
+ * the new state to any subscription.
+ * <p>
+ * The original intention was to "auto update" by calling these from the setValue() method. Upon reflection this
+ * seems a bad idea, as in many cases there may be several properties that an Agent may wish to change which would
+ * lead to unnecessary calls to currentTimeMillis(), but also as the Subscription update is run via a TimerTask it
+ * is possible that an update indication could get sent part way through setting an object's overall state.
+ * Similarly calling the publish() method directly from setValue() would force an update indication on partial
+ * changes of state, which is generally not the desired behaviour.
+ */
+ public final void publish()
+ {
+ update();
+ if (getObjectId() == null)
+ { // If ObjectId is null the Object isn't yet Managed to we can't publish
+ return;
+ }
+
+ List<Map> results = new ArrayList<Map>();
+ results.add(mapEncode());
+ for (Map.Entry<String, Subscription> entry : _subscriptions.entrySet())
+ {
+ Subscription subscription = entry.getValue();
+ subscription.publish(results);
+ }
+ }
+
+ /**
+ * Return the underlying map.
+ * <p>
+ * In most cases, for efficiency, it is expected that Agents would <i>actually</i> manage objects that are
+ * subclasses of QmfAgentData and maintain subclass specific properties as primitives, only actually explicitly
+ * setting the underlying Map properties via setValue() etc. when the object needs to be "serialised". This would
+ * most obviously be done by extending the mapEncode() method (noting that it's important to call QmfAgentData's
+ * mapEncode() first via super.mapEncode(); as this will set the state of the underlying QmfData).
+ *
+ * @return the underlying map.
+ */
+ @Override
+ public Map<String, Object> mapEncode()
+ {
+ Map<String, Object> map = new HashMap<String, Object>();
+ map.put("_values", super.mapEncode());
+ if (_subtypes != null)
+ {
+ map.put("_subtypes", _subtypes);
+ }
+ map.put("_schema_id", getSchemaClassId().mapEncode());
+ map.put("_object_id", getObjectId().mapEncode());
+ map.put("_update_ts", _updateTimestamp);
+ map.put("_create_ts", _createTimestamp);
+ map.put("_delete_ts", _deleteTimestamp);
+ return map;
+ }
+
+ /**
+ * Helper/debug method to list the QMF Object properties and their type.
+ */
+ @Override
+ public void listValues()
+ {
+ super.listValues();
+ System.out.println("QmfAgentData:");
+ System.out.println("create timestamp: " + new Date(getCreateTime()/1000000l));
+ System.out.println("update timestamp: " + new Date(getUpdateTime()/1000000l));
+ System.out.println("delete timestamp: " + new Date(getDeleteTime()/1000000l));
+ }
+
+ // The following methods allow instances of QmfAgentData to be compared with each other and sorted.
+ // N.B. This behaviour is not part of the specified QmfAgentData, but it's quite useful for some Agents.
+
+ /**
+ * Set the key String to be used for comparing two QmfAgentData instances. This is primarily used by the Agent
+ * to allow it to order Query results (e.g. for getObjects()).
+ * @param compareKey the String that we wish to use as a compare key.
+ */
+ public void setCompareKey(String compareKey)
+ {
+ _compareKey = compareKey;
+ }
+
+ /**
+ * If a compare key has been set then the QmfAgentData is sortable.
+ * @return true if a compare key has been set and the QmfAgentData is sortable otherwise return false.
+ */
+ public boolean isSortable()
+ {
+ return _compareKey != null;
+ }
+
+ /**
+ * Compare the compare key of this QmfAgentData with the specified other QmfAgentData.
+ * Compares the compare keys (which are Strings) lexicographically. The comparison is based on the Unicode
+ * value of each character in the strings.
+ * @param rhs the String to be compared.
+ * @return the value 0 if the argument string is equal to this string; a value less than 0 if this string is
+ * lexicographically less than the string argument; and a value greater than 0 if this string is lexicographically
+ * greater than the string argument.
+ */
+ public int compareTo(QmfAgentData rhs)
+ {
+ if (_compareKey == null)
+ {
+ return 0;
+ }
+ else
+ {
+ return this._compareKey.compareTo(rhs._compareKey);
+ }
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/QueryWorkItem.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/QueryWorkItem.java
new file mode 100644
index 0000000000..d617307c20
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/QueryWorkItem.java
@@ -0,0 +1,85 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.agent;
+
+import java.util.Map;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.Handle;
+import org.apache.qpid.qmf2.common.QmfQuery;
+import org.apache.qpid.qmf2.common.WorkItem;
+
+/**
+ * Descriptions below are taken from <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API Proposal</a>
+ * <pre>
+ * QUERY: The QUERY WorkItem describes a query that the application must service. The application should call the
+ * queryResponse() method for each object that satisfies the query. When complete, the application must call the
+ * queryComplete() method. If a failure occurs, the application should indicate the error to the agent by calling
+ * the query_complete() method with a description of the error.
+ *
+ * The getParams() method of a QUERY WorkItem will return an instance of the QmfQuery class.
+ *
+ * The getHandle() WorkItem method returns the reply handle which should be passed to the Agent's queryResponse()
+ * and queryComplete() methods.
+ * </pre>
+ * Note that the API is a bit sketchy on the description of the QUERY WorkItem and whereas most WorkItems seem to return
+ * defined classes for their getParams() it's not so obvious here. There's an implication that getParams() just returns
+ * QmfQuery, but it's not clear where the uset_id bit fits.
+ * <p>
+ * As the API doesn't define a "QueryParams" class I've not included one, but I've added a getUserId() method to
+ * QueryWorkItem, this is a bit inconsistent with the approach for the other WorkItems though.
+ *
+ * @author Fraser Adams
+ */
+
+public final class QueryWorkItem extends WorkItem
+{
+ /**
+ * Construct a QueryWorkItem. Convenience constructor not in API.
+ *
+ * @param handle the reply handle.
+ * @param params the QmfQuery used to populate the WorkItem's param.
+ */
+ public QueryWorkItem(final Handle handle, final QmfQuery params)
+ {
+ super(WorkItemType.QUERY, handle, params);
+ }
+
+ /**
+ * Return the QmfQuery stored in the params Map.
+ * @return the QmfQuery stored in the params Map.
+ */
+ public QmfQuery getQmfQuery()
+ {
+ return (QmfQuery)getParams();
+ }
+
+ /**
+ * Return authenticated user id of caller if present, else null.
+ * @return authenticated user id of caller if present, else null.
+ */
+ public String getUserId()
+ {
+ Map map = getQmfQuery().mapEncode();
+ return (String)map.get("_user_id");
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/ResubscribeParams.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/ResubscribeParams.java
new file mode 100644
index 0000000000..475b378b75
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/ResubscribeParams.java
@@ -0,0 +1,78 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.agent;
+
+import java.util.Map;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.QmfData;
+
+/**
+ * Holds the information contained in a resubscription request made by a Console to an Agent
+ *
+ * @author Fraser Adams
+ */
+public final class ResubscribeParams extends QmfData
+{
+ /**
+ * Construct ResubscribeParams.
+ *
+ * @param m the Map used to populate the ResubscribeParams state.
+ */
+ public ResubscribeParams(final Map m)
+ {
+ super(m);
+ }
+
+ /**
+ * Return a SubscriptionId object.
+ * @return a SubscriptionId object.
+ */
+ public String getSubscriptionId()
+ {
+ if (hasValue("_subscription_id"))
+ {
+ return getStringValue("_subscription_id");
+ }
+ return null;
+ }
+
+ /**
+ * Return the requested lifetime for the subscription.
+ * @return the requested lifetime for the subscription. Zero if the previous interval should be used.
+ */
+ public long getLifetime()
+ {
+ return getLongValue("_duration");
+ }
+
+ /**
+ * Return authenticated user id of caller if present, else null.
+ * @return authenticated user id of caller if present, else null.
+ */
+ public String getUserId()
+ {
+ return getStringValue("_user_id");
+ }
+}
+
+
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/ResubscribeRequestWorkItem.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/ResubscribeRequestWorkItem.java
new file mode 100644
index 0000000000..2ec9ed2c9c
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/ResubscribeRequestWorkItem.java
@@ -0,0 +1,66 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.agent;
+
+import java.util.Map;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.Handle;
+import org.apache.qpid.qmf2.common.WorkItem;
+
+/**
+ * Descriptions below are taken from <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API Proposal</a>
+ * <pre>
+ * RESUBSCRIBE_REQUEST: The RESUBSCRIBE_REQUEST is sent by a Console to renew an existing subscription. The Console may
+ * request a new duration for the subscription, otherwise the previous lifetime interval is repeated.
+ *
+ * The getParams() method of a RESUBSCRIBE_REQUEST WorkItem will return an instance of the
+ * ResubscribeParams class.
+ *
+ * The getHandle() WorkItem method returns the reply handle which should be passed to the Agent's
+ * subscriptionResponse() method.
+ * </pre>
+ * @author Fraser Adams
+ */
+
+public final class ResubscribeRequestWorkItem extends WorkItem
+{
+ /**
+ * Construct a ResubscribeRequestWorkItem. Convenience constructor not in API.
+ *
+ * @param handle the reply handle.
+ * @param params the ResubscribeParams used to populate the WorkItem's param.
+ */
+ public ResubscribeRequestWorkItem(final Handle handle, final ResubscribeParams params)
+ {
+ super(WorkItemType.RESUBSCRIBE_REQUEST, handle, params);
+ }
+
+ /**
+ * Return the ResubscribeParams stored in the params Map.
+ * @return the ResubscribeParams stored in the params Map.
+ */
+ public ResubscribeParams getResubscribeParams()
+ {
+ return (ResubscribeParams)getParams();
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/SubscribableAgent.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/SubscribableAgent.java
new file mode 100644
index 0000000000..619d00df7b
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/SubscribableAgent.java
@@ -0,0 +1,70 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.agent;
+
+// Misc Imports
+import java.util.List;
+import java.util.Map;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.Handle;
+import org.apache.qpid.qmf2.common.QmfQuery;
+
+/**
+ * This interface provides a number of methods that are called by a Subscription in order to interact with an
+ * Agent or an Agent's managed data.
+ * <p>
+ * The purpose of this interface is primarily about removing a circular dependency between Subscription and Agent
+ * so the Subscription doesn't invoke these methods on an Agent instance, rather it invokes them on a
+ * SubscribeableAgent instance.
+ * <p>
+ * The following diagram illustrates the interactions between the Agent, Subscription and SubscribableAgent.
+ * <p>
+ * <img alt="" src="doc-files/Subscriptions.png">
+ *
+ * @author Fraser Adams
+ */
+public interface SubscribableAgent
+{
+ /**
+ * Send a list of updated subscribed data to the Console.
+ *
+ * @param handle the console reply handle
+ * @param results a list of subscribed data in Map encoded form
+ */
+ public void sendSubscriptionIndicate(Handle handle, List<Map> results);
+
+ /**
+ * This method evaluates a QmfQuery over the Agent's data on behalf of a Subscription
+ *
+ * @param query the QmfQuery that the Subscription wants to be evaluated over the Agent's data
+ * @return a List of QmfAgentData objects that match the specified QmfQuery
+ */
+ public List<QmfAgentData> evaluateQuery(QmfQuery query);
+
+ /**
+ * This method is called by the Subscription to tell the SubscriberProxy that the Subscription has been cancelled.
+ *
+ * @param subscription the Subscription that has been cancelled and is requesting removal.
+ */
+ public void removeSubscription(Subscription subscription);
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/SubscribeRequestWorkItem.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/SubscribeRequestWorkItem.java
new file mode 100644
index 0000000000..a9c4816aba
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/SubscribeRequestWorkItem.java
@@ -0,0 +1,69 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.agent;
+
+import java.util.Map;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.Handle;
+import org.apache.qpid.qmf2.common.WorkItem;
+
+/**
+ * Descriptions below are taken from <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API Proposal</a>
+ * <pre>
+ * SUBSCRIBE_REQUEST: The SUBSCRIBE_REQUEST WorkItem provides a query that the agent application must periodically
+ * publish until the subscription is cancelled or expires. On receipt of this WorkItem, the
+ * application should call the Agent subscriptionResponse() method to acknowledge the request.
+ * On each publish interval, the application should call Agent subscriptionIndicate(), passing a
+ * list of the objects that satisfy the query. The subscription remains in effect until an
+ * UNSUBSCRIBE_REQUEST WorkItem for the subscription is received, or the subscription expires.
+ *
+ * The getParams() method of a QUERY WorkItem will return an instance of the SubscriptionParams class.
+ *
+ * The getHandle() WorkItem method returns the reply handle which should be passed to the Agent's
+ * subscriptionResponse() method.
+ * </pre>
+ * @author Fraser Adams
+ */
+
+public final class SubscribeRequestWorkItem extends WorkItem
+{
+ /**
+ * Construct a SubscribeRequestWorkItem. Convenience constructor not in API
+ *
+ * @param handle the reply handle
+ * @param params the SubscriptionParams used to populate the WorkItem's param
+ */
+ public SubscribeRequestWorkItem(final Handle handle, final SubscriptionParams params)
+ {
+ super(WorkItemType.SUBSCRIBE_REQUEST, handle, params);
+ }
+
+ /**
+ * Return the SubscriptionParams stored in the params Map.
+ * @return the SubscriptionParams stored in the params Map.
+ */
+ public SubscriptionParams getSubscriptionParams()
+ {
+ return (SubscriptionParams)getParams();
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/Subscription.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/Subscription.java
new file mode 100644
index 0000000000..2f49702c7c
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/Subscription.java
@@ -0,0 +1,280 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.agent;
+
+// Simple Logging Facade 4 Java
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TimerTask;
+import java.util.UUID;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.Handle;
+import org.apache.qpid.qmf2.common.QmfException;
+import org.apache.qpid.qmf2.common.QmfQuery;
+import org.apache.qpid.qmf2.common.QmfQueryTarget;
+
+/**
+ * This TimerTask represents a running Subscription on the Agent.
+ * <p>
+ * The main reason we have Subscriptions as TimerTasks is to enable proper cleanup of the references stored in
+ * the _subscriptions Map when the Subscription expires. The timer also causes QmfAgenData that have been updated
+ * since the last interval to be published.
+ * <p>
+ * The following diagram illustrates the Subscription relationships with the Agent and QmfAgentData.
+ * <p>
+ * <img alt="" src="doc-files/Subscriptions.png">
+ * @author Fraser Adams
+ */
+public final class Subscription extends TimerTask
+{
+ private static final Logger _log = LoggerFactory.getLogger(Subscription.class);
+
+ // Duration is the time (in seconds) the Subscription is active before it automatically expires unless refreshed
+ private static final int DEFAULT_DURATION = 300;
+ private static final int MAX_DURATION = 3600;
+ private static final int MIN_DURATION = 10;
+
+ // Interval is the period (in milliseconds) between subscription ubdates.
+ private static final int DEFAULT_INTERVAL = 30000;
+ private static final int MIN_INTERVAL = 1000;
+
+ private SubscribableAgent _agent;
+ private long _startTime = System.currentTimeMillis();
+ private long _lastUpdate = _startTime*1000000l;
+ private String _subscriptionId;
+ private Handle _consoleHandle;
+ private QmfQuery _query;
+ private long _duration = 0;
+ private long _interval = 0;
+
+ /**
+ * Tells the SubscribableAgent to send the results to the Console via a subscription indicate message.
+ *
+ * @param results the list of mapEncoded QmfAgentData that currently match the query associated with this
+ * Subscription.
+ */
+ protected void publish(List<Map> results)
+ {
+ _agent.sendSubscriptionIndicate(_consoleHandle, results);
+ _lastUpdate = System.currentTimeMillis()*1000000l;
+ }
+
+ /**
+ * Construct a new Subscription.
+ * @param agent the SubscribableAgent to which this Subscription is associated.
+ * @param params the SubscriptionParams object that contains the information needed to create a Subscription.
+ */
+ public Subscription(SubscribableAgent agent, SubscriptionParams params) throws QmfException
+ {
+ _agent = agent;
+ _subscriptionId = UUID.randomUUID().toString();
+ _consoleHandle = params.getConsoleHandle();
+ _query = params.getQuery();
+ setDuration(params.getLifetime());
+ setInterval(params.getPublishInterval());
+
+ _log.debug("Creating Subscription {}, duration = {}, interval = {}", new Object[] {_subscriptionId, _duration, _interval});
+ }
+
+ /**
+ * This method gets called periodically by the Timer scheduling this TimerTask.
+ * <p>
+ * First a check is made to see if the Subscription has expired, if it has then it is cancelled.
+ * <p>
+ * If the Subscription isn't cancelled the Query gets evaluated against all registered objects and any that match
+ * which are new to the Subscription or have changed since the last update get published.
+ */
+ public void run()
+ {
+ long elapsed = (long)Math.round((System.currentTimeMillis() - _startTime)/1000.0f);
+ if (elapsed >= _duration)
+ {
+ _log.debug("Subscription {} has expired, removing", _subscriptionId);
+ // The Subscription has expired so cancel it
+ cancel();
+ }
+ else
+ {
+ List<QmfAgentData> objects = _agent.evaluateQuery(_query);
+ List<Map> results = new ArrayList<Map>(objects.size());
+ for (QmfAgentData object : objects)
+ {
+ if (object.getSubscription(_subscriptionId) == null)
+ {
+ // The object is new to this Subscription so publish it
+ object.addSubscription(_subscriptionId, this);
+ results.add(object.mapEncode());
+ }
+ else
+ {
+ // If the object has had update() called since last Subscription update publish it.
+ // Note that in many cases an Agent might call publish() on a managed object rather than
+ // update() which immediately forces a data indication to be sent to the subscriber on
+ // the Console.
+ if (object.getUpdateTime() > _lastUpdate)
+ {
+ results.add(object.mapEncode());
+ }
+ }
+ }
+
+ if (results.size() > 0)
+ {
+ publish(results);
+ }
+ }
+ }
+
+ /**
+ * Refresh the subscription by zeroing its elapsed time.
+ *
+ * @param resubscribeParams the ResubscribeParams passed by the Console potentially containing new duration
+ * information.
+ */
+ public void refresh(ResubscribeParams resubscribeParams)
+ {
+ _log.debug("Refreshing Subscription {}", _subscriptionId);
+ _startTime = System.currentTimeMillis();
+ setDuration(resubscribeParams.getLifetime());
+ }
+
+ /**
+ * Cancel the Subscription, tidying references up and cancelling the TimerTask.
+ */
+ @Override
+ public boolean cancel()
+ {
+ _log.debug("Cancelling Subscription {}", _subscriptionId);
+ // This Subscription is about to be deleted, remove it from any Objects that may be referencing it.
+ List<QmfAgentData> objects = _agent.evaluateQuery(_query);
+ for (QmfAgentData object : objects)
+ {
+ object.removeSubscription(_subscriptionId);
+ }
+
+ _agent.removeSubscription(this);
+ return super.cancel(); // Cancel the TimerTask
+ }
+
+ /**
+ * Return the SubscriptionId of this subscription.
+ * @return the SubscriptionId of this subscription.
+ */
+ public String getSubscriptionId()
+ {
+ return _subscriptionId;
+ }
+
+ /**
+ * Return the consoleHandle of this subscription.
+ * @return the consoleHandle of this subscription.
+ */
+ public Handle getConsoleHandle()
+ {
+ return _consoleHandle;
+ }
+
+ /**
+ * Set the Subscription lifetime in seconds. If the value passed to this method is zero the duration gets
+ * set to the Agent's DEFAULT_DURATION is the duration has not already been set, if the duration has already
+ * been set passing in a zero value has no effect on the duration.
+ * If the value passed is non-zero the duration passed gets restricted between the Agent's MIN_DURATION
+ * and MAX_DURATION.
+ *
+ * @param duration the new Subscription lifetime in seconds.
+ */
+ public void setDuration(long duration)
+ {
+ if (duration == 0)
+ {
+ if (_duration == 0)
+ {
+ _duration = DEFAULT_DURATION;
+ }
+ return;
+ }
+ else
+ {
+ if (duration > MAX_DURATION)
+ {
+ duration = MAX_DURATION;
+ }
+ else if (duration < MIN_DURATION)
+ {
+ duration = MIN_DURATION;
+ }
+ }
+ _duration = duration;
+ }
+
+ /**
+ * Return the current Subscription lifetime value in seconds.
+ * @return the current Subscription lifetime value in seconds.
+ */
+ public long getDuration()
+ {
+ return _duration;
+ }
+
+ /**
+ * Set the Subscription refresh interval in seconds. If the value passed to this method is zero the interval gets
+ * set to the Agent's DEFAULT_INTERVAL otherwise the interval passed gets restricted to be {@literal >= } the Agent's
+ * MIN_INTERVAL.
+ *
+ * @param interval the time (in milliseconds) between periodic updates of data in this Subscription.
+ */
+ public void setInterval(long interval)
+ {
+ if (interval == 0)
+ {
+ interval = DEFAULT_INTERVAL;
+ }
+ else if (interval < MIN_INTERVAL)
+ {
+ interval = MIN_INTERVAL;
+ }
+ _interval = interval;
+ }
+
+ /**
+ * Return The time (in milliseconds) between periodic updates of data in this Subscription.
+ * @return The time (in milliseconds) between periodic updates of data in this Subscription.
+ */
+ public long getInterval()
+ {
+ return _interval;
+ }
+
+ /**
+ * Return The Subscription's QmfQuery.
+ * @return The Subscription's QmfQuery.
+ */
+ public QmfQuery getQuery()
+ {
+ return _query;
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/SubscriptionParams.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/SubscriptionParams.java
new file mode 100644
index 0000000000..1cc2a9a5d1
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/SubscriptionParams.java
@@ -0,0 +1,102 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.agent;
+
+import java.util.Map;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.Handle;
+import org.apache.qpid.qmf2.common.QmfData;
+import org.apache.qpid.qmf2.common.QmfException;
+import org.apache.qpid.qmf2.common.QmfQuery;
+
+/**
+ * Holds the information contained in a subscription request made by a Console to an Agent
+ *
+ * @author Fraser Adams
+ */
+public final class SubscriptionParams extends QmfData
+{
+ private final Handle _consoleHandle;
+
+ /**
+ * Construct SubscriptionParams.
+ *
+ * @param handle the handle that the console uses to identify this subscription.
+ * @param m the Map used to populate the SubscriptionParams state.
+ */
+ public SubscriptionParams(final Handle handle, final Map m)
+ {
+ super(m);
+ _consoleHandle = handle;
+ }
+
+ /**
+ * Return the handle that the console uses to identify this subscription.
+ * @return the handle that the console uses to identify this subscription.
+ * <p>
+ * This handle must be passed along with every published update from the Agent.
+ */
+ public Handle getConsoleHandle()
+ {
+ return _consoleHandle;
+ }
+
+ /**
+ * Return the QmfQuery object associated with the SubscriptionParams.
+ * @return the QmfQuery object associated with the SubscriptionParams.
+ */
+ public QmfQuery getQuery() throws QmfException
+ {
+ return new QmfQuery((Map)getValue("_query"));
+ }
+
+ /**
+ * Return the requested time interval in seconds for updates.
+ * @return the requested time interval in seconds for updates. Zero if the Agent's default interval should be used.
+ */
+ public long getPublishInterval()
+ {
+ return getLongValue("_interval");
+ }
+
+ /**
+ * Return the requested lifetime for the subscription.
+ * @return the requested lifetime for the subscription. Zero if the Agent's default subscription lifetime
+ * should be used.
+ */
+ public long getLifetime()
+ {
+ return getLongValue("_duration");
+ }
+
+ /**
+ * Return authenticated user id of caller if present, else null.
+ * @return authenticated user id of caller if present, else null.
+ */
+ public String getUserId()
+ {
+ return getStringValue("_user_id");
+ }
+}
+
+
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/UnsubscribeRequestWorkItem.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/UnsubscribeRequestWorkItem.java
new file mode 100644
index 0000000000..45f316b1b4
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/UnsubscribeRequestWorkItem.java
@@ -0,0 +1,65 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.agent;
+
+import java.util.Map;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.Handle;
+import org.apache.qpid.qmf2.common.WorkItem;
+
+/**
+ * Descriptions below are taken from <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API Proposal</a>
+ * <pre>
+ * UNSUBSCRIBE_REQUEST: The UNSUBSCRIBE_REQUEST is sent by a Console to terminate an existing subscription. The Agent
+ * application should terminate the given subscription if it exists, and cancel sending any further
+ * updates against it.
+ *
+ * The getParams() method of a UNSUBSCRIBE_REQUEST WorkItem will return a String holding the
+ * subscriptionId
+ *
+ * The getHandle() method returns null.
+ * </pre>
+ * @author Fraser Adams
+ */
+
+public final class UnsubscribeRequestWorkItem extends WorkItem
+{
+ /**
+ * Construct an UnsubscribeRequestWorkItem. Convenience constructor not in API
+ *
+ * @param params the ResubscribeParams used to populate the WorkItem's param
+ */
+ public UnsubscribeRequestWorkItem(final String params)
+ {
+ super(WorkItemType.UNSUBSCRIBE_REQUEST, null, params);
+ }
+
+ /**
+ * Return the subscriptionId String stored in the params Map.
+ * @return the subscriptionId String stored in the params Map.
+ */
+ public String getSubscriptionId()
+ {
+ return (String)getParams();
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/QmfData.png b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/QmfData.png
new file mode 100644
index 0000000000..2665803e39
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/QmfData.png
Binary files differ
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/QmfEventListenerModel.png b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/QmfEventListenerModel.png
new file mode 100644
index 0000000000..26a5f71b56
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/QmfEventListenerModel.png
Binary files differ
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/QmfQuery.png b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/QmfQuery.png
new file mode 100644
index 0000000000..9e471a08c0
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/QmfQuery.png
Binary files differ
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/Schema.png b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/Schema.png
new file mode 100644
index 0000000000..b0277f4fc5
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/Schema.png
Binary files differ
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/Subscriptions.png b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/Subscriptions.png
new file mode 100644
index 0000000000..d43370e4ef
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/Subscriptions.png
Binary files differ
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/WorkQueueEventModel.png b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/WorkQueueEventModel.png
new file mode 100644
index 0000000000..fc2a722985
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/WorkQueueEventModel.png
Binary files differ
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/AMQPMessage.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/AMQPMessage.java
new file mode 100644
index 0000000000..94b539c0b3
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/AMQPMessage.java
@@ -0,0 +1,309 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// JMS Imports
+import javax.jms.BytesMessage;
+import javax.jms.JMSException;
+import javax.jms.MapMessage;
+import javax.jms.Message;
+import javax.jms.MessageFormatException;
+import javax.jms.Session;
+
+// Misc Imports
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
+
+// Need the following to decode and encode amqp/list messages
+import java.nio.ByteBuffer;
+import org.apache.qpid.transport.codec.BBDecoder;
+import org.apache.qpid.transport.codec.BBEncoder;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.QmfData;
+
+/**
+ * Provides static helper methods for encoding and decoding "amqp/list" and "amqp/map" ContentTypes.
+ *<p>
+ * Unfortunately the encoding of amqp/map and amqp/list messages is not as useful as it might be in the
+ * Qpid JMS runtime. amqp/list messages don't <i>actually</i> have a useful encoding in Qpid JMS, so we have to
+ * fake it in this class by encoding/decoding java.util.List objects into a JMS BytesMessage and setting
+ * the ContentType to "amqp/list".
+ *<p>
+ * Whilst amqp/map messages are encoded as JMS MapMessage this isn't necessarily the most useful format as
+ * MapMessage does not conform to the java.util.Map interface. As QMF methods returning lists return lists
+ * of java.util.Map there's a bit of an inconsistency of type that getMap() resolves.
+ *
+ * @author Fraser Adams
+ */
+public final class AMQPMessage
+{
+ /**
+ * Make constructor private at this class provides a set of static helper methods and doesn't need instantiated.
+ */
+ private AMQPMessage()
+ {
+ }
+
+ /**
+ * This method exposes the AMQP Content-Type from a JMS Message. This has been put into an accessor
+ * method because some evil hackery has to take place to set the Content-Type as no pure JMS API
+ * property currently gets mapped to Content-Type, so we have to cast to AbstractJMSMessage.
+ *
+ * @param message a JMS Message.
+ * @return the AMQP Content-Type e.g. amqp/list, amqp/map etc.
+ */
+ public static String getContentType(final Message message)
+ {
+ return ((org.apache.qpid.client.message.AbstractJMSMessage)message).getContentType();
+ }
+
+ /**
+ * This method sets the AMQP Content-Type on a JMS Message. This has been put into a mutator
+ * method because some evil hackery has to take place to set the Content-Type as no pure JMS API
+ * property currently gets mapped to Content-Type, so we have to cast to AbstractJMSMessage.
+ *
+ * @param message a JMS Message.
+ * @param contentType the AMQP Content-Type that we'd like to set, e.g. amqp/list, amqp/map etc.
+ */
+ public static void setContentType(final Message message, String contentType)
+ {
+ ((org.apache.qpid.client.message.AbstractJMSMessage)message).setContentType(contentType);
+ }
+
+ /**
+ * Provides an abstracted way for client code to explicitly check if a Message is an AMQP List.
+ *
+ * @param message a JMS Message.
+ * @return true if the Message is an AMQP List, otherwise returns false.
+ */
+ public static boolean isAMQPList(final Message message)
+ {
+ if (getContentType(message).equals("amqp/list") || message instanceof BytesMessage)
+ {
+ // I *think* that the test for BytesMessage is actually redundant and that Content-Type would
+ // always have to be "amqp/list" for JMS to expose the Message as a BytesMessage but I've
+ // kept the test because pre Qpid 0.20 exposed lists as BytesMessage.
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Provides an abstracted way for client code to explicitly check if a Message is an AMQP Map.
+ *
+ * @param message a JMS Message.
+ * @return true if the Message is an AMQP Map, otherwise returns false.
+ */
+ public static boolean isAMQPMap(final Message message)
+ {
+ if (getContentType(message).equals("amqp/map") ||
+ (message instanceof MapMessage && !isAMQPList(message)))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Builds a java.util.Map from a JMS MapMessage.
+ * This is really a helper method to make code more homogenous as QmfData objects are constructed from Maps
+ * but JMS returns MapMessages which don't share a common interface. This method enumerates MapMessage
+ * Properties and Objects and stores them in a java.util.Map.
+ *
+ * @param message a JMS Message
+ * @return a java.util.Map containing the the properties extracted from the Message.
+ * <p>
+ * Note that this method copies the Message properties <b>and</b> the properties from the MapMessage Map.
+ * <p>
+ * This method also attempts to populate "_user_id" using the JMSXUserID property, however that's not as
+ * easy as it sounds!! There's a bug in AMQMessageDelegate_0_10.getStringProperty() whereby if the property
+ * is "JMSXUserID" it returns "new String(_messageProps.getUserId());" however if the client uses anonymous
+ * authentication _messageProps.getUserId() returns null. In order to get around this this class unfortunately
+ * has to delve inside "org.apache.qpid.client.message.AbstractJMSMessage".
+ */
+ public static Map<String, Object> getMap(final Message message) throws JMSException
+ {
+ if (message == null)
+ {
+ throw new MessageFormatException("Attempting to do AMQPMessage.getMap() on null Message");
+ }
+ else if (message instanceof MapMessage)
+ {
+ Map<String, Object> object = new HashMap<String, Object>();
+ MapMessage msg = (MapMessage)message;
+ for (Enumeration e = msg.getMapNames(); e.hasMoreElements();)
+ {
+ String key = (String)e.nextElement();
+ object.put(key, msg.getObject(key));
+ }
+
+ if (object.size() == 0)
+ { // If there is no MapMessage content return an empty Map.
+ return object;
+ }
+
+ // If there is MapMessage content include the Message properties in the returned Map.
+ for (Enumeration e = msg.getPropertyNames(); e.hasMoreElements();)
+ {
+ String prop = (String)e.nextElement();
+ object.put(prop, QmfData.getString(msg.getObjectProperty(prop)));
+ }
+
+ // Should be msg.getStringProperty("JMSXUserID"). See comments above for the reason behind this evil hack.
+ org.apache.qpid.client.message.AMQMessageDelegate_0_10 delegate = (org.apache.qpid.client.message.AMQMessageDelegate_0_10)(((org.apache.qpid.client.message.AbstractJMSMessage)msg).getDelegate());
+ byte[] rawUserId = delegate.getMessageProperties().getUserId();
+ if (rawUserId != null)
+ {
+ String userId = new String(rawUserId);
+ object.put("_user_id", userId);
+ }
+
+ return object;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * JMS QMF returns amqp/list types as a BytesMessage this method decodes that into a java.util.List
+ * <p>
+ * Taken from Gordon Sim's initial JMS QMF Example using the BBDecoder
+ * <p>
+ * Trivia: This block of code from Gordon Sim is the seed that spawned the whole of this Java QMF2 API
+ * implementation - cheers Gordon.
+ *
+ * @param message amqp/list encoded JMS Message
+ * @return a java.util.List decoded from Message
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> List<T> getList(final Message message) throws JMSException
+ {
+ if (message == null)
+ {
+ throw new MessageFormatException("Attempting to do AMQPMessage.getList() on null Message");
+ }
+ else if (message instanceof BytesMessage)
+ {
+ BytesMessage msg = (BytesMessage)message;
+
+ //only handles responses up to 2^31-1 bytes long
+ byte[] data = new byte[(int) msg.getBodyLength()];
+ msg.readBytes(data);
+ BBDecoder decoder = new BBDecoder();
+ decoder.init(ByteBuffer.wrap(data));
+ return (List<T>)decoder.readList();
+ }
+ else if (message instanceof MapMessage)
+ { /*
+ * In Qpid version 0.20 instead of exposing amqp/list as a BytesMessage as above rather it is exposed
+ * as a MapMessage!!??? the Object Keys are the indices into the List. We create a java.util.List
+ * out of this by iterating through the getMapNames() Enumeration and copying the Objects into the List.
+ * This amount of copying doesn't feel healthy and we can't even work out the capacity for the List
+ * a priori, but I'm not sure of a better way at present. I can't say I much like how amqp/list or indeed
+ * amqp/map are currently encoded. I'd *much* prefer to see them exposed as JMS ObjectMessage.
+ */
+ MapMessage msg = (MapMessage)message;
+ List resultList = new ArrayList(50); // Initial capacity of 50, can we better estimate this?
+
+ for (Enumeration e = msg.getMapNames(); e.hasMoreElements();)
+ {
+ String key = (String)e.nextElement();
+ resultList.add(msg.getObject(key));
+ }
+ return resultList;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Creates an amqp/list encoded Message out of a BytesMessage.
+ * <p>
+ * This is somewhat of a dirty hack that needs to be monitored as qpid versions change.
+ * <p>
+ * Unfortunately there's no "clean" way to encode or decode amqp/list messages via the JMS API.
+ *
+ * @param session used to create the JMS Message
+ * @return an amqp/list encoded JMS Message
+ */
+ public static Message createListMessage(final Session session) throws JMSException
+ {
+ BytesMessage message = session.createBytesMessage();
+ setContentType(message, "amqp/list");
+ return message;
+ }
+
+ /**
+ * Encodes a java.util.List on an amqp/list encoded BytesMessage.
+ * <p>
+ * This is somewhat of a dirty hack that needs to be monitored as qpid versions change.
+ * <p>
+ * This method uses the org.apache.qpid.transport.codec.BBEncoder writeList() method to encode
+ * a List into a ByteBuffer then writes the bytes from the buffer into a JMS BytesMessage.
+ *
+ * @param message amqp/list encoded JMS BytesMessage
+ * @param list to encode into JMS Message
+ */
+ @SuppressWarnings("unchecked")
+ public static void setList(final Message message, final List list) throws JMSException
+ {
+ String type = getContentType(message);
+ if (!type.equals("amqp/list"))
+ {
+ throw new MessageFormatException("Can only do setList() on amqp/list encoded Message");
+ }
+
+ if (message == null)
+ {
+ throw new MessageFormatException("Attempting to do AMQPMessage.setList() on null Message");
+ }
+ else if (message instanceof BytesMessage)
+ {
+ BBEncoder encoder = new BBEncoder(1024);
+ encoder.writeList(list);
+ ByteBuffer buf = encoder.segment();
+ byte[] data = new byte[buf.limit()];
+ buf.get(data);
+ ((BytesMessage)message).writeBytes(data);
+ }
+ else
+ {
+ throw new MessageFormatException("Attempting to do setList() on " + message.getClass().getCanonicalName());
+ }
+ }
+}
+
+
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BlockingNotifier.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BlockingNotifier.java
new file mode 100644
index 0000000000..634a5e60b0
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BlockingNotifier.java
@@ -0,0 +1,65 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+/**
+ * Implementation of the Notifier Interface that provides a waitForWorkItem() method which blocks until the
+ * Console has called the indication() method indicating that there are WorkItems available.
+ * <p>
+ * This class isn't part of the QMF2 API however it's almost certainly how most clients would choose to use the
+ * Notifier API so it seems useful to provide an implementation.
+ *
+ * @author Fraser Adams
+ */
+public final class BlockingNotifier implements Notifier
+{
+ private boolean _waiting = true;
+
+ /**
+ * This method blocks until the indication() method has been called, this is generally called by the Console
+ * when new WorkItems are made available.
+ */
+ public synchronized void waitForWorkItem()
+ {
+ while (_waiting)
+ {
+ try
+ {
+ wait();
+ }
+ catch (InterruptedException ie)
+ {
+ continue;
+ }
+ }
+ _waiting = true;
+ }
+
+ /**
+ * Called to indicate the availability of WorkItems. This method unblocks waitForWorkItem()
+ */
+ public synchronized void indication()
+ {
+ _waiting = false;
+ notifyAll();
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanEquals.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanEquals.java
new file mode 100644
index 0000000000..4eba04cc03
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanEquals.java
@@ -0,0 +1,79 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.List;
+
+/**
+ * A class to create and evaluate the BooleanEquals Expression
+ *
+ * @author Fraser Adams
+ */
+public final class BooleanEquals extends BooleanExpression
+{
+ /**
+ * Factory method to create an instance of BooleanEquals
+ * @param expr the List of Expressions extracted by parsing the Query predicate
+ * @return an instance of the concrete BooleanExpression
+ */
+ public Expression create(final List expr) throws QmfException
+ {
+ return new BooleanEquals(expr);
+ }
+
+ /**
+ * Basic Constructor primarily used by the prototype instance of each concrete BooleanExpression
+ */
+ public BooleanEquals()
+ {
+ }
+
+ /**
+ * Main Constructor, uses base class constructor to populate unevaluated operands
+ * @param expr the List of Expressions extracted by parsing the Query predicate
+ */
+ public BooleanEquals(final List expr) throws QmfException
+ {
+ super(2, expr);
+ }
+
+ /**
+ * Evaluate "equal to" expression against a QmfData instance.
+ * N.B. to avoid complexities with types this class treats operands as Strings performing an appropriate evaluation
+ * of the String that makes sense for a given expression e.g. parsing as a double for {@literal >, >=, <, <= }
+ *
+ * @param data the object to evaluate the expression against
+ * @return true if query matches the QmfData instance, else false.
+ */
+ public boolean evaluate(final QmfData data)
+ {
+ populateOperands(data);
+
+ if (_operands[0] == null || _operands[1] == null)
+ {
+ return false;
+ }
+
+ return _operands[0].equals(_operands[1]);
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanExists.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanExists.java
new file mode 100644
index 0000000000..522748ebb6
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanExists.java
@@ -0,0 +1,72 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.List;
+
+/**
+ * A class to create and evaluate the BooleanExists Expression
+ *
+ * @author Fraser Adams
+ */
+public final class BooleanExists extends BooleanExpression
+{
+ /**
+ * Factory method to create an instance of BooleanExists
+ * @param expr the List of Expressions extracted by parsing the Query predicate
+ * @return an instance of the concrete BooleanExpression
+ */
+ public Expression create(final List expr) throws QmfException
+ {
+ return new BooleanExists(expr);
+ }
+
+ /**
+ * Basic Constructor primarily used by the prototype instance of each concrete BooleanExpression
+ */
+ public BooleanExists()
+ {
+ }
+
+ /**
+ * Main Constructor, uses base class constructor to populate unevaluated operands
+ * @param expr the List of Expressions extracted by parsing the Query predicate
+ */
+ public BooleanExists(final List expr) throws QmfException
+ {
+ super(1, expr);
+ }
+
+ /**
+ * Evaluate "exists" expression against a QmfData instance.
+ * N.B. to avoid complexities with types this class treats operands as Strings performing an appropriate evaluation
+ * of the String that makes sense for a given expression e.g. parsing as a double for {@literal >, >=, <, <= }
+ *
+ * @param data the object to evaluate the expression against
+ * @return true if query matches the QmfData instance, else false.
+ */
+ public boolean evaluate(final QmfData data)
+ {
+ return _operands[0] != null;
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanExpression.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanExpression.java
new file mode 100644
index 0000000000..999dd6d222
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanExpression.java
@@ -0,0 +1,221 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class represents the base class for all Boolean Expressions created by expanding the Query predicate.
+ *
+ * @author Fraser Adams
+ */
+public abstract class BooleanExpression extends Expression
+{
+ private static Map<String, BooleanExpression> _factories = new HashMap<String, BooleanExpression>();
+ protected String[] _operands;
+ private String[] _keys;
+
+ /**
+ * Initialise the _factories Map, which contains the prototype instances of each concrete BooleanExpression
+ * keyed by the operator String.
+ */
+ static
+ {
+ _factories.put("eq", new BooleanEquals());
+ _factories.put("ne", new BooleanNotEquals());
+ _factories.put("lt", new BooleanLessThan());
+ _factories.put("le", new BooleanLessEqual());
+ _factories.put("gt", new BooleanGreaterThan());
+ _factories.put("ge", new BooleanGreaterEqual());
+ _factories.put("re_match", new BooleanRegexMatch());
+ _factories.put("exists", new BooleanExists());
+ _factories.put("true", new BooleanTrue());
+ _factories.put("false", new BooleanFalse());
+ }
+
+ /**
+ * Factory method to create concrete Expression instances based on the operator name extracted from the expression List.
+ * This method will create a BooleanExpression from an "eq", "ne", "lt" etc. operator using the prototype
+ * obtained from the _factories Map.
+ *
+ * @param expr the List of Expressions extracted by parsing the Query predicate
+ */
+ public static Expression createExpression(final List expr) throws QmfException
+ {
+ Iterator iter = expr.listIterator();
+ if (!iter.hasNext())
+ {
+ throw new QmfException("Missing operator in predicate expression");
+ }
+
+ String op = (String)iter.next();
+ BooleanExpression factory = _factories.get(op);
+ if (factory == null)
+ {
+ throw new QmfException("Unknown operator in predicate expression");
+ }
+
+ return factory.create(expr);
+ }
+
+ /**
+ * Factory method to create a concrete instance of BooleanExpression
+ * @param expr the List of Expressions extracted by parsing the Query predicate
+ * @return an instance of the concrete BooleanExpression
+ */
+ public abstract Expression create(final List expr) throws QmfException;
+
+ /**
+ * Basic Constructor primarily used by the prototype instance of each concrete BooleanExpression
+ */
+ protected BooleanExpression()
+ {
+ }
+
+ /**
+ * Main Constructor, used to populate unevaluated operands. This loops through the input expression list. If the
+ * Object is a String is is treated as a key such that when the expression is evaluated the key will be used to
+ * obtain a propery from the QmfData object. If the Object is a sub-List it is checked to see if it's a quoted
+ * String, if it is the quoted String is stored as the operand. If it's neither of these the actual object from
+ * the expression List is used as the operand.
+ *
+ * @param operandCount the number of operands in this Expression, the value is generally passed by the subclass.
+ * @param expr the List of Expressions extracted by parsing the Query predicate
+ */
+ protected BooleanExpression(final int operandCount, final List expr) throws QmfException
+ {
+ Iterator iter = expr.listIterator();
+ String op = (String)iter.next(); // We've already tested for hasNext() in the factory
+
+ _operands = new String[operandCount];
+ _keys = new String[operandCount];
+
+ for (int i = 0; i < operandCount; i++)
+ {
+ if (!iter.hasNext())
+ {
+ throw new QmfException("Too few operands for operation: " + op);
+ }
+
+ Object object = iter.next();
+ _operands[i] = object.toString();
+
+ if (object instanceof String)
+ {
+ _keys[i] = _operands[i];
+ _operands[i] = null;
+ }
+ else if (object instanceof List)
+ {
+ List sublist = (List)object;
+ Iterator subiter = sublist.listIterator();
+
+ if (subiter.hasNext() && ((String)subiter.next()).equals("quote"))
+ {
+ if (subiter.hasNext())
+ {
+ _operands[i] = subiter.next().toString();
+ if (subiter.hasNext())
+ {
+ throw new QmfException("Extra tokens at end of 'quote'");
+ }
+ }
+ }
+ else
+ {
+ throw new QmfException("Expected '[quote, <token>]'");
+ }
+ }
+ }
+
+ if (iter.hasNext())
+ {
+ throw new QmfException("Too many operands for operation: " + op);
+ }
+ }
+
+ /**
+ * Populates operands that are obtained at evaluation time. In other words operands that are obtained by using
+ * the key obtained from the static operand evaluation to look up an associated property from the QmfData object.
+ * @param data the object to extract the operand(s) from
+ */
+ protected void populateOperands(final QmfData data)
+ {
+ for (int i = 0; i < _operands.length; i++)
+ {
+ String key = _keys[i];
+ if (key != null)
+ {
+ String value = null;
+
+ if (data.hasValue(key))
+ { // If there's a property of the data object named key look it up as a String
+ value = data.getStringValue(key);
+ }
+ else
+ { // If there's no property of the data object named key look up its Described/Managed metadata
+ if (data instanceof QmfManaged)
+ {
+ QmfManaged managedData = (QmfManaged)data;
+ if (key.equals("_schema_id"))
+ {
+ value = managedData.getSchemaClassId().toString();
+ }
+ else if (key.equals("_object_id"))
+ {
+ value = managedData.getObjectId().toString();
+ }
+ else if (managedData.getSchemaClassId().hasValue(key))
+ { // If it's not _schema_id or _object_id check the SchemaClassId properties e.g.
+ // _package_name, _class_name, _type or _hash
+ value = managedData.getSchemaClassId().getStringValue(key);
+ }
+ }
+
+ if (value == null)
+ { // If a value still can't be found for the key check if it's available in the mapEncoded form
+ Map m = data.mapEncode();
+ if (m.containsKey(key))
+ {
+ value = QmfData.getString(m.get(key));
+ }
+ }
+ }
+
+ _operands[i] = value;
+ }
+//System.out.println("key: " + key + ", operand = " + _operands[i]);
+ }
+ }
+
+ /**
+ * Evaluate expression against a QmfData instance.
+ * @param data the object to evaluate the expression against
+ * @return true if query matches the QmfData instance, else false.
+ */
+ public abstract boolean evaluate(final QmfData data);
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanFalse.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanFalse.java
new file mode 100644
index 0000000000..00080dfb10
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanFalse.java
@@ -0,0 +1,60 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.List;
+
+/**
+ * A class to create and evaluate the BooleanFalse Expression
+ *
+ * @author Fraser Adams
+ */
+public final class BooleanFalse extends BooleanExpression
+{
+ /**
+ * Factory method to create an instance of BooleanFalse
+ * @param expr the List of Expressions extracted by parsing the Query predicate
+ * @return an instance of the concrete BooleanExpression
+ */
+ public Expression create(final List expr) throws QmfException
+ {
+ return new BooleanFalse();
+ }
+
+ /**
+ * Basic Constructor primarily used by the prototype instance of each concrete BooleanExpression
+ */
+ public BooleanFalse()
+ {
+ }
+
+ /**
+ * Evaluate "false" expression against a QmfData instance.
+ * @param data the object to evaluate the expression against
+ * @return false.
+ */
+ public boolean evaluate(final QmfData data)
+ {
+ return false;
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanGreaterEqual.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanGreaterEqual.java
new file mode 100644
index 0000000000..7aaeb8e687
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanGreaterEqual.java
@@ -0,0 +1,89 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.List;
+
+/**
+ * A class to create and evaluate the BooleanGreaterEqual Expression
+ *
+ * @author Fraser Adams
+ */
+public final class BooleanGreaterEqual extends BooleanExpression
+{
+ /**
+ * Factory method to create an instance of BooleanGreaterEqual
+ * @param expr the List of Expressions extracted by parsing the Query predicate
+ * @return an instance of the concrete BooleanExpression
+ */
+ public Expression create(final List expr) throws QmfException
+ {
+ return new BooleanGreaterEqual(expr);
+ }
+
+ /**
+ * Basic Constructor primarily used by the prototype instance of each concrete BooleanExpression
+ */
+ public BooleanGreaterEqual()
+ {
+ }
+
+ /**
+ * Main Constructor, uses base class constructor to populate unevaluated operands
+ * @param expr the List of Expressions extracted by parsing the Query predicate
+ */
+ public BooleanGreaterEqual(final List expr) throws QmfException
+ {
+ super(2, expr);
+ }
+
+ /**
+ * Evaluate "greater than or equal to" expression against a QmfData instance.
+ * N.B. to avoid complexities with types this class treats operands as Strings performing an appropriate evaluation
+ * of the String that makes sense for a given expression e.g. parsing as a double for {@literal >, >=, <, <= }
+ *
+ * @param data the object to evaluate the expression against
+ * @return true if query matches the QmfData instance, else false.
+ */
+ public boolean evaluate(final QmfData data)
+ {
+ populateOperands(data);
+
+ if (_operands[0] == null || _operands[1] == null)
+ {
+ return false;
+ }
+
+ try
+ {
+ double l = Double.parseDouble(_operands[0]);
+ double r = Double.parseDouble(_operands[1]);
+ return l >= r;
+ }
+ catch (NumberFormatException nfe)
+ {
+ // If converting to double fails try a lexicographic comparison
+ return _operands[0].compareTo(_operands[1]) >= 0;
+ }
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanGreaterThan.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanGreaterThan.java
new file mode 100644
index 0000000000..2ac44f35ec
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanGreaterThan.java
@@ -0,0 +1,89 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.List;
+
+/**
+ * A class to create and evaluate the BooleanGreaterThan Expression
+ *
+ * @author Fraser Adams
+ */
+public final class BooleanGreaterThan extends BooleanExpression
+{
+ /**
+ * Factory method to create an instance of BooleanGreaterThan
+ * @param expr the List of Expressions extracted by parsing the Query predicate
+ * @return an instance of the concrete BooleanExpression
+ */
+ public Expression create(final List expr) throws QmfException
+ {
+ return new BooleanGreaterThan(expr);
+ }
+
+ /**
+ * Basic Constructor primarily used by the prototype instance of each concrete BooleanExpression
+ */
+ public BooleanGreaterThan()
+ {
+ }
+
+ /**
+ * Main Constructor, uses base class constructor to populate unevaluated operands
+ * @param expr the List of Expressions extracted by parsing the Query predicate
+ */
+ public BooleanGreaterThan(final List expr) throws QmfException
+ {
+ super(2, expr);
+ }
+
+ /**
+ * Evaluate "greater than" expression against a QmfData instance.
+ * N.B. to avoid complexities with types this class treats operands as Strings performing an appropriate evaluation
+ * of the String that makes sense for a given expression e.g. parsing as a double for {@literal >, >=, <, <= }
+ *
+ * @param data the object to evaluate the expression against
+ * @return true if query matches the QmfData instance, else false.
+ */
+ public boolean evaluate(QmfData data)
+ {
+ populateOperands(data);
+
+ if (_operands[0] == null || _operands[1] == null)
+ {
+ return false;
+ }
+
+ try
+ {
+ double l = Double.parseDouble(_operands[0]);
+ double r = Double.parseDouble(_operands[1]);
+ return l > r;
+ }
+ catch (NumberFormatException nfe)
+ {
+ // If converting to double fails try a lexicographic comparison
+ return _operands[0].compareTo(_operands[1]) > 0;
+ }
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanLessEqual.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanLessEqual.java
new file mode 100644
index 0000000000..2461f116d9
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanLessEqual.java
@@ -0,0 +1,89 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.List;
+
+/**
+ * A class to create and evaluate the BooleanLessEqual Expression
+ *
+ * @author Fraser Adams
+ */
+public final class BooleanLessEqual extends BooleanExpression
+{
+ /**
+ * Factory method to create an instance of BooleanLessEqual
+ * @param expr the List of Expressions extracted by parsing the Query predicate
+ * @return an instance of the concrete BooleanExpression
+ */
+ public Expression create(final List expr) throws QmfException
+ {
+ return new BooleanLessEqual(expr);
+ }
+
+ /**
+ * Basic Constructor primarily used by the prototype instance of each concrete BooleanExpression
+ */
+ public BooleanLessEqual()
+ {
+ }
+
+ /**
+ * Main Constructor, uses base class constructor to populate unevaluated operands
+ * @param expr the List of Expressions extracted by parsing the Query predicate
+ */
+ public BooleanLessEqual(final List expr) throws QmfException
+ {
+ super(2, expr);
+ }
+
+ /**
+ * Evaluate "less than or equal to" expression against a QmfData instance.
+ * N.B. to avoid complexities with types this class treats operands as Strings performing an appropriate evaluation
+ * of the String that makes sense for a given expression e.g. parsing as a double for {@literal >, >=, <, <= }
+ *
+ * @param data the object to evaluate the expression against
+ * @return true if query matches the QmfData instance, else false.
+ */
+ public boolean evaluate(final QmfData data)
+ {
+ populateOperands(data);
+
+ if (_operands[0] == null || _operands[1] == null)
+ {
+ return false;
+ }
+
+ try
+ {
+ double l = Double.parseDouble(_operands[0]);
+ double r = Double.parseDouble(_operands[1]);
+ return l <= r;
+ }
+ catch (NumberFormatException nfe)
+ {
+ // If converting to double fails try a lexicographic comparison
+ return _operands[0].compareTo(_operands[1]) <= 0;
+ }
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanLessThan.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanLessThan.java
new file mode 100644
index 0000000000..1a65119e59
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanLessThan.java
@@ -0,0 +1,89 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.List;
+
+/**
+ * A class to create and evaluate the BooleanLessThan Expression
+ *
+ * @author Fraser Adams
+ */
+public final class BooleanLessThan extends BooleanExpression
+{
+ /**
+ * Factory method to create an instance of BooleanLessThan
+ * @param expr the List of Expressions extracted by parsing the Query predicate
+ * @return an instance of the concrete BooleanExpression
+ */
+ public Expression create(final List expr) throws QmfException
+ {
+ return new BooleanLessThan(expr);
+ }
+
+ /**
+ * Basic Constructor primarily used by the prototype instance of each concrete BooleanExpression
+ */
+ public BooleanLessThan()
+ {
+ }
+
+ /**
+ * Main Constructor, uses base class constructor to populate unevaluated operands
+ * @param expr the List of Expressions extracted by parsing the Query predicate
+ */
+ public BooleanLessThan(final List expr) throws QmfException
+ {
+ super(2, expr);
+ }
+
+ /**
+ * Evaluate "less than" expression against a QmfData instance.
+ * N.B. to avoid complexities with types this class treats operands as Strings performing an appropriate evaluation
+ * of the String that makes sense for a given expression e.g. parsing as a double for {@literal >, >=, <, <= }
+ *
+ * @param data the object to evaluate the expression against
+ * @return true if query matches the QmfData instance, else false.
+ */
+ public boolean evaluate(final QmfData data)
+ {
+ populateOperands(data);
+
+ if (_operands[0] == null || _operands[1] == null)
+ {
+ return false;
+ }
+
+ try
+ {
+ double l = Double.parseDouble(_operands[0]);
+ double r = Double.parseDouble(_operands[1]);
+ return l < r;
+ }
+ catch (NumberFormatException nfe)
+ {
+ // If converting to double fails try a lexicographic comparison
+ return _operands[0].compareTo(_operands[1]) < 0;
+ }
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanNotEquals.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanNotEquals.java
new file mode 100644
index 0000000000..a72a467c1f
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanNotEquals.java
@@ -0,0 +1,79 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.List;
+
+/**
+ * A class to create and evaluate the BooleanNotEquals Expression
+ *
+ * @author Fraser Adams
+ */
+public final class BooleanNotEquals extends BooleanExpression
+{
+ /**
+ * Factory method to create an instance of BooleanNotEquals
+ * @param expr the List of Expressions extracted by parsing the Query predicate
+ * @return an instance of the concrete BooleanExpression
+ */
+ public Expression create(final List expr) throws QmfException
+ {
+ return new BooleanNotEquals(expr);
+ }
+
+ /**
+ * Basic Constructor primarily used by the prototype instance of each concrete BooleanExpression
+ */
+ public BooleanNotEquals()
+ {
+ }
+
+ /**
+ * Main Constructor, uses base class constructor to populate unevaluated operands
+ * @param expr the List of Expressions extracted by parsing the Query predicate
+ */
+ public BooleanNotEquals(final List expr) throws QmfException
+ {
+ super(2, expr);
+ }
+
+ /**
+ * Evaluate "not equal to" expression against a QmfData instance.
+ * N.B. to avoid complexities with types this class treats operands as Strings performing an appropriate evaluation
+ * of the String that makes sense for a given expression e.g. parsing as a double for {@literal >, >=, <, <= }
+ *
+ * @param data the object to evaluate the expression against
+ * @return true if query matches the QmfData instance, else false.
+ */
+ public boolean evaluate(final QmfData data)
+ {
+ populateOperands(data);
+
+ if (_operands[0] == null || _operands[1] == null)
+ {
+ return false;
+ }
+
+ return !_operands[0].equals(_operands[1]);
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanRegexMatch.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanRegexMatch.java
new file mode 100644
index 0000000000..30311ba712
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanRegexMatch.java
@@ -0,0 +1,95 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * A class to create and evaluate the BooleanRegexMatch Expression
+ *
+ * @author Fraser Adams
+ */
+public final class BooleanRegexMatch extends BooleanExpression
+{
+ private final Pattern _pattern;
+
+ /**
+ * Factory method to create an instance of BooleanRegexMatch
+ * @param expr the List of Expressions extracted by parsing the Query predicate
+ * @return an instance of the concrete BooleanExpression
+ */
+ public Expression create(final List expr) throws QmfException
+ {
+ return new BooleanRegexMatch(expr);
+ }
+
+ /**
+ * Basic Constructor primarily used by the prototype instance of each concrete BooleanExpression
+ */
+ public BooleanRegexMatch()
+ {
+ _pattern = null;
+ }
+
+ /**
+ * Main Constructor, uses base class constructor to populate unevaluated operands
+ * @param expr the List of Expressions extracted by parsing the Query predicate
+ */
+ public BooleanRegexMatch(final List expr) throws QmfException
+ {
+ super(2, expr);
+
+ try
+ {
+ _pattern = Pattern.compile(_operands[1]);
+ }
+ catch (PatternSyntaxException pse)
+ {
+ throw new QmfException("Error in regular expression " + pse.getMessage());
+ }
+ }
+
+ /**
+ * Evaluate "regex match" expression against a QmfData instance.
+ * N.B. to avoid complexities with types this class treats operands as Strings performing an appropriate evaluation
+ * of the String that makes sense for a given expression e.g. parsing as a double for {@literal >, >=, <, <= }
+ *
+ * @param data the object to evaluate the expression against
+ * @return true if query matches the QmfData instance, else false.
+ */
+ public boolean evaluate(final QmfData data)
+ {
+ populateOperands(data);
+
+ if (_operands[0] == null || _operands[1] == null || _pattern == null)
+ {
+ return false;
+ }
+
+ Matcher matcher = _pattern.matcher(_operands[0]);
+ return matcher.find();
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanTrue.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanTrue.java
new file mode 100644
index 0000000000..b092e7daaf
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanTrue.java
@@ -0,0 +1,60 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.List;
+
+/**
+ * A class to create and evaluate the BooleanTrue Expression
+ *
+ * @author Fraser Adams
+ */
+public final class BooleanTrue extends BooleanExpression
+{
+ /**
+ * Factory method to create an instance of BooleanTrue
+ * @param expr the List of Expressions extracted by parsing the Query predicate
+ * @return an instance of the concrete BooleanExpression
+ */
+ public Expression create(final List expr) throws QmfException
+ {
+ return new BooleanTrue();
+ }
+
+ /**
+ * Basic Constructor primarily used by the prototype instance of each concrete BooleanExpression
+ */
+ public BooleanTrue()
+ {
+ }
+
+ /**
+ * Evaluate "true" expression against a QmfData instance.
+ * @param data the object to evaluate the expression against
+ * @return true.
+ */
+ public boolean evaluate(final QmfData data)
+ {
+ return true;
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/Expression.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/Expression.java
new file mode 100644
index 0000000000..4e92af911b
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/Expression.java
@@ -0,0 +1,77 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This class represents the base class for all Expressions created by expanding the Query predicate.
+ * <p>
+ * Depending on the structure of the expression list there might be a nested structure of Expressions comprising
+ * a mixture of LogicalExpressions and BooleanExpressions.
+ * <p>
+ * The Expression structure is illustrated below in the context of its relationship with QmfQuery.
+ * <img alt="" src="doc-files/QmfQuery.png">
+ *
+ * @author Fraser Adams
+ */
+public abstract class Expression
+{
+ /**
+ * Factory method to create concrete Expression instances base on the operator name extracted from the expression List.
+ * This method will create a LogicalExpression from an "and", "or" or "not" operator otherwise it will create
+ * a BooleanExpression.
+ *
+ * @param expr the List of Expressions extracted by parsing the Query predicate
+ */
+ public static Expression createExpression(final List expr) throws QmfException
+ {
+ Iterator iter = expr.listIterator();
+ if (!iter.hasNext())
+ {
+ throw new QmfException("Missing operator in predicate expression");
+ }
+
+ String op = (String)iter.next();
+ if (op.equals("not"))
+ {
+ return new LogicalNot(expr);
+ }
+ if (op.equals("and"))
+ {
+ return new LogicalAnd(expr);
+ }
+ if (op.equals("or"))
+ {
+ return new LogicalOr(expr);
+ }
+ return BooleanExpression.createExpression(expr);
+ }
+
+ /**
+ * Evaluate expression against a QmfData instance.
+ * @return true if query matches the QmfData instance, else false.
+ */
+ public abstract boolean evaluate(final QmfData data);
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/Handle.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/Handle.java
new file mode 100644
index 0000000000..7f81b06900
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/Handle.java
@@ -0,0 +1,107 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// JMS Imports
+import javax.jms.Destination; // Needed for replyTo
+import javax.jms.JMSException;
+
+/**
+ * This class represents the reply Handle used for asynchronous operations
+ *
+ * @author Fraser Adams
+ */
+public final class Handle
+{
+ private final String _correlationId;
+ private final Destination _replyTo;
+
+ /**
+ * Construct a Handle containing only a correlationId
+ *
+ * @param correlationId - a String used to tie together requests and responses
+ */
+ public Handle(final String correlationId)
+ {
+ _correlationId = correlationId;
+ _replyTo = null;
+ }
+
+ /**
+ * Construct a Handle containing a correlationId and a replyTo.
+ *
+ * @param correlationId - a String used to tie together requests and responses
+ * @param replyTo - the JMS replyTo
+ */
+ public Handle(final String correlationId, final Destination replyTo)
+ {
+ _correlationId = correlationId;
+ _replyTo = replyTo;
+ }
+
+ /**
+ * Returns the correlationId String.
+ * @return the correlationId String
+ */
+ public String getCorrelationId()
+ {
+ return _correlationId;
+ }
+
+ /**
+ * Return the replyTo Destination.
+ * @return the replyTo Destination
+ */
+ public Destination getReplyTo()
+ {
+ return _replyTo;
+ }
+
+ /**
+ * Returns the Routing Key for the replyTo as a String
+ * <p>
+ * All things being equal it probably makes most logical sense to use the replyTo obtained from the JMS
+ * Message when replying to a request however..... for Qpid up to version 0.12 at least there seems to be
+ * a bug with the replyTo whereby invoking send() on the replyTo causes spurious exchangeDeclares to occur.
+ * The exchangeDeclare is apparently to validate the destination however there is supposed to be a cache
+ * that should prevent this from occurring if the replyTo Destination is reused, but that's broken.
+ * <p>
+ * As an alternative we get hold of the Routing Key of the replyTo which, is sneakily available from getTopicName()
+ * the Routing Key can then be used as the subject of the returned message to enable delivery of the Message
+ * to the appropriate address.
+ * <p>
+ * org.apache.qpid.client.AMQTopic.getTopicName() returns "getRoutingKey().asString()" so this seems an OK
+ * way to get the Routing Key from the replyTo using the pure JMS API.
+ *
+ * @return the Routing Key for the replyTo
+ */
+ public String getRoutingKey()
+ {
+ try
+ {
+ return ((javax.jms.Topic)_replyTo).getTopicName();
+ }
+ catch (JMSException jmse)
+ {
+ return "";
+ }
+ }
+}
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/LogicalAnd.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/LogicalAnd.java
new file mode 100644
index 0000000000..12b9f3d093
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/LogicalAnd.java
@@ -0,0 +1,62 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.List;
+
+/**
+ * A class to evaluate the LogicalAnd Expression
+ *
+ * @author Fraser Adams
+ */
+
+public final class LogicalAnd extends LogicalExpression
+{
+ /**
+ * This method iterates through collecting the sub-expressions of the Logical Expression
+ *
+ * @param expr the List of sub-expressions extracted by parsing the Query predicate, the first one should be
+ * the Logical Expression's operator name
+ */
+ public LogicalAnd(final List expr) throws QmfException
+ {
+ super(expr);
+ }
+
+ /**
+ * Evaluate the Logical And expression against a QmfData instance.
+ * @return false if any of the sub-expressions is false otherwise returns true
+ */
+ public boolean evaluate(final QmfData data)
+ {
+ for (Expression e : _subExpressions)
+ {
+ if (!e.evaluate(data))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+}
+
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/LogicalExpression.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/LogicalExpression.java
new file mode 100644
index 0000000000..23a5ac3abd
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/LogicalExpression.java
@@ -0,0 +1,64 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/*
+ * This class represents the base class for all Logical Expressions (and, or, not) created by expanding the Query predicate.
+ *
+ * @author Fraser Adams
+ */
+public abstract class LogicalExpression extends Expression
+{
+ protected List<Expression> _subExpressions = new ArrayList<Expression>();
+
+ /**
+ * Constructor. This method iterates through collecting the sub-expressions of the Logical Expression
+ *
+ * @param expr the List of sub-expressions extracted by parsing the Query predicate, the first one should be
+ * the Logical Expression's operator name
+ */
+ public LogicalExpression(final List expr) throws QmfException
+ {
+ Iterator iter = expr.listIterator();
+ String op = (String)iter.next();
+//System.out.println("LogicalExpression, op = " + op);
+
+ // Collect sub-expressions
+ while (iter.hasNext())
+ {
+ Object object = iter.next();
+ if (object instanceof List)
+ {
+ _subExpressions.add(createExpression((List)object));
+ }
+ else
+ {
+ throw new QmfException("Operands of " + op + " must be Lists");
+ }
+ }
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/LogicalNot.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/LogicalNot.java
new file mode 100644
index 0000000000..029ea00af9
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/LogicalNot.java
@@ -0,0 +1,62 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.List;
+
+/**
+ * A class to evaluate the LogicalNot Expression
+ *
+ * @author Fraser Adams
+ */
+
+public final class LogicalNot extends LogicalExpression
+{
+ /**
+ * This method iterates through collecting the sub-expressions of the Logical Expression
+ *
+ * @param expr the List of sub-expressions extracted by parsing the Query predicate, the first one should be
+ * the Logical Expression's operator name
+ */
+ public LogicalNot(final List expr) throws QmfException
+ {
+ super(expr);
+ }
+
+ /**
+ * Evaluate the Logical Not expression against a QmfData instance.
+ * @return false if any of the sub-expressions is true otherwise returns true
+ */
+ public boolean evaluate(final QmfData data)
+ {
+ for (Expression e : _subExpressions)
+ {
+ if (e.evaluate(data))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+}
+
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/LogicalOr.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/LogicalOr.java
new file mode 100644
index 0000000000..72c8c1ff15
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/LogicalOr.java
@@ -0,0 +1,62 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.List;
+
+/**
+ * A class to evaluate the LogicalOr Expression
+ *
+ * @author Fraser Adams
+ */
+
+public final class LogicalOr extends LogicalExpression
+{
+ /**
+ * This method iterates through collecting the sub-expressions of the Logical Expression
+ *
+ * @param expr the List of sub-expressions extracted by parsing the Query predicate, the first one should be
+ * the Logical Expression's operator name
+ */
+ public LogicalOr(final List expr) throws QmfException
+ {
+ super(expr);
+ }
+
+ /**
+ * Evaluate the Logical Or expression against a QmfData instance.
+ * @return true if any of the sub-expressions is true otherwise returns false
+ */
+ public boolean evaluate(final QmfData data)
+ {
+ for (Expression e : _subExpressions)
+ {
+ if (e.evaluate(data))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+}
+
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/Notifier.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/Notifier.java
new file mode 100644
index 0000000000..ecdce269b4
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/Notifier.java
@@ -0,0 +1,67 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+/**
+ * The QMF2 API has a work-queue Callback approach. All asynchronous events are represented by a WorkItem object.
+ * When a QMF event occurs it is translated into a WorkItem object and placed in a FIFO queue. It is left to the
+ * application to drain this queue as needed.
+ * <p>
+ * This new API does require the application to provide a single callback. The callback is used to notify the
+ * application that WorkItem object(s) are pending on the work queue. This callback is invoked by QMF when one or
+ * more new WorkItem objects are added to the queue. To avoid any potential threading issues, the application is
+ * not allowed to call any QMF API from within the context of the callback. The purpose of the callback is to
+ * notify the application to schedule itself to drain the work queue at the next available opportunity.
+ * <p>
+ * For example, a console application may be designed using a select() loop. The application waits in the select()
+ * for any of a number of different descriptors to become ready. In this case, the callback could be written to
+ * simply make one of the descriptors ready, and then return. This would cause the application to exit the wait state,
+ * and start processing pending events.
+ * <p>
+ * The callback is represented by the Notifier virtual base class. This base class contains a single method. An
+ * application derives a custom notification handler from this class, and makes it available to the Console or Agent object.
+ * <p>
+ * The following diagram illustrates the Notifier and WorkQueue QMF2 API Event model.
+ * <p>
+ * Notes
+ * <ol>
+ * <li>There is an alternative (simpler but not officially QMF2) API based on implementing the QmfEventListener.</li>
+ * <li>BlockingNotifier is not part of QMF2 either but is how most people would probably write a Notifier.</li>
+ * <li>It's generally not necessary to use a Notifier as the Console provides a blocking getNextWorkitem() method.</li>
+ * </ol>
+ * <p>
+ * <img alt="" src="doc-files/WorkQueueEventModel.png">
+ *
+ * @author Fraser Adams
+ */
+public interface Notifier extends QmfCallback
+{
+ /**
+ * Called when the Console internal work queue becomes non-empty due to the arrival of one or more WorkItems.
+ * <p>
+ * This method will be called by the internal QMF management thread. It is illegal to invoke any QMF APIs
+ * from within this callback. The purpose of this callback is to indicate that the application should schedule
+ * itself to process the work items. A common implementation would be to call notify() to unblock a waiting Thread.
+ *
+ */
+ public void indication();
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/NotifierWrapper.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/NotifierWrapper.java
new file mode 100644
index 0000000000..42bb9cc9d7
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/NotifierWrapper.java
@@ -0,0 +1,57 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+/**
+ * Implementation of QmfEventListener that wraps a Notifier instance. This class populates the WorkItem
+ * queue then invokes the Notifier's indication() method to notify clients of available data.
+ * <p>
+ * This approach allows us to support two separate asynchronous notification APIs without too much effort.
+ *
+ * @author Fraser Adams
+ */
+public final class NotifierWrapper implements QmfEventListener
+{
+ private final Notifier _notifier;
+ private final WorkQueue _workQueue;
+
+ /**
+ * Wraps a Notifier and WorkQueue so that they me be triggered by a QmfEventListener onEvent() call.
+ * @param notifier the Notifier instance that will be triggered when NotifierWrapper receives a WorkItem.
+ * @param workQueue the WorkQueue instance that the WorkItem will be placed on.
+ */
+ public NotifierWrapper(final Notifier notifier, final WorkQueue workQueue)
+ {
+ _notifier = notifier;
+ _workQueue = workQueue;
+ }
+
+ /**
+ * This method adds the WorkItem to the WorkQueue then notifies any clients through the Notifier.indication().
+ *
+ * @param item the WorkItem to add to the queue
+ */
+ public void onEvent(final WorkItem item)
+ {
+ _workQueue.addWorkItem(item);
+ _notifier.indication();
+ }
+}
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/NullQmfEventListener.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/NullQmfEventListener.java
new file mode 100644
index 0000000000..447adbe391
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/NullQmfEventListener.java
@@ -0,0 +1,43 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+/**
+ * Implementation of QmfEventListener with an empty onEvent().
+ * <p>
+ * Agent or Console instantiate this class if no QmfCallback is supplied so they can avoid lots of ugly tests for
+ * _eventListener == null.
+ *
+ * @author Fraser Adams
+ */
+public final class NullQmfEventListener implements QmfEventListener
+{
+ /**
+ * Passes a WorkItem to the listener. This class provides a null implementation
+ *
+ * @param item the WorkItem passed to the listener
+ */
+ public void onEvent(final WorkItem item)
+ {
+ // Null implementation
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/ObjectId.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/ObjectId.java
new file mode 100644
index 0000000000..b84822def7
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/ObjectId.java
@@ -0,0 +1,159 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.Map;
+
+/**
+ * This class provides a wrapper for QMF Object IDs to enable easier comparisons.
+ * <p>
+ * QMF Object IDs are Maps and <i>in theory</i> equals should work, but strings in QMF are actually often returned
+ * as byte[] due to inconsistent binary and UTF-8 encodings being used and byte[].equals() compares the address not a
+ * bytewise comparison.
+ * <p>
+ * This class creates a String from the internal ObjectId state information to enable easier comparison and rendering.
+ *
+ * @author Fraser Adams
+ */
+public final class ObjectId extends QmfData
+{
+ private final String _agentName;
+ private final String _objectName;
+ private final long _agentEpoch;
+
+ /**
+ * Create an ObjectId given the ID created via ObjectId.toString().
+ * @param oid a string created via ObjectId.toString().
+ */
+ public ObjectId(String oid)
+ {
+ String[] split = oid.split("@");
+
+ _agentName = split.length == 3 ? split[0] : "";
+ _agentEpoch = split.length == 3 ? Long.parseLong(split[1]) : 0;
+ _objectName = split.length == 3 ? split[2] : "";
+
+ setValue("_agent_name", _agentName);
+ setValue("_agent_epoch", _agentEpoch);
+ setValue("_object_name", _objectName);
+ }
+
+ /**
+ * Create an ObjectId given an agentName, objectName and agentEpoch.
+ * @param agentName the name of the Agent managing the object.
+ * @param objectName the name of the managed object.
+ * @param agentEpoch a count used to identify if an Agent has been restarted.
+ */
+ public ObjectId(String agentName, String objectName, long agentEpoch)
+ {
+ _agentName = agentName;
+ _objectName = objectName;
+ _agentEpoch = agentEpoch;
+ setValue("_agent_name", _agentName);
+ setValue("_object_name", _objectName);
+ setValue("_agent_epoch", _agentEpoch);
+ }
+
+ /**
+ * Create an ObjectId from a Map. In essence it "deserialises" its state from the Map.
+ * @param m the Map the Object is retrieving its state from.
+ */
+ public ObjectId(Map m)
+ {
+ super(m);
+ _agentName = getStringValue("_agent_name");
+ _objectName = getStringValue("_object_name");
+ _agentEpoch = getLongValue("_agent_epoch");
+ }
+
+ /**
+ * Create an ObjectId from a QmfData object. In essence it "deserialises" its state from the QmfData object.
+ * @param qmfd the QmfData the Object is retrieving its state from.
+ */
+ public ObjectId(QmfData qmfd)
+ {
+ this(qmfd.mapEncode());
+ }
+
+ /**
+ * Returns the name of the Agent managing the object.
+ * @return the name of the Agent managing the object.
+ */
+ public String getAgentName()
+ {
+ return _agentName;
+ }
+
+ /**
+ * Returns the name of the managed object.
+ * @return the name of the managed object.
+ */
+ public String getObjectName()
+ {
+ return _objectName;
+ }
+
+ /**
+ * Returns the Epoch count.
+ * @return the Epoch count.
+ */
+ public long getAgentEpoch()
+ {
+ return _agentEpoch;
+ }
+
+ /**
+ * Compares two ObjectId objects for equality.
+ * @param rhs the right hands side ObjectId in the comparison.
+ * @return true if the two ObjectId objects are equal otherwise returns false.
+ */
+ @Override
+ public boolean equals(Object rhs)
+ {
+ if (rhs instanceof ObjectId)
+ {
+ return toString().equals(rhs.toString());
+ }
+ return false;
+ }
+
+ /**
+ * Returns the ObjectId hashCode.
+ * @return the ObjectId hashCode.
+ */
+ @Override
+ public int hashCode()
+ {
+ return toString().hashCode();
+ }
+
+ /**
+ * Returns a String representation of the ObjectId.
+ * @return a String representation of the ObjectId.
+ */
+ @Override
+ public String toString()
+ {
+ return _agentName + "@" + _agentEpoch + "@" + _objectName;
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfCallback.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfCallback.java
new file mode 100644
index 0000000000..d57282b2e7
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfCallback.java
@@ -0,0 +1,33 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+/**
+ * QmfCallback is a "marker interface" used to specify objects that will be notified of a particular
+ * condition by the Console. This interface is extended by the QmfEventListener and Notifier interfaces
+ * in order to provide two different types of callback semantic.
+ *
+ * @author Fraser Adams
+ */
+public interface QmfCallback
+{
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfData.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfData.java
new file mode 100644
index 0000000000..c634564ba4
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfData.java
@@ -0,0 +1,443 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * QMF defines the QmfData class to represent an atomic unit of managment data.
+ * <p>
+ * The QmfData class defines a collection of named data values. Optionally, a string tag, or "sub-type" may be
+ * associated with each data item. This tag may be used by an application to annotate the data item.
+ * (Note the tag implementation is TBD).
+ * <p>
+ * In QMFv2, message bodies are AMQP maps and are therefore easily extended without causing backward
+ * compatibility problems. Another benefit of the map-message format is that all messages can be fully
+ * parsed when received without the need for external schema data
+ * <p>
+ * This class is the base class for all QMF2 Data Types in this implementation. It is intended to provide a
+ * useful wrapper for the underlying Map, supplying accessor and mutator methods that give an element of type
+ * safety (for example strings appear to be encoded as a mixture of byte[] and String depending of the agent, so
+ * this class checks for this in its getString())
+ * <p>
+ * The following diagram represents the QmfData class hierarchy.
+ * <p>
+ * <img alt="" src="doc-files/QmfData.png">
+ *
+ * @author Fraser Adams
+ */
+public class QmfData
+{
+ protected Map<String, Object> _values = null;
+ protected Map<String, String> _subtypes = null;
+
+ /**
+ * The default constructor, initialises the QmfData with an empty Map.
+ */
+ public QmfData()
+ {
+ _values = new HashMap<String, Object>();
+ }
+
+ /**
+ * The main constructor, taking a java.util.Map as a parameter. In essence it "deserialises" its state from the Map.
+ *
+ * @param m the Map used to construct the QmfData.
+ */
+ @SuppressWarnings("unchecked")
+ public QmfData(final Map m)
+ {
+ if (m == null)
+ { // This branch is just a safety precaution and shouldn't generally occur in normal usage scenarios.
+ // Initialise with a new HashMap. Should then behave the same as the default Constructor.
+ _values = new HashMap<String, Object>();
+ }
+ else
+ {
+ Map<String, Object> values = (Map<String, Object>)m.get("_values");
+ _values = (values == null) ? m : values;
+
+ Map<String, String> subtypes = (Map<String, String>)m.get("_subtypes");
+ _subtypes = subtypes;
+ }
+ }
+
+ /**
+ * Get the state of the _subtypes Map, (generally used when serialising method request/response arguments.
+ *
+ * @return the value of the _subtypes Map.
+ */
+ public Map<String, String> getSubtypes()
+ {
+ return _subtypes;
+ }
+
+
+ /**
+ * Set the state of the _subtypes Map, (generally used when deserialising method request/response arguments.
+ *
+ * @param subtypes the new value of the _subtypes Map.
+ */
+ @SuppressWarnings("unchecked")
+ public void setSubtypes(Map subtypes)
+ {
+ _subtypes = subtypes;
+ }
+
+ /**
+ * Helper method to return the <i>best</i> String representation of the given Object.
+ * <p>
+ * There seem to be some inconsistencies where string properties are sometimes returned as byte[] and
+ * sometimes as Strings. It seems to vary depending on the Agent and is due to strings being encoded as a mix of
+ * binary strings and UTF-8 strings by C++ Agent classes. Getting it wrong results in ClassCastExceptions, which
+ * is clearly unfortunate.
+ * <p>
+ * This is basically a helper method to check the type of a property and return the most "appropriate"
+ * String representation for it.
+ *
+ * @param p a property in Object form
+ * @return the most appropriate String representation of the property
+ */
+ public static final String getString(final Object p)
+ {
+ if (p == null)
+ {
+ return "";
+ }
+ else if (p instanceof String)
+ {
+ return (String)p;
+ }
+ else if (p instanceof byte[])
+ {
+ return new String((byte[])p);
+ }
+ else return p.toString();
+ }
+
+ /**
+ * Helper method to return the <i>best</i> long representation of the given Object.
+ * <p>
+ * There seem to be some inconsistencies where properties are sometimes returned as Integer and
+ * sometimes as Long. It seems to vary depending on the Agent and getting it wrong results in
+ * ClassCastExceptions, which is clearly unfortunate.
+ * <p>
+ * This is basically a helper method to check the type of a property and return a long representation for it.
+ *
+ * @param p a property in Object form
+ * @return the long representation for it.
+ */
+ public static final long getLong(final Object p)
+ {
+ if (p == null)
+ {
+ return 0;
+ }
+ else if (p instanceof Long)
+ {
+ return ((Long)p).longValue();
+ }
+ else if (p instanceof Integer)
+ {
+ return ((Integer)p).intValue();
+ }
+ else if (p instanceof Short)
+ {
+ return ((Short)p).shortValue();
+ }
+ else return 0;
+ }
+
+ /**
+ * Helper method to return the <i>best</i> boolean representation of the given Object.
+ * <p>
+ * There seem to be some inconsistencies where boolean properties are sometimes returned as Boolean and
+ * sometimes as Long/Integer/Short. It seems to vary depending on the Agent and getting it wrong results in
+ * ClassCastExceptions, which is clearly unfortunate.
+ * <p>
+ * This is basically a helper method to check the type of a property and return a boolean representation for it.
+ *
+ * @param p a property in Object form
+ * @return the boolean representation for it.
+ */
+ public static final boolean getBoolean(final Object p)
+ {
+ if (p == null)
+ {
+ return false;
+ }
+ else if (p instanceof Boolean)
+ {
+ return ((Boolean)p).booleanValue();
+ }
+ else if (p instanceof Long)
+ {
+ return ((Long)p).longValue() > 0;
+ }
+ else if (p instanceof Integer)
+ {
+ return ((Integer)p).intValue() > 0;
+ }
+ else if (p instanceof Short)
+ {
+ return ((Short)p).shortValue() > 0;
+ }
+ else if (p instanceof String)
+ {
+ return Boolean.parseBoolean((String)p);
+ }
+ else return false;
+ }
+
+ /**
+ * Helper method to return the <i>best</i> double representation of the given Object.
+ * <p>
+ * There seem to be some inconsistencies where properties are sometimes returned as Float and
+ * sometimes as Double. It seems to vary depending on the Agent and getting it wrong results in
+ * ClassCastExceptions, which is clearly unfortunate.
+ * <p>
+ * This is basically a helper method to check the type of a property and return a Double representation for it.
+ *
+ * @param p a property in Object form
+ * @return the Double representation for it.
+ */
+ public static final double getDouble(final Object p)
+ {
+ if (p == null)
+ {
+ return 0.0d;
+ }
+ else if (p instanceof Float)
+ {
+ return ((Float)p).floatValue();
+ }
+ else if (p instanceof Double)
+ {
+ return ((Double)p).doubleValue();
+ }
+ else return 0.0d;
+ }
+
+ /**
+ * Determines if the named property exists.
+ *
+ * @param name of the property to check.
+ * @return true if the property exists otherwise false.
+ */
+ public final boolean hasValue(final String name)
+ {
+ return _values.containsKey(name);
+ }
+
+ /**
+ * Accessor method to return a named property as an Object.
+ *
+ * @param name of the property to return as an Object.
+ * @return value of property as an Object.
+ */
+ @SuppressWarnings("unchecked")
+ public final <T> T getValue(final String name)
+ {
+ return (T)_values.get(name);
+ }
+
+ /**
+ * Mutator method to set a named Object property.
+ *
+ * @param name the name of the property to set.
+ * @param value the value of the property to set.
+ */
+ public final void setValue(final String name, final Object value)
+ {
+ _values.put(name, value);
+ }
+
+ /**
+ * Mutator method to set a named Object property.
+ *
+ * @param name the name of the property to set.
+ * @param value the value of the property to set.
+ * @param subtype the subtype of the property.
+ */
+ public final void setValue(final String name, final Object value, final String subtype)
+ {
+ setValue(name, value);
+ setSubtype(name, subtype);
+ }
+
+ /**
+ * Mutator to set or modify the subtype associated with name.
+ *
+ * @param name the name of the property to set the subtype for.
+ * @param subtype the subtype of the property.
+ */
+ public final void setSubtype(final String name, final String subtype)
+ {
+ if (_subtypes == null)
+ {
+ _subtypes = new HashMap<String, String>();
+ }
+ _subtypes.put(name, subtype);
+ }
+
+ /**
+ * Accessor to return the subtype associated with named property.
+ *
+ * @param name the name of the property to get the subtype for.
+ * @return the subtype of the named property or null if not present.
+ */
+ public final String getSubtype(final String name)
+ {
+ if (_subtypes == null)
+ {
+ return null;
+ }
+ return _subtypes.get(name);
+ }
+
+ /**
+ * Accessor method to return a named property as a boolean.
+ *
+ * @param name of the property to return as a boolean.
+ * @return value of property as a boolean.
+ */
+ public final boolean getBooleanValue(final String name)
+ {
+ return getBoolean(getValue(name));
+ }
+
+ /**
+ * Accessor method to return a named property as a long.
+ *
+ * @param name of the property to return as a long.
+ * @return value of property as a long.
+ */
+ public final long getLongValue(final String name)
+ {
+ return getLong(getValue(name));
+ }
+
+ /**
+ * Accessor method to return a named property as a double.
+ *
+ * @param name of the property to return as a double.
+ * @return value of property as a double.
+ */
+ public final double getDoubleValue(final String name)
+ {
+ return getDouble(getValue(name));
+ }
+
+ /**
+ * Accessor method to return a named property as a String.
+ *
+ * @param name of the property to return as a String.
+ * @return value of property as a String.
+ */
+ public final String getStringValue(final String name)
+ {
+ return getString(getValue(name));
+ }
+
+ /**
+ * Accessor method to return a reference property.
+ * <p>
+ * Many QMF Objects contain reference properties, e.g. references to other QMF Objects.
+ * This method allows these to be obtained as ObjectId objects to enable much easier
+ * comparison and rendering.
+ * @return the retrieved value as an ObjectId instance.
+ */
+ public final ObjectId getRefValue(final String name)
+ {
+ return new ObjectId((Map)getValue(name));
+ }
+
+ /**
+ * Mutator method to set a named reference property.
+ * <p>
+ * Many QMF Objects contain reference properties, e.g. references to other QMF Objects.
+ * This method allows these to be set as ObjectId objects.
+ *
+ * @param name the name of the property to set.
+ * @param value the value of the property to set.
+ */
+ public final void setRefValue(final String name, final ObjectId value)
+ {
+ setValue(name, value.mapEncode());
+ }
+
+ /**
+ * Mutator method to set a named reference property.
+ * <p>
+ * Many QMF Objects contain reference properties, e.g. references to other QMF Objects.
+ * This method allows these to be set as ObjectId objects.
+ *
+ * @param name the name of the property to set.
+ * @param value the value of the property to set.
+ * @param subtype the subtype of the property.
+ */
+ public final void setRefValue(final String name, final ObjectId value, final String subtype)
+ {
+ setRefValue(name, value);
+ setSubtype(name, subtype);
+ }
+
+ /**
+ * Return the underlying Map representation of this QmfData.
+ * @return the underlying Map.
+ */
+ public Map<String, Object> mapEncode()
+ {
+ return _values;
+ }
+
+ /**
+ * Helper/debug method to list the properties and their type.
+ */
+ public void listValues()
+ {
+ for (Map.Entry<String, Object> entry : _values.entrySet())
+ {
+ Object key = entry.getKey();
+ Object value = entry.getValue();
+ if (value instanceof Map)
+ { // Check if the value part is an ObjectId and display appropriately
+ Map map = (Map)value;
+ if (map.containsKey("_object_name"))
+ {
+ System.out.println(key + ": " + new ObjectId(map));
+ }
+ else
+ {
+ System.out.println(key + ": " + getString(value));
+ }
+ }
+ else
+ {
+ System.out.println(key + ": " + getString(value));
+ }
+ }
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfDescribed.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfDescribed.java
new file mode 100644
index 0000000000..c81f674d1f
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfDescribed.java
@@ -0,0 +1,84 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * Subclass of QmfData.
+ * <p>
+ * When representing formally defined data, a QmfData instance is assigned a Schema.
+ *
+ * @author Fraser Adams
+ */
+public class QmfDescribed extends QmfData
+{
+ private SchemaClassId _schema_id;
+
+ /**
+ * The default constructor, initialises the underlying QmfData base class with an empty Map
+ */
+ protected QmfDescribed()
+ {
+ }
+
+ /**
+ * The main constructor, taking a java.util.Map as a parameter. In essence it "deserialises" its state from the Map.
+ *
+ * @param m the map used to construct the QmfDescribed
+ */
+ public QmfDescribed(final Map m)
+ {
+ super(m);
+ _schema_id = (m == null) ? null : new SchemaClassId((Map)m.get("_schema_id"));
+ }
+
+ /**
+ * Returns the SchemaClassId describing this object.
+ * @return the SchemaClassId describing this object.
+ */
+ public final SchemaClassId getSchemaClassId()
+ {
+ return _schema_id;
+ }
+
+ /**
+ * Sets the SchemaClassId describing this object.
+ * @param schema_id the SchemaClassId describing this object.
+ */
+ public final void setSchemaClassId(final SchemaClassId schema_id)
+ {
+ _schema_id = schema_id;
+ }
+
+ /**
+ * Helper/debug method to list the QMF Object properties and their type.
+ */
+ @Override
+ public void listValues()
+ {
+ super.listValues();
+ _schema_id.listValues();
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfEvent.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfEvent.java
new file mode 100644
index 0000000000..06c19ca0ab
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfEvent.java
@@ -0,0 +1,224 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * QMF supports event management functionality. An event is a notification that is sent by an Agent to alert Console(s)
+ * of a change in some aspect of the system under management. Like QMF Data, Events may be formally defined by a Schema
+ * or not. Unlike QMF Data, Events are not manageable entities - they have no lifecycle. Events simply indicate a point
+ * in time where some interesting action occurred.
+ * <p>
+ * An AMQP timestamp value is associated with each QmfEvent instance. It indicates the moment in time the event occurred.
+ * This timestamp is mandatory.
+ * <p>
+ * A severity level may be associated with each QmfEvent instance. The following severity levels are supported:
+ * <pre>
+ * * "emerg" - system is unusable
+ * * "alert" - action must be taken immediately
+ * * "crit" - the system is in a critical condition
+ * * "err" - there is an error condition
+ * * "warning" - there is a warning condition
+ * * "notice" - a normal but significant condition
+ * * "info" - a purely informational message
+ * * "debug" - messages generated to debug the application
+ * </pre>
+ * The default severity is "notice".
+ *
+ * @author Fraser Adams
+ */
+public final class QmfEvent extends QmfDescribed
+{
+ private static final String[] severities = {"emerg", "alert", "crit", "err", "warning", "notice", "info", "debug"};
+
+ private long _timestamp;
+ private int _severity;
+
+ /**
+ * The main constructor, taking a java.util.Map as a parameter. In essence it "deserialises" its state from the Map.
+ *
+ * @param m the map used to construct the QmfEvent
+ */
+ public QmfEvent(final Map m)
+ {
+ super(m);
+ _timestamp = m.containsKey("_timestamp") ? getLong(m.get("_timestamp")) : System.currentTimeMillis()*1000000l;
+ _severity = m.containsKey("_severity") ? (int)getLong(m.get("_severity")) : 5;
+ }
+
+ /**
+ * This constructor taking a SchemaEventClass is the main constructor used by Agents when creating Events
+ *
+ * @param schema the SchemaEventClass describing the Event
+ */
+ public QmfEvent(final SchemaEventClass schema)
+ {
+ _timestamp = System.currentTimeMillis()*1000000l;
+ setSchemaClassId(schema.getClassId());
+ }
+
+ /**
+ * Return the timestamp. Timestamps are recorded in nanoseconds since the epoch.
+ * <p>
+ * An AMQP timestamp value is associated with each QmfEvent instance. It indicates the moment in time the event
+ * occurred. This timestamp is mandatory.
+ *
+ * @return the AMQP timestamp value in nanoseconds since the epoch.
+ */
+ public long getTimestamp()
+ {
+ return _timestamp;
+ }
+
+ /**
+ * Return the severity.
+ * <p>
+ * A severity level may be associated with each QmfEvent instance. The following severity levels are supported:
+ * <pre>
+ * * "emerg" - system is unusable
+ * * "alert" - action must be taken immediately
+ * * "crit" - the system is in a critical condition
+ * * "err" - there is an error condition
+ * * "warning" - there is a warning condition
+ * * "notice" - a normal but significant condition
+ * * "info" - a purely informational message
+ * * "debug" - messages generated to debug the application
+ * </pre>
+ * The default severity is "notice"
+ *
+ * @return the severity value as a String as described above
+ */
+ public String getSeverity()
+ {
+ return severities[_severity];
+ }
+
+ /**
+ * Set the severity level of the Event
+ * @param severity the severity level of the Event as an int
+ */
+ public void setSeverity(final int severity)
+ {
+ if (severity < 0 || severity > 7)
+ {
+ // If supplied value is out of range we set to the default severity
+ _severity = 5;
+ return;
+ }
+ _severity = severity;
+ }
+
+ /**
+ * Set the severity level of the Event
+ * @param severity the severity level of the Event as a String.
+ * <p>
+ * The following severity levels are supported:
+ * <pre>
+ * * "emerg" - system is unusable
+ * * "alert" - action must be taken immediately
+ * * "crit" - the system is in a critical condition
+ * * "err" - there is an error condition
+ * * "warning" - there is a warning condition
+ * * "notice" - a normal but significant condition
+ * * "info" - a purely informational message
+ * * "debug" - messages generated to debug the application
+ * </pre>
+ */
+ public void setSeverity(final String severity)
+ {
+ for (int i = 0; i < severities.length; i++)
+ {
+ if (severity.equals(severities[i]))
+ {
+ _severity = i;
+ return;
+ }
+ }
+ // If we can't match the values we set to the default severity
+ _severity = 5;
+ }
+
+ /**
+ * Return the underlying Map representation of this QmfEvent
+ * @return the underlying map.
+ */
+ @Override
+ public Map<String, Object> mapEncode()
+ {
+ Map<String, Object> map = new HashMap<String, Object>();
+ map.put("_values", super.mapEncode());
+ if (_subtypes != null)
+ {
+ map.put("_subtypes", _subtypes);
+ }
+ map.put("_schema_id", getSchemaClassId().mapEncode());
+ map.put("_timestamp", _timestamp);
+ map.put("_severity", _severity);
+ return map;
+ }
+
+ /**
+ * Helper/debug method to list the object properties and their type.
+ */
+ @Override
+ public void listValues()
+ {
+ System.out.println("QmfEvent:");
+ System.out.println(this);
+ }
+
+ /**
+ * Returns a String representation of the QmfEvent.
+ * <p>
+ * The String representation attempts to mirror the python class Event __repr__ method as far as possible.
+ * @return a String representation of the QmfEvent.
+ */
+ @Override
+ public String toString()
+ {
+ if (getSchemaClassId() == null)
+ {
+ return "<uninterpretable>";
+ }
+
+ String out = new Date(getTimestamp()/1000000l).toString();
+ out += " " + getSeverity() + " " + getSchemaClassId().getPackageName() + ":" + getSchemaClassId().getClassName();
+
+ StringBuilder buf = new StringBuilder();
+ for (Map.Entry<String, Object> entry : super.mapEncode().entrySet())
+ {
+ String disp = getString(entry.getValue());
+ if (disp.contains(" "))
+ {
+ disp = "\"" + disp + "\"";
+ }
+
+ buf.append(" " + entry.getKey() + "=" + disp);
+ }
+
+ return out + buf.toString();
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfEventListener.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfEventListener.java
new file mode 100644
index 0000000000..c6b1c14350
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfEventListener.java
@@ -0,0 +1,51 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+/**
+ * A QmfEventListener object is used to receive asynchronously delivered WorkItems.
+ * <p>
+ * This provides an alternative (simpler) API to the official QMF2 WorkQueue API that some (including the Author)
+ * may prefer over the official API.
+ * <p>
+ * The following diagram illustrates the QmfEventListener Event model.
+ * <p>
+ * Notes
+ * <ol>
+ * <li>This is provided as an alternative to the official QMF2 WorkQueue and Notifier Event model.</li>
+ * <li>Agent and Console methods are sufficiently thread safe that it is possible to call them from a callback fired
+ * from the onEvent() method that may have been called from the JMS MessageListener. Internally the synchronous
+ * and asynchronous calls are processed on different JMS Sessions to facilitate this</li>
+ * </ol>
+ * <p>
+ * <img alt="" src="doc-files/QmfEventListenerModel.png">
+ *
+ * @author Fraser Adams
+ */
+public interface QmfEventListener extends QmfCallback
+{
+ /**
+ * Passes a WorkItem to the listener.
+ *
+ * @param item the WorkItem passed to the listener
+ */
+ public void onEvent(WorkItem item);
+}
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfException.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfException.java
new file mode 100644
index 0000000000..aa397e6116
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfException.java
@@ -0,0 +1,42 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+/**
+ * A QmfException object
+ *
+ * @author Fraser Adams
+ */
+public class QmfException extends Exception
+{
+ private static final long serialVersionUID = 7526471155622776147L;
+
+ /**
+ * Create a QmfException with a given message String.
+ * @param message the message String.
+ */
+ public QmfException(String message)
+ {
+ super(message);
+ }
+}
+
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfManaged.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfManaged.java
new file mode 100644
index 0000000000..44dbcef9bf
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfManaged.java
@@ -0,0 +1,83 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.Map;
+
+/**
+ * Subclass of QmfDescribed, which is itself a subclass of QmfData.
+ * <p>
+ * When representing managed data, a QmfData instance is assigned an object identifier
+ *
+ * @author Fraser Adams
+ */
+public class QmfManaged extends QmfDescribed
+{
+ private ObjectId _object_id;
+
+ /**
+ * The default constructor, initialises the underlying QmfData base class with an empty Map
+ */
+ protected QmfManaged()
+ {
+ }
+
+ /**
+ * The main constructor, taking a java.util.Map as a parameter. In essence it "deserialises" its state from the Map.
+ *
+ * @param m the map used to construct the QmfManaged
+ */
+ public QmfManaged(final Map m)
+ {
+ super(m);
+ _object_id = (m == null) ? null : new ObjectId((Map)m.get("_object_id"));
+ }
+
+ /**
+ * Returns the ObjectId of this managed object.
+ * @return the ObjectId of this managed object.
+ */
+ public final ObjectId getObjectId()
+ {
+ return _object_id;
+ }
+
+ /**
+ * Sets the ObjectId of this managed object.
+ * @param object_id the ObjectId of this managed object.
+ */
+ public final void setObjectId(final ObjectId object_id)
+ {
+ _object_id = object_id;
+ }
+
+ /**
+ * Helper/debug method to list the QMF Object properties and their type.
+ */
+ @Override
+ public void listValues()
+ {
+ super.listValues();
+ System.out.println("_object_id: " + getObjectId());
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfQuery.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfQuery.java
new file mode 100644
index 0000000000..e9b8e13deb
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfQuery.java
@@ -0,0 +1,343 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+// Reuse this class as it provides a handy mechanism to parse an predicate String into a Map
+import org.apache.qpid.messaging.util.AddressParser;
+
+/**
+ * A Query is a mechanism for interrogating the management database. A Query represents a selector which is sent to
+ * an Agent. The Agent applies the Query against its management database, and returns those objects which meet the
+ * constraints described in the query.
+ * <p>
+ * A Query must specify the class of information it is selecting. This class of information is considered the target
+ * of the query. Any data objects selected by the query will be of the type indicated by the target.
+ * <p>
+ * A Query may also specify a selector which is used as a filter against the set of all target instances. Only those
+ * instances accepted by the filter will be returned in response to the query.
+ * <p>
+ * N.B. There appear to be a number of differences in the description of the map encoding of a Query between the
+ * QMF2 API specified at <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API Proposal</a> and the
+ * QMF2 protocol that is specified at <a href=https://cwiki.apache.org/qpid/qmf-map-message-protocol.html>QMF Map
+ * Message Protocol</a> in particular the use of the underscore to specify key names e.g. "_what", "_where",
+ * "_object_id", "_schema_id".
+ * <p>
+ * This implementation trusts the protocol specification more than the API specification as the underscores are more
+ * consistent with the rest of the protocol and the underscored variants are what have been observed when querying
+ * the broker ManagementAgent.
+ * <p>
+ * A QmfQuery may be constructed as either an "ID" query (to query for a specific ObjectId or SchemaClassId) or a
+ * "PREDICATE" query (to query based upon an expression). Note that QMF considers string arguments in boolean
+ * expressions to be names of data values in the target object. When evaluating a predicate expression, QMF will fetch
+ * the value of the named data item from each candidate target object. The value is then used in the boolean expression.
+ * In other words, QMF considers string arguments to be variables in the expression. In order to indicate that a string
+ * should be treated as a literal instead, the string must be quoted using the "quote" expression.
+ * <p>
+ * <b>Examples</b>
+ * <p>
+ * Assume a QmfData type defines fields named "name", "address" and "town". The following predicate expression matches
+ * any instance with a name field set to "tross", or any instance where the name field is "jross", the address field is
+ * "1313 Spudboy Lane" and the town field is "Utopia":
+ * <p>
+ * <pre>
+ * ["or" ["eq" "name" ["quote" "tross"]]
+ * ["and" ["eq" "name" ["quote" "jross"]]
+ * ["eq" "address" ["quote" "1313 Spudboy Lane"]]
+ * ["eq" ["quote" "Utopia"] "town"]
+ * ]
+ * ]
+ * </pre>
+ * Assume a QmfData type with fields "name" and "age". A predicate to find all instances with name matching the regular
+ * expression "?ross" with an optional age field that is greater than the value 29 or less than 12 would be:
+ * <pre>
+ * ["and" ["re_match" "name" ["quote" "?ross"]]
+ * ["and" ["exists" "age"]
+ * ["or" ["gt" "age" 27] ["lt" "age" 12]]
+ * ]
+ * ]
+ * </pre>
+ * <p>
+ * The Expression structure is illustrated below in the context of its relationship with QmfQuery.
+ * <img alt="" src="doc-files/QmfQuery.png">
+ *
+ *
+ * @author Fraser Adams
+ */
+public final class QmfQuery extends QmfData
+{
+ public static final QmfQuery ID = new QmfQuery();
+ public static final QmfQuery PREDICATE = new QmfQuery();
+
+ private QmfQueryTarget _target;
+ private SchemaClassId _classId;
+ private String _packageName;
+ private String _className;
+ private ObjectId _objectId;
+ private List _predicate;
+ private Expression _expression;
+
+ /**
+ * This Constructor is only used to construct the ID and PREDICATE objects
+ */
+ private QmfQuery()
+ {
+ }
+
+ /**
+ * Construct an QmfQuery with no Selector from a QmfQueryTarget
+ * @param target the query target
+ */
+ public QmfQuery(final QmfQueryTarget target)
+ {
+ _target = target;
+ setValue("_what", _target.toString());
+ }
+
+ /**
+ * Construct an ID QmfQuery from a QmfQueryTarget and SchemaClassId
+ * @param target the query target
+ * @param classId the SchemaClassId to evaluate against
+ */
+ public QmfQuery(final QmfQueryTarget target, final SchemaClassId classId)
+ {
+ _target = target;
+ _classId = classId;
+ _packageName = _classId.getPackageName();
+ _className = _classId.getClassName();
+ setValue("_what", _target.toString());
+ setValue("_schema_id", _classId.mapEncode());
+ }
+
+ /**
+ * Construct an ID QmfQuery from a QmfQueryTarget and ObjectId
+ * @param target the query target
+ * @param objectId the ObjectId to evaluate against
+ */
+ public QmfQuery(final QmfQueryTarget target, final ObjectId objectId)
+ {
+ _target = target;
+ _objectId = objectId;
+ setValue("_what", _target.toString());
+ setValue("_object_id", _objectId.mapEncode());
+ }
+
+ /**
+ * Construct a PREDICATE QmfQuery from a QmfQueryTarget and predicate String
+ * @param target the query target
+ * @param predicateString the predicate to evaluate against
+ */
+ public QmfQuery(final QmfQueryTarget target, final String predicateString) throws QmfException
+ {
+ _target = target;
+
+ if (predicateString.charAt(0) == '[')
+ {
+ Map predicateMap = new AddressParser("{'_where': " + predicateString + "}").map();
+ _predicate = (List)predicateMap.get("_where");
+ _expression = Expression.createExpression(_predicate);
+ }
+ else
+ {
+ throw new QmfException("Invalid predicate format");
+ }
+
+ setValue("_what", _target.toString());
+ setValue("_where", _predicate);
+ }
+
+ /**
+ * Construct a QmfQuery from a Map encoding
+ * @param m encoding the query
+ */
+ public QmfQuery(final Map m) throws QmfException
+ {
+ super(m);
+
+ _target = QmfQueryTarget.valueOf(getStringValue("_what"));
+
+ if (hasValue("_object_id"))
+ {
+ _objectId = getRefValue("_object_id");
+ }
+
+ if (hasValue("_schema_id"))
+ {
+ _classId = new SchemaClassId((Map)getValue("_schema_id"));
+ _packageName = _classId.getPackageName();
+ _className = _classId.getClassName();
+ }
+
+ if (hasValue("_where"))
+ {
+ _predicate = (List)getValue("_where");
+ _expression = Expression.createExpression(_predicate);
+ }
+ }
+
+ /**
+ * Return target name.
+ * @return target name.
+ */
+ public QmfQueryTarget getTarget()
+ {
+ return _target;
+ }
+
+ /**
+ * Undefined by QMF2 API.
+ * <p>
+ * According to <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API Specification</a>
+ * "The value of the &lt;target name string&gt; map entry is ignored for now, its use is TBD."
+ * so this method returns a null Map.
+ */
+ public Map getTargetParam()
+ {
+ return null;
+ }
+
+ /**
+ * Return QmfQuery.ID or QmfQuery.PREDICATE or null if there is no Selector
+ * @return QmfQuery.ID or QmfQuery.PREDICATE or null if there is no Selector
+ */
+ public QmfQuery getSelector()
+ {
+ if (_predicate == null)
+ {
+ if (_objectId == null && _classId == null)
+ {
+ return null;
+ }
+ return ID;
+ }
+ return PREDICATE;
+ }
+
+ /**
+ * Return predicate expression if selector type is QmfQuery.PREDICATE
+ * @return predicate expression if selector type is QmfQuery.PREDICATE
+ */
+ public List getPredicate()
+ {
+ return _predicate;
+ }
+
+ /**
+ * Return the SchemaClassId if selector type is QmfQuery.ID
+ * @return the SchemaClassId if selector type is QmfQuery.ID
+ */
+ public SchemaClassId getSchemaClassId()
+ {
+ return _classId;
+ }
+
+ /**
+ * Return the ObjectId if selector type is QmfQuery.ID
+ * @return the ObjectId if selector type is QmfQuery.ID
+ */
+ public ObjectId getObjectId()
+ {
+ return _objectId;
+ }
+
+ /**
+ * Evaluate query against a QmfData instance.
+ * @return true if query matches the QmfData instance, else false.
+ */
+ public boolean evaluate(final QmfData data)
+ {
+ if (_predicate == null)
+ {
+ if (data instanceof QmfManaged)
+ {
+ QmfManaged managedData = (QmfManaged)data;
+ // Evaluate an ID query on Managed Data
+ if (_objectId != null && _objectId.equals(managedData.getObjectId()))
+ {
+ return true;
+ }
+ else if (_classId != null)
+ {
+ SchemaClassId dataClassId = managedData.getSchemaClassId();
+ String dataClassName = dataClassId.getClassName();
+ String dataPackageName = dataClassId.getPackageName();
+
+ // Wildcard the package name if it hasn't been specified when checking class name
+ if (_className.equals(dataClassName) &&
+ (_packageName.length() == 0 || _packageName.equals(dataPackageName)))
+ {
+ return true;
+ }
+
+ // Wildcard the class name if it hasn't been specified when checking package name
+ if (_packageName.equals(dataPackageName) &&
+ (_className.length() == 0 || _className.equals(dataClassName)))
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ else
+ {
+ // Evaluate a PREDICATE query by evaluating against the expression created from the predicate
+ if (_predicate.size() == 0)
+ {
+ return true;
+ }
+
+ return _expression.evaluate(data);
+ }
+ }
+
+ /**
+ * Helper/debug method to list the QMF Object properties and their type.
+ */
+ @Override
+ public void listValues()
+ {
+ System.out.println("QmfQuery:");
+ System.out.println("target: " + _target);
+ if (_predicate != null)
+ {
+ System.out.println("selector: QmfQuery.PREDICATE");
+ System.out.println("predicate: " + _predicate);
+ }
+ else if (_classId != null)
+ {
+ System.out.println("selector: QmfQuery.ID");
+ _classId.listValues();
+ }
+ else if (_objectId != null)
+ {
+ System.out.println("selector: QmfQuery.ID");
+ System.out.println(_objectId);
+ }
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfQueryTarget.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfQueryTarget.java
new file mode 100644
index 0000000000..01aba88bfc
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfQueryTarget.java
@@ -0,0 +1,36 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+/**
+ * An enum containing the QMF type code indicating a QMF query target defined in
+ * <a href=https://cwiki.apache.org/qpid/qmf-map-message-protocol.html>QMF Map Message Protocol</a>
+ *
+ * @author Fraser Adams
+ */
+public enum QmfQueryTarget
+{
+ SCHEMA_ID,
+ SCHEMA,
+ OBJECT_ID,
+ OBJECT;
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfType.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfType.java
new file mode 100644
index 0000000000..a19ebcf393
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfType.java
@@ -0,0 +1,39 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+/**
+ * An enum containing the QMF type code indicating a QMF object's data type
+ *
+ * @author Fraser Adams
+ */
+public enum QmfType
+{
+ TYPE_VOID,
+ TYPE_BOOL,
+ TYPE_INT,
+ TYPE_FLOAT,
+ TYPE_STRING,
+ TYPE_MAP,
+ TYPE_LIST,
+ TYPE_UUID;
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaClass.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaClass.java
new file mode 100644
index 0000000000..045628dc1a
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaClass.java
@@ -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.qpid.qmf2.common;
+
+// Misc Imports
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * Subclass of QmfData
+ * <p>
+ * When representing formally defined data, a QmfData instance is assigned a Schema.
+ * <p>
+ * The following diagram illustrates the QMF2 Schema class hierarchy.
+ * <p>
+ * <img alt="" src="doc-files/Schema.png">
+ *
+ * @author Fraser Adams
+ */
+public class SchemaClass extends QmfData
+{
+ public static final SchemaClass EMPTY_SCHEMA = new SchemaClass();
+
+ private SchemaClassId _classId;
+
+ /**
+ * The default constructor, initialises the underlying QmfData base class with an empty Map
+ */
+ protected SchemaClass()
+ {
+ }
+
+ /**
+ * The main constructor, taking a java.util.Map as a parameter.
+ *
+ * @param m the map used to construct the SchemaClass
+ */
+ public SchemaClass(final Map m)
+ {
+ super(m);
+ _classId = new SchemaClassId((Map)getValue("_schema_id"));
+ }
+
+ /**
+ * Return the SchemaClassId that identifies this Schema instance.
+ * @return the SchemaClassId that identifies this Schema instance.
+ */
+ public final SchemaClassId getClassId()
+ {
+ if (_classId.getHashString() == null)
+ {
+ _classId = new SchemaClassId(_classId.getPackageName(),
+ _classId.getClassName(),
+ _classId.getType(),
+ generateHash());
+ }
+ return _classId;
+ }
+
+ /**
+ * Set the SchemaClassId that identifies this Schema instance.
+ * @param cid the SchemaClassId that identifies this Schema instance.
+ */
+ public final void setClassId(final SchemaClassId cid)
+ {
+ _classId = cid;
+ }
+
+ /**
+ * Return a hash generated over the body of the schema, and return a representation of the hash
+ * @return a hash generated over the body of the schema, and return a representation of the hash
+ */
+ public final UUID generateHash()
+ {
+ try
+ {
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+ updateHash(md5);
+ return UUID.nameUUIDFromBytes(md5.digest());
+ }
+ catch (NoSuchAlgorithmException nsae)
+ {
+ }
+ return null;
+ }
+
+ /**
+ * Generate the partial hash for the schema fields in this base class
+ * @param md5 the MessageDigest to be updated
+ */
+ protected void updateHash(MessageDigest md5)
+ {
+ try
+ {
+ md5.update(_classId.getPackageName().getBytes("UTF-8"));
+ md5.update(_classId.getClassName().getBytes("UTF-8"));
+ md5.update(_classId.getType().getBytes("UTF-8"));
+ }
+ catch (UnsupportedEncodingException uee)
+ {
+ }
+ }
+
+ /**
+ * Helper/debug method to list the QMF Object properties and their type.
+ */
+ @Override
+ public void listValues()
+ {
+ super.listValues();
+ _classId.listValues();
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaClassId.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaClassId.java
new file mode 100644
index 0000000000..5ad4ba8249
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaClassId.java
@@ -0,0 +1,202 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * Schema are identified by a combination of their package name and class name. A hash value over the body of
+ * the schema provides a revision identifier. The class SchemaClassId represents this Schema identifier.
+ * <p>
+ * If the hash value is not supplied, then the value of the hash string will be set to None. This will be the
+ * case when a SchemaClass is being dynamically constructed, and a proper hash is not yet available.
+ *
+ * @author Fraser Adams
+ */
+public final class SchemaClassId extends QmfData
+{
+ private String _packageName = "";
+ private String _className = "";
+ private String _type = "";
+ private UUID _hash = null;
+
+ /**
+ * The main constructor, taking a java.util.Map as a parameter.
+ *
+ * @param m the map used to construct the SchemaClassId.
+ */
+ public SchemaClassId(final Map m)
+ {
+ super(m);
+ _packageName = getStringValue("_package_name");
+ _className = getStringValue("_class_name");
+ _type = getStringValue("_type");
+ _hash = hasValue("_hash") ? (UUID)getValue("_hash") : null;
+ }
+
+ /**
+ * Construct a SchemaClassId from a given class name.
+ *
+ * @param className the class name.
+ */
+ public SchemaClassId(final String className)
+ {
+ this(null, className, null, null);
+ }
+
+ /**
+ * Construct a SchemaClassId from a given package name and class name.
+ *
+ * @param packageName the package name.
+ * @param className the class name.
+ */
+ public SchemaClassId(final String packageName, final String className)
+ {
+ this(packageName, className, null, null);
+ }
+
+ /**
+ * Construct a SchemaClassId from a given package name and class name and type (_data or _event).
+ *
+ * @param packageName the package name.
+ * @param className the class name.
+ * @param type the schema type (_data or _event).
+ */
+ public SchemaClassId(final String packageName, final String className, final String type)
+ {
+ this(packageName, className, type, null);
+ }
+
+ /**
+ * Construct a SchemaClassId from a given package name and class name, type (_data or _event) and hash.
+ *
+ * @param packageName the package name.
+ * @param className the class name.
+ * @param type the schema type (_data or _event).
+ * @param hash a UUID representation of the md5 hash of the Schema,
+ */
+ public SchemaClassId(final String packageName, final String className, final String type, final UUID hash)
+ {
+ if (packageName != null)
+ {
+ setValue("_package_name", packageName);
+ _packageName = packageName;
+ }
+
+ if (className != null)
+ {
+ setValue("_class_name", className);
+ _className = className;
+ }
+
+ if (type != null)
+ {
+ setValue("_type", type);
+ _type = type;
+ }
+
+ if (hash != null)
+ {
+ setValue("_hash", hash);
+ _hash = hash;
+ }
+ }
+
+ /**
+ * Return The name of the associated package.
+ * @return The name of the associated package. Returns empty String if there's no package name.
+ */
+ public String getPackageName()
+ {
+ return _packageName;
+ }
+
+ /**
+ * Return The name of the class within the package.
+ * @return The name of the class within the package. Returns empty String if there's no class name.
+ */
+ public String getClassName()
+ {
+ return _className;
+ }
+
+ /**
+ * Return The type of schema, either "_data" or "_event".
+ * @return The type of schema, either "_data" or "_event". Returns empty String if type is unknown.
+ */
+ public String getType()
+ {
+ return _type;
+ }
+
+ /**
+ * Return The MD5 hash of the schema.
+ * @return The MD5 hash of the schema, in the format "%08x-%08x-%08x-%08x"
+ */
+ public UUID getHashString()
+ {
+ return _hash;
+ }
+
+ /**
+ * Compares two SchemaClassId objects for equality.
+ * @param rhs the right hands side SchemaClassId in the comparison.
+ * @return true if the two SchemaClassId objects are equal otherwise returns false.
+ */
+ @Override
+ public boolean equals(final Object rhs)
+ {
+ if (rhs instanceof SchemaClassId)
+ {
+ SchemaClassId that = (SchemaClassId)rhs;
+ String lvalue = _packageName + _className + _hash;
+ String rvalue = that._packageName + that._className + that._hash;
+ return lvalue.equals(rvalue);
+ }
+ return false;
+ }
+
+ /**
+ * Returns the SchemaClassId hashCode.
+ * @return the SchemaClassId hashCode.
+ */
+ @Override
+ public int hashCode()
+ {
+ String lvalue = _packageName + _className + _hash;
+ return lvalue.hashCode();
+ }
+
+ /**
+ * Helper/debug method to list the QMF Object properties and their type.
+ */
+ @Override
+ public void listValues()
+ {
+ System.out.println("_package_name: " + getPackageName());
+ System.out.println("_class_name: " + getClassName());
+ System.out.println("_type: " + getType());
+ System.out.println("_hash: " + getHashString());
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaEventClass.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaEventClass.java
new file mode 100644
index 0000000000..c563c63362
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaEventClass.java
@@ -0,0 +1,266 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Subclass of SchemaClass
+ * <p>
+ * Agent applications may dynamically construct instances of these objects by adding properties at run
+ * time. However, once the Schema is made public, it must be considered immutable, as the hash value
+ * must be constant once the Schema is in use.
+ * <p>
+ * Note that <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API</a> suggests that the
+ * properties are represented by an unordered map of SchemaProperty entries indexed by property name, however
+ * these are actually represented in the QMF2 protocol as a "List of SCHEMA_PROPERTY elements that describe the
+ * schema event's properties.
+ * <p>
+ * In this implementation getProperties() returns a {@code List<SchemaProperty>} reflecting the reality of the protocol
+ * rather than what is suggested by the API documentation.
+ *
+ * @author Fraser Adams
+ */
+public final class SchemaEventClass extends SchemaClass
+{
+ private List<SchemaProperty> _properties = new ArrayList<SchemaProperty>();
+
+ /**
+ * The main constructor, taking a java.util.Map as a parameter.
+ *
+ * @param m the map used to construct the SchemaEventClass
+ */
+ public SchemaEventClass(final Map m)
+ {
+ super(m);
+ if (m != null)
+ {
+ List<Map> mapEncodedProperties = this.<List<Map>>getValue("_properties");
+ if (mapEncodedProperties != null)
+ { // In theory this shouldn't be null as "_properties" property of SchemaClass is not optional but.....
+ for (Map property : mapEncodedProperties)
+ {
+ addProperty(new SchemaProperty(property));
+ }
+ }
+ }
+ }
+
+ /**
+ * Construct a SchemaEventClass from a package name and a class name.
+ *
+ * @param packageName the package name.
+ * @param className the class name.
+ */
+ public SchemaEventClass(final String packageName, final String className)
+ {
+ setClassId(new SchemaClassId(packageName, className, "_event"));
+ }
+
+ /**
+ * Construct a SchemaEventClass from a SchemaClassId.
+ *
+ * @param classId the SchemaClassId identifying this Schema.
+ */
+ public SchemaEventClass(final SchemaClassId classId)
+ {
+ setClassId(new SchemaClassId(classId.getPackageName(), classId.getClassName(), "_event"));
+ }
+
+ /**
+ * Return optional default severity of this Property.
+ * @return optional default severity of this Property.
+ */
+ public long getDefaultSeverity()
+ {
+ return getLongValue("_default_severity");
+ }
+
+ /**
+ * Set the default severity of this Schema Event
+ *
+ * @param severity optional severity of this Schema Event.
+ */
+ public void setDefaultSeverity(final int severity)
+ {
+ setValue("_default_severity", severity);
+ }
+
+ /**
+ * Return optional string description of this Schema Event.
+ * @return optional string description of this Schema Event.
+ */
+ public String getDesc()
+ {
+ return getStringValue("_desc");
+ }
+
+ /**
+ * Set the optional string description of this Schema Object.
+ *
+ * @param description optional string description of this Schema Object.
+ */
+ public void setDesc(final String description)
+ {
+ setValue("_desc", description);
+ }
+
+ /**
+ * Return the count of SchemaProperties in this instance.
+ * @return the count of SchemaProperties in this instance.
+ */
+ public long getPropertyCount()
+ {
+ return getProperties().size();
+ }
+
+ /**
+ * Return Schema Object's properties.
+ * <p>
+ * Note that <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API</a> suggests that
+ * the properties are represented by an unordered map of SchemaProperty indexed by property name however it
+ * is actually represented in the QMF2 protocol as a "List of SCHEMA_PROPERTY elements that describe the
+ * schema objects's properties. In this implementation getProperties() returns a {@code List<SchemaProperty>}
+ * reflecting the reality of the protocol rather than what is suggested by the API documentation.
+ *
+ * @return Schema Object's properties.
+ */
+ public List<SchemaProperty> getProperties()
+ {
+ return _properties;
+ }
+
+ /**
+ * Return the SchemaProperty for the parameter "name".
+ * @param name the name of the SchemaProperty to return.
+ * @return the SchemaProperty for the parameter "name".
+ */
+ public SchemaProperty getProperty(final String name)
+ {
+ for (SchemaProperty p : _properties)
+ {
+ if (p.getName().equals(name))
+ {
+ return p;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return the SchemaProperty for the index i.
+ * @param i the index of the SchemaProperty to return.
+ * @return the SchemaProperty for the index i.
+ */
+ public SchemaProperty getProperty(final int i)
+ {
+ return _properties.get(i);
+ }
+
+ /**
+ * Add a new Property.
+ *
+ * @param name the name of the SchemaProperty
+ * @param value the SchemaProperty associated with "name"
+ */
+ public void addProperty(final String name, final SchemaProperty value)
+ {
+ value.setValue("_name", name);
+ _properties.add(value);
+ }
+
+ /**
+ * Add a new Property.
+ *
+ * @param value the SchemaProperty associated with "name"
+ */
+ public void addProperty(final SchemaProperty value)
+ {
+ _properties.add(value);
+ }
+
+
+ /**
+ * Helper/debug method to list the QMF Object properties and their type.
+ */
+ @Override
+ public void listValues()
+ {
+ System.out.println("SchemaEventClass:");
+ getClassId().listValues();
+
+ if (hasValue("_desc")) System.out.println("desc: " + getDesc());
+ if (hasValue("_default_severity")) System.out.println("default severity: " + getDefaultSeverity());
+
+ if (getPropertyCount() > 0)
+ {
+ System.out.println("properties:");
+ }
+ for (SchemaProperty p : _properties)
+ {
+ p.listValues();
+ }
+ }
+
+ /**
+ * Return the underlying map.
+ * <p>
+ * We need to convert any properties from SchemaProperty to Map.
+ *
+ * @return the underlying map.
+ */
+ @Override
+ public Map<String, Object> mapEncode()
+ {
+ // I think a "_methods" property is mandatory for a SchemaClass even if it's empty
+ setValue("_methods", Collections.EMPTY_LIST);
+ List<Map> mapEncodedProperties = new ArrayList<Map>();
+ for (SchemaProperty p : _properties)
+ {
+ mapEncodedProperties.add(p.mapEncode());
+ }
+ setValue("_properties", mapEncodedProperties);
+ setValue("_schema_id", getClassId().mapEncode());
+ return super.mapEncode();
+ }
+
+ /**
+ * Generate the partial hash for the schema fields in this class.
+ * @param md5 the MessageDigest to be updated.
+ */
+ @Override
+ protected void updateHash(MessageDigest md5)
+ {
+ super.updateHash(md5);
+
+ for (SchemaProperty p : _properties)
+ {
+ p.updateHash(md5);
+ }
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaMethod.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaMethod.java
new file mode 100644
index 0000000000..c1db746851
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaMethod.java
@@ -0,0 +1,275 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The SchemaMethod class describes a method call's parameter list.
+ * <p>
+ * Note that <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API</a> suggests that
+ * the parameter list is represented by an unordered map of SchemaProperty entries indexed by parameter name,
+ * however is is actually represented in the QMF2 protocol as a "List of SCHEMA_PROPERTY elements that describe
+ * the method's arguments".
+ * <p>
+ * In this implementation getArguments() returns a {@code List<SchemaProperty>} reflecting the reality of the protocol
+ * rather than what is suggested by the API documentation.
+ *
+ * @author Fraser Adams
+ */
+public final class SchemaMethod extends QmfData
+{
+ private List<SchemaProperty> _arguments = new ArrayList<SchemaProperty>();
+
+ /**
+ * The main constructor, taking a java.util.Map as a parameter.
+ *
+ * @param m the map used to construct the SchemaMethod.
+ */
+ public SchemaMethod(final Map m)
+ {
+ super(m);
+ if (m != null)
+ {
+ List<Map> mapEncodedArguments = this.<List<Map>>getValue("_arguments");
+ if (mapEncodedArguments != null)
+ { // In theory this shouldn't be null as "_arguments" property of SchemaMethod is not optional but.....
+ for (Map argument : mapEncodedArguments)
+ {
+ addArgument(new SchemaProperty(argument));
+ }
+ }
+ }
+ }
+
+ /**
+ * Construct a SchemaMethod from its name.
+ *
+ * @param name the name of the SchemaMethod.
+ */
+ public SchemaMethod(final String name)
+ {
+ this(name, null);
+ }
+
+ /**
+ * Construct a SchemaMethod from its name and description.
+ *
+ * @param name the name of the SchemaMethod.
+ * @param description a description of the SchemaMethod.
+ */
+ public SchemaMethod(final String name, final String description)
+ {
+ setValue("_name", name);
+
+ if (description != null)
+ {
+ setValue("_desc", description);
+ }
+ }
+
+ /**
+ * Construct a SchemaMethod from a map of "name":{@code <SchemaProperty>} entries and description.
+ *
+ * Note this Constructor is the one given in the QMF2 API specification at
+ * <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API</a>Note too that this method does not
+ * set a name so setName() needs to be called explicitly by clients after construction.
+ *
+ * @param args a Map of "name":{@code <SchemaProperty>} entries.
+ * @param description a description of the SchemaMethod.
+ */
+ public SchemaMethod(final Map<String, SchemaProperty> args, final String description)
+ {
+ if (description != null)
+ {
+ setValue("_desc", description);
+ }
+
+ for (Map.Entry<String, SchemaProperty> entry : args.entrySet())
+ {
+ addArgument(entry.getKey(), entry.getValue());
+ }
+ }
+
+ /**
+ * Return the method's name.
+ * @return the method's name.
+ */
+ public String getName()
+ {
+ return getStringValue("_name");
+ }
+
+ /**
+ * Sets the method's name.
+ * @param name the method's name.
+ */
+ public void setName(final String name)
+ {
+ setValue("_name", name);
+ }
+
+ /**
+ * Return a description of the method.
+ * @return a description of the method.
+ */
+ public String getDesc()
+ {
+ return getStringValue("_desc");
+ }
+
+ /**
+ * Return the number of arguments for this method.
+ * @return the number of arguments for this method.
+ */
+ public int getArgumentCount()
+ {
+ return getArguments().size();
+ }
+
+ /**
+ * Return the Method's arguments.
+ *<p>
+ * <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API</a> suggests that
+ * the parameter list is represented by an unordered map of SchemaProperty entries indexed by parameter name,
+ * however is is actually represented in the QMF2 protocol as a "List of SCHEMA_PROPERTY elements that describe
+ * the method's arguments". In this implementation getArguments() returns a {@code List<SchemaProperty>} reflecting the
+ * reality of the protocol rather than what is suggested by the API documentation.
+ *
+ * @return the Method's arguments.
+ */
+ public List<SchemaProperty> getArguments()
+ {
+ return _arguments;
+ }
+
+ /**
+ * Return the argument with the name "name" as a SchemaProperty.
+ * @param name the name of the SchemaProperty to return.
+ * @return the argument with the name "name" as a SchemaProperty.
+ */
+ public SchemaProperty getArgument(final String name)
+ {
+ for (SchemaProperty p : _arguments)
+ {
+ if (p.getName().equals(name))
+ {
+ return p;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return the argument for the index i as a SchemaProperty.
+ * @param i the index of the SchemaProperty to return.
+ * @return the argument for the index i as a SchemaProperty.
+ */
+ public SchemaProperty getArgument(final int i)
+ {
+ return _arguments.get(i);
+ }
+
+ /**
+ * Add a new method argument.
+ *
+ * @param name the name of the SchemaProperty.
+ * @param value the SchemaProperty to add.
+ */
+ public void addArgument(final String name, final SchemaProperty value)
+ {
+ value.setValue("_name", name);
+ _arguments.add(value);
+ }
+
+ /**
+ * Add a new method argument.
+ *
+ * @param value the SchemaProperty to add.
+ */
+ public void addArgument(final SchemaProperty value)
+ {
+ _arguments.add(value);
+ }
+
+ /**
+ * Return the underlying map.
+ *
+ * @return the underlying map.
+ */
+ @Override
+ public Map<String, Object> mapEncode()
+ {
+ List<Map> args = new ArrayList<Map>();
+ for (SchemaProperty p : _arguments)
+ {
+ args.add(p.mapEncode());
+ }
+ setValue("_arguments", args);
+
+ return super.mapEncode();
+ }
+
+ /**
+ * Generate the partial hash for the schema fields in this class.
+ * @param md5 the MessageDigest to be updated.
+ */
+ protected void updateHash(MessageDigest md5)
+ {
+ try
+ {
+ md5.update(getName().getBytes("UTF-8"));
+ md5.update(getDesc().toString().getBytes("UTF-8"));
+ }
+ catch (UnsupportedEncodingException uee)
+ {
+ }
+ for (SchemaProperty p : _arguments)
+ {
+ p.updateHash(md5);
+ }
+ }
+
+ /**
+ * Helper/debug method to list the QMF Object properties and their type.
+ */
+ @Override
+ public void listValues()
+ {
+ System.out.println("SchemaMethod:");
+ System.out.println("_name: " + getName());
+ if (hasValue("_desc")) System.out.println("_desc: " + getDesc());
+ if (getArgumentCount() > 0)
+ {
+ System.out.println("_arguments:");
+ }
+ for (SchemaProperty p : _arguments)
+ {
+ p.listValues();
+ }
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaObjectClass.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaObjectClass.java
new file mode 100644
index 0000000000..734d040be6
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaObjectClass.java
@@ -0,0 +1,378 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Subclass of SchemaClass.
+ * <p>
+ * The structure of QmfData objects is formally defined by the class SchemaObjectClass.
+ * <p>
+ * Agent applications may dynamically construct instances of these objects by adding properties and methods
+ * at run time. However, once the Schema is made public, it must be considered immutable, as the hash value
+ * must be constant once the Schema is in use.
+ * <p>
+ * Note that <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API</a> suggests that
+ * the properties and methods are represented by an unordered map of SchemaProperty or SchemaMethod entries indexed by
+ * property or method name, however these are actually represented in the QMF2 protocol as a "List of SCHEMA_PROPERTY
+ * and "List of SCHEMA_METHOD" elements that describe the schema objects's properties and methods". In this
+ * implementation getProperties() returns a {@code List<SchemaProperty>} and getMethods() returns a {@code List<SchemaMethod>}
+ * reflecting the reality of the protocol rather than what is suggested by the API documentation.
+ *
+ * @author Fraser Adams
+ */
+public final class SchemaObjectClass extends SchemaClass
+{
+ private List<SchemaMethod> _methods = new ArrayList<SchemaMethod>();
+ private List<SchemaProperty> _properties = new ArrayList<SchemaProperty>();
+ private String[] _idNames = {};
+
+ /**
+ * The main constructor, taking a java.util.Map as a parameter.
+ *
+ * @param m the map used to construct the SchemaObjectClass.
+ */
+ public SchemaObjectClass(final Map m)
+ {
+ super(m);
+ if (m != null)
+ {
+ List<Map> mapEncodedMethods = this.<List<Map>>getValue("_methods");
+ if (mapEncodedMethods != null)
+ { // In theory this shouldn't be null as "_methods" property of SchemaClass is not optional but.....
+ for (Map method : mapEncodedMethods)
+ {
+ addMethod(new SchemaMethod(method));
+ }
+ }
+
+ List<Map> mapEncodedProperties = this.<List<Map>>getValue("_properties");
+ if (mapEncodedProperties != null)
+ { // In theory this shouldn't be null as "_properties" property of SchemaClass is not optional but.....
+ for (Map property : mapEncodedProperties)
+ {
+ addProperty(new SchemaProperty(property));
+ }
+ }
+ }
+ }
+
+ /**
+ * Construct a SchemaObjectClass from a package name and a class name.
+ *
+ * @param packageName the package name.
+ * @param className the class name.
+ */
+ public SchemaObjectClass(final String packageName, final String className)
+ {
+ setClassId(new SchemaClassId(packageName, className, "_data"));
+ }
+
+ /**
+ * Construct a SchemaObjectClass from a SchemaClassId.
+ *
+ * @param classId the SchemaClassId identifying this SchemaObjectClass.
+ */
+ public SchemaObjectClass(final SchemaClassId classId)
+ {
+ setClassId(new SchemaClassId(classId.getPackageName(), classId.getClassName(), "_data"));
+ }
+
+ /**
+ * Return optional string description of this Schema Object.
+ * @return optional string description of this Schema Object.
+ */
+ public String getDesc()
+ {
+ return getStringValue("_desc");
+ }
+
+ /**
+ * Set the Schema Object's description.
+ *
+ * @param description optional string description of this Schema Object.
+ */
+ public void setDesc(final String description)
+ {
+ setValue("_desc", description);
+ }
+
+ /**
+ * Return the list of property names to use when constructing the object identifier.
+ * <p>
+ * Get the value of the list of property names to use when constructing the object identifier.
+ * When a QmfAgentData object is created the values of the properties specified here are used to
+ * create the associated ObjectId object name.
+ * @return the list of property names to use when constructing the object identifier.
+ */
+ public String[] getIdNames()
+ {
+ // As it's not performance critical copy _idNames, because "return _idNames;" causes findBugs to moan.
+ return Arrays.copyOf(_idNames, _idNames.length);
+ }
+
+ /**
+ * Return the count of SchemaProperties in this instance.
+ * @return the count of SchemaProperties in this instance.
+ */
+ public long getPropertyCount()
+ {
+ return getProperties().size();
+ }
+
+ /**
+ * Return Schema Object's properties.
+ * <p>
+ * Note that <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API</a> suggests that
+ * the properties are represented by an unordered map of SchemaProperty indexed by property name, however it
+ * is actually represented in the QMF2 protocol as a "List of SCHEMA_PROPERTY elements that describe the
+ * schema objects's properties. In this implementation getProperties() returns a {@code List<SchemaProperty>}
+ * reflecting the reality of the protocol rather than what is suggested by the API documentation.
+ *
+ * @return Schema Object's properties.
+ */
+ public List<SchemaProperty> getProperties()
+ {
+ return _properties;
+ }
+
+ /**
+ * Return the SchemaProperty for the parameter "name".
+ * @param name the name of the SchemaProperty to return.
+ * @return the SchemaProperty for the parameter "name".
+ */
+ public SchemaProperty getProperty(final String name)
+ {
+ for (SchemaProperty p : _properties)
+ {
+ if (p.getName().equals(name))
+ {
+ return p;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return the SchemaProperty for the index i.
+ * @param i the index of the SchemaProperty to return.
+ * @return the SchemaProperty for the index i.
+ */
+ public SchemaProperty getProperty(final int i)
+ {
+ return _properties.get(i);
+ }
+
+ /**
+ * Return the count of SchemaMethod's in this instance.
+ * @return the count of SchemaMethod's in this instance.
+ */
+ public long getMethodCount()
+ {
+ return getMethods().size();
+ }
+
+ /**
+ * Return Schema Object's methods.
+ * <p>
+ * Note that <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API</a> suggests that
+ * the methods are represented by an unordered map of SchemaMethod indexed by method name, however it
+ * is actually represented in the QMF2 protocol as a "List of SCHEMA_METHOD elements that describe the
+ * schema objects's methods. In this implementation getMethods() returns a {@code List<SchemaMethod>}
+ * reflecting the reality of the protocol rather than what is suggested by the API documentation.
+ *
+ * @return Schema Object's methods.
+ */
+ public List<SchemaMethod> getMethods()
+ {
+ return _methods;
+ }
+
+ /**
+ * Return the SchemaMethod for the parameter "name".
+ * @param name the name of the SchemaMethod to return.
+ * @return the SchemaMethod for the parameter "name".
+ */
+ public SchemaMethod getMethod(final String name)
+ {
+ for (SchemaMethod m : _methods)
+ {
+ if (m.getName().equals(name))
+ {
+ return m;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return the SchemaMethod for the index i.
+ * @param i the index of the SchemaMethod to return.
+ * @return the SchemaMethod for the index i.
+ */
+ public SchemaMethod getMethod(final int i)
+ {
+ return _methods.get(i);
+ }
+
+ /**
+ * Add a new Property.
+ *
+ * @param name the name of the SchemaProperty.
+ * @param value the SchemaProperty associated with "name".
+ */
+ public void addProperty(final String name, final SchemaProperty value)
+ {
+ value.setValue("_name", name);
+ _properties.add(value);
+ }
+
+ /**
+ * Add a new Property.
+ *
+ * @param value the SchemaProperty associated with "name".
+ */
+ public void addProperty(final SchemaProperty value)
+ {
+ _properties.add(value);
+ }
+
+ /**
+ * Add a new Method.
+ *
+ * @param name the name of the SchemaMethod.
+ * @param value the SchemaMethod associated with "name".
+ */
+ public void addMethod(final String name, final SchemaMethod value)
+ {
+ value.setValue("_name", name);
+ _methods.add(value);
+ }
+
+ /**
+ * Add a new Method.
+ *
+ * @param value the SchemaMethod associated with "name".
+ */
+ public void addMethod(final SchemaMethod value)
+ {
+ _methods.add(value);
+ }
+
+ /**
+ * Set the value of the list of property names to use when constructing the object identifier.
+ * <p>
+ * When a QmfAgentData object is created the values of the properties specified here are used to
+ * create the associated ObjectId object name.
+ * @param idNames the list of property names to use when constructing the object identifier.
+ */
+ public void setIdNames(final String... idNames)
+ {
+ _idNames = idNames;
+ }
+
+ /**
+ * Helper/debug method to list the QMF Object properties and their type.
+ */
+ @Override
+ public void listValues()
+ {
+ System.out.println("SchemaObjectClass:");
+ getClassId().listValues();
+
+ if (hasValue("_desc")) System.out.println("desc: " + getDesc());
+
+ if (getMethodCount() > 0)
+ {
+ System.out.println("methods:");
+ }
+ for (SchemaMethod m : _methods)
+ {
+ m.listValues();
+ }
+
+ if (getPropertyCount() > 0)
+ {
+ System.out.println("properties:");
+ }
+ for (SchemaProperty p : _properties)
+ {
+ p.listValues();
+ }
+ }
+
+ /**
+ * Return the underlying map.
+ * <p>
+ * We need to convert any methods from SchemaMethod to Map and any properties from SchemaProperty to Map
+ *
+ * @return the underlying map.
+ */
+ @Override
+ public Map<String, Object> mapEncode()
+ {
+ List<Map> mapEncodedMethods = new ArrayList<Map>();
+ for (SchemaMethod m : _methods)
+ {
+ mapEncodedMethods.add(m.mapEncode());
+ }
+ setValue("_methods", mapEncodedMethods);
+
+ List<Map> mapEncodedProperties = new ArrayList<Map>();
+ for (SchemaProperty p : _properties)
+ {
+ mapEncodedProperties.add(p.mapEncode());
+ }
+ setValue("_properties", mapEncodedProperties);
+
+ setValue("_schema_id", getClassId().mapEncode());
+
+ return super.mapEncode();
+ }
+
+ /**
+ * Generate the partial hash for the schema fields in this class.
+ * @param md5 the MessageDigest to be updated.
+ */
+ @Override
+ protected void updateHash(MessageDigest md5)
+ {
+ super.updateHash(md5);
+
+ for (SchemaMethod m : _methods)
+ {
+ m.updateHash(md5);
+ }
+
+ for (SchemaProperty p : _properties)
+ {
+ p.updateHash(md5);
+ }
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaProperty.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaProperty.java
new file mode 100644
index 0000000000..80c6e3ebd0
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaProperty.java
@@ -0,0 +1,364 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.util.Map;
+
+// Reuse this class as it provides a handy mechanism to parse an options String into a Map
+import org.apache.qpid.messaging.util.AddressParser;
+
+/**
+ * The SchemaProperty class describes a single data item in a QmfData object. A SchemaProperty is a list of
+ * named attributes and their values. QMF defines a set of primitive attributes. An application can extend
+ * this set of attributes with application-specific attributes.
+ * <p>
+ * QMF reserved attribute names all start with the underscore character ("_"). Do not create an application-specific
+ * attribute with a name starting with an underscore.
+ * <p>
+ * Once instantiated, the SchemaProperty is immutable.
+ * <p>
+ * Note that there appear to be some differences between the fields mentioned in
+ * <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API propsal</a> and
+ * <a href=https://cwiki.apache.org/qpid/qmf-map-message-protocol.html>QMF2 Map Message protocol</a>.
+ * I've gone with what's stated in the protocol documentation as this seems more accurate, at least for Qpid 0.10
+ *
+ * @author Fraser Adams
+ */
+public final class SchemaProperty extends QmfData
+{
+ /**
+ * Construct a SchemaProperty from its Map representation.
+ */
+ public SchemaProperty(final Map m)
+ {
+ super(m);
+ }
+
+ /**
+ * Construct a SchemaProperty from its name and type.
+ *
+ * @param name the name of the SchemaProperty.
+ * @param type the QmfType of the SchemaProperty.
+ */
+ public SchemaProperty(final String name, final QmfType type) throws QmfException
+ {
+ this(name, type, null);
+ }
+
+ /**
+ * Construct a SchemaProperty from its name, type and options.
+ *
+ * @param name the name of the SchemaProperty.
+ * @param type the QmfType of the SchemaProperty.
+ * @param options a String containing the SchemaProperty options in the form:
+ * <pre>
+ * "{&lt;option1&gt;: &lt;value1&gt;, &lt;option2&gt;: &lt;value2&gt;}".
+ * </pre>
+ * Example:
+ * <pre>
+ * "{dir:IN, unit:metre, min:1, max:5, desc: 'ladder extension range'}"
+ * </pre>
+ */
+ public SchemaProperty(final String name, final QmfType type, final String options) throws QmfException
+ {
+ setValue("_name", name);
+ setValue("_type", type.toString());
+
+ if (options != null && options.length() != 0)
+ {
+ Map optMap = new AddressParser(options).map();
+
+ if (optMap.containsKey("index"))
+ {
+ String value = optMap.get("index").toString();
+ setValue("_index", Boolean.valueOf(value));
+ }
+
+ if (optMap.containsKey("access"))
+ {
+ String value = (String)optMap.get("access");
+ if (value.equals("RC") || value.equals("RO") || value.equals("RW"))
+ {
+ setValue("_access", value);
+ }
+ else
+ {
+ throw new QmfException("Invalid value for 'access' option. Expected RC, RO, or RW");
+ }
+ }
+
+ if (optMap.containsKey("unit"))
+ {
+ String value = (String)optMap.get("unit");
+ setValue("_unit", value);
+ }
+
+ if (optMap.containsKey("min"))
+ { // Slightly odd way of parsing, but AddressParser stores integers as Integer, which seems OK but it limits
+ // things like queue length to 2GB. I think this should change so this code deliberately avoids assuming
+ // integers are encoded as Integer by using the String representation instead.
+ long value = Long.parseLong(optMap.get("min").toString());
+ setValue("_min", value);
+ }
+
+ if (optMap.containsKey("max"))
+ { // Slightly odd way of parsing, but AddressParser stores integers as Integer. which seems OK but it limits
+ // things like queue length to 2GB. I think this should change so this code deliberately avoids assuming
+ // integers are encoded as Integer by using the String representation instead.
+ long value = Long.parseLong(optMap.get("max").toString());
+ setValue("_max", value);
+ }
+
+ if (optMap.containsKey("maxlen"))
+ { // Slightly odd way of parsing, but AddressParser stores integers as Integer, which seems OK but it limits
+ // things like queue length to 2GB. I think this should change so this code deliberately avoids assuming
+ // integers are encoded as Integer by using the String representation instead.
+ long value = Long.parseLong(optMap.get("maxlen").toString());
+ setValue("_maxlen", value);
+ }
+
+ if (optMap.containsKey("desc"))
+ {
+ String value = (String)optMap.get("desc");
+ setValue("_desc", value);
+ }
+
+ if (optMap.containsKey("dir"))
+ {
+ String value = (String)optMap.get("dir");
+ if (value.equals("IN"))
+ {
+ setValue("_dir", "I");
+ }
+ else if (value.equals("OUT"))
+ {
+ setValue("_dir", "O");
+ }
+ else if (value.equals("INOUT"))
+ {
+ setValue("_dir", "IO");
+ }
+ else
+ {
+ throw new QmfException("Invalid value for 'dir' option. Expected IN, OUT, or INOUT");
+ }
+ }
+
+ if (optMap.containsKey("subtype"))
+ {
+ String value = (String)optMap.get("subtype");
+ setValue("_subtype", value);
+ }
+ }
+ }
+
+ /**
+ * Return the property's name.
+ * @return the property's name.
+ */
+ public String getName()
+ {
+ return getStringValue("_name");
+ }
+
+ /**
+ * Return the property's QmfType.
+ * @return the property's QmfType.
+ */
+ public QmfType getType()
+ {
+ return QmfType.valueOf(getStringValue("_type"));
+ }
+
+ /**
+ * Return true iff this property is an index of an object.
+ * @return true iff this property is an index of an object. Default is false.
+ */
+ public boolean isIndex()
+ {
+ return hasValue("_index") ? getBooleanValue("_index") : false;
+ }
+
+ /**
+ * Return true iff this property is optional.
+ * @return true iff this property is optional. Default is false.
+ */
+ public boolean isOptional()
+ {
+ return hasValue("_optional") ? getBooleanValue("_optional") : false;
+ }
+
+ /**
+ * Return the property's remote access rules.
+ * @return the property's remote access rules "RC"=read/create, "RW"=read/write, "RO"=read only (default).
+ */
+ public String getAccess()
+ {
+ return getStringValue("_access");
+ }
+
+ /**
+ * Return an annotation string describing units of measure for numeric values (optional).
+ * @return an annotation string describing units of measure for numeric values (optional).
+ */
+ public String getUnit()
+ {
+ return getStringValue("_unit");
+ }
+
+ /**
+ * Return minimum value (optional).
+ * @return minimum value (optional).
+ */
+ public long getMin()
+ {
+ return getLongValue("_min");
+ }
+
+ /**
+ * Return maximum value (optional).
+ * @return maximum value (optional).
+ */
+ public long getMax()
+ {
+ return getLongValue("_max");
+ }
+
+ /**
+ * Return maximum length for string values (optional).
+ * @return maximum length for string values (optional).
+ */
+ public long getMaxLen()
+ {
+ return getLongValue("_maxlen");
+ }
+
+ /**
+ * Return optional string description of this Property.
+ * @return optional string description of this Property.
+ */
+ public String getDesc()
+ {
+ return getStringValue("_desc");
+ }
+
+ /**
+ * Return the direction of information travel.
+ * @return "I"=input, "O"=output, or "IO"=input/output (required for method arguments, otherwise optional).
+ */
+ public String getDirection()
+ {
+ return getStringValue("_dir");
+ }
+
+ /**
+ * Return string indicating the formal application type.
+ * @return string indicating the formal application type for the data, example: "URL", "Telephone number", etc.
+ */
+ public String getSubtype()
+ {
+ return getStringValue("_subtype");
+ }
+
+ /**
+ * Return a SchemaClassId. If the type is a reference to another managed object.
+ * @return a SchemaClassId. If the type is a reference to another managed object, this field may be used.
+ to specify the required class for that object
+ */
+ public SchemaClassId getReference()
+ {
+ return new SchemaClassId((Map)getValue("_references"));
+ }
+
+ /**
+ * Return the value of the attribute named "name".
+ * @return the value of the attribute named "name".
+ * <p>
+ * This method can be used to retrieve application-specific attributes. "name" should start with the prefix "x-".
+ */
+ public String getAttribute(final String name)
+ {
+ return getStringValue(name);
+ }
+
+ /**
+ * Generate the partial hash for the schema fields in this class.
+ * @param md5 the MessageDigest to be updated
+ */
+ protected void updateHash(MessageDigest md5)
+ {
+ try
+ {
+ md5.update(getName().getBytes("UTF-8"));
+ md5.update(getType().toString().getBytes("UTF-8"));
+ md5.update(getSubtype().getBytes("UTF-8"));
+ md5.update(getAccess().getBytes("UTF-8"));
+ if (isIndex())
+ {
+ md5.update((byte)1);
+ }
+ else
+ {
+ md5.update((byte)0);
+ }
+ if (isOptional())
+ {
+ md5.update((byte)1);
+ }
+ else
+ {
+ md5.update((byte)0);
+ }
+ md5.update(getUnit().getBytes("UTF-8"));
+ md5.update(getDesc().getBytes("UTF-8"));
+ md5.update(getDirection().getBytes("UTF-8"));
+ }
+ catch (UnsupportedEncodingException uee)
+ {
+ }
+ }
+
+ /**
+ * Helper/debug method to list the QMF Object properties and their type.
+ */
+ @Override
+ public void listValues()
+ {
+ System.out.println("SchemaProperty:");
+ System.out.println("_name: " + getName());
+ System.out.println("_type: " + getType());
+ if (hasValue("_index")) System.out.println("is index: " + isIndex());
+ if (hasValue("_optional")) System.out.println("is optional: " + isOptional());
+ if (hasValue("_access")) System.out.println("access: " + getAccess());
+ if (hasValue("_unit")) System.out.println("unit: " + getUnit());
+ if (hasValue("_min")) System.out.println("min: " + getMin());
+ if (hasValue("_max")) System.out.println("max: " + getMax());
+ if (hasValue("_max_len")) System.out.println("maxlen: " + getMaxLen());
+ if (hasValue("_desc")) System.out.println("desc: " + getDesc());
+ if (hasValue("_dir")) System.out.println("dir: " + getDirection());
+ if (hasValue("_subtype")) System.out.println("subtype: " + getSubtype());
+ if (hasValue("_references")) System.out.println("reference: " + getReference());
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/WorkItem.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/WorkItem.java
new file mode 100644
index 0000000000..90afc70f31
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/WorkItem.java
@@ -0,0 +1,400 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+import java.util.HashMap;
+import java.util.Map;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.console.Agent;
+
+/**
+ * Descriptions below are taken from <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API Proposal</a>
+ * <p>
+ * A WorkItem describes an event that has arrived for the application to process.
+ * <p>
+ * The Notifier is invoked when one or more WorkItems become available for processing.
+ * <p>
+ * The type of the Object returned by getParams is determined by the WorkItemType and described below.
+ * <p>
+ *
+ * <table width="100%" border="1" summary=""><thead><tr><th>WorkItem Type</th><th>Description</th></tr></thead><tbody>
+ *
+ *
+ * <tr>
+ * <td>AGENT_ADDED:</td>
+ * <td>
+ * When the QMF Console receives the first heartbeat from an Agent, an AGENT_ADDED WorkItem is pushed onto the
+ * work-queue.
+ * <p>
+ * The getParams() method returns a map which contains a reference to the new Console Agent instance. The reference is
+ * indexed from the map using the key string "agent". There is no handle associated with this WorkItem.
+ * <p>
+ * Note: If a new Agent is discovered as a result of the Console findAgent() method, then no AGENT_ADDED WorkItem is
+ * generated for that Agent.
+ * <p>
+ * Use AgentAddedWorkItem to enable neater access.
+ * </td>
+ * </tr>
+ *
+ *
+ * <tr>
+ * <td>AGENT_DELETED:</td>
+ * <td>
+ * When a known Agent stops sending heartbeat messages the Console will time out that Agent. On Agent timeout, an
+ * AGENT_DELETED WorkItem is pushed onto the work-queue.
+ * <p>
+ * The getParams() method returns a map which contains a reference to the Agent instance that has been deleted.
+ * The reference is indexed from the map using the key string "agent". There is no handle associated with this WorkItem.
+ * <p>
+ * The Console application must release all saved references to the Agent before returning the WorkItem.
+ * <p>
+ * Use AgentDeletedWorkItem to enable neater access.
+ * </td>
+ * </tr>
+ *
+ *
+ * <tr>
+ * <td>AGENT_RESTARTED:</td>
+ * <td>
+ * Sent when the QMF Console detects an Agent was restarted, an AGENT_RESTARTED WorkItem is pushed onto the work-queue.
+ * <p>
+ * The getParams() method returns a map which contains a reference to the Console Agent instance. The reference
+ * is indexed from the map using the key string "agent". There is no handle associated with this WorkItem.
+ * <p>
+ * Use AgentRestartedWorkItem to enable neater access.
+ * </td>
+ * </tr>
+ *
+ *
+ * <tr>
+ * <td>AGENT_HEARTBEAT:</td>
+ * <td>
+ * When the QMF Console receives heartbeats from an Agent, an AGENT_HEARTBEAT WorkItem is pushed onto the work-queue.
+ * <p>
+ * The getParams() method returns a map which contains a reference to the Console Agent instance. The reference
+ * is indexed from the map using the key string "agent". There is no handle associated with this WorkItem.
+ * <p>
+ * Note: the first heartbeat results in an AGENT_ADDED WorkItem for Agent not an AGENT_HEARTBEAT.
+ * <p>
+ * Use AgentHeartbeatWorkItem to enable neater access.
+ * </td>
+ * </tr>
+ *
+ *
+ * <tr>
+ * <td>NEW_PACKAGE:</td><td>TBD</td>
+ * </tr>
+ *
+ *
+ * <tr>
+ * <td>NEW_CLASS:</td><td>TBD</td>
+ * </tr>
+ *
+ *
+ * <tr>
+ * <td>OBJECT_UPDATE:</td>
+ * <td>
+ * The OBJECT_UPDATE WorkItem is generated in response to an asynchronous refresh made by a QmfConsoleData object.
+ * <p>
+ * The getParams() method will return a QmfConsoleData.
+ * <p>
+ * The getHandle() method returns the reply handle provided to the refresh() method call.
+ * This handle is merely the handle used for the asynchronous response, it is not associated with the QmfConsoleData
+ * in any other way.
+ * <p>
+ * Use ObjectUpdateWorkItem to enable neater access.
+ * </td>
+ * </tr>
+ *
+ *
+ * <tr>
+ * <td>METHOD_RESPONSE:</td>
+ * <td>
+ * The METHOD_RESPONSE WorkItem is generated in response to an asynchronous invokeMethod made by a QmfConsoleData object.
+ * <p>
+ * The getParams() method will return a MethodResult object.
+ * <p>
+ * The getHandle() method returns the reply handle provided to the refresh() method call.
+ * This handle is merely the handle used for the asynchronous response, it is not associated with the QmfConsoleData
+ * in any other way.
+ * <p>
+ * Use MethodResponseWorkItem to enable neater access.
+ * </td>
+ * </tr>
+ *
+ *
+ * <tr>
+ * <td>EVENT_RECEIVED:</td>
+ * <td>
+ * When an Agent generates a QmfEvent an EVENT_RECEIVED WorkItem is pushed onto the work-queue.
+ * <p>
+ * The getParams() method returns a map which contains a reference to the Console Agent instance that generated
+ * the Event and a reference to the QmfEvent itself. The Agent reference is indexed from the map using the key string
+ * "agent, The QmfEvent reference is indexed from the map using the key string "event". There is no handle associated
+ * with this WorkItem.
+ * <p>
+ * Use EventReceivedWorkItem to enable neater access.
+ * </td>
+ * </tr>
+ *
+ *
+ * <tr>
+ * <td>SUBSCRIBE_RESPONSE:</td>
+ * <td>
+ * The SUBSCRIBE_RESPONSE WorkItem returns the result of a subscription request made by this Console. This WorkItem is
+ * generated when the Console's createSubscription() is called in an asychronous manner, rather than pending for the result.
+ * <p>
+ * The getParams() method will return an instance of the SubscribeParams class.
+ * <p>
+ * The SubscriptionId object must be used when the subscription is refreshed or cancelled. It must be passed to the
+ * Console's refresh_subscription() and cancelSubscription() methods. The value of the SubscriptionId does not change
+ * over the lifetime of the subscription.
+ * <p>
+ * The console handle will be provided by the Agent on each data indication event that corresponds to this subscription.
+ * It should not change for the lifetime of the subscription.
+ * <p>
+ * The getHandle() method returns the reply handle provided to the createSubscription() method call. This handle is
+ * merely the handle used for the asynchronous response, it is not associated with the subscription in any other way.
+ * <p>
+ * Once a subscription is created, the Agent that maintains the subscription will periodically issue updates for the
+ * subscribed data. This update will contain the current values of the subscribed data, and will appear as the first
+ * SUBSCRIPTION_INDICATION WorkItem for this subscription.
+ * <p>
+ * Use SubscribeResponseWorkItem to enable neater access.
+ * </td>
+ * </tr>
+ *
+ *
+ * <tr>
+ * <td>SUBSCRIPTION_INDICATION:</td>
+ * <td>
+ * The SUBSCRIPTION_INDICATION WorkItem signals the arrival of an update to subscribed data from the Agent.
+ * <p>
+ * The getParams() method will return an instance of the SubscribeIndication class.
+ * The getHandle() method returns null.
+ * <p>
+ * Use SubscriptionIndicationWorkItem to enable neater access.
+ * </td>
+ * </tr>
+ *
+ *
+ * <tr>
+ * <td>RESUBSCRIBE_RESPONSE:</td>
+ * <td>
+ * The RESUBSCRIBE_RESPONSE WorkItem is generated in response to a subscription refresh request made by this Console.
+ * This WorkItem is generated when the Console's refreshSubscription() is called in an asychronous manner, rather than
+ * pending for the result.
+ * <p>
+ * The getParams() method will return an instance of the SubscribeParams class.
+ * <p>
+ * The getHandle() method returns the reply handle provided to the refreshSubscription() method call. This handle is
+ * merely the handle used for the asynchronous response, it is not associated with the subscription in any other way.
+ * <p>
+ * Use ResubscribeResponseWorkItem to enable neater access.
+ * </td>
+ * </tr>
+ *
+ *
+ * <tr>
+ * <td>METHOD_CALL:</td>
+ * <td>
+ * The METHOD_CALL WorkItem describes a method call that must be serviced by the application on behalf of this Agent.
+ * <p>
+ * The getParams() method will return an instance of the MethodCallParams class.
+ * <p>
+ * Use MethodCallWorkItem to enable neater access.
+ * </td>
+ * </tr>
+ *
+ *
+ * <tr>
+ * <td>QUERY:</td>
+ * <td>
+ * The QUERY WorkItem describes a query that the application must service. The application should call the
+ * queryResponse() method for each object that satisfies the query. When complete, the application must call the
+ * queryComplete() method. If a failure occurs, the application should indicate the error to the agent by calling
+ * the query_complete() method with a description of the error.
+ * <p>
+ * The getParams() method will return an instance of the QmfQuery class.
+ * <p>
+ * The getHandle() WorkItem method returns the reply handle which should be passed to the Agent's queryResponse()
+ * and queryComplete() methods.
+ * <p>
+ * Use QueryWorkItem to enable neater access.
+ * </td>
+ * </tr>
+ *
+ *
+ * <tr>
+ * <td>SUBSCRIBE_REQUEST:</td>
+ * <td>
+ * The SUBSCRIBE_REQUEST WorkItem provides a query that the agent application must periodically publish until the
+ * subscription is cancelled or expires. On receipt of this WorkItem, the application should call the Agent
+ * subscriptionResponse() method to acknowledge the request. On each publish interval, the application should call Agent
+ * subscriptionIndicate(), passing a list of the objects that satisfy the query. The subscription remains in effect until
+ * an UNSUBSCRIBE_REQUEST WorkItem for the subscription is received, or the subscription expires.
+ * <p>
+ * The getParams() method will return an instance of the SubscriptionParams class.
+ * <p>
+ * The getHandle() WorkItem method returns the reply handle which should be passed to the Agent's
+ * subscriptionResponse() method.
+ * <p>
+ * Use SubscribeRequestWorkItem to enable neater access.
+ * </td>
+ * </tr>
+ *
+ *
+ * <tr>
+ * <td>RESUBSCRIBE_REQUEST:</td>
+ * <td>
+ * The RESUBSCRIBE_REQUEST is sent by a Console to renew an existing subscription. The Console may request a new
+ * duration for the subscription, otherwise the previous lifetime interval is repeated.
+ * <p>
+ * The getParams() method will return an instance of the ResubscribeParams class.
+ * <p>
+ * The getHandle() WorkItem method returns the reply handle which should be passed to the Agent's
+ * subscriptionResponse() method.
+ * <p>
+ * Use ResubscribeRequestWorkItem to enable neater access.
+ * </td>
+ * </tr>
+ *
+ *
+ * <tr>
+ * <td>UNSUBSCRIBE_REQUEST:</td>
+ * <td>
+ * The UNSUBSCRIBE_REQUEST is sent by a Console to terminate an existing subscription. The Agent application should
+ * terminate the given subscription if it exists, and cancel sending any further updates against it.
+ * <p>
+ * The getParams() method will return a String holding the subscriptionId.
+ * <p>
+ * The getHandle() method returns null.
+ * <p>
+ * Use UnsubscribeRequestWorkItem to enable neater access.
+ * </td>
+ * </tr>
+ * </tbody>
+ * </table>
+ * <p>
+ * The following diagram illustrates the QMF2 WorkItem class hierarchy.
+ * <p>
+ * <img alt="" src="doc-files/WorkItem.png">
+ * @author Fraser Adams
+ */
+
+public class WorkItem
+{
+ /**
+ * An Enumeration of the types of WorkItems produced on the Console or Agent.
+ */
+ public enum WorkItemType
+ {
+ // Enumeration of the types of WorkItems produced on the Console
+ AGENT_ADDED,
+ AGENT_DELETED,
+ AGENT_RESTARTED,
+ AGENT_HEARTBEAT,
+ NEW_PACKAGE,
+ NEW_CLASS,
+ OBJECT_UPDATE,
+ EVENT_RECEIVED,
+ METHOD_RESPONSE,
+ SUBSCRIBE_RESPONSE,
+ SUBSCRIPTION_INDICATION,
+ RESUBSCRIBE_RESPONSE,
+ // Enumeration of the types of WorkItems produced on the Agent
+ METHOD_CALL,
+ QUERY,
+ SUBSCRIBE_REQUEST,
+ RESUBSCRIBE_REQUEST,
+ UNSUBSCRIBE_REQUEST;
+ }
+
+ private final WorkItemType _type;
+ private final Handle _handle;
+ private final Object _params;
+
+ /**
+ * Construct a WorkItem
+ *
+ * @param type the type of WorkItem specified by the WorkItemType enum
+ * @param handle the handle passed by async calls - the correlation ID
+ * @param params the payload of the WorkItem
+ */
+ public WorkItem(WorkItemType type, Handle handle, Object params)
+ {
+ _type = type;
+ _handle = handle;
+ _params = params;
+ }
+
+ /**
+ * Return the type of work item.
+ * @return the type of work item.
+ */
+ public final WorkItemType getType()
+ {
+ return _type;
+ }
+
+ /**
+ * Return the reply handle for an asynchronous operation, if present.
+ * @return the reply handle for an asynchronous operation, if present.
+ */
+ public final Handle getHandle()
+ {
+ return _handle;
+ }
+
+ /**
+ * Return the payload of the work item.
+ * @return the payload of the work item.
+ * <p>
+ * The type of this object is determined by the type of the workitem as follows:
+ * <pre>
+ * <b>Console</b>
+ * AGENT_ADDED: Map{"agent":Agent} - Use AgentAddedWorkItem to enable neater access
+ * AGENT_DELETED: Map{"agent":Agent} - Use AgentDeletedWorkItem to enable neater access
+ * AGENT_RESTARTED: Map{"agent":Agent} - Use AgentRestartedWorkItem to enable neater access
+ * AGENT_HEARTBEAT: Map{"agent":Agent} - Use AgentHeartbeatWorkItem to enable neater access
+ * OBJECT_UPDATE: QmfConsoleData - Use ObjectUpdateWorkItem to enable neater access
+ * METHOD_RESPONSE: MethodResult - Use MethodResponseWorkItem to enable neater access
+ * EVENT_RECEIVED: Map{"agent":Agent, "event":QmfEvent} - Use EventReceivedWorkItem to enable neater access
+ * SUBSCRIBE_RESPONSE: SubscribeParams - Use SubscribeResponseWorkItem to enable neater access
+ * SUBSCRIPTION_INDICATION: SubscribeIndication - Use SubscriptionIndicationWorkItem to enable neater access
+ * RESUBSCRIBE_RESPONSE: SubscribeParams - Use ResubscribeResponseWorkItem to enable neater access
+ *
+ * <b>Agent</b>
+ * METHOD_CALL: MethodCallParams - Use MethodCallWorkItem to enable neater access
+ * QUERY: QmfQuery - Use QueryWorkItem to enable neater access
+ * SUBSCRIBE_REQUEST: SubscriptionParams - Use SubscribeRequestWorkItem to enable neater access
+ * RESUBSCRIBE_REQUEST: ResubscribeParams - Use ResubscribeRequestWorkItem to enable neater access
+ * UNSUBSCRIBE_REQUEST: String (subscriptionId) - Use UnsubscribeRequestWorkItem to enable neater access
+ * </pre>
+ */
+ @SuppressWarnings("unchecked")
+ public final <T> T getParams()
+ {
+ return (T)_params;
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/WorkQueue.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/WorkQueue.java
new file mode 100644
index 0000000000..75b4e5d855
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/WorkQueue.java
@@ -0,0 +1,106 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.common;
+
+// Misc Imports
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This is an implementation of a QMF2 WorkQueue. In practice this is likely to be used by an Agent or Console.
+ *
+ * @author Fraser Adams
+ */
+public class WorkQueue
+{
+ /**
+ * Used to implement a thread safe queue of WorkItem objects
+ */
+ private BlockingQueue<WorkItem> _workQueue = new LinkedBlockingQueue<WorkItem>();
+
+ /**
+ * Return the count of pending WorkItems that can be retrieved.
+ * @return the count of pending WorkItems that can be retrieved.
+ */
+ public int size()
+ {
+ return _workQueue.size();
+ }
+
+ /**
+ * Obtains the next pending work item - blocking version
+ *
+ * @return the next pending work item, or null if none available.
+ */
+ public WorkItem getNextWorkitem()
+ {
+ try
+ {
+ return _workQueue.take();
+ }
+ catch (InterruptedException ie)
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Obtains the next pending work item - balking version
+ *
+ * @param timeout the timeout in seconds. If timeout = 0 it returns immediately with either a WorkItem or null
+ * @return the next pending work item, or null if none available.
+ */
+ public WorkItem getNextWorkitem(long timeout)
+ {
+ try
+ {
+ return _workQueue.poll(timeout, TimeUnit.SECONDS);
+ }
+ catch (InterruptedException ie)
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Adds a WorkItem to the WorkQueue.
+ *
+ * @param item the WorkItem passed to the WorkQueue
+ */
+ public void addWorkItem(WorkItem item)
+ {
+ // We wrap the blocking put() method in a loop "just in case" InterruptedException occurs
+ // if it does we retry the put otherwise we carry on, notify then exit.
+ while (true)
+ {
+ try
+ {
+ _workQueue.put(item);
+ break;
+ }
+ catch (InterruptedException ie)
+ {
+ continue;
+ }
+ }
+ }
+}
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/Console.png b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/Console.png
new file mode 100644
index 0000000000..cb2a5ee800
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/Console.png
Binary files differ
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/QmfData.png b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/QmfData.png
new file mode 100644
index 0000000000..2665803e39
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/QmfData.png
Binary files differ
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/QmfEventListenerModel.png b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/QmfEventListenerModel.png
new file mode 100644
index 0000000000..26a5f71b56
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/QmfEventListenerModel.png
Binary files differ
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/QmfQuery.png b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/QmfQuery.png
new file mode 100644
index 0000000000..9e471a08c0
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/QmfQuery.png
Binary files differ
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/Schema.png b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/Schema.png
new file mode 100644
index 0000000000..b0277f4fc5
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/Schema.png
Binary files differ
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/Subscriptions.png b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/Subscriptions.png
new file mode 100644
index 0000000000..977e222129
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/Subscriptions.png
Binary files differ
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/WorkItem.png b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/WorkItem.png
new file mode 100644
index 0000000000..14c8c56d28
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/WorkItem.png
Binary files differ
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/WorkQueueEventModel.png b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/WorkQueueEventModel.png
new file mode 100644
index 0000000000..fc2a722985
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/WorkQueueEventModel.png
Binary files differ
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/Agent.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/Agent.java
new file mode 100644
index 0000000000..26c80df809
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/Agent.java
@@ -0,0 +1,482 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.console;
+
+// Misc Imports
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.ObjectId;
+import org.apache.qpid.qmf2.common.QmfData;
+import org.apache.qpid.qmf2.common.QmfException;
+import org.apache.qpid.qmf2.common.SchemaClass;
+import org.apache.qpid.qmf2.common.SchemaClassId;
+
+/**
+ * Local representation (proxy) of a remote Agent.
+ * <p>
+ * This class holds some state that relates to the Agent and in addition some methods may be called on the agent.
+ * destroy(), invokeMethod() and refresh() are actually called by a proxy class AgentProxy. AgentProxy is actually
+ * an interface that is implemented by Console (as that's where all the JMS stuff is), we use this approach to
+ * avoid introducing a circular dependency between Agent and Console.
+ * <p>
+ * The Console application maintains a list of all known remote Agents.
+ * Each Agent is represented by an instance of the Agent class:
+ * <p>
+ * The following diagram illustrates the interactions between the Console, AgentProxy and the client side Agent
+ * representation.
+ * <p>
+ * <img alt="" src="doc-files/Subscriptions.png">
+ * @author Fraser Adams
+ */
+public final class Agent extends QmfData
+{
+ private AgentProxy _proxy;
+ private List<String> _packages = new ArrayList<String>();
+ private Map<SchemaClassId, SchemaClass> _schemaCache = new ConcurrentHashMap<SchemaClassId, SchemaClass>();
+ private long _epoch;
+ private long _heartbeatInterval;
+ private long _timestamp;
+ private boolean _eventsEnabled = true;
+ private boolean _isActive = true;
+
+ /**
+ * The main constructor, taking a java.util.Map as a parameter. In essence it "deserialises" its state from the Map.
+ *
+ * @param m the map used to construct the SchemaClass
+ * @param p the AgentProxy instance that implements some of the concrete behaviour of the local Agent representation.
+ */
+ public Agent(final Map m, final AgentProxy p)
+ {
+ super(m);
+ // Populate attributes translating any old style keys if necessary.
+ _epoch = hasValue("_epoch") ? getLongValue("_epoch") : getLongValue("epoch");
+ _heartbeatInterval = hasValue("_heartbeat_interval") ? getLongValue("_heartbeat_interval") :
+ getLongValue("heartbeat_interval");
+ _timestamp = hasValue("_timestamp") ? getLongValue("_timestamp") : getLongValue("timestamp");
+ _proxy = p;
+ }
+
+ /**
+ * Sets the state of the Agent, used as an assignment operator.
+ *
+ * @param m the Map used to initialise the Agent.
+ */
+ @SuppressWarnings("unchecked")
+ public void initialise(final Map m)
+ {
+ Map<String, Object> values = (Map<String, Object>)m.get("_values");
+ _values = (values == null) ? m : values;
+
+ // Populate attributes translating any old style keys if necessary.
+ _epoch = hasValue("_epoch") ? getLongValue("_epoch") : getLongValue("epoch");
+ _heartbeatInterval = hasValue("_heartbeat_interval") ? getLongValue("_heartbeat_interval") :
+ getLongValue("heartbeat_interval");
+ _timestamp = hasValue("_timestamp") ? getLongValue("_timestamp") : getLongValue("timestamp");
+ }
+
+ /**
+ * Return whether or not events are enabled for this Agent.
+ * @return a boolean indication of whether or not events are enabled for this Agent.
+ */
+ public boolean eventsEnabled()
+ {
+ return _eventsEnabled;
+ }
+
+ /**
+ * Deactivated this Agent. Called by the Console when the Agent times out.
+ */
+ public void deactivate()
+ {
+ _isActive = false;
+ }
+
+ /**
+ * Return the Agent instance name.
+ * @return the Agent instance name.
+ */
+ public String getInstance()
+ {
+ return getStringValue("_instance");
+ }
+
+ /**
+ * Return the identifying name string of the Agent.
+ * @return the identifying name string of the Agent. This name is used to send AMQP messages directly to this agent.
+ */
+ public String getName()
+ {
+ return getStringValue("_name");
+ }
+
+ /**
+ * Return the product name string of the Agent.
+ * @return the product name string of the Agent.
+ */
+ public String getProduct()
+ {
+ return getStringValue("_product");
+ }
+
+ /**
+ * Return the Agent vendor name.
+ * @return the Agent vendor name.
+ */
+ public String getVendor()
+ {
+ return getStringValue("_vendor");
+ }
+
+ /**
+ * Return the Epoch stamp.
+ * @return the Epoch stamp, used to determine if an Agent has been restarted.
+ */
+ public long getEpoch()
+ {
+ return _epoch;
+ }
+
+ /**
+ * Set the Epoch stamp.
+ * @param epoch the new Epoch stamp, used to indicate that an Agent has been restarted.
+ */
+ public void setEpoch(long epoch)
+ {
+ _epoch = epoch;
+ }
+
+ /**
+ * Return the time that the Agent waits between sending hearbeat messages.
+ * @return the time that the Agent waits between sending hearbeat messages.
+ */
+ public long getHeartbeatInterval()
+ {
+ return _heartbeatInterval;
+ }
+
+ /**
+ * Return the timestamp of the Agent's last update.
+ * @return the timestamp of the Agent's last update.
+ */
+ public long getTimestamp()
+ {
+ return _timestamp;
+ }
+
+ /**
+ * Return true if the agent is alive.
+ * @return true if the agent is alive (heartbeats have not timed out).
+ */
+ public boolean isActive()
+ {
+ return _isActive;
+ }
+
+ /**
+ * Request that the Agent updates the value of this object's contents.
+ *
+ * @param objectId the ObjectId being queried for..
+ * @param replyHandle the correlation handle used to tie asynchronous method requests with responses.
+ * @param timeout the maximum time to wait for a response, overrides default replyTimeout.
+ * @return the refreshed object.
+ */
+ public QmfConsoleData refresh(final ObjectId objectId, final String replyHandle, final int timeout) throws QmfException
+ {
+ if (isActive())
+ {
+ return _proxy.refresh(this, objectId, replyHandle, timeout);
+ }
+ else
+ {
+ throw new QmfException("Agent.refresh() called from deactivated Agent");
+ }
+ }
+
+ /**
+ * Helper method to create a Map containing a QMF method request.
+ *
+ * @param objectId the objectId of the remote object.
+ * @param name the remote method name.
+ * @param inArgs the formal parameters of the remote method name.
+ * @return a Map containing a QMF method request.
+ */
+ private Map<String, Object> createRequest(final ObjectId objectId, final String name, final QmfData inArgs)
+ {
+ // Default sizes for HashMap should be fine for request
+ Map<String, Object> request = new HashMap<String, Object>();
+ if (objectId != null)
+ {
+ request.put("_object_id", objectId.mapEncode());
+ }
+ request.put("_method_name", name);
+ if (inArgs != null)
+ {
+ request.put("_arguments", inArgs.mapEncode());
+ if (inArgs.getSubtypes() != null)
+ {
+ request.put("_subtypes", inArgs.getSubtypes());
+ }
+ }
+ return request;
+ }
+
+ /**
+ * Sends a method request to the Agent. Delegates to the AgentProxy to actually send the method as it's the
+ * AgentProxy that knows about connections, sessions and messages.
+ *
+ * @param objectId the objectId of the remote object.
+ * @param name the remote method name.
+ * @param inArgs the formal parameters of the remote method name.
+ * @param timeout the maximum time to wait for a response, overrides default replyTimeout.
+ * @return the MethodResult.
+ */
+ protected MethodResult invokeMethod(final ObjectId objectId, final String name,
+ final QmfData inArgs, final int timeout) throws QmfException
+ {
+ if (isActive())
+ {
+ return _proxy.invokeMethod(this, createRequest(objectId, name, inArgs), null, timeout);
+ }
+ else
+ {
+ throw new QmfException("Agent.invokeMethod() called from deactivated Agent");
+ }
+ }
+
+ /**
+ * Sends an asynchronous method request to the Agent. Delegates to the AgentProxy to actually send the method as
+ * it's the AgentProxy that knows about connections, sessions and messages.
+ *
+ * @param objectId the objectId of the remote object.
+ * @param name the remote method name.
+ * @param inArgs the formal parameters of the remote method name.
+ * @param replyHandle the correlation handle used to tie asynchronous method requests with responses.
+ */
+ protected void invokeMethod(final ObjectId objectId, final String name,
+ final QmfData inArgs, final String replyHandle) throws QmfException
+ {
+ if (isActive())
+ {
+ _proxy.invokeMethod(this, createRequest(objectId, name, inArgs), replyHandle, -1);
+ }
+ else
+ {
+ throw new QmfException("Agent.invokeMethod() called from deactivated Agent");
+ }
+ }
+
+ /**
+ * Sends a method request to the Agent. Delegates to the AgentProxy to actually send the method as it's the
+ * AgentProxy that knows about connections, sessions and messages.
+ *
+ * @param name the remote method name.
+ * @param inArgs the formal parameters of the remote method name.
+ * @return the MethodResult.
+ */
+ public MethodResult invokeMethod(final String name, final QmfData inArgs) throws QmfException
+ {
+ return invokeMethod(null, name, inArgs, -1);
+ }
+
+ /**
+ * Sends a method request to the Agent. Delegates to the AgentProxy to actually send the method as it's the
+ * AgentProxy that knows about connections, sessions and messages.
+ *
+ * @param name the remote method name.
+ * @param inArgs the formal parameters of the remote method name.
+ * @param timeout the maximum time to wait for a response, overrides default replyTimeout.
+ * @return the MethodResult.
+ */
+ public MethodResult invokeMethod(final String name, final QmfData inArgs, final int timeout) throws QmfException
+ {
+ return invokeMethod(null, name, inArgs, timeout);
+ }
+
+ /**
+ * Sends a method request to the Agent. Delegates to the AgentProxy to actually send the method as it's the
+ * AgentProxy that knows about connections, sessions and messages.
+ *
+ * @param name the remote method name.
+ * @param inArgs the formal parameters of the remote method name.
+ * @param replyHandle the correlation handle used to tie asynchronous method requests with responses.
+ */
+ public void invokeMethod(final String name, final QmfData inArgs, final String replyHandle) throws QmfException
+ {
+ invokeMethod(null, name, inArgs, replyHandle);
+ }
+
+ /**
+ * Remove a Subscription. Delegates to the AgentProxy to actually remove the Subscription as it's the AgentProxy
+ * that really knows about subscriptions.
+ *
+ * @param subscription the SubscriptionManager that we wish to remove.
+ */
+ public void removeSubscription(final SubscriptionManager subscription)
+ {
+ _proxy.removeSubscription(subscription);
+ }
+
+ /**
+ * Allows reception of events from this agent.
+ */
+ public void enableEvents()
+ {
+ _eventsEnabled = true;
+ }
+
+ /**
+ * Prevents reception of events from this agent.
+ */
+ public void disableEvents()
+ {
+ _eventsEnabled = false;
+ }
+
+ /**
+ * Releases this Agent instance. Once called, the Console application should not reference this instance again.
+ */
+ public void destroy()
+ {
+ _timestamp = 0;
+ _proxy.destroy(this);
+ }
+
+ /**
+ * Clears the internally cached schema. Generally done when we wich to refresh the schema information from the
+ * remote Agent.
+ */
+ public void clearSchemaCache()
+ {
+ _schemaCache.clear();
+ _packages.clear();
+ }
+
+ /**
+ * Stores the schema and package information obtained by querying the remote Agent.
+ *
+ * @param classes the list of SchemaClassIds obtained by querying the remote Agent.
+ */
+ public void setClasses(final List<SchemaClassId> classes)
+ {
+ if (classes == null)
+ {
+ clearSchemaCache();
+ return;
+ }
+
+ for (SchemaClassId classId : classes)
+ {
+ _schemaCache.put(classId, SchemaClass.EMPTY_SCHEMA);
+ if (!_packages.contains(classId.getPackageName()))
+ {
+ _packages.add(classId.getPackageName());
+ }
+ }
+ }
+
+ /**
+ * Return the list of SchemaClassIds associated with this Agent.
+ * @return the list of SchemaClassIds associated with this Agent.
+ */
+ public List<SchemaClassId> getClasses()
+ {
+ if (_schemaCache.size() == 0)
+ {
+ return Collections.emptyList();
+ }
+ return new ArrayList<SchemaClassId>(_schemaCache.keySet());
+ }
+
+ /**
+ * Return the list of packages associated with this Agent.
+ * @return the list of packages associated with this Agent.
+ */
+ public List<String> getPackages()
+ {
+ return _packages;
+ }
+
+ /**
+ * Return the SchemaClass associated with this Agent.
+ * @return the list of SchemaClass associated with this Agent.
+ * <p>
+ * I <i>believe</i> that there should only be one entry in the list returned when looking up a specific chema by classId.
+ */
+ public List<SchemaClass> getSchema(final SchemaClassId classId)
+ {
+ SchemaClass schema = _schemaCache.get(classId);
+ if (schema == SchemaClass.EMPTY_SCHEMA)
+ {
+ return Collections.emptyList();
+ }
+
+ List<SchemaClass> results = new ArrayList<SchemaClass>();
+ results.add(schema);
+ return results;
+ }
+
+ /**
+ * Set a schema keyed by SchemaClassId.
+ *
+ * @param classId the SchemaClassId indexing the particular schema.
+ * @param schemaList the schema being indexed.
+ * <p>
+ * I <i>believe</i> that there should only be one entry in the list returned when looking up a specific chema by classId.
+ */
+ public void setSchema(final SchemaClassId classId, final List<SchemaClass> schemaList)
+ {
+ if (schemaList == null || schemaList.size() == 0)
+ {
+ _schemaCache.put(classId, SchemaClass.EMPTY_SCHEMA);
+ }
+ else
+ {
+ // I believe that there should only be one entry in the list returned when looking up
+ // a specific chema by classId
+ _schemaCache.put(classId, schemaList.get(0));
+ }
+ }
+
+ /**
+ * Helper/debug method to list the QMF Object properties and their type.
+ */
+ @Override
+ public void listValues()
+ {
+ super.listValues();
+ System.out.println("Agent:");
+ System.out.println("instance: " + getInstance());
+ System.out.println("name: " + getName());
+ System.out.println("product: " + getProduct());
+ System.out.println("vendor: " + getVendor());
+ System.out.println("epoch: " + getEpoch());
+ System.out.println("heartbeatInterval: " + getHeartbeatInterval());
+ System.out.println("timestamp: " + new Date(getTimestamp()/1000000l));
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentAccessWorkItem.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentAccessWorkItem.java
new file mode 100644
index 0000000000..ff5668fe2e
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentAccessWorkItem.java
@@ -0,0 +1,80 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.console;
+
+import java.util.HashMap;
+import java.util.Map;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.Handle;
+import org.apache.qpid.qmf2.common.QmfEvent;
+import org.apache.qpid.qmf2.common.WorkItem;
+
+/**
+ * Abstract class that acts as a superclass for all WorkItems that need to set and retrieve an Agent.
+ * <p>
+ * This class is a convenience class to enable neater access the the WorkItem params for this type of WorkItem.
+ *
+ * @author Fraser Adams
+ */
+
+public abstract class AgentAccessWorkItem extends WorkItem
+{
+ /**
+ * Helper method to create the WorkItem params as a Map.
+ *
+ * @param agent the Agent associated with the WorkItem.
+ * @param event the QmfEvent associated with the WorkItem.
+ */
+ protected static Map<String, Object> newParams(final Agent agent, final QmfEvent event)
+ {
+ Map<String, Object> params = new HashMap<String, Object>();
+ params.put("agent", agent);
+ if (event != null)
+ {
+ params.put("event", event);
+ }
+ return params;
+ }
+
+ /**
+ * Construct an AgentAccessWorkItem. Convenience constructor not in API
+ *
+ * @param type the type of WorkItem specified by the WorkItemType enum
+ * @param handle the handle passed by async calls - the correlation ID
+ * @param params the payload of the WorkItem
+ */
+ public AgentAccessWorkItem(final WorkItemType type, final Handle handle, final Object params)
+ {
+ super(type, handle, params);
+ }
+
+ /**
+ * Return the Agent stored in the params Map.
+ * @return the Agent stored in the params Map.
+ */
+ public final Agent getAgent()
+ {
+ Map<String, Object> p = this.<Map<String, Object>>getParams();
+ return (Agent)p.get("agent");
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentAddedWorkItem.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentAddedWorkItem.java
new file mode 100644
index 0000000000..6f7b1812c6
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentAddedWorkItem.java
@@ -0,0 +1,49 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.console;
+
+/**
+ * Descriptions below are taken from <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API Proposal</a>
+ * <pre>
+ * AGENT_ADDED: When the QMF Console receives the first heartbeat from an Agent, an AGENT_ADDED WorkItem
+ * is pushed onto the work-queue. The WorkItem's getParam() call returns a map which contains
+ * a reference to the new Console Agent instance. The reference is indexed from the map using
+ * the key string "agent". There is no handle associated with this WorkItem.
+ *
+ * Note: If a new Agent is discovered as a result of the Console findAgent() method, then no
+ * AGENT_ADDED WorkItem is generated for that Agent.
+ * </pre>
+ * @author Fraser Adams
+ */
+
+public final class AgentAddedWorkItem extends AgentAccessWorkItem
+{
+ /**
+ * Construct an AgentAddedWorkItem. Convenience constructor not in API
+ *
+ * @param agent the Agent used to populate the WorkItem's param
+ */
+ public AgentAddedWorkItem(final Agent agent)
+ {
+ super(WorkItemType.AGENT_ADDED, null, newParams(agent, null));
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentDeletedWorkItem.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentDeletedWorkItem.java
new file mode 100644
index 0000000000..9644d205a7
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentDeletedWorkItem.java
@@ -0,0 +1,50 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.console;
+
+/**
+ * Descriptions below are taken from <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API Proposal</a>
+ * <pre>
+ * AGENT_DELETED: When a known Agent stops sending heartbeat messages, the Console will time out that Agent.
+ * On Agent timeout, an AGENT_DELETED WorkItem is pushed onto the work-queue. The WorkItem's
+ * getParam() call returns a map which contains a reference to the Agent instance that has
+ * been deleted. The reference is indexed from the map using the key string "agent". There is
+ * no handle associated with this WorkItem.
+ *
+ * The Console application must release all saved references to the Agent before returning the
+ * WorkItem.
+ * </pre>
+ * @author Fraser Adams
+ */
+
+public final class AgentDeletedWorkItem extends AgentAccessWorkItem
+{
+ /**
+ * Construct an AgentDeletedWorkItem. Convenience constructor not in API
+ *
+ * @param agent the Agent used to populate the WorkItem's param
+ */
+ public AgentDeletedWorkItem(final Agent agent)
+ {
+ super(WorkItemType.AGENT_DELETED, null, newParams(agent, null));
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentHeartbeatWorkItem.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentHeartbeatWorkItem.java
new file mode 100644
index 0000000000..4406d96567
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentHeartbeatWorkItem.java
@@ -0,0 +1,48 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.console;
+
+/**
+ * Descriptions below are taken from <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API Proposal</a>
+ * <pre>
+ * AGENT_HEARTBEAT: When the QMF Console receives heartbeats from an Agent, an AGENT_HEARTBEAT WorkItem
+ * is pushed onto the work-queue. The WorkItem's getParam() call returns a map which contains
+ * a reference to the Console Agent instance. The reference is indexed from the map using
+ * the key string "agent". There is no handle associated with this WorkItem.
+ *
+ * Note: the first heartbeat results in an AGENT_ADDED WorkItem for Agent not an AGENT_HEARTBEAT.
+ * </pre>
+ * @author Fraser Adams
+ */
+
+public final class AgentHeartbeatWorkItem extends AgentAccessWorkItem
+{
+ /**
+ * Construct an AgentHeartbeatWorkItem. Convenience constructor not in API
+ *
+ * @param agent the Agent used to populate the WorkItem's param
+ */
+ public AgentHeartbeatWorkItem(final Agent agent)
+ {
+ super(WorkItemType.AGENT_HEARTBEAT, null, newParams(agent, null));
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentProxy.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentProxy.java
new file mode 100644
index 0000000000..55eb0df7b6
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentProxy.java
@@ -0,0 +1,85 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.console;
+
+// Misc Imports
+import java.util.Map;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.ObjectId;
+import org.apache.qpid.qmf2.common.QmfException;
+
+/**
+ * This interface is implemented by the Console and provides a number of "Agent" related behaviours.
+ * <p>
+ * Arguably it would be possible to implement these directly in the org.apache.qpid.qmf2.console.Agent class but as
+ * it happens they tend to use more Console behaviours, for example refresh() and invokeMethod() are pretty much
+ * about constructing the appropriate JMS Message, so have more in common with the rest of
+ * org.apache.qpid.qmf2.console.Console.
+ * <p>
+ * The purpose of this interface is primarily about removing a circular dependency between Console and Agent so the
+ * Agent doesn't invoke these methods on a Console instance, rather it invokes them on an AgentProxy instance.
+ * <p>
+ * The following diagram illustrates the interactions between the Console, AgentProxy and the client side Agent
+ * representation.
+ * <p>
+ * <img alt="" src="doc-files/Subscriptions.png">
+ * @author Fraser Adams
+ */
+public interface AgentProxy
+{
+ /**
+ * Releases the Agent instance. Once called, the console application should not reference this instance again.
+ *
+ * @param agent the Agent to be destroyed.
+ */
+ public void destroy(Agent agent);
+
+ /**
+ * Request that the Agent update the value of an object's contents.
+ *
+ * @param agent the Agent to get the refresh from.
+ * @param objectId the ObjectId being queried for.
+ * @param replyHandle the correlation handle used to tie asynchronous method requests with responses.
+ * @param timeout the maximum time to wait for a response, overrides default replyTimeout.
+ * @return the refreshed object.
+ */
+ public QmfConsoleData refresh(Agent agent, ObjectId objectId, String replyHandle, int timeout);
+
+ /**
+ * Invoke the named method on the named Agent.
+ *
+ * @param agent the Agent to invoke the method on.
+ * @param content an unordered set of key/value pairs comprising the method arguments.
+ * @param replyHandle the correlation handle used to tie asynchronous method requests with responses.
+ * @param timeout the maximum time to wait for a response, overrides default replyTimeout.
+ * @return the MethodResult.
+ */
+ public MethodResult invokeMethod(Agent agent, Map<String, Object> content, String replyHandle, int timeout) throws QmfException;
+
+ /**
+ * Remove a Subscription.
+ *
+ * @param subscription the SubscriptionManager that we wish to remove.
+ */
+ public void removeSubscription(SubscriptionManager subscription);
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentRestartedWorkItem.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentRestartedWorkItem.java
new file mode 100644
index 0000000000..25bd03654a
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentRestartedWorkItem.java
@@ -0,0 +1,46 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.console;
+
+/**
+ * Descriptions below are taken from <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API Proposal</a>
+ * <pre>
+ * AGENT_RESTARTED: Sent when the QMF Console detects an Agent was restarted, an AGENT_RESTARTED WorkItem
+ * is pushed onto the work-queue. The WorkItem's getParam() call returns a map which contains
+ * a reference to the Console Agent instance. The reference is indexed from the map using
+ * the key string "agent". There is no handle associated with this WorkItem.
+ * </pre>
+ * @author Fraser Adams
+ */
+
+public final class AgentRestartedWorkItem extends AgentAccessWorkItem
+{
+ /**
+ * Construct an AgentRestartedWorkItem. Convenience constructor not in API
+ *
+ * @param agent the Agent used to populate the WorkItem's param
+ */
+ public AgentRestartedWorkItem(final Agent agent)
+ {
+ super(WorkItemType.AGENT_RESTARTED, null, newParams(agent, null));
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/Console.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/Console.java
new file mode 100644
index 0000000000..9a5c2e560d
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/Console.java
@@ -0,0 +1,2237 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.console;
+
+// JMS Imports
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.MapMessage;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.MessageListener;
+import javax.jms.Session;
+
+// Used to get the PID equivalent
+import java.lang.management.ManagementFactory;
+
+// Simple Logging Facade 4 Java
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+// Misc Imports
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.AMQPMessage;
+import org.apache.qpid.qmf2.common.Handle;
+import org.apache.qpid.qmf2.common.Notifier;
+import org.apache.qpid.qmf2.common.NotifierWrapper;
+import org.apache.qpid.qmf2.common.NullQmfEventListener;
+import org.apache.qpid.qmf2.common.ObjectId;
+import org.apache.qpid.qmf2.common.QmfCallback;
+import org.apache.qpid.qmf2.common.QmfData;
+import org.apache.qpid.qmf2.common.QmfEvent;
+import org.apache.qpid.qmf2.common.QmfEventListener;
+import org.apache.qpid.qmf2.common.QmfException;
+import org.apache.qpid.qmf2.common.QmfQuery;
+import org.apache.qpid.qmf2.common.QmfQueryTarget;
+import org.apache.qpid.qmf2.common.SchemaClass;
+import org.apache.qpid.qmf2.common.SchemaClassId;
+import org.apache.qpid.qmf2.common.SchemaEventClass;
+import org.apache.qpid.qmf2.common.SchemaObjectClass;
+import org.apache.qpid.qmf2.common.WorkItem;
+import org.apache.qpid.qmf2.common.WorkQueue;
+
+// Reuse this class as it provides a handy mechanism to parse an options String into a Map
+import org.apache.qpid.messaging.util.AddressParser;
+
+/**
+ * The Console class is the top-level object used by a console application. All QMF console functionality
+ * is made available by this object. A console application must instatiate one of these objects.
+ * <p>
+ * If a name is supplied, it must be unique across all Consoles attached to the AMQP bus under the given
+ * domain. If no name is supplied, a unique name will be synthesized in the format: {@literal "qmfc-<hostname>.<pid>"}
+ * <p>
+ * <h3>Interactions with remote Agent</h3>
+ * As noted below, some Console methods require contacting a remote Agent. For these methods, the caller
+ * has the option to either block for a (non-infinite) timeout waiting for a reply, or to allow the method
+ * to complete asynchonously. When the asynchronous approach is used, the caller must provide a unique
+ * handle that identifies the request. When the method eventually completes, a WorkItem will be placed on
+ * the work queue. The WorkItem will contain the handle that was provided to the corresponding method call.
+ * <p>
+ * The following diagram illustrates the interactions between the Console, Agent and client side Agent proxy.
+ * <p>
+ * <img alt="" src="doc-files/Console.png">
+ * <p>
+ * All blocking calls are considered thread safe - it is possible to have a multi-threaded implementation
+ * have multiple blocking calls in flight simultaneously.
+ * <p>
+ * <h3>Subscriptions</h3>
+ * This implementation of the QMF2 API has full support for QMF2 Subscriptions where they are supported by an Agent.
+ * <p>
+ * N.B. That the 0.12 C++ broker does not <i>actually</i> support subscriptions, however it does periodically "push"
+ * QmfConsoleData Object updates as _data indications. The Console class uses these to provide client side
+ * emulation of broker subscriptions.
+ * The System Property "disable_subscription_emulation" may be set to true to disable this behaviour.
+ * <p>
+ * The diagram below shows the relationship between the Console, the SubscriptionManager (which is used to manage the
+ * lifecycle of Subscriptions on the client side) and the local Agent representation.
+ * <p>
+ * The SubscriptionManager is also used to maintain the Subscription query to enable ManagementAgent _data indications
+ * to be tested in order to emulate Subscriptions to the broker ManagementAgent on the client side.
+ * <p>
+ * <img alt="" src="doc-files/Subscriptions.png">
+ * <p>
+ * <h3>Receiving Asynchronous Notifications</h3>
+ * This implementation of the QMF2 Console actually supports two independent APIs to enable clients to receive
+ * Asynchronous notifications.
+ * <p>
+ * A QmfEventListener object is used to receive asynchronously delivered WorkItems.
+ * <p>
+ * This provides an alternative (simpler) API to the official QMF2 WorkQueue API that some (including the Author)
+ * may prefer over the official API.
+ * <p>
+ * The following diagram illustrates the QmfEventListener Event model.
+ * <p>
+ * Notes
+ * <ol>
+ * <li>This is provided as an alternative to the official QMF2 WorkQueue and Notifier Event model.</li>
+ * <li>Agent and Console methods are sufficiently thread safe that it is possible to call them from a callback fired
+ * from the onEvent() method that may have been called from the JMS MessageListener. Internally the synchronous
+ * and asynchronous calls are processed on different JMS Sessions to facilitate this</li>
+ * </ol>
+ * <p>
+ * <img alt="" src="doc-files/QmfEventListenerModel.png">
+ * <p>
+ * The QMF2 API has a work-queue Callback approach. All asynchronous events are represented by a WorkItem object.
+ * When a QMF event occurs it is translated into a WorkItem object and placed in a FIFO queue. It is left to the
+ * application to drain this queue as needed.
+ * <p>
+ * This new API does require the application to provide a single callback. The callback is used to notify the
+ * application that WorkItem object(s) are pending on the work queue. This callback is invoked by QMF when one or
+ * more new WorkItem objects are added to the queue. To avoid any potential threading issues, the application is
+ * not allowed to call any QMF API from within the context of the callback. The purpose of the callback is to
+ * notify the application to schedule itself to drain the work queue at the next available opportunity.
+ * <p>
+ * For example, a console application may be designed using a select() loop. The application waits in the select()
+ * for any of a number of different descriptors to become ready. In this case, the callback could be written to
+ * simply make one of the descriptors ready, and then return. This would cause the application to exit the wait state,
+ * and start processing pending events.
+ * <p>
+ * The callback is represented by the Notifier virtual base class. This base class contains a single method. An
+ * application derives a custom notification handler from this class, and makes it available to the Console or
+ * Agent object.
+ * <p>
+ * The following diagram illustrates the Notifier and WorkQueue QMF2 API Event model.
+ * <p>
+ * Notes
+ * <ol>
+ * <li>There is an alternative (simpler but not officially QMF2) API based on implementing the QmfEventListener as
+ * described previously.</li>
+ * <li>BlockingNotifier is not part of QMF2 either but is how most people would probably write a Notifier.</li>
+ * <li>It's generally not necessary to use a Notifier as the Console provides a blocking getNextWorkitem() method.</li>
+ * </ol>
+ * <p>
+ * <img alt="" src="doc-files/WorkQueueEventModel.png">
+
+
+ * <h3>Potential Issues with Qpid versions earlier than 0.12</h3>
+ * Note 1: This uses QMF2 so requires that the "--mgmt-qmf2 yes" option is applied to the broker (this is the default
+ * from Qpid 0.10).
+ * <p>
+ * Note 2: In order to use QMF2 the app-id field needs to be set. There appears to be no way to set the AMQP 0-10
+ * specific app-id field on a message which the brokers QMFv2 implementation currently requires.
+ * <p>
+ * Gordon Sim has put together a patch for org.apache.qpid.client.message.AMQMessageDelegate_0_10
+ * Found in client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate_0_10.java
+ * <pre>
+ * public void setStringProperty(String propertyName, String value) throws JMSException
+ * {
+ * checkPropertyName(propertyName);
+ * checkWritableProperties();
+ * setApplicationHeader(propertyName, value);
+ *
+ * if ("x-amqp-0-10.app-id".equals(propertyName))
+ * {
+ * _messageProps.setAppId(value.getBytes());
+ * }
+ * }
+ * </pre>
+ * This gets things working.
+ * <p>
+ * A jira <a href=https://issues.apache.org/jira/browse/QPID-3302>QPID-3302</a> has been raised.
+ * This is fixed in Qpid 0.12.
+ *
+ * @author Fraser Adams
+ */
+public final class Console implements MessageListener, AgentProxy
+{
+ private static final Logger _log = LoggerFactory.getLogger(Console.class);
+
+ // Attributes
+ // ********************************************************************************************************
+
+ /**
+ * The eventListener may be a real application QmfEventListener, a NullQmfEventListener or an application
+ * Notifier wrapped in a QmfEventListener. In all cases the Console may call _eventListener.onEvent() at
+ * various places to pass a WorkItem to an asynchronous receiver.
+ */
+ private QmfEventListener _eventListener;
+
+ /**
+ * Explicitly store Agents in a ConcurrentHashMap, as we know the MessageListener thread may modify its contents.
+ */
+ private Map<String, Agent> _agents = new ConcurrentHashMap<String, Agent>();
+
+ /**
+ * This Map is used to look up a Subscription by consoleHandle.
+ */
+ private Map<String, SubscriptionManager> _subscriptionByHandle = new ConcurrentHashMap<String, SubscriptionManager>();
+
+ /**
+ * This Map is used to look up a Subscription by subscriptionId
+ */
+ private Map<String, SubscriptionManager> _subscriptionById = new ConcurrentHashMap<String, SubscriptionManager>();
+
+ /**
+ * Used to implement a thread safe queue of WorkItem objects used to implement the Notifier API
+ */
+ private WorkQueue _workQueue = new WorkQueue();
+
+ /**
+ * The name of the broker Agent is explicitly recorded when the broker Agent is discovered, we use this so
+ * we can support the synonyms "broker" and "qpidd" for the broker Agent, as its full name isn't especially
+ * easy to use givent that it contains a UUID "instance" component.
+ */
+ private String _brokerAgentName = null;
+
+ /**
+ * A flag to indicate that an Agent has been registered, used as a condition variable.
+ */
+ private boolean _agentAvailable = false;
+
+ /**
+ * The domain string is used to construct the name of the AMQP exchange to which the component's
+ * name string will be bound. If not supplied, the value of the domain defaults to "default". Both
+ * Agents and Components must belong to the same domain in order to communicate.
+ */
+ private String _domain;
+
+ /**
+ * A QMF address is composed of two parts - an optional domain string, and a mandatory
+ * name string "qmf.<domain-string>.direct/<name-string>"
+ */
+ private String _address;
+
+ /**
+ * This flag enables AGENT_* work items to be sent to the Event Listener. Note this is enabled by default
+ */
+ private boolean _discoverAgents = true;
+
+ /**
+ * This Query is set by the enableAgentDiscovery() method that takes a QmfQuery as a parameter. It is used
+ * in conjunction with _discoverAgents to decide whether to send notifications of Agent activity
+ */
+ private QmfQuery _agentQuery = null;
+
+ /**
+ * This flag disbles asynchronous behaviour such as QMF Events, Agent discovery etc. useful in simple
+ * Use Cases such as getObjects() on the broker. Note that asynchronous behaviour enabled by default.
+ */
+ private boolean _disableEvents = false;
+
+ /**
+ * If the "disable_subscription_emulation" System Property is set then we disable Console side emulation
+ * of broker subscriptions
+ */
+ private boolean _subscriptionEmulationEnabled = !Boolean.getBoolean("disable_subscription_emulation");
+
+ /**
+ * Various timeouts used internally.
+ * replyTimeout is the default maximum time we wait for synchronous responses
+ * agentTimeout is the maximum time we wait for any Agent activity before expiring the Agent
+ * subscriptionDuration is the default maximum time we keep a subscription active
+ */
+ private int _replyTimeout = 10;
+ private int _agentTimeout = 60; // 1 minute
+ private int _subscriptionDuration = 300; // 5 minutes
+
+ /**
+ * This timer is used tidy up Subscription references where a Subscription has expired. Ideally a client should
+ * call cancelSubscription(), but we can't rely on it.
+ */
+ private Timer _timer;
+
+ /**
+ * Various JMS related fields
+ */
+ private Connection _connection = null;
+ private Session _asyncSession;
+ private Session _syncSession;
+ private MessageConsumer _eventConsumer;
+ private MessageConsumer _responder;
+ private MessageConsumer _asyncResponder;
+ private MessageProducer _requester;
+ private MessageProducer _broadcaster;
+ private Destination _replyAddress;
+ private Destination _asyncReplyAddress;
+
+ // private implementation methods
+ // ********************************************************************************************************
+
+ /**
+ * Send an asynchronous _agent_locate_request to the topic broadcast address with the subject
+ * "console.request.agent_locate". This should cause all active Agents to respond on the async
+ * direct address, which gets handled by onMessage()
+ */
+ private void broadcastAgentLocate()
+ {
+ try
+ {
+ Message request = AMQPMessage.createListMessage(_syncSession);
+ request.setJMSReplyTo(_asyncReplyAddress);
+ request.setStringProperty("x-amqp-0-10.app-id", "qmf2");
+ request.setStringProperty("method", "request");
+ request.setStringProperty("qmf.opcode", "_agent_locate_request");
+ request.setStringProperty("qpid.subject", "console.request.agent_locate");
+ AMQPMessage.setList(request, Collections.emptyList());
+ _broadcaster.send(request);
+ }
+ catch (JMSException jmse)
+ {
+ _log.info("JMSException {} caught in broadcastAgentLocate()", jmse.getMessage());
+ }
+ }
+
+ /**
+ * Check whether any of the registered Agents has expired by comparing their timestamp against the
+ * current time. We explicitly use an iterator rather than a foreach loop because if the Agent has
+ * expired we want to remove it and the only safe way to do that whilst still iterating is to use
+ * iterator.remove() and the foreach loop hides the underlying iterator from us.
+ */
+ private void handleAgentExpiry()
+ {
+ long currentTime = System.currentTimeMillis()*1000000l;
+
+ // Use the iterator approach rather than foreach as we may want to call iterator.remove() to zap an entry
+ Iterator<Agent> i = _agents.values().iterator();
+ while (i.hasNext())
+ {
+ Agent agent = i.next();
+ // Get the time difference in seconds between now and the last Agent update.
+ long diff = (currentTime - agent.getTimestamp())/1000000000l;
+ if (diff > _agentTimeout)
+ {
+ if (agent.getVendor().equals("apache.org") && agent.getProduct().equals("qpidd"))
+ {
+ _brokerAgentName = null;
+ }
+ agent.deactivate();
+ i.remove();
+ _log.info("Agent {} has expired", agent.getName());
+ if (_discoverAgents && (_agentQuery == null || _agentQuery.evaluate(agent)))
+ {
+ _eventListener.onEvent(new AgentDeletedWorkItem(agent));
+ }
+ }
+ }
+ }
+
+ /**
+ * MessageListener for QMF2 Agent Events, Hearbeats and Asynchronous data indications
+ *
+ * @param message the JMS Message passed to the listener
+ */
+ public void onMessage(Message message)
+ {
+ try
+ {
+ String agentName = QmfData.getString(message.getObjectProperty("qmf.agent"));
+ String content = QmfData.getString(message.getObjectProperty("qmf.content"));
+ String opcode = QmfData.getString(message.getObjectProperty("qmf.opcode"));
+ //String routingKey = ((javax.jms.Topic)message.getJMSDestination()).getTopicName();
+ //String contentType = ((org.apache.qpid.client.message.AbstractJMSMessage)message).getContentType();
+
+//System.out.println();
+//System.out.println("agentName = " + agentName);
+//System.out.println("content = " + content);
+//System.out.println("opcode = " + opcode);
+//System.out.println("routingKey = " + routingKey);
+//System.out.println("contentType = " + contentType);
+
+ if (opcode.equals("_agent_heartbeat_indication") || opcode.equals("_agent_locate_response"))
+ { // This block handles Agent lifecycle information (discover, register, delete)
+ if (_agents.containsKey(agentName))
+ { // This block handles Agents that have previously been registered
+ Agent agent = _agents.get(agentName);
+ long originalEpoch = agent.getEpoch();
+
+ // If we already know about an Agent we simply update the Agent's state using initialise()
+ agent.initialise(AMQPMessage.getMap(message));
+
+ // If the Epoch has changed it means the Agent has been restarted so we send a notification
+ if (agent.getEpoch() != originalEpoch)
+ {
+ agent.clearSchemaCache(); // Clear cache to force a lookup
+ List<SchemaClassId> classes = getClasses(agent);
+ getSchema(classes, agent); // Discover the schema for this Agent and cache it
+ _log.info("Agent {} has been restarted", agentName);
+ if (_discoverAgents && (_agentQuery == null || _agentQuery.evaluate(agent)))
+ {
+ _eventListener.onEvent(new AgentRestartedWorkItem(agent));
+ }
+ }
+ else
+ { // Otherwise just send a heartbeat notification
+ _log.info("Agent {} heartbeat", agent.getName());
+ if (_discoverAgents && (_agentQuery == null || _agentQuery.evaluate(agent)))
+ {
+ _eventListener.onEvent(new AgentHeartbeatWorkItem(agent));
+ }
+ }
+ }
+ else
+ { // This block handles Agents that haven't already been registered
+ Agent agent = new Agent(AMQPMessage.getMap(message), this);
+ List<SchemaClassId> classes = getClasses(agent);
+ getSchema(classes, agent); // Discover the schema for this Agent and cache it
+ _agents.put(agentName, agent);
+ _log.info("Adding Agent {}", agentName);
+
+ // If the Agent is the Broker Agent we record it as _brokerAgentName to make retrieving
+ // the Agent more "user friendly" than using the full Agent name.
+ if (agent.getVendor().equals("apache.org") && agent.getProduct().equals("qpidd"))
+ {
+ _log.info("Recording {} as _brokerAgentName", agentName);
+ _brokerAgentName = agentName;
+ }
+
+ // Notify any waiting threads that an Agent has been registered. Note that we only notify if
+ // we've already found the broker Agent to avoid a race condition in addConnection(), as another
+ // Agent could in theory trigger this block first. In addConnection() we *explicitly* want to
+ // wait for the broker Agent to become available.
+ if (_brokerAgentName != null)
+ {
+ synchronized(this)
+ {
+ _agentAvailable = true;
+ notifyAll();
+ }
+ }
+
+ if (_discoverAgents && (_agentQuery == null || _agentQuery.evaluate(agent)))
+ {
+ _eventListener.onEvent(new AgentAddedWorkItem(agent));
+ }
+ }
+
+ // The broker Agent sends periodic heartbeats and that Agent should *always* be available given
+ // a running broker, so we should get here every "--mgmt-pub-interval" seconds or so, so it's
+ // a good place to periodically check for the expiry of any other Agents.
+ handleAgentExpiry();
+ return;
+ }
+
+ if (!_agents.containsKey(agentName))
+ {
+ _log.info("Ignoring Event from unregistered Agent {}", agentName);
+ return;
+ }
+
+ Agent agent = _agents.get(agentName);
+ if (!agent.eventsEnabled())
+ {
+ _log.info("{} has disabled Event reception, ignoring Event", agentName);
+ return;
+ }
+
+ // If we get to here the Agent from whence the Event came should be registered and should
+ // have Event reception enabled, so we should be able to send events to the EventListener
+
+ Handle handle = new Handle(message.getJMSCorrelationID());
+ if (opcode.equals("_method_response") || opcode.equals("_exception"))
+ {
+ if (AMQPMessage.isAMQPMap(message))
+ {
+ _eventListener.onEvent(
+ new MethodResponseWorkItem(handle, new MethodResult(AMQPMessage.getMap(message)))
+ );
+ }
+ else
+ {
+ _log.info("onMessage() Received Method Response message in incorrect format");
+ }
+ }
+
+ // Query Response. The only asynchronous query response we expect to see is the result of an async
+ // refresh() call on QmfConsoleData so the number of results in the returned list *should* be one.
+ if (opcode.equals("_query_response") && content.equals("_data"))
+ {
+ if (AMQPMessage.isAMQPList(message))
+ {
+ List<Map> list = AMQPMessage.getList(message);
+ for (Map m : list)
+ {
+ _eventListener.onEvent(new ObjectUpdateWorkItem(handle, new QmfConsoleData(m, agent)));
+ }
+ }
+ else
+ {
+ _log.info("onMessage() Received Query Response message in incorrect format");
+ }
+ }
+
+ // This block handles responses to createSubscription and refreshSubscription
+ if (opcode.equals("_subscribe_response"))
+ {
+ if (AMQPMessage.isAMQPMap(message))
+ {
+ String correlationId = message.getJMSCorrelationID();
+ SubscribeParams params = new SubscribeParams(correlationId, AMQPMessage.getMap(message));
+ String subscriptionId = params.getSubscriptionId();
+
+ if (subscriptionId != null && correlationId != null)
+ {
+ SubscriptionManager subscription = _subscriptionById.get(subscriptionId);
+ if (subscription == null)
+ { // This is a createSubscription response so the correlationId should be the consoleHandle
+ subscription = _subscriptionByHandle.get(correlationId);
+ if (subscription != null)
+ {
+ _subscriptionById.put(subscriptionId, subscription);
+ subscription.setSubscriptionId(subscriptionId);
+ subscription.setDuration(params.getLifetime());
+ String replyHandle = subscription.getReplyHandle();
+ if (replyHandle == null)
+ {
+ subscription.signal();
+ }
+ else
+ {
+ _eventListener.onEvent(new SubscribeResponseWorkItem(new Handle(replyHandle), params));
+ }
+ }
+ }
+ else
+ { // This is a refreshSubscription response
+ params.setConsoleHandle(subscription.getConsoleHandle());
+ subscription.setDuration(params.getLifetime());
+ subscription.refresh();
+ _eventListener.onEvent(new SubscribeResponseWorkItem(handle, params));
+ }
+ }
+ }
+ else
+ {
+ _log.info("onMessage() Received Subscribe Response message in incorrect format");
+ }
+ }
+
+ // Subscription Indication - in other words the asynchronous results of a Subscription
+ if (opcode.equals("_data_indication") && content.equals("_data"))
+ {
+ if (AMQPMessage.isAMQPList(message))
+ {
+ String consoleHandle = handle.getCorrelationId();
+ if (consoleHandle != null && _subscriptionByHandle.containsKey(consoleHandle))
+ { // If we have a valid consoleHandle the data has come from a "real" Subscription.
+ List<Map> list = AMQPMessage.getList(message);
+ List<QmfConsoleData> resultList = new ArrayList<QmfConsoleData>(list.size());
+ for (Map m : list)
+ {
+ resultList.add(new QmfConsoleData(m, agent));
+ }
+ _eventListener.onEvent(
+ new SubscriptionIndicationWorkItem(new SubscribeIndication(consoleHandle, resultList))
+ );
+ }
+ else if (_subscriptionEmulationEnabled && agentName.equals(_brokerAgentName))
+ { // If the data has come from is the broker Agent we emulate a Subscription on the Console
+ for (SubscriptionManager subscription : _subscriptionByHandle.values())
+ {
+ QmfQuery query = subscription.getQuery();
+ if (subscription.getAgent().getName().equals(_brokerAgentName) &&
+ query.getTarget() == QmfQueryTarget.OBJECT)
+ { // Only evaluate broker Agent subscriptions with QueryTarget == OBJECT on the Console.
+ long objectEpoch = 0;
+ consoleHandle = subscription.getConsoleHandle();
+ List<Map> list = AMQPMessage.getList(message);
+ List<QmfConsoleData> resultList = new ArrayList<QmfConsoleData>(list.size());
+ for (Map m : list)
+ { // Evaluate the QmfConsoleData object against the query
+ QmfConsoleData object = new QmfConsoleData(m, agent);
+ if (query.evaluate(object))
+ {
+ long epoch = object.getObjectId().getAgentEpoch();
+ objectEpoch = (epoch > objectEpoch && !object.isDeleted()) ? epoch : objectEpoch;
+ resultList.add(object);
+ }
+ }
+
+ if (resultList.size() > 0)
+ { // If there are any results available after evaluating the query we deliver them
+ // via a SubscribeIndicationWorkItem.
+
+ // Before we send the WorkItem we take a peek at the Agent Epoch value that forms
+ // part of the ObjectID and compare it against the current Epoch value. If they
+ // are different we send an AgentRestartedWorkItem. We *normally* check for Epoch
+ // changes when we receive heartbeat indications, but unfortunately the broker
+ // ManagementAgent pushes data *before* it pushes heartbeats. Its more useful
+ // however for clients to know that an Agent has been restarted *before* they get
+ // data from the restarted Agent (in case they need to reset any state).
+ if (objectEpoch > agent.getEpoch())
+ {
+ agent.setEpoch(objectEpoch);
+ agent.clearSchemaCache(); // Clear cache to force a lookup
+ List<SchemaClassId> classes = getClasses(agent);
+ getSchema(classes, agent); // Discover the schema for this Agent and cache it
+ _log.info("Agent {} has been restarted", agentName);
+ if (_discoverAgents && (_agentQuery == null || _agentQuery.evaluate(agent)))
+ {
+ _eventListener.onEvent(new AgentRestartedWorkItem(agent));
+ }
+ }
+
+ _eventListener.onEvent(
+ new SubscriptionIndicationWorkItem(
+ new SubscribeIndication(consoleHandle, resultList))
+ );
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ _log.info("onMessage() Received Subscribe Indication message in incorrect format");
+ }
+ }
+
+ // The results of an Event delivered from an Agent
+ if (opcode.equals("_data_indication") && content.equals("_event"))
+ { // There are differences in the type of message sent by Qpid 0.8 and 0.10 onwards.
+ if (AMQPMessage.isAMQPMap(message))
+ { // 0.8 broker passes Events as amqp/map encoded as MapMessages (we convert into java.util.Map)
+ _eventListener.onEvent(new EventReceivedWorkItem(agent, new QmfEvent(AMQPMessage.getMap(message))));
+ }
+ else if (AMQPMessage.isAMQPList(message))
+ { // 0.10 and above broker passes Events as amqp/list encoded as BytesMessage (needs decoding)
+ // 0.20 encodes amqp/list in a MapMessage!!?? AMQPMessage hopefully abstracts this detail.
+ List<Map> list = AMQPMessage.getList(message);
+ for (Map m : list)
+ {
+ _eventListener.onEvent(new EventReceivedWorkItem(agent, new QmfEvent(m)));
+ }
+ }
+ else
+ {
+ _log.info("onMessage() Received Event message in incorrect format");
+ }
+ }
+ }
+ catch (JMSException jmse)
+ {
+ _log.info("JMSException {} caught in onMessage()", jmse.getMessage());
+ }
+ } // end of onMessage()
+
+ /**
+ * Retrieve the schema for a List of classes.
+ * This method explicitly retrieves the schema from the remote Agent and is generally used for schema
+ * discovery when an Agent is added or updated.
+ *
+ * @param classes the list of SchemaClassId of the classes who's schema we want to retrieve
+ * @param agent the Agent we want to retrieve the schema from
+ */
+ private List<SchemaClass> getSchema(final List<SchemaClassId> classes, final Agent agent)
+ {
+ List<SchemaClass> results = new ArrayList<SchemaClass>();
+ for (SchemaClassId classId : classes)
+ {
+ agent.setSchema(classId, Collections.<SchemaClass>emptyList()); // Clear Agent's schema value for classId
+ results.addAll(getSchema(classId, agent));
+ }
+ return results;
+ }
+
+ /**
+ * Perform a query for QmfConsoleData objects. Returns a list (possibly empty) of matching objects.
+ * If replyHandle is null this method will block until the agent replies, or the timeout expires.
+ * Once the timeout expires, all data retrieved to date is returned. If replyHandle is non-null an
+ * asynchronous request is performed
+ *
+ * @param agent the Agent being queried
+ * @param query the ObjectId or SchemaClassId being queried for.
+ * @param replyHandle the correlation handle used to tie asynchronous method requests with responses
+ * @param timeout the time to wait for a reply from the Agent, a value of -1 means use the default timeout
+ * @return a List of QMF Objects describing that class
+ */
+ private List<QmfConsoleData> getObjects(final Agent agent, final QmfData query,
+ final String replyHandle, int timeout)
+ {
+ String agentName = agent.getName();
+ timeout = (timeout < 1) ? _replyTimeout : timeout;
+ List<QmfConsoleData> results = Collections.emptyList();
+ try
+ {
+ Destination destination = (replyHandle == null) ? _replyAddress : _asyncReplyAddress;
+ MapMessage request = _syncSession.createMapMessage();
+ request.setJMSReplyTo(destination);
+ request.setJMSCorrelationID(replyHandle);
+ request.setStringProperty("x-amqp-0-10.app-id", "qmf2");
+ request.setStringProperty("method", "request");
+ request.setStringProperty("qmf.opcode", "_query_request");
+ request.setStringProperty("qpid.subject", agentName);
+
+ // Create a QMF Query for an "OBJECT" target using either a schema ID or object ID
+ String queryType = (query instanceof SchemaClassId) ? "_schema_id" : "_object_id";
+ request.setObject("_what", "OBJECT");
+ request.setObject(queryType, query.mapEncode());
+
+ // Wrap request & response in synchronized block in case any other threads invoke a request
+ // it would be somewhat unfortunate if their response got interleaved with ours!!
+ synchronized(this)
+ {
+ _requester.send(request);
+ if (replyHandle == null)
+ {
+ boolean lastResult = true;
+ ArrayList<QmfConsoleData> partials = new ArrayList<QmfConsoleData>();
+ do
+ { // Wrap in a do/while loop to cater for the case where the Agent may send partial results.
+ Message response = _responder.receive(timeout*1000);
+ if (response == null)
+ {
+ _log.info("No response received in getObjects()");
+ return partials;
+ }
+
+ lastResult = !response.propertyExists("partial");
+
+ if (AMQPMessage.isAMQPList(response))
+ {
+ List<Map> mapResults = AMQPMessage.getList(response);
+ partials.ensureCapacity(partials.size() + mapResults.size());
+ for (Map content : mapResults)
+ {
+ partials.add(new QmfConsoleData(content, agent));
+ }
+ }
+ else if (AMQPMessage.isAMQPMap(response))
+ {
+ // Error responses are returned as MapMessages, though they are being ignored here.
+ //QmfData exception = new QmfData(AMQPMessage.getMap(response));
+ //System.out.println(agentName + " " + exception.getStringValue("error_text"));
+ }
+ else
+ {
+ _log.info("getObjects() Received response message in incorrect format");
+ }
+ } while (!lastResult);
+ results = partials;
+ }
+ }
+ }
+ catch (JMSException jmse)
+ {
+ _log.info("JMSException {} caught in getObjects()", jmse.getMessage());
+ }
+ return results;
+ }
+
+ // methods implementing AgentProxy interface
+ // ********************************************************************************************************
+
+ /**
+ * Releases the specified Agent instance. Once called, the console application should not reference this
+ * instance again.
+ * <p>
+ * Intended to by called by the AgentProxy. Shouldn't generally be called directly by Console applications.
+ *
+ * @param agent the Agent that we wish to destroy.
+ */
+ public void destroy(final Agent agent)
+ {
+ handleAgentExpiry();
+ }
+
+ /**
+ * Request that the Agent update the value of an object's contents.
+ * <p>
+ * Intended to by called by the AgentProxy. Shouldn't generally be called directly by Console applications.
+ *
+ * @param agent the Agent to get the refresh from.
+ * @param objectId the ObjectId being queried for
+ * @param replyHandle the correlation handle used to tie asynchronous method requests with responses
+ * @param timeout the time to wait for a reply from the Agent, a value of -1 means use the default timeout
+ * @return the refreshed object
+ */
+ public QmfConsoleData refresh(final Agent agent, final ObjectId objectId, final String replyHandle, final int timeout)
+ {
+ List<QmfConsoleData> objects = getObjects(agent, objectId, replyHandle, timeout);
+ return (objects.size() == 0) ? null : objects.get(0);
+ }
+
+ /**
+ * Invoke the named method on the named Agent.
+ * <p>
+ * Intended to by called by the AgentProxy. Shouldn't generally be called directly by Console applications.
+ *
+ * @param agent the Agent to invoke the method on.
+ * @param content an unordered set of key/value pairs comprising the method arguments.
+ * @param replyHandle the correlation handle used to tie asynchronous method requests with responses
+ * @param timeout the time to wait for a reply from the Agent, a value of -1 means use the default timeout
+ * @return the method response Arguments in Map form
+ */
+ public MethodResult invokeMethod(final Agent agent, final Map<String, Object> content,
+ final String replyHandle, int timeout) throws QmfException
+ {
+ if (!agent.isActive())
+ {
+ throw new QmfException("Called invokeMethod() with inactive agent");
+ }
+ String agentName = agent.getName();
+ timeout = (timeout < 1) ? _replyTimeout : timeout;
+ try
+ {
+ Destination destination = (replyHandle == null) ? _replyAddress : _asyncReplyAddress;
+ MapMessage request = _syncSession.createMapMessage();
+ request.setJMSReplyTo(destination);
+ request.setJMSCorrelationID(replyHandle);
+ request.setStringProperty("x-amqp-0-10.app-id", "qmf2");
+ request.setStringProperty("method", "request");
+ request.setStringProperty("qmf.opcode", "_method_request");
+ request.setStringProperty("qpid.subject", agentName);
+
+ for (Map.Entry<String, Object> entry : content.entrySet())
+ {
+ request.setObject(entry.getKey(), entry.getValue());
+ }
+
+ // Wrap request & response in synchronized block in case any other threads invoke a request
+ // it would be somewhat unfortunate if their response got interleaved with ours!!
+ synchronized(this)
+ {
+ _requester.send(request);
+ if (replyHandle == null)
+ { // If this is a synchronous request get the response
+ Message response = _responder.receive(timeout*1000);
+ if (response == null)
+ {
+ _log.info("No response received in invokeMethod()");
+ throw new QmfException("No response received for Console.invokeMethod()");
+ }
+ MethodResult result = new MethodResult(AMQPMessage.getMap(response));
+ QmfException exception = result.getQmfException();
+ if (exception != null)
+ {
+ throw exception;
+ }
+ return result;
+ }
+ }
+ // If this is an asynchronous request return without waiting for a response
+ return null;
+ }
+ catch (JMSException jmse)
+ {
+ _log.info("JMSException {} caught in invokeMethod()", jmse.getMessage());
+ throw new QmfException(jmse.getMessage());
+ }
+ }
+
+ /**
+ * Remove a Subscription.
+ *
+ * @param subscription the SubscriptionManager that we wish to remove.
+ */
+ public void removeSubscription(final SubscriptionManager subscription)
+ {
+ String consoleHandle = subscription.getConsoleHandle();
+ String subscriptionId = subscription.getSubscriptionId();
+ if (consoleHandle != null)
+ {
+ _subscriptionByHandle.remove(consoleHandle);
+ }
+ if (subscriptionId != null)
+ {
+ _subscriptionById.remove(subscriptionId);
+ }
+ }
+
+ // QMF API Methods
+ // ********************************************************************************************************
+
+ /**
+ * Constructor that provides defaults for name and domain and has no Notifier/Listener
+ * <p>
+ * Warning!! If more than one Console is needed in a process be sure to use the Constructor that allows one to
+ * supply a name as &lt;hostname&gt;.&lt;pid&gt; isn't unique enough and will result in "odd results" (trust me!!).
+ */
+ public Console() throws QmfException
+ {
+ this(null, null, null, null);
+ }
+
+ /**
+ * Constructor that provides defaults for name and domain and takes a Notifier/Listener.
+ * <p>
+ * Warning!! If more than one Console is needed in a process be sure to use the Constructor that allows one to
+ * supply a name as &lt;hostname&gt;.&lt;pid&gt; isn't unique enough and will result in "odd results" (trust me!!).
+ *
+ * @param notifier this may be either a QMF2 API Notifier object OR a QMFEventListener.
+ * <p>
+ * The latter is an alternative API that avoids the need for an explicit Notifier thread to be created the
+ * EventListener is called from the JMS MessageListener thread. This API may be simpler and more convenient
+ * than the QMF2 Notifier API for many applications.
+ */
+ public Console(final QmfCallback notifier) throws QmfException
+ {
+ this(null, null, notifier, null);
+ }
+
+ /**
+ * Main constructor, creates a Console, but does NOT start it, that requires us to do addConnection()
+ *
+ * @param name if no name is supplied we synthesise one in the format: <pre>"qmfc-&lt;hostname&gt;.&lt;pid&gt;"</pre>
+ * if we can, otherwise we create a name using a randomUUID.
+ * <p>
+ * Warning!! If more than one Console is needed in a process be sure to supply a name as
+ * &lt;hostname&gt;.&lt;pid&gt; isn't unique enough and will result in "odd results" (trust me!!).
+ * @param domain the QMF "domain". A QMF address is composed of two parts - an optional domain string, and a
+ * mandatory name string <pre>"qmf.&lt;domain-string&gt;.direct/&lt;name-string&gt;"</pre>
+ * The domain string is used to construct the name of the AMQP exchange to which the component's
+ * name string will be bound. If not supplied, the value of the domain defaults to "default". Both
+ * Agents and Components must belong to the same domain in order to communicate.
+ * @param notifier this may be either a QMF2 API Notifier object OR a QMFEventListener.
+ * <p>
+ * The latter is an alternative API that avoids the need for an explicit Notifier thread to be created the
+ * EventListener is called from the JMS MessageListener thread. This API may be simpler and more convenient
+ * than the QMF2 Notifier API for many applications.
+ * @param options a String representation of a Map containing the options in the form
+ * <pre>"{replyTimeout:&lt;value&gt;, agentTimeout:&lt;value&gt;, subscriptionDuration:&lt;value&gt;}"</pre>
+ * they are all optional and may appear in any order.
+ * <pre>
+ * <b>replyTimeout</b>=&lt;default for all blocking calls&gt;
+ * <b>agentTimeout</b>=&lt;default timeout for agent heartbeat&gt;,
+ * <b>subscriptionDuration</b>=&lt;default lifetime of a subscription&gt;
+ * </pre>
+ */
+ public Console(String name, final String domain,
+ final QmfCallback notifier, final String options) throws QmfException
+ {
+ if (name == null)
+ {
+ // ManagementFactory.getRuntimeMXBean().getName()) returns the name representing the running virtual machine.
+ // The returned name string can be any arbitrary string and a Java virtual machine implementation can choose
+ // to embed platform-specific useful information in the returned name string.
+ // As it happens on Linux the format for this is PID@hostname
+ String vmName = ManagementFactory.getRuntimeMXBean().getName();
+ String[] split = vmName.split("@");
+ if (split.length == 2)
+ {
+ name = "qmfc-" + split[1] + "." + split[0];
+ }
+ else
+ {
+ name = "qmfc-" + UUID.randomUUID();
+ }
+ }
+
+ _domain = (domain == null) ? "default" : domain;
+ _address = "qmf." + _domain + ".direct" + "/" + name;
+
+ if (notifier == null)
+ {
+ _eventListener = new NullQmfEventListener();
+ }
+ else if (notifier instanceof Notifier)
+ {
+ _eventListener = new NotifierWrapper((Notifier)notifier, _workQueue);
+ }
+ else if (notifier instanceof QmfEventListener)
+ {
+ _eventListener = (QmfEventListener)notifier;
+ }
+ else
+ {
+ throw new QmfException("QmfCallback listener must be either a Notifier or QmfEventListener");
+ }
+
+ if (options != null)
+ { // We wrap the Map in a QmfData object to avoid potential class cast issues with the parsed options
+ QmfData optMap = new QmfData(new AddressParser(options).map());
+ if (optMap.hasValue("replyTimeout"))
+ {
+ _replyTimeout = (int)optMap.getLongValue("replyTimeout");
+ }
+
+ if (optMap.hasValue("agentTimeout"))
+ {
+ _agentTimeout = (int)optMap.getLongValue("agentTimeout");
+ }
+
+ if (optMap.hasValue("subscriptionDuration"))
+ {
+ _subscriptionDuration = (int)optMap.getLongValue("subscriptionDuration");
+ }
+ }
+ }
+
+ /**
+ * Release the Console's resources.
+ */
+ public void destroy()
+ {
+ try
+ {
+ if (_connection != null)
+ {
+ removeConnection(_connection);
+ }
+ }
+ catch (QmfException qmfe)
+ {
+ // Ignore as we've already tested for _connection != null this should never occur
+ }
+ }
+
+ /**
+ * Connect the console to the AMQP cloud.
+ *
+ * @param conn a javax.jms.Connection
+ */
+ public void addConnection(final Connection conn) throws QmfException
+ {
+ addConnection(conn, "");
+ }
+
+ /**
+ * Connect the console to the AMQP cloud.
+ * <p>
+ * This is an extension to the standard QMF2 API allowing the user to specify address options in order to allow
+ * finer control over the Console's request and reply queues, e.g. explicit name, non-default size or durability.
+ *
+ * @param conn a javax.jms.Connection
+ * @param addressOptions options String giving finer grained control of the receiver queue.
+ * <p>
+ * As an example the following gives the Console's queues the name test-console, size = 500000000 and ring policy.
+ * <pre>
+ * " ; {link: {name:'test-console', x-declare: {arguments: {'qpid.policy_type': ring, 'qpid.max_size': 500000000}}}}"
+ * </pre>
+ * Note that the Console uses several queues so this will actually create a test-console queue plus a
+ * test-console-async queue and a test-console-event queue.
+ * <p>
+ * If a name parameter is not present temporary queues will be created, but the other options will still be applied.
+ */
+ public void addConnection(final Connection conn, final String addressOptions) throws QmfException
+ {
+ // Make the test and set of _connection synchronized just in case multiple threads attempt to add a _connection
+ // to the same Console instance at the same time.
+ synchronized(this)
+ {
+ if (_connection != null)
+ {
+ throw new QmfException("Multiple connections per Console is not supported");
+ }
+ _connection = conn;
+ }
+ try
+ {
+ String syncReplyAddressOptions = addressOptions;
+ String asyncReplyAddressOptions = addressOptions;
+ String eventAddressOptions = addressOptions;
+
+ if (!addressOptions.equals(""))
+ { // If there are address options supplied we need to check if a name parameter is present.
+ String[] split = addressOptions.split("name");
+ if (split.length == 2)
+ { // If options contains a name parameter we extract it and create variants for async and event queues.
+ split = split[1].split("[,}]"); // Look for the end of the key/value block
+ String nameValue = split[0].replaceAll("[ :'\"]", ""); // Remove initial colon, space any any quotes.
+ // Hopefully at this point nameValue is actually the value of the name parameter.
+ asyncReplyAddressOptions = asyncReplyAddressOptions.replace(nameValue, nameValue + "-async");
+ eventAddressOptions = eventAddressOptions.replace(nameValue, nameValue + "-event");
+ }
+ }
+
+ String topicBase = "qmf." + _domain + ".topic";
+ _syncSession = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ // Create a MessageProducer for the QMF topic address used to broadcast requests
+ Destination topicAddress = _syncSession.createQueue(topicBase);
+ _broadcaster = _syncSession.createProducer(topicAddress);
+
+ // If Asynchronous Behaviour is enabled we create the Queues used to receive async responses
+ // Data Indications, QMF Events, Heartbeats etc. from the broker (or other Agents).
+ if (!_disableEvents)
+ {
+ // TODO it should be possible to bind _eventConsumer and _asyncResponder to the same queue
+ // if I can figure out the correct AddressString to use, probably not a big deal though.
+
+ _asyncSession = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ // Set up MessageListener on the Event Address
+ Destination eventAddress = _asyncSession.createQueue(topicBase + "/agent.ind.#" + eventAddressOptions);
+ _eventConsumer = _asyncSession.createConsumer(eventAddress);
+ _eventConsumer.setMessageListener(this);
+
+ // Create the asynchronous JMSReplyTo _replyAddress and MessageConsumer
+ _asyncReplyAddress = _asyncSession.createQueue(_address + ".async" + asyncReplyAddressOptions);
+ _asyncResponder = _asyncSession.createConsumer(_asyncReplyAddress);
+ _asyncResponder.setMessageListener(this);
+ }
+
+ // I've extended the synchronized block to include creating the _requester and _responder. I don't believe
+ // that this is strictly necessary, but it stops findbugs moaning about inconsistent synchronization
+ // so makes sense if only to get that warm and fuzzy feeling of keeping findbugs happy :-)
+ synchronized(this)
+ {
+ // Create a MessageProducer for the QMF direct address, mainly used for request/response
+ Destination directAddress = _syncSession.createQueue("qmf." + _domain + ".direct");
+ _requester = _syncSession.createProducer(directAddress);
+
+ // Create the JMSReplyTo _replyAddress and MessageConsumer
+ _replyAddress = _syncSession.createQueue(_address + syncReplyAddressOptions);
+ _responder = _syncSession.createConsumer(_replyAddress);
+
+ _connection.start();
+
+ // If Asynchronous Behaviour is disabled we create an Agent instance to represent the broker
+ // ManagementAgent the only info that needs to be populated is the _name and we can use the
+ // "broker" synonym. We populate this fake Agent so getObjects() behaviour is consistent whether
+ // we've any received *real* Agent updates or not.
+ if (_disableEvents)
+ {
+ _brokerAgentName = "broker";
+ Map<String, String> map = new HashMap<String, String>();
+ map.put("_name", _brokerAgentName);
+ Agent agent = new Agent(map, this);
+ _agents.put(_brokerAgentName, agent);
+ _agentAvailable = true;
+ }
+ else
+ {
+ // If Asynchronous Behaviour is enabled Broadcast an Agent Locate message to get Agent info quickly.
+ broadcastAgentLocate();
+ }
+
+ // Wait until the Broker Agent has been located (this should generally be pretty quick)
+ while (!_agentAvailable)
+ {
+ long startTime = System.currentTimeMillis();
+ try
+ {
+ wait(_replyTimeout*1000);
+ }
+ catch (InterruptedException ie)
+ {
+ continue;
+ }
+ // Measure elapsed time to test against spurious wakeups and ensure we really have timed out
+ long elapsedTime = (System.currentTimeMillis() - startTime)/1000;
+ if (!_agentAvailable && elapsedTime >= _replyTimeout)
+ {
+ _log.info("Broker Agent not found");
+ throw new QmfException("Broker Agent not found");
+ }
+ }
+
+ // Timer used for tidying up Subscriptions.
+ _timer = new Timer(true);
+ }
+ }
+ catch (JMSException jmse)
+ {
+ // If we can't create the QMF Destinations there's not much else we can do
+ _log.info("JMSException {} caught in addConnection()", jmse.getMessage());
+ throw new QmfException("Failed to create sessions or destinations " + jmse.getMessage());
+ }
+ }
+
+ /**
+ * Remove the AMQP connection from the console. Un-does the addConnection() operation, and releases
+ * any Agents associated with the connection. All blocking methods are unblocked and given a failure
+ * status. All outstanding asynchronous operations are cancelled without producing WorkItems.
+ *
+ * @param conn a javax.jms.Connection
+ */
+ public void removeConnection(final Connection conn) throws QmfException
+ {
+ if (conn != _connection)
+ {
+ throw new QmfException("Attempt to delete unknown connection");
+ }
+
+ try
+ {
+ _timer.cancel();
+ _connection.close(); // Should we close() the connection here or just stop() it ???
+ }
+ catch (JMSException jmse)
+ {
+ throw new QmfException("Failed to remove connection, caught JMSException " + jmse.getMessage());
+ }
+ _connection = null;
+ }
+
+ /**
+ * Get the AMQP address this Console is listening to.
+ *
+ * @return the console's replyTo address. Note that there are actually two, there's a synchronous one
+ * which is the return address for synchronous request/response type invocations and there's an
+ * asynchronous address with a ".async" suffix which is the return address for asynchronous invocations
+ */
+ public String getAddress()
+ {
+ return _address;
+ }
+
+ /**
+ * Query for the presence of a specific agent in the QMF domain. Returns a class Agent if the agent is
+ * present. If the agent is not already known to the console, this call will send a query for the agent
+ * and block (with default timeout override) waiting for a response.
+ *
+ * @param agentName the name of the Agent to be returned.
+ * <p>
+ * "broker" or "qpidd" may be used as synonyms for the broker Agent name and the method will try to match
+ * agentName against the Agent name, the Agent product name and will also check if the Agent name contains
+ * the agentName String.
+ * <p>
+ * Checking against a partial match is useful because the full Agent name has a UUID representing
+ * the "instance" so it's hard to know the full name without having actually retrieved the Agent.
+ * @return the found Agent instance or null if an Agent of the given name could not be found
+ */
+ public Agent findAgent(final String agentName)
+ {
+ return findAgent(agentName, _replyTimeout);
+ }
+
+ /**
+ * Query for the presence of a specific agent in the QMF domain. Returns a class Agent if the agent is
+ * present. If the agent is not already known to the console, this call will send a query for the agent
+ * and block (with specified timeout override) waiting for a response.
+ *
+ * @param agentName the name of the Agent to be returned.
+ * <p>
+ * "broker" or "qpidd" may be used as synonyms for the broker Agent name and the method will try to match
+ * agentName against the Agent name, the Agent product name and will also check if the Agent name contains
+ * the agentName String.
+ * <p>
+ * Checking against a partial match is useful because the full Agent name has a UUID representing
+ * the "instance" so it's hard to know the full name without having actually retrieved the Agent.
+ * @param timeout the time (in seconds) to wait for the Agent to be found
+ * @return the found Agent instance or null if an Agent of the given name could not be found
+ */
+ public Agent findAgent(final String agentName, final int timeout)
+ {
+ Agent agent = getAgent(agentName);
+ if (agent == null)
+ {
+ broadcastAgentLocate();
+ long startTime = System.currentTimeMillis();
+ do
+ {
+ agent = getAgent(agentName);
+ if (agent != null)
+ {
+ return agent;
+ }
+
+ synchronized(this)
+ {
+ try
+ { // At worst this behaves as a 1 second sleep, but will return sooner if an Agent gets registered
+ wait(1000);
+ }
+ catch (InterruptedException ie)
+ {
+ }
+ }
+ } while ((System.currentTimeMillis() - startTime)/1000 < _replyTimeout);
+ }
+ return agent;
+ }
+
+ /**
+ * Called to enable the asynchronous Agent Discovery process. Once enabled, AGENT_ADDED and AGENT_DELETED
+ * work items can arrive on the WorkQueue.
+ * <p>
+ * Note that in this implementation Agent Discovery is enabled by default. Note too that enableAgentDiscovery()
+ * or disableAgentDiscovery() should be called before addConnection(), as this starts a MessageListener Thread
+ * that could place events on the work queue.
+ */
+ public void enableAgentDiscovery()
+ {
+ _discoverAgents = true;
+ _agentQuery = null;
+ }
+
+ /**
+ * Called to enable the asynchronous Agent Discovery process. Once enabled, AGENT_ADDED and AGENT_DELETED
+ * work items can arrive on the WorkQueue. The supplied query will be used to filter agent notifications.
+ * <p>
+ * Note that in this implementation Agent Discovery is enabled by default. Note too that enableAgentDiscovery()
+ * or disableAgentDiscovery() should be called before addConnection(), as this starts a MessageListener Thread
+ * that could place events on the work queue.
+ *
+ * @param query the query used to filter agent notifications.
+ */
+ public void enableAgentDiscovery(final QmfQuery query)
+ {
+ _discoverAgents = true;
+ _agentQuery = query;
+ }
+
+ /**
+ * Called to disable the async Agent Discovery process enabled by calling enableAgentDiscovery().
+ * <p>
+ * Note that in this implementation Agent Discovery is enabled by default. Note too that enableAgentDiscovery()
+ * or disableAgentDiscovery() should be called before addConnection() as this starts a MessageListener Thread
+ * that could place events on the work queue.
+ */
+ public void disableAgentDiscovery()
+ {
+ _discoverAgents = false;
+ _agentQuery = null;
+ }
+
+ /**
+ * Called to disable asynchronous behaviour such as QMF Events, Agent discovery etc. useful in simple
+ * Use Cases such as getObjects() on the broker. Note that asynchronous behaviour enabled by default.
+ * <p>
+ * Note too that disableEvents() should be called <b>before</b> addConnection() as this
+ * starts the MessageListener Thread and creates the additional queues used for Asynchronous Behaviour.
+ * <p>
+ * This method is <b>not</b> an official method specified in the QMF2 API, however it is a useful extension
+ * as Consoles that only call getObjects() on the broker ManagementAgent is an extremely common scenario.
+ */
+ public void disableEvents()
+ {
+ _disableEvents = true;
+ }
+
+ /**
+ * Return the count of pending WorkItems that can be retrieved.
+ * @return the count of pending WorkItems that can be retrieved.
+ */
+ public int getWorkitemCount()
+ {
+ return _workQueue.size();
+ }
+
+ /**
+ * Obtains the next pending work item - blocking version.
+ * <p>
+ * The blocking getNextWorkitem() can be used without the need for a Notifier as it will block until
+ * a new item gets added to the work queue e.g. the following usage pattern.
+ * <pre>
+ * while ((wi = console.getNextWorkitem()) != null)
+ * {
+ * System.out.println("WorkItem type: " + wi.getType());
+ * }
+ * </pre>
+ * @return the next pending work item, or null if none available.
+ */
+ public WorkItem getNextWorkitem()
+ {
+ return _workQueue.getNextWorkitem();
+ }
+
+ /**
+ * Obtains the next pending work item - balking version.
+ * <p>
+ * The balking getNextWorkitem() is generally used with a Notifier which can be used as a gate to determine
+ * if any work items are available. e.g. the following usage pattern.
+ * <pre>
+ * while (true)
+ * {
+ * notifier.waitForWorkItem(); // Assuming a BlockingNotifier has been used here
+ * System.out.println("WorkItem available, count = " + console.getWorkitemCount());
+ *
+ * WorkItem wi;
+ * while ((wi = console.getNextWorkitem(0)) != null)
+ * {
+ * System.out.println("WorkItem type: " + wi.getType());
+ * }
+ * }
+ * </pre>
+ * Note that it is possible for the getNextWorkitem() loop to retrieve multiple items from the workQueue
+ * and for the Console to add new items as the loop is looping, thus when it finally exits and goes
+ * back to the outer loop notifier.waitForWorkItems() may return immediately as it had been notified
+ * whilst we were in the getNextWorkitem() loop. This will be evident by a getWorkitemCount() of 0
+ * after returning from waitForWorkItem().
+ * <p>
+ * This is the expected behaviour, but illustrates the need to check for nullness of the value returned
+ * by getNextWorkitem(), or alternatively to use getWorkitemCount() to put getNextWorkitem() in a
+ * bounded loop.
+ *
+ * @param timeout the timeout in seconds. If timeout = 0 it returns immediately with either a WorkItem or null
+ * @return the next pending work item, or null if none available.
+ */
+ public WorkItem getNextWorkitem(final long timeout)
+ {
+ return _workQueue.getNextWorkitem(timeout);
+ }
+
+ /**
+ * Releases a WorkItem instance obtained by getNextWorkItem(). Called when the application has finished
+ * processing the WorkItem.
+ */
+ public void releaseWorkitem()
+ {
+ // To be honest I'm not clear what the intent of this method actually is. One thought is that it's here
+ // to support equivalent behaviour to the Python Queue.task_done() which is used by queue consumer threads.
+ // For each get() used to fetch a task, a subsequent call to task_done() tells the queue that the processing
+ // on the task is complete.
+ //
+ // The problem with that theory is there is no equivalent QMF2 API call that would invoke the
+ // Queue.join() which is used in conjunction with Queue.task_done() to enable a synchronisation gate to
+ // be implemented to wait for completion of all worker thread.
+ //
+ // I'm a bit stumped and there's no obvious Java equivalent on BlockingQueue, so for now this does nothing.
+ }
+
+ /**
+ * Returns a list of all known Agents
+ * <p>
+ * Note that this call is synchronous and non-blocking. It only returns locally cached data and will
+ * not send any messages to the remote agent.
+ *
+ * @return a list of available Agents
+ */
+ public List<Agent> getAgents()
+ {
+ return new ArrayList<Agent>(_agents.values());
+ }
+
+ /**
+ * Return the named Agent, if known.
+ *
+ * @param agentName the name of the Agent to be returned.
+ * <p>
+ * "broker" or "qpidd" may be used as synonyms for the broker Agent name and the method will try to match
+ * agentName against the Agent name, the Agent product name and will also check if the Agent name contains
+ * the agentName String.
+ * <p>
+ * Checking against a partial match is useful because the full Agent name has a UUID representing
+ * the "instance" so it's hard to know the full name without having actually retrieved the Agent.
+ * @return the found Agent instance or null if an Agent of the given name could not be found
+ */
+ public Agent getAgent(final String agentName)
+ {
+ if (agentName == null)
+ {
+ return null;
+ }
+
+ // First we check if the Agent name is one of the aliases of the broker Agent
+ if (_brokerAgentName != null)
+ {
+ if (agentName.equals("broker") || agentName.equals("qpidd") || agentName.equals(_brokerAgentName))
+ {
+ return _agents.get(_brokerAgentName);
+ }
+ }
+
+ for (Agent agent : getAgents())
+ {
+ String product = agent.getProduct();
+ String name = agent.getName();
+ if (agentName.equals(product) || agentName.equals(name) || name.contains(agentName))
+ {
+ return agent;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return a list of all known Packages.
+ * @return a list of all known Packages.
+ */
+ public List<String> getPackages()
+ {
+ List<String> results = new ArrayList<String>();
+ for (Agent agent : getAgents())
+ {
+ results.addAll(getPackages(agent));
+ }
+ return results;
+ }
+
+ /**
+ * Return a list of Packages for the specified Agent.
+ * @param agent the Agent being queried.
+ * @return a list of Packages for the specified Agent.
+ */
+ public List<String> getPackages(final Agent agent)
+ {
+ return agent.getPackages();
+ }
+
+ /**
+ * Return a List of SchemaClassId for all available Schema.
+ * @return a List of SchemaClassId for all available Schema.
+ */
+ public List<SchemaClassId> getClasses()
+ {
+ List<SchemaClassId> results = new ArrayList<SchemaClassId>();
+ for (Agent agent : getAgents())
+ {
+ results.addAll(getClasses(agent));
+ }
+ return results;
+ }
+
+ /**
+ * Return a list of SchemaClassIds for all available Schema for the specified Agent.
+ * @param agent the Agent being queried
+ * @return a list of SchemaClassIds for all available Schema for the specified Agent.
+ */
+ public List<SchemaClassId> getClasses(final Agent agent)
+ {
+ // First look to see if there are cached results and if there are return those.
+ List<SchemaClassId> results = agent.getClasses();
+ if (results.size() > 0)
+ {
+ return results;
+ }
+
+ String agentName = agent.getName();
+ results = new ArrayList<SchemaClassId>();
+ try
+ {
+ MapMessage request = _syncSession.createMapMessage();
+ request.setJMSReplyTo(_replyAddress);
+ request.setStringProperty("x-amqp-0-10.app-id", "qmf2");
+ request.setStringProperty("method", "request");
+ request.setStringProperty("qmf.opcode", "_query_request");
+ request.setStringProperty("qpid.subject", agentName);
+
+ // Create a QMF Query for an "SCHEMA_ID" target
+ request.setObject("_what", "SCHEMA_ID");
+ // Wrap request & response in synchronized block in case any other threads invoke a request
+ // it would be somewhat unfortunate if their response got interleaved with ours!!
+ synchronized(this)
+ {
+ _requester.send(request);
+ Message response = _responder.receive(_replyTimeout*1000);
+ if (response == null)
+ {
+ _log.info("No response received in getClasses()");
+ return Collections.emptyList();
+ }
+
+ if (AMQPMessage.isAMQPList(response))
+ {
+ List<Map> mapResults = AMQPMessage.getList(response);
+ for (Map content : mapResults)
+ {
+//new SchemaClassId(content).listValues();
+ results.add(new SchemaClassId(content));
+ }
+ }
+ else if (AMQPMessage.isAMQPMap(response))
+ {
+ // Error responses are returned as MapMessages, though they are being ignored here.
+ //System.out.println("Console.getClasses() no results for " + agentName);
+ //QmfData exception = new QmfData(AMQPMessage.getMap(response));
+ //System.out.println(agentName + " " + exception.getStringValue("error_text"));
+ }
+ else
+ {
+ _log.info("getClasses() Received response message in incorrect format");
+ }
+ }
+ }
+ catch (JMSException jmse)
+ {
+ _log.info("JMSException {} caught in getClasses()", jmse.getMessage());
+ }
+ agent.setClasses(results);
+ return results;
+ }
+
+ /**
+ * Return a list of all available class SchemaClass across all known Agents. If an optional Agent is
+ * provided, restrict the returned schema to those supported by that Agent.
+ * <p>
+ * This call will return cached information if it is available. If not, it will send a query message
+ * to the remote agent and block waiting for a response. The timeout argument specifies the maximum time
+ * to wait for a response from the agent.
+ *
+ * @param schemaClassId the SchemaClassId we wish to return schema information for.
+ */
+ public List<SchemaClass> getSchema(final SchemaClassId schemaClassId)
+ {
+ List<SchemaClass> results = new ArrayList<SchemaClass>();
+ List<Agent> agentList = getAgents();
+ for (Agent agent : agentList)
+ {
+ results.addAll(getSchema(schemaClassId, agent));
+ }
+ return results;
+ }
+
+ /**
+ * Return a list of all available class SchemaClass from a specified Agent.
+ * <p>
+ * This call will return cached information if it is available. If not, it will send a query message
+ * to the remote agent and block waiting for a response. The timeout argument specifies the maximum time
+ * to wait for a response from the agent.
+ *
+ * @param schemaClassId the SchemaClassId we wish to return schema information for.
+ * @param agent the Agent we want to retrieve the schema from
+ */
+ public List<SchemaClass> getSchema(final SchemaClassId schemaClassId, final Agent agent)
+ {
+ // First look to see if there are cached results and if there are return those.
+ List<SchemaClass> results = agent.getSchema(schemaClassId);
+ if (results.size() > 0)
+ {
+ return results;
+ }
+
+ String agentName = agent.getName();
+//System.out.println("getSchema for agent " + agentName);
+ results = new ArrayList<SchemaClass>();
+ try
+ {
+ MapMessage request = _syncSession.createMapMessage();
+ request.setJMSReplyTo(_replyAddress);
+ request.setStringProperty("x-amqp-0-10.app-id", "qmf2");
+ request.setStringProperty("method", "request");
+ request.setStringProperty("qmf.opcode", "_query_request");
+ request.setStringProperty("qpid.subject", agentName);
+
+ // Create a QMF Query for an "SCHEMA" target
+ request.setObject("_what", "SCHEMA");
+ request.setObject("_schema_id", schemaClassId.mapEncode());
+
+ // Wrap request & response in synchronized block in case any other threads invoke a request
+ // it would be somewhat unfortunate if their response got interleaved with ours!!
+ synchronized(this)
+ {
+ _requester.send(request);
+ Message response = _responder.receive(_replyTimeout*1000);
+ if (response == null)
+ {
+ _log.info("No response received in getSchema()");
+ return Collections.emptyList();
+ }
+
+ if (AMQPMessage.isAMQPList(response))
+ {
+ List<Map> mapResults = AMQPMessage.getList(response);
+ for (Map content : mapResults)
+ {
+ SchemaClass schema = new SchemaObjectClass(content);
+ if (schema.getClassId().getType().equals("_event"))
+ {
+ schema = new SchemaEventClass(content);
+ }
+//schema.listValues();
+ results.add(schema);
+ }
+ }
+ else if (AMQPMessage.isAMQPMap(response))
+ {
+ // Error responses are returned as MapMessages, though they are being ignored here.
+ //System.out.println("Console.getSchema() no results for " + agentName);
+ //QmfData exception = new QmfData(AMQPMessage.getMap(response));
+ //System.out.println(agentName + " " + exception.getStringValue("error_text"));
+ }
+ else
+ {
+ _log.info("getSchema() Received response message in incorrect format");
+ }
+ }
+ }
+ catch (JMSException jmse)
+ {
+ _log.info("JMSException {} caught in getSchema()", jmse.getMessage());
+ }
+ agent.setSchema(schemaClassId, results);
+ return results;
+ }
+
+ /**
+ * Perform a blocking query for QmfConsoleData objects. Returns a list (possibly empty) of matching objects
+ * This method will block until all known Agents reply, or the timeout expires. Once the timeout expires, all
+ * data retrieved to date is returned.
+ *
+ * @param className the schema class name we're looking up objects for.
+ * @return a List of QMF Objects describing that class.
+ */
+ public List<QmfConsoleData> getObjects(final String className)
+ {
+ return getObjects(new SchemaClassId(className));
+ }
+
+ /**
+ * Perform a blocking query for QmfConsoleData objects. Returns a list (possibly empty) of matching objects
+ * This method will block until all known Agents reply, or the timeout expires. Once the timeout expires, all
+ * data retrieved to date is returned.
+ *
+ * @param className the schema class name we're looking up objects for.
+ * @param timeout overrides the default replyTimeout.
+ * @return a List of QMF Objects describing that class.
+ */
+ public List<QmfConsoleData> getObjects(final String className, final int timeout)
+ {
+ return getObjects(new SchemaClassId(className), timeout);
+ }
+
+ /**
+ * Perform a blocking query for QmfConsoleData objects. Returns a list (possibly empty) of matching objects
+ * This method will block until all known Agents reply, or the timeout expires. Once the timeout expires, all
+ * data retrieved to date is returned.
+ *
+ * @param className the schema class name we're looking up objects for.
+ * @param agentList if this parameter is supplied then the query is sent to only those Agents.
+ * @return a List of QMF Objects describing that class.
+ */
+ public List<QmfConsoleData> getObjects(final String className, final List<Agent> agentList)
+ {
+ return getObjects(new SchemaClassId(className), agentList);
+ }
+
+ /**
+ * Perform a blocking query for QmfConsoleData objects. Returns a list (possibly empty) of matching objects
+ * This method will block until all known Agents reply, or the timeout expires. Once the timeout expires, all
+ * data retrieved to date is returned.
+ *
+ * @param className the schema class name we're looking up objects for.
+ * @param timeout overrides the default replyTimeout.
+ * @param agentList if this parameter is supplied then the query is sent to only those Agents.
+ * @return a List of QMF Objects describing that class.
+ */
+ public List<QmfConsoleData> getObjects(final String className, final int timeout, final List<Agent> agentList)
+ {
+ return getObjects(new SchemaClassId(className), timeout, agentList);
+ }
+
+ /**
+ * Perform a blocking query for QmfConsoleData objects. Returns a list (possibly empty) of matching objects
+ * This method will block until all known Agents reply, or the timeout expires. Once the timeout expires, all
+ * data retrieved to date is returned.
+ *
+ * @param packageName the schema package name we're looking up objects for.
+ * @param className the schema class name we're looking up objects for.
+ * @return a List of QMF Objects describing that class
+ */
+ public List<QmfConsoleData> getObjects(final String packageName, final String className)
+ {
+ return getObjects(new SchemaClassId(packageName, className));
+ }
+
+ /**
+ * Perform a blocking query for QmfConsoleData objects. Returns a list (possibly empty) of matching objects
+ * This method will block until all known Agents reply, or the timeout expires. Once the timeout expires, all
+ * data retrieved to date is returned.
+ *
+ * @param packageName the schema package name we're looking up objects for.
+ * @param className the schema class name we're looking up objects for.
+ * @param timeout overrides the default replyTimeout.
+ * @return a List of QMF Objects describing that class.
+ */
+ public List<QmfConsoleData> getObjects(final String packageName, final String className, final int timeout)
+ {
+ return getObjects(new SchemaClassId(packageName, className), timeout);
+ }
+
+ /**
+ * Perform a blocking query for QmfConsoleData objects. Returns a list (possibly empty) of matching objects
+ * This method will block until all known Agents reply, or the timeout expires. Once the timeout expires, all
+ * data retrieved to date is returned.
+ *
+ * @param packageName the schema package name we're looking up objects for.
+ * @param className the schema class name we're looking up objects for.
+ * @param agentList if this parameter is supplied then the query is sent to only those Agents.
+ * @return a List of QMF Objects describing that class.
+ */
+ public List<QmfConsoleData> getObjects(final String packageName, final String className, final List<Agent> agentList)
+ {
+ return getObjects(new SchemaClassId(packageName, className), agentList);
+ }
+
+ /**
+ * Perform a blocking query for QmfConsoleData objects. Returns a list (possibly empty) of matching objects
+ * This method will block until all known Agents reply, or the timeout expires. Once the timeout expires, all
+ * data retrieved to date is returned.
+ *
+ * @param packageName the schema package name we're looking up objects for.
+ * @param className the schema class name we're looking up objects for.
+ * @param timeout overrides the default replyTimeout.
+ * @param agentList if this parameter is supplied then the query is sent to only those Agents.
+ * @return a List of QMF Objects describing that class.
+ */
+ public List<QmfConsoleData> getObjects(final String packageName, final String className,
+ final int timeout, final List<Agent> agentList)
+ {
+ return getObjects(new SchemaClassId(packageName, className), timeout, agentList);
+ }
+
+ /**
+ * Perform a blocking query for QmfConsoleData objects. Returns a list (possibly empty) of matching objects
+ * This method will block until all known Agents reply, or the timeout expires. Once the timeout expires, all
+ * data retrieved to date is returned.
+ *
+ * @param query the SchemaClassId or ObjectId we're looking up objects for.
+ * @return a List of QMF Objects describing that class.
+ */
+ public List<QmfConsoleData> getObjects(final QmfData query)
+ {
+ return getObjects(query, _replyTimeout, getAgents());
+ }
+
+ /**
+ * Perform a blocking query for QmfConsoleData objects. Returns a list (possibly empty) of matching objects
+ * This method will block until all known Agents reply, or the timeout expires. Once the timeout expires, all
+ * data retrieved to date is returned.
+ *
+ * @param query the SchemaClassId or ObjectId we're looking up objects for.
+ * @param timeout overrides the default replyTimeout.
+ * @return a List of QMF Objects describing that class.
+ */
+ public List<QmfConsoleData> getObjects(final QmfData query, final int timeout)
+ {
+ return getObjects(query, timeout, getAgents());
+ }
+
+ /**
+ * Perform a blocking query for QmfConsoleData objects. Returns a list (possibly empty) of matching objects
+ * This method will block until all known Agents reply, or the timeout expires. Once the timeout expires, all
+ * data retrieved to date is returned.
+ *
+ * @param query the SchemaClassId or ObjectId we're looking up objects for.
+ * @param agentList if this parameter is supplied then the query is sent to only those Agents.
+ * @return a List of QMF Objects describing that class.
+ */
+ public List<QmfConsoleData> getObjects(final QmfData query, final List<Agent> agentList)
+ {
+ return getObjects(query, _replyTimeout, agentList);
+ }
+
+ /**
+ * Perform a blocking query for QmfConsoleData objects. Returns a list (possibly empty) of matching objects
+ * This method will block until all known Agents reply, or the timeout expires. Once the timeout expires, all
+ * data retrieved to date is returned.
+ *
+ * @param query the SchemaClassId or ObjectId we're looking up objects for.
+ * @param timeout overrides the default replyTimeout.
+ * @param agentList if this parameter is supplied then the query is sent to only those Agents.
+ * @return a List of QMF Objects describing that class.
+ */
+ public List<QmfConsoleData> getObjects(final QmfData query, final int timeout, final List<Agent> agentList)
+ {
+ List<QmfConsoleData> results = new ArrayList<QmfConsoleData>();
+ for (Agent agent : agentList)
+ {
+ results.addAll(getObjects(agent, query, null, timeout));
+ }
+ return results;
+ }
+
+ /**
+ * Creates a subscription to the agent using the given Query.
+ * <p>
+ * The consoleHandle is an application-provided handle that will accompany each subscription update sent from
+ * the Agent. Subscription updates will appear as SUBSCRIPTION_INDICATION WorkItems on the Console's work queue.
+ *
+ * @param agent the Agent on which to create the subscription.
+ * @param query the Query to perform on the Agent
+ * @param consoleHandle an application-provided handle that will accompany each subscription update sent
+ * from the Agent.
+ */
+ public SubscribeParams createSubscription(final Agent agent, final QmfQuery query,
+ final String consoleHandle) throws QmfException
+ {
+ return createSubscription(agent, query, consoleHandle, null);
+ }
+
+ /**
+ * Creates a subscription to the agent using the given Query.
+ * <p>
+ * The consoleHandle is an application-provided handle that will accompany each subscription update sent from
+ * the Agent. Subscription updates will appear as SUBSCRIPTION_INDICATION WorkItems on the Console's work queue.
+ * <p>
+ * The publishInterval is the requested time interval in seconds on which the Agent should publish updates.
+ * <p>
+ * The lifetime parameter is the requested time interval in seconds for which this subscription should remain in
+ * effect. Both the requested lifetime and publishInterval may be overridden by the Agent, as indicated in the
+ * subscription response.
+ * <p>
+ * This method may be called asynchronously by providing a replyHandle argument. When called
+ * asynchronously, the result of this method call is returned in a SUBSCRIBE_RESPONSE WorkItem with a
+ * handle matching the value of replyHandle.
+ * <p>
+ * Timeout can be used to override the console's default reply timeout.
+ * <p>
+ * When called synchronously, this method returns a SubscribeParams object containing the result of the
+ * subscription request.
+ *
+ * @param agent the Agent on which to create the subscription.
+ * @param query the Query to perform on the Agent
+ * @param consoleHandle an application-provided handle that will accompany each subscription update sent
+ * from the Agent.
+ * @param options a String representation of a Map containing the options in the form
+ * <pre>"{lifetime:&lt;value&gt;, publishInterval:&lt;value&gt;, replyHandle:&lt;value&gt;, timeout:&lt;value&gt;}"</pre>
+ * they are optional and may appear in any order.
+ * <pre>
+ * <b>lifetime</b> the requested time interval in seconds for which this subscription should remain in effect.
+ * <b>publishInterval</b> the requested time interval in seconds on which the Agent should publish updates
+ * <b>replyHandle</b> the correlation handle used to tie asynchronous method requests with responses.
+ * <b>timeout</b> the time to wait for a reply from the Agent.
+ * </pre>
+ */
+ public synchronized SubscribeParams createSubscription(final Agent agent, final QmfQuery query,
+ final String consoleHandle, final String options) throws QmfException
+ {
+ if (consoleHandle == null)
+ {
+ throw new QmfException("Called createSubscription() with null consoleHandle");
+ }
+ if (_subscriptionByHandle.get(consoleHandle) != null)
+ {
+ throw new QmfException("Called createSubscription() with a consoleHandle that is already in use");
+ }
+ if (agent == null)
+ {
+ throw new QmfException("Called createSubscription() with null agent");
+ }
+ if (!agent.isActive())
+ {
+ throw new QmfException("Called createSubscription() with inactive agent");
+ }
+ String agentName = agent.getName();
+
+ // Initialise optional values to defaults;
+ long lifetime = _subscriptionDuration;
+ long publishInterval = 10000;
+ long timeout = _replyTimeout;
+ String replyHandle = null;
+
+ if (options != null)
+ { // We wrap the Map in a QmfData object to avoid potential class cast issues with the parsed options
+ QmfData optMap = new QmfData(new AddressParser(options).map());
+ if (optMap.hasValue("lifetime"))
+ {
+ lifetime = optMap.getLongValue("lifetime");
+ }
+
+ if (optMap.hasValue("publishInterval"))
+ { // Multiply publishInterval by 1000 because the QMF2 protocol spec says interval is
+ // "The request time (in milliseconds) between periodic updates of data in this subscription"
+ publishInterval = 1000*optMap.getLongValue("publishInterval");
+ }
+
+ if (optMap.hasValue("timeout"))
+ {
+ timeout = optMap.getLongValue("timeout");
+ }
+
+ if (optMap.hasValue("replyHandle"))
+ {
+ replyHandle = optMap.getStringValue("replyHandle");
+ }
+ }
+
+ try
+ {
+ MapMessage request = _syncSession.createMapMessage();
+ request.setJMSReplyTo(_asyncReplyAddress); // Deliberately forcing all replies to the _asyncReplyAddress
+ request.setJMSCorrelationID(consoleHandle); // Deliberately using consoleHandle not replyHandle here
+ request.setStringProperty("x-amqp-0-10.app-id", "qmf2");
+ request.setStringProperty("method", "request");
+ request.setStringProperty("qmf.opcode", "_subscribe_request");
+ request.setStringProperty("qpid.subject", agentName);
+
+ request.setObject("_query", query.mapEncode());
+ request.setObject("_interval", publishInterval);
+ request.setObject("_duration", lifetime);
+
+ SubscriptionManager subscription =
+ new SubscriptionManager(agent, query, consoleHandle, replyHandle, publishInterval, lifetime);
+ _subscriptionByHandle.put(consoleHandle, subscription);
+ _timer.schedule(subscription, 0, publishInterval);
+
+ if (_subscriptionEmulationEnabled && agentName.equals(_brokerAgentName))
+ { // If the Agent is the broker Agent we emulate the Subscription on the Console
+ String subscriptionId = UUID.randomUUID().toString();
+ _subscriptionById.put(subscriptionId, subscription);
+ subscription.setSubscriptionId(subscriptionId);
+ final SubscribeParams params = new SubscribeParams(consoleHandle, subscription.mapEncode());
+ if (replyHandle == null)
+ {
+ return params;
+ }
+ else
+ {
+ final String handle = replyHandle;
+ Thread thread = new Thread()
+ {
+ public void run()
+ {
+ _eventListener.onEvent(new SubscribeResponseWorkItem(new Handle(handle), params));
+ }
+ };
+ thread.start();
+ }
+ return null;
+ }
+
+ _requester.send(request);
+ if (replyHandle == null)
+ { // If this is an synchronous request get the response
+ subscription.await(timeout*1000);
+ if (subscription.getSubscriptionId() == null)
+ {
+ _log.info("No response received in createSubscription()");
+ throw new QmfException("No response received for Console.createSubscription()");
+ }
+ return new SubscribeParams(consoleHandle, subscription.mapEncode());
+ }
+
+ // If this is an asynchronous request return without waiting for a response
+ return null;
+ }
+ catch (JMSException jmse)
+ {
+ _log.info("JMSException {} caught in createSubscription()", jmse.getMessage());
+ throw new QmfException(jmse.getMessage());
+ }
+ } // end of createSubscription()
+
+ /**
+ * Renews a subscription identified by SubscriptionId.
+ *
+ * @param subscriptionId the ID of the subscription to be refreshed
+ */
+ public void refreshSubscription(final String subscriptionId) throws QmfException
+ {
+ refreshSubscription(subscriptionId, null);
+ }
+
+ /**
+ * Renews a subscription identified by SubscriptionId.
+ * <p>
+ * The Console may request a new subscription duration by providing a requested lifetime. This method may be called
+ * asynchronously by providing a replyHandle argument.
+ * <p>
+ * When called asynchronously, the result of this method call is returned in a SUBSCRIBE_RESPONSE WorkItem.
+ * <p>
+ * Timeout can be used to override the console's default reply timeout.
+ * <p>
+ * When called synchronously, this method returns a class SubscribeParams object containing the result of the
+ * subscription request.
+ *
+ * @param subscriptionId the ID of the subscription to be refreshed
+ * @param options a String representation of a Map containing the options in the form
+ * <pre>"{lifetime:&lt;value&gt;, replyHandle:&lt;value&gt;, timeout:&lt;value&gt;}"</pre>
+ * they are optional and may appear in any order.
+ * <pre>
+ * <b>lifetime</b> requests a new subscription duration.
+ * <b>replyHandle</b> the correlation handle used to tie asynchronous method requests with responses.
+ * <b>timeout</b> the time to wait for a reply from the Agent.
+ * </pre>
+ */
+ public SubscribeParams refreshSubscription(String subscriptionId, final String options) throws QmfException
+ {
+ if (subscriptionId == null)
+ {
+ throw new QmfException("Called refreshSubscription() with null subscriptionId");
+ }
+ SubscriptionManager subscription = _subscriptionById.get(subscriptionId);
+ if (subscription == null)
+ {
+ throw new QmfException("Called refreshSubscription() with invalid subscriptionId");
+ }
+ String consoleHandle = subscription.getConsoleHandle();
+ Agent agent = subscription.getAgent();
+ if (!agent.isActive())
+ {
+ throw new QmfException("Called refreshSubscription() with inactive agent");
+ }
+ String agentName = agent.getName();
+
+ // Initialise optional values to defaults;
+ long lifetime = 0;
+ long timeout = _replyTimeout;
+ String replyHandle = null;
+
+ if (options != null)
+ { // We wrap the Map in a QmfData object to avoid potential class cast issues with the parsed options
+ QmfData optMap = new QmfData(new AddressParser(options).map());
+ if (optMap.hasValue("lifetime"))
+ {
+ lifetime = optMap.getLongValue("lifetime");
+ }
+
+ if (optMap.hasValue("timeout"))
+ {
+ timeout = optMap.getLongValue("timeout");
+ }
+
+ if (optMap.hasValue("replyHandle"))
+ {
+ replyHandle = optMap.getStringValue("replyHandle");
+ }
+ }
+
+ try
+ {
+ Destination destination = (replyHandle == null) ? _replyAddress : _asyncReplyAddress;
+ MapMessage request = _syncSession.createMapMessage();
+ request.setJMSReplyTo(destination);
+ request.setJMSCorrelationID(replyHandle);
+ request.setStringProperty("x-amqp-0-10.app-id", "qmf2");
+ request.setStringProperty("method", "request");
+ request.setStringProperty("qmf.opcode", "_subscribe_refresh_indication");
+ request.setStringProperty("qpid.subject", agentName);
+
+ request.setObject("_subscription_id", subscriptionId);
+ if (lifetime > 0)
+ {
+ request.setObject("_duration", lifetime);
+ }
+
+ // Wrap request & response in synchronized block in case any other threads invoke a request
+ // it would be somewhat unfortunate if their response got interleaved with ours!!
+ synchronized(this)
+ {
+ if (_subscriptionEmulationEnabled && agentName.equals(_brokerAgentName))
+ { // If the Agent is the broker Agent we emulate the Subscription on the Console
+ subscription.refresh();
+ final SubscribeParams params = new SubscribeParams(consoleHandle, subscription.mapEncode());
+ if (replyHandle == null)
+ {
+ return params;
+ }
+ else
+ {
+ final String handle = replyHandle;
+ Thread thread = new Thread()
+ {
+ public void run()
+ {
+ _eventListener.onEvent(new SubscribeResponseWorkItem(new Handle(handle), params));
+ }
+ };
+ thread.start();
+ }
+ return null;
+ }
+
+ _requester.send(request);
+ if (replyHandle == null)
+ { // If this is an synchronous request get the response
+ Message response = _responder.receive(timeout*1000);
+ if (response == null)
+ {
+ subscription.cancel();
+ _log.info("No response received in refreshSubscription()");
+ throw new QmfException("No response received for Console.refreshSubscription()");
+ }
+ SubscribeParams result = new SubscribeParams(consoleHandle, AMQPMessage.getMap(response));
+ subscriptionId = result.getSubscriptionId();
+ if (subscriptionId == null)
+ {
+ subscription.cancel();
+ }
+ else
+ {
+ subscription.setDuration(result.getLifetime());
+ subscription.refresh();
+ }
+ return result;
+ }
+ }
+ // If this is an asynchronous request return without waiting for a response
+ return null;
+ }
+ catch (JMSException jmse)
+ {
+ _log.info("JMSException {} caught in refreshSubscription()", jmse.getMessage());
+ throw new QmfException(jmse.getMessage());
+ }
+ } // end of refreshSubscription()
+
+ /**
+ * Terminates the given subscription.
+ *
+ * @param subscriptionId the ID of the subscription to be cancelled
+ */
+ public void cancelSubscription(final String subscriptionId) throws QmfException
+ {
+ if (subscriptionId == null)
+ {
+ throw new QmfException("Called cancelSubscription() with null subscriptionId");
+ }
+ SubscriptionManager subscription = _subscriptionById.get(subscriptionId);
+ if (subscription == null)
+ {
+ throw new QmfException("Called cancelSubscription() with invalid subscriptionId");
+ }
+ String consoleHandle = subscription.getConsoleHandle();
+ Agent agent = subscription.getAgent();
+ if (!agent.isActive())
+ {
+ throw new QmfException("Called cancelSubscription() with inactive agent");
+ }
+ String agentName = agent.getName();
+
+ try
+ {
+ MapMessage request = _syncSession.createMapMessage();
+ request.setStringProperty("x-amqp-0-10.app-id", "qmf2");
+ request.setStringProperty("method", "request");
+ request.setStringProperty("qmf.opcode", "_subscribe_cancel_indication");
+ request.setStringProperty("qpid.subject", agentName);
+ request.setObject("_subscription_id", subscriptionId);
+
+ synchronized(this)
+ {
+ if (!_subscriptionEmulationEnabled || !agentName.equals(_brokerAgentName))
+ {
+ _requester.send(request);
+ }
+ }
+ subscription.cancel();
+ }
+ catch (JMSException jmse)
+ {
+ _log.info("JMSException {} caught in cancelSubscription()", jmse.getMessage());
+ }
+ }
+}
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/EventReceivedWorkItem.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/EventReceivedWorkItem.java
new file mode 100644
index 0000000000..311acf3dbb
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/EventReceivedWorkItem.java
@@ -0,0 +1,63 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.console;
+
+import java.util.Map;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.QmfEvent;
+
+/**
+ * Descriptions below are taken from <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API Proposal</a>
+ * <pre>
+ * EVENT_RECEIVED: When an Agent generates a QmfEvent an EVENT_RECEIVED WorkItem is pushed onto the work-queue.
+ * The WorkItem's getParam() call returns a map which contains a reference to the Console Agent
+ * instance that generated the Event and a reference to the QmfEvent itself. The Agent reference
+ * is indexed from the map using the key string "agent, The QmfEvent reference is indexed from
+ * the map using the key string "event". There is no handle associated with this WorkItem.
+ * </pre>
+ * @author Fraser Adams
+ */
+
+public final class EventReceivedWorkItem extends AgentAccessWorkItem
+{
+ /**
+ * Construct a EventReceivedWorkItem. Convenience constructor not in API
+ *
+ * @param agent the Agent used to populate the WorkItem's param
+ * @param event the QmfEvent used to populate the WorkItem's param
+ */
+ public EventReceivedWorkItem(final Agent agent, final QmfEvent event)
+ {
+ super(WorkItemType.EVENT_RECEIVED, null, newParams(agent, event));
+ }
+
+ /**
+ * Return the QmfEvent stored in the params Map.
+ * @return the QmfEvent stored in the params Map.
+ */
+ public QmfEvent getEvent()
+ {
+ Map<String, Object> p = this.<Map<String, Object>>getParams();
+ return (QmfEvent)p.get("event");
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/MethodResponseWorkItem.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/MethodResponseWorkItem.java
new file mode 100644
index 0000000000..01dde0b6b6
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/MethodResponseWorkItem.java
@@ -0,0 +1,65 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.console;
+
+import java.util.Map;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.Handle;
+import org.apache.qpid.qmf2.common.WorkItem;
+
+/**
+ * Descriptions below are taken from <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API Proposal</a>
+ * <pre>
+ * METHOD_RESPONSE: The METHOD_RESPONSE WorkItem is generated in response to an asynchronous invokeMethod made
+ * by a QmfConsoleData object.
+ *
+ * The getParams() method of a METHOD_RESPONSE WorkItem will return a MethodResult object.
+ * The getHandle() method returns the reply handle provided to the method call.
+ * This handle is merely the handle used for the asynchronous response, it is not associated
+ * with the QmfConsoleData in any other way.
+ * </pre>
+ * @author Fraser Adams
+ */
+
+public final class MethodResponseWorkItem extends WorkItem
+{
+ /**
+ * Construct a MethodResponseWorkItem. Convenience constructor not in API
+ *
+ * @param handle the reply handle used to associate requests and responses
+ * @param params the MethodCallParams used to populate the WorkItem's param
+ */
+ public MethodResponseWorkItem(final Handle handle, final MethodResult params)
+ {
+ super(WorkItemType.METHOD_RESPONSE, handle, params);
+ }
+
+ /**
+ * Return the MethodResult stored in the params.
+ * @return the MethodResult stored in the params.
+ */
+ public MethodResult getMethodResult()
+ {
+ return (MethodResult)getParams();
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/MethodResult.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/MethodResult.java
new file mode 100644
index 0000000000..8822e02752
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/MethodResult.java
@@ -0,0 +1,151 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.console;
+
+import java.util.Map;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.QmfData;
+import org.apache.qpid.qmf2.common.QmfException;
+
+/**
+ * The value(s) returned to the Console when the method call completes are represented by the MethodResult class.
+ * <p>
+ * The MethodResult class indicates whether the method call succeeded or not, and, on success, provides access to all
+ * data returned by the method call.
+ * <p>
+ * Returned data is provided in QmfData map indexed by the name of the parameter. The QmfData map contains only those
+ * parameters that are classified as "output" by the SchemaMethod.
+ * <p>
+ * Should a method call result in a failure, this failure is indicated by the presence of an error object in
+ * the MethodResult. This object is represented by a QmfException object, which contains a description of the
+ * reason for the failure. There are no returned parameters when a method call fails.
+ * <p>
+ * Although not part of the QMF2 API I've made MethodResult extend QmfData so we can directly access the argument
+ * or exception values of the MethodResult object, which tends to neaten up client code.
+ *
+ * @author Fraser Adams
+ */
+public final class MethodResult extends QmfData
+{
+ private QmfData _arguments = null;
+ private QmfData _exception = null;
+
+ /**
+ * The main constructor, taking a java.util.Map as a parameter. In essence it "deserialises" its state from the Map.
+ *
+ * @param m the map used to construct the MethodResult.
+ */
+ @SuppressWarnings("unchecked")
+ public MethodResult(final Map m)
+ {
+ super(m);
+ _exception = this;
+ String opcode = (m == null || !m.containsKey("qmf.opcode")) ? "none" : (String)m.get("qmf.opcode");
+ if (m.size() == 0)
+ { // Valid response from a method returning void
+ _values = m;
+ _arguments = this;
+ _exception = null;
+ }
+ else if (opcode.equals("_method_response"))
+ {
+ Map args = (Map)m.get("_arguments");
+ if (args != null)
+ {
+ _values = args;
+ _arguments = this;
+ _exception = null;
+ }
+ }
+ else if (!opcode.equals("_exception"))
+ {
+ setValue("error_text", "Invalid response received, opcode: " + opcode);
+ }
+ }
+
+ /**
+ * Return true if the method call executed without error.
+ * @return true if the method call executed without error.
+ */
+ public boolean succeeded()
+ {
+ return (_exception == null);
+ }
+
+ /**
+ * Return the QmfData error object if method fails, else null.
+ * @return the QmfData error object if method fails, else null.
+ */
+ public QmfData getException()
+ {
+ return _exception;
+ }
+
+ /**
+ * Return a map of "name"=&lt;value&gt; pairs of all returned arguments.
+ * @return a map of "name"=&lt;value&gt; pairs of all returned arguments.
+ */
+ public QmfData getArguments()
+ {
+ return _arguments;
+ }
+
+ /**
+ * Return value of argument named "name".
+ * @return value of argument named "name".
+ */
+ public Object getArgument(final String name)
+ {
+ if (_arguments == this)
+ {
+ return getValue(name);
+ }
+ return null;
+ }
+
+ /**
+ * Return a QmfException object.
+ * @return a QmfException object.
+ * <p>
+ * If the QmfData exception object contains a String property "error_text" or "message" return a QmfException object
+ * who's message is set to this value else return null;
+ */
+ public QmfException getQmfException()
+ {
+ if (_exception == this)
+ {
+ if (hasValue("error_text"))
+ {
+ return new QmfException(getStringValue("error_text"));
+ }
+
+ if (hasValue("message"))
+ {
+ return new QmfException(getStringValue("message"));
+ }
+ }
+ return null;
+ }
+}
+
+
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/ObjectUpdateWorkItem.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/ObjectUpdateWorkItem.java
new file mode 100644
index 0000000000..13a197b121
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/ObjectUpdateWorkItem.java
@@ -0,0 +1,65 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.console;
+
+import java.util.Map;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.Handle;
+import org.apache.qpid.qmf2.common.WorkItem;
+
+/**
+ * Descriptions below are taken from <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API Proposal</a>
+ * <pre>
+ * OBJECT_UPDATE: The OBJECT_UPDATE WorkItem is generated in response to an asynchronous refresh made by
+ * a QmfConsoleData object.
+ *
+ * The getParams() method of an OBJECT_UPDATE WorkItem will return a QmfConsoleData.
+ * The getHandle() method returns the reply handle provided to the refresh() method call.
+ * This handle is merely the handle used for the asynchronous response, it is not associated
+ * with the QmfConsoleData in any other way.
+ * </pre>
+ * @author Fraser Adams
+ */
+
+public final class ObjectUpdateWorkItem extends WorkItem
+{
+ /**
+ * Construct a ObjectUpdateWorkItem. Convenience constructor not in API
+ *
+ * @param handle the reply handle used to associate requests and responses
+ * @param params the QmfConsoleData used to populate the WorkItem's param
+ */
+ public ObjectUpdateWorkItem(final Handle handle, final QmfConsoleData params)
+ {
+ super(WorkItemType.OBJECT_UPDATE, handle, params);
+ }
+
+ /**
+ * Return the QmfConsoleData stored in the params.
+ * @return the QmfConsoleData stored in the params.
+ */
+ public QmfConsoleData getQmfConsoleData()
+ {
+ return (QmfConsoleData)getParams();
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/QmfConsoleData.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/QmfConsoleData.java
new file mode 100644
index 0000000000..10d764d3d0
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/QmfConsoleData.java
@@ -0,0 +1,277 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.console;
+
+// Misc Imports
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.ObjectId;
+import org.apache.qpid.qmf2.common.QmfData;
+import org.apache.qpid.qmf2.common.QmfException;
+import org.apache.qpid.qmf2.common.QmfManaged;
+import org.apache.qpid.qmf2.common.SchemaClassId;
+
+/**
+ * Subclass of QmfManaged to provide a Console specific representation of management data.
+ * <p>
+ * The Console application represents a managed data object by the QmfConsoleData class. The Console has "read only"
+ * access to the data values in the data object via this class. The Console can also invoke the methods defined by
+ * the object via this class.
+ * <p>
+ * The actual data stored in this object is cached from the Agent. In order to update the cached values,
+ * the Console invokes the instance's refresh() method.
+ * <p>
+ * Note that the refresh() and invokeMethod() methods require communication with the remote Agent. As such, they
+ * may block. For these two methods, the Console has the option of blocking in the call until the call completes.
+ * Optionally, the Console can receive a notification asynchronously when the operation is complete.
+ *
+ * @author Fraser Adams
+ */
+public class QmfConsoleData extends QmfManaged
+{
+ private final Agent _agent;
+ private long _updateTimestamp;
+ private long _createTimestamp;
+ private long _deleteTimestamp;
+
+ /**
+ * The main constructor, taking a java.util.Map as a parameter. In essence it "deserialises" its state from the Map.
+ *
+ * @param m the map used to construct the SchemaClass.
+ * @param a the Agent that manages this object.
+ */
+ public QmfConsoleData(final Map m, final Agent a)
+ {
+ super(m);
+ long currentTime = System.currentTimeMillis()*1000000l;
+ _updateTimestamp = m.containsKey("_update_ts") ? getLong(m.get("_update_ts")) : currentTime;
+ _createTimestamp = m.containsKey("_create_ts") ? getLong(m.get("_create_ts")) : currentTime;
+ _deleteTimestamp = m.containsKey("_delete_ts") ? getLong(m.get("_delete_ts")) : currentTime;
+ _agent = a;
+ }
+
+ /**
+ * Sets the state of the QmfConsoleData, used as an assignment operator.
+ *
+ * @param m the Map used to initialise the QmfConsoleData
+ */
+ @SuppressWarnings("unchecked")
+ public void initialise(final Map m)
+ {
+ Map<String, Object> values = (Map<String, Object>)m.get("_values");
+ _values = (values == null) ? m : values;
+
+ Map<String, String> subtypes = (Map<String, String>)m.get("_subtypes");
+ _subtypes = subtypes;
+
+ setSchemaClassId(new SchemaClassId((Map)m.get("_schema_id")));
+ setObjectId(new ObjectId((Map)m.get("_object_id")));
+
+ long currentTime = System.currentTimeMillis()*1000000l;
+ _updateTimestamp = m.containsKey("_update_ts") ? getLong(m.get("_update_ts")) : currentTime;
+ _createTimestamp = m.containsKey("_create_ts") ? getLong(m.get("_create_ts")) : currentTime;
+ _deleteTimestamp = m.containsKey("_delete_ts") ? getLong(m.get("_delete_ts")) : currentTime;
+ }
+
+ /**
+ * Sets the state of the QmfConsoleData, used as an assignment operator.
+ *
+ * @param rhs the QmfConsoleData used to initialise the QmfConsoleData
+ */
+ public void initialise(final QmfConsoleData rhs)
+ {
+ _values = rhs._values;
+ _subtypes = rhs._subtypes;
+ setSchemaClassId(rhs.getSchemaClassId());
+ setObjectId(rhs.getObjectId());
+ _updateTimestamp = rhs._updateTimestamp;
+ _createTimestamp = rhs._createTimestamp;
+ _deleteTimestamp = rhs._deleteTimestamp;
+ }
+
+ /**
+ * Return a list of timestamps describing the lifecycle of the object.
+ * @return a list of timestamps describing the lifecycle of the object.
+ * <p>
+ * All timestamps are represented by the AMQP timestamp type recorded in nanoseconds since the epoch.
+ * <pre>
+ * [0] = time of last update from Agent,
+ * [1] = creation timestamp
+ * [2] = deletion timestamp, or zero if not deleted.
+ * </pre>
+ */
+ public final long[] getTimestamps()
+ {
+ long[] timestamps = {_updateTimestamp, _createTimestamp, _deleteTimestamp};
+ return timestamps;
+ }
+
+ /**
+ * Return the creation timestamp.
+ * @return the creation timestamp. Timestamps are recorded in nanoseconds since the epoch.
+ */
+ public final long getCreateTime()
+ {
+ return _createTimestamp;
+ }
+
+ /**
+ * Return the update timestamp.
+ * @return the update timestamp. Timestamps are recorded in nanoseconds since the epoch.
+ */
+ public final long getUpdateTime()
+ {
+ return _updateTimestamp;
+ }
+
+ /**
+ * Return the deletion timestamp, or zero if not deleted.
+ * @return the deletion timestamp, or zero if not deleted. Timestamps are recorded in nanoseconds since the epoch.
+ */
+ public final long getDeleteTime()
+ {
+ return _deleteTimestamp;
+ }
+
+ /**
+ * Return true if deletion timestamp not zero.
+ * @return true if deletion timestamp not zero.
+ */
+ public final boolean isDeleted()
+ {
+ return getDeleteTime() != 0;
+ }
+
+ /**
+ * Request that the Agent updates the value of this object's contents.
+ */
+ public final void refresh() throws QmfException
+ {
+ refresh(-1);
+ }
+
+ /**
+ * Request that the Agent updates the value of this object's contents.
+ *
+ * @param timeout the maximum time in seconds to wait for a response, overrides default replyTimeout.
+ */
+ public final void refresh(final int timeout) throws QmfException
+ {
+ if (_agent == null)
+ {
+ throw new QmfException("QmfConsoleData.refresh() called with null Agent");
+ }
+ QmfConsoleData newContents = _agent.refresh(getObjectId(), null, timeout);
+ if (newContents == null)
+ {
+ _deleteTimestamp = System.currentTimeMillis()*1000000l;
+ }
+ else
+ {
+ // Save the original values of create and delete timestamps as the ManagementAgent doesn't return
+ // these on a call to getObjects(ObjectId);
+ long createTimestamp = _createTimestamp;
+ long deleteTimestamp = _deleteTimestamp;
+ initialise(newContents);
+ _createTimestamp = createTimestamp;
+ _deleteTimestamp = deleteTimestamp;
+ }
+ }
+
+ /**
+ * Request that the Agent updates the value of this object's contents asynchronously.
+ *
+ * @param replyHandle the correlation handle used to tie asynchronous refresh requests with responses.
+ */
+ public final void refresh(final String replyHandle) throws QmfException
+ {
+ if (_agent == null)
+ {
+ throw new QmfException("QmfConsoleData.refresh() called with null Agent");
+ }
+ _agent.refresh(getObjectId(), replyHandle, -1);
+ }
+
+ /**
+ * Invoke the named method on this instance.
+ *
+ * @param name name of the method to invoke.
+ * @param inArgs inArgs an unordered set of key/value pairs comprising the method arguments.
+ * @return the MethodResult.
+ */
+ public final MethodResult invokeMethod(final String name, final QmfData inArgs) throws QmfException
+ {
+ if (_agent == null)
+ {
+ throw new QmfException("QmfConsoleData.invokeMethod() called with null Agent");
+ }
+ return _agent.invokeMethod(getObjectId(), name, inArgs, -1);
+ }
+
+ /**
+ * Invoke the named method on this instance.
+ *
+ * @param name name of the method to invoke.
+ * @param inArgs inArgs an unordered set of key/value pairs comprising the method arguments.
+ * @param timeout the maximum time in seconds to wait for a response, overrides default replyTimeout.
+ * @return the MethodResult.
+ */
+ public final MethodResult invokeMethod(final String name, final QmfData inArgs, final int timeout) throws QmfException
+ {
+ if (_agent == null)
+ {
+ throw new QmfException("QmfConsoleData.invokeMethod() called with null Agent");
+ }
+ return _agent.invokeMethod(getObjectId(), name, inArgs, timeout);
+ }
+
+ /**
+ * Invoke the named method asynchronously on this instance.
+ *
+ * @param name name of the method to invoke.
+ * @param inArgs inArgs an unordered set of key/value pairs comprising the method arguments.
+ * @param replyHandle the correlation handle used to tie asynchronous method requests with responses.
+ */
+ public final void invokeMethod(final String name, final QmfData inArgs, final String replyHandle) throws QmfException
+ {
+ if (_agent == null)
+ {
+ throw new QmfException("QmfConsoleData.invokeMethod() called with null Agent");
+ }
+ _agent.invokeMethod(getObjectId(), name, inArgs, replyHandle);
+ }
+
+ /**
+ * Helper/debug method to list the QMF Object properties and their type.
+ */
+ @Override
+ public void listValues()
+ {
+ super.listValues();
+ System.out.println("_create_ts: " + new Date(getCreateTime()/1000000l));
+ System.out.println("_update_ts: " + new Date(getUpdateTime()/1000000l));
+ System.out.println("_delete_ts: " + new Date(getDeleteTime()/1000000l));
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscribeIndication.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscribeIndication.java
new file mode 100644
index 0000000000..8277e3c0e1
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscribeIndication.java
@@ -0,0 +1,66 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.console;
+
+import java.util.List;
+
+/**
+ * Holds the result of a subscription data indication from the Agent.
+ *
+ * @author Fraser Adams
+ */
+public final class SubscribeIndication
+{
+ private final String _consoleHandle;
+ private final List<QmfConsoleData> _data;
+
+ /**
+ * Construct a SubscribeIndication from a consoleHandle and list of QmfConsoleData.
+ * @param consoleHandle the handle containing the correlation ID.
+ * @param data the list of QmfConsoleData to pass to the Console application.
+ */
+ public SubscribeIndication(final String consoleHandle, final List<QmfConsoleData> data)
+ {
+ _consoleHandle = consoleHandle;
+ _data = data;
+ }
+
+ /**
+ * Return the console handle as passed to the createSubscription() call.
+ * @return the console handle as passed to the createSubscription() call.
+ */
+ public String getConsoleHandle()
+ {
+ return _consoleHandle;
+ }
+
+ /**
+ * Return a list containing all updated QmfData objects associated with the Subscripion.
+ * @return a list containing all updated QmfData objects associated with the Subscripion.
+ */
+ public List<QmfConsoleData> getData()
+ {
+ return _data;
+ }
+}
+
+
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscribeParams.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscribeParams.java
new file mode 100644
index 0000000000..2e15a3153b
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscribeParams.java
@@ -0,0 +1,130 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.console;
+
+import java.util.Map;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.QmfData;
+
+/**
+ * Holds the result of a subscription request made by this Console.
+ * <p>
+ * The SubscriptionId object must be used when the subscription is refreshed or cancelled - it must be passed to the
+ * Console's refreshSubscription() and cancel_subscription() methods. The value of the SubscriptionId does not
+ * change over the lifetime of the subscription.
+ * <p>
+ * The console handle will be provided by the Agent on each data indication event that corresponds to this subscription.
+ * It should not change for the lifetime of the subscription.
+ * <p>
+ * The getHandle() method returns the reply handle provided to the createSubscription() method call. This handle
+ * is merely the handle used for the asynchronous response, it is not associated with the subscription in any other way.
+ * <p>
+ * Once a subscription is created, the Agent that maintains the subscription will periodically issue updates for the
+ * subscribed data. This update will contain the current values of the subscribed data, and will appear as the first
+ * SUBSCRIPTION_INDICATION WorkItem for this subscription.
+ *
+ * @author Fraser Adams
+ */
+public final class SubscribeParams extends QmfData
+{
+ private String _consoleHandle;
+
+ /**
+ * Construct SubscribeParams from a consoleHandle and the Map encoded representation.
+ * @param consoleHandle the console handle as passed to the createSubscription() call.
+ * @param m a Map containing the Map encoded representation of this SubscribeParams.
+ */
+ public SubscribeParams(final String consoleHandle, final Map m)
+ {
+ super(m);
+ _consoleHandle = consoleHandle;
+ }
+
+ /**
+ * If the subscription is successful, this method returns a SubscriptionId object.
+ * Should the subscription fail, this method returns null, and getError() can be used to obtain an
+ * application-specific QmfData error object.
+ *
+ * @return a SubscriptionId object.
+ */
+ public String getSubscriptionId()
+ {
+ if (hasValue("_subscription_id"))
+ {
+ return getStringValue("_subscription_id");
+ }
+ return null;
+ }
+
+ /**
+ * Return the time interval in seconds on which the Agent will publish updates for this subscription.
+ * @return the time interval in seconds on which the Agent will publish updates for this subscription.
+ */
+ public long getPublishInterval()
+ {
+ return getLongValue("_interval");
+ }
+
+ /**
+ * Return the lifetime in seconds for the subscription.
+ * @return the lifetime in seconds for the subscription. The subscription will automatically expire after
+ * this interval if not renewed by the console.
+ */
+ public long getLifetime()
+ {
+ return getLongValue("_duration");
+ }
+
+ /**
+ * Return the QmfData error object if method fails, else null.
+ * @return the QmfData error object if method fails, else null.
+ */
+ public QmfData getError()
+ {
+ if (getSubscriptionId() == null)
+ {
+ return this;
+ }
+ return null;
+ }
+
+ /**
+ * Sets the consoleHandle.
+ * @param consoleHandle the new console handle.
+ */
+ public void setConsoleHandle(final String consoleHandle)
+ {
+ _consoleHandle = consoleHandle;
+ }
+
+ /**
+ * Return the console handle as passed to the createSubscription() call.
+ * @return the console handle as passed to the createSubscription() call.
+ */
+ public String getConsoleHandle()
+ {
+ return _consoleHandle;
+ }
+}
+
+
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscribeResponseWorkItem.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscribeResponseWorkItem.java
new file mode 100644
index 0000000000..968ef0a6a2
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscribeResponseWorkItem.java
@@ -0,0 +1,80 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.console;
+
+import java.util.Map;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.Handle;
+import org.apache.qpid.qmf2.common.WorkItem;
+
+/**
+ * Descriptions below are taken from <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API Proposal</a>
+ * <pre>
+ * SUBSCRIBE_RESPONSE: The SUBSCRIBE_RESPONSE WorkItem returns the result of a subscription request made by
+ * this Console. This WorkItem is generated when the Console's createSubscription() is
+ * called in an asychronous manner, rather than pending for the result.
+ *
+ * The getParams() method of a SUBSCRIBE_RESPONSE WorkItem will return an instance of the
+ * SubscribeParams class.
+ *
+ * The SubscriptionId object must be used when the subscription is refreshed or cancelled.
+ * It must be passed to the Console's refresh_subscription() and cancelSubscription() methods.
+ * The value of the SubscriptionId does not change over the lifetime of the subscription.
+ *
+ * The console handle will be provided by the Agent on each data indication event that
+ * corresponds to this subscription. It should not change for the lifetime of the subscription.
+ *
+ * The getHandle() method returns the reply handle provided to the createSubscription()
+ * method call. This handle is merely the handle used for the asynchronous response, it is
+ * not associated with the subscription in any other way.
+ *
+ * Once a subscription is created, the Agent that maintains the subscription will periodically
+ * issue updates for the subscribed data. This update will contain the current values of the
+ * subscribed data, and will appear as the first SUBSCRIPTION_INDICATION WorkItem for this
+ * subscription.
+ * </pre>
+ * @author Fraser Adams
+ */
+
+public final class SubscribeResponseWorkItem extends WorkItem
+{
+ /**
+ * Construct a SubscribeResponseWorkItem. Convenience constructor not in API
+ *
+ * @param handle the reply handle used to associate requests and responses
+ * @param params the SubscribeParams used to populate the WorkItem's param
+ */
+ public SubscribeResponseWorkItem(final Handle handle, final SubscribeParams params)
+ {
+ super(WorkItemType.SUBSCRIBE_RESPONSE, handle, params);
+ }
+
+ /**
+ * Return the SubscribeParams stored in the params.
+ * @return the SubscribeParams stored in the params.
+ */
+ public SubscribeParams getSubscribeParams()
+ {
+ return (SubscribeParams)getParams();
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscriptionIndicationWorkItem.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscriptionIndicationWorkItem.java
new file mode 100644
index 0000000000..d9123e9a2c
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscriptionIndicationWorkItem.java
@@ -0,0 +1,62 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.console;
+
+import java.util.Map;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.Handle;
+import org.apache.qpid.qmf2.common.WorkItem;
+
+/**
+ * Descriptions below are taken from <a href=https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal>QMF2 API Proposal</a>
+ * <pre>
+ * SUBSCRIPTION_INDICATION: The SUBSCRIPTION_INDICATION WorkItem signals the arrival of an update to subscribed
+ * data from the Agent.
+ *
+ * The getParams() method of a SUBSCRIPTION_INDICATION WorkItem will return an instance
+ * of the SubscribeIndication class. The getHandle() method returns null.
+ * </pre>
+ * @author Fraser Adams
+ */
+
+public final class SubscriptionIndicationWorkItem extends WorkItem
+{
+ /**
+ * Construct a SubscriptionIndicationWorkItem. Convenience constructor not in API
+ *
+ * @param params the SubscribeParams used to populate the WorkItem's param
+ */
+ public SubscriptionIndicationWorkItem(final SubscribeIndication params)
+ {
+ super(WorkItemType.SUBSCRIPTION_INDICATION, null, params);
+ }
+
+ /**
+ * Return the SubscribeIndication stored in the params.
+ * @return the SubscribeIndication stored in the params.
+ */
+ public SubscribeIndication getSubscribeIndication()
+ {
+ return (SubscribeIndication)getParams();
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscriptionManager.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscriptionManager.java
new file mode 100644
index 0000000000..69889d85de
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscriptionManager.java
@@ -0,0 +1,255 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.console;
+
+// Simple Logging Facade 4 Java
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+// Misc Imports
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TimerTask;
+
+// QMF2 Imports
+import org.apache.qpid.qmf2.common.QmfQuery;
+
+/**
+ * A SubscriptionManager represents a running Subscription on the Console.
+ * <p>
+ * The main reason we have SubscriptionManagers as TimerTasks is to enable proper cleanup of the references stored in
+ * the subscriptionByHandle and subscriptionById Maps. Ideally these will be cleaned up by a client calling
+ * cancelSubscription but we can't rely on that as the client may forget or the Agent may not respond.
+ * <p>
+ * The SubscriptionManager acts like a client/Console side representation of a Subscription running on an Agent.
+ * As mentioned above its primary purpose is to enable references to Subscriptions maintained by the Console to
+ * be cleaned up should the Subscription time out rather than being cancelled, however as a side effect it is
+ * used to enable emulation of Subscriptions to the broker ManagementAgent, which does not yet natively implement
+ * Subscription.
+ * <p>
+ * To emulate Subscriptions the Console receives the periodic _data indications pushed by the ManagementAgent. The
+ * Console then iterates through Subscriptions referencing the broker Agent and evaluates their queries against
+ * the QmfConsoleData returned by the _data indication. Any QmfConsoleData that match the query are passed to the
+ * client application with the consoleHandle of the matching Subscription.
+ * <p>
+ * The following diagram illustrates the Subscription relationships with the Console and local Agent proxy.
+ * <p>
+ * <img alt="" src="doc-files/Subscriptions.png">
+ *
+ * @author Fraser Adams
+ */
+public final class SubscriptionManager extends TimerTask
+{
+ private static final Logger _log = LoggerFactory.getLogger(SubscriptionManager.class);
+
+ private final Agent _agent;
+ private long _startTime = System.currentTimeMillis();
+ private String _subscriptionId;
+ private String _consoleHandle;
+ private String _replyHandle;
+ private QmfQuery _query;
+ private long _duration = 0;
+ private long _interval = 0;
+ private boolean _waiting = true;
+
+ /**
+ * Construct a Console side proxy of a Subscription. Primarily to manage references to the Subscription.
+ *
+ * @param agent the Agent from which the Subscription has been requested
+ * @param query the QmfQuery that the Subscription will run
+ * @param consoleHandle the handle that uniquely identifies the Subscription
+ * @param interval the interval between subscription updates
+ * @param duration the duration of the subscription (assuming it doesn't get refreshed)
+ */
+ SubscriptionManager(final Agent agent, final QmfQuery query, final String consoleHandle,
+ final String replyHandle, final long interval, final long duration)
+ {
+ _agent = agent;
+ _query = query;
+ _consoleHandle = consoleHandle;
+ _replyHandle = replyHandle;
+ _interval = interval;
+ _duration = duration;
+ _log.debug("Creating SubscriptionManager {}, on Agent {}",_consoleHandle, _agent.getName());
+ }
+
+ /**
+ * This method gets called periodically by the Timer scheduling this TimerTask.
+ * <p>
+ * First a check is made to see if the Subscription has expired, if it has then it is cancelled.
+ */
+ public void run()
+ {
+ long elapsed = (long)Math.round((System.currentTimeMillis() - _startTime)/1000.0f);
+ if (elapsed >= _duration || !_agent.isActive())
+ {
+ _log.debug("Subscription {} has expired, removing", _subscriptionId);
+ // The Subscription has expired so cancel it
+ cancel();
+ }
+ }
+
+ /**
+ * Causes the current thread to wait until it is signalled or times out.
+ * <p>
+ * This method is primarily used as a means to enable a synchronous call to createSubscription().
+ * For most synchronous calls we simply use the receive() call on the synchronous session, but we can't do that
+ * for createSubscription() as we specifically need to use the replyTo on the asynchronous session as once
+ * subscriptions are created the results are asynchronously pushed. This means we have to get the response to
+ * createSession() on the asynchronous replyTo then signal the (blocked) main thread that the response has
+ * been received.
+ *
+ * @param timeout the maximum time to wait to be signalled.
+ */
+ public synchronized void await(final long timeout)
+ {
+ while (_waiting)
+ {
+ long _startTime = System.currentTimeMillis();
+ try
+ {
+ wait(timeout);
+ }
+ catch (InterruptedException ie)
+ {
+ continue;
+ }
+ // Measure elapsed time to test against spurious wakeups and ensure we really have timed out
+ long elapsedTime = (System.currentTimeMillis() - _startTime);
+ if (elapsedTime >= timeout)
+ {
+ break;
+ }
+ }
+ _waiting = true;
+ }
+
+ /**
+ * Wakes up all waiting threads.
+ */
+ public synchronized void signal()
+ {
+ _waiting = false;
+ notifyAll();
+ }
+
+ /**
+ * Refresh the subscription by zeroing its elapsed time.
+ */
+ public void refresh()
+ {
+ _log.debug("Refreshing Subscription {}", _subscriptionId);
+ _startTime = System.currentTimeMillis();
+ }
+
+ /**
+ * Cancel the Subscription, tidying references up and cancelling the TimerTask.
+ */
+ @Override
+ public boolean cancel()
+ {
+ _log.debug("Cancelling Subscription {}, {}", _consoleHandle, _subscriptionId);
+ _agent.removeSubscription(this);
+ signal(); // Just in case anything is blocking on this Subscription.
+ return super.cancel(); // Cancel the TimerTask
+ }
+
+ /**
+ * Set the SubscriptionId.
+ * @param subscriptionId the new SubscriptionId of this Subscription.
+ */
+ public void setSubscriptionId(final String subscriptionId)
+ {
+ _subscriptionId = subscriptionId;
+ }
+
+ /**
+ * return the SubscriptionId of this Subscription.
+ * @return the SubscriptionId of this Subscription.
+ */
+ public String getSubscriptionId()
+ {
+ return _subscriptionId;
+ }
+
+ /**
+ * Return the consoleHandle of this Subscription.
+ * @return the consoleHandle of this Subscription.
+ */
+ public String getConsoleHandle()
+ {
+ return _consoleHandle;
+ }
+
+ /**
+ * Return the replyHandle of this Subscription.
+ * @return the replyHandle of this Subscription.
+ */
+ public String getReplyHandle()
+ {
+ return _replyHandle;
+ }
+
+ /**
+ * Return the Agent running this Subscription.
+ * @return the Agent running this Subscription.
+ */
+ public Agent getAgent()
+ {
+ return _agent;
+ }
+
+ /**
+ * Set the Subscription lifetime in seconds.
+ *
+ * @param duration the new Subscription lifetime in seconds
+ */
+ public void setDuration(final long duration)
+ {
+ _duration = duration;
+ }
+
+ /**
+ * Return The Subscription's QmfQuery.
+ * @return The Subscription's QmfQuery.
+ */
+ public QmfQuery getQuery()
+ {
+ return _query;
+ }
+
+ /**
+ * Create a Map encoded version.
+ * <p>
+ * When we do a synchronous createSubscription the Subscription itself holds the info needed to populate
+ * the SubscriptionParams result. We encode the info in a Map to pass to the SubscribeParams Constructor
+ */
+ public Map<String, Object> mapEncode()
+ {
+ Map<String, Object> map = new HashMap<String, Object>();
+ map.put("_interval", _interval);
+ map.put("_duration", _duration);
+ map.put("_subscription_id", _subscriptionId);
+ return map;
+ }
+}
+
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/doc-files/Console.png b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/doc-files/Console.png
new file mode 100644
index 0000000000..cb2a5ee800
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/doc-files/Console.png
Binary files differ
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/doc-files/QmfEventListenerModel.png b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/doc-files/QmfEventListenerModel.png
new file mode 100644
index 0000000000..26a5f71b56
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/doc-files/QmfEventListenerModel.png
Binary files differ
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/doc-files/Subscriptions.png b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/doc-files/Subscriptions.png
new file mode 100644
index 0000000000..01772c6d45
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/doc-files/Subscriptions.png
Binary files differ
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/doc-files/WorkQueueEventModel.png b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/doc-files/WorkQueueEventModel.png
new file mode 100644
index 0000000000..fc2a722985
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/doc-files/WorkQueueEventModel.png
Binary files differ
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/util/ConnectionHelper.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/util/ConnectionHelper.java
new file mode 100644
index 0000000000..b5655dda29
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/util/ConnectionHelper.java
@@ -0,0 +1,862 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.util;
+
+// JMS Imports
+import javax.jms.ConnectionFactory;
+import javax.jms.Connection;
+import javax.jms.JMSException;
+
+// JNDI Imports
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+// Simple Logging Facade 4 Java
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+// Misc Imports
+import java.util.Map;
+import java.util.Properties;
+
+// Reuse this class as it provides a handy mechanism to parse an options String into a Map
+import org.apache.qpid.messaging.util.AddressParser;
+
+/**
+ * The Qpid M4 Java and C++ clients and the Python QMF tools all use different URL formats.
+ * This class provides helper methods to support a variety of URL formats and connection options
+ * in order to provide flexibility when creating connections.
+ * <p>
+ * Much of the following information is taken from <a href="https://cwiki.apache.org/qpid/url-format-proposal.html">
+ * New URL format for AMQP + Qpid</a>
+ * <p>
+ * <h3>AMQP 0-10 format</h3>
+ * C++ uses the AMQP 0-10 format: section 9.1.2 as follows:
+ * <pre>
+ * amqp_url = "amqp:" prot_addr_list
+ * prot_addr_list = [prot_addr ","]* prot_addr
+ * prot_addr = tcp_prot_addr | tls_prot_addr
+ *
+ * tcp_prot_addr = tcp_id tcp_addr
+ * tcp_id = "tcp:" | ""
+ * tcp_addr = [host [":" port] ]
+ * host = &lt;as per <a href="http://www.ietf.org/rfc/rfc3986.txt">rfc3986</a>&gt;
+ * port = number
+ * </pre>
+ * The AMQP 0-10 format only provides protocol address information for a (list of) brokers.
+ * <p>
+ * <p>
+ *
+ * <h3>Python tool BrokerURL format</h3>
+ * The Python tools bundled with Qpid such as qpid-config use a "BrokerURL" format with the following Address syntax:
+ * <pre>
+ * [&lt;user&gt;/&lt;pass&gt;@]&lt;hostname&gt; | &lt;ip-address&gt;[:&lt;port&gt;]
+ * </pre>
+ *
+ * <h3>Qpid M4 Java Connection URL format</h3>
+ * The Qpid M4 Java format provides additional options for connection options (user, password, vhost etc.)
+ * Documentation for this format may be found here: <a href="https://cwiki.apache.org/qpid/connection-url-format.html">
+ * Qpid M4 Java Connection URL Format</a>
+ * <p>
+ * Java ConnectionURLs look like this:
+ * <pre>
+ * amqp://[&lt;user&gt;:&lt;pass&gt;@][&lt;clientid&gt;]/&lt;virtualhost&gt;[?&lt;option&gt;='&lt;value&gt;'[&amp;&lt;option&gt;='&lt;value&gt;']]
+ * </pre>
+ * This syntax is very powerful, but it can also be fairly complex to work with, especially when one realises
+ * that one of the options in the above syntax is brokerlist='&lt;broker url&gt;' where broker url is itself a URL
+ * of the format:
+ * <pre>
+ * &lt;transport&gt;://&lt;host&gt;[:&lt;port&gt;][?&lt;option&gt;='&lt;value&gt;'[&amp;&lt;option&gt;='&lt;value&gt;']]
+ * </pre>
+ * so one may see ConnectionURLs that look like:
+ * <pre>
+ * {@literal amqp://guest:guest@clientid/test?brokerlist='tcp://localhost:5672?retries='10'&connectdelay='1000''}
+ * </pre>
+ *
+ * <p>
+ * <p>
+ * <h3>Extended AMQP 0-10 URL format</h3>
+ * There is a proposal to extend the AMQP 0-10 URL syntax to include user:pass@ style authentication
+ * information, virtual host and extensible name/value options. It also makes the implied extension points of
+ * the original grammar more explicit.
+ * <pre>
+ * amqp_url = "amqp://" [ userinfo "@" ] addr_list [ vhost ]
+ * addr_list = addr *( "," addr )
+ * addr = prot_addr [ options ]
+ * prot_addr = tcp_prot_addr | other_prot_addr
+ * vhost = "/" *pchar [ options ]
+ *
+ * tcp_prot_addr = tcp_id tcp_addr
+ * tcp_id = "tcp:" / "" ; tcp is the default
+ * tcp_addr = [ host [ ":" port ] ]
+ *
+ * other_prot_addr = other_prot_id ":" *pchar
+ * other_prot_id = scheme
+ *
+ * options = "?" option *( ";" option )
+ * option = name "=" value
+ * name = *pchar
+ * value = *pchar
+ * </pre>
+ *
+ * <h3>Incompatibility with AMQP 0-10 format</h3>
+ * This syntax is backward compatible with AMQP 0-10 with one exception: AMQP 0-10 did not have an initial
+ * // after amqp: The justification was that that the // form is only used for URIs with hierarchical structure
+ * <p>
+ * However it's been pointed out that in fact the URL does already specify a 1-level hierarchy of address / vhost.
+ * In the future the hierarchy could be extended to address objects within a vhost such as queues, exchanges etc.
+ * So this proposal adopts amqp:// syntax.
+ * <p>
+ * It's easy to write a backward-compatible parser by relaxing the grammar as follows:
+ * <pre>
+ * amqp_url = "amqp:" [ "//" ] [ userinfo "@" ] addr_list [ vhost ]
+ * </pre>
+ *
+ * <h3>Differences from Qpid M4 Java Connection URL format</h3>
+ * Addresses are at the start of the URL rather than in the "brokerlist" option.
+ * <p>
+ * Option format is {@literal ?foo=bar;x=y } rather than {@literal ?foo='bar'&x='y'}. The use of "'" quotes is not common for URI query
+ * strings. The use of "&amp;" as a separator creates problems
+ * <p>
+ * user, pass and clientid are options rather than having a special place at the front of the URL. clientid is
+ * a Qpid proprietary property and user/pass are not relevant in all authentication schemes.
+ * <p>
+ * Qpid M4 Java URLs requires the brokerlist option, so this is an easy way to detect a Qpid M4 URL vs. an
+ * Extended AMQP 0-10 URL and parse accordingly.
+ *
+ * <h3>Options</h3>
+ * Some of the URL forms are fairly limited in terms of options, so it is useful to be able to pass options as
+ * an additional string, though it's important to note that if multiple brokers are supplied in the AMQP 0.10 format
+ * the same options will be applied to all brokers.
+ * <p>
+ * The option format is the same as that of the C++ qpid::messaging Connection class. for example: "{reconnect: true,
+ * tcp-nodelay: true}":
+ * <p>
+ * <table summary="Connection Options" width="100%" border="1"><thead>
+ * <tr><th>option name</th><th>value type</th><th>semantics</th></tr></thead><tbody>
+ * <tr>
+ * <td><code class="literal">maxprefetch</code></td>
+ * <td>integer</td>
+ * <td>The maximum number of pre-fetched messages per destination.</td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">sync_publish</code></td>
+ * <td>{'persistent' | 'all'}</td>
+ * <td>A sync command is sent after every persistent message to guarantee that it has been received; if the
+ * value is 'persistent', this is done only for persistent messages.</td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">sync_ack</code></td>
+ * <td>boolean</td>
+ * <td>A sync command is sent after every acknowledgement to guarantee that it has been received.</td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">use_legacy_map_msg_format</code></td>
+ * <td>boolean</td>
+ * <td>If you are using JMS Map messages and deploying a new client with any JMS client older than 0.8 release,
+ * you must set this to true to ensure the older clients can understand the map message encoding.</td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">failover</code></td>
+ * <td>{'roundrobin' | 'singlebroker' | 'nofailover' | 'failover_exchange'}</td>
+ * <td>If roundrobin is selected it will try each broker given in the broker list. If failover_exchange is
+ * selected it connects to the initial broker given in the broker URL and will receive membership updates
+ * via the failover exchange. </td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">cyclecount</code></td>
+ * <td>integer</td>
+ * <td>For roundrobin failover cyclecount is the number of times to loop through the list of available brokers
+ * before failure.</td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">username</code></td>
+ * <td>string</td>
+ * <td>The username to use when authenticating to the broker.</td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">password</code></td>
+ * <td>string</td>
+ * <td>The password to use when authenticating to the broker.</td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">sasl_mechanisms</code></td>
+ * <td>string</td>
+ * <td>The specific SASL mechanisms to use when authenticating to the broker. The value is a space separated list.</td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">sasl_mechs</code></td>
+ * <td>string</td>
+ * <td>The specific SASL mechanisms to use when authenticating to the broker. The value is a space separated
+ * is a space separated list. This is simply a synonym for sasl_mechanisms above</td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">sasl_encryption</code></td>
+ * <td>boolean</td>
+ * <td>If <code class="literal">sasl_encryption='true'</code>, the JMS client attempts to negotiate a security
+ * layer with the broker using GSSAPI to encrypt the connection. Note that for this to happen, GSSAPI must
+ * be selected as the sasl_mech.</td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">ssl</code></td>
+ * <td>boolean</td>
+ * <td>If <code class="literal">ssl='true'</code>, the JMS client will encrypt the connection using SSL.</td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">reconnect</code></td>
+ * <td>boolean</td>
+ * <td>Transparently reconnect if the connection is lost.</td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">reconnect_timeout</code></td>
+ * <td>integer</td>
+ * <td>Total number of seconds to continue reconnection attempts before giving up and raising an exception.</td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">reconnect_limit</code></td>
+ * <td>integer</td>
+ * <td>Maximum number of reconnection attempts before giving up and raising an exception.</td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">reconnect_interval_min</code></td>
+ * <td>integer representing time in seconds</td>
+ * <td> Minimum number of seconds between reconnection attempts. The first reconnection attempt is made
+ * immediately; if that fails, the first reconnection delay is set to the value of <code class="literal">
+ * reconnect_interval_min</code>; if that attempt fails, the reconnect interval increases exponentially
+ * until a reconnection attempt succeeds or <code class="literal">reconnect_interval_max</code> is reached.</td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">reconnect_interval_max</code></td>
+ * <td>integer representing time in seconds</td>
+ * <td>Maximum reconnect interval.</td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">reconnect_interval</code></td>
+ * <td>integer representing time in seconds</td>
+ * <td>Sets both <code class="literal">reconnection_interval_min</code> and <code class="literal">
+ * reconnection_interval_max</code> to the same value. The default value is 5 seconds</td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">heartbeat</code></td>
+ * <td>integer representing time in seconds</td>
+ * <td>Requests that heartbeats be sent every N seconds. If two successive heartbeats are missed the connection is
+ * considered to be lost.</td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">protocol</code></td>
+ * <td>string</td>
+ * <td>Sets the underlying protocol used. The default option is 'tcp'. To enable ssl, set to 'ssl'. The C++ client
+ * additionally supports 'rdma'. </td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">tcp-nodelay</code></td>
+ * <td>boolean</td>
+ * <td>Set tcp no-delay, i.e. disable Nagle algorithm.</td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">sasl_protocol</code></td>
+ * <td>string</td>
+ * <td>Used only for Kerberos. <code class="literal">sasl_protocol</code> must be set to the principal for the
+ * qpidd broker, e.g. <code class="literal">qpidd/</code></td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">sasl_server</code></td>
+ * <td>string</td>
+ * <td>For Kerberos, sasl_mechs must be set to GSSAPI, <code class="literal">sasl_server</code> must be set to
+ * the host for the SASL server, e.g. <code class="literal">sasl.com.</code></td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">trust_store</code></td>
+ * <td>string</td>
+ * <td>path to Keberos trust store</td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">trust_store_password</code></td>
+ * <td>string</td>
+ * <td>Kerberos trust store password</td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">key_store</code></td>
+ * <td>string</td>
+ * <td>path to Kerberos key store </td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">key_store_password</code></td>
+ * <td>string</td>
+ * <td>Kerberos key store password</td>
+ * </tr>
+ * <tr>
+ * <td><code class="literal">ssl_cert_alias</code></td>
+ * <td>string</td>
+ * <td>If multiple certificates are present in the keystore, the alias will be used to extract the correct
+ * certificate.</td>
+ * </tr>
+ * </tbody></table>
+ *
+ * <h3>Other features of this class</h3>
+ * Whilst it is generally the norm to use JNDI to specify Connections, Destinations etc. it is also often quite useful
+ * to specify Connections programmatically, for example when writing a tool one may wish to specify the broker via the
+ * command line to enable the tool to connect to different broker instances.
+ * To facilitate this this class provides a basic createConnection() method that takes a URL and returns a JMS
+ * Connection.
+ *
+ * @author Fraser Adams
+ */
+public final class ConnectionHelper
+{
+ private static final Logger _log = LoggerFactory.getLogger(ConnectionHelper.class);
+
+ /**
+ * Make constructor private as the class comprises only static helper methods.
+ */
+ private ConnectionHelper()
+ {
+ }
+
+ /**
+ * Create a ConnectionURL from the proposed Extended AMQP 0.10 URL format. This is experimental and may or may
+ * not work. Options are assumed to be the same as the Java Connection URL, which will probably not be the case
+ * if this URL form is ultimately adopted. For example the example URLs have "amqp://host1,host2?retry=2,host3"
+ * whereas the Java Connection URL uses &retries=2
+ *
+ * I'm not overly keen on this code it looks pretty inelegant and I'm slightly embarrassed by it, but it
+ * is really just an experiment.
+ *
+ * @param url the input URL.
+ * @param username the username.
+ * @param password the password.
+ * @return a String containing the Java Connection URL.
+ */
+ private static String parseExtendedAMQPURL(String url, String username, String password)
+ {
+ String vhost = ""; // Specifying an empty vhost uses default Virtual Host.
+ String urlOptions = "";
+ String brokerList = "";
+
+ url = url.substring(7); // Chop off "amqp://"
+ String[] split = url.split("@"); // First break out the userinfo if present
+ String remainder = split[0];
+ if (split.length == 2)
+ { // Extract username and password from the userinfo field
+ String[] userinfo = split[0].split(":");
+ remainder = split[1];
+
+ username = userinfo[0];
+ if (userinfo.length == 2)
+ {
+ password = userinfo[1];
+ }
+ }
+
+ // Replace foo=baz with foo='baz'. There's probably a more elegant way to do this using a fancy
+ // regex, but unfortunately I'm not terribly good at regexes so this is the brute force approach :-(
+ // OTOH it's probably more readable and obvious than a regex to do the same thing would be.
+ split = remainder.split("=");
+ StringBuilder buf = new StringBuilder(split[0]);
+ for (int i = 1; i < split.length; i++)
+ {
+ String substring = "='" + split[i];
+ if (substring.contains(";"))
+ {
+ substring = substring.replaceFirst(";", "'&"); // Note we also replace the option separator here
+ }
+ else if (substring.contains("/"))
+ {
+ substring = substring.replaceFirst("/", "'/");
+ }
+ else if (substring.contains(","))
+ {
+ substring = substring.replaceFirst(",", "',");
+ }
+ else
+ {
+ substring = substring + "'";
+ }
+ buf.append(substring);
+ }
+ remainder = buf.toString();
+
+ // Now split into addrList and vhost parts (see Javadoc for the grammar of this URL format)
+ split = remainder.split("/"); // vhost starts with a mandatory '/' character
+ String[] addrSplit = split[0].split(","); // prot_addrs are comma separated
+ boolean firstBroker = true;
+ buf = new StringBuilder();
+ for (String broker : addrSplit)
+ { // Iterate through the address list creating brokerList style URLs
+ broker = broker.trim();
+ String protocol = "tcp"; // set default protocol
+ String[] components = broker.split(":");
+
+ // Note protocols other than tcp and vm are not supported by the Connection URL so the results
+ // are pretty much undefined if other protocols are passed on the input URL.
+ if (components.length == 1)
+ { // Assume protocol = tcp and broker = hostname
+ }
+ else if (components.length == 2)
+ { // Probably host:port but could be some other protocol in and Extended AMQP 0.10 URL
+ try
+ { // Somewhat ugly, but effective test to check if the second component is an integer
+ Integer.parseInt(components[1]);
+ // If the above succeeds the components are likely host:port
+ broker = components[0] + ":" + components[1];
+ }
+ catch (NumberFormatException nfe)
+ { // If the second component isn't an integer then it's likely a wacky protocol...
+ protocol = components[0];
+ broker = components[1];
+ }
+ }
+ else if (components.length == 3)
+ {
+ protocol = components[0];
+ broker = components[1] + ":" + components[2];
+ }
+
+ if (firstBroker)
+ {
+ buf.append(protocol + "://" + broker);
+ }
+ else
+ { // https://cwiki.apache.org/qpid/connection-url-format.html says "A minimum of one broker url is
+ // required additional URLs are semi-colon(';') delimited."
+ buf.append(";" + protocol + "://" + broker);
+ }
+ firstBroker = false;
+ }
+ brokerList = "'" + buf.toString() + "'";
+
+ if (split.length == 2)
+ { // Extract the vhost and any connection level options
+ vhost = split[1];
+ String[] split2 = vhost.split("\\?"); // Look for options
+ vhost = split2[0];
+ if (split2.length == 2)
+ {
+ urlOptions = "&" + split2[1];
+ }
+ }
+
+ String connectionURL = "amqp://" + username + ":" + password + "@QpidJMS/" + vhost + "?brokerlist=" +
+ brokerList + urlOptions;
+ return connectionURL;
+ }
+
+ /**
+ * If no explicit username is supplied then explicitly set sasl mechanism to ANONYMOUS. If this isn't done
+ * The default is PLAIN which causes the broker to fail with "warning Failed to retrieve sasl username".
+ *
+ * @param username the previously extracted username.
+ * @param brokerListOptions the brokerList options extracted so far.
+ * @return the brokerList options adjusted with sasl_mechs='ANONYMOUS' if no username has been supplied.
+ */
+ private static String adjustBrokerListOptions(final String username, final String brokerListOptions)
+ {
+ if (username.equals(""))
+ {
+ if (brokerListOptions.equals(""))
+ {
+ return "?sasl_mechs='ANONYMOUS'";
+ }
+ else
+ {
+ if (brokerListOptions.contains("sasl_mechs"))
+ {
+ return brokerListOptions;
+ }
+ else
+ {
+ return brokerListOptions + "&sasl_mechs='ANONYMOUS'";
+ }
+ }
+ }
+ else
+ {
+ return brokerListOptions;
+ }
+ }
+
+ /**
+ * Create a ConnectionURL from the input "generic" URL.
+ *
+ * @param url the input URL.
+ * @param username the username.
+ * @param password the password.
+ * @param urlOptions the pre-parsed set of connection level options.
+ * @param brokerListOptions the pre-parsed set of specific brokerList options.
+ * @return a String containing the Java Connection URL.
+ */
+ private static String parseURL(String url, String username, String password,
+ String urlOptions, String brokerListOptions)
+ {
+ if (url.startsWith("amqp://"))
+ { // Somewhat experimental. This new format is only a "proposed" format
+ return parseExtendedAMQPURL(url, username, password);
+ }
+
+ String vhost = ""; // Specifying an empty vhost uses default Virtual Host.
+ String brokerList = "";
+ if (url.startsWith("amqp:"))
+ { // AMQP 0.10 URL format
+ url = url.substring(5); // Chop off "amqp:"
+ String[] addrSplit = url.split(","); // prot_addrs are comma separated
+ boolean firstBroker = true;
+ brokerListOptions = adjustBrokerListOptions(username, brokerListOptions);
+ StringBuilder buf = new StringBuilder();
+ for (String broker : addrSplit)
+ { // Iterate through the address list creating brokerList style URLs
+ broker = broker.trim();
+ if (broker.startsWith("tcp:"))
+ { // Only tcp is supported in an AMQP 0.10 prot_addr so we *should* only have to account for
+ // a "tcp:" prefix when normalising broker URLs
+ broker = broker.substring(4); // Chop off "tcp:"
+ }
+
+ if (firstBroker)
+ {
+ buf.append("tcp://" + broker + brokerListOptions);
+ }
+ else
+ { // https://cwiki.apache.org/qpid/connection-url-format.html says "A minimum of one broker url is
+ // required additional URLs are semi-colon(';') delimited."
+ buf.append(";tcp://" + broker + brokerListOptions);
+ }
+ firstBroker = false;
+ }
+ brokerList = "'" + buf.toString() + "'";
+ }
+ else if (url.contains("@"))
+ { // BrokerURL format as used in the Python tools.
+ String[] split = url.split("@");
+ url = split[1];
+
+ split = split[0].split("[/:]"); // Accept both <username>/<password> and <username>:<password>
+ username = split[0];
+
+ if (split.length == 2)
+ {
+ password = split[1];
+ }
+
+ brokerListOptions = adjustBrokerListOptions(username, brokerListOptions);
+ brokerList = "'tcp://" + url + brokerListOptions + "'";
+ }
+ else
+ { // Basic host:port format
+ brokerListOptions = adjustBrokerListOptions(username, brokerListOptions);
+ brokerList = "'tcp://" + url + brokerListOptions + "'";
+ }
+
+ String connectionURL = "amqp://" + username + ":" + password + "@QpidJMS/" + vhost + "?brokerlist=" +
+ brokerList + urlOptions;
+ return connectionURL;
+ }
+
+
+ /**
+ * Creates a Java Connection URL from one of the other supported URL formats.
+ *
+ * @param url an AMQP 0.10 URL, an extended AMQP 0-10 URL, a Broker URL or a Connection URL (the latter is simply
+ * returned untouched).
+ * @return a String containing the Java Connection URL.
+ */
+ public static String createConnectionURL(String url)
+ {
+ return createConnectionURL(url, null);
+ }
+
+ /**
+ * Creates a Java Connection URL from one of the other supported URL formats plus options.
+ *
+ * @param url an AMQP 0.10 URL, an extended AMQP 0-10 URL, a Broker URL or a Connection URL (the latter is simply
+ * returned untouched).
+ * @param opts a String containing the options encoded using the same form as the C++ qpid::messaging
+ * Connection class.
+ * @return a String containing the Java Connection URL.
+ */
+ public static String createConnectionURL(String url, String opts)
+ {
+ // This method is actually mostly about parsing the options, when the options are extracted it delegates
+ // to parseURL() to do the actual URL parsing.
+
+ // If a Java Connection URL has been passed in we simply return it.
+ if (url.startsWith("amqp://") && url.contains("brokerlist"))
+ {
+ return url;
+ }
+
+ // Initialise options to default values
+ String username = "";
+ String password = "";
+ String urlOptions = "";
+ String brokerListOptions = "";
+
+ // Get options from option String
+ if (opts != null && opts.startsWith("{") && opts.endsWith("}"))
+ {
+ // Connection URL Options
+ String maxprefetch = "";
+ String sync_publish = "";
+ String sync_ack = "";
+ String use_legacy_map_msg_format = "";
+ String failover = "";
+
+ // Broker List Options
+ String heartbeat = "";
+ String retries = "";
+ String connectdelay = "";
+ String connecttimeout = "";
+
+ String tcp_nodelay = "";
+
+ String sasl_mechs = "";
+ String sasl_encryption = "";
+ String sasl_protocol = "";
+ String sasl_server = "";
+
+ String ssl = "";
+ String ssl_verify_hostname = "";
+ String ssl_cert_alias = "";
+
+ String trust_store = "";
+ String trust_store_password = "";
+ String key_store = "";
+ String key_store_password = "";
+
+ Map options = new AddressParser(opts).map();
+
+ if (options.containsKey("maxprefetch"))
+ {
+ maxprefetch = "&maxprefetch='" + options.get("maxprefetch").toString() + "'";
+ }
+
+ if (options.containsKey("sync_publish"))
+ {
+ sync_publish = "&sync_publish='" + options.get("sync_publish").toString() + "'";
+ }
+
+ if (options.containsKey("sync_ack"))
+ {
+ sync_ack = "&sync_ack='" + options.get("sync_ack").toString() + "'";
+ }
+
+ if (options.containsKey("use_legacy_map_msg_format"))
+ {
+ use_legacy_map_msg_format = "&use_legacy_map_msg_format='" +
+ options.get("use_legacy_map_msg_format").toString() + "'";
+ }
+
+ if (options.containsKey("failover"))
+ {
+ if (options.containsKey("cyclecount"))
+ {
+ failover = "&failover='" + options.get("failover").toString() + "?cyclecount='" +
+ options.get("cyclecount").toString() + "''";
+ }
+ else
+ {
+ failover = "&failover='" + options.get("failover").toString() + "'";
+ }
+ }
+
+ if (options.containsKey("username"))
+ {
+ username = options.get("username").toString();
+ }
+
+ if (options.containsKey("password"))
+ {
+ password = options.get("password").toString();
+ }
+
+ if (options.containsKey("reconnect"))
+ {
+ String value = options.get("reconnect").toString();
+ if (value.equalsIgnoreCase("true"))
+ {
+ retries = "&retries='" + Integer.MAX_VALUE + "'";
+ connectdelay = "&connectdelay='5000'";
+ }
+ }
+
+ if (options.containsKey("reconnect_limit"))
+ {
+ retries = "&retries='" + options.get("reconnect_limit").toString() + "'";
+ }
+
+ if (options.containsKey("reconnect_interval"))
+ {
+ connectdelay = "&connectdelay='" + options.get("reconnect_interval").toString() + "000'";
+ }
+
+ if (options.containsKey("reconnect_interval_min"))
+ {
+ connectdelay = "&connectdelay='" + options.get("reconnect_interval_min").toString() + "000'";
+ }
+
+ if (options.containsKey("reconnect_interval_max"))
+ {
+ connectdelay = "&connectdelay='" + options.get("reconnect_interval_max").toString() + "000'";
+ }
+
+ if (options.containsKey("reconnect_timeout"))
+ {
+ connecttimeout = "&connecttimeout='" + options.get("reconnect_timeout").toString() + "000'";
+ }
+
+ if (options.containsKey("heartbeat"))
+ {
+ heartbeat = "&heartbeat='" + options.get("heartbeat").toString() + "'";
+ }
+
+ if (options.containsKey("tcp-nodelay"))
+ {
+ tcp_nodelay = "&tcp_nodelay='" + options.get("tcp-nodelay").toString() + "'";
+ }
+
+ if (options.containsKey("sasl_mechanisms"))
+ {
+ sasl_mechs = "&sasl_mechs='" + options.get("sasl_mechanisms").toString() + "'";
+ }
+
+ if (options.containsKey("sasl_mechs"))
+ {
+ sasl_mechs = "&sasl_mechs='" + options.get("sasl_mechs").toString() + "'";
+ }
+
+ if (options.containsKey("sasl_encryption"))
+ {
+ sasl_encryption = "&sasl_encryption='" + options.get("sasl_encryption").toString() + "'";
+ }
+
+ if (options.containsKey("sasl_protocol"))
+ {
+ sasl_protocol = "&sasl_protocol='" + options.get("sasl_protocol").toString() + "'";
+ }
+
+ if (options.containsKey("sasl_server"))
+ {
+ sasl_server = "&sasl_server='" + options.get("sasl_server").toString() + "'";
+ }
+
+ if (options.containsKey("trust_store"))
+ {
+ trust_store = "&trust_store='" + options.get("trust_store").toString() + "'";
+ }
+
+ if (options.containsKey("trust_store_password"))
+ {
+ trust_store_password = "&trust_store_password='" + options.get("trust_store_password").toString() + "'";
+ }
+
+ if (options.containsKey("key_store"))
+ {
+ key_store = "&key_store='" + options.get("key_store").toString() + "'";
+ }
+
+ if (options.containsKey("key_store_password"))
+ {
+ key_store_password = "&key_store_password='" + options.get("key_store_password").toString() + "'";
+ }
+
+ if (options.containsKey("protocol"))
+ {
+ String value = options.get("protocol").toString();
+ if (value.equalsIgnoreCase("ssl"))
+ {
+ ssl = "&ssl='true'";
+ if (options.containsKey("ssl_verify_hostname"))
+ {
+ ssl_verify_hostname = "&ssl_verify_hostname='" + options.get("ssl_verify_hostname").toString() + "'";
+ }
+
+ if (options.containsKey("ssl_cert_alias"))
+ {
+ ssl_cert_alias = "&ssl_cert_alias='" + options.get("ssl_cert_alias").toString() + "'";
+ }
+ }
+ }
+
+ urlOptions = maxprefetch + sync_publish + sync_ack + use_legacy_map_msg_format + failover;
+
+ brokerListOptions = heartbeat + retries + connectdelay + connecttimeout + tcp_nodelay +
+ sasl_mechs + sasl_encryption + sasl_protocol + sasl_server +
+ ssl + ssl_verify_hostname + ssl_cert_alias +
+ trust_store + trust_store_password + key_store + key_store_password;
+
+ if (brokerListOptions.startsWith("&"))
+ {
+ brokerListOptions = "?" + brokerListOptions.substring(1);
+ }
+ }
+
+ return parseURL(url, username, password, urlOptions, brokerListOptions);
+ }
+
+ /**
+ * Creates a JMS Connection from one of the supported URL formats.
+ *
+ * @param url an AMQP 0.10 URL, an extended AMQP 0-10 URL, a Broker URL or a Connection URL.
+ * @return a javax.jms.Connection.
+ */
+ public static Connection createConnection(String url)
+ {
+ return createConnection(url, null);
+ }
+
+ /**
+ * Creates a JMS Connection from one of the supported URL formats plus options.
+ *
+ * @param url an AMQP 0.10 URL, an extended AMQP 0-10 URL, a Broker URL or a Connection URL.
+ * @param opts a String containing the options encoded using the same form as the C++ qpid::messaging
+ * Connection class.
+ * @return a javax.jms.Connection.
+ */
+ public static Connection createConnection(String url, String opts)
+ {
+ String connectionUrl = createConnectionURL(url, opts);
+ _log.info("ConnectionHelper.createConnection() {}", connectionUrl);
+
+ // Initialise JNDI names etc into properties
+ Properties props = new Properties();
+ props.setProperty("java.naming.factory.initial", "org.apache.qpid.jndi.PropertiesFileInitialContextFactory");
+ props.setProperty("connectionfactory.ConnectionFactory", connectionUrl);
+
+ Connection connection = null;
+ try
+ {
+ Context jndi = new InitialContext(props);
+ ConnectionFactory connectionFactory = (ConnectionFactory)jndi.lookup("ConnectionFactory");
+ connection = connectionFactory.createConnection();
+ }
+ catch (NamingException ne)
+ {
+ _log.info("NamingException {} caught in createConnection()", ne.getMessage());
+ }
+ catch (JMSException jmse)
+ {
+ _log.info("JMSException {} caught in createConnection()", jmse.getMessage());
+ }
+
+ return connection;
+ }
+}
+
diff --git a/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/util/GetOpt.java b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/util/GetOpt.java
new file mode 100644
index 0000000000..d38fa03b5a
--- /dev/null
+++ b/qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/util/GetOpt.java
@@ -0,0 +1,209 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.qmf2.util;
+
+// Misc Imports
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Basic Java port of the python getopt function.
+ *
+ * Takes a standard args String array plus short form and long form options lists.
+ * Searches the args for options and any option arguments and stores these in optList
+ * any remaining args are stored in encArgs.
+ *
+ * <p>
+ * Example usage (paraphrased from QpidConfig):
+ * <pre>{@code
+ * String[] longOpts = {"help", "durable", "bindings", "broker-addr=", "file-count=",
+ * "file-size=", "max-queue-size=", "max-queue-count=", "limit-policy=",
+ * "order=", "sequence", "ive", "force", "force-if-not-empty",
+ * "force-if-used", "alternate-exchange=", "passive", "timeout=", "file=", "flow-stop-size=",
+ * "flow-resume-size=", "flow-stop-count=", "flow-resume-count=", "argument="};
+ *
+ * try
+ * {
+ * GetOpt getopt = new GetOpt(args, "ha:bf:", longOpts);
+ * List<String[]> optList = getopt.getOptList();
+ * String[] cargs = {};
+ * cargs = getopt.getEncArgs().toArray(cargs);
+ *
+ * for (String[] opt : optList)
+ * {
+ * //System.out.println(opt[0] + ":" + opt[1]);
+ * if (opt[0].equals("-a") || opt[0].equals("--broker-addr"))
+ * {
+ * _host = opt[1];
+ * }
+ * // Just a sample - more parsing would follow....
+ * }
+ *
+ * int nargs = cargs.length;
+ * if (nargs == 0)
+ * {
+ * overview();
+ * }
+ * else
+ * {
+ * String cmd = cargs[0];
+ * String modifier = "";
+ *
+ * if (nargs > 1)
+ * {
+ * modifier = cargs[1];
+ * }
+ *
+ * if (cmd.equals("exchanges"))
+ * {
+ * if (_recursive)
+ * {
+ * exchangeListRecurse(modifier);
+ * }
+ * else
+ * {
+ * exchangeList(modifier);
+ * }
+ * }
+ * // Just a sample - more parsing would follow....
+ * }
+ * }
+ * catch (IllegalArgumentException e)
+ * {
+ * System.err.println(e.toString());
+ * usage();
+ * }
+ * }
+ * </pre>
+ *
+ * @author Fraser Adams
+ */
+public final class GetOpt
+{
+ private List<String[]> _optList = new ArrayList<String[]>();
+ private List<String> _encArgs = new ArrayList<String>();
+
+ /**
+ * Returns the options and option arguments as a list containing a String array. The first element of the array
+ * contains the option and the second contains any arguments if present.
+ *
+ * @return the options and option arguments
+ */
+ public List<String[]> getOptList()
+ {
+ return _optList;
+ }
+
+ /**
+ * Returns any remaining arguments. This is any argument from a command line that doesn't begin "--" or "-".
+ *
+ * @return any remaining arguments not made available by getOptList().
+ */
+ public List<String> getEncArgs()
+ {
+ return _encArgs;
+ }
+
+ /**
+ * Takes a standard args String array plus short form and long form options lists.
+ * Searches the args for options and any option arguments and stores these in optList
+ * any remaining args are stored in encArgs.
+ *
+ * @param args standard arg String array
+ * @param opts short form option list of the form "ab:cd:" where b and d are options with arguments.
+ * @param longOpts long form option list as an array of strings. "option=" signifies an options with arguments.
+ */
+ public GetOpt(String[] args, String opts, String[] longOpts) throws IllegalArgumentException
+ {
+ int argslength = args.length;
+ for (int i = 0; i < argslength; i++)
+ {
+ String arg = args[i];
+ if (arg.startsWith("--"))
+ {
+ String extractedOption = arg.substring(2);
+ int nargs = _optList.size();
+ for (String j : longOpts)
+ {
+ String k = j.substring(0, j.length() - 1);
+ if (j.equals(extractedOption) || k.equals(extractedOption))
+ { // Handle where option and value are space separated
+ String arg0 = arg;
+ String arg1 = "";
+ if (i < argslength - 1 && k.equals(extractedOption))
+ {
+ i++;
+ arg1 = args[i];
+ }
+ String[] option = {arg0, arg1};
+ _optList.add(option);
+ }
+ else
+ { // Handle where option and value are separated by '='
+ String[] split = arg.split("=", 2); // Split on the first occurrence of '='
+ String arg0 = split[0];
+ String arg1 = "";
+ if (split.length == 2)
+ {
+ j = j.substring(0, j.length() - 1);
+ arg1 = split[1];
+ }
+
+ if (arg0.substring(2).equals(j))
+ {
+ String[] option = {arg0, arg1};
+ _optList.add(option);
+ }
+ }
+ }
+
+ if (nargs == _optList.size())
+ {
+ throw new IllegalArgumentException("Unknown Option " + arg);
+ }
+ }
+ else if (arg.startsWith("-"))
+ {
+ String extractedOption = arg.substring(1);
+ int index = opts.indexOf(extractedOption);
+ if (index++ != -1)
+ {
+ String arg1 = "";
+ if (i < argslength - 1 && index < opts.length() && opts.charAt(index) == ':')
+ {
+ i++;
+ arg1 = args[i];
+ }
+ String[] option = {arg, arg1};
+ _optList.add(option);
+ }
+ else
+ {
+ throw new IllegalArgumentException("Unknown Option " + arg);
+ }
+ }
+ else
+ {
+ _encArgs.add(arg);
+ }
+ }
+ }
+}