From 255979cc180383f3adc212e5d06756d236e011e6 Mon Sep 17 00:00:00 2001 From: Robert Gemmell Date: Fri, 2 May 2014 21:57:03 +0000 Subject: QPID-5610: copy the mavenised qmf2 java tools build tree to trunk git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk@1592057 13f79535-47bb-0310-9956-ffa450edef68 --- qpid/tools/src/java/README.txt | 214 ++ qpid/tools/src/java/TODO | 6 + qpid/tools/src/java/pom.xml | 44 + .../qpid-broker-plugins-management-qmf2/LICENSE | 202 ++ .../qpid-broker-plugins-management-qmf2/NOTICE | 5 + .../qpid-broker-plugins-management-qmf2/README.txt | 103 + .../qpid-broker-plugins-management-qmf2/pom.xml | 84 + .../qpid-broker-plugins-management-qmf2-bin.xml | 28 + .../qpid/server/qmf2/QmfManagementAgent.java | 600 ++++ .../qpid/server/qmf2/QmfManagementPlugin.java | 36 + .../qpid/server/qmf2/QmfManagementPluginImpl.java | 255 ++ .../apache/qpid/server/qmf2/agentdata/Binding.java | 199 ++ .../apache/qpid/server/qmf2/agentdata/Broker.java | 698 ++++ .../qpid/server/qmf2/agentdata/Connection.java | 192 ++ .../qpid/server/qmf2/agentdata/Exchange.java | 254 ++ .../apache/qpid/server/qmf2/agentdata/Queue.java | 296 ++ .../apache/qpid/server/qmf2/agentdata/Session.java | 116 + .../qpid/server/qmf2/agentdata/Subscription.java | 211 ++ qpid/tools/src/java/qpid-qmf2-rest/pom.xml | 42 + .../org/apache/qpid/restapi/ConnectionProxy.java | 287 ++ .../org/apache/qpid/restapi/ConnectionStore.java | 138 + .../java/org/apache/qpid/restapi/FileServer.java | 369 +++ .../org/apache/qpid/restapi/HttpTransaction.java | 165 + .../main/java/org/apache/qpid/restapi/JSON.java | 265 ++ .../org/apache/qpid/restapi/JSONMapParser.java | 402 +++ .../java/org/apache/qpid/restapi/QpidRestAPI.java | 206 ++ .../java/org/apache/qpid/restapi/QpidServer.java | 654 ++++ .../main/java/org/apache/qpid/restapi/Server.java | 95 + .../qpid/restapi/httpserver/Authenticator.java | 151 + .../apache/qpid/restapi/httpserver/Delegator.java | 89 + .../httpserver/HttpExchangeTransaction.java | 311 ++ .../main/java/org/apache/qpid/restapi/servlet/TODO | 25 + qpid/tools/src/java/qpid-qmf2-test/README.txt | 55 + qpid/tools/src/java/qpid-qmf2-test/log4j.xml | 50 + qpid/tools/src/java/qpid-qmf2-test/pom.xml | 149 + .../apache/qpid/qmf2/test/AgentExternalTest.java | 558 ++++ .../qmf2/test/AgentSubscriptionTestConsole.java | 224 ++ .../java/org/apache/qpid/qmf2/test/AgentTest.java | 298 ++ .../apache/qpid/qmf2/test/AgentTestConsole.java | 308 ++ .../apache/qpid/qmf2/test/BigPayloadAgentTest.java | 174 + .../qpid/qmf2/test/BigPayloadAgentTestConsole.java | 155 + .../qmf2/test/BrokerSubscriptionTestConsole.java | 262 ++ .../apache/qpid/qmf2/test/InvokeMethodTest.java | 112 + .../qpid/qmf2/test/PartialGetObjectsTest.java | 177 ++ .../java/org/apache/qpid/qmf2/test/SchemaTest.java | 126 + .../main/java/org/apache/qpid/qmf2/test/Test1.java | 140 + .../main/java/org/apache/qpid/qmf2/test/Test2.java | 121 + .../main/java/org/apache/qpid/qmf2/test/Test3.java | 137 + .../main/java/org/apache/qpid/qmf2/test/Test4.java | 339 ++ .../java/org/apache/qpid/qmf2/test/URLTest.java | 82 + qpid/tools/src/java/qpid-qmf2-tools/LICENSE | 202 ++ qpid/tools/src/java/qpid-qmf2-tools/NOTICE | 5 + .../java/qpid-qmf2-tools/bin/ConnectionAudit.sh | 44 + .../java/qpid-qmf2-tools/bin/ConnectionLogger.sh | 44 + .../src/java/qpid-qmf2-tools/bin/QpidConfig.sh | 44 + .../tools/src/java/qpid-qmf2-tools/bin/QpidCtrl.sh | 44 + .../java/qpid-qmf2-tools/bin/QpidPrintEvents.sh | 44 + .../src/java/qpid-qmf2-tools/bin/QpidQueueStats.sh | 44 + .../src/java/qpid-qmf2-tools/bin/QpidRestAPI.sh | 44 + .../src/java/qpid-qmf2-tools/bin/QueueFuse.sh | 44 + qpid/tools/src/java/qpid-qmf2-tools/bin/log4j.xml | 50 + .../bin/qpid-web/authentication/account.properties | 23 + .../bin/qpid-web/web/apple-touch-icon.png | Bin 0 -> 25357 bytes .../qpid-qmf2-tools/bin/qpid-web/web/favicon.ico | Bin 0 -> 4286 bytes .../qpid-qmf2-tools/bin/qpid-web/web/index.html | 56 + .../bin/qpid-web/web/itablet/css/itablet-ie6.css | 323 ++ .../bin/qpid-web/web/itablet/css/itablet-ie7.css | 214 ++ .../bin/qpid-web/web/itablet/css/itablet-ie8.css | 161 + .../bin/qpid-web/web/itablet/css/itablet-ie9.css | 45 + .../bin/qpid-web/web/itablet/css/itablet.css | 993 ++++++ .../bin/qpid-web/web/itablet/images/action.png | Bin 0 -> 1253 bytes .../bin/qpid-web/web/itablet/images/add.png | Bin 0 -> 2042 bytes .../bin/qpid-web/web/itablet/images/bin.png | Bin 0 -> 889 bytes .../web/itablet/images/blue-button-sprite.png | Bin 0 -> 2102 bytes .../qpid-web/web/itablet/images/blue-chevron.png | Bin 0 -> 1615 bytes .../bin/qpid-web/web/itablet/images/blueball.png | Bin 0 -> 851 bytes .../qpid-web/web/itablet/images/button-sprite.png | Bin 0 -> 2405 bytes .../qpid-web/web/itablet/images/chevron-active.png | Bin 0 -> 308 bytes .../bin/qpid-web/web/itablet/images/chevron.png | Bin 0 -> 259 bytes .../bin/qpid-web/web/itablet/images/delete.png | Bin 0 -> 5361 bytes .../bin/qpid-web/web/itablet/images/flag.png | Bin 0 -> 1068 bytes .../bin/qpid-web/web/itablet/images/home.png | Bin 0 -> 727 bytes .../web/itablet/images/ie/active-gradient.png | Bin 0 -> 174 bytes .../web/itablet/images/ie/blue-button-sprite.gif | Bin 0 -> 2214 bytes .../web/itablet/images/ie/blue-chevron.gif | Bin 0 -> 763 bytes .../web/itablet/images/ie/button-sprite.gif | Bin 0 -> 2287 bytes .../web/itablet/images/ie/chevron-active.gif | Bin 0 -> 166 bytes .../bin/qpid-web/web/itablet/images/ie/chevron.gif | Bin 0 -> 97 bytes .../web/itablet/images/ie/header-gradient.png | Bin 0 -> 204 bytes .../web/itablet/images/ie/radial-gradient.png | Bin 0 -> 865 bytes .../web/itablet/images/ie/radius-10px-sprite.png | Bin 0 -> 750 bytes .../web/itablet/images/ie/radius-5px-sprite.png | Bin 0 -> 212 bytes .../bin/qpid-web/web/itablet/images/ie/red6.png | Bin 0 -> 187 bytes .../bin/qpid-web/web/itablet/images/ie/smoked.png | Bin 0 -> 187 bytes .../qpid-web/web/itablet/images/ie/tick-active.gif | Bin 0 -> 101 bytes .../bin/qpid-web/web/itablet/images/ie/tick.gif | Bin 0 -> 167 bytes .../qpid-web/web/itablet/images/ie/transparent.gif | Bin 0 -> 49 bytes .../qpid-web/web/itablet/images/ie/transparent.png | Bin 0 -> 178 bytes .../bin/qpid-web/web/itablet/images/mask.png | Bin 0 -> 941 bytes .../bin/qpid-web/web/itablet/images/move.png | Bin 0 -> 1061 bytes .../bin/qpid-web/web/itablet/images/on_off.png | Bin 0 -> 3398 bytes .../qpid-web/web/itablet/images/tick-active.png | Bin 0 -> 380 bytes .../bin/qpid-web/web/itablet/images/tick.png | Bin 0 -> 559 bytes .../bin/qpid-web/web/itablet/images/toggle-off.png | Bin 0 -> 181 bytes .../web/itablet/images/toggle-on-border.png | Bin 0 -> 452 bytes .../bin/qpid-web/web/itablet/images/toggle-on.png | Bin 0 -> 211 bytes .../bin/qpid-web/web/itablet/images/write.png | Bin 0 -> 1200 bytes .../bin/qpid-web/web/itablet/scripts/LICENCE | 48 + .../bin/qpid-web/web/itablet/scripts/iscroll.js | 1117 +++++++ .../bin/qpid-web/web/itablet/scripts/itablet.js | 1516 +++++++++ .../bin/qpid-web/web/itablet/scripts/jquery.js | 4 + .../bin/qpid-web/web/qmf-ui/css/index.css | 64 + .../bin/qpid-web/web/qmf-ui/css/qmf.css | 144 + .../bin/qpid-web/web/qmf-ui/images/brokers.png | Bin 0 -> 1794 bytes .../bin/qpid-web/web/qmf-ui/images/connections.png | Bin 0 -> 1931 bytes .../bin/qpid-web/web/qmf-ui/images/events.png | Bin 0 -> 2419 bytes .../bin/qpid-web/web/qmf-ui/images/exchanges.png | Bin 0 -> 2428 bytes .../bin/qpid-web/web/qmf-ui/images/gradient.png | Bin 0 -> 89 bytes .../bin/qpid-web/web/qmf-ui/images/links.png | Bin 0 -> 2499 bytes .../bin/qpid-web/web/qmf-ui/images/loading.gif | Bin 0 -> 3208 bytes .../bin/qpid-web/web/qmf-ui/images/qpid-logo.png | Bin 0 -> 32948 bytes .../bin/qpid-web/web/qmf-ui/images/queues.png | Bin 0 -> 2293 bytes .../qpid-web/web/qmf-ui/images/route-topology.png | Bin 0 -> 2014 bytes .../bin/qpid-web/web/qmf-ui/images/settings.png | Bin 0 -> 2687 bytes .../bin/qpid-web/web/qmf-ui/scripts/LICENCE | 39 + .../bin/qpid-web/web/qmf-ui/scripts/excanvas.js | 1416 +++++++++ .../bin/qpid-web/web/qmf-ui/scripts/qmf-ui.js | 3332 ++++++++++++++++++++ .../bin/qpid-web/web/qpid/scripts/LICENCE | 23 + .../bin/qpid-web/web/qpid/scripts/qpid.js | 745 +++++ .../qpid-qmf2-tools/bin/qpid-web/web/ui/config.js | 37 + .../qpid-qmf2-tools/bin/qpid-web/web/ui/qmf.html | 1238 ++++++++ .../src/java/qpid-qmf2-tools/bin/whitelist.xml | 33 + qpid/tools/src/java/qpid-qmf2-tools/pom.xml | 91 + .../src/main/assembly/qpid-qmf-tools-bin.xml | 37 + .../apache/qpid/qmf2/tools/ConnectionAudit.java | 474 +++ .../apache/qpid/qmf2/tools/ConnectionLogger.java | 383 +++ .../org/apache/qpid/qmf2/tools/QpidConfig.java | 1542 +++++++++ .../java/org/apache/qpid/qmf2/tools/QpidCtrl.java | 358 +++ .../apache/qpid/qmf2/tools/QpidPrintEvents.java | 210 ++ .../org/apache/qpid/qmf2/tools/QpidQueueStats.java | 374 +++ .../java/org/apache/qpid/qmf2/tools/QueueFuse.java | 364 +++ qpid/tools/src/java/qpid-qmf2/pom.xml | 54 + .../java/org/apache/qpid/qmf2/agent/Agent.java | 1462 +++++++++ .../org/apache/qpid/qmf2/agent/AgentExternal.java | 259 ++ .../apache/qpid/qmf2/agent/MethodCallParams.java | 123 + .../apache/qpid/qmf2/agent/MethodCallWorkItem.java | 64 + .../org/apache/qpid/qmf2/agent/QmfAgentData.java | 364 +++ .../org/apache/qpid/qmf2/agent/QueryWorkItem.java | 85 + .../apache/qpid/qmf2/agent/ResubscribeParams.java | 78 + .../qmf2/agent/ResubscribeRequestWorkItem.java | 66 + .../apache/qpid/qmf2/agent/SubscribableAgent.java | 70 + .../qpid/qmf2/agent/SubscribeRequestWorkItem.java | 69 + .../org/apache/qpid/qmf2/agent/Subscription.java | 280 ++ .../apache/qpid/qmf2/agent/SubscriptionParams.java | 102 + .../qmf2/agent/UnsubscribeRequestWorkItem.java | 65 + .../apache/qpid/qmf2/agent/doc-files/QmfData.png | Bin 0 -> 3876 bytes .../qmf2/agent/doc-files/QmfEventListenerModel.png | Bin 0 -> 5412 bytes .../apache/qpid/qmf2/agent/doc-files/QmfQuery.png | Bin 0 -> 8444 bytes .../apache/qpid/qmf2/agent/doc-files/Schema.png | Bin 0 -> 4911 bytes .../qpid/qmf2/agent/doc-files/Subscriptions.png | Bin 0 -> 3365 bytes .../qmf2/agent/doc-files/WorkQueueEventModel.png | Bin 0 -> 7521 bytes .../org/apache/qpid/qmf2/common/AMQPMessage.java | 309 ++ .../apache/qpid/qmf2/common/BlockingNotifier.java | 65 + .../org/apache/qpid/qmf2/common/BooleanEquals.java | 79 + .../org/apache/qpid/qmf2/common/BooleanExists.java | 72 + .../apache/qpid/qmf2/common/BooleanExpression.java | 221 ++ .../org/apache/qpid/qmf2/common/BooleanFalse.java | 60 + .../qpid/qmf2/common/BooleanGreaterEqual.java | 89 + .../qpid/qmf2/common/BooleanGreaterThan.java | 89 + .../apache/qpid/qmf2/common/BooleanLessEqual.java | 89 + .../apache/qpid/qmf2/common/BooleanLessThan.java | 89 + .../apache/qpid/qmf2/common/BooleanNotEquals.java | 79 + .../apache/qpid/qmf2/common/BooleanRegexMatch.java | 95 + .../org/apache/qpid/qmf2/common/BooleanTrue.java | 60 + .../org/apache/qpid/qmf2/common/Expression.java | 77 + .../java/org/apache/qpid/qmf2/common/Handle.java | 107 + .../org/apache/qpid/qmf2/common/LogicalAnd.java | 62 + .../apache/qpid/qmf2/common/LogicalExpression.java | 64 + .../org/apache/qpid/qmf2/common/LogicalNot.java | 62 + .../org/apache/qpid/qmf2/common/LogicalOr.java | 62 + .../java/org/apache/qpid/qmf2/common/Notifier.java | 67 + .../apache/qpid/qmf2/common/NotifierWrapper.java | 57 + .../qpid/qmf2/common/NullQmfEventListener.java | 43 + .../java/org/apache/qpid/qmf2/common/ObjectId.java | 159 + .../org/apache/qpid/qmf2/common/QmfCallback.java | 33 + .../java/org/apache/qpid/qmf2/common/QmfData.java | 443 +++ .../org/apache/qpid/qmf2/common/QmfDescribed.java | 84 + .../java/org/apache/qpid/qmf2/common/QmfEvent.java | 224 ++ .../apache/qpid/qmf2/common/QmfEventListener.java | 51 + .../org/apache/qpid/qmf2/common/QmfException.java | 42 + .../org/apache/qpid/qmf2/common/QmfManaged.java | 83 + .../java/org/apache/qpid/qmf2/common/QmfQuery.java | 343 ++ .../apache/qpid/qmf2/common/QmfQueryTarget.java | 36 + .../java/org/apache/qpid/qmf2/common/QmfType.java | 39 + .../org/apache/qpid/qmf2/common/SchemaClass.java | 135 + .../org/apache/qpid/qmf2/common/SchemaClassId.java | 202 ++ .../apache/qpid/qmf2/common/SchemaEventClass.java | 266 ++ .../org/apache/qpid/qmf2/common/SchemaMethod.java | 275 ++ .../apache/qpid/qmf2/common/SchemaObjectClass.java | 378 +++ .../apache/qpid/qmf2/common/SchemaProperty.java | 364 +++ .../java/org/apache/qpid/qmf2/common/WorkItem.java | 399 +++ .../org/apache/qpid/qmf2/common/WorkQueue.java | 106 + .../apache/qpid/qmf2/common/doc-files/Console.png | Bin 0 -> 6234 bytes .../apache/qpid/qmf2/common/doc-files/QmfData.png | Bin 0 -> 3876 bytes .../common/doc-files/QmfEventListenerModel.png | Bin 0 -> 5412 bytes .../apache/qpid/qmf2/common/doc-files/QmfQuery.png | Bin 0 -> 8444 bytes .../apache/qpid/qmf2/common/doc-files/Schema.png | Bin 0 -> 4911 bytes .../qpid/qmf2/common/doc-files/Subscriptions.png | Bin 0 -> 5382 bytes .../apache/qpid/qmf2/common/doc-files/WorkItem.png | Bin 0 -> 9007 bytes .../qmf2/common/doc-files/WorkQueueEventModel.png | Bin 0 -> 7521 bytes .../java/org/apache/qpid/qmf2/console/Agent.java | 482 +++ .../qpid/qmf2/console/AgentAccessWorkItem.java | 80 + .../qpid/qmf2/console/AgentAddedWorkItem.java | 49 + .../qpid/qmf2/console/AgentDeletedWorkItem.java | 50 + .../qpid/qmf2/console/AgentHeartbeatWorkItem.java | 48 + .../org/apache/qpid/qmf2/console/AgentProxy.java | 85 + .../qpid/qmf2/console/AgentRestartedWorkItem.java | 46 + .../java/org/apache/qpid/qmf2/console/Console.java | 2237 +++++++++++++ .../qpid/qmf2/console/EventReceivedWorkItem.java | 63 + .../qpid/qmf2/console/MethodResponseWorkItem.java | 65 + .../org/apache/qpid/qmf2/console/MethodResult.java | 151 + .../qpid/qmf2/console/ObjectUpdateWorkItem.java | 65 + .../apache/qpid/qmf2/console/QmfConsoleData.java | 277 ++ .../qpid/qmf2/console/SubscribeIndication.java | 66 + .../apache/qpid/qmf2/console/SubscribeParams.java | 130 + .../qmf2/console/SubscribeResponseWorkItem.java | 80 + .../console/SubscriptionIndicationWorkItem.java | 62 + .../qpid/qmf2/console/SubscriptionManager.java | 255 ++ .../apache/qpid/qmf2/console/doc-files/Console.png | Bin 0 -> 6234 bytes .../console/doc-files/QmfEventListenerModel.png | Bin 0 -> 5412 bytes .../qpid/qmf2/console/doc-files/Subscriptions.png | Bin 0 -> 2789 bytes .../qmf2/console/doc-files/WorkQueueEventModel.png | Bin 0 -> 7521 bytes .../apache/qpid/qmf2/util/ConnectionHelper.java | 862 +++++ .../java/org/apache/qpid/qmf2/util/GetOpt.java | 208 ++ 234 files changed, 40383 insertions(+) create mode 100644 qpid/tools/src/java/README.txt create mode 100644 qpid/tools/src/java/TODO create mode 100644 qpid/tools/src/java/pom.xml create mode 100644 qpid/tools/src/java/qpid-broker-plugins-management-qmf2/LICENSE create mode 100644 qpid/tools/src/java/qpid-broker-plugins-management-qmf2/NOTICE create mode 100644 qpid/tools/src/java/qpid-broker-plugins-management-qmf2/README.txt create mode 100644 qpid/tools/src/java/qpid-broker-plugins-management-qmf2/pom.xml create mode 100644 qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/assembly/qpid-broker-plugins-management-qmf2-bin.xml create mode 100644 qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/QmfManagementAgent.java create mode 100644 qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/QmfManagementPlugin.java create mode 100644 qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/QmfManagementPluginImpl.java create mode 100644 qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Binding.java create mode 100644 qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Broker.java create mode 100644 qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Connection.java create mode 100644 qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Exchange.java create mode 100644 qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Queue.java create mode 100644 qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Session.java create mode 100644 qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Subscription.java create mode 100644 qpid/tools/src/java/qpid-qmf2-rest/pom.xml create mode 100644 qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/ConnectionProxy.java create mode 100644 qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/ConnectionStore.java create mode 100644 qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/FileServer.java create mode 100644 qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/HttpTransaction.java create mode 100644 qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/JSON.java create mode 100644 qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/JSONMapParser.java create mode 100644 qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/QpidRestAPI.java create mode 100644 qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/QpidServer.java create mode 100644 qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/Server.java create mode 100644 qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/httpserver/Authenticator.java create mode 100644 qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/httpserver/Delegator.java create mode 100644 qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/httpserver/HttpExchangeTransaction.java create mode 100644 qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/servlet/TODO create mode 100644 qpid/tools/src/java/qpid-qmf2-test/README.txt create mode 100644 qpid/tools/src/java/qpid-qmf2-test/log4j.xml create mode 100644 qpid/tools/src/java/qpid-qmf2-test/pom.xml create mode 100644 qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/AgentExternalTest.java create mode 100644 qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/AgentSubscriptionTestConsole.java create mode 100644 qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/AgentTest.java create mode 100644 qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/AgentTestConsole.java create mode 100644 qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/BigPayloadAgentTest.java create mode 100644 qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/BigPayloadAgentTestConsole.java create mode 100644 qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/BrokerSubscriptionTestConsole.java create mode 100644 qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/InvokeMethodTest.java create mode 100644 qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/PartialGetObjectsTest.java create mode 100644 qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/SchemaTest.java create mode 100644 qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/Test1.java create mode 100644 qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/Test2.java create mode 100644 qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/Test3.java create mode 100644 qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/Test4.java create mode 100644 qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/URLTest.java create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/LICENSE create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/NOTICE create mode 100755 qpid/tools/src/java/qpid-qmf2-tools/bin/ConnectionAudit.sh create mode 100755 qpid/tools/src/java/qpid-qmf2-tools/bin/ConnectionLogger.sh create mode 100755 qpid/tools/src/java/qpid-qmf2-tools/bin/QpidConfig.sh create mode 100755 qpid/tools/src/java/qpid-qmf2-tools/bin/QpidCtrl.sh create mode 100755 qpid/tools/src/java/qpid-qmf2-tools/bin/QpidPrintEvents.sh create mode 100755 qpid/tools/src/java/qpid-qmf2-tools/bin/QpidQueueStats.sh create mode 100755 qpid/tools/src/java/qpid-qmf2-tools/bin/QpidRestAPI.sh create mode 100755 qpid/tools/src/java/qpid-qmf2-tools/bin/QueueFuse.sh create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/log4j.xml create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/authentication/account.properties create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/apple-touch-icon.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/favicon.ico create mode 100755 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/index.html create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/css/itablet-ie6.css create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/css/itablet-ie7.css create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/css/itablet-ie8.css create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/css/itablet-ie9.css create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/css/itablet.css create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/action.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/add.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/bin.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/blue-button-sprite.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/blue-chevron.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/blueball.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/button-sprite.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/chevron-active.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/chevron.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/delete.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/flag.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/home.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/active-gradient.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/blue-button-sprite.gif create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/blue-chevron.gif create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/button-sprite.gif create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/chevron-active.gif create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/chevron.gif create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/header-gradient.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/radial-gradient.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/radius-10px-sprite.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/radius-5px-sprite.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/red6.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/smoked.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/tick-active.gif create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/tick.gif create mode 100755 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/transparent.gif create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/transparent.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/mask.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/move.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/on_off.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/tick-active.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/tick.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/toggle-off.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/toggle-on-border.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/toggle-on.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/write.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/scripts/LICENCE create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/scripts/iscroll.js create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/scripts/itablet.js create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/scripts/jquery.js create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/qmf-ui/css/index.css create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/qmf-ui/css/qmf.css create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/qmf-ui/images/brokers.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/qmf-ui/images/connections.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/qmf-ui/images/events.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/qmf-ui/images/exchanges.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/qmf-ui/images/gradient.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/qmf-ui/images/links.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/qmf-ui/images/loading.gif create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/qmf-ui/images/qpid-logo.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/qmf-ui/images/queues.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/qmf-ui/images/route-topology.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/qmf-ui/images/settings.png create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/qmf-ui/scripts/LICENCE create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/qmf-ui/scripts/excanvas.js create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/qmf-ui/scripts/qmf-ui.js create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/qpid/scripts/LICENCE create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/qpid/scripts/qpid.js create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/ui/config.js create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/ui/qmf.html create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/bin/whitelist.xml create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/pom.xml create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/src/main/assembly/qpid-qmf-tools-bin.xml create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/src/main/java/org/apache/qpid/qmf2/tools/ConnectionAudit.java create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/src/main/java/org/apache/qpid/qmf2/tools/ConnectionLogger.java create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/src/main/java/org/apache/qpid/qmf2/tools/QpidConfig.java create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/src/main/java/org/apache/qpid/qmf2/tools/QpidCtrl.java create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/src/main/java/org/apache/qpid/qmf2/tools/QpidPrintEvents.java create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/src/main/java/org/apache/qpid/qmf2/tools/QpidQueueStats.java create mode 100644 qpid/tools/src/java/qpid-qmf2-tools/src/main/java/org/apache/qpid/qmf2/tools/QueueFuse.java create mode 100644 qpid/tools/src/java/qpid-qmf2/pom.xml create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/Agent.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/AgentExternal.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/MethodCallParams.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/MethodCallWorkItem.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/QmfAgentData.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/QueryWorkItem.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/ResubscribeParams.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/ResubscribeRequestWorkItem.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/SubscribableAgent.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/SubscribeRequestWorkItem.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/Subscription.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/SubscriptionParams.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/UnsubscribeRequestWorkItem.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/QmfData.png create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/QmfEventListenerModel.png create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/QmfQuery.png create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/Schema.png create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/Subscriptions.png create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent/doc-files/WorkQueueEventModel.png create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/AMQPMessage.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BlockingNotifier.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanEquals.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanExists.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanExpression.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanFalse.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanGreaterEqual.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanGreaterThan.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanLessEqual.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanLessThan.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanNotEquals.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanRegexMatch.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/BooleanTrue.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/Expression.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/Handle.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/LogicalAnd.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/LogicalExpression.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/LogicalNot.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/LogicalOr.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/Notifier.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/NotifierWrapper.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/NullQmfEventListener.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/ObjectId.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfCallback.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfData.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfDescribed.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfEvent.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfEventListener.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfException.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfManaged.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfQuery.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfQueryTarget.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/QmfType.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaClass.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaClassId.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaEventClass.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaMethod.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaObjectClass.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/SchemaProperty.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/WorkItem.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/WorkQueue.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/Console.png create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/QmfData.png create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/QmfEventListenerModel.png create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/QmfQuery.png create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/Schema.png create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/Subscriptions.png create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/WorkItem.png create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common/doc-files/WorkQueueEventModel.png create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/Agent.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentAccessWorkItem.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentAddedWorkItem.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentDeletedWorkItem.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentHeartbeatWorkItem.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentProxy.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/AgentRestartedWorkItem.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/Console.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/EventReceivedWorkItem.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/MethodResponseWorkItem.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/MethodResult.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/ObjectUpdateWorkItem.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/QmfConsoleData.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscribeIndication.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscribeParams.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscribeResponseWorkItem.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscriptionIndicationWorkItem.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/SubscriptionManager.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/doc-files/Console.png create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/doc-files/QmfEventListenerModel.png create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/doc-files/Subscriptions.png create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console/doc-files/WorkQueueEventModel.png create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/util/ConnectionHelper.java create mode 100644 qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/util/GetOpt.java (limited to 'qpid/tools/src/java') diff --git a/qpid/tools/src/java/README.txt b/qpid/tools/src/java/README.txt new file mode 100644 index 0000000000..48b609049c --- /dev/null +++ b/qpid/tools/src/java/README.txt @@ -0,0 +1,214 @@ +/* + * + * 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. + * + */ + +********************************************** Introduction *********************************************** + +This directory provides a set of Java and JavaScript based tools that allow interaction with the Qpid brokers. +The tools are based on QMF2 (Qpid Management Framework v2) and work with the C++ broker by default. In order +to enable QMF2 support in the Java Broker you must compile the QMF plugin (see the directory +qpid-broker-plugins-management-qmf2 and contained README.txt). + +In order to build the Java QMF2 API, the Tools and the Java Broker QMF2 plugin simply do: + +mvn clean package + +This will create the jar files for the various modules, and additionally create packaged release archives for +the combined tools package and the broker plugin which can be extracted for installation and use. + + +There is fairly comprehensive JavaDoc available, which you can generate in a couple of ways: + +mvn javadoc:aggregate - Builds the Javadoc for all the modules combined, output located at: +qpid/tools/src/java/target/site/apidocs/index.html + +mvn javadoc:javadoc - Builds the Javadoc for each module in turn individually, output located at: +qpid/tools/src/java//target/site/apidocs/index.html + + +N.B. At the moment the QMF2 API and tools use the "traditional" Qpid AMQP 0.10 JMS API. The intention is that +over time this will migrate to AMQP 1.0 and move from being QMF2 based to using the AMQP 1.0 Management Spec. +However there is no concrete schedule for this migration at this time. + +************************************************* The API ************************************************* + +The tools are build on a Java JMS implementation of the QMF2 API specified at +https://cwiki.apache.org/confluence/display/qpid/QMFv2+API+Proposal + +Once built as described earlier, the API jar is included in the overall tools bundle described below, and +additionally the API jar itself file will be placed in: +qpid/tools/src/java/qpid-qmf2/target + +There is fairly comprehensive JavaDoc available, see earlier for build instructions. + +Looking at the source code for the tools (see "The Tools" below) might be a quicker way to get started. + +The source code for the Java QMF2 API can be found under: +qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/console +qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/agent +qpid/tools/src/java/qpid-qmf2/src/main/java/org/apache/qpid/qmf2/common + +console: contains the classes for the QMF2 "console", which is what most of the tools make use of +agent: contains the classes for the QMF2 "agent", which it what exposes management services, this is + what the Java Broker plugin uses to "externalise" its management model as QMF. +common: contains classes common to both the console and the agent. + +************************************************ The Tools ************************************************ + +A number of Java based tools are provided, and additionally a web based GUI with underlying REST api which +are described later, utilising the core components from the API outlined above. + +Once built as described earlier, the tools jar is included in the overall tools release tar.gz placed in: +qpid/tools/src/java/qpid-qmf2-tools/target + +There are executable shell scripts included in the tools bundle that should allow the tools to be run fairly +easily. To use them, extract the tar.gz release to your preferred installation location, and open the +included bin/ directory. + +The source code for the Java QMF2 Tools can be found under: +qpid/tools/src/java/qpid-qmf2-tools/src/main/java/org/apache/qpid/qmf2/tools + +The available tools are: +QpidConfig: Is a Java port of the standard Python based qpid-config tool. This exercises most of the QMF2 API + and is probably a good bet to see how things work if you want to use the API in your own projects. +QpidCtrl: Is a Java port of the qpid-ctrl tool found in qpid/cpp/src/tests. This is a little known, but useful + little tool that lets one send low-level QMF constructs from the command line. The JavaDoc is the + best place to look for example usage (see earlier for build instructions). +QpidPrintEvents: Is a Java port of the Python qpid-printevents and illustrates the asynchronous delivery + of QMF2 notification events. +QpidQueueStats: Is a Java port of the Python qpid-queue-stats. This was written mainly to illustrate the use + of the QMF2 "QuerySubscription" API that lets one specify how to be asynchronously notified + of changes to QMF Management Objects matching a specified set of criteria. +ConnectionAudit: Is a tool that allows one to audit connections to one or more Qpid brokers. It uses QMF + Events to identify when connections have been made to a broker and if so it logs information + about the connection. A whitelist can be specified to flag connections that you don't + want to have logged (e.g. ones that you like). +ConnectionLogger: Is similar to ConnectionAudit but a bit simpler this tool just logs connections being made + the tool is mainly there to illustrate how to dereference the associations between the + various QMF Management Objects (Connection, Session, Subscription, Queue, Binding Exchange etc.) +QueueFuse: Is a tool that monitors QMF Events looking for a QueueThresholdExceeded, which occurs when a queue + gets more than 80% full. When this Event occurs the tool sends a QMF method to "purge" 10% of the + messages off the offending queue, i.e. it acts rather like a fuse. It's mainly a bit of a toy, but + it's a pretty good illustration of how to trigger QMF method invocation from QMF Events. It would + be pretty easy to modify this to redirect messages to a different queue if a particular queue fills. +QpidRestAPI: This is a Web Service that exposes QMF2 via a REST API, see "The GUI" section below for details. + +************************************************* The GUI ************************************************* + +Included in the tools package, there is a fairly comprehensive Web based GUI available for Qpid that works +with the C++ Broker and also the Java Broker if the QMF plugin has been installed (see README-Java-Broker.txt). + +The GUI is in the form of a pure client side "single page" Web App written in JavaScript that uses the +QpidRestAPI to proxy the QMF API, and also serve up the GUI. + +There is comprehensive JavaDoc for the QpidRestAPI (see earlier for build instructions), where +the most useful classes to look at are: +QpidRestAPI: This describes the various command line options available. +QpidServer: This provides documentation for the actual REST API itself, in effect the REST mapping for QMF + +QpidRestAPI provides a fairly complete REST mapping for QMF, it was primarily written as the back-end to +the GUI, but there's no reason why it couldn't be used in its own right. + +To get started, after you have extracted the tools release as described earlier, the simplest and probably +most common use case can be kicked offby changing into the bin/ directory and firing up the REST API via: +./QpidRestAPI + +This will bind the HTTP port to 8080 on the "wildcard" address (0.0.0.0). The QMF connection will default to +the host that QpidRestAPI is running on and use the default AMQP port 5672. + +If you point a Browser to :8080 the GUI should start up asking for a User Name and Password, the +defaults for those are the rather "traditional" admin admin. + + +If you have a non-trivial broker set-up you'll probably see "Failed to Connect", which is most likely due +to having authentication enabled (you can check this by firing up the C++ broker using qpidd --auth no) + + +There are a few ways to configure the Brokers that you can control via the GUI: +The first way is to specify the -a (or --broker-addr) command line option e.g. +./QpidRestAPI -a guest/guest@localhost + +This option accepts the Broker Address syntax used by the standard Python tools and it also accepts the +Java ConnectionURL syntax specified here (though to be honest the syntax used by the Python tools is simpler) +http://qpid.apache.org/releases/qpid-0.24/programming/book/QpidJNDI.html#section-jms-connection-url + + +This way of specifying the AMQP address of the default broker that you want to manage is probably the best +approach, but it is possible to add as many QMF Console Connections as you like by clicking +"Add QMF Console Connection" on the GUI Settings page. The popup lets you specify the Address URL such as +"guest/guest@host:5672" - again it also accepts the JMS Connection URLs, though I only use them if I'm +doing a copy/paste of an existing Connection URL. +The Name is simply a "friendly name" that you want to use to identify a particular Broker. + + +Clearly if you want to be able to manage a number of brokers you'd probably prefer not to have to enter +them every time you fire up the GUI - particularly because the list gets wiped if you hit refresh :-) + +The good news is that the initial set of Console Connections is configurable via the file: +qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/ui/config.js + + +This is a simple JSON file and it contains example Console Connection configuration including a fairly complex one + +If you use this mechanism to configure the GUI you can quickly switch between however many Brokers +you'd like to be able to control. + + +As mentioned above the default User Name and Password are admin and admin, these are set in the file +qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/authentication/account.properties + + +It's worth pointing out that at the moment authentication is limited to basic uthentication. This is mainly +due to lack of time/energy/motivation to do anything fancier (I only tend to use it on a private network) +I also had a need to minimise dependencies, so the Web Server is actually based on the Java 1.6 +com.sun.net.httpserver Web Server. + + +In practice though basic authentication shouldn't be as much of a restriction as it might sound especially +if you're only managing a single Broker. + +When one fires up QpidRestAPI with the -a option the Broker connection information does not pass between the +GUI and the QpidRestAPI so it's ultimately no less secure than using say qpid-config in this case though +note that if one configures multiple Brokers via config.js the contents of that file get served to the GUI +when it gets loaded so you probably want to restrict use of the GUI to the same network you'd be happy to +run qpid-config from. + + + + + +*********************************************** Important!! *********************************************** +* If your Qpid C++ broker is older than 0.10 the QMF2 API won't work unless your setup is as follows: * +*********************************************** Important!! *********************************************** + +Note that if you are talking to a broker < Qpid 0.10 +you need to set "--mgmt-qmf2 yes" when you start up qpidd if you want to get QMF2 Events and heartbeats pushed. +This is particularly important to note if you are using the Qpid GUI, as in default mode its updates are +triggered by the QMF2 heartbeats. If "--mgmt-qmf2 yes" isn't set on a 0.8 broker you'll see "Broker Disconnected" +flash briefly every 30 seconds or so as timeouts occur. Creating a QMF Console Connecton in the GUI with +"Disable Events" selected uses a timed poll rather than a heartbeat so it may be better to do that for cases +where access to the broker configuration is not available. + +*********************************************************************************************************** + +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 onwards) + +Note 2: In order to use QMF2 the app-id field needs to be set. This requires the Qpid 0.12+ Java client diff --git a/qpid/tools/src/java/TODO b/qpid/tools/src/java/TODO new file mode 100644 index 0000000000..b72b07f433 --- /dev/null +++ b/qpid/tools/src/java/TODO @@ -0,0 +1,6 @@ +- The qpid-qmf2-tools package needs a README.txt for its release package + - Could just be a link to the main one in this dir, or vice versa. +- The LICENCE and/or NOTICE files inluded in the binary assemblies need updated to account for any 3rd party dependencies + + +- This file needs deleted. Deliberately not licenced. diff --git a/qpid/tools/src/java/pom.xml b/qpid/tools/src/java/pom.xml new file mode 100644 index 0000000000..5176aeafd4 --- /dev/null +++ b/qpid/tools/src/java/pom.xml @@ -0,0 +1,44 @@ + + + + 4.0.0 + + + org.apache.qpid + qpid-parent + 1.0 + + + org.apache.qpid + qpid-qmf2-parent + Qpid QMF2 Parent + 0.30-SNAPSHOT + pom + + + qpid-qmf2 + qpid-qmf2-rest + qpid-qmf2-tools + qpid-broker-plugins-management-qmf2 + qpid-qmf2-test + + + + + + diff --git a/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/LICENSE b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/NOTICE b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/NOTICE new file mode 100644 index 0000000000..cb35331d07 --- /dev/null +++ b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/NOTICE @@ -0,0 +1,5 @@ +Apache Qpid QMF2 Java Broker Management Plugin +Copyright 2012-2014 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). diff --git a/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/README.txt b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/README.txt new file mode 100644 index 0000000000..ec03527925 --- /dev/null +++ b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/README.txt @@ -0,0 +1,103 @@ +/* + * + * 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. + * + */ + +The Qpid Java Broker by default uses either JMX or an HTTP Management GUI & REST API, however by building +a QMF2 Agent as a Java Broker plugin it's possible to provide better synergy between the C++ and Java Brokers +and allow the Java Broker to be controlled by the Qpid Command Line tools and also via the QMF2 REST API +and GUI thus providing a unified view across a mixture of C++ and Java Brokers. + + +To build the Java Broker QMF2 plugin do: + +mvn clean package + +This will build the main plugin jar, and additionally a tar.gz release assembly which contains all the necessary +dependencies to install the plugin in a broker installation. To install the plugin, copy all of the files from +the lib/ dir of the assembly into the lib/ or lib/plugins dir of your extracted broker installation. + + +************************************************* Config ************************************************** + +The Java Broker stores its main configuration in a JSON file (default: $QPID_WORK/config.json), which is typically +managed file via the Web Management GUI. It is IMPORTANT to ensure that the following: + +{ + "name" : "qmf2Management", + "type" : "MANAGEMENT-QMF2", + "connectionURL" : "amqp://guest:guest@/?brokerlist='tcp://0.0.0.0:5672'" + } + +is added to the "plugins" list of $QPID_WORK/config.json or the Plugin will not be used. Each entry in the file +has an "id" property, but it is not necessary to specify one here as it will get automatically initialised the first +time the broker starts with the updated configuration. + +The "connectionURL" property is particularly important. The Plugin connects to AMQP via the JMS Client so +"connectionURL" represents a valid Java ConnectionURL to the Broker so the username/password and any other +ConnectionURL configuration needs to be valid as for any other AMQP Connection to the Broker. + + +If the QMF GUI is to be used then either the -p option of QpidRestAPI.sh should be used to set the REST Server's +HTTP port to something other than 8080, or the brokers list of "ports" in $QPID_WORK/config.json should be modified from e.g. +{ + "id" : , + "name" : "HTTP", + "port" : 8080, + "protocols" : [ "HTTP" ] + } + +to + +{ + "id" : , + "name" : "HTTP", + "port" : 9090, + "protocols" : [ "HTTP" ] + } + +So that the HTTP ports don't conflict. + + +******************************************** Troubleshooting ********************************************** + +If all has gone well you should see + +[Broker] MNG-1001 : QMF2 Management Startup +[Broker] MNG-1004 : QMF2 Management Ready + +in the Broker log messages when you run qpid-server, if you don't see these then there is likely to be a problem. + +1. Check your broker/plugin installation directory (e.g. $QPID_HOME/lib or $QPID_HOME/lib/plugins) now contains +at least the following additional jars: +qpid-qmf2-.jar +qpid-broker-plugins-management-qmf2-.jar +qpid-client-.jar +geronimo-jms_1.1_spec-1.0.jar + +2. If the jars mentioned above are present and correct in the plugins directory the most likely cause of failure +is incorrect configuration. + +Ensure the steps outlined in the Config section have been taken to add the plugin configuration. + +Additionally, in the top-level Broker config the "defaultVirtualHost" *MUST* be set to the name of the +default Virtual Host. If this property is not set or is set to a name that doesn't match the name of one of +the Virtual Hosts then the Plugin will not start correctly. + + diff --git a/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/pom.xml b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/pom.xml new file mode 100644 index 0000000000..10228911eb --- /dev/null +++ b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/pom.xml @@ -0,0 +1,84 @@ + + + + 4.0.0 + + + org.apache.qpid + qpid-qmf2-parent + 0.30-SNAPSHOT + + + qpid-broker-plugins-management-qmf2 + Qpid QMF2 Management Broker Plug-in + QMF2 Management broker plug-in + + + + org.apache.qpid + qpid-qmf2 + ${project.version} + compile + + + + org.apache.qpid + qpid-broker-core + ${project.version} + provided + + + + org.apache.qpid + qpid-broker-codegen + ${project.version} + provided + + + + org.slf4j + slf4j-api + ${slf4j-version} + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + src/main/assembly/qpid-broker-plugins-management-qmf2-bin.xml + + + + + create-qpid-broker-plugins-management-qmf2-assembly + package + + single + + + + + + + + diff --git a/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/assembly/qpid-broker-plugins-management-qmf2-bin.xml b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/assembly/qpid-broker-plugins-management-qmf2-bin.xml new file mode 100644 index 0000000000..d6f4326127 --- /dev/null +++ b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/assembly/qpid-broker-plugins-management-qmf2-bin.xml @@ -0,0 +1,28 @@ + + bin + + tar.gz + + qpid-broker-plugins-management-qmf2/${project.version} + + + / + + LICENSE + NOTICE + README.txt + + 0644 + 0755 + + + + + /lib + true + + + + diff --git a/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/QmfManagementAgent.java b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/QmfManagementAgent.java new file mode 100644 index 0000000000..4246bd7336 --- /dev/null +++ b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/QmfManagementAgent.java @@ -0,0 +1,600 @@ +/* + * + * 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.server.qmf2; + +// Misc Imports + +import static org.apache.qpid.qmf2.common.WorkItem.WorkItemType.METHOD_CALL; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.qpid.qmf2.agent.Agent; +import org.apache.qpid.qmf2.agent.MethodCallParams; +import org.apache.qpid.qmf2.agent.MethodCallWorkItem; +import org.apache.qpid.qmf2.agent.QmfAgentData; +import org.apache.qpid.qmf2.common.ObjectId; +import org.apache.qpid.qmf2.common.QmfEventListener; +import org.apache.qpid.qmf2.common.QmfException; +import org.apache.qpid.qmf2.common.WorkItem; +import org.apache.qpid.qmf2.util.ConnectionHelper; +import org.apache.qpid.server.model.Binding; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfigurationChangeListener; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.Connection; +import org.apache.qpid.server.model.Consumer; +import org.apache.qpid.server.model.Exchange; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.Session; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.model.VirtualHostNode; + +// Simple Logging Facade 4 Java +// QMF2 Imports +// Java Broker model Imports + +/** + * This class implements a QMF2 Agent providing access to the Java broker Management Objects via QMF2 thus + * allowing the Java Broker to be managed in the same way to the C++ Broker. + *

+ * The intention is for the QmfManagementAgent to conform to the same Management Schema as the C++ Broker + * (e.g. as specified in management-schema.xml) in order to provide maximum cohesion between the + * two Broker implementations, however that's not entirely possible given differences between the underlying + * Management Models. + *

+ * This Plugin attempts to map properties from the Java org.apache.qpid.server.model.* classes to equivalent + * properties and statistics in the C++ broker's Management Schema rather than expose them "natively", this is + * in order to try and maximise alignment between the two implementations and to try to allow the Java Broker + * to be managed by the Command Line tools used with the C++ Broker such as qpid-config etc. it's also to + * enable the Java Broker to be accessed via the QMF2 REST API and GUI. + * + * @author Fraser Adams + */ +public class QmfManagementAgent implements ConfigurationChangeListener, QmfEventListener +{ + private static final Logger _log = LoggerFactory.getLogger(QmfManagementAgent.class); + + // Set heartbeat interval to 10 seconds. TODO Should probably be config driven, but I *think* that this is + // different than "heartbeat.delay" and "heartbeat.timeoutFactor" currently present in the config? + private static final int HEARTBEAT_INTERVAL = 10; + private Agent _agent = null; + + // The first Connection Object relates to the QmfManagementAgent, we use this flag to avoid mapping that Connection + // to a QMF Object thus hiding it from Consoles. This is done to provide consistency with the C++ Broker which + // also "hides" its own private AMQP Connections, Queues & Bindings. + private boolean agentConnection = true; + + private final Broker _broker; // Passed in by Plugin bootstrapping. + private final String _defaultVirtualHost; // Pulled from the broker attributes. + + /** + * A Map of QmfAgentData keyed by ConfiguredObject. This is mainly used for Management Object "lifecycle management". + * In an ideal world the Agent class could retain all information, but I want to track ConfiguredObject state and + * use that to create and delete QmfAgentData data, which means I need to be able to *find* the QmfAgentData and + * the *official* Agent API doesn't have a public method to query Objects (though the evaluateQuery() method used + * by Query Subscriptions could be used, but it's not really a "public API method" so probably best not to use it) + * Arguably this is what the AgentExternal subclass of Agent is for whereby queries are handled in the Agent + * implementation by handling "(wi.getType() == QUERY)" but the AgentExternal 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. + *

+ * So rather than go through all that faff we simply retain an additional Map as below which allows navigation + * between the ConfiguredObject and QmfAgentData. The subclasses of QmfAgentData will contain references to + * allow navigation back to the concrete subclasses of ConfiguredObject if necessary. + * The capacity of 100 is pretty arbitrary but the default of 16 seems too low for a ManagementAgent. + */ + private Map _objects = new ConcurrentHashMap(100); + + /** + * Constructor. Creates the AMQP Connection to the Broker and starts the QMF2 Agent. + * @param url the Connection URL to be used to construct the AMQP Connection. + * @param broker the root Broker Management Object from which the other Management Objects may be obtained. + * to work without explicitly setting a Virtual Host, which I think is necessary because the C++ Broker and + * the python command line tools aren't currently Virtual Host aware (are they?). The intention is to mark + * queues and exchanges with [vhost:/] in other words if we want to add things to + * the non-default Virtual Host prefix their names with [vhost:/]. This approach *ought* to allow + * non-Virtual Host aware command line tools the ability to add queues/exchanges to a particular vhost. + */ + public QmfManagementAgent(final String url, final Broker broker) + { + _broker = broker; + _defaultVirtualHost = (String)broker.getAttribute("defaultVirtualHost"); + + try + { + // Create the actual JMS Connection. ConnectionHelper allows us to work with a variety of URL + // formats so we can abstract away from the somewhat complex Java AMQP URL format. + javax.jms.Connection connection = ConnectionHelper.createConnection(url); + if (connection == null) + { + _log.info("QmfManagementAgent Constructor failed due to null AMQP Connection"); + } + else + { + _agent = new Agent(this, HEARTBEAT_INTERVAL); + // Vendor and Product are deliberately set to be the same as for the C++ broker. + _agent.setVendor("apache.org"); + _agent.setProduct("qpidd"); + _agent.setConnection(connection); + + // Register the schema for the Management Objects. These don't have to be completely populated + // the minimum is to register package name and class name for the QmfAgentData. + _agent.registerObjectClass(org.apache.qpid.server.qmf2.agentdata.Broker.getSchema()); + _agent.registerObjectClass(org.apache.qpid.server.qmf2.agentdata.Connection.getSchema()); + _agent.registerEventClass(org.apache.qpid.server.qmf2.agentdata.Connection.getClientConnectSchema()); + _agent.registerEventClass(org.apache.qpid.server.qmf2.agentdata.Connection.getClientDisconnectSchema()); + + _agent.registerObjectClass(org.apache.qpid.server.qmf2.agentdata.Exchange.getSchema()); + _agent.registerEventClass(org.apache.qpid.server.qmf2.agentdata.Exchange.getExchangeDeclareSchema()); + _agent.registerEventClass(org.apache.qpid.server.qmf2.agentdata.Exchange.getExchangeDeleteSchema()); + + _agent.registerObjectClass(org.apache.qpid.server.qmf2.agentdata.Queue.getSchema()); + _agent.registerEventClass(org.apache.qpid.server.qmf2.agentdata.Queue.getQueueDeclareSchema()); + _agent.registerEventClass(org.apache.qpid.server.qmf2.agentdata.Queue.getQueueDeleteSchema()); + + _agent.registerObjectClass(org.apache.qpid.server.qmf2.agentdata.Binding.getSchema()); + _agent.registerEventClass(org.apache.qpid.server.qmf2.agentdata.Binding.getBindSchema()); + _agent.registerEventClass(org.apache.qpid.server.qmf2.agentdata.Binding.getUnbindSchema()); + + _agent.registerObjectClass(org.apache.qpid.server.qmf2.agentdata.Subscription.getSchema()); + _agent.registerEventClass(org.apache.qpid.server.qmf2.agentdata.Subscription.getSubscribeSchema()); + _agent.registerEventClass(org.apache.qpid.server.qmf2.agentdata.Subscription.getUnsubscribeSchema()); + + _agent.registerObjectClass(org.apache.qpid.server.qmf2.agentdata.Session.getSchema()); + + // Initialise QmfAgentData Objects and track changes to the broker Management Objects. + registerConfigurationChangeListeners(); + } + } + catch (QmfException qmfe) + { + _log.info("QmfException {} caught in QmfManagementAgent Constructor", qmfe.getMessage()); + _agent = null; // Causes isConnected() to be false and thus prevents the "QMF2 Management Ready" message. + } + catch (Exception e) + { + _log.info("Exception {} caught in QmfManagementAgent Constructor", e.getMessage()); + _agent = null; // Causes isConnected() to be false and thus prevents the "QMF2 Management Ready" message. + } + } + + /** + * Close the QmfManagementAgent clearing the QMF2 Agent and freeing its resources. + */ + public void close() + { + if (isConnected()) + { + _agent.destroy(); + } + } + + /** + * Returns whether the Agent is connected and running. + * @return true if the Agent is connected and running otherwise return false. + */ + public boolean isConnected() + { + return _agent != null; + } + + /** + * This method initialises the initial set of QmfAgentData Objects and tracks changes to the Broker Management + * Objects via the childAdded() method call. + */ + private void registerConfigurationChangeListeners() + { + childAdded(null, _broker); + + for (VirtualHostNode vhostNode : _broker.getVirtualHostNodes()) + { + VirtualHost vhost = vhostNode.getVirtualHost(); + + // We don't add QmfAgentData VirtualHost objects. Possibly TODO, but it's a bit awkward at the moment + // because the C++ Broker doesn't *seem* to do much with them and the command line tools such + // as qpid-config don't appear to be VirtualHost aware. A way to stay compatible is to mark queues, + // exchanges etc with [vhost:/] (see Constructor comments). + vhost.addChangeListener(this); + + for (Connection connection : vhost.getConnections()) + { + childAdded(vhost, connection); + + for (Session session : connection.getSessions()) + { + childAdded(connection, session); + + if (session.getConsumers() != null) + { + for (Consumer subscription : session.getConsumers()) + { + childAdded(session, subscription); + } + } + } + } + + // The code blocks for adding Bindings (and adding Queues) contain checks to see if what is being added + // relates to Queues or Bindings for the QmfManagementAgent. If they are QmfManagementAgent related + // we avoid registering the Object as a QMF Object, in other words we "hide" QmfManagementAgent QMF Objects. + // This is done to be consistent with the C++ broker which also "hides" its own Connection, Queue & Binding. + for (Exchange exchange : vhost.getExchanges()) + { + childAdded(vhost, exchange); + + for (Binding binding : exchange.getBindings()) + { + String key = binding.getName(); + if (key.equals("broker") || key.equals("console.request.agent_locate") || + key.startsWith("apache.org:qpidd:") || key.startsWith("TempQueue")) + { // Don't add QMF related Bindings in registerConfigurationChangeListeners as those will relate + } // to the Agent and we want to "hide" those. + else + { + childAdded(exchange, binding); + } + } + } + + for (Queue queue : vhost.getQueues()) + { + boolean agentQueue = false; + for (Binding binding : queue.getBindings()) + { + String key = binding.getName(); + if (key.equals("broker") || key.equals("console.request.agent_locate") || + key.startsWith("apache.org:qpidd:")) + { + agentQueue = true; + break; + } + } + + // Don't add QMF related bindings or Queues in registerConfigurationChangeListeners as those will + // relate to the Agent itself and we want to "hide" those to be consistent with the C++ Broker. + if (!agentQueue) + { + childAdded(vhost, queue); + + for (Binding binding : queue.getBindings()) + { + childAdded(queue, binding); + } + + for (Consumer subscription : queue.getChildren(Consumer.class)) + { + childAdded(queue, subscription); + } + } + } + } + } + + + // ************************* ConfigurationChangeListener implementation methods ************************* + + /** + * ConfigurationChangeListener method called when the state is changed (ignored here). + * @param object the object being modified. + * @param oldState the state of the object prior to this method call. + * @param newState the desired state of the object. + */ + @Override + public void stateChanged(final ConfiguredObject object, final State oldState, final State newState) + { + // no-op + } + + /** + * ConfigurationChangeListener method called when an attribute is set (ignored here). + * @param object the object being modified. + * @param attributeName the name of the object attribute that we want to change. + * @param oldAttributeValue the value of the attribute prior to this method call. + * @param newAttributeValue the desired value of the attribute. + */ + @Override + public void attributeSet(ConfiguredObject object, String attributeName, + Object oldAttributeValue, Object newAttributeValue) + { + // no-op + } + + /** + * ConfigurationChangeListener method called when a child ConfiguredObject is added. + *

+ * This method checks the type of the child ConfiguredObject that has been added and creates the equivalent + * QMF2 Management Object if one doesn't already exist. In most cases it's a one-to-one mapping, but for + * Binding for example the Binding child is added to both Queue and Exchange so we only create the Binding + * QMF2 Management Object once and add the queueRef and exchangeRef reference properties referencing the Queue + * and Exchange parent Objects respectively, Similarly for Consumer (AKA Subscription). + *

+ * This method is also responsible for raising the appropriate QMF2 Events when Management Objects are created. + * @param object the parent object that the child is being added to. + * @param child the child object being added. + */ + @Override + public void childAdded(final ConfiguredObject object, final ConfiguredObject child) + { +//System.out.println("childAdded: " + child.getClass().getSimpleName() + "." + child.getName()); + + QmfAgentData data = null; + + if (child instanceof Broker) + { + data = new org.apache.qpid.server.qmf2.agentdata.Broker((Broker)child); + } + else if (child instanceof Connection) + { + if (!agentConnection && !_objects.containsKey(child)) + { + // If the parent object is the default vhost set it to null so that the Connection ignores it. + VirtualHost vhost = (object.getName().equals(_defaultVirtualHost)) ? null : (VirtualHost)object; + data = new org.apache.qpid.server.qmf2.agentdata.Connection(vhost, (Connection)child); + _objects.put(child, data); + + // Raise a Client Connect Event. + _agent.raiseEvent(((org.apache.qpid.server.qmf2.agentdata.Connection)data).createClientConnectEvent()); + } + agentConnection = false; // Only ignore the first Connection, which is the one from the Agent. + } + else if (child instanceof Session) + { + if (!_objects.containsKey(child)) + { + QmfAgentData ref = _objects.get(object); // Get the Connection QmfAgentData so we can get connectionRef. + if (ref != null) + { + data = new org.apache.qpid.server.qmf2.agentdata.Session((Session)child, ref.getObjectId()); + _objects.put(child, data); + } + } + } + else if (child instanceof Exchange) + { + if (!_objects.containsKey(child)) + { + // If the parent object is the default vhost set it to null so that the Connection ignores it. + VirtualHost vhost = (object.getName().equals(_defaultVirtualHost)) ? null : (VirtualHost)object; + data = new org.apache.qpid.server.qmf2.agentdata.Exchange(vhost, (Exchange)child); + _objects.put(child, data); + + // Raise an Exchange Declare Event. + _agent.raiseEvent(((org.apache.qpid.server.qmf2.agentdata.Exchange)data).createExchangeDeclareEvent()); + + } + } + else if (child instanceof Queue) + { + if (!_objects.containsKey(child)) + { + // If the parent object is the default vhost set it to null so that the Connection ignores it. + VirtualHost vhost = (object.getName().equals(_defaultVirtualHost)) ? null : (VirtualHost)object; + data = new org.apache.qpid.server.qmf2.agentdata.Queue(vhost, (Queue)child); + _objects.put(child, data); + + // Raise a Queue Declare Event. + _agent.raiseEvent(((org.apache.qpid.server.qmf2.agentdata.Queue)data).createQueueDeclareEvent()); + } + } + else if (child instanceof Binding) + { + // Bindings are a little more complex because in QMF bindings contain exchangeRef and queueRef properties + // whereas with the Java Broker model Binding is a child of Queue and Exchange. To cope with this we + // first try to create or retrieve the QMF Binding Object then add either the Queue or Exchange reference + // depending on whether Queue or Exchange was the parent of this addChild() call. + if (!_objects.containsKey(child)) + { + data = new org.apache.qpid.server.qmf2.agentdata.Binding((Binding)child); + _objects.put(child, data); + + String eName = ((Binding)child).getExchange().getName(); + if (!eName.equals("<>")) // Don't send Event for Binding to default direct. + { + // Raise a Bind Event. + _agent.raiseEvent(((org.apache.qpid.server.qmf2.agentdata.Binding)data).createBindEvent()); + } + } + + org.apache.qpid.server.qmf2.agentdata.Binding binding = + (org.apache.qpid.server.qmf2.agentdata.Binding)_objects.get(child); + + QmfAgentData ref = _objects.get(object); + if (ref != null) + { + if (object instanceof Queue) + { + binding.setQueueRef(ref.getObjectId()); + } + else if (object instanceof Exchange) + { + binding.setExchangeRef(ref.getObjectId()); + } + } + } + else if (child instanceof Consumer) // AKA Subscription + { + // Subscriptions are a little more complex because in QMF Subscriptions contain sessionRef and queueRef + // properties whereas with the Java Broker model Consumer is a child of Queue and Session. To cope with + // this we first try to create or retrieve the QMF Subscription Object then add either the Queue or + // Session reference depending on whether Queue or Session was the parent of this addChild() call. + if (!_objects.containsKey(child)) + { + data = new org.apache.qpid.server.qmf2.agentdata.Subscription((Consumer)child); + _objects.put(child, data); + } + + org.apache.qpid.server.qmf2.agentdata.Subscription subscription = + (org.apache.qpid.server.qmf2.agentdata.Subscription)_objects.get(child); + + QmfAgentData ref = _objects.get(object); + if (ref != null) + { + if (object instanceof Queue) + { + subscription.setQueueRef(ref.getObjectId(), (Queue)object); + // Raise a Subscribe Event - N.B. Need to do it *after* we've set the queueRef. + _agent.raiseEvent(subscription.createSubscribeEvent()); + } + else if (object instanceof Session) + { + subscription.setSessionRef(ref.getObjectId()); + } + } + } + + try + { + // If we've created new QmfAgentData we register it with the Agent. + if (data != null) + { + _agent.addObject(data); + } + } + catch (QmfException qmfe) + { + _log.info("QmfException {} caught in QmfManagementAgent.addObject()", qmfe.getMessage()); + } + + child.addChangeListener(this); + } + + + /** + * ConfigurationChangeListener method called when a child ConfiguredObject is removed. + *

+ * This method checks the type of the child ConfiguredObject that has been removed and raises the appropriate + * QMF2 Events, it then destroys the QMF2 Management Object and removes the mapping between child and the QMF Object. + * + * @param object the parent object that the child is being removed from. + * @param child the child object being removed. + */ + @Override + public void childRemoved(final ConfiguredObject object, final ConfiguredObject child) + { +//System.out.println("childRemoved: " + child.getClass().getSimpleName() + "." + child.getName()); + + child.removeChangeListener(this); + + // Look up the associated QmfAgentData and mark it for deletion by the Agent. + QmfAgentData data = _objects.get(child); + + if (data != null) + { + if (child instanceof Connection) + { + // Raise a Client Disconnect Event. + _agent.raiseEvent(((org.apache.qpid.server.qmf2.agentdata.Connection)data).createClientDisconnectEvent()); + } + else if (child instanceof Session) + { + // no-op, don't need to do anything specific when Session is removed. + } + else if (child instanceof Exchange) + { + // Raise an Exchange Delete Event. + _agent.raiseEvent(((org.apache.qpid.server.qmf2.agentdata.Exchange)data).createExchangeDeleteEvent()); + } + else if (child instanceof Queue) + { + // Raise a Queue Delete Event. + _agent.raiseEvent(((org.apache.qpid.server.qmf2.agentdata.Queue)data).createQueueDeleteEvent()); + } + else if (child instanceof Binding) + { + String eName = ((Binding)child).getExchange().getName(); + if (!eName.equals("<>")) // Don't send Event for Unbinding from default direct. + { + // Raise an Unbind Event. + _agent.raiseEvent(((org.apache.qpid.server.qmf2.agentdata.Binding)data).createUnbindEvent()); + } + } + else if (child instanceof Consumer) + { + // Raise an Unsubscribe Event. + _agent.raiseEvent(((org.apache.qpid.server.qmf2.agentdata.Subscription)data).createUnsubscribeEvent()); + } + + data.destroy(); + } + + // Remove the mapping from the internal ConfiguredObject->QmfAgentData Map. + _objects.remove(child); + } + + // ******************************* QmfEventListener implementation method ******************************* + + /** + * Callback method triggered when the underlying QMF2 Agent has WorkItems available for processing. + * The purpose of this method is mainly to handle the METHOD_CALL WorkItem and demultiplex & delegate + * to the invokeMethod() call on the relevant concrete QmfAgentData Object. + * @param wi the WorkItem that has been passed by the QMF2 Agent to be processed here (mainly METHOD_CALL). + */ + @Override + public void onEvent(final WorkItem wi) + { + if (wi.getType() == METHOD_CALL) + { + MethodCallWorkItem item = (MethodCallWorkItem)wi; + MethodCallParams methodCallParams = item.getMethodCallParams(); + + String methodName = methodCallParams.getName(); + ObjectId objectId = methodCallParams.getObjectId(); + + // Look up QmfAgentData by ObjectId from the Agent's internal Object store. + QmfAgentData object = _agent.getObject(objectId); + if (object == null) + { + _agent.raiseException(item.getHandle(), "No object found with ID=" + objectId); + } + else + { + // If we've found a valid QmfAgentData check it's a Broker or Queue and if so call the generic + // invokeMethod on these objects, if not send an Exception as we don't support methods on + // other classes yet. + if (object instanceof org.apache.qpid.server.qmf2.agentdata.Broker) + { + org.apache.qpid.server.qmf2.agentdata.Broker broker = + (org.apache.qpid.server.qmf2.agentdata.Broker) object; + broker.invokeMethod(_agent, item.getHandle(), methodName, methodCallParams.getArgs()); + } + else if (object instanceof org.apache.qpid.server.qmf2.agentdata.Queue) + { + org.apache.qpid.server.qmf2.agentdata.Queue queue = + (org.apache.qpid.server.qmf2.agentdata.Queue) object; + queue.invokeMethod(_agent, item.getHandle(), methodName, methodCallParams.getArgs()); + } + else + { + _agent.raiseException(item.getHandle(), "Unknown Method " + methodName + " on " + + object.getClass().getSimpleName()); + } + } + } + } +} diff --git a/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/QmfManagementPlugin.java b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/QmfManagementPlugin.java new file mode 100644 index 0000000000..d8e7a73a13 --- /dev/null +++ b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/QmfManagementPlugin.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.server.qmf2; + +import org.apache.qpid.server.model.ManagedAttribute; +import org.apache.qpid.server.model.ManagedObject; +import org.apache.qpid.server.model.Plugin; + +@ManagedObject( category = false, type = "MANAGEMENT-QMF2" ) +public interface QmfManagementPlugin> extends Plugin +{ + + // attributes + String CONNECTION_URL = "connectionURL"; + + @ManagedAttribute( automate = true , defaultValue = "amqp://guest:guest@/?brokerlist='tcp://0.0.0.0:5672'") + String getConnectionURL(); +} diff --git a/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/QmfManagementPluginImpl.java b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/QmfManagementPluginImpl.java new file mode 100644 index 0000000000..63260be6be --- /dev/null +++ b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/QmfManagementPluginImpl.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.server.qmf2; + +// Misc Imports +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.server.logging.messages.ManagementConsoleMessages; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Exchange; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.ManagedAttributeField; +import org.apache.qpid.server.model.ManagedObjectFactoryConstructor; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.model.VirtualHostNode; +import org.apache.qpid.server.model.adapter.AbstractPluginAdapter; + +// Simple Logging Facade 4 Java +// Java Broker Management Imports + +/** + * This class is a Qpid Java Broker Plugin which follows the Plugin API added in Qpid 0.22 it implements + * org.apache.qpid.server.model.Plugin and extends org.apache.qpid.server.model.adapter.AbstractPluginAdapter. + *

+ * This Plugin provides access to the Java Broker Management Objects via QMF2 thus allowing the Java Broker to + * be managed and monitored in the same way as the C++ Broker. + *

+ * The intention is for the Java Broker QmfManagementPlugin to conform to the same Management Schema as the C++ + * Broker (e.g. as specified in the management-schema.xml) in order to provide maximum cohesion between the + * two Broker implementations, however that's not entirely possible given differences between the underlying + * Management Models. The ultimate aim is to align the Management Models of the two Qpid Brokers and migrate + * to the AMQP 1.0 Management architecture when it becomes available. + *

+ * This Plugin attempts to map properties from the Java org.apache.qpid.server.model.* classes to equivalent + * properties and statistics in the C++ Broker's Management Schema rather than expose them "natively", this is + * in order to try and maximise alignment between the two implementations and to try to allow the Java Broker + * to be managed by the Command Line tools used with the C++ Broker such as qpid-config etc. it's also to + * enable the Java Broker to be accessed via the QMF2 REST API and GUI. + *

+ * This class only bootstraps the ManagementPlugin, the actual business logic is run from QmfManagementAgent. + * It's worth also mentioning that this Plugin actually establishes an AMQP Connection to the Broker via JMS. + * As it's a Broker Plugin it could conceivably use the low level Broker internal transport, this would probably + * be a little more efficient, but OTOH by using the JMS based approach I can use the QMF2 Agent code + * directly and implementing a complete QMF2 Agent for the Java Broker becomes "fairly simple" only requiring + * mappings between the org.apache.qpid.server.model.* classes and their QmfAgentData equivalents. + *

+ * This Plugin requires config to be set, if this is not done the Plugin will not bootstrap. Config may be + * set in $QPID_WORK/config.json as part of the "plugins" config e.g. + *

+ * "plugins" : [ {
+ *    "id" : "26887211-842c-3c4a-ab09-b1a1f64de369",
+ *    "name" : "qmf2Management",
+ *    "pluginType" : "MANAGEMENT-QMF2",
+ *    "connectionURL" : "amqp://guest:guest@/?brokerlist='tcp://0.0.0.0:5672'"
+ * }]
+ * 
+ * @author Fraser Adams + */ +public class QmfManagementPluginImpl extends AbstractPluginAdapter implements QmfManagementPlugin +{ + private static final Logger _log = LoggerFactory.getLogger(QmfManagementPluginImpl.class); + + private static final String OPERATIONAL_LOGGING_NAME = "QMF2"; + + /************* Static initialiser used to implement org.apache.qpid.server.model.Plugin *************/ + + public static final String PLUGIN_TYPE = "MANAGEMENT-QMF2"; + public static final String QMF_DEFAULT_DIRECT = "qmf.default.direct"; + public static final String QMF_DEFAULT_TOPIC = "qmf.default.topic"; + + + /************************************ End of Static initialiser *************************************/ + + private final Broker _broker; // Passed in by Plugin bootstrapping. + private String _defaultVirtualHost; // Pulled from the broker attributes. + + @ManagedAttributeField + private String _connectionURL; // Pulled from the Plugin config. + private QmfManagementAgent _agent; + + /** + * Constructor, called at broker startup by QmfManagementFactory.createInstance(). + * @param attributes a Map containing configuration information for the Plugin. + * @param broker the root Broker Management Object from which the other Management Objects may be obtained. + */ + @ManagedObjectFactoryConstructor + public QmfManagementPluginImpl(Map attributes, Broker broker) + { + super(attributes, broker); + _broker = broker; + } + + @Override + protected void onOpen() + { + super.onOpen(); + _defaultVirtualHost = _broker.getDefaultVirtualHost(); + } + + /** + * Set the state of the Plugin, I believe that this is called from the BrokerAdapter object when it + * has its own state set to State.ACTIVE or State.STOPPED. + * When State.ACTIVE is set this calls the start() method to startup the Plugin, when State.STOPPED + * is set this calls the stop() method to shutdown the Plugin. + * @param desiredState the desired state of the Plugin (either State.ACTIVE or State.STOPPED). + * @return true if a valid state has been set, otherwise false. + */ + @Override // From org.apache.qpid.server.model.adapter.AbstractAdapter + protected boolean setState(State desiredState) + { + if (desiredState == State.ACTIVE) + { + start(); + return true; + } + else if (desiredState == State.STOPPED) + { + stop(); + return true; + } + else + { + _log.info("QmfManagementPlugin.setState() received invalid desiredState {}", desiredState); + return false; + } + } + + /** + * Start the Plugin. Note that we bind the QMF Connection the the default Virtual Host, this is important + * in order to allow C++ or Python QMF Consoles to control the Java Broker, as they know nothing of Virtual + * Hosts and their Connection URL formats don't have a mechanism to specify Virtual Hosts. + *

+ * Note too that it may be necessary to create the "qmf.default.direct" and "qmf.default.topic" exchanges + * as these don't exist by default on the Java Broker, however we have to check if they already exist + * as attempting to add an Exchange that already exists will cause IllegalArgumentException. + */ + private void start() + { + // Log "QMF2 Management Startup" message. + getBroker().getEventLogger().message(ManagementConsoleMessages.STARTUP(OPERATIONAL_LOGGING_NAME)); + + // Wrap main startup logic in a try/catch block catching Exception. The idea is that if anything goes + // wrong with QmfManagementPlugin startup it shouldn't fatally prevent the Broker from starting, though + // clearly QMF2 management will not be available. + try + { + // Iterate through the Virtual Hosts looking for the default Virtual Host. When we find the default + // we create the QMF exchanges then construct the QmfManagementAgent passing it the ConnectionURL. + boolean foundDefaultVirtualHost = false; + + for(VirtualHostNode node : _broker.getVirtualHostNodes()) + { + VirtualHost vhost = node.getVirtualHost(); + + if (vhost.getName().equals(_defaultVirtualHost)) + { + foundDefaultVirtualHost = true; + + // Create the QMF2 exchanges if necessary. + if (vhost.getChildByName(Exchange.class, QMF_DEFAULT_DIRECT) == null) + { + Map attributes = new HashMap<>(); + attributes.put(Exchange.NAME, QMF_DEFAULT_DIRECT); + attributes.put(Exchange.TYPE, ExchangeDefaults.DIRECT_EXCHANGE_CLASS); + attributes.put(Exchange.STATE, State.ACTIVE); + attributes.put(Exchange.LIFETIME_POLICY, LifetimePolicy.PERMANENT); + attributes.put(Exchange.DURABLE, true); + vhost.createExchange(attributes); + } + + if (vhost.getChildByName(Exchange.class, QMF_DEFAULT_TOPIC) == null) + { + Map attributes = new HashMap<>(); + attributes.put(Exchange.NAME, QMF_DEFAULT_TOPIC); + attributes.put(Exchange.TYPE, ExchangeDefaults.TOPIC_EXCHANGE_CLASS); + attributes.put(Exchange.STATE, State.ACTIVE); + attributes.put(Exchange.LIFETIME_POLICY, LifetimePolicy.PERMANENT); + attributes.put(Exchange.DURABLE, true); + vhost.createExchange(attributes); + } + + // Now create the *real* Agent which maps Broker Management Objects to QmdAgentData Objects. + _agent = new QmfManagementAgent(_connectionURL, _broker); + } + + + } + // If we can't find a defaultVirtualHost we log that fact, the Plugin can't start in this case. + // Question. If defaultVirtualHost isn't configured or it doesn't match the name of one of the actual + // Virtual Hosts should we make the first one we find the de facto default for this Plugin?? + if (!foundDefaultVirtualHost) + { + _log.info("QmfManagementPlugin.start() could not find defaultVirtualHost"); + } + else if (_agent.isConnected()) + { + // Log QMF2 Management Ready message. + getBroker().getEventLogger().message(ManagementConsoleMessages.READY(OPERATIONAL_LOGGING_NAME)); + } + } + catch (Exception e) // Catch and log any Exception so we avoid Plugin failures stopping Broker startup. + { + _log.info("Exception {} caught in QmfManagementPlugin.start()", e.getMessage()); + } + } + + /** + * Stop the Plugin, closing the QMF Connection and logging "QMF2 Management Stopped". + */ + private void stop() + { + // When the Plugin state gets set to STOPPED we close the QMF Connection. + if (_agent != null) + { + _agent.close(); + } + + // Log "QMF2 Management Stopped" message (may not get displayed). + getBroker().getEventLogger().message(ManagementConsoleMessages.STOPPED(OPERATIONAL_LOGGING_NAME)); + } + + /** + * Accessor to retrieve the connectionURL attribute. + * @return the JMS connectionURL of the Plugin. + */ + public String getConnectionURL() + { + return _connectionURL; + } +} diff --git a/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Binding.java b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Binding.java new file mode 100644 index 0000000000..5feca96bc2 --- /dev/null +++ b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Binding.java @@ -0,0 +1,199 @@ +/* + * + * 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.server.qmf2.agentdata; + +// Misc Imports +import java.util.Map; + +// Simple Logging Facade 4 Java +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// QMF2 Imports +import org.apache.qpid.qmf2.agent.QmfAgentData; +import org.apache.qpid.qmf2.common.QmfEvent; +import org.apache.qpid.qmf2.common.ObjectId; +import org.apache.qpid.qmf2.common.QmfEvent; +import org.apache.qpid.qmf2.common.SchemaEventClass; +//import org.apache.qpid.qmf2.common.SchemaMethod; +import org.apache.qpid.qmf2.common.SchemaObjectClass; +//import org.apache.qpid.qmf2.common.SchemaProperty; + +/** + * This class provides a concrete implementation of QmfAgentData for the Binding Management Object. + * In general it's possible to use QmfAgentData without sub-classing as it's really a "bean" style class + * that retains its properties in a Map, but in the case of the Java Broker Management Agent it's useful + * to sub-class as we need to map between the properties/statistics as specified in the Java Broker + * management model and those specified in qpid/spec/management-schema.xml which is what the C++ broker + * uses. This class retains a reference to its peer org.apache.qpid.server.model.Binding and does the + * necessary mapping when its mapEncode() method is called (which is used to serialise the QmfAgentData). + * + * @author Fraser Adams + */ +public class Binding extends QmfAgentData +{ + private static final Logger _log = LoggerFactory.getLogger(Binding.class); + + /** + * This static initialiser block initialises the QMF2 Schema information needed by the Agent to find + * QmfAgentData and QmfEvent Objects of a given type. + */ + private static final SchemaObjectClass _schema; + private static final SchemaEventClass _bindSchema; + private static final SchemaEventClass _unbindSchema; + + /** + * Returns the schema for the Binding class. + * @return the SchemaObjectClass for the Binding class. + */ + public static SchemaObjectClass getSchema() + { + return _schema; + } + + /** + * Returns the schema for the Bind Event. + * @return the SchemaEventClass for the Bind Event. + */ + public static SchemaEventClass getBindSchema() + { + return _bindSchema; + } + + /** + * Returns the schema for the Unbind Event. + * @return the SchemaEventClass for the Unbind Event. + */ + public static SchemaEventClass getUnbindSchema() + { + return _unbindSchema; + } + + static + { + // Declare the schema for the QMF2 broker class. + _schema = new SchemaObjectClass("org.apache.qpid.broker", "binding"); + + // TODO + //_schema.addProperty(new SchemaProperty("whatHappened", QmfType.TYPE_STRING)); + + // Declare the schema for the QMF2 bind Event class. + _bindSchema = new SchemaEventClass("org.apache.qpid.broker", "bind"); + + // Declare the schema for the QMF2 unbind Event class. + _unbindSchema = new SchemaEventClass("org.apache.qpid.broker", "unbind"); + } + // End of static initialiser. + + private final org.apache.qpid.server.model.Binding _binding; + + /** + * Constructor. + * @param binding the Binding ConfiguredObject from the broker model. + */ + @SuppressWarnings("unchecked") + public Binding(final org.apache.qpid.server.model.Binding binding) + { + super(getSchema()); + _binding = binding; // Will eventually be used in mapEncode() to retrieve statistics. + setValue("bindingKey", binding.getName()); + + Map arguments = binding.getArguments(); + // binding.getArguments() always returns a Map, but with the C++ broker if there are no arguments + // the property isn't populated, so we only add it if the _arguments.size() > 0 + if (arguments.size() > 0) + { + setValue("arguments", arguments); + } + // origin not implemented in Java Broker - not really sure what the origin property means anyway??? + } + + /** + * Set the exchangeRef property. + * @param exchangeRef the exchangeRef ObjectId. + */ + public void setExchangeRef(final ObjectId exchangeRef) + { + setRefValue("exchangeRef", exchangeRef); + } + + /** + * Set the queueRef property. + * @param queueRef the queueRef ObjectId. + */ + public void setQueueRef(final ObjectId queueRef) + { + setRefValue("queueRef", queueRef); + } + + /** + * Factory method to create a Bind Event Object with timestamp of now. + * @return the newly created Bind Event Object. + */ + public QmfEvent createBindEvent() + { + QmfEvent bind = new QmfEvent(_bindSchema); + bind.setSeverity("info"); + bind.setValue("args", _binding.getArguments()); + bind.setValue("exName", _binding.getExchange().getName()); + bind.setValue("key", _binding.getName()); + bind.setValue("qName", _binding.getQueue().getName()); + // TODO Not sure of a way to get these for Java Broker Exchange. + //bind.setValue("rhost", _connection.getName()); + //bind.setValue("user", getStringValue("authIdentity")); + return bind; + } + + /** + * Factory method to create an Unbind Event Object with timestamp of now. + * @return the newly created Unbind Event Object. + */ + public QmfEvent createUnbindEvent() + { + QmfEvent unbind = new QmfEvent(_unbindSchema); + unbind.setSeverity("info"); + unbind.setValue("exName", _binding.getExchange().getName()); + unbind.setValue("key", _binding.getName()); + unbind.setValue("qName", _binding.getQueue().getName()); + // TODO Not sure of a way to get these for Java Broker Exchange. + //unbind.setValue("rhost", _connection.getName()); + //unbind.setValue("user", getStringValue("authIdentity")); + return unbind; + } + + /** + * This method maps the org.apache.qpid.server.model.Binding to QMF2 broker properties where possible then + * serialises into the underlying Map for transmission via AMQP. This method is called by handleQueryRequest() + * in the org.apache.qpid.qmf2.agent.Agent class implementing the main QMF2 Agent behaviour. + * + * @return the underlying map. + */ + @Override + public Map mapEncode() + { + // Statistics + setValue("msgMatched", _binding.getMatches()); + + update(); // TODO only set update if a statistic has actually changed value. + return super.mapEncode(); + } +} diff --git a/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Broker.java b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Broker.java new file mode 100644 index 0000000000..f33a07ecdd --- /dev/null +++ b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Broker.java @@ -0,0 +1,698 @@ +/* + * + * 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.server.qmf2.agentdata; + +// Misc Imports + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.qpid.qmf2.agent.Agent; +import org.apache.qpid.qmf2.agent.QmfAgentData; +import org.apache.qpid.qmf2.common.Handle; +import org.apache.qpid.qmf2.common.ObjectId; +import org.apache.qpid.qmf2.common.QmfData; +import org.apache.qpid.qmf2.common.SchemaObjectClass; +import org.apache.qpid.server.model.Binding; +import org.apache.qpid.server.model.Exchange; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Protocol; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.model.VirtualHostNode; + +// Simple Logging Facade 4 Java +// QMF2 Imports +/*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.QmfType;*/ +// Java Broker model Imports + +/** + * This class provides a concrete implementation of QmfAgentData for the Broker Management Object. + * In general it's possible to use QmfAgentData without sub-classing as it's really a "bean" style class + * that retains its properties in a Map, but in the case of the Java Broker Management Agent it's useful + * to sub-class as we need to map between the properties/statistics as specified in the Java Broker + * management model and those specified in qpid/spec/management-schema.xml which is what the C++ broker + * uses. This class retains a reference to its peer org.apache.qpid.server.model.Broker and does the + * necessary mapping when its mapEncode() method is called (which is used to serialise the QmfAgentData). + * + * @author Fraser Adams + */ +public class Broker extends QmfAgentData +{ + private static final Logger _log = LoggerFactory.getLogger(Broker.class); + + /** + * This static initialiser block initialises the QMF2 Schema information needed by the Agent to find + * QmfAgentData Objects of a given type. + */ + private static final SchemaObjectClass _schema; + public static SchemaObjectClass getSchema() + { + return _schema; + } + + static + { + // Declare the schema for the QMF2 broker class. + _schema = new SchemaObjectClass("org.apache.qpid.broker", "broker"); + + // TODO + //_schema.addProperty(new SchemaProperty("whatHappened", QmfType.TYPE_STRING)); + } + + private final org.apache.qpid.server.model.Broker _broker; // Passed in by Plugin bootstrapping. + private final String _defaultVirtualHost; // Pulled from the broker attributes. + + /** + * This inner class parses the name String that was passed in as a QMF method argument. + * There are a few quirks with this name. In the first instance it may be prefixed with Virtual Host information + * e.g. [vhost:/] this is because in order to allow Java Broker QMF to work with things like + * qpid-config which are not particularly Virtual Host aware prefixing the names seemed to be the most natural, + * if slightly ugly approach. In addition the way bindings are named is of the form: + * /[/] so we need a mechanism to parse the relevant information. + *

+ * N.B. the parsing that takes place in this class makes an assumption that there are no valid exchange or queue + * names that contain a "/". This is probably a reasonable assumption given the way that a binding name is + * constructed, but it's worth recording the restriction here in case such a beast crops up. + *

+ * This class also provides accessors that allow the Exchange, Queue and Binding ConfiguredObjects for the + * name parsed in the Constructor to be retrieved. This is generally useful because although in QMF the create + * and delete methods are invoked on the Broker object in the Java Broker ConfiguredObject model the underlying + * methods are distributed across a number of different classes. + */ + private class NameParser + { + private String _vhostName; + private VirtualHost _vhost = null; + private String _exchangeName = ""; + private Exchange _exchange = null; + private String _queueName = ""; + private Queue _queue = null; + private String _bindingKey = ""; + private Binding _binding = null; + + /** + * NameParser Constructor. + * The Constructor actually does the majority of the parsing, the remaining method are largely just accessors. + * + * @param name the name argument that was retrieved from the QMF method inArgs. This will be the exchange name, + * the queue name or the binding name (which is of the form /[/]) + * exchange and queue names may be prefixed by a Virtual Host name e.g. [vhost:/] + * @param type the type argument that was retrieved from the QMF method inArgs. Valid types are "exchange, + * "queue" or "binding". + */ + public NameParser(final String name, final String type) + { + boolean malformedVHostName = false; + String[] splitName = name.split("/"); // A slash is used as a separator in a couple of scenarios. + if (name.startsWith("vhost:")) + { + if (splitName.length == 1) // If it starts with vhost: the name should also contain at least one "/". + { + malformedVHostName = true; + _vhostName = name; + } + else + { + _vhostName = splitName[0]; + _vhostName = _vhostName.substring(6, _vhostName.length()); + } + } + else + { + _vhostName = _defaultVirtualHost; + } + + // If the vhostName isn't malformed then try to find the actual Virtual Host that it relates to. + // If it is malformed the vhost stays set to null, which will cause an exception to be returned later. + if (!malformedVHostName) + { + for (VirtualHostNode vhostNode : _broker.getVirtualHostNodes()) + { + VirtualHost vhost = vhostNode.getVirtualHost(); + if (vhost.getName().equals(_vhostName)) + { + _vhost = vhost; + break; + } + } + } + + // Populate the exchange, queue and binding names. We only populate the names in the constructor + // when we actually want to find the Object associated with the name we do it "on demand" in the + // relevant accessor and cache the result. + if (type.equals("exchange")) + { + _exchangeName = splitName[splitName.length - 1]; + } + else if (type.equals("queue")) + { + _queueName = splitName[splitName.length - 1]; + } + else if (type.equals("binding")) + { // TODO is there a way to make this parse less nasty and a bit more elegant.... + int i = 0; + String vhost1Name = _defaultVirtualHost; // The exchange and queue vhostName need to be the same. + if (splitName[i].startsWith("vhost:")) // Does the exchange name specify a vhost? + { + vhost1Name = splitName[i]; + i++; + } + + if (i < splitName.length) // Extract the exchange name sans vhost part. + { + _exchangeName = splitName[i]; + i++; + } + + String vhost2Name = _defaultVirtualHost; + if (i < splitName.length && splitName[i].startsWith("vhost:")) // Does the queue name specify a vhost? + { + vhost2Name = splitName[i]; + i++; + } + + // If the exchange and queue vhost names differ set _vhost and _vhostName to null which causes + // an exception that says "VirtualHost names for exchange and queue must match.". + if (!vhost2Name.equals(vhost1Name)) + { + _vhost = null; + _vhostName = null; + } + + if (i < splitName.length) // Extract the queue name sans vhost part. + { + _queueName = splitName[i]; + i++; + } + + if (i < splitName.length) // Extract the binding key if present (it's optional). + { + _bindingKey = splitName[i]; + i++; + } + } // End of binding name parse. + } + + // NameParser accessors. + + /** + * Retrieves the name of the Virtual Host that was parsed from the name supplied in the Constructor. + * @return the parsed Virtual Host name (may be an empty String). + */ + public String getVirtualHostName() + { + return _vhostName; + } + + /** + * Retrieves the Virtual Host with the name that was parsed from the name supplied in the Constructor. + * @return the Virtual Host with the name that was parsed from the name supplied in the Constructor (may be null). + */ + public VirtualHost getVirtualHost() + { + return _vhost; + } + + /** + * Retrieves the name of the Exchange that was parsed from the name supplied in the Constructor. + * @return the parsed Exchange name (may be an empty String). + */ + public String getExchangeName() + { + return _exchangeName; + } + + /** + * Retrieves the Exchange with the name that was parsed from the name supplied in the Constructor. + * @return the Exchange with the name that was parsed from the name supplied in the Constructor (may be null). + */ + public Exchange getExchange() + { + // If we've not previously cached the _exchange and the previously parsed Virtual Host isn't null we do a + // look up for the actual Exchange with the name _exchangeName and cache it. + if (_exchange == null && _vhost != null) + { + for (Exchange exchange : _vhost.getExchanges()) + { + if (exchange.getName().equals(_exchangeName)) + { + _exchange = exchange; + break; + } + } + } + + return _exchange; + } + + /** + * Retrieves the name of the Queue that was parsed from the name supplied in the Constructor. + * @return the parsed Queue name (may be an empty String). + */ + public String getQueueName() + { + return _queueName; + } + + /** + * Retrieves the Queue with the name that was parsed from the name supplied in the Constructor. + * @return the Queue with the name that was parsed from the name supplied in the Constructor (may be null). + */ + public Queue getQueue() + { + // If we've not previously cached the _queue and the previously parsed Virtual Host isn't null we do a + // look up for the actual Queue with the name _queueName and cache it. + if (_queue == null && _vhost != null) + { + for (Queue queue : _vhost.getQueues()) + { + if (queue.getName().equals(_queueName)) + { + _queue = queue; + break; + } + } + } + + return _queue; + } + + /** + * Retrieves the name of the Binding that was parsed from the name supplied in the Constructor. + * @return the parsed Binding name (may be an empty String). + */ + public String getBindingKey() + { + return _bindingKey; + } + + /** + * Retrieves the Binding with the name that was parsed from the name supplied in the Constructor. + * @return the Binding with the name that was parsed from the name supplied in the Constructor (may be null). + */ + public Binding getBinding() + { + // In order to retrieve a Binding it's first necessary to get the Exchange (or Queue) ConfiguredObject. + _exchange = getExchange(); // Need to get it via the accessor as it's initialised by lazy evaluation. + + // If we've not previously cached the _binding and the previously retrieved Exchange isn't null we do a + // look up for the actual Binding with the name _bindingKey and cache it. + if (_binding == null && _exchange != null) + { + for (Binding binding : _exchange.getBindings()) + { + if (binding.getName().equals(_bindingKey)) + { + _binding = binding; + break; + } + } + } + + return _binding; + } + } // End of class NameParser + + /** + * Broker Constructor. + * @param broker the root Broker Management Object from which the other Management Objects may be obtained. + */ + public Broker(final org.apache.qpid.server.model.Broker broker) + { + super(getSchema()); + + _broker = broker; + _defaultVirtualHost = (String)broker.getAttribute("defaultVirtualHost"); + int amqpPort = 5672; // Default AMQP Port. + + // Search through the available Ports on this Broker looking for the AMQP Port using the TCP Transport + // and record that in amqpPort. N.B. The Java Broker supports several Protocol and Transport types so + // it might be good to return more detailed info, but for QMF the "port" property description for the + // Broker Object says "TCP Port for AMQP Service" so doing anything fancier might break consumers. + // TODO The C++ and Java Brokers should really return consistent information. + for (Port port : _broker.getPorts()) + { + boolean isAMQP = false; + boolean isTCP = false; + for (Protocol protocol : port.getProtocols()) + { + isAMQP = protocol.isAMQP(); + if (isAMQP) + { + break; + } + } + + for (Transport transport : port.getTransports()) + { + isTCP = (transport == Transport.TCP); + if (isTCP) + { + break; + } + } + + if (isAMQP && isTCP) + { + amqpPort = port.getPort(); + break; + } + } + + // systemRef is ignored in this implementation. + // stagingThreshold is ignored in this implementation (it's deprecated anyway I believe). + + // Use this name to be fairly consistent with C++ broker which uses "amqp-broker". + // N.B. although it's useful to be able to distinguish between C++ and Java brokers note that the + // _object_name in the ObjectId that we set below uses actually uses "amqp-broker" vice "amqp-java-broker", + // this is because qpid-config uses a "hardcoded" ObjectId to invoke methods so we need to use the same name. + setValue("name", "amqp-java-broker"); + setValue("port", amqpPort); + + // workerThreads doesn't *appear* to be configurable in the Java Broker, looks like there's no pool and the + // Threads just grow with the number of Connections? + setValue("workerThreads", 0); + + // maxConns doesn't *appear* to be configurable in the Java Broker. + setValue("maxConns", 0); + + // The Java Broker ServerSocket seems to be created in org.apache.qpid.transport.network.io.IoNetworkTransport + // In AcceptingThread. The does't appear to use any configuration for ServerSocket, which suggests that the + // backlog is the default value which is assumed to be 10. + setValue("connBacklog", 10); + + // "Technically" this isn't quite the same as for the C++ broker, which pushes management data to a particular + // topic - the subscription "qmf.default.topic/agent.ind.#" grabs that plus heartbeats for the C++ broker. + // This Agent allows the use of the QMF2 Query Subscriptions (which the C++ broker does not!! - the Console + // class fakes this client side for the C++ broker. TODO make sure that the Console does not fake for Java broker. + setValue("mgmtPublish", true); + setValue("mgmtPubInterval", 10); + + setValue("version", org.apache.qpid.common.QpidProperties.getReleaseVersion()); + setValue("dataDir", System.getProperty("QPID_WORK")); + + // ObjectId needs to be set here in Broker because the QMF2 version of qpid-config uses a hardcoded + // _object_name of "org.apache.qpid.broker:broker:amqp-broker" in the _object_id that it sets. + // It *shouldn't* do this and should really use the _object_id of the broker object returned by + // getObjects("broker"), but it does. The following line causes the Agent to use the explicit + // ObjectId below rather than constructing its own, which fixes the qpid-config issue. + // Note we use "amqp-broker" in the ObjectId to be compatible with qpid-config but we set the actual + // name to "amqp-java-broker" as it's useful to be able to distinguish between C++ and Java Brokers. + setObjectId(new ObjectId("", "org.apache.qpid.broker:broker:amqp-broker", 0)); + } + + /** + * This helper method checks the supplied properties Map for the "alternate-exchange" property, if it is present + * the property is removed from the map and the alternate exchange is parsed to recover the Virtual Host name + * and the actual alternate exchange name. If the alternate exchange Virtual Host name is not the same as the + * supplied vhostName this method returns "invalid" otherwise it returns the alternate exchange name or null. + * + * @param vhostName the Virtual Host name that we want to compare the alternate exchange's Virtual Host name with. + * @param properties a Map of properties that might contain "alternate-exchange". + * @return the alternate exchange name if present, null if not present or "invalid" if the Virtual Host name that + * was parsed from the alternate exchange doesn't match the name supplied in the vhostName parameter. + */ + private String parseAlternateExchange(String vhostName, Map properties) + { + String alternateExchange = null; + Object property = properties.get("alternate-exchange"); + if (property != null && property instanceof String) // Alternate exchange has been specified. + { + alternateExchange = property.toString(); + properties.remove("alternate-exchange"); + + String altExVhostName = _defaultVirtualHost; + String[] splitName = alternateExchange.split("/"); + if (alternateExchange.startsWith("vhost:")) + { + altExVhostName = splitName[0]; + altExVhostName = altExVhostName.substring(6, altExVhostName.length()); + } + + // If the Virtual Hosts differ raise an exception and return. + if (!altExVhostName.equals(vhostName)) + { + return "invalid"; + } + } + + return alternateExchange; + } + + /** + * This method acts as a single entry point for QMF methods invoked on the Broker Object. + * + * @param agent the org.apache.qpid.qmf2.agent.Agent instance that we call methodResponse() and raiseException() on. + * @param handle the reply handle used by methodResponse() and raiseException(). + * @param methodName the name of the QMF method being invoked. + * @param inArgs a Map of input arguments wrapped in a QmfData Object. + */ + @SuppressWarnings("unchecked") + public void invokeMethod(Agent agent, Handle handle, String methodName, QmfData inArgs) + { + if (methodName.equals("create") || methodName.equals("delete")) + { + QmfData outArgs = new QmfData(); + + String name = inArgs.getStringValue("name"); + String type = inArgs.getStringValue("type"); + + NameParser nameParser = new NameParser(name, type); + String vhostName = nameParser.getVirtualHostName(); + VirtualHost vhost = nameParser.getVirtualHost(); + + if (vhost == null) + { + if (vhostName == null) + { + agent.raiseException(handle, "VirtualHost names for exchange and queue must match."); + } + else + { + agent.raiseException(handle, "VirtualHost " + vhostName + " not found."); + } + } + else + { + if (methodName.equals("create")) // method = create + { + try + { + //boolean strict = inArgs.getBooleanValue("strict"); + Map properties = inArgs.getValue("properties"); + + boolean durable = false; + Object property = properties.get("durable"); + if (property != null && property instanceof Boolean) + { + Boolean durableProperty = (Boolean)property; + durable = durableProperty.booleanValue(); + properties.remove("durable"); + } + + if (type.equals("exchange")) // create exchange. + { +/* +System.out.println("Create Exchange"); +System.out.println("vhostName = " + vhostName); +System.out.println("exchange name = " + nameParser.getExchangeName()); +System.out.println("properties = " + properties); +*/ + String exchangeType = ""; + property = properties.get("exchange-type"); + if (property != null && property instanceof String) + { + exchangeType = property.toString(); + properties.remove("exchange-type"); + } + + String alternateExchange = parseAlternateExchange(vhostName, properties); + if (alternateExchange != null && alternateExchange.equals("invalid")) + { + agent.raiseException(handle, "Alternate Exchange must belong to the same Virtual Host as the Exchange being added."); + return; + } + + // Note that for Qpid 0.20 the "qpid.msg_sequence=1" and "qpid.ive=1" properties are + // not suppored, indeed no exchange properties seem to be supported yet. + Map attributes = new HashMap<>(); + attributes.put(Exchange.NAME, nameParser.getExchangeName()); + attributes.put(Exchange.STATE, State.ACTIVE); + attributes.put(Exchange.DURABLE, durable); + attributes.put(Exchange.LIFETIME_POLICY, LifetimePolicy.PERMANENT); + attributes.put(Exchange.TYPE, exchangeType); + attributes.put(Exchange.ALTERNATE_EXCHANGE, alternateExchange); + + + vhost.createExchange(attributes); + + } // End of create exchange. + else if (type.equals("queue")) // create queue. + { +/* +System.out.println("Create Queue"); +System.out.println("vhostName = " + vhostName); +System.out.println("queue name = " + nameParser.getQueueName()); +System.out.println("properties = " + properties); +*/ + + // TODO Try to map from the QMF create queue properties to the closest equivalents on + // the Java Broker. Unfortunately there are a *lot* of frustrating little differences. + + + String alternateExchange = parseAlternateExchange(vhostName, properties); + if (alternateExchange != null && alternateExchange.equals("invalid")) + { + agent.raiseException(handle, "Alternate Exchange must belong to the same Virtual Host as the Queue being added."); + return; + } + + // I don't *think* that it make sense to allow setting exclusive or autoDelete to + // a queue created from config. + Map attributes = new HashMap(properties); + attributes.put(Queue.NAME, nameParser.getQueueName()); + attributes.put(Queue.DURABLE, durable); + attributes.put(Queue.LIFETIME_POLICY, LifetimePolicy.PERMANENT); + + + // Set the queue's alternateExchange, which is just a little bit involved...... + // The queue.setAttribute() method needs an org.apache.qpid.server.model.Exchange instance + // not just a name, so we look up org.apache.qpid.server.qmf2.agentdata.Exchange by ID + // and get its associated org.apache.qpid.server.model.Exchange. We can do a look up by ID + // because we needed to use ObjectIds that were based on names in order to allow qpid-config + // to work, so we may as well make use of this convenience here too. + if (alternateExchange != null) + { + ObjectId objectId = + new ObjectId("", "org.apache.qpid.broker:exchange:" + alternateExchange, 0); + + // Look up Exchange QmfAgentData by ObjectId from the Agent's internal Object store. + QmfAgentData object = agent.getObject(objectId); + if (object != null) + { + org.apache.qpid.server.qmf2.agentdata.Exchange ex = + (org.apache.qpid.server.qmf2.agentdata.Exchange)object; + + Exchange altEx = ex.getExchange(); + attributes.put(Queue.ALTERNATE_EXCHANGE, altEx.getId()); + } + } + Queue queue = vhost.createQueue(attributes); + } + else if (type.equals("binding")) // create binding. + { + Exchange exchange = nameParser.getExchange(); + if (exchange == null) + { + agent.raiseException(handle, "Cannot create binding on Exchange " + + nameParser.getExchangeName()); + return; + } + else + { + Map attributes = Collections.emptyMap(); + exchange.createBinding(nameParser.getBindingKey(), nameParser.getQueue(), + properties, attributes); + } + } + + agent.methodResponse(methodName, handle, outArgs, null); + } + catch (Exception e) + { + agent.raiseException(handle, e.getMessage()); + } + } + else // method = delete + { + try + { + if (type.equals("exchange")) // delete exchange. + { + Exchange exchange = nameParser.getExchange(); + if (exchange != null) + { + exchange.delete(); + } + } + else if (type.equals("queue")) // delete queue. + { + Queue queue = nameParser.getQueue(); + if (queue != null) + { + queue.delete(); + } + } + else if (type.equals("binding")) // delete binding. + { + Binding binding = nameParser.getBinding(); + if (binding != null) + { + binding.delete(); + } + } + + agent.methodResponse(methodName, handle, outArgs, null); + } + catch (Exception e) + { + agent.raiseException(handle, e.getMessage()); + } + } + } + } + else // If methodName is not create or delete. + { + agent.raiseException(handle, methodName + " not yet implemented on Broker."); + } + } // End of invokeMethod. + + /** + * This method maps the org.apache.qpid.server.model.Broker to QMF2 broker properties where possible then + * serialises into the underlying Map for transmission via AMQP. This method is called by handleQueryRequest() + * in the org.apache.qpid.qmf2.agent.Agent class implementing the main QMF2 Agent behaviour. + * + * @return the underlying map. + */ + @Override + public Map mapEncode() + { + update(); // Need to do update before setting uptime in order to get the latest getUpdateTime() value. + + // Not sure if there's an "official" broker uptime anywhere, but as the QmfManagementAgent is created when + // the broker is and the Broker object is created then too the following approach should be good enough. + setValue("uptime", getUpdateTime() - getCreateTime()); + + return super.mapEncode(); + } +} diff --git a/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Connection.java b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Connection.java new file mode 100644 index 0000000000..0dc9ba4975 --- /dev/null +++ b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Connection.java @@ -0,0 +1,192 @@ +/* + * + * 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.server.qmf2.agentdata; + +// Misc Imports +import java.util.Map; + +// Simple Logging Facade 4 Java +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// QMF2 Imports +import org.apache.qpid.qmf2.agent.QmfAgentData; +import org.apache.qpid.qmf2.common.QmfEvent; +import org.apache.qpid.qmf2.common.SchemaEventClass; +//import org.apache.qpid.qmf2.common.SchemaMethod; +import org.apache.qpid.qmf2.common.SchemaObjectClass; +//import org.apache.qpid.qmf2.common.SchemaProperty; + +/** + * This class provides a concrete implementation of QmfAgentData for the Connection Management Object. + * In general it's possible to use QmfAgentData without sub-classing as it's really a "bean" style class + * that retains its properties in a Map, but in the case of the Java Broker Management Agent it's useful + * to sub-class as we need to map between the properties/statistics as specified in the Java Broker + * management model and those specified in qpid/spec/management-schema.xml which is what the C++ broker + * uses. This class retains a reference to its peer org.apache.qpid.server.model.Connection and does the + * necessary mapping when its mapEncode() method is called (which is used to serialise the QmfAgentData). + * + * @author Fraser Adams + */ +public class Connection extends QmfAgentData +{ + private static final Logger _log = LoggerFactory.getLogger(Connection.class); + + /** + * This static initialiser block initialises the QMF2 Schema information needed by the Agent to find + * QmfAgentData and QmfEvent Objects of a given type. + */ + private static final SchemaObjectClass _schema; + private static final SchemaEventClass _clientConnectSchema; + private static final SchemaEventClass _clientDisconnectSchema; + + /** + * Returns the schema for the Connection class. + * @return the SchemaObjectClass for the Connection class. + */ + public static SchemaObjectClass getSchema() + { + return _schema; + } + + /** + * Returns the schema for the Client Connect Event. + * @return the SchemaEventClass for the Client Connect Event. + */ + public static SchemaEventClass getClientConnectSchema() + { + return _clientConnectSchema; + } + + /** + * Returns the schema for the Client Disconnect Event. + * @return the SchemaEventClass for the Client Disconnect Event. + */ + public static SchemaEventClass getClientDisconnectSchema() + { + return _clientDisconnectSchema; + } + + static + { + // Declare the schema for the QMF2 connection class. + _schema = new SchemaObjectClass("org.apache.qpid.broker", "connection"); + + // TODO + //_schema.addProperty(new SchemaProperty("whatHappened", QmfType.TYPE_STRING)); + + // Declare the schema for the QMF2 clientConnect Event class. + _clientConnectSchema = new SchemaEventClass("org.apache.qpid.broker", "clientConnect"); + + // Declare the schema for the QMF2 clientDisconnect Event class. + _clientDisconnectSchema = new SchemaEventClass("org.apache.qpid.broker", "clientDisconnect"); + } + // End of static initialiser. + + private final org.apache.qpid.server.model.Connection _connection; + + /** + * Constructor. + * @param vhost the parent VirtualHost ConfiguredObject from the broker model. + * @param connection the Connection ConfiguredObject from the broker model. + */ + public Connection(final org.apache.qpid.server.model.VirtualHost vhost, + final org.apache.qpid.server.model.Connection connection) + { + super(getSchema()); + _connection = connection; // Will eventually be used to retrieve statistics (when useful ones get populated). + String vhostName = (vhost == null) ? "" : "vhost:" + vhost.getName() + "/"; + String address = vhostName + _connection.getName(); + + // TODO vhostRef - currently just use its name to try and get things working with standard command line tools. + + setValue("address", address); + setValue("incoming", connection.isIncoming()); + + // Although not implemented in Java Broker it's reasonable for them to be false + setValue("SystemConnection", false); // Is the S in System really a capital? not implemented in Java Broker + setValue("userProxyAuth", false); // Not implemented in Java Broker + setValue("federationLink", false); // Not implemented in Java Broker + setValue("authIdentity", (connection.getPrincipal() == null ? "unknown" : connection.getPrincipal())); + setValue("remoteProcessName", (connection.getRemoteProcessName() == null ? + "unknown" : connection.getRemoteProcessName())); + setValue("remotePid", (connection.getRemoteProcessPid() == null ? + "unknown" : connection.getRemoteProcessPid())); + setValue("remoteParentPid", "unknown"); // remoteProcessName not supported in Java Broker + + // shadow Not implemented in Java Broker + // saslMechanism Not implemented in Java Broker + // saslSsf Not implemented in Java Broker + // protocol Not implemented in Java Broker + } + + /** + * Factory method to create a Client Connect Event Object with timestamp of now. + * @return the newly created Client Connect Event Object. + */ + public QmfEvent createClientConnectEvent() + { + QmfEvent clientConnect = new QmfEvent(_clientConnectSchema); + clientConnect.setSeverity("info"); + // TODO Set properties Map - can't really get much info from the org.apache.qpid.server.model.Connection yet. + clientConnect.setValue("rhost", _connection.getName()); + clientConnect.setValue("user", getStringValue("authIdentity")); + return clientConnect; + } + + /** + * Factory method to create a Client Disconnect Event Object with timestamp of now. + * @return the newly created Client Disconnect Event Object. + */ + public QmfEvent createClientDisconnectEvent() + { + QmfEvent clientDisconnect = new QmfEvent(_clientDisconnectSchema); + clientDisconnect.setSeverity("info"); + // TODO Set properties Map - can't really get much info from the org.apache.qpid.server.model.Connection yet. + clientDisconnect.setValue("rhost", _connection.getName()); + clientDisconnect.setValue("user", getStringValue("authIdentity")); + return clientDisconnect; + } + + /** + * This method maps the org.apache.qpid.server.model.Connection to QMF2 connection properties where possible then + * serialises into the underlying Map for transmission via AMQP. This method is called by handleQueryRequest() + * in the org.apache.qpid.qmf2.agent.Agent class implementing the main QMF2 Agent behaviour. + * + * @return the underlying map. + */ + @Override + public Map mapEncode() + { + // Statistics + // closing Not implemented in Java Broker + setValue("framesFromClient", 0); // framesFromClient Not implemented in Java Broker + setValue("framesToClient", 0); // framesToClient Not implemented in Java Broker + setValue("bytesFromClient", _connection.getBytesIn()); + setValue("bytesToClient", _connection.getBytesOut()); + setValue("msgsFromClient", _connection.getMessagesIn()); + setValue("msgsToClient", _connection.getMessagesOut()); + + update(); // TODO only set update if statistics change. + return super.mapEncode(); + } +} diff --git a/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Exchange.java b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Exchange.java new file mode 100644 index 0000000000..e86140b8cd --- /dev/null +++ b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Exchange.java @@ -0,0 +1,254 @@ +/* + * + * 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.server.qmf2.agentdata; + +// Misc Imports +import java.util.Collections; +import java.util.Map; + +// Simple Logging Facade 4 Java +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// QMF2 Imports +import org.apache.qpid.qmf2.agent.QmfAgentData; +import org.apache.qpid.qmf2.common.QmfEvent; +import org.apache.qpid.qmf2.common.ObjectId; +import org.apache.qpid.qmf2.common.SchemaEventClass; +//import org.apache.qpid.qmf2.common.SchemaMethod; +import org.apache.qpid.qmf2.common.SchemaObjectClass; +//import org.apache.qpid.qmf2.common.SchemaProperty; + +import org.apache.qpid.server.model.LifetimePolicy; + +/** + * This class provides a concrete implementation of QmfAgentData for the Exchange Management Object. + * In general it's possible to use QmfAgentData without sub-classing as it's really a "bean" style class + * that retains its properties in a Map, but in the case of the Java Broker Management Agent it's useful + * to sub-class as we need to map between the properties/statistics as specified in the Java Broker + * management model and those specified in qpid/spec/management-schema.xml which is what the C++ broker + * uses. This class retains a reference to its peer org.apache.qpid.server.model.Exchange and does the + * necessary mapping when its mapEncode() method is called (which is used to serialise the QmfAgentData). + * + * @author Fraser Adams + */ +public class Exchange extends QmfAgentData +{ + private static final Logger _log = LoggerFactory.getLogger(Exchange.class); + + /** + * This static initialiser block initialises the QMF2 Schema information needed by the Agent to find + * QmfAgentData and QmfEvent Objects of a given type. + */ + private static final SchemaObjectClass _schema; + private static final SchemaEventClass _exchangeDeclareSchema; + private static final SchemaEventClass _exchangeDeleteSchema; + + /** + * Returns the schema for the Exchange class. + * @return the SchemaObjectClass for the Exchange class. + */ + public static SchemaObjectClass getSchema() + { + return _schema; + } + + /** + * Returns the schema for the Exchange Declare Event. + * @return the SchemaEventClass for the Exchange Declare Event. + */ + public static SchemaEventClass getExchangeDeclareSchema() + { + return _exchangeDeclareSchema; + } + + /** + * Returns the schema for the Exchange Delete Event. + * @return the SchemaEventClass for the Exchange Delete Event. + */ + public static SchemaEventClass getExchangeDeleteSchema() + { + return _exchangeDeleteSchema; + } + + static + { + // Declare the schema for the QMF2 broker class. + _schema = new SchemaObjectClass("org.apache.qpid.broker", "exchange"); + + // TODO + //_schema.addProperty(new SchemaProperty("whatHappened", QmfType.TYPE_STRING)); + + // Declare the schema for the QMF2 exchangeDeclare Event class. + _exchangeDeclareSchema = new SchemaEventClass("org.apache.qpid.broker", "exchangeDeclare"); + + // Declare the schema for the QMF2 exchangeDelete Event class. + _exchangeDeleteSchema = new SchemaEventClass("org.apache.qpid.broker", "exchangeDelete"); + } + // End of static initialiser. + + private final org.apache.qpid.server.model.Exchange _exchange; + private String _name; + + /** + * Constructor. + * @param vhost the parent VirtualHost ConfiguredObject from the broker model. + * @param exchange the Exchange ConfiguredObject from the broker model. + */ + public Exchange(final org.apache.qpid.server.model.VirtualHost vhost, + final org.apache.qpid.server.model.Exchange exchange) + { + super(getSchema()); + _exchange = exchange; + + _name = _exchange.getName(); + _name = (_name.equals("<>")) ? "" : _name; + + if (vhost == null) + { // Note we include an empty vhost name in the compare key to make sure things get sorted properly. + setCompareKey("vhost:/" + _name); + } + else + { + _name = "vhost:" + vhost.getName() + "/" + _name; + setCompareKey(_name); + } + + // In the Java Broker LifetimePolicy may be PERMANENT, DELETE_ON_CONNECTION_CLOSE, + // DELETE_ON_SESSION_END, DELETE_ON_NO_OUTBOUND_LINKS, DELETE_ON_NO_LINKS, IN_USE + // We map these to a boolean value to be consistent with the C++ Broker QMF value. + // TODO The C++ and Java Brokers should really return consistent information. + LifetimePolicy lifetimePolicy = (LifetimePolicy)_exchange.getAttribute("lifetimePolicy"); + boolean autoDelete = (lifetimePolicy != LifetimePolicy.PERMANENT) ? true : false; + + // TODO vhostRef - currently just use its name to try and get things working with standard command line tools. + + setValue("name", _name); + setValue("type", _exchange.getAttribute("type")); + setValue("durable", _exchange.getAttribute("durable")); + setValue("autoDelete", autoDelete); + + // TODO altExchange and arguments properties. + + // ObjectId needs to be set here in Exchange because the QMF2 version of qpid-config uses a hardcoded + // _object_name as below in the _object_id that it sets in the getExchange() call and in exchangeRef. + // It *shouldn't* do this and should really use the _object_id of the exchange object returned by + // getObjects("exchange"), but it does. The following line causes the Agent to use the explicit + // ObjectId below rather than constructing its own, which fixes the qpid-config issue. + setObjectId(new ObjectId("", "org.apache.qpid.broker:exchange:" + _name, 0)); + } + + /** + * Get the peer org.apache.qpid.server.model.Exchange instance. This is mainly used when creating an Alternate + * Exchange on a Queue as the underlying method requires an org.apache.qpid.server.model.Exchange. + */ + public org.apache.qpid.server.model.Exchange getExchange() + { + return _exchange; + } + + /** + * Factory method to create an Exchange Declare Event Object with timestamp of now. + * @return the newly created Exchange Declare Event Object. + */ + public QmfEvent createExchangeDeclareEvent() + { + QmfEvent exchangeDeclare = new QmfEvent(_exchangeDeclareSchema); + exchangeDeclare.setSeverity("info"); + exchangeDeclare.setValue("altEx", ""); // Java Broker can't set Alternate Exchange on Exchange + exchangeDeclare.setValue("args", Collections.EMPTY_MAP); + exchangeDeclare.setValue("autoDel", getBooleanValue("autoDelete")); + exchangeDeclare.setValue("disp", "created"); + exchangeDeclare.setValue("durable", getBooleanValue("durable")); + exchangeDeclare.setValue("exName", _name); + exchangeDeclare.setValue("exType", getStringValue("type")); + // TODO Not sure of a way to get these for Java Broker Exchange. + //exchangeDeclare.setValue("rhost", _connection.getName()); + //exchangeDeclare.setValue("user", getStringValue("authIdentity")); + return exchangeDeclare; + } + + /** + * Factory method to create an Exchange Delete Event Object with timestamp of now. + * @return the newly created Exchange Delete Event Object. + */ + public QmfEvent createExchangeDeleteEvent() + { + QmfEvent exchangeDelete = new QmfEvent(_exchangeDeleteSchema); + exchangeDelete.setSeverity("info"); + exchangeDelete.setValue("exName", _name); + // TODO Not sure of a way to get these for Java Broker Exchange. + //exchangeDelete.setValue("rhost", _connection.getName()); + //exchangeDelete.setValue("user", getStringValue("authIdentity")); + return exchangeDelete; + } + + /** + * This method maps the org.apache.qpid.server.model.Exchange to QMF2 broker properties where possible then + * serialises into the underlying Map for transmission via AMQP. This method is called by handleQueryRequest() + * in the org.apache.qpid.qmf2.agent.Agent class implementing the main QMF2 Agent behaviour. + * + * @return the underlying map. + */ + @Override + public Map mapEncode() + { + // Statistics + long msgReceives = _exchange.getMessagesIn(); + long msgDrops = _exchange.getMessagesDropped(); + long msgRoutes = msgReceives - msgDrops; + + long byteReceives = _exchange.getBytesIn(); + long byteDrops = _exchange.getBytesDropped(); + long byteRoutes = byteReceives - byteDrops; + + setValue("producerCount", _exchange.getPublishers().size()); + + // We have to modify the value of bindingCount for Exchange because the QmfManagementAgent "hides" the + // QMF Objects that relate to its own AMQP Connection/Queues/Bindings so the bindingCount for default direct + // qmf.default.direct and qmf.default.topic is different to the actual number of QMF bindings. + long bindingCount = _exchange.getBindingCount(); + if (_name.equals("")) + { + bindingCount -= 3; + } + else if (_name.equals("qmf.default.direct")) + { + bindingCount -= 2; + } + else if (_name.equals("qmf.default.topic")) + { + bindingCount -= 1; + } + setValue("bindingCount", bindingCount); + + setValue("msgReceives", msgReceives); + setValue("msgDrops", msgDrops); + setValue("msgRoutes", msgRoutes); + setValue("byteReceives", byteReceives); + setValue("byteDrops", byteDrops); + setValue("byteRoutes", byteRoutes); + + update(); // TODO only set update if a statistic has actually changed value. + return super.mapEncode(); + } +} diff --git a/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Queue.java b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Queue.java new file mode 100644 index 0000000000..5fef3216da --- /dev/null +++ b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Queue.java @@ -0,0 +1,296 @@ +/* + * + * 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.server.qmf2.agentdata; + +// Misc Imports +import java.util.Collections; +import java.util.Map; + +// Simple Logging Facade 4 Java +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// QMF2 Imports +import org.apache.qpid.qmf2.agent.Agent; +import org.apache.qpid.qmf2.agent.QmfAgentData; +import org.apache.qpid.qmf2.common.QmfEvent; +import org.apache.qpid.qmf2.common.Handle; +import org.apache.qpid.qmf2.common.ObjectId; +import org.apache.qpid.qmf2.common.QmfData; +import org.apache.qpid.qmf2.common.SchemaEventClass; +//import org.apache.qpid.qmf2.common.SchemaMethod; +import org.apache.qpid.qmf2.common.SchemaObjectClass; +//import org.apache.qpid.qmf2.common.SchemaProperty; + +import org.apache.qpid.server.model.Exchange; +import org.apache.qpid.server.model.ExclusivityPolicy; +import org.apache.qpid.server.model.LifetimePolicy; + +/** + * This class provides a concrete implementation of QmfAgentData for the Queue Management Object. + * In general it's possible to use QmfAgentData without sub-classing as it's really a "bean" style class + * that retains its properties in a Map, but in the case of the Java Broker Management Agent it's useful + * to sub-class as we need to map between the properties/statistics as specified in the Java Broker + * management model and those specified in qpid/spec/management-schema.xml which is what the C++ broker + * uses. This class retains a reference to its peer org.apache.qpid.server.model.Queue and does the + * necessary mapping when its mapEncode() method is called (which is used to serialise the QmfAgentData). + * + * @author Fraser Adams + */ +public class Queue extends QmfAgentData +{ + private static final Logger _log = LoggerFactory.getLogger(Queue.class); + + /** + * This static initialiser block initialises the QMF2 Schema information needed by the Agent to find + * QmfAgentData and QmfEvent Objects of a given type. + */ + private static final SchemaObjectClass _schema; + private static final SchemaEventClass _queueDeclareSchema; + private static final SchemaEventClass _queueDeleteSchema; + + /** + * Returns the schema for the Queue class. + * @return the SchemaObjectClass for the Queue class. + */ + public static SchemaObjectClass getSchema() + { + return _schema; + } + + /** + * Returns the schema for the Queue Declare Event. + * @return the SchemaEventClass for the Queue Declare Event. + */ + public static SchemaEventClass getQueueDeclareSchema() + { + return _queueDeclareSchema; + } + + /** + * Returns the schema for the Queue Delete Event. + * @return the SchemaEventClass for the Queue Delete Event. + */ + public static SchemaEventClass getQueueDeleteSchema() + { + return _queueDeleteSchema; + } + + static + { + // Declare the schema for the QMF2 broker class. + _schema = new SchemaObjectClass("org.apache.qpid.broker", "queue"); + + // TODO + //_schema.addProperty(new SchemaProperty("whatHappened", QmfType.TYPE_STRING)); + + // Declare the schema for the QMF2 queueDeclare Event class. + _queueDeclareSchema = new SchemaEventClass("org.apache.qpid.broker", "queueDeclare"); + + // Declare the schema for the QMF2 queueDelete Event class. + _queueDeleteSchema = new SchemaEventClass("org.apache.qpid.broker", "queueDelete"); + } + // End of static initialiser. + + private final org.apache.qpid.server.model.Queue _queue; + private String _vhostName = ""; + private ObjectId _alternateExchange = null; + private String _alternateExchangeName = ""; + + /** + * Constructor. + * @param vhost the parent VirtualHost ConfiguredObject from the broker model. + * @param queue the Queue ConfiguredObject from the broker model. + */ + public Queue(final org.apache.qpid.server.model.VirtualHost vhost, + final org.apache.qpid.server.model.Queue queue) + { + super(getSchema()); + _queue = queue; + + String name = _queue.getName(); + + if (vhost == null) + { // Note we include an empty vhost name in the compare key to make sure things get sorted properly. + setCompareKey("vhost:/" + name); + } + else + { + _vhostName = "vhost:" + vhost.getName() + "/"; + name = _vhostName + name; + setCompareKey(name); + } + + // In the Java Broker LifetimePolicy may be PERMANENT, DELETE_ON_CONNECTION_CLOSE, + // DELETE_ON_SESSION_END, DELETE_ON_NO_OUTBOUND_LINKS, DELETE_ON_NO_LINKS, IN_USE + // We map these to a boolean value to be consistent with the C++ Broker QMF value. + // TODO The C++ and Java Brokers should really return consistent information. + LifetimePolicy lifetimePolicy = (LifetimePolicy)_queue.getAttribute("lifetimePolicy"); + boolean autoDelete = (lifetimePolicy != LifetimePolicy.PERMANENT) ? true : false; + + // In the Java Broker exclusivity may be NONE, SESSION, CONNECTION, CONTAINER, PRINCIPAL, LINK + // We map these to a boolean value to be consistent with the C++ Broker QMF value. + // TODO The C++ and Java Brokers should really return consistent information. + ExclusivityPolicy exclusivityPolicy = (ExclusivityPolicy)_queue.getAttribute("exclusive"); + boolean exclusive = (exclusivityPolicy != ExclusivityPolicy.NONE) ? true : false; + + // TODO vhostRef - currently just use its name to try and get things working with standard command line tools. + + setValue("name", name); + setValue("durable", _queue.getAttribute("durable")); + setValue("autoDelete", autoDelete); + setValue("exclusive", exclusive); + + // altExchange needs to be set later, done in mapEncode() for convenience, because it isn't set during + // Queue construction in the Java Broker. + + // TODO arguments properties. + + + // ObjectId needs to be set here in Queue because the QMF2 version of qpid-config uses a hardcoded + // _object_name as below in the _object_id that it sets in the getQueue() call and in queueRef. + // It *shouldn't* do this and should really use the _object_id of the queue object returned by + // getObjects("queue"), but it does. The following line causes the Agent to use the explicit + // ObjectId below rather than constructing its own, which fixes the qpid-config issue. + setObjectId(new ObjectId("", "org.apache.qpid.broker:queue:" + name, 0)); + } + + /** + * TODO + * + */ + public void invokeMethod(Agent agent, Handle handle, String methodName, QmfData inArgs) + { + /*if (methodName.equals("purge")) + { + //broker.create(inArgs); + } + else if (methodName.equals("reroute")) + { + //broker.create(inArgs); + } + else*/ + { + agent.raiseException(handle, methodName + " not yet implemented on Queue."); + } + } + + /** + * Factory method to create a Queue Declare Event Object with timestamp of now. + * @return the newly created Queue Declare Event Object. + */ + public QmfEvent createQueueDeclareEvent() + { + QmfEvent queueDeclare = new QmfEvent(_queueDeclareSchema); + queueDeclare.setSeverity("info"); + // TODO the _alternateExchangeName gets set some time after the Constructor - how do I get its value for + // the queueDeclareEvent???!!! + queueDeclare.setValue("altEx", _alternateExchangeName); + queueDeclare.setValue("args", Collections.EMPTY_MAP); // TODO + queueDeclare.setValue("autoDel", getBooleanValue("autoDelete")); + queueDeclare.setValue("disp", "created"); + queueDeclare.setValue("durable", getBooleanValue("durable")); + queueDeclare.setValue("excl", getBooleanValue("exclusive")); + queueDeclare.setValue("qName", getStringValue("name")); + // TODO Not sure of a way to get these for Java Broker Exchange. + //queueDeclare.setValue("rhost", _connection.getName()); + //queueDeclare.setValue("user", getStringValue("authIdentity")); + return queueDeclare; + } + + /** + * Factory method to create a Queue Delete Event Object with timestamp of now. + * @return the newly created Queue Delete Event Object. + */ + public QmfEvent createQueueDeleteEvent() + { + QmfEvent queueDelete = new QmfEvent(_queueDeleteSchema); + queueDelete.setSeverity("info"); + queueDelete.setValue("qName", getStringValue("name")); + // TODO Not sure of a way to get these for Java Broker Exchange. + //queueDelete.setValue("rhost", _connection.getName()); + //queueDelete.setValue("user", getStringValue("authIdentity")); + return queueDelete; + } + + /** + * This method maps the org.apache.qpid.server.model.Queue to QMF2 broker properties where possible then + * serialises into the underlying Map for transmission via AMQP. This method is called by handleQueryRequest() + * in the org.apache.qpid.qmf2.agent.Agent class implementing the main QMF2 Agent behaviour. + * + * @return the underlying map. + */ + @Override + public Map mapEncode() + { + // Set the altExchange reference if an alternateExchange exists and hasn't already been set. + // Not sure how to set this closer to the Constructor. At the moment the _alternateExchangeName gets set + // too late to populate the "altEx" property of the queueDeclareEvent. + if (_alternateExchange == null) + { + Exchange altEx = (Exchange)_queue.getAttribute("alternateExchange"); + if (altEx != null) + { + _alternateExchangeName = _vhostName + altEx.getName(); + _alternateExchange = new ObjectId("", "org.apache.qpid.broker:exchange:" + _alternateExchangeName, 0); + setRefValue("altExchange", _alternateExchange); + } + } + + // Statistics + setValue("msgTotalEnqueues", _queue.getTotalEnqueuedMessages()); + setValue("msgTotalDequeues", _queue.getTotalDequeuedMessages()); + // msgTxnEnqueues not implemented in Java Broker + // msgTxnDequeues not implemented in Java Broker + setValue("msgPersistEnqueues", _queue.getPersistentEnqueuedMessages()); + setValue("msgPersistDequeues", _queue.getPersistentDequeuedMessages()); + setValue("msgDepth", _queue.getQueueDepthMessages()); + setValue("byteDepth", _queue.getQueueDepthBytes()); + setValue("byteTotalEnqueues", _queue.getTotalEnqueuedBytes()); + setValue("byteTotalDequeues", _queue.getTotalDequeuedBytes()); + // byteTxnEnqueues not implemented in Java Broker + // byteTxnDequeues not implemented in Java Broker + setValue("bytePersistEnqueues", _queue.getPersistentEnqueuedBytes()); + setValue("bytePersistDequeues", _queue.getPersistentDequeuedBytes()); + + // Flow-to-disk Statistics not implemented in Java Broker + // releases & acquires not implemented in Java Broker + // discardsTtl (discardsTtlMessages) not implemented in Java Broker + // discardsRing not implemented in Java Broker + // discardsLvq not implemented in Java Broker + // discardsOverflow not implemented in Java Broker + // discardsSubscriber not implemented in Java Broker + // discardsPurge not implemented in Java Broker + // reroutes not implemented in Java Broker + + setValue("consumerCount", _queue.getConsumerCount()); + setValue("bindingCount", _queue.getBindingCount()); + setValue("unackedMessages", _queue.getUnacknowledgedMessages()); + + setValue("messageLatency", "Not yet implemented"); + // flowStopped not implemented in Java Broker + // flowStoppedCount not implemented in Java Broker + + update(); // TODO only update if statistics have actually changed. + return super.mapEncode(); + } +} diff --git a/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Session.java b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Session.java new file mode 100644 index 0000000000..7c4f121503 --- /dev/null +++ b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Session.java @@ -0,0 +1,116 @@ +/* + * + * 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.server.qmf2.agentdata; + +// Misc Imports +import java.util.Collections; +import java.util.Map; + +// Simple Logging Facade 4 Java +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// QMF2 Imports +import org.apache.qpid.qmf2.agent.QmfAgentData; +import org.apache.qpid.qmf2.common.ObjectId; +//import org.apache.qpid.qmf2.common.SchemaEventClass; +//import org.apache.qpid.qmf2.common.SchemaMethod; +import org.apache.qpid.qmf2.common.SchemaObjectClass; +//import org.apache.qpid.qmf2.common.SchemaProperty; + +/** + * This class provides a concrete implementation of QmfAgentData for the Session Management Object. + * In general it's possible to use QmfAgentData without sub-classing as it's really a "bean" style class + * that retains its properties in a Map, but in the case of the Java Broker Management Agent it's useful + * to sub-class as we need to map between the properties/statistics as specified in the Java Broker + * management model and those specified in qpid/spec/management-schema.xml which is what the C++ broker + * uses. This class retains a reference to its peer org.apache.qpid.server.model.Consumer and does the + * necessary mapping when its mapEncode() method is called (which is used to serialise the QmfAgentData). + * + * @author Fraser Adams + */ +public class Session extends QmfAgentData +{ + private static final Logger _log = LoggerFactory.getLogger(Session.class); + + /** + * This static initialiser block initialises the QMF2 Schema information needed by the Agent to find + * QmfAgentData and QmfEvent Objects of a given type. + */ + private static final SchemaObjectClass _schema; + + /** + * Returns the schema for the Session class. + * @return the SchemaObjectClass for the Session class. + */ + public static SchemaObjectClass getSchema() + { + return _schema; + } + + static + { + // Declare the schema for the QMF2 session class. + _schema = new SchemaObjectClass("org.apache.qpid.broker", "session"); + + // TODO + //_schema.addProperty(new SchemaProperty("whatHappened", QmfType.TYPE_STRING)); + } + // End of static initialiser. + + private final org.apache.qpid.server.model.Session _session; + + /** + * Constructor. + * @param session the Session ConfiguredObject from the broker model. + * @param connectionRef the ObjectId of the Connection Object that is the parent of the Session. + */ + public Session(final org.apache.qpid.server.model.Session session, final ObjectId connectionRef) + { + super(getSchema()); + _session = session; + + setValue("name", session.getAttribute("id")); // Use ID to be consistent with C++ Broker. + setValue("channelId", session.getName()); // The Java Broker name uses the channelId. + setRefValue("connectionRef", connectionRef); + } + + /** + * This method maps the org.apache.qpid.server.model.Session to QMF2 subscribe properties where possible then + * serialises into the underlying Map for transmission via AMQP. This method is called by handleQueryRequest() + * in the org.apache.qpid.qmf2.agent.Agent class implementing the main QMF2 Agent behaviour. + * + * @return the underlying map. + */ + @Override + public Map mapEncode() + { + // Statistics + setValue("unackedMessages", _session.getUnacknowledgedMessages()); + setValue("TxnStarts", _session.getLocalTransactionBegins()); + setValue("TxnRejects", _session.getLocalTransactionRollbacks()); + + update(); // TODO Only Update if statistics have changes. + + return super.mapEncode(); + } +} diff --git a/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Subscription.java b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Subscription.java new file mode 100644 index 0000000000..6e12949c31 --- /dev/null +++ b/qpid/tools/src/java/qpid-broker-plugins-management-qmf2/src/main/java/org/apache/qpid/server/qmf2/agentdata/Subscription.java @@ -0,0 +1,211 @@ +/* + * + * 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.server.qmf2.agentdata; + +// Misc Imports +import java.util.Collections; +import java.util.Map; + +// Simple Logging Facade 4 Java +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// QMF2 Imports +import org.apache.qpid.qmf2.agent.QmfAgentData; +import org.apache.qpid.qmf2.common.QmfEvent; +import org.apache.qpid.qmf2.common.ObjectId; +import org.apache.qpid.qmf2.common.QmfEvent; +import org.apache.qpid.qmf2.common.SchemaEventClass; +//import org.apache.qpid.qmf2.common.SchemaMethod; +import org.apache.qpid.qmf2.common.SchemaObjectClass; +//import org.apache.qpid.qmf2.common.SchemaProperty; + +import org.apache.qpid.server.model.ExclusivityPolicy; +import org.apache.qpid.server.model.Queue; + +/** + * This class provides a concrete implementation of QmfAgentData for the Subscription Management Object. + * In general it's possible to use QmfAgentData without sub-classing as it's really a "bean" style class + * that retains its properties in a Map, but in the case of the Java Broker Management Agent it's useful + * to sub-class as we need to map between the properties/statistics as specified in the Java Broker + * management model and those specified in qpid/spec/management-schema.xml which is what the C++ broker + * uses. This class retains a reference to its peer org.apache.qpid.server.model.Consumer and does the + * necessary mapping when its mapEncode() method is called (which is used to serialise the QmfAgentData). + * + * @author Fraser Adams + */ +public class Subscription extends QmfAgentData +{ + private static final Logger _log = LoggerFactory.getLogger(Subscription.class); + + /** + * This static initialiser block initialises the QMF2 Schema information needed by the Agent to find + * QmfAgentData and QmfEvent Objects of a given type. + */ + private static final SchemaObjectClass _schema; + private static final SchemaEventClass _subscribeSchema; + private static final SchemaEventClass _unsubscribeSchema; + + /** + * Returns the schema for the Subscription class. + * @return the SchemaObjectClass for the Subscription class. + */ + public static SchemaObjectClass getSchema() + { + return _schema; + } + + /** + * Returns the schema for the Subscribe Event. + * @return the SchemaEventClass for the Subscribe Event. + */ + public static SchemaEventClass getSubscribeSchema() + { + return _subscribeSchema; + } + + /** + * Returns the schema for the Unsubscribe Event. + * @return the SchemaEventClass for the Unsubscribe Event. + */ + public static SchemaEventClass getUnsubscribeSchema() + { + return _unsubscribeSchema; + } + + static + { + // Declare the schema for the QMF2 subscription class. + _schema = new SchemaObjectClass("org.apache.qpid.broker", "subscription"); + + // TODO + //_schema.addProperty(new SchemaProperty("whatHappened", QmfType.TYPE_STRING)); + + // Declare the schema for the QMF2 subscribe Event class. + _subscribeSchema = new SchemaEventClass("org.apache.qpid.broker", "subscribe"); + + // Declare the schema for the QMF2 unsubscribe Event class. + _unsubscribeSchema = new SchemaEventClass("org.apache.qpid.broker", "unsubscribe"); + } + // End of static initialiser. + + private final org.apache.qpid.server.model.Consumer _subscription; + + private boolean _exclusive = false; + private String _qName = ""; + + /** + * Constructor. + * @param subscription the Consumer ConfiguredObject from the broker model. + */ + public Subscription(final org.apache.qpid.server.model.Consumer subscription) + { + super(getSchema()); + _subscription = subscription; // Will eventually be used in mapEncode() to retrieve statistics. + + setValue("name", subscription.getName()); + setValue("browsing", false); // TODO not supported in Java Broker. + setValue("acknowledged", true); // TODO not supported in Java Broker. + setValue("creditMode", "WINDOW"); // TODO not supported in Java Broker. + } + + /** + * Set the sessionRef property. + * @param sessionRef the sessionRef ObjectId. + */ + public void setSessionRef(final ObjectId sessionRef) + { + setRefValue("sessionRef", sessionRef); + } + + /** + * Set the queueRef property. + * @param queueRef the queueRef ObjectId. + */ + public void setQueueRef(final ObjectId queueRef, final Queue queue) + { + setRefValue("queueRef", queueRef); + + // Unfortunately the org.apache.qpid.server.model.Consumer doesn't yet allow access to its associated Queue + // so we pass a reference ourselves when we do setQueueRef. This is because some Subscription properties + // are *actually" related to the associated Queue. + _qName = queue.getName(); + + // In the Java Broker exclusivity may be NONE, SESSION, CONNECTION, CONTAINER, PRINCIPAL, LINK + // We map these to a boolean value to be consistent with the C++ Broker QMF values. + // TODO The C++ and Java Brokers should really return consistent information. + ExclusivityPolicy exclusivityPolicy = (ExclusivityPolicy)queue.getAttribute("exclusive"); + _exclusive = (exclusivityPolicy != ExclusivityPolicy.NONE) ? true : false; + } + + /** + * Factory method to create a Subscribe Event Object with timestamp of now. + * @return the newly created Subscribe Event Object. + */ + public QmfEvent createSubscribeEvent() + { + QmfEvent subscribe = new QmfEvent(_subscribeSchema); + subscribe.setSeverity("info"); + subscribe.setValue("args", Collections.EMPTY_MAP); + subscribe.setValue("dest", getStringValue("name")); + subscribe.setValue("excl", _exclusive); + subscribe.setValue("qName", _qName); + // TODO Not sure of a way to get these for Java Broker Subscription. + //subscribe.setValue("rhost", _connection.getName()); + //subscribe.setValue("user", getStringValue("authIdentity")); + return subscribe; + } + + /** + * Factory method to create an Unsubscribe Event Object with timestamp of now. + * @return the newly created Unsubscribe Event Object. + */ + public QmfEvent createUnsubscribeEvent() + { + QmfEvent unsubscribe = new QmfEvent(_unsubscribeSchema); + unsubscribe.setSeverity("info"); + unsubscribe.setValue("dest", getStringValue("name")); + // TODO Not sure of a way to get these for Java Broker Subscription. + //unsubscribe.setValue("rhost", _connection.getName()); + //unsubscribe.setValue("user", getStringValue("authIdentity")); + return unsubscribe; + } + + /** + * This method maps the org.apache.qpid.server.model.Consumer to QMF2 subscribe properties where possible then + * serialises into the underlying Map for transmission via AMQP. This method is called by handleQueryRequest() + * in the org.apache.qpid.qmf2.agent.Agent class implementing the main QMF2 Agent behaviour. + * + * @return the underlying map. + */ + @Override + public Map mapEncode() + { + // Statistics + setValue("delivered", _subscription.getMessagesOut()); + + setValue("exclusive", _exclusive); + + update(); // TODO Only Update if statistics have changes. + return super.mapEncode(); + } +} diff --git a/qpid/tools/src/java/qpid-qmf2-rest/pom.xml b/qpid/tools/src/java/qpid-qmf2-rest/pom.xml new file mode 100644 index 0000000000..c89e5df85c --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-rest/pom.xml @@ -0,0 +1,42 @@ + + + + 4.0.0 + + + org.apache.qpid + qpid-qmf2-parent + 0.30-SNAPSHOT + + + qpid-qmf2-rest + Qpid QMF2 REST + QMF2 REST API + + + + org.apache.qpid + qpid-qmf2 + ${project.version} + + + + + + + diff --git a/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/ConnectionProxy.java b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/ConnectionProxy.java new file mode 100644 index 0000000000..435372a0d6 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/ConnectionProxy.java @@ -0,0 +1,287 @@ +/* + * + * 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.restapi; + +// Misc Imports +import java.util.TimerTask; + +// JMS Imports +import javax.jms.Connection; +import javax.jms.ExceptionListener; +import javax.jms.JMSException; + +// Simple Logging Facade 4 Java +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// QMF2 Imports +import org.apache.qpid.qmf2.common.BlockingNotifier; +import org.apache.qpid.qmf2.common.QmfException; +import org.apache.qpid.qmf2.console.Console; +import org.apache.qpid.qmf2.util.ConnectionHelper; + +/** + * Contains a Connection object under a "leasehold agreement" whereby the Connection (and associated Sessions and QMF + * Consoles) will expire after a period of time. + *

+ * The idea here is to allow a user to create multiple Connection instances (for example to monitor multiple brokers) + * but by using the lease metaphor we can expire instances that haven't been used for some predetermined period. + * Using the leashold agreement means that we don't have to rely on users explicitly deleting Connections that they + * are no longer interested in, because obviously we can't rely on that :-) + * + * @author Fraser Adams + */ +public final class ConnectionProxy extends TimerTask implements ExceptionListener +{ + private static final Logger _log = LoggerFactory.getLogger(ConnectionProxy.class); + + private static final int MAX_WORKITEM_QUEUE_SIZE = 20; // Maximum number of items allowed on WorkItem queue. + + // Connections expire after 20 minutes of no use. + private static final int TIMEOUT_THRESHOLD = (20*60000)/ConnectionStore.PING_PERIOD; + + // Connections expire after 1 minute if they have never been dereferenced. + private static final int UNUSED_THRESHOLD = 60000/ConnectionStore.PING_PERIOD; + + private Connection _connection; + private Console _console; + private boolean _connected; + private int _expireCount; + private final ConnectionStore _store; + private final String _name; + private final String _url; + private final String _connectionOptions; + private final boolean _disableEvents; + + /** + * Actually create the Qpid Connection and QMF2 Console specified in the Constructor. + */ + private synchronized void createConnection() + { + //System.out.println("ConnectionProxy createConnection() name: " + _name + ", thread: " + Thread.currentThread().getId() + ", creating connection to " + _url + ", options " + _connectionOptions); + try + { + _connection = ConnectionHelper.createConnection(_url, _connectionOptions); + if (_connection != null) + { + _connection.setExceptionListener(this); + + // N.B. creating a Console with a notifier causes the internal WorkQueue to get populated, so care must + // be taken to manage its size. In a normal Console application the application would only declare this + // if there was an intention to retrieve work items, but in a fairly general REST API we can't guarantee + // that clients will. ConsoleLease acts to make the WorkQueue "circular" by deleting items from the + // front of the WorkQueue if it exceeds a particular size. + if (_disableEvents) + { + _console = new Console(_name, null, null, null); + _console.disableEvents(); + } + else + { + BlockingNotifier notifier = new BlockingNotifier(); + _console = new Console(_name, null, notifier, null); + } + _console.addConnection(_connection); + _connected = true; + _expireCount = UNUSED_THRESHOLD; + notifyAll(); + } + } + catch (Exception ex) + { + _log.info("Exception {} caught in ConnectionProxy constructor.", ex.getMessage()); + _connected = false; + } + } + + /** + * This method blocks until the Connection has been created. + */ + public synchronized void waitForConnection() + { + while (!_connected) + { + try + { + wait(); + } + catch (InterruptedException ie) + { + continue; + } + } + } + + /** + * This method blocks until the Connection has been created or timeout expires (or wait has been interrupted). + * @param timeout the maximum time in milliseconds to wait for notification of the connection's availability. + */ + public synchronized void waitForConnection(long timeout) + { + try + { + wait(timeout); + } + catch (InterruptedException ie) + { // Ignore + } + } + + /** + * Construct a Proxy to the specified Qpid Connection with the supplied name to be stored in the specified store. + * @param store The ConnectionStore that we want to store this ConnectionProxy in. + * @param name A unique name for the Connection that we want to create. + * @param url A Connection URL using one of the forms supported by {@link org.apache.qpid.qmf2.util.ConnectionHelper}. + * @param connectionOptions A set of connection options in the form supported by {@link org.apache.qpid.qmf2.util.ConnectionHelper}. + * @param disableEvents if true create a QMF Console Connection that can only perform synchronous + * operations like getObjects() and cannot do asynchronous things like Agent discovery or receive Events. + */ + public ConnectionProxy(final ConnectionStore store, final String name, + final String url, final String connectionOptions, final boolean disableEvents) + { + _connected = false; + _store = store; + _name = name; + _url = url; + _connectionOptions = connectionOptions; + _disableEvents = disableEvents; + } + + /** + * The exception listener for the underlying Qpid Connection. This is used to trigger the ConnectionProxy internal + * reconnect logic. N.B. ConnectionProxy uses its own reconnection logic for two reasons: firstly the Qpid auto + * retry mechanism has some undesireable and unreliable behaviours prior to Qpid version 0.16 and secondly the + * Qpid auto retry mechanism is transparent whereas we actually want to detect connection failures in the REST + * API so that we can report failures back to the client. + * @param jmse The JMSException that has caused onException to be triggered. + */ + public void onException(JMSException jmse) + { + _log.info("ConnectionProxy onException {}", jmse.getMessage()); + _connected = false; + } + + /** + * This method is called periodically by {@link org.apache.qpid.restapi.ConnectionStore} to carry out a number + * of housekeeping tasks. It checks if the Qpid Connection is still connected and if not it attempts to reconnect + * it also checks whether the Connection "lease" has run out and if it has it tidies up the Connection. Finally + * it restricts the size of the QMF2 WorkItem queue as the REST API has no control over whether a client is or + * is not interested in being notified of QMF2 Events. + */ + public void run() + { + if (_connected) + { + //System.out.println("ConnectionProxy name: " + _name + ", thread: " + Thread.currentThread().getId() + ", WorkItem count = " + _console.getWorkitemCount()); + + while (_console.getWorkitemCount() > MAX_WORKITEM_QUEUE_SIZE) + { + _console.getNextWorkitem(); + } + + _expireCount--; + //System.out.println("ConnectionProxy name: " + _name + ", thread: " + Thread.currentThread().getId() + ", expireCount = " + _expireCount); + if (_expireCount == 0) + { + _store.delete(_name); + } + } + else + { + createConnection(); + } + } + + /** + * Stops scheduled housekeeping, destroys any attached QMF2 Console instances then closes the Qpid Connection. + */ + public synchronized void close() + { + //System.out.println("ConnectionProxy close() name: " + _name + ", thread: " + Thread.currentThread().getId() + ", expireCount = " + _expireCount); + + cancel(); + + try + { + _console.destroy(); + _connection.close(); + } + catch (Exception e) + { // Log and Ignore + _log.info("ConnectionProxy close() caught Exception {}", e.getMessage()); + } + } + + /** + * Retrieves the QMF2 Console that we've associated with this Connection. + * @return The QMF2 Console that we've associated with this Connection. + */ + public Console getConsole() + { + _expireCount = TIMEOUT_THRESHOLD; + return _console; + } + + /** + * Returns whether or not the Connection is currently connected to the broker. This is used by the REST API to + * tell any clients about the Connection state. + * @return true if currently connected or false if not. + */ + public boolean isConnected() + { + _expireCount = TIMEOUT_THRESHOLD; + return _connected; + } + + /** + * Returns the Connection URL String used to create the Connection. + * @return The Connection URL String used to create the Connection. + */ + public String getUrl() + { + _expireCount = TIMEOUT_THRESHOLD; + return _url; + } + + /** + * Returns the Connection options String used to create the Connection. + * @return The Connection options String used to create the Connection. + */ + public String getConnectionOptions() + { + _expireCount = TIMEOUT_THRESHOLD; + return _connectionOptions; + } + + /** + * Returns a String representation of a ConnectionProxy. + * @return The String representation of this ConnectionProxy Object. + */ + @Override + public String toString() + { + // The reason we use JSON.toMap on the string is because it is fairly tolerant and doesn't need pure JSON + // if we then call JSON.fromMap we get a pure JSON String. + return "{" + "\"url\":\"" + _url + "\",\"connectionOptions\":" + + JSON.fromMap(JSON.toMap(_connectionOptions)) + "}"; + } +} + diff --git a/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/ConnectionStore.java b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/ConnectionStore.java new file mode 100644 index 0000000000..79b85c9c42 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/ConnectionStore.java @@ -0,0 +1,138 @@ +/* + * + * 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.restapi; + +// Misc Imports +import java.util.Map; +import java.util.Timer; +import java.util.concurrent.ConcurrentHashMap; + +// QMF2 Imports +import org.apache.qpid.qmf2.common.QmfException; +import org.apache.qpid.qmf2.console.Console; + +/** + * A ConnectionStore is a container for Qpid Connection Objects, or rather it's a container for ConnectionProxy + * Objects which wrap Qpid Connections and provide some additional housekeeping behaviour necessary for a distributed + * system. The ConnectionStore schedules regular housekeeping tasks to be executed on the ConnectionProxy Objects + * using a java.util.Timer, which scales fairly well. + * + * @author Fraser Adams + */ +public class ConnectionStore +{ + /** + * This represents the time between "pings" to the stored ConnectionProxy Objects. The pings run scheduled tasks + * such as attempting reconnection if the broker has disconnected and checking lease timeouts. + */ + public static final int PING_PERIOD = 5000; + + /** + * This Map is used to associate connection names with their ConnectionProxies. Note that the names are prefixed + * internally with the authenticated user name to prevent users accidentally (or maliciously) sharing connections. + */ + private Map _connections = new ConcurrentHashMap(); + + /** + * Create a Timer used to schedule regular checks on ConnectionProxy Objects to see that they are still in use. + * In essence ConnectionProxy Objects behave in a similar way to RMI Leases in that if they are not used + * (dereferenced) within a particular period it is assumed that the client has lost interest and they are reaped. + */ + private Timer _timer = new Timer(true); + + /** + * Creates a new ConnectionProxy Object with the given name, which in turn creates a Qpid Connection using the + * supplied Connection URL and options. In addition it schedules some regular housekeeping on the ConnectionProxy + * to enable it to manage Connection failures and perform what amounts to distributed garbage collection. + * When a ConnectionProxy with a given name has been created it is cached and subsequent calls to this method + * will return the cached instance. If an new instance is required one must first call the delete method. + * @param name A unique name for the Connection that we want to create. + * @param url A Connection URL using one of the forms supported by {@link org.apache.qpid.qmf2.util.ConnectionHelper}. + * @param opts A set of connection options in the form supported by {@link org.apache.qpid.qmf2.util.ConnectionHelper}. + * @param disableEvents if true create a QMF Console Connection that can only perform synchronous + * operations like getObjects() and cannot do asynchronous things like Agent discovery or receive Events. + * @return the ConnectionProxy Object created or cached by this method. + */ + public synchronized ConnectionProxy create(final String name, final String url, final String opts, + final boolean disableEvents) + { + ConnectionProxy connection = _connections.get(name); + if (connection == null) + { + connection = new ConnectionProxy(this, name, url, opts, disableEvents); + _connections.put(name, connection); + _timer.schedule(connection, 0, PING_PERIOD); + } + return connection; + } + + /** + * Closes the named Connection, stops its scheduled housekeeping and removes from the store. + * @param name the name of the Connection that we want to delete. + */ + public synchronized void delete(final String name) + { + ConnectionProxy connection = _connections.get(name); + if (connection != null) + { + connection.close(); + _connections.remove(name); + } + } + + /** + * Retrieves the named Connection from the store. + * @param name the name of the Connection that we want to retrieve. + * @return the ConnectionProxy instance with the given name. + */ + public ConnectionProxy get(final String name) + { + return _connections.get(name); + } + + /** + * Return a Map of ConnectionProxies associated with a given user. Note that this method is fairly inefficient + * as the main _connections Map contains all ConnectionProxies and we normally do a lookup by prefixing the + * key with the user name in order to demultiplex. It is done this way as we need to look up ConnectionProxy + * for each API call, whereas looking up all Connections for a user is likely to be rarely called. + * @param user the security principal associated with a particular user. + * @return a Map of ConnectionProxy objects associated with the given user. + */ + public Map getAll(final String user) + { + Map map = new ConcurrentHashMap(); + String prefix = user + "."; + int prefixLength = prefix.length(); + for (Map.Entry entry : _connections.entrySet()) + { + String key = entry.getKey(); + if (key.startsWith(prefix)) + { + key = key.substring(prefixLength); + ConnectionProxy value = entry.getValue(); + map.put(key, value); + } + } + return map; + } +} + + diff --git a/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/FileServer.java b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/FileServer.java new file mode 100644 index 0000000000..2cbb1140b1 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/FileServer.java @@ -0,0 +1,369 @@ +/* + * + * 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.restapi; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; + +import static java.net.HttpURLConnection.HTTP_OK; +import static java.net.HttpURLConnection.HTTP_BAD_METHOD; +import static java.net.HttpURLConnection.HTTP_PARTIAL; +import static java.net.HttpURLConnection.HTTP_MOVED_PERM; +import static java.net.HttpURLConnection.HTTP_FORBIDDEN; +import static java.net.HttpURLConnection.HTTP_NOT_FOUND; +import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR; + +/** + * FileServer is a fairly simple HTTP File Server that can be used to serve files from the root directory specified + * during construction. + *

+ * Although this is a relatively simple File Server it is still able to serve large files as it uses streaming, in + * addition it uses the HTTP Range/Content-Range/Content-Length Headers to allow resuming of partial downloads + * from clients that support it. + * + * @author Fraser Adams + */ +public class FileServer implements Server +{ + /** + * HashMap mapping file extension to MIME type for common types. + * TODO make this set of mappings configurable via properties or similar mechanism. + */ + private static Map _mimeTypes = new HashMap(); + static + { + _mimeTypes.put("htm", "text/html"); + _mimeTypes.put("html", "text/html"); + _mimeTypes.put("txt", "text/plain"); + _mimeTypes.put("asc", "text/plain"); + _mimeTypes.put("xml", "text/xml"); + _mimeTypes.put("css", "text/css"); + _mimeTypes.put("htc", "text/x-component"); + _mimeTypes.put("gif", "image/gif"); + _mimeTypes.put("jpg", "image/jpeg"); + _mimeTypes.put("jpeg", "image/jpeg"); + _mimeTypes.put("png", "image/png"); + _mimeTypes.put("ico", "image/x-icon"); + _mimeTypes.put("mp3", "audio/mpeg"); + _mimeTypes.put("m3u", "audio/mpeg-url"); + _mimeTypes.put("js", "application/x-javascript"); + _mimeTypes.put("pdf", "application/pdf"); + _mimeTypes.put("doc", "application/msword"); + _mimeTypes.put("ppt", "application/mspowerpoint"); + _mimeTypes.put("xls", "application/excel"); + _mimeTypes.put("ogg", "application/x-ogg"); + _mimeTypes.put("zip", "application/octet-stream"); + _mimeTypes.put("exe", "application/octet-stream"); + _mimeTypes.put("class", "application/octet-stream"); + } + + private final File _home; + private final boolean _allowDirectoryListing; + + /** + * URL-encodes everything between "/"-characters. Encodes spaces as '%20' instead of '+'. + * + * @param uri the uri to be encoded. + * @return the encoded uri as a String. + */ + private String encodeUri(final String uri) + { + StringBuilder encodedUri = new StringBuilder(); + StringTokenizer st = new StringTokenizer(uri, "/ ", true); + while (st.hasMoreTokens()) + { + String tok = st.nextToken(); + if (tok.equals("/")) + { + encodedUri.append("/"); + } + else if (tok.equals(" ")) + { + encodedUri.append("%20"); + } + else + { + try + { + encodedUri.append(URLEncoder.encode(tok, "UTF-8")); + } + catch (UnsupportedEncodingException uee) + { + } + } + } + return encodedUri.toString(); + } + + /** + * Renders a number in a more "human readable" format providing a bytes/KB/MB/GB format depending on the size. + * + * @param number the number to be rendered. + * @return a String representation of the number in a more human readable form. + */ + private String renderNumber(float number) + { + if (number < 1000) + { + return String.format("%.0f", number) + " bytes"; + } + else if (number < 1000000) + { + number /= 1000.0f; + return String.format("%.1f", number) + " KB"; + } + else if (number < 1000000000) + { + number /= 1000000.0f; + return String.format("%.1f", number) + " MB"; + } + else + { + number /= 1000000000.0f; + return String.format("%.1f", number) + " GB"; + } + } + + /** + * Construct an instance of FileServer. + * + * @param home the path name of the root directory that we wish this FieServer to serve via HTTP. + * @param allowDirectoryListing a flag that if set will serve a directory listing to the client and thus enable + * browsing to sub-directories of the home directory. N.B. protection has been put in place to mitigate + * against the possibility of serving directories that may be parents of the root directory. + */ + public FileServer(final String home, final boolean allowDirectoryListing) + { + _home = new File(home); + _allowDirectoryListing = allowDirectoryListing; + } + + /** + * Called by the Web Server to allow a Server to handle a GET request. + * + * @param tx the HttpTransaction containing the request from the client and used to send the response. + */ + public void doGet(final HttpTransaction tx) throws IOException + { + String user = tx.getPrincipal() != null ? tx.getPrincipal() : "none"; + String path = tx.getRequestURI(); + + //System.out.println(); + //System.out.println("FileServer doGet " + path + ", user: " + user); + //System.out.println("thread = " + Thread.currentThread().getId()); + //tx.logRequest(); + + // If the _home filesystem that we use as a root to serve files from is not a directory then + // we sent an error response and return. + if (!_home.isDirectory()) + { + tx.sendResponse(HTTP_INTERNAL_ERROR, "text/plain", + "500 Internal Server Error: given document root is not a directory."); + return; + } + + //String path = tx.getRequestURI(); + + // Prohibit getting out of _home directory + if (path.startsWith("..") || path.endsWith("..") || path.indexOf("../") >= 0) + { + tx.sendResponse(HTTP_FORBIDDEN, "text/plain", "403 Forbidden: Won't serve ../ for security reasons."); + return; + } + + File file = new File(_home, path); + if (!file.exists()) + { + tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found: File " + path + " not found."); + return; + } + + // List the directory, if necessary + if (file.isDirectory()) + { + File directory = file; + // Browsers get confused without '/' after the directory, send a redirect. + if (!path.endsWith("/")) + { + path += "/"; + tx.setHeader("Location", path); + tx.sendResponse(HTTP_MOVED_PERM, "text/html", + "Redirected: " + path + ""); + return; + } + + // First try index.html and index.htm + if (new File(directory, "index.html").exists()) + { + file = new File(_home, path + "/index.html"); + } + else if (new File(directory, "index.htm").exists()) + { + file = new File(_home, path + "/index.htm"); + } + else if (_allowDirectoryListing) + { // No index file, list the directory + StringBuilder response = new StringBuilder("

Directory " + path + "


"); + + if (path.length() > 1) + { + String u = path.substring(0, path.length() - 1); + int slash = u.lastIndexOf('/'); + if (slash >= 0 && slash < u.length()) + { + response.append("..
"); + } + } + + String[] files = directory.list(); + for (String name : files) + { + File current = new File(directory, name); + boolean isDir = current.isDirectory(); + boolean isFile = current.isFile(); + if (isDir) + { + response.append(""); + name += "/"; + } + + response.append("" + name + ""); + + if (isFile) + { // If it's a file show the file size + response.append("  (" + renderNumber(current.length()) + ")"); + } + response.append("
"); + if (isDir) + { + response.append("
"); + } + } + tx.sendResponse(HTTP_OK, "text/html", response.toString()); + return; + } + else + { + tx.sendResponse(HTTP_FORBIDDEN, "text/plain", "403 Forbidden: No directory listing."); + return; + } + } + + try + { + // Get MIME type from file name extension, if possible + String fileName = file.getCanonicalPath(); + String mime = null; + int dot = fileName.lastIndexOf('.'); + if (dot >= 0) + { + String fileExtension = fileName.substring(dot + 1).toLowerCase(); + mime = _mimeTypes.get(fileExtension); + } + + if (mime == null) + { + mime = "application/octet-stream"; + } + + // Use Range header allow download resuming. + long startFrom = 0; + long length = file.length(); + + String range = tx.getHeader("Range"); + if (range != null) + { + if (range.startsWith("bytes=")) + { + range = range.substring("bytes=".length()); + + int minus = range.indexOf('-'); + if (minus > 0) + { + range = range.substring(0, minus); + } + + try + { + startFrom = Long.parseLong(range); + } + catch (NumberFormatException nfe) + { + } + } + } + + FileInputStream is = new FileInputStream(file); + is.skip(startFrom); + + int status = (startFrom == 0) ? HTTP_OK : HTTP_PARTIAL; + tx.setHeader("Content-Length", "" + (length - startFrom)); + tx.setHeader("Content-Range", "" + startFrom + "-" + (length - 1) + "/" + length); + + tx.sendResponse(status, mime, is); + } + catch (IOException ioe) + { + tx.sendResponse(HTTP_FORBIDDEN, "text/plain", "403 Forbidden: Reading file failed."); + } + } + + /** + * Called by the Web Server to allow a Server to handle a POST request. + * + * @param tx the HttpTransaction containing the request from the client and used to send the response. + */ + public void doPost(final HttpTransaction tx) throws IOException + { + tx.sendResponse(HTTP_BAD_METHOD, "text/plain", "405 Bad Method."); + } + + /** + * Called by the Web Server to allow a Server to handle a PUT request. + * + * @param tx the HttpTransaction containing the request from the client and used to send the response. + */ + public void doPut(final HttpTransaction tx) throws IOException + { + tx.sendResponse(HTTP_BAD_METHOD, "text/plain", "405 Bad Method."); + } + + /** + * Called by the Web Server to allow a Server to handle a DELETE request. + * + * @param tx the HttpTransaction containing the request from the client and used to send the response. + */ + public void doDelete(final HttpTransaction tx) throws IOException + { + tx.sendResponse(HTTP_BAD_METHOD, "text/plain", "405 Bad Method."); + } +} + diff --git a/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/HttpTransaction.java b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/HttpTransaction.java new file mode 100644 index 0000000000..323ba29861 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/HttpTransaction.java @@ -0,0 +1,165 @@ +/* + * + * 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.restapi; + +import java.io.IOException; +import java.io.InputStream; + +/** + * An HttpTransaction encapsulates an HTTP request received and a response to be generated in one HTTP request/response + * "transaction". It provides methods for examining the request from the client, and for building and sending the + * response from the server. + *

+ * Note that the HttpTransaction interface isn't intended to be completely general, rather it is intended to abstract + * the services needed in org.apache.qpid.restapi in such a way as to be neutral as to whether the Web Server is + * based on com.sun.net.httpserver.HttpServer or javax.servlet.http.HttpServlet. + *

+ * The Server and HttpTransaction interfaces are intended to provide abstractions to enable the "business logic" to + * be isolated from the actual Web Server implementation choice, so for example a concrete HttpTransaction implementation + * could be created by wrapping a com.sun.net.httpserver.HttpExchange, but equally another implementation could wrap + * javax.servlet.http.HttpServletRequest and javax.servlet.http.HttpServletResponse so for example an HttpServlet + * could delegate to a Server instance passing the HttpTransaction it constructed from the HttpServletRequest and + * HttpServletResponse. + * + * @author Fraser Adams + */ +public interface HttpTransaction +{ + /** + * Log the HTTP request information (primarily for debugging purposes) + */ + public void logRequest(); + + /** + * Return the content passed in the request from the client as a Stream. + * @return the content passed in the request from the client as a Stream. + */ + public InputStream getRequestStream() throws IOException; + + /** + * Return the content passed in the request from the client as a String. + * @return the content passed in the request from the client as a String. + */ + public String getRequestString() throws IOException; + + /** + * Return the content passed in the request from the client as a byte[]. + * @return the content passed in the request from the client as a byte[]. + */ + public byte[] getRequest() throws IOException; + + /** + * Send the content passed as a String as an HTTP response back to the client. + * @param status the HTTP status code e.g. 200 for OK. + * @param mimeType the mimeType of the response content e.g. text/plain, text/xml, image/jpeg etc. + * @param content the content of the response passed as a String. + */ + public void sendResponse(final int status, final String mimeType, final String content) throws IOException; + + /** + * Send the content passed as a byte[] as an HTTP response back to the client. + * @param status the HTTP status code e.g. 200 for OK. + * @param mimeType the mimeType of the response content e.g. text/plain, text/xml, image/jpeg etc. + * @param content the content of the response passed as a byte[]. + */ + public void sendResponse(final int status, final String mimeType, final byte[] content) throws IOException; + + /** + * Send the content passed as an InputStream as an HTTP response back to the client. + * @param status the HTTP status code e.g. 200 for OK. + * @param mimeType the mimeType of the response content e.g. text/plain, text/xml, image/jpeg etc. + * @param is the content of the response passed as an InputStream. + */ + public void sendResponse(final int status, final String mimeType, final InputStream is) throws IOException; + + /** + * Returns the Internet Protocol (IP) address of the client or last proxy that sent the request. + * @return the Internet Protocol (IP) address of the client or last proxy that sent the request. + */ + public String getRemoteAddr(); + + /** + * Returns the fully qualified name of the client or the last proxy that sent the request. + * @return the fully qualified name of the client or the last proxy that sent the request. + */ + public String getRemoteHost(); + + /** + * Returns the Internet Protocol (IP) source port of the client or last proxy that sent the request. + * @return the Internet Protocol (IP) source port of the client or last proxy that sent the request. + */ + public int getRemotePort(); + + /** + * Returns a String containing the name of the current authenticated user. If the user has not been authenticated, + * the method returns null. + * @return a String containing the name of the user making this request; null if the user has not been authenticated. + */ + public String getPrincipal(); + + /** + * Returns the name of the HTTP method with which this request was made, for example, GET, POST, or PUT. + * @return a String specifying the name of the method with which this request was made. + */ + public String getMethod(); + + /** + * Returns the part of this request's URL from the protocol name up to the query string in the first line of + * the HTTP request. + * @return a String containing the part of the URL from the protocol name up to the query string. + */ + public String getRequestURI(); + + /** + * Sets a response header with the given name and value. If the header had already been set, the new value + * overwrites the previous one. + * @param name a String specifying the header name. + * @param value a String specifying the header value. If it contains octet string, it should be encoded according + * to RFC 2047. + */ + public void setHeader(final String name, final String value); + + /** + * Returns the value of the specified request header as a String. If the request did not include a header of the + * specified name, this method returns null. If there are multiple headers with the same name, this method returns + * the first head in the request. The header name is case insensitive. You can use this method with any request + * header. + * @param name a String specifying the header name. + * @return a String containing the value of the requested header, or null if the request does not have a header of + * that name. + */ + public String getHeader(final String name); + + /** + * Returns the String value of the specified cookie. + * @param name a String specifying the cookie name. + */ + public String getCookie(final String name); + + /** + * Adds the specified cookie to the response. This method can be called multiple times to set more than one cookie. + * @param name a String specifying the cookie name. + * @param value a String specifying the cookie value. + */ + public void addCookie(final String name, final String value); +} + + diff --git a/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/JSON.java b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/JSON.java new file mode 100644 index 0000000000..dfc1e0c85d --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/JSON.java @@ -0,0 +1,265 @@ +/* + * + * 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.restapi; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +// QMF2 imports +import org.apache.qpid.qmf2.common.Handle; +import org.apache.qpid.qmf2.common.ObjectId; +import org.apache.qpid.qmf2.common.QmfData; +import org.apache.qpid.qmf2.common.QmfDescribed; +import org.apache.qpid.qmf2.common.SchemaClassId; +import org.apache.qpid.qmf2.common.WorkItem; +import org.apache.qpid.qmf2.console.QmfConsoleData; + +/** + * This class provides a number of convenience methods to serialise and deserialise JSON strings to/from Java + * Collections or QmfData objects. + * + * The JSONMapParser class used here is largely a direct copy of org.apache.qpid.messaging.util.AddressParser + * as it provides a handy mechanism to parse a JSON String into a Map which is the only JSON requirement that + * we really need for QMF. Originally this code simply did "import org.apache.qpid.messaging.util.AddressParser;" + * but there's a restriction/bug on the core AddressParser whereby it serialises integers into Java Integer + * which means that long integer values aren't correctly stored. It's this restriction that gives Java Address + * Strings a defacto 2GB queue size. I should really provide a patch for the *real* AddressParser but it's better + * to add features covering "shorthand" forms for large values (e.g. k/K, m/M, g/G for kilo, mega, giga etc.) + * to both the Java and C++ AddressParser to ensure maximum consistency. + * + * Because the JSON requirements for the REST API are relatively modest a fairly simple serialisation/deserialisation + * mechanism is included here and in the modified AddressParser classes rather than incorporating a full-blown + * Java JSON parser. This helps avoid a bot of bloat and keeps dependencies limited to the core qpid classes. + * + * @author Fraser Adams + */ +public final class JSON +{ + /** + * Serialise an Object to JSON. Note this isn't a full JSON serialisation of java.lang.Object, rather it only + * includes types that are relevant to QmfData Objects and the types that may be contained therein. + * @param item the Object that we wish to serialise to JSON. + * @return the JSON String encoding. + */ + public final static String fromObject(final Object item) + { + if (item == null) + { + return ""; + } + else + { + if (item instanceof Map) + { // Check if the value part is an ObjectId and serialise appropriately + Map map = (Map)item; + if (map.containsKey("_object_name")) + { // Serialise "ref" properties as String versions of ObjectId to match encoding used in fromQmfData() + return "\"" + new ObjectId(map) + "\""; + } + else + { + return fromMap(map); + } + } + else if (item instanceof List) + { + return fromList((List)item); + } + else if (item instanceof QmfData) + { + return fromQmfData((QmfData)item); + } + else if (item instanceof WorkItem) + { + return fromWorkItem((WorkItem)item); + } + else if (item instanceof String) + { + return "\"" + item + "\""; + } + else if (item instanceof byte[]) + { + return "\"" + new String((byte[])item) + "\""; + } + else if (item instanceof UUID) + { + return "\"" + item.toString() + "\""; + } + else + { + return item.toString(); + } + } + } + + /** + * Encode the Map contents so we can use the same code for fromMap and fromQmfData as the latter also needs + * to encode _object_id and _schema_id. This returns a String that needs to be topped and tailed with braces. + * @param m the Map that we wish to serialise to JSON. + * @return the String encoding of the contents. + */ + @SuppressWarnings("unchecked") + private final static String encodeMapContents(final Map m) + { + Map map = (Map)m; + StringBuilder buffer = new StringBuilder(512); + int size = map.size(); + int count = 1; + for (Map.Entry entry : map.entrySet()) + { + String key = (String)entry.getKey(); + buffer.append("\"" + key + "\":"); + + Object value = entry.getValue(); + buffer.append(fromObject(value)); + if (count++ < size) + { + buffer.append(","); + } + } + return buffer.toString(); + } + + /** + * Serialise a Map to JSON. + * @param m the Map that we wish to serialise to JSON. + * @return the JSON String encoding. + */ + @SuppressWarnings("unchecked") + public final static String fromMap(final Map m) + { + return "{" + encodeMapContents(m) + "}"; + } + + /** + * Serialise a List to JSON. + * @param list the List that we wish to serialise to JSON. + * @return the JSON String encoding. + */ + public final static String fromList(final List list) + { + StringBuilder buffer = new StringBuilder(512); + buffer.append("["); + int size = list.size(); + int count = 1; + for (Object item : list) + { + buffer.append(fromObject(item)); + if (count++ < size) + { + buffer.append(","); + } + } + buffer.append("]"); + return buffer.toString(); + } + + /** + * Serialise a QmfData Object to JSON. If the Object is a QmfConsoleData we serialise the ObjectId as a String + * which is the same encoding used for the various "ref" properies in fromObject(). + * @param data the QmfData that we wish to serialise to JSON. + * @return the JSON String encoding. + */ + public final static String fromQmfData(final QmfData data) + { + String consoleDataInfo = ""; + + if (data instanceof QmfConsoleData) + { + QmfConsoleData consoleData = (QmfConsoleData)data; + SchemaClassId sid = consoleData.getSchemaClassId(); + long[] ts = consoleData.getTimestamps(); + + String objectId = "\"_object_id\":\"" + consoleData.getObjectId().toString() + "\","; + String schemaId = "\"_schema_id\":{" + + "\"_package_name\":\"" + sid.getPackageName() + + "\",\"_class_name\":\"" + sid.getClassName() + + "\",\"_type\":\"" + sid.getType() + + "\",\"_hash\":\"" + sid.getHashString() + + "\"},"; + + String timestamps = "\"_update_ts\":" + ts[0] + "," + + "\"_create_ts\":" + ts[1] + "," + + "\"_delete_ts\":" + ts[2] + ","; + + consoleDataInfo = objectId + schemaId + timestamps; + } + + return "{" + consoleDataInfo + encodeMapContents(data.mapEncode()) + "}"; + } + + /** + * Serialise a WorkItem Object to JSON. + * @param data the WorkItem that we wish to serialise to JSON. + * @return the JSON String encoding. + */ + public final static String fromWorkItem(final WorkItem data) + { + // TODO There are a couple of WorkItem types that won't serialise correctly - SubscriptionIndicationWorkItem + // and MethodCallWorkItem. Their params require a custom serialiser - though they probably won't be used + // from a REST API so they've been parked for now. + String type = "\"_type\":\"" + data.getType() + "\","; + Handle handle = data.getHandle(); + String handleString = handle == null ? "" : "\"_handle\":\"" + handle.getCorrelationId() + "\","; + String params = "\"_params\":" + fromObject(data.getParams()); + + return "{" + type + handleString + params + "}"; + } + + /** + * Create a Map from a JSON String. + * The JSONMapParser class used here is largely a direct copy of org.apache.qpid.messaging.util.AddressParser + * as it provides a handy mechanism to parse a JSON String into a Map which is the only JSON requirement that + * we really need for QMF. Originally this code simply did "import org.apache.qpid.messaging.util.AddressParser;" + * but there's a restriction/bug on the core AddressParser whereby it serialises integers into Java Integer + * which means that long integer values aren't correctly stored. It's this restriction that gives Java Address + * Strings a defacto 2GB queue size. I should really provide a patch for the *real* AddressParser but it's better + * to add features covering "shorthand" forms for large values (e.g. k/K, m/M, g/G for kilo, mega, giga etc.) + * to both the Java and C++ AddressParser to ensure maximum consistency. + * @param json the JSON String that we wish to decode into a Map. + * @return the Map encoding of the JSON String. + */ + public final static Map toMap(final String json) + { + if (json == null || json.equals("")) + { + return Collections.EMPTY_MAP; + } + else + { + return new JSONMapParser(json).map(); + } + } + + /** + * Create a QmfData from a JSON String. + * @param json the JSON String that we wish to decode into a QmfData. + * @return the QmfData encoding of the JSON String. + */ + public final static QmfData toQmfData(final String json) + { + return new QmfData(toMap(json)); + } +} + + diff --git a/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/JSONMapParser.java b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/JSONMapParser.java new file mode 100644 index 0000000000..9a6354e099 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/JSONMapParser.java @@ -0,0 +1,402 @@ +/* + * + * 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.restapi; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.qpid.messaging.Address; +import org.apache.qpid.messaging.util.Lexer; +import org.apache.qpid.messaging.util.Lexicon; +import org.apache.qpid.messaging.util.ParseError; +import org.apache.qpid.messaging.util.Token; + +/** + * AddressParser + * + * This JSONMapParser class used is mostly just a direct copy of org.apache.qpid.messaging.util.AddressParser + * as it provides a handy mechanism to parse a JSON String into a Map which is the only JSON requirement that + * we really need for QMF. + * + * Unfortunately there's a restriction/bug on the core AddressParser whereby it serialises integers into Java Integer + * which means that long integer values aren't correctly stored. It's this restriction that gives Java Address + * Strings a defacto 2GB queue size. I should really provide a patch for the *real* AddressParser but it's better + * to add features covering "shorthand" forms for large values (e.g. k/K, m/M, g/G for kilo, mega, giga etc.) + * to both the Java and C++ AddressParser to ensure maximum consistency. + * + * This AddressParser clone largely uses the classes from org.apache.qpid.messaging.util like the real AddressParser + * but unfortunately the Parser class was package scope rather than public, so I've done some "copy and paste reuse" + * to add the Parser methods into this version of AddressParser. I've also removed the bits that actually create + * an Address as all we need to do is to parse into a java.util.Map. + */ + +public class JSONMapParser +{ + + private static Lexicon lxi = new Lexicon(); + + private static Token.Type LBRACE = lxi.define("LBRACE", "\\{"); + private static Token.Type RBRACE = lxi.define("RBRACE", "\\}"); + private static Token.Type LBRACK = lxi.define("LBRACK", "\\["); + private static Token.Type RBRACK = lxi.define("RBRACK", "\\]"); + private static Token.Type COLON = lxi.define("COLON", ":"); + private static Token.Type SEMI = lxi.define("SEMI", ";"); + private static Token.Type SLASH = lxi.define("SLASH", "/"); + private static Token.Type COMMA = lxi.define("COMMA", ","); + private static Token.Type NUMBER = lxi.define("NUMBER", "[+-]?[0-9]*\\.?[0-9]+"); + // Make test for true and false case insensitive. N.B. org.apache.qpid.messaging.util.AddressParser test is + // case sensitive - not sure if that's a bug/oversight in the AddressParser?? + private static Token.Type TRUE = lxi.define("TRUE", "(?i)True"); + private static Token.Type FALSE = lxi.define("FALSE", "(?i)False"); + private static Token.Type ID = lxi.define("ID", "[a-zA-Z_](?:[a-zA-Z0-9_-]*[a-zA-Z0-9_])?"); + private static Token.Type STRING = lxi.define("STRING", "\"(?:[^\\\"]|\\.)*\"|'(?:[^\\']|\\.)*'"); + private static Token.Type ESC = lxi.define("ESC", "\\\\[^ux]|\\\\x[0-9a-fA-F][0-9a-fA-F]|\\\\u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]"); + private static Token.Type SYM = lxi.define("SYM", "[.#*%@$^!+-]"); + private static Token.Type WSPACE = lxi.define("WSPACE", "[\\s]+"); + private static Token.Type EOF = lxi.eof("EOF"); + + private static Lexer LEXER = lxi.compile(); + +/********** Copied from org.apache.qpid.messaging.util.Parser as Parser was package scope **********/ + + private List tokens; + private int idx = 0; + + Token next() + { + return tokens.get(idx); + } + + boolean matches(Token.Type ... types) + { + for (Token.Type t : types) + { + if (next().getType() == t) + { + return true; + } + } + return false; + } + + Token eat(Token.Type ... types) + { + if (types.length > 0 && !matches(types)) + { + throw new ParseError(next(), types); + } + else + { + Token t = next(); + idx += 1; + return t; + } + } + +/***************************************************************************************************/ + + public static List lex(String input) + { + return LEXER.lex(input); + } + + static List wlex(String input) + { + List tokens = new ArrayList(); + for (Token t : lex(input)) + { + if (t.getType() != WSPACE) + { + tokens.add(t); + } + } + return tokens; + } + + static String unquote(String st, Token tok) + { + StringBuilder result = new StringBuilder(); + for (int i = 1; i < st.length() - 1; i++) + { + char ch = st.charAt(i); + if (ch == '\\') + { + char code = st.charAt(i+1); + switch (code) + { + case '\n': + break; + case '\\': + result.append('\\'); + break; + case '\'': + result.append('\''); + break; + case '"': + result.append('"'); + break; + case 'a': + result.append((char) 0x07); + break; + case 'b': + result.append((char) 0x08); + break; + case 'f': + result.append('\f'); + break; + case 'n': + result.append('\n'); + break; + case 'r': + result.append('\r'); + break; + case 't': + result.append('\t'); + break; + case 'u': + result.append(decode(st.substring(i+2, i+6))); + i += 4; + break; + case 'v': + result.append((char) 0x0b); + break; + case 'o': + result.append(decode(st.substring(i+2, i+4), 8)); + i += 2; + break; + case 'x': + result.append(decode(st.substring(i+2, i+4))); + i += 2; + break; + default: + throw new ParseError(tok); + } + i += 1; + } + else + { + result.append(ch); + } + } + + return result.toString(); + } + + static char[] decode(String hex) + { + return decode(hex, 16); + } + + static char[] decode(String code, int radix) + { + return Character.toChars(Integer.parseInt(code, radix)); + } + + /** + * This method is the the main place where this class differs from org.apache.qpid.messaging.util.AddressParser. + * If the token type is a STRING it checks for a number (with optional floating point) ending in K, M or G + * and if it is of this type it creates a Long out of the float value multiplied by 1000, 1000000 or 1000000000. + * If the token type is a NUMBER it tries to parse into an Integer like AddressParser, but if that fails it + * tries to parse onto a Long which allows much larger integer values to be used. + */ + static Object tok2obj(Token tok) + { + Token.Type type = tok.getType(); + String value = tok.getValue(); + if (type == STRING) + { + value = unquote(value, tok); + + // Initial regex to check for a number (with optional floating point) ending in K, M or G + if (value.matches("([0-9]*\\.[0-9]+|[0-9]+)\\s*[kKmMgG]")) + { + // If it's a numeric string perform the relevant multiplication and return as a Long. + int length = value.length(); + if (length > 1) + { + String end = value.substring(length - 1, length).toUpperCase(); + String start = value.substring(0, length - 1).trim(); + + if (end.equals("K")) + { + return Long.valueOf((long)(Float.parseFloat(start) * 1000.0)); + } + else if (end.equals("M")) + { + return Long.valueOf((long)(Float.parseFloat(start) * 1000000.0)); + } + else if (end.equals("G")) + { + return Long.valueOf((long)(Float.parseFloat(start) * 1000000000.0)); + } + } + + return value; + } + else + { + return value; + } + } + else if (type == NUMBER) + { + // This block extends the original AddressParser handling of NUMBER. It first attempts to parse the String + // into an Integer in order to be backwards compatible with AddressParser however if this causes a + // NumberFormatException it then attempts to parse into a Long. + if (value.indexOf('.') >= 0) + { + return Double.valueOf(value); + } + else + { + try + { + return Integer.decode(value); + } + catch (NumberFormatException nfe) + { + return Long.decode(value); + } + } + } + else if (type == TRUE) + { + return true; + } + else if (type == FALSE) + { + return false; + } + else + { + return value; + } + } + + public JSONMapParser(String input) + { + this.tokens = wlex(input); // Copied from org.apache.qpid.messaging.util.Parser + this.idx = 0; // Copied from org.apache.qpid.messaging.util.Parser + } + + public Map map() + { + eat(LBRACE); + + Map result = new HashMap(); + while (true) + { + if (matches(NUMBER, STRING, ID, LBRACE, LBRACK)) + { + keyval(result); + if (matches(COMMA)) + { + eat(COMMA); + } + else if (matches(RBRACE)) + { + break; + } + else + { + throw new ParseError(next(), COMMA, RBRACE); + } + } + else if (matches(RBRACE)) + { + break; + } + else + { + throw new ParseError(next(), NUMBER, STRING, ID, LBRACE, LBRACK, + RBRACE); + } + } + + eat(RBRACE); + return result; + } + + void keyval(Map map) + { + Object key = value(); + eat(COLON); + Object val = value(); + map.put(key, val); + } + + Object value() + { + if (matches(NUMBER, STRING, ID, TRUE, FALSE)) + { + return tok2obj(eat()); + } + else if (matches(LBRACE)) + { + return map(); + } + else if (matches(LBRACK)) + { + return list(); + } + else + { + throw new ParseError(next(), NUMBER, STRING, ID, LBRACE, LBRACK); + } + } + + List list() + { + eat(LBRACK); + + List result = new ArrayList(); + + while (true) + { + if (matches(RBRACK)) + { + break; + } + else + { + result.add(value()); + if (matches(COMMA)) + { + eat(COMMA); + } + else if (matches(RBRACK)) + { + break; + } + else + { + throw new ParseError(next(), COMMA, RBRACK); + } + } + } + + eat(RBRACK); + return result; + } + +} diff --git a/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/QpidRestAPI.java b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/QpidRestAPI.java new file mode 100644 index 0000000000..b796c12e98 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/QpidRestAPI.java @@ -0,0 +1,206 @@ +/* + * + * 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.restapi; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.concurrent.Executors; +import java.util.List; + +import com.sun.net.httpserver.HttpServer; + +import org.apache.qpid.qmf2.util.GetOpt; + +import org.apache.qpid.restapi.httpserver.Authenticator; +import org.apache.qpid.restapi.httpserver.Delegator; + +/** + * Note QpidRestAPI makes use of the Java 1.6 "Easter Egg" HttpServer and associated classes to create a simple + * HTTP REST interface to Qpid and QMF2 functionality. + *

+ * Because HttpServer is in the com.sun.net.httpserver.HttpServer namespace it is technically not part of core Java + * and so may not be supported in all JREs, however it seems to be present in the OpenJDK Runtime Environment that is + * installed on many Linux variants. It is documented here com.sun.net.httpserver + *

+ * The reason for choosing com.sun.net.httpserver.HttpServer is simply in order to provide a very lightweight and + * dependency free approach to creating an HTTP Proxy. Clearly other approaches such as full-blown Servlet containers + * and the like might provide a better mechanism for some environments, but HttpServer really is very simple to use. + *

+ * If there is a desire to use Servlets rather than HttpServer it should be fairly straightforward as two interfaces + * have been provided (Server and HttpTransaction) that abstract the key behaviour, so for example a concrete HttpTransaction + * implementation could be created by wrapping a com.sun.net.httpserver.HttpExchange, but equally another implementation + * could wrap javax.servlet.http.HttpServletRequest and javax.servlet.http.HttpServletResponse, so for example an + * HttpServlet could delegate to a Server instance passing the Conversation it constructed from the HttpServletRequest + * and HttpServletResponse in a similar way that our Delegator implementation of HttpHandler delegates to the Servers. + * + *

+ * Usage:  QpidRestAPI [options]
+ *
+ * Options:
+ *        -h,             --help
+ *                        show this help message and exit
+ *        -a <address>,   --broker-addr=<address>
+ *                         broker-addr is in the form:  [username/password@]
+ *                         hostname | ip-address [:<port>]   ex:  localhost,
+ *                         10.1.1.7:10000, broker-host:10000,
+ *                         guest/guest@localhost
+ *                         Default is the host QpidRestAPI runs on & port 5672.
+ *        -i <address>,   --addr=<address>
+ *                        the hostname of the QpidRestAPI default is the wildcard address
+ *                        (Bind to a specific address on a multihomed host)
+ *        -p <port>,      --port=<port>
+ *                        the port the QpidRestAPI is bound to default is 8080
+ *        -b <backlog>,   --backlog=<backlog>
+ *                        the socket backlog default is 10
+ *        -w <directory>, --webroot=<directory>
+ *                        the directory of the QpidRestAPI Web Site default is qpid-web
+ * 
+ * @author Fraser Adams + */ +public class QpidRestAPI +{ + private static final String _usage = + "Usage: QpidRestAPI [options]\n"; + + private static final String _description = + "Creates an HTTP REST interface to enable us to send messages to Qpid brokers and use QMF2 via HTTP.\n"; + + private static final String _options = + "Options:\n" + + " -h, --help show this help message and exit.\n" + + " -a
, --broker-addr=
\n" + + " broker-addr is in the form: [username/password@]\n" + + " hostname | ip-address [:<port>] e.g.\n" + + " localhost, 10.1.1.7:10000, broker-host:10000,\n" + + " guest/guest@localhost, guest/guest@broker-host:10000\n" + + " default is the host QpidRestAPI runs on & port 5672.\n" + + " -i
, --addr=
\n" + + " the hostname of the QpidRestAPI.\n" + + " default is the wildcard address.\n" + + " (Bind to a specific address on a multihomed host).\n" + + " -p , --port=\n" + + " the port the QpidRestAPI is bound to.\n" + + " default is 8080.\n" + + " -b , --backlog=\n" + + " the socket backlog.\n" + + " default is 10\n" + + " -w , --webroot=\n" + + " the directory of the QpidRestAPI Web Site.\n" + + " default is qpid-web.\n"; + + + /** + * Construct and start an instance of QpidRestAPI. This class used a Delegator class to delegate to underlying + * Server instances that actually implement the business logic of the REST API. + * + * @param addr the the address the QpidRestAPI is bound to (null = default). + * @param port the port the QpidRestAPI is bound to. + * @param broker the address of the Qpid broker to connect to (null = default). + * @param backlog the socket backlog. + * @param webroot the directory of the QpidRestAPI Web Site. + */ + public QpidRestAPI(final String addr, final int port, String broker, final int backlog, final String webroot) + throws IOException + { + final InetSocketAddress inetaddr = (addr == null) ? new InetSocketAddress(port) : + new InetSocketAddress(addr, port); + final HttpServer server = HttpServer.create(inetaddr, backlog); + + broker = (broker == null) ? inetaddr.getAddress().getHostAddress() + ":5672" : broker; + + Delegator fileserver = new Delegator(new FileServer(webroot + "/web", true)); + Delegator qpidserver = new Delegator(new QpidServer(broker)); + + Authenticator authenticator = new Authenticator(this.getClass().getCanonicalName(), webroot + "/authentication"); + + server.setExecutor(Executors.newCachedThreadPool()); + server.createContext("/", fileserver); + server.createContext("/ui", fileserver).setAuthenticator(authenticator); + server.createContext("/qpid/connection", qpidserver).setAuthenticator(authenticator); + server.start(); + } + + /** + * Runs QpidRestAPI. + * @param args the command line arguments. + */ + public static void main(String[] args) throws IOException + { + String logLevel = System.getProperty("amqj.logging.level"); + logLevel = (logLevel == null) ? "FATAL" : logLevel; // Set default log level to FATAL rather than DEBUG. + System.setProperty("amqj.logging.level", logLevel); + + String[] longOpts = {"help", "host=", "port=", "backlog=", "webroot="}; + try + { + String addr = null; + int port = 8080; + String broker = null; + int backlog = 10; + String webroot = "qpid-web"; + + GetOpt getopt = new GetOpt(args, "ha:i:p:b:w:", longOpts); + List optList = getopt.getOptList(); + String[] cargs = {}; + cargs = getopt.getEncArgs().toArray(cargs); + + for (String[] opt : optList) + { + if (opt[0].equals("-h") || opt[0].equals("--help")) + { + System.out.println(_usage); + System.out.println(_description); + System.out.println(_options); + System.exit(1); + } + else if (opt[0].equals("-a") || opt[0].equals("--broker-addr")) + { + broker = opt[1]; + } + else if (opt[0].equals("-i") || opt[0].equals("--addr")) + { + addr = opt[1]; + } + else if (opt[0].equals("-p") || opt[0].equals("--port")) + { + port = Integer.parseInt(opt[1]); + } + else if (opt[0].equals("-b") || opt[0].equals("--backlog")) + { + backlog = Integer.parseInt(opt[1]); + } + else if (opt[0].equals("-w") || opt[0].equals("--webroot")) + { + webroot = opt[1]; + } + } + + QpidRestAPI restAPI = new QpidRestAPI(addr, port, broker, backlog, webroot); + } + catch (IllegalArgumentException e) + { + System.out.println(_usage); + System.out.println(e.getMessage()); + System.exit(1); + } + } +} + diff --git a/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/QpidServer.java b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/QpidServer.java new file mode 100644 index 0000000000..f1bdcf85bc --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/QpidServer.java @@ -0,0 +1,654 @@ +/* + * + * 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.restapi; + +// Simple Logging Facade 4 Java +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// Misc Imports +import java.io.IOException; +import java.net.InetAddress; +import java.util.Collections; +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.console.Agent; +import org.apache.qpid.qmf2.console.Console; +import org.apache.qpid.qmf2.console.MethodResult; +import org.apache.qpid.qmf2.console.QmfConsoleData; + +import static java.net.HttpURLConnection.HTTP_OK; +import static java.net.HttpURLConnection.HTTP_BAD_METHOD; +import static java.net.HttpURLConnection.HTTP_CREATED; +import static java.net.HttpURLConnection.HTTP_NOT_FOUND; +import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED; +import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR; + + +/** + * This class implements the REST API proper, it is an implementation of the Server interface which allows us to + * abstract the "business logic" from the Web Server implementation technology (HttpServer/Servlet etc.). + *

+ * The REST API is as follows: + *

+ * PUT: <host>:<port>/qpid/connection/<name>
+ *      HTTP body: {"url":<url>,"connectionOptions":<connectionOptions>[,"disableEvents":true;]}
+ *      <url>: A string containing an AMQP connection URL as used in the qpid::messaging API.
+ *      <connectionOptions>: A JSON string containing connectionOptions in the form specified in the 
+ *      qpid::messaging API.
+ *
+ *      This method creates a Qpid Connection Object with the name <name> using the specified url and options.
+ *
+ *      The optional disableEvents property is used to start up a QMF Connection which can only
+ *      do synchronous calls such as getObjects() and can't receive Agent updates or QMF2 Events.
+ *
+ * DELETE: <host>:<port>/qpid/connection/<name>
+ *
+ *      This method deletes the Qpid Connection Object with the name <name>.
+ *
+ * POST: <host>:<port>/qpid/connection/<name>/object/<ObjectId>
+ *      HTTP body: {"_method_name":<method>,"_arguments":<inArgs>}
+ *      <method>: A string containing the QMF2 method name e.g. "getLogLevel", "setLogLevel", "create", "delete".
+ *      <inArgs>: A JSON string containing the method arguments e.g. {"level":"debug+:Broker"} for setLogLevel.
+ *      HTTP response: A JSON string containing the response e.g. {"level":"notice+"} for getLogLevel (may be empty).
+ *
+ *      This method invokes the QMF2 method <method> with arguments <inArgs> on the object <ObjectId>
+ *
+ * GET: <host>:<port>/qpid/connection;
+ * 
+ *      This method retrieves (as a JSON string) the complete set of connections currently enabled on the Server.
+ *      The returned JSON string represents a Map keyed by the connection name/handle. The value part is itself a
+ *      Map containing the Connection URL and Connection Options used to create the Qpid Connection. e.g.
+ *      {"8c5116d6-46a1-489b-93d8-fde525e0d76d":{"url":"0.0.0.0:5672","connectionOptions":{}}}
+ *
+ * GET: <host>:<port>/qpid/connection/<name>
+ * 
+ *      This method retrieves (as a JSON string) a Map containing the Connection URL and Connection Options used to    
+ *      create the Qpid Connection with the specified <name>. e.g.
+ *      {"url":"0.0.0.0:5672","connectionOptions":{}}
+ *
+ * GET: <host>:<port>/qpid/connection/<name>/console/objects/<className>
+ * 
+ *      This method retrieves (as a JSON string) the list of QmfConsoleData objects with the specified <className> 
+ *      using the QMF2 Console associated with the Qpid Connection Object with the name <name>.
+ *      This is the REST equivalent of Console.getObjects(className) which searches across all packages and all Agents 
+ *      for the specified className.
+ *
+ * GET: <host>:<port>/qpid/connection/<name>/console/objects/<packageName>/<className>
+ *      !!not yet implemented!!
+ *      This method retrieves (as a JSON string) the list of QmfConsoleData objects with the specified 
+ *      <packageName> and <className> using the QMF2 Console associated with the Qpid Connection Object
+ *      with the name <name>.
+ *      This is the REST equivalent of Console.getObjects(packageName, className) which searches across all Agents 
+ *      for the specified className in the package packageName.
+ *
+ * GET: <host>:<port>/qpid/connection/<name>/object/<ObjectId>
+ *      This method retrieves (as a JSON string) the QmfConsoleData object with the specified <ObjectId>
+ *      using the QMF2 Console associated with the Qpid Connection Object with the name <name>.
+ *      This is the REST implementation of getObjects(oid) it is also the equivalent of the QmfConsoleData refresh() 
+ *      method where an object can update its state.
+ *      Note that there's a slight variance on the QMF2 API here as that returns an array/list of QmfConsoledData
+ *      objects for all queries, however as the call with ObjectId will only return one or zero objects this 
+ *      implementation returns the single QmfConsoleData object found or a 404 Not Found response.
+ *
+ *      N.B. that the ManagementAgent on the broker appears not to set the timestamp properties in the response to this
+ *      call, which means that they get set to current time in the QmfConsoleData, this is OK for _update_ts but not
+ *      for _create_ts and _delete_ts. Users of this call should be aware of that in their own code.
+ *
+ * GET: <host>:<port>/qpid/connection/<name>/console/objects
+ * GET: <host>:<port>/qpid/connection/<name>/console/classes (synonyms)
+ * 
+ *      This method retrieves (as a JSON string) the list of SchemaClassId for all available Schema for all Agents.
+ *      This is the REST equivalent of Console.getClasses() which searches across all Agents.
+ *
+ * GET: <host>:<port>/qpid/connection/<name>/console/classes/<agentName>
+ * 
+ *      This method retrieves (as a JSON string) the list of SchemaClassId for all available Schema on the
+ *      Agent named <agentName>.
+ *      This is the REST equivalent of Console.getClasses(agent) which searches across a specified Agent.
+ *
+ * GET: <host>:<port>/qpid/connection/<name>/console/packages
+ * 
+ *      This method retrieves (as a JSON string) the list of all known Packages for all Agents.
+ *      This is the REST equivalent of Console.getPackages() which searches across all Agents.
+ *
+ * GET: <host>:<port>/qpid/connection/<name>/console/packages/<agentName>
+ * 
+ *      This method retrieves (as a JSON string) the list of all known Packages on the Agent named <agentName>.
+ *      This is the REST equivalent of Console.getPackages(agent) which searches across a specified Agent.
+ *
+ * GET: <host>:<port>/qpid/connection/<name>/console/agents
+ * GET: <host>:<port>/qpid/connection/<name>/console/agent (synonyms)
+ * 
+ *      This method retrieves (as a JSON string) the list of all known Agents.
+ *      This is the REST equivalent of Console.getAgents().
+ *
+ * GET: <host>:<port>/qpid/connection/<name>/console/agent/<agentName>
+ * 
+ *      This method retrieves (as a JSON string) the Agent named <agentName>.
+ *      This is the REST equivalent of Console.getAgent(agentName).
+ *
+ * GET: <host>:<port>/qpid/connection/<name>/console/address
+ * 
+ *      This method retrieves (as a JSON string) the AMQP address this Console is listening to.
+ *      This is the REST equivalent of Console.getAddress().
+ *
+ * GET: <host>:<port>/qpid/connection/<name>/console/workItemCount
+ * 
+ *      This method retrieves (as a plain text string) the count of pending WorkItems that can be retrieved
+ *      from this Console.
+ *      This is the REST equivalent of Console.getWorkItemCount().
+ *
+ * GET: <host>:<port>/qpid/connection/<name>/console/nextWorkItem
+ * 
+ *      This method retrieves (as a JSON string) the next pending work item from this Console (N.B. this method
+ *      blocks until a WorkItem is available so should only be called asynchronously e.g. via AJAX).
+ *      This is the REST equivalent of Console.getNextWorkitem().
+ * 
+ * @author Fraser Adams + */ +public final class QpidServer implements Server +{ + private static final Logger _log = LoggerFactory.getLogger(QpidServer.class); + + private ConnectionStore _connections = new ConnectionStore(); + private String _defaultBroker = null; + + public QpidServer(final String broker) + { + _defaultBroker = broker; + } + + /** + * Handle a "/qpid/connection//console/objects" or + * "/qpid/connection//console/objects/" request, + * in other words a request for information about an object resource specified by the remaining path. + * Only the GET method is valid for this resource and it is in effect the REST mapping for Console.getObjects(). + */ + private void sendGetObjectsResponse(final HttpTransaction tx, final Console console, final String path) throws IOException + { + String[] params = path.split("/"); + if (params.length == 1) + { // With one parameter we call getObjects(className) + tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(console.getObjects(params[0]))); + } + else if (params.length == 2) + { // With two parameters we call getObjects(packageName, className) + //System.out.println("params = " + params[0] + ", " + params[1]); + tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(console.getObjects(params[0], params[1]))); + } + else if (params.length == 3) + { // TODO With three parameters we call getObjects(packageName, className, agent) + //System.out.println("params = " + params[0] + ", " + params[1] + ", " + params[2]); + tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Too many parameters for objects GET request."); + } else { + tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Too many parameters for objects GET request."); + } + } + + /** + * Called by the Web Server to allow a Server to handle a GET request. + * The HTTP GET URL structure for the REST API is specified above in the overall class documentation. + * + * @param tx the HttpTransaction containing the request from the client and used to send the response. + */ + public void doGet(final HttpTransaction tx) throws IOException + { + String path = tx.getRequestURI(); + + //System.out.println(); + //System.out.println("QpidServer doGet " + path); + //tx.logRequest(); + + if (path.startsWith("/qpid/connection/")) + { + path = path.substring(17); + + String user = tx.getPrincipal(); // Using the principal lets different users use the default connection. + if (path.length() == 0) + { // handle "/qpid/connection/" request with unspecified connection (returns list of available connections). + tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(_connections.getAll(user))); + } + else + { // if path.length() > 0 we're dealing with a specified Connection so extract the name and look it up. + String connectionName = path; + int i = path.indexOf("/"); + if (i > 0) // Can use > rather than >= as we've already tested for "/qpid/connection/" above. + { + connectionName = path.substring(0, i); + path = path.substring(i + 1); + } + else + { + path = ""; + } + + connectionName = user + "." + connectionName; + + // TODO what if we don't want a default connection. + // If necessary we create a new "default" Connection associated with the user. The default connection + // attempts to connect to a broker specified in the QpidRestAPI config (or default 0.0.0.0:5672). + if (connectionName.equals(user + ".default")) + { + ConnectionProxy defaultConnection = _connections.get(connectionName); + if (defaultConnection == null) + { + defaultConnection = _connections.create(connectionName, _defaultBroker, "", false); + + // Wait a maximum of 1000ms for the underlying Qpid Connection to become available. If we + // don't do this the first call using the default will return 404 Not Found. + defaultConnection.waitForConnection(1000); + } + } + + // Find the Connection with the name extracted from the URI. + ConnectionProxy connection = _connections.get(connectionName); + + if (connection == null) + { + tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found."); + } + else if (!connection.isConnected()) + { + tx.sendResponse(HTTP_INTERNAL_ERROR, "text/plain", "500 Broker Disconnected."); + } + else + { + if (path.length() == 0) + { // handle request for information about a specified Console + tx.sendResponse(HTTP_OK, "application/json", connection.toString()); + } + else + { // In this block we are dealing with resources associated with a specified connectionName. + // path describes the resources specifically related to "/qpid/connection/" + Console console = connection.getConsole(); + + if (path.startsWith("console/objects/")) + { // Get information about specified objects. + path = path.substring(16); + sendGetObjectsResponse(tx, console, path); + } + else if (path.startsWith("console/objects") && path.length() == 15) + { // If objects is unspecified treat as a synonym for classes. + tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(console.getClasses())); + } + else if (path.startsWith("console/address/")) + { // Get the Console AMQP Address + tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(console.getAddress())); + } + else if (path.startsWith("console/address") && path.length() == 15) + { // Get the Console AMQP Address + tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(console.getAddress())); + } + else if (path.startsWith("console/workItemCount/")) + { // Returns the count of pending WorkItems that can be retrieved. + tx.sendResponse(HTTP_OK, "text/plain", "" + console.getWorkitemCount()); + } + else if (path.startsWith("console/workItemCount") && path.length() == 21) + { // Returns the count of pending WorkItems that can be retrieved. + tx.sendResponse(HTTP_OK, "text/plain", "" + console.getWorkitemCount()); + } + else if (path.startsWith("console/nextWorkItem/")) + { // Obtains the next pending work item, or null if none available. + tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(console.getNextWorkitem())); + } + else if (path.startsWith("console/nextWorkItem") && path.length() == 20) + { // Obtains the next pending work item, or null if none available. + tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(console.getNextWorkitem())); + } + else if (path.startsWith("console/agents") && path.length() == 14) + { // Get information about all available Agents. + tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(console.getAgents())); + } + else if (path.startsWith("console/agent/")) + { // Get information about a specified Agent. + Agent agent = console.getAgent(path.substring(14)); + if (agent == null) + { + tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found."); + } + else + { + tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(agent)); + } + } + else if (path.startsWith("console/agent") && path.length() == 13) + { // If agent is unspecified treat as a synonym for agents. + tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(console.getAgents())); + } + else if (path.startsWith("console/classes/")) + { // Get information about the classes for a specified Agent + path = path.substring(16); // Get Agent name + + // TODO handle getClasses() for specified Agent + tx.sendResponse(HTTP_NOT_IMPLEMENTED, "text/plain", "501 getClasses() for specified Agent not yet implemented."); + } + else if (path.startsWith("console/classes") && path.length() == 15) + { // Get information about all the classes for all Agents + tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(console.getClasses())); + } + else if (path.startsWith("console/packages/")) + { // Get information about the packages for a specified Agent + path = path.substring(17); // Get Agent name + + // TODO handle getPackages() for specified Agent. + tx.sendResponse(HTTP_NOT_IMPLEMENTED, "text/plain", "501 getPackages() for specified Agent not yet implemented."); + } + else if (path.startsWith("object/")) + { + /** + * This is the REST implementation of getObjects(oid) it is also the equivalent of + * the QmfConsoleData refresh() method where an object can update its state. + * N.B. that the ManagementAgent on the broker appears not to set the timestamp properties + * in the response to this call, which means that they get set to current time in the + * QmfConsoleData, this is OK for _update_ts but not for _create_ts and _delete_ts + * users of this call should be aware of that in their own code. + */ + path = path.substring(7); + + // The ObjectId has been passed in the URI, create a real ObjectId + ObjectId oid = new ObjectId(path); + + List objects = console.getObjects(oid); + if (objects.size() == 0) + { + tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found."); + } + else + { + // Not that in a departure from the QMF2 API this returns the QmfConsoleData object + // rather than a list of size one. Perhaps the APIs should be completely consistent + // but this response seems more convenient. + tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(objects.get(0))); + } + } + else if (path.startsWith("console/packages") && path.length() == 16) + { // Get information about all the packages for all Agents + tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(console.getPackages())); + } + else + { + tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found."); + } + } + } + } + } + else if (path.startsWith("/qpid/connection")) + { // handle "/qpid/connection" request with unspecified connection (returns list of available connections). + String user = tx.getPrincipal(); // Using the principal lets different users use the default connection. + tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(_connections.getAll(user))); + } + else + { + tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found."); + } + } + + /** + * Called by the Web Server to allow a Server to handle a POST request. + *
+     * POST: <host>:<port>/qpid/connection/<name>/object/<ObjectId>
+     *      HTTP body: {"_method_name":<method>,"_arguments":<inArgs>}
+     *      <method>: A string containing the QMF2 method name e.g. "getLogLevel", "setLogLevel", "create", "delete".
+     *      <inArgs>: A JSON string containing the method arguments e.g. {"level":"debug+:Broker"} for setLogLevel.
+     *      HTTP response: A JSON string containing the response e.g. {"level":"notice+"} for getLogLevel (may be empty).
+     *
+     *      This method invokes the QMF2 method <method> with arguments <inArgs> on the object <ObjectId>
+     * 
+ * @param tx the HttpTransaction containing the request from the client and used to send the response. + */ + @SuppressWarnings("unchecked") + public void doPost(final HttpTransaction tx) throws IOException + { + String path = tx.getRequestURI(); + + //System.out.println(); + //System.out.println("QpidServer doPost " + path); + //System.out.println("thread = " + Thread.currentThread().getId()); + + if (path.startsWith("/qpid/connection/")) + { + path = path.substring(17); + String user = tx.getPrincipal(); + + String connectionName = path; + int i = path.indexOf("/"); + if (i > 0) // Can use > rather than >= as we've already tested for "/qpid/connection/" above. + { + connectionName = user + "." + path.substring(0, i); + path = path.substring(i + 1); + + // Find the Connection with the name extracted from the URI. + ConnectionProxy connection = _connections.get(connectionName); + + if (connection == null) + { + _log.info("QpidServer.doPost path: {} Connection not found.", tx.getRequestURI()); + tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found."); + } + else if (!connection.isConnected()) + { + tx.sendResponse(HTTP_INTERNAL_ERROR, "text/plain", "500 Broker Disconnected."); + } + else + { // If we get this far we should have found a Qpid Connection so retrieve the QMF2 Console Object. + Console console = connection.getConsole(); + + if (path.startsWith("object/")) + { + path = path.substring(7); + + // The ObjectId has been passed in the URI create an ObjectId and retrieve the Agent Name. + ObjectId oid = new ObjectId(path); + String agentName = oid.getAgentName(); + + // The qpidd ManagementAgent doesn't populate AgentName, if it's empty assume it's the broker. + agentName = agentName.equals("") ? "broker" : agentName; + + Agent agent = console.getAgent(agentName); // Find the Agent we got the QmfData from. + if (agent == null) + { + _log.info("QpidServer.doPost path: {} Agent: {} not found.", tx.getRequestURI(), agentName); + tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found."); + } + else + { // If we get this far we can create the Object and invoke the method. + // We can create a QmfConsoleData with nothing but an ObjectId and the agent. + QmfConsoleData object = new QmfConsoleData(Collections.EMPTY_MAP, agent); + object.setObjectId(oid); + + String request = tx.getRequestString(); + _log.info("QpidServer.doPost path: {} body: {}", tx.getRequestURI(), request); + + //System.out.println(request); + String method = ""; + try + { + Map reqMap = JSON.toMap(request); + + method = (String)reqMap.get("_method_name"); + Object arguments = reqMap.get("_arguments"); + + Map args = (arguments instanceof Map) ? (Map)arguments : null; + //System.out.println("method: " + method + ", args: " + args); + + // Parse the args if present into a QmfData (needed by invokeMethod). + QmfData inArgs = (args == null) ? new QmfData() : new QmfData(args); + + // Invoke the specified method on the QmfConsoleData we've created. + MethodResult results = null; + + _log.info("invokeMethod: {}", request); + results = object.invokeMethod(method, inArgs); + tx.sendResponse(HTTP_OK, "application/json", JSON.fromObject(results)); + } + catch (QmfException qmfe) + { + _log.info("QpidServer.doPost() caught Exception {}", qmfe.getMessage()); + tx.sendResponse(HTTP_INTERNAL_ERROR, "text/plain", "invokeMethod(" + + method + ") -> " + qmfe.getMessage()); + } + catch (Exception e) + { + _log.info("QpidServer.doPost() caught Exception {}", e.getMessage()); + tx.sendResponse(HTTP_INTERNAL_ERROR, "text/plain", "500 " + e.getMessage()); + } + } + } + else + { + tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found."); + } + } + } + else + { + tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found."); + } + } + else + { + tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found."); + } + } + + /** + *
+     * PUT: <host>:<port>/qpid/connection/<name>
+     *      HTTP body: {"url":<url>,"connectionOptions":<connectionOptions>[,"disableEvents":true;]}
+     *      <url>: A string containing an AMQP connection URL as used in the qpid::messaging API.
+     *      <connectionOptions>: A JSON string containing connectionOptions in the form specified in the 
+     *      qpid::messaging API.
+     * 
+ * Called by the Web Server to allow a Server to handle a PUT request. + * This method creates a Qpid Connection Object with the name <name> using the specified url and options. + *

+ * The optional disableEvents property is used to start up a QMF Connection which can only + * do synchronous calls such as getObjects() and can't receive Agent updates or QMF2 Events. + *

+ * N.B. It is possible for the Qpid broker to be unavailable when this method is called so it is actually a + * ConnectionProxy that is created. This method waits for up to 1000ms for the underlying Qpid Connection to + * become available and then returns. Clients should be aware that this method successfully returning only + * implies that the ConnectionProxy is in place and the underlying Qpid Connection may not be available. + * If the broker is down the ConnectionProxy will periodically attempt to reconnect, but whilst it is down + * the REST API will return a 500 Broker Disconnected response to any PUT or POST call. + * + * @param tx the HttpTransaction containing the request from the client and used to send the response. + */ + @SuppressWarnings("unchecked") + public void doPut(final HttpTransaction tx) throws IOException + { + String path = tx.getRequestURI(); + //tx.logRequest(); + + if (path.startsWith("/qpid/connection/")) + { + path = path.substring(17); + String user = tx.getPrincipal(); + String request = tx.getRequestString(); + _log.info("QpidServer.doPut path: {} body: {}", tx.getRequestURI(), request); + + String name = user + "." + path; + + try + { + // The PUT request is a JSON string containing a url String property and a connectionOptions + // property which is itself a JSON String. + Map reqMap = JSON.toMap(request); + + String url = reqMap.get("url"); + url = url.equals("") ? _defaultBroker : url; + + // Extract the connectionOptions property and check its type is a MAP + Object options = reqMap.get("connectionOptions"); + Map optionsMap = (options instanceof Map) ? (Map)options : null; + + // Turn the connectionOptions Map back into a JSON String. Note that we can't just get + // connectionOptions as a String from reqMap as it is sent as JSON and the JSON.toMap() + // call on the POST request will fully parse it. + String connectionOptions = JSON.fromObject(optionsMap); + + boolean disableEvents = false; + String disableEventsString = reqMap.get("disableEvents"); + if (disableEventsString != null) + { + disableEvents = disableEventsString.equalsIgnoreCase("true"); + } + + ConnectionProxy proxy = _connections.create(name, url, connectionOptions, disableEvents); + + // Wait a maximum of 1000ms for the underlying Qpid Connection to become available then return. + proxy.waitForConnection(1000); + tx.sendResponse(HTTP_CREATED, "text/plain", "201 Created."); + } + catch (Exception e) + { + _log.info("QpidServer.doPut() caught Exception {}", e.getMessage()); + tx.sendResponse(HTTP_INTERNAL_ERROR, "text/plain", "500 " + e.getMessage()); + } + } + else + { + tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found."); + } + } + + /** + * Called by the Web Server to allow a Server to handle a DELETE request. + * + * DELETE: <host>:<port>/qpid/connection/<name> + * + * This method deletes the Qpid Connection Object with the name <name>. + * + * @param tx the HttpTransaction containing the request from the client and used to send the response. + */ + public void doDelete(final HttpTransaction tx) throws IOException + { + String path = tx.getRequestURI(); + //tx.logRequest(); + + if (path.startsWith("/qpid/connection/")) + { + path = path.substring(17); + String user = tx.getPrincipal(); + String name = user + "." + path; + + //System.out.println("Deleting " + name); + _log.info("QpidServer.doDelete path: {}", tx.getRequestURI()); + + _connections.delete(name); + tx.sendResponse(HTTP_OK, "text/plain", "200 Deleted."); + } + else + { + tx.sendResponse(HTTP_NOT_FOUND, "text/plain", "404 Not Found."); + } + } +} + + diff --git a/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/Server.java b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/Server.java new file mode 100644 index 0000000000..e7f5d61219 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/Server.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.restapi; + +import java.io.IOException; + +/** + * A Server represents a handler that runs within a Web server, which is invoked to process HTTP exchanges. + * Servers receive and respond to requests from Web clients that are encapsulated into HttpTransactions. + *

+ * The Server and HttpTransaction interfaces are intended to provide abstractions to enable the "business logic" to + * be isolated from the actual Web Server implementation choice, so for example a concrete HttpTransaction implementation + * could be created by wrapping a com.sun.net.httpserver.HttpExchange, but equally another implementation could wrap + * javax.servlet.http.HttpServletRequest and javax.servlet.http.HttpServletResponse so for example an HttpServlet + * could delegate to a Server instance passing the HttpTransaction it constructed from the HttpServletRequest and + * HttpServletResponse. + * + * @author Fraser Adams + */ +public interface Server +{ + /** + * Called by the Web Server to allow a Server to handle a GET request. + *

+ * The GET method should be safe, that is, without any side effects for which users are held responsible. For + * example, most form queries have no side effects. If a client request is intended to change stored data, the + * request should use some other HTTP method. + *

+ * The GET method should also be idempotent, meaning that it can be safely repeated. Sometimes making a method safe + * also makes it idempotent. For example, repeating queries is both safe and idempotent, but buying a product online + * or modifying data is neither safe nor idempotent. + * + * @param tx the HttpTransaction containing the request from the client and used to send the response. + */ + public void doGet(HttpTransaction tx) throws IOException; + + /** + * Called by the Web Server to allow a Server to handle a POST request. + *

+ * The HTTP POST method allows the client to send data of unlimited length to the Web server a single time and is + * useful when posting information such as credit card numbers. + *

+ * This method does not need to be either safe or idempotent. Operations requested through POST can have side + * effects for which the user can be held accountable, for example, updating stored data or buying items online. + * + * @param tx the HttpTransaction containing the request from the client and used to send the response. + */ + public void doPost(HttpTransaction tx) throws IOException; + + /** + * Called by the Web Server to allow a Server to handle a PUT request. + *

+ * The PUT operation allows a client to place a file on the server and is similar to sending a file by FTP. + *

+ * This method does not need to be either safe or idempotent. Operations that doPut performs can have side effects + * for which the user can be held accountable. When using this method, it may be useful to save a copy of the + * affected URL in temporary storage. + * + * @param tx the HttpTransaction containing the request from the client and used to send the response. + */ + public void doPut(HttpTransaction tx) throws IOException; + + /** + * Called by the Web Server to allow a Server to handle a DELETE request. + *

+ * The DELETE operation allows a client to remove a document or Web page from the server. + *

+ * This method does not need to be either safe or idempotent. Operations requested through DELETE can have side + * effects for which users can be held accountable. When using this method, it may be useful to save a copy of the + * affected URL in temporary storage. + * + * @param tx the HttpTransaction containing the request from the client and used to send the response. + */ + public void doDelete(HttpTransaction tx) throws IOException; +} + + diff --git a/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/httpserver/Authenticator.java b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/httpserver/Authenticator.java new file mode 100644 index 0000000000..1083cb48df --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/httpserver/Authenticator.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.restapi.httpserver; + +// Simple Logging Facade 4 Java +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; +import java.util.Timer; +import java.util.TimerTask; + +import com.sun.net.httpserver.BasicAuthenticator; + +/** + * This class implements a simple com.sun.net.httpserver.BasicAuthenticator. Clearly it's not very secure being a + * BasicAuthenticator that takes a plain (well Base64 encoded) username/password. + * TODO Clearly something more secure needs to be implemented..... + * + * This class creates a Timer used to schedule regular checks on the account.properties file and if it has changed + * it reloads the cache used for looking up the credentials. The poll period is every 10 seconds which should be OK + * for checking account updates. The class only updates the cache if the account.properties file has actually changed. + * Polling isn't ideal, but for this application it's probably no big deal. With Java 7 there is a Watch Service API + * https://blogs.oracle.com/thejavatutorials/entry/watching_a_directory_for_changes that allows asynchronous + * notification of changes, but it's an unnecessary dependency on Java 7 for this application. + * + * @author Fraser Adams + */ +public class Authenticator extends BasicAuthenticator +{ + private static final int CHECK_PERIOD = 10000; // Check every 10 seconds if the account properties have been changed. + private static final String ACCOUNT_FILENAME = "account.properties"; + private static final Logger _log = LoggerFactory.getLogger(Authenticator.class); + + private File _file; + private Properties _accountCache; + private long _accountFileLastModified = 0; // Used to check for updates to the account properties. + + /** + * Create a Timer used to schedule regular checks on the account.properties file. + */ + private Timer _timer = new Timer(true); + + /** + * This private inner class is a fairly trivial TimerTask whose run() method simply calls checkAccountFile() + * in the main Authenticator class to check for account changes. + */ + private final class CacheUpdater extends TimerTask + { + public void run() + { + checkAccountFile(); + } + } + + /** + * Construct the Authenticator. This fires up the CacheUpdater TimerTask to periodically check for changes. + * @param realm the authentication realm to use. + * @param path the path of the directory holding the account properties file. + */ + public Authenticator(final String realm, final String path) + { + super(realm); + + String accountPathname = path + "/" + ACCOUNT_FILENAME; + _file = new File(accountPathname); + + if (_file.exists()) + { + CacheUpdater updater = new CacheUpdater(); + _timer.schedule(updater, 0, CHECK_PERIOD); + } + else + { + System.out.println("Authentication file " + accountPathname + " is missing.\nCannot continue - exiting."); + System.exit(1); + } + } + + /** + * Checks if the account properties file has been updated, if it has it calls loadCache() to reload the cache. + */ + public void checkAccountFile() + { + long mtime = _file.lastModified(); + if (mtime != _accountFileLastModified) + { + _accountFileLastModified = mtime; + loadCache(); + } + } + + /** + * Load the account properties file into the account cache. + */ + private void loadCache() + { + try + { + Properties properties = new Properties(); + properties.load(new FileInputStream(_file)); + + // Set the cache to be the newly loaded one. + _accountCache = properties; + } + catch (IOException ex) + { + _log.info("loadCache failed with {}.", ex.getMessage()); + } + } + + @Override + public boolean checkCredentials(final String username, final String password) + { + //System.out.println("username = " + username); + //System.out.println("password = " + password); + + // The original version of this forgot to check check for a null username Property. Thanks to + // Bruno Matos for picking that up and supplying the fix below. + if (_accountCache.getProperty(username) != null && + _accountCache.getProperty(username).equals(password)) + { + return true; + } + else + { + return false; + } + } +} diff --git a/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/httpserver/Delegator.java b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/httpserver/Delegator.java new file mode 100644 index 0000000000..9dfd1fea0c --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/httpserver/Delegator.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.restapi.httpserver; + +import java.io.IOException; +import static java.net.HttpURLConnection.HTTP_BAD_METHOD; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +import org.apache.qpid.restapi.HttpTransaction; +import org.apache.qpid.restapi.Server; + +/** + * Delegator is an implementation of com.sun.net.httpserver.HttpHandler that is used to delegate to Server objects + * and thus provide an abstraction between the Web Server implementation (e.g. HttpServer or Servlets) and the + * implementation neutral Server and HttpTransaction interfaces. + *

+ * In order to replace the HttpServer implementation with a Servlet based implementation all that should be necessary + * is a concrete implementation of HttpTransaction that wraps HttpServletRequest and HttpServletResponse and subclasses + * of HttpServlet that delegate to the appropriate Server instances from the appropriate context in a similar way + * to the Delegator class here. + * + * @author Fraser Adams + */ +public class Delegator implements HttpHandler +{ + private final Server _server; + + /** + * Construct a Delegator instance that delegates to the specified Server instance. + * @param server the Server instance that this Delegator delegates to. + */ + public Delegator(Server server) + { + _server = server; + } + + /** + * Implements the HttpHandler handle interface and delegates to the Server instance. + * @param exchange the HttpExchange exchange object passed by the HttpServer. This will be used to construct + * an HttpTransaction instance that will be passed to the Server. + */ + public void handle(final HttpExchange exchange) throws IOException + { + HttpTransaction tx = new HttpExchangeTransaction(exchange); + String method = tx.getMethod(); + if (method.equals("GET")) + { + _server.doGet(tx); + } + else if (method.equals("POST")) + { + _server.doPost(tx); + } + else if (method.equals("PUT")) + { + _server.doPut(tx); + } + else if (method.equals("DELETE")) + { + _server.doDelete(tx); + } + else + { + tx.sendResponse(HTTP_BAD_METHOD, "text/plain", "405 Bad Method."); + } + } +} + + diff --git a/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/httpserver/HttpExchangeTransaction.java b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/httpserver/HttpExchangeTransaction.java new file mode 100644 index 0000000000..78932ec399 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/httpserver/HttpExchangeTransaction.java @@ -0,0 +1,311 @@ +/* + * + * 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.restapi.httpserver; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; +import java.util.Map; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpPrincipal; + +import org.apache.qpid.restapi.HttpTransaction; + +/** + * This class provides an implementation of the HttpTransaction interface that wraps com.sun.net.httpserver.HttpExchange + * in order to provide an implementation neutral facade to the Server classes. + * + * @author Fraser Adams + */ +public final class HttpExchangeTransaction implements HttpTransaction +{ + final HttpExchange _exchange; + + /** + * Construct an HttpExchangeTransaction from an HttpExchange object. + */ + public HttpExchangeTransaction(final HttpExchange exchange) + { + _exchange = exchange; + } + + /** + * Log the HTTP request information (primarily for debugging purposes) + */ + public void logRequest() + { + System.out.println(_exchange.getRequestMethod() + " " + _exchange.getRequestURI()); + for (Map.Entry> header : _exchange.getRequestHeaders().entrySet()) + { + System.out.println(header); + } + System.out.println("From: " + getRemoteHost() + ":" + getRemotePort()); + } + + /** + * Return the content passed in the request from the client as a Stream. + * @return the content passed in the request from the client as a Stream. + */ + public InputStream getRequestStream() throws IOException + { + return _exchange.getRequestBody(); + } + + /** + * Return the content passed in the request from the client as a String. + * @return the content passed in the request from the client as a String. + */ + public String getRequestString() throws IOException + { + return new String(getRequest()); + } + + + /** + * Return the content passed in the request from the client as a byte[]. + * @return the content passed in the request from the client as a byte[]. + */ + public byte[] getRequest() throws IOException + { + InputStream is = _exchange.getRequestBody(); + + // Convert InputStream to byte[]. + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len; + while ((len = is.read(buffer, 0, 1024)) != -1) + { + bos.write(buffer, 0, len); + } + return bos.toByteArray(); + } + + /** + * Send the content passed as a String as an HTTP response back to the client. + * @param status the HTTP status code e.g. 200 for OK. + * @param mimeType the mimeType of the response content e.g. text/plain, text/xml, image/jpeg etc. + * @param content the content of the response passed as a String. + */ + public void sendResponse(final int status, final String mimeType, final String content) throws IOException + { + if (content == null) + { // If response length has the value -1 then no response body is being sent. + _exchange.getResponseHeaders().set("Content-Type", mimeType); + _exchange.sendResponseHeaders(status, -1); + _exchange.close(); + } + else + { + sendResponse(status, mimeType, content.getBytes()); + } + } + + /** + * Send the content passed as a byte[] as an HTTP response back to the client. + * @param status the HTTP status code e.g. 200 for OK. + * @param mimeType the mimeType of the response content e.g. text/plain, text/xml, image/jpeg etc. + * @param content the content of the response passed as a byte[]. + */ + public void sendResponse(final int status, final String mimeType, final byte[] content) throws IOException + { + _exchange.getResponseHeaders().set("Content-Type", mimeType); + if (content == null) + { // If response length has the value -1 then no response body is being sent. + _exchange.sendResponseHeaders(status, -1); + _exchange.close(); + } + else + { + _exchange.sendResponseHeaders(status, content.length); + OutputStream os = _exchange.getResponseBody(); + os.write(content); + os.flush(); + os.close(); + _exchange.close(); + } + } + + /** + * Send the content passed as an InputStream as an HTTP response back to the client. + * @param status the HTTP status code e.g. 200 for OK. + * @param mimeType the mimeType of the response content e.g. text/plain, text/xml, image/jpeg etc. + * @param is the content of the response passed as an InputStream. + */ + public void sendResponse(final int status, final String mimeType, final InputStream is) throws IOException + { + _exchange.getResponseHeaders().set("Content-Type", mimeType); + if (is == null) + { // If response length has the value -1 then no response body is being sent. + _exchange.sendResponseHeaders(status, -1); + _exchange.close(); + } + else + { + _exchange.sendResponseHeaders(status, 0); // For a stream we set to zero to force chunked transfer encoding. + OutputStream os = _exchange.getResponseBody(); + + byte[] buffer = new byte[8192]; + while (true) + { + int read = is.read(buffer, 0, buffer.length); + if (read == -1) // Loop until EOF is reached + { + break; + } + os.write(buffer, 0, read); + } + + os.flush(); + os.close(); + _exchange.close(); + } + } + + /** + * Returns the Internet Protocol (IP) address of the client or last proxy that sent the request. + * @return the Internet Protocol (IP) address of the client or last proxy that sent the request. + */ + public String getRemoteAddr() + { + return _exchange.getRemoteAddress().getAddress().getHostAddress(); + } + + /** + * Returns the fully qualified name of the client or the last proxy that sent the request. + * @return the fully qualified name of the client or the last proxy that sent the request. + */ + public String getRemoteHost() + { + return _exchange.getRemoteAddress().getHostName(); + } + + /** + * Returns the Internet Protocol (IP) source port of the client or last proxy that sent the request. + * @return the Internet Protocol (IP) source port of the client or last proxy that sent the request. + */ + public int getRemotePort() + { + return _exchange.getRemoteAddress().getPort(); + } + + /** + * Returns a String containing the name of the current authenticated user. If the user has not been authenticated, + * the method returns null. + * @return a String containing the name of the user making this request; null if the user has not been authenticated. + */ + public String getPrincipal() + { + HttpPrincipal principal = _exchange.getPrincipal(); + return principal == null ? null : principal.getUsername(); + } + + /** + * Returns the name of the HTTP method with which this request was made, for example, GET, POST, or PUT. + * @return a String specifying the name of the method with which this request was made. + */ + public String getMethod() + { + return _exchange.getRequestMethod(); + } + + /** + * Returns the part of this request's URL from the protocol name up to the query string in the first line of + * the HTTP request. + * @return a String containing the part of the URL from the protocol name up to the query string. + */ + public String getRequestURI() + { + return _exchange.getRequestURI().getPath(); + } + + /** + * Sets a response header with the given name and value. If the header had already been set, the new value + * overwrites the previous one. + * @param name a String specifying the header name. + * @param value a String specifying the header value. If it contains octet string, it should be encoded according + * to RFC 2047. + */ + public void setHeader(final String name, final String value) + { + _exchange.getResponseHeaders().set(name, value); + } + + /** + * Returns the value of the specified request header as a String. If the request did not include a header of the + * specified name, this method returns null. If there are multiple headers with the same name, this method returns + * the first head in the request. The header name is case insensitive. You can use this method with any request + * header. + * @param name a String specifying the header name. + * @return a String containing the value of the requested header, or null if the request does not have a header of + * that name. + */ + public String getHeader(final String name) + { + return _exchange.getRequestHeaders().getFirst(name); + } + + /** + * Returns the String value of the specified cookie. + * @param name a String specifying the cookie name. + */ + public String getCookie(final String name) + { + Headers headers = _exchange.getRequestHeaders(); + if (!headers.containsKey("Cookie")) + { + return null; + } + + List values = headers.get("cookie"); + for (String value : values) + { + String[] cookies = value.split(";"); + for (String cookie : cookies) + { + String[] cdata = cookie.split("="); + if (cdata[0].trim().equals(name)) + { + //return URLDecode(cdata[1]); + return cdata[1]; + } + } + } + return null; + } + + /** + * Adds the specified cookie to the response. This method can be called multiple times to set more than one cookie. + * @param name a String specifying the cookie name. + * @param value a String specifying the cookie value. + */ + public void addCookie(final String name, final String value) + { + //String data = name + "=" + URLEncode(value) + "; path=/"; + String data = name + "=" + value + "; path=/"; + _exchange.getResponseHeaders().add("Set-Cookie", data); + } +} + + diff --git a/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/servlet/TODO b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/servlet/TODO new file mode 100644 index 0000000000..9da5ee871a --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-rest/src/main/java/org/apache/qpid/restapi/servlet/TODO @@ -0,0 +1,25 @@ +/* + * + * 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. + * + */ + +The org.apache.qpid.restapi.HttpTransaction and org.apache.qpid.restapi.Server interfaces are intended to +provide abstractions to the underlying HTTP Server technology and thus should make it fairly easy to do +a Servlet based implementation without having to rewrite the core REST API code. A Servlet implementation +is still on the "TODO" list though. diff --git a/qpid/tools/src/java/qpid-qmf2-test/README.txt b/qpid/tools/src/java/qpid-qmf2-test/README.txt new file mode 100644 index 0000000000..bdc9d532b6 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-test/README.txt @@ -0,0 +1,55 @@ +/* + * + * 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. + * + */ + +This module contains various manual test cases.The moudle pom has a helper profile +with helper exec plugin config to allow executing a particular test class from +the org.apache.qpid.qmf2.test package. + +The test classes can be found at: +src/main/java/org/apache/qpid/qmf2/test/ + +To execute a particular test class, use: + +mvn test -DtestCase= [-Dexec.args=] + + +The above command can be run either from the parent directory, or from within +the modules own directory, though in the latter case the other modules should +be installed into your local repo (mvn clean install) first to ensure the latest +code is being tested rather than a remote dependency. + +Currently available classes are: + +AgentExternalTest +AgentSubscriptionTestConsole +AgentTestConsole +AgentTest +BigPayloadAgentTestConsole +BigPayloadAgentTest +BrokerSubscriptionTestConsole +InvokeMethodTest +PartialGetObjectsTest +SchemaTest +Test1 +Test2 +Test3 +Test4 +URLTest diff --git a/qpid/tools/src/java/qpid-qmf2-test/log4j.xml b/qpid/tools/src/java/qpid-qmf2-test/log4j.xml new file mode 100644 index 0000000000..d5169932bc --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-test/log4j.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/qpid/tools/src/java/qpid-qmf2-test/pom.xml b/qpid/tools/src/java/qpid-qmf2-test/pom.xml new file mode 100644 index 0000000000..4c8012cdbf --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-test/pom.xml @@ -0,0 +1,149 @@ + + + + 4.0.0 + + + org.apache.qpid + qpid-qmf2-parent + 0.30-SNAPSHOT + + + qpid-qmf2-test + Qpid QMF2 Test + QMF2 Test + + + SpecifyTestOnCommandLine + + + + + org.apache.qpid + qpid-qmf2 + ${project.version} + + + + org.apache.qpid + qpid-qmf2-rest + ${project.version} + + + + org.apache.qpid + qpid-qmf2-tools + ${project.version} + + + + org.slf4j + slf4j-api + ${slf4j-version} + + + + org.slf4j + slf4j-log4j12 + ${slf4j-version} + runtime + + + + + log4j + log4j + ${log4j-version} + runtime + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + + true + + + + + + + + + test + + + testCase + + + + + + org.codehaus.mojo + exec-maven-plugin + + + + test + + java + + + + + org.apache.qpid.qmf2.test.${testCase} + + + log4j.configuration + file://${basedir}/log4j.xml + + + + + + + + + + diff --git a/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/AgentExternalTest.java b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/AgentExternalTest.java new file mode 100644 index 0000000000..3094ba406f --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/AgentExternalTest.java @@ -0,0 +1,558 @@ +/* + * + * 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.test; + +import javax.jms.Connection; + +// Misc Imports +import java.io.*; +import java.util.ArrayList; +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.agent.AgentExternal; +import org.apache.qpid.qmf2.agent.MethodCallParams; +import org.apache.qpid.qmf2.agent.MethodCallWorkItem; +import org.apache.qpid.qmf2.agent.ResubscribeParams; +import org.apache.qpid.qmf2.agent.ResubscribeRequestWorkItem; +import org.apache.qpid.qmf2.agent.SubscribeRequestWorkItem; +import org.apache.qpid.qmf2.agent.SubscribableAgent; +import org.apache.qpid.qmf2.agent.Subscription; +import org.apache.qpid.qmf2.agent.SubscriptionParams; +import org.apache.qpid.qmf2.agent.UnsubscribeRequestWorkItem; +import org.apache.qpid.qmf2.agent.QueryWorkItem; +import org.apache.qpid.qmf2.agent.QmfAgentData; +import org.apache.qpid.qmf2.common.Handle; +import org.apache.qpid.qmf2.common.ObjectId; +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.QmfType; +import org.apache.qpid.qmf2.common.SchemaEventClass; +import org.apache.qpid.qmf2.common.SchemaMethod; +import org.apache.qpid.qmf2.common.SchemaObjectClass; +import org.apache.qpid.qmf2.common.SchemaProperty; +import org.apache.qpid.qmf2.common.WorkItem; +import org.apache.qpid.qmf2.util.ConnectionHelper; +import static org.apache.qpid.qmf2.common.WorkItem.WorkItemType.*; + +/** + * Class used to test the AgentExternal. + * This class provides a demo of all of the features available on the AgentExternal model including Subscriptions. + * It provides essentially the same behaviour as the AgentTest class though requires a lot more code. + * + * The AgentExternal class and this demo are largely provided for completeness (although they do behave correctly) + * as the author isn't convinced that there's a good reason for using AgentExternal rather than Agent. + * + * @author Fraser Adams + */ +public final class AgentExternalTest implements QmfEventListener, SubscribableAgent +{ + /** + * This TimerTask causes the Agent to Reap any objects marked as deleted when it gets scheduled + */ + private final class Reaper extends TimerTask + { + public void run() + { + // 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 i = _objectIndex.values().iterator(); + while (i.hasNext()) + { + QmfAgentData object = i.next(); + if (object.isDeleted()) + { +System.out.println("****** Removing deleted Object *******"); + i.remove(); + } + } + } + } + + private AgentExternal _agent; + private QmfAgentData _control; + private SchemaObjectClass _exceptionSchema; + private SchemaObjectClass _controlSchema; + private SchemaObjectClass _childSchema; + private SchemaEventClass _eventSchema; + + /** + * objectIndex is the global index of QmfAgentData objects registered with this Agent + */ + private Map _objectIndex = new ConcurrentHashMap(); + + /** + * This Map is used to look up Subscriptions by SubscriptionId + */ + private Map _subscriptions = new ConcurrentHashMap(); + + private Timer _timer; + + public AgentExternalTest(String url) + { + try + { + System.out.println("** Starting AgentExternalTest a test of basic AgentExternal class functions **"); + + Connection connection = ConnectionHelper.createConnection(url, "{reconnect: true}"); + _agent = new AgentExternal(this); + _agent.setVendor("profitron.com"); + _agent.setProduct("gizmo"); + _agent.setValue("attr1", 2000); + + System.out.println("Agent name: " + _agent.getName()); + + // Schedule a Reap every 10 seconds sending the first one immediately + _timer = new Timer(true); + _timer.schedule(new Reaper(), 0, 10000); + + setupSchema(); + populateData(); + + _agent.setConnection(connection); + + for (int i = 0; i < 100; i++) + { + _control.setValue("offset", i); + //control.update(); // Send data indication to the Subscriber on the next Subscription interval + _control.publish(); // Send data indication to the Subscriber immediately + try + { + Thread.sleep(1000); + } + catch (InterruptedException ie) + { + } + } + + _control.destroy(); + + + // A getObjects call seems necessary to enable automatic reconnection when broker restarts + // I've got no idea why this is the case though!!! + //List connections = console.getObjects("broker"); + } + catch (QmfException qmfe) + { + System.err.println("QmfException " + qmfe.getMessage() + " caught: AgentExternalTest failed"); + } + } + + public void onEvent(WorkItem wi) + { + System.out.println("WorkItem type: " + wi.getType()); + + if (wi.getType() == METHOD_CALL) + { + _control.incValue("methodCount", 1); + + MethodCallWorkItem item = (MethodCallWorkItem)wi; + MethodCallParams methodCallParams = item.getMethodCallParams(); + String methodName = methodCallParams.getName(); + ObjectId objectId = methodCallParams.getObjectId(); + String userId = methodCallParams.getUserId(); + QmfData inArgs = methodCallParams.getArgs(); + ObjectId controlAddress = _control.getObjectId(); + + System.out.println("Method Call User ID = " + userId); + + try + { + if (objectId == null) + { + // Method invoked directly on Agent + if (methodName.equals("toString")) + { + QmfData outArgs = new QmfData(); + outArgs.setValue("string", _agent.toString()); + _agent.methodResponse(methodName, item.getHandle(), outArgs, null); + } + } + else if (objectId.equals(controlAddress)) + { + if (methodName.equals("stop")) + { + System.out.println("Invoked stop method"); + String message = inArgs.getStringValue("message"); + System.out.println("Stopping: message = " + message); + _agent.methodResponse(methodName, item.getHandle(), null, null); + _agent.destroy(); + System.exit(1); + } + else if (methodName.equals("echo")) + { + System.out.println("Invoked echo method"); + _agent.methodResponse(methodName, item.getHandle(), inArgs, null); + } + else if (methodName.equals("event")) + { + System.out.println("Invoked event method"); + QmfEvent event = new QmfEvent(_eventSchema); + event.setSeverity((int)inArgs.getLongValue("severity")); + event.setValue("text", inArgs.getStringValue("text")); + _agent.raiseEvent(event); + _agent.methodResponse(methodName, item.getHandle(), null, null); + } + else if (methodName.equals("fail")) + { + System.out.println("Invoked fail method"); + QmfData error = new QmfData(); + if (inArgs.getBooleanValue("useString")) + { + error.setValue("error_text", inArgs.getStringValue("stringVal")); + } + else + { + error.setValue("whatHappened", "It Failed"); + error.setValue("howBad", 75); + error.setValue("details", inArgs.getValue("details")); + } + _agent.methodResponse(methodName, item.getHandle(), null, error); + } + else if (methodName.equals("create_child")) + { + System.out.println("Invoked create_child method"); + String childName = inArgs.getStringValue("name"); + System.out.println("childName = " + childName); + QmfAgentData child = new QmfAgentData(_childSchema); + child.setValue("name", childName); + addObject(child); + QmfData outArgs = new QmfData(); + outArgs.setRefValue("childAddr", child.getObjectId(), "reference"); // Set suptype just to test + _agent.methodResponse(methodName, item.getHandle(), outArgs, null); + } + } + } + catch (QmfException qmfe) + { + System.err.println("QmfException " + qmfe.getMessage() + " caught: AgentExternalTest failed"); + QmfData error = new QmfData(); + error.setValue("error_text", qmfe.getMessage()); + _agent.methodResponse(methodName, item.getHandle(), null, error); + } + } + + if (wi.getType() == QUERY) + { + QueryWorkItem item = (QueryWorkItem)wi; + QmfQuery query = item.getQmfQuery(); + + System.out.println("Query User ID = " + item.getUserId()); + + 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()) + { + _agent.queryResponse(item.getHandle(), object); + } + _agent.queryComplete(item.getHandle(), 0); + } + 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........ + for (QmfAgentData object : _objectIndex.values()) + { + if (!object.isDeleted() && query.evaluate(object)) + { + _agent.queryResponse(item.getHandle(), object); + } + } + _agent.queryComplete(item.getHandle(), 0); + } + } + + if (wi.getType() == SUBSCRIBE_REQUEST) + { + SubscribeRequestWorkItem item = (SubscribeRequestWorkItem)wi; + SubscriptionParams params = item.getSubscriptionParams(); + Handle handle = item.getHandle(); + + System.out.println("Subscribe Request User ID = " + params.getUserId()); + + try + { + Subscription subscription = new Subscription(this, params); + _subscriptions.put(subscription.getSubscriptionId(), subscription); + _timer.schedule(subscription, 0, params.getPublishInterval()); + + if (subscription == null) + { +System.out.println("Requested Subscription has already expired or been cancelled"); + QmfData error = new QmfData(); + error.setValue("error_text", "Requested Subscription has already expired or been cancelled"); + _agent.subscriptionResponse(handle, subscription.getConsoleHandle(), null, 0, 0, error); + } + else + { + _agent.subscriptionResponse(handle, subscription.getConsoleHandle(), subscription.getSubscriptionId(), + subscription.getDuration(), subscription.getInterval(), null); + } + } + catch (QmfException qmfe) + { + _agent.raiseException(handle, "Subscribe Request failed, invalid Query: " + qmfe.getMessage()); + } + } + + if (wi.getType() == RESUBSCRIBE_REQUEST) + { + ResubscribeRequestWorkItem item = (ResubscribeRequestWorkItem)wi; + ResubscribeParams params = item.getResubscribeParams(); + Handle handle = item.getHandle(); + + System.out.println("Resubscribe Request User ID = " + params.getUserId()); + + String subscriptionId = params.getSubscriptionId(); + Subscription subscription = _subscriptions.get(subscriptionId); + if (subscription != null) + { + subscription.refresh(params); + _agent.subscriptionResponse(handle, subscription.getConsoleHandle(), subscription.getSubscriptionId(), + subscription.getDuration(), subscription.getInterval(), null); + } + else + { +System.out.println("Requested Subscription has already expired or been cancelled"); + QmfData error = new QmfData(); + error.setValue("error_text", "Requested Subscription has already expired or been cancelled"); + _agent.subscriptionResponse(handle, subscription.getConsoleHandle(), null, 0, 0, error); + } + } + + if (wi.getType() == UNSUBSCRIBE_REQUEST) + { + UnsubscribeRequestWorkItem item = (UnsubscribeRequestWorkItem)wi; + String subscriptionId = item.getSubscriptionId(); +System.out.println("Received cancellation request for " + subscriptionId); + Subscription subscription = _subscriptions.get(subscriptionId); + if (subscription != null) + { + subscription.cancel(); + } + } + + } + + public void setupSchema() throws QmfException + { + System.out.println("*** AgentExternalTest initialising the various Schema classes ***"); + + // Create and register schema for this agent. + String packageName = "com.profitron.gizmo"; + + // Declare a schema for a structured exception that can be used in failed method invocations. + _exceptionSchema = new SchemaObjectClass(packageName, "exception"); + _exceptionSchema.addProperty(new SchemaProperty("whatHappened", QmfType.TYPE_STRING)); + _exceptionSchema.addProperty(new SchemaProperty("howBad", QmfType.TYPE_INT)); + _exceptionSchema.addProperty(new SchemaProperty("details", QmfType.TYPE_MAP)); + + // Declare a control object to test methods against. + _controlSchema = new SchemaObjectClass(packageName, "control"); + _controlSchema.addProperty(new SchemaProperty("state", QmfType.TYPE_STRING)); + _controlSchema.addProperty(new SchemaProperty("methodCount", QmfType.TYPE_INT)); + _controlSchema.addProperty(new SchemaProperty("offset", QmfType.TYPE_INT)); + _controlSchema.setIdNames("state"); + + SchemaMethod stopMethod = new SchemaMethod("stop", "Stop Agent"); + stopMethod.addArgument(new SchemaProperty("message", QmfType.TYPE_STRING, "{dir:IN}")); + _controlSchema.addMethod(stopMethod); + + SchemaMethod echoMethod = new SchemaMethod("echo", "Echo Arguments"); + echoMethod.addArgument(new SchemaProperty("message", QmfType.TYPE_STRING, "{dir:INOUT}")); + _controlSchema.addMethod(echoMethod); + + SchemaMethod eventMethod = new SchemaMethod("event", "Raise an Event"); + eventMethod.addArgument(new SchemaProperty("text", QmfType.TYPE_STRING, "{dir:IN}")); + eventMethod.addArgument(new SchemaProperty("severity", QmfType.TYPE_INT, "{dir:IN}")); + _controlSchema.addMethod(eventMethod); + + SchemaMethod failMethod = new SchemaMethod("fail", "Expected to Fail"); + failMethod.addArgument(new SchemaProperty("useString", QmfType.TYPE_BOOL, "{dir:IN}")); + failMethod.addArgument(new SchemaProperty("stringVal", QmfType.TYPE_STRING, "{dir:IN}")); + failMethod.addArgument(new SchemaProperty("details", QmfType.TYPE_MAP, "{dir:IN}")); + _controlSchema.addMethod(failMethod); + + SchemaMethod createMethod = new SchemaMethod("create_child", "Create Child Object"); + createMethod.addArgument(new SchemaProperty("name", QmfType.TYPE_STRING, "{dir:IN}")); + createMethod.addArgument(new SchemaProperty("childAddr", QmfType.TYPE_MAP, "{dir:OUT}")); + _controlSchema.addMethod(createMethod); + + // Declare the child class + _childSchema = new SchemaObjectClass(packageName, "child"); + _childSchema.addProperty(new SchemaProperty("name", QmfType.TYPE_STRING)); + _childSchema.setIdNames("name"); + + // Declare the event class + _eventSchema = new SchemaEventClass(packageName, "event"); + _eventSchema.addProperty(new SchemaProperty("text", QmfType.TYPE_STRING)); + + System.out.println("AgentExternalTest Schema classes initialised OK"); + + _agent.registerObjectClass(_exceptionSchema); + _agent.registerObjectClass(_controlSchema); + _agent.registerObjectClass(_childSchema); + _agent.registerEventClass(_eventSchema); + + System.out.println("AgentExternalTest Schema classes registered OK"); + } + + public void populateData() throws QmfException + { + System.out.println("*** AgentExternalTest creating a control object ***"); + + _control = new QmfAgentData(_controlSchema); + _control.setValue("state", "OPERATIONAL"); + _control.setValue("methodCount", 0); + + addObject(_control); + System.out.println("AgentExternalTest Schema control object added OK"); + } + + public void addObject(QmfAgentData object) throws QmfException + { + ObjectId addr = _agent.allocObjectId(UUID.randomUUID().toString()); + object.setObjectId(addr); + _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(); + } + } + } + + + + // methods implementing SubscriberProxy 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 void sendSubscriptionIndicate(Handle handle, List results) + { + _agent.sendSubscriptionIndicate(handle, 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 evaluateQuery(QmfQuery query) + { + List results = new ArrayList(_objectIndex.size()); + if (query.getTarget() == QmfQueryTarget.OBJECT) + { + 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 SubscriberProxy that the Subscription has been cancelled. + * + * @param subscription the Subscription that has been cancelled and is requesting removal. + */ + public void removeSubscription(Subscription subscription) + { + _subscriptions.remove(subscription.getSubscriptionId()); + } + + + public static void main(String[] args) + { + //System.out.println("Setting log level to FATAL"); + System.setProperty("amqj.logging.level", "FATAL"); + + String url = (args.length == 1) ? args[0] : "localhost"; + AgentExternalTest test1 = new AgentExternalTest(url); + + BufferedReader commandLine = new BufferedReader(new InputStreamReader(System.in)); + try + { // Blocks here until return is pressed + System.out.println("Hit Return to exit"); + String s = commandLine.readLine(); + System.exit(0); + } + catch (IOException e) + { + System.out.println ("ConnectionAudit main(): IOException: " + e.getMessage()); + } + + System.out.println("*** Ending AgentExternalTest ***"); + } +} diff --git a/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/AgentSubscriptionTestConsole.java b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/AgentSubscriptionTestConsole.java new file mode 100644 index 0000000000..3bc84e1ee9 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/AgentSubscriptionTestConsole.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.test; + +import javax.jms.Connection; + +// Misc Imports +import java.io.*; +import java.util.HashMap; +import java.util.List; +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.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.WorkItem; +import org.apache.qpid.qmf2.console.Agent; +import org.apache.qpid.qmf2.console.AgentAddedWorkItem; +import org.apache.qpid.qmf2.console.AgentHeartbeatWorkItem; +import org.apache.qpid.qmf2.console.Console; +import org.apache.qpid.qmf2.console.EventReceivedWorkItem; +import org.apache.qpid.qmf2.console.MethodResult; +import org.apache.qpid.qmf2.console.MethodResponseWorkItem; +import org.apache.qpid.qmf2.console.ObjectUpdateWorkItem; +import org.apache.qpid.qmf2.console.QmfConsoleData; +import org.apache.qpid.qmf2.console.SubscribeIndication; +import org.apache.qpid.qmf2.console.SubscribeParams; +import org.apache.qpid.qmf2.console.SubscribeResponseWorkItem; +import org.apache.qpid.qmf2.console.SubscriptionIndicationWorkItem; +import org.apache.qpid.qmf2.util.ConnectionHelper; +import static org.apache.qpid.qmf2.common.WorkItem.WorkItemType.*; + +/** + * This class is the Console part of AgentTest which together provide a test of subscription behaviour + * + * N.B. AgentTest needs to be running for this test to behave as expected. + * + * @author Fraser Adams + */ +public final class AgentSubscriptionTestConsole implements QmfEventListener +{ + private Console _console; + private Agent _gizmo; + + public AgentSubscriptionTestConsole(String url) + { + try + { + System.out.println("** Starting AgentSubscriptionTestConsole used to test subscription behaviour **"); + + Connection connection = ConnectionHelper.createConnection(url, "{reconnect: true}"); + _console = new Console(this); + _console.addConnection(connection); + + // Wait until the gizmo Agent has been discovered + _gizmo = _console.findAgent("gizmo"); + if (_gizmo == null) + { + System.out.println("gizmo Agent not found, you probably need to run AgentTest1"); + System.exit(1); + } + + System.out.println("Creating Query for objects whose state property has a value that starts with 'OP'"); + + SubscribeParams params; + QmfQuery query = new QmfQuery(QmfQueryTarget.OBJECT, "['re_match', 'state', ['quote', '^OP']]"); + + // Create a subscription, response returned synchronously + params = _console.createSubscription(_gizmo, query, "consoleHandle1", "{publishInterval:5}"); + System.out.println("duration = " + params.getLifetime()); + System.out.println("interval = " + params.getPublishInterval()); + System.out.println("subscriptionId = " + params.getSubscriptionId()); + System.out.println("consoleHandle = " + params.getConsoleHandle()); + + // Sleep a while, getting query result as they become available + try + { + Thread.sleep(20000); + } + catch (InterruptedException ie) + { + } + + // Refresh the subscription getting results asynchronously, just for variety + System.out.println("Calling refreshSubscription on " + params.getSubscriptionId()); + _console.refreshSubscription(params.getSubscriptionId(), "{replyHandle:ignored}"); + + + // Sleep a bit more + try + { + Thread.sleep(350000); + } + catch (InterruptedException ie) + { + } + + } + catch (QmfException qmfe) + { + System.err.println("QmfException " + qmfe.getMessage() + ": AgentSubscriptionTestConsole failed"); + System.exit(1); + } + } + + public void onEvent(WorkItem wi) + { + System.out.println("WorkItem type: " + wi.getType()); + + if (wi.getType() == AGENT_HEARTBEAT) + { + AgentHeartbeatWorkItem item = (AgentHeartbeatWorkItem)wi; + Agent agent = item.getAgent(); + System.out.println(agent.getName()); + } + + if (wi.getType() == EVENT_RECEIVED) + { + EventReceivedWorkItem item = (EventReceivedWorkItem)wi; + Agent agent = item.getAgent(); + QmfEvent event = item.getEvent(); + + String className = event.getSchemaClassId().getClassName(); + System.out.println("Event: " + className); +//event.listValues(); + } + + if (wi.getType() == METHOD_RESPONSE) + { + MethodResponseWorkItem item = (MethodResponseWorkItem)wi; + MethodResult result = item.getMethodResult(); + String correlationId = item.getHandle().getCorrelationId(); + System.out.println("correlationId = " + correlationId); + System.out.println(result.getStringValue("message")); + } + + if (wi.getType() == OBJECT_UPDATE) + { + ObjectUpdateWorkItem item = (ObjectUpdateWorkItem)wi; + QmfConsoleData object = item.getQmfConsoleData(); + ObjectId objectId = object.getObjectId(); + String correlationId = item.getHandle().getCorrelationId(); + System.out.println("correlationId = " + correlationId); + System.out.println("objectId = " + objectId); + System.out.println("MethodCount = " + object.getLongValue("methodCount")); + } + + if (wi.getType() == SUBSCRIBE_RESPONSE) + { + SubscribeResponseWorkItem item = (SubscribeResponseWorkItem)wi; + SubscribeParams params = item.getSubscribeParams(); + System.out.println("duration = " + params.getLifetime()); + System.out.println("interval = " + params.getPublishInterval()); + System.out.println("subscriptionId = " + params.getSubscriptionId()); + System.out.println("consoleHandle = " + params.getConsoleHandle()); + String correlationId = item.getHandle().getCorrelationId(); + System.out.println("correlationId = " + correlationId); + } + + if (wi.getType() == SUBSCRIPTION_INDICATION) + { + SubscriptionIndicationWorkItem item = (SubscriptionIndicationWorkItem)wi; + SubscribeIndication indication = item.getSubscribeIndication(); + String correlationId = indication.getConsoleHandle(); + System.out.println("correlationId = " + correlationId); + + List objects = indication.getData(); + for (QmfConsoleData object : objects) + { + if (object.isDeleted()) + { + System.out.println("object has been deleted"); + } + System.out.println("offset = " + object.getValue("offset")); + } + } + } + + public static void main(String[] args) + { + //System.out.println ("Setting log level to FATAL"); + System.setProperty("amqj.logging.level", "FATAL"); + + String url = (args.length == 1) ? args[0] : "localhost"; + AgentSubscriptionTestConsole test = new AgentSubscriptionTestConsole(url); + + BufferedReader commandLine = new BufferedReader(new InputStreamReader(System.in)); + try + { // Blocks here until return is pressed + System.out.println("Hit Return to exit"); + String s = commandLine.readLine(); + System.exit(0); + } + catch (IOException e) + { + System.out.println ("AgentSubscriptionTestConsole main(): IOException: " + e.getMessage()); + } + + System.out.println("*** Ending AgentSubscriptionTestConsole ***"); + } +} diff --git a/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/AgentTest.java b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/AgentTest.java new file mode 100644 index 0000000000..8aebe76937 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/AgentTest.java @@ -0,0 +1,298 @@ +/* + * + * 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.test; + +import javax.jms.Connection; + +// Misc Imports +import java.io.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +// QMF2 Imports +import org.apache.qpid.qmf2.agent.Agent; +import org.apache.qpid.qmf2.agent.MethodCallParams; +import org.apache.qpid.qmf2.agent.MethodCallWorkItem; +import org.apache.qpid.qmf2.agent.QmfAgentData; +import org.apache.qpid.qmf2.common.ObjectId; +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.QmfType; +import org.apache.qpid.qmf2.common.SchemaEventClass; +import org.apache.qpid.qmf2.common.SchemaMethod; +import org.apache.qpid.qmf2.common.SchemaObjectClass; +import org.apache.qpid.qmf2.common.SchemaProperty; +import org.apache.qpid.qmf2.common.WorkItem; +import org.apache.qpid.qmf2.util.ConnectionHelper; +import static org.apache.qpid.qmf2.common.WorkItem.WorkItemType.*; + +/** + * A class used to test the Agent API functionality. + * + * @author Fraser Adams + */ +public final class AgentTest implements QmfEventListener +{ + private Agent _agent; + private QmfAgentData _control; + private SchemaObjectClass _exceptionSchema; + private SchemaObjectClass _controlSchema; + private SchemaObjectClass _childSchema; + private SchemaEventClass _eventSchema; + + public AgentTest(String url) + { + try + { + System.out.println("** Starting AgentTest a test of basic Agent class functions **"); + + Connection connection = ConnectionHelper.createConnection(url, "{reconnect: true}"); + _agent = new Agent(this); + _agent.setVendor("profitron.com"); + _agent.setProduct("gizmo"); + _agent.setValue("attr1", 2000); + + System.out.println("Agent name: " + _agent.getName()); + + setupSchema(); + populateData(); + + _agent.setConnection(connection); + + for (int i = 0; i < 100; i++) + { + _control.setValue("offset", i); + //control.update(); // Send data indication to the Subscriber on the next Subscription interval + _control.publish(); // Send data indication to the Subscriber immediately + try + { + Thread.sleep(1000); + } + catch (InterruptedException ie) + { + } + } + + _control.destroy(); + + } + catch (QmfException qmfe) + { + System.err.println("QmfException " + qmfe.getMessage() + " caught: AgentTest failed"); + } + } + + public void onEvent(WorkItem wi) + { + System.out.println("WorkItem type: " + wi.getType()); + _control.incValue("methodCount", 1); + + if (wi.getType() == METHOD_CALL) + { + MethodCallWorkItem item = (MethodCallWorkItem)wi; + MethodCallParams methodCallParams = item.getMethodCallParams(); + String methodName = methodCallParams.getName(); + ObjectId objectId = methodCallParams.getObjectId(); + String userId = methodCallParams.getUserId(); + userId = userId.equals("") ? "anonymous" : userId; + QmfData inArgs = methodCallParams.getArgs(); + ObjectId controlAddress = _control.getObjectId(); + + System.out.println("Method Call User ID = " + userId); + + try + { + if (objectId == null) + { + // Method invoked directly on Agent + if (methodName.equals("toString")) + { + QmfData outArgs = new QmfData(); + outArgs.setValue("string", _agent.toString()); + _agent.methodResponse(methodName, item.getHandle(), outArgs, null); + } + } + else if (objectId.equals(controlAddress)) + { + if (methodName.equals("stop")) + { + System.out.println("Invoked stop method"); + String message = inArgs.getStringValue("message"); + System.out.println("Stopping: message = " + message); + _agent.methodResponse(methodName, item.getHandle(), null, null); + _agent.destroy(); + System.exit(1); + } + else if (methodName.equals("echo")) + { + System.out.println("Invoked echo method"); + _agent.methodResponse(methodName, item.getHandle(), inArgs, null); + } + else if (methodName.equals("event")) + { + System.out.println("Invoked event method"); + QmfEvent event = new QmfEvent(_eventSchema); + event.setSeverity((int)inArgs.getLongValue("severity")); + event.setValue("text", inArgs.getStringValue("text")); + _agent.raiseEvent(event); + _agent.methodResponse(methodName, item.getHandle(), null, null); + } + else if (methodName.equals("fail")) + { + System.out.println("Invoked fail method"); + QmfData error = new QmfData(); + if (inArgs.getBooleanValue("useString")) + { + error.setValue("error_text", inArgs.getStringValue("stringVal")); + } + else + { + error.setValue("whatHappened", "It Failed"); + error.setValue("howBad", 75); + error.setValue("details", inArgs.getValue("details")); + } + _agent.methodResponse(methodName, item.getHandle(), null, error); + } + else if (methodName.equals("create_child")) + { + System.out.println("Invoked create_child method"); + String childName = inArgs.getStringValue("name"); + System.out.println("childName = " + childName); + QmfAgentData child = new QmfAgentData(_childSchema); + child.setValue("name", childName); + _agent.addObject(child); + QmfData outArgs = new QmfData(); + outArgs.setRefValue("childAddr", child.getObjectId(), "reference"); // Set subtype just to test + _agent.methodResponse(methodName, item.getHandle(), outArgs, null); + } + } + } + catch (QmfException qmfe) + { + System.err.println("QmfException " + qmfe.getMessage() + " caught: AgentTest failed"); + QmfData error = new QmfData(); + error.setValue("error_text", qmfe.getMessage()); + _agent.methodResponse(methodName, item.getHandle(), null, error); + } + } + } + + public void setupSchema() throws QmfException + { + System.out.println("*** AgentTest initialising the various Schema classes ***"); + + // Create and register schema for this agent. + String packageName = "com.profitron.gizmo"; + + // Declare a schema for a structured exception that can be used in failed method invocations. + _exceptionSchema = new SchemaObjectClass(packageName, "exception"); + _exceptionSchema.addProperty(new SchemaProperty("whatHappened", QmfType.TYPE_STRING)); + _exceptionSchema.addProperty(new SchemaProperty("howBad", QmfType.TYPE_INT)); + _exceptionSchema.addProperty(new SchemaProperty("details", QmfType.TYPE_MAP)); + + // Declare a control object to test methods against. + _controlSchema = new SchemaObjectClass(packageName, "control"); + _controlSchema.addProperty(new SchemaProperty("state", QmfType.TYPE_STRING)); + _controlSchema.addProperty(new SchemaProperty("methodCount", QmfType.TYPE_INT)); + _controlSchema.addProperty(new SchemaProperty("offset", QmfType.TYPE_INT)); + _controlSchema.setIdNames("state"); + + SchemaMethod stopMethod = new SchemaMethod("stop", "Stop Agent"); + stopMethod.addArgument(new SchemaProperty("message", QmfType.TYPE_STRING, "{dir:IN}")); + _controlSchema.addMethod(stopMethod); + + SchemaMethod echoMethod = new SchemaMethod("echo", "Echo Arguments"); + echoMethod.addArgument(new SchemaProperty("message", QmfType.TYPE_STRING, "{dir:INOUT}")); + _controlSchema.addMethod(echoMethod); + + SchemaMethod eventMethod = new SchemaMethod("event", "Raise an Event"); + eventMethod.addArgument(new SchemaProperty("text", QmfType.TYPE_STRING, "{dir:IN}")); + eventMethod.addArgument(new SchemaProperty("severity", QmfType.TYPE_INT, "{dir:IN}")); + _controlSchema.addMethod(eventMethod); + + SchemaMethod failMethod = new SchemaMethod("fail", "Expected to Fail"); + failMethod.addArgument(new SchemaProperty("useString", QmfType.TYPE_BOOL, "{dir:IN}")); + failMethod.addArgument(new SchemaProperty("stringVal", QmfType.TYPE_STRING, "{dir:IN}")); + failMethod.addArgument(new SchemaProperty("details", QmfType.TYPE_MAP, "{dir:IN}")); + _controlSchema.addMethod(failMethod); + + SchemaMethod createMethod = new SchemaMethod("create_child", "Create Child Object"); + createMethod.addArgument(new SchemaProperty("name", QmfType.TYPE_STRING, "{dir:IN}")); + createMethod.addArgument(new SchemaProperty("childAddr", QmfType.TYPE_MAP, "{dir:OUT}")); + _controlSchema.addMethod(createMethod); + + // Declare the child class + _childSchema = new SchemaObjectClass(packageName, "child"); + _childSchema.addProperty(new SchemaProperty("name", QmfType.TYPE_STRING)); + _childSchema.setIdNames("name"); + + // Declare the event class + _eventSchema = new SchemaEventClass(packageName, "event"); + _eventSchema.addProperty(new SchemaProperty("text", QmfType.TYPE_STRING)); + + System.out.println("AgentTest Schema classes initialised OK"); + + _agent.registerObjectClass(_exceptionSchema); + _agent.registerObjectClass(_controlSchema); + _agent.registerObjectClass(_childSchema); + _agent.registerEventClass(_eventSchema); + + System.out.println("AgentTest Schema classes registered OK"); + } + + public void populateData() throws QmfException + { + System.out.println("*** AgentTest creating a control object ***"); + + _control = new QmfAgentData(_controlSchema); + _control.setValue("state", "OPERATIONAL"); + _control.setValue("methodCount", 0); + _agent.addObject(_control); + System.out.println("AgentTest Schema control object added OK"); + } + + + public static void main(String[] args) + { + //System.out.println("Setting log level to FATAL"); + System.setProperty("amqj.logging.level", "FATAL"); + + String url = (args.length == 1) ? args[0] : "localhost"; + AgentTest test1 = new AgentTest(url); + + BufferedReader commandLine = new BufferedReader(new InputStreamReader(System.in)); + try + { // Blocks here until return is pressed + System.out.println("Hit Return to exit"); + String s = commandLine.readLine(); + System.exit(0); + } + catch (IOException e) + { + System.out.println ("ConnectionAudit main(): IOException: " + e.getMessage()); + } + + System.out.println("*** Ending AgentTest ***"); + } +} diff --git a/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/AgentTestConsole.java b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/AgentTestConsole.java new file mode 100644 index 0000000000..edae41bcf4 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/AgentTestConsole.java @@ -0,0 +1,308 @@ +/* + * + * 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.test; + +import javax.jms.Connection; + +// Misc Imports +import java.io.*; +import java.util.HashMap; +import java.util.List; +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.QmfEvent; +import org.apache.qpid.qmf2.common.QmfEventListener; +import org.apache.qpid.qmf2.common.QmfException; +import org.apache.qpid.qmf2.common.WorkItem; +import org.apache.qpid.qmf2.console.Agent; +import org.apache.qpid.qmf2.console.AgentAddedWorkItem; +import org.apache.qpid.qmf2.console.AgentHeartbeatWorkItem; +import org.apache.qpid.qmf2.console.Console; +import org.apache.qpid.qmf2.console.EventReceivedWorkItem; +import org.apache.qpid.qmf2.console.MethodResult; +import org.apache.qpid.qmf2.console.MethodResponseWorkItem; +import org.apache.qpid.qmf2.console.ObjectUpdateWorkItem; +import org.apache.qpid.qmf2.console.QmfConsoleData; +import org.apache.qpid.qmf2.util.ConnectionHelper; +import static org.apache.qpid.qmf2.common.WorkItem.WorkItemType.*; + +/** + * This class is the Console part of AgentTest which together provide a test of a number of core Console and + * Agent behaviours such as Schema creation, registration and lookup, Object lookup, method invocation on Objects + * Object refreshing (updating state of local proxy objects from the real Agent). + * + * N.B. AgentTest needs to be running for this test to behave as expected. + * + * @author Fraser Adams + */ +public final class AgentTestConsole implements QmfEventListener +{ + private Console _console; + private Agent _gizmo; + + public AgentTestConsole(String url) + { + try + { + System.out.println("*** Starting AgentTestConsole used to test basic Console and Agent behaviour ***"); + + Connection connection = ConnectionHelper.createConnection(url, "{reconnect: true}"); + _console = new Console(this); + _console.addConnection(connection); + + // Wait until the gizmo Agent has been discovered + synchronized(this) + { + while (_gizmo == null) + { + long startTime = System.currentTimeMillis(); + try + { + wait(10*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 (_gizmo == null && elapsedTime >= 10) + { + System.out.println("gizmo Agent not found, you probably need to run AgentTest"); + System.exit(1); + } + } + } + + System.out.println("Testing lookup of control objects by name"); + List controls = _console.getObjects("com.profitron.gizmo", "control"); + if (controls.size() > 0) + { + System.out.println("control object found"); + QmfConsoleData control = controls.get(0); + //control.listValues(); + + ObjectId oid = control.getObjectId(); + //System.out.println("Agent Name = " + oid.getAgentName()); + //System.out.println("Agent Epoch = " + oid.getAgentEpoch()); + //System.out.println("Object Name = " + oid.getObjectName()); + + System.out.println("Testing lookup of object by ObjectId"); + controls = _console.getObjects(oid); + + if (controls.size() == 0) + { + System.out.println("No objects returned from ObjectId lookup: AgentTestConsole failed"); + System.exit(1); + } + + System.out.println("MethodCount = " + control.getLongValue("methodCount")); + QmfData inArgs; + QmfData outArgs; + MethodResult results; + +/* + System.out.println("Testing invokeMethod(toString, args) - method called directly on Agent"); + results = _gizmo.invokeMethod("toString", null); + System.out.println("gizmo.toString() = " + results.getArguments().getStringValue("string")); +*/ + + // ********** Invoke create_child nethod ********** + System.out.println("Testing invokeMethod(create_child, args)"); + inArgs = new QmfData(); + inArgs.setValue("name", "child 1"); + + results = control.invokeMethod("create_child", inArgs); + if (!results.succeeded()) + { + System.out.println("create_child returned an exception object"); + System.exit(1); + } + + if (!results.hasValue("childAddr")) + { + System.out.println("create_child returned an unexpected value"); + System.exit(1); + } + + ObjectId childId = results.getRefValue("childAddr"); + System.out.println("childId = " + childId); + System.out.println("childAddr subtype = " + results.getSubtype("childAddr")); + QmfConsoleData child1 = _console.getObjects(childId).get(0); + System.out.println("child1 name = " + child1.getStringValue("name")); + + + // Update and display state of control object + control.refresh(); + System.out.println("MethodCount = " + control.getLongValue("methodCount")); + + + // ********** Invoke event nethod ********** + System.out.println("Testing invokeMethod(event, args) "); + inArgs = new QmfData(); + inArgs.setValue("text", "Attention Will Robinson!! Aliens have just invaded"); + inArgs.setValue("severity", 0); + control.invokeMethod("event", inArgs); + + + // Update and display state of control object + control.refresh(); + System.out.println("MethodCount = " + control.getLongValue("methodCount")); + + + // ********** Invoke fail nethod ********** + System.out.println("Testing invokeMethod(fail, args) "); + QmfData details = new QmfData(); + details.setValue("detail1", "something bad"); + details.setValue("detail2", "something even badder"); + inArgs = new QmfData(); + inArgs.setValue("details", details.mapEncode()); + results = control.invokeMethod("fail", inArgs); + System.out.println("whatHappened: " + results.getStringValue("whatHappened")); + System.out.println("howBad: " + results.getLongValue("howBad")); + + // Update and display state of control object + control.refresh(); + System.out.println("MethodCount = " + control.getLongValue("methodCount")); + + + // ********** Invoke echo nethod asynchronously ********** + System.out.println("Testing asynchronous call of invokeMethod(echo, args) "); + inArgs = new QmfData(); + inArgs.setValue("message", "This message should be echoed by the Agent"); + control.invokeMethod("echo", inArgs, "echoMethodCorrelationId"); + + + // Asynchronous update and display state of control object. The state here should be the same as + // the last time it was called as this is an asynchronous refresh. The ObjectUpdateWorkItem in + // the event handler contains the new state + control.refresh("echoMethodCorrelationId"); + System.out.println("MethodCount = " + control.getLongValue("methodCount") + " (should be same as last value)"); + + + + // ********** Invoke stop nethod, this will stop the Agent ********** + System.out.println("Testing invokeMethod(stop, args) "); + inArgs = new QmfData(); + inArgs.setValue("message", "Ladies and gentlemen Elvis has just left the building"); + control.invokeMethod("stop", inArgs); + + + } + else + { + System.out.println("No control objects returned: AgentTestConsole failed"); + System.exit(1); + } + } + catch (QmfException qmfe) + { + System.err.println("QmfException " + qmfe.getMessage() + ": AgentTestConsole failed"); + System.exit(1); + } + } + + public void onEvent(WorkItem wi) + { + System.out.println("WorkItem type: " + wi.getType()); + + if (wi.getType() == AGENT_ADDED) + { + AgentAddedWorkItem item = (AgentAddedWorkItem)wi; + Agent agent = item.getAgent(); + + // If this is the gizmo Agent we notify the main thread so processing can continue. + if (agent.getProduct().equals("gizmo")) + { + synchronized(this) + { + _gizmo = agent; + notify(); + } + } + } + + if (wi.getType() == AGENT_HEARTBEAT) + { + AgentHeartbeatWorkItem item = (AgentHeartbeatWorkItem)wi; + Agent agent = item.getAgent(); + System.out.println(agent.getName()); + } + + if (wi.getType() == EVENT_RECEIVED) + { + EventReceivedWorkItem item = (EventReceivedWorkItem)wi; + Agent agent = item.getAgent(); + QmfEvent event = item.getEvent(); + + String className = event.getSchemaClassId().getClassName(); + System.out.println("Event: " + className); +//event.listValues(); + } + + if (wi.getType() == METHOD_RESPONSE) + { + MethodResponseWorkItem item = (MethodResponseWorkItem)wi; + MethodResult result = item.getMethodResult(); + String correlationId = item.getHandle().getCorrelationId(); + System.out.println("correlationId = " + correlationId); + System.out.println(result.getStringValue("message")); + } + + if (wi.getType() == OBJECT_UPDATE) + { + ObjectUpdateWorkItem item = (ObjectUpdateWorkItem)wi; + QmfConsoleData object = item.getQmfConsoleData(); + ObjectId objectId = object.getObjectId(); + String correlationId = item.getHandle().getCorrelationId(); + System.out.println("correlationId = " + correlationId); + System.out.println("objectId = " + objectId); + System.out.println("MethodCount = " + object.getLongValue("methodCount")); + } + + } + + public static void main(String[] args) + { + //System.out.println ("Setting log level to FATAL"); + System.setProperty("amqj.logging.level", "FATAL"); + + String url = (args.length == 1) ? args[0] : "localhost"; + AgentTestConsole test = new AgentTestConsole(url); + + BufferedReader commandLine = new BufferedReader(new InputStreamReader(System.in)); + try + { // Blocks here until return is pressed + System.out.println("Hit Return to exit"); + String s = commandLine.readLine(); + System.exit(0); + } + catch (IOException e) + { + System.out.println ("AgentTestConsole main(): IOException: " + e.getMessage()); + } + + System.out.println("*** Ending AgentTestConsole ***"); + } +} diff --git a/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/BigPayloadAgentTest.java b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/BigPayloadAgentTest.java new file mode 100644 index 0000000000..a6613464b5 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/BigPayloadAgentTest.java @@ -0,0 +1,174 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.qmf2.test; + +import javax.jms.Connection; + +// Misc Imports +import java.io.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +// QMF2 Imports +import org.apache.qpid.qmf2.agent.Agent; +import org.apache.qpid.qmf2.agent.MethodCallParams; +import org.apache.qpid.qmf2.agent.MethodCallWorkItem; +import org.apache.qpid.qmf2.agent.QmfAgentData; +import org.apache.qpid.qmf2.common.ObjectId; +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.QmfType; +import org.apache.qpid.qmf2.common.SchemaEventClass; +import org.apache.qpid.qmf2.common.SchemaMethod; +import org.apache.qpid.qmf2.common.SchemaObjectClass; +import org.apache.qpid.qmf2.common.SchemaProperty; +import org.apache.qpid.qmf2.common.WorkItem; +import org.apache.qpid.qmf2.util.ConnectionHelper; +import static org.apache.qpid.qmf2.common.WorkItem.WorkItemType.*; + +/** + * A class used to test the Agent API functionality. This Agent specifies an explicit queue name and a larger than + * default queue size so that it can receive large payloads on its methods. + * + * @author Fraser Adams + */ +public final class BigPayloadAgentTest implements QmfEventListener +{ + private Agent _agent; + private QmfAgentData _control; + private SchemaObjectClass _controlSchema; + + public BigPayloadAgentTest(String url) + { + try + { + System.out.println("** Starting BigPayloadAgentTest a test of basic Agent class functions **"); + + Connection connection = ConnectionHelper.createConnection(url, "{reconnect: true}"); + _agent = new Agent(this); + _agent.setVendor("test.com"); + _agent.setProduct("big-payload-agent"); + + System.out.println("Agent name: " + _agent.getName()); + + setupSchema(); + populateData(); + _agent.setConnection(connection, " ; {link: {name:'big-payload-agent', x-declare: {arguments: {'qpid.policy_type': ring, 'qpid.max_size': 500000000}}}}"); + + } + catch (QmfException qmfe) + { + System.err.println("QmfException " + qmfe.getMessage() + " caught: BigPayloadAgentTest failed"); + } + } + + public void onEvent(WorkItem wi) + { + System.out.println("WorkItem type: " + wi.getType()); + + if (wi.getType() == METHOD_CALL) + { + MethodCallWorkItem item = (MethodCallWorkItem)wi; + MethodCallParams methodCallParams = item.getMethodCallParams(); + String methodName = methodCallParams.getName(); + ObjectId objectId = methodCallParams.getObjectId(); + + QmfData inArgs = methodCallParams.getArgs(); + ObjectId controlAddress = _control.getObjectId(); + + if (objectId.equals(controlAddress)) + { + if (methodName.equals("processPayload")) + { + System.out.println("Invoked processPayload method"); + + byte[] parameter = inArgs.getValue("parameter"); + System.out.println("payload size = " + parameter.length); + + QmfData outArgs = new QmfData(); + outArgs.setValue("return", parameter); + _agent.methodResponse(methodName, item.getHandle(), outArgs, null); + } + } + } + } + + public void setupSchema() throws QmfException + { + System.out.println("*** BigPayloadAgentTest initialising the various Schema classes ***"); + + // Create and register schema for this agent. + String packageName = "com.test.bigagent"; + + // Declare a control object to test methods against. + _controlSchema = new SchemaObjectClass(packageName, "control"); + _controlSchema.addProperty(new SchemaProperty("name", QmfType.TYPE_STRING)); + _controlSchema.setIdNames("name"); + + SchemaMethod createMethod = new SchemaMethod("processPayload", "Process a large payload"); + createMethod.addArgument(new SchemaProperty("parameter", QmfType.TYPE_STRING, "{dir:IN}")); + createMethod.addArgument(new SchemaProperty("return", QmfType.TYPE_STRING, "{dir:OUT}")); + _controlSchema.addMethod(createMethod); + + System.out.println("BigPayloadAgentTest Schema classes initialised OK"); + + _agent.registerObjectClass(_controlSchema); + + System.out.println("BigPayloadAgentTest Schema classes registered OK"); + } + + public void populateData() throws QmfException + { + System.out.println("*** BigPayloadAgentTest creating a control object ***"); + + _control = new QmfAgentData(_controlSchema); + _control.setValue("name", "controller"); + _agent.addObject(_control); + System.out.println("BigPayloadAgentTest Schema control object added OK"); + } + + + public static void main(String[] args) + { + //System.out.println("Setting log level to FATAL"); + System.setProperty("amqj.logging.level", "FATAL"); + + String url = (args.length == 1) ? args[0] : "localhost"; + BigPayloadAgentTest test1 = new BigPayloadAgentTest(url); + + BufferedReader commandLine = new BufferedReader(new InputStreamReader(System.in)); + try + { // Blocks here until return is pressed + System.out.println("Hit Return to exit"); + String s = commandLine.readLine(); + System.exit(0); + } + catch (IOException e) + { + System.out.println ("ConnectionAudit main(): IOException: " + e.getMessage()); + } + + System.out.println("*** Ending BigPayloadAgentTest ***"); + } +} diff --git a/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/BigPayloadAgentTestConsole.java b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/BigPayloadAgentTestConsole.java new file mode 100644 index 0000000000..4457dea5e5 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/BigPayloadAgentTestConsole.java @@ -0,0 +1,155 @@ +/* + * + * 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.test; + +import javax.jms.Connection; + +// Misc Imports +import java.io.*; +import java.util.HashMap; +import java.util.List; +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.QmfEvent; +import org.apache.qpid.qmf2.common.QmfEventListener; +import org.apache.qpid.qmf2.common.QmfException; +import org.apache.qpid.qmf2.common.WorkItem; +import org.apache.qpid.qmf2.console.Agent; +import org.apache.qpid.qmf2.console.AgentAddedWorkItem; +import org.apache.qpid.qmf2.console.AgentHeartbeatWorkItem; +import org.apache.qpid.qmf2.console.Console; +import org.apache.qpid.qmf2.console.EventReceivedWorkItem; +import org.apache.qpid.qmf2.console.MethodResult; +import org.apache.qpid.qmf2.console.MethodResponseWorkItem; +import org.apache.qpid.qmf2.console.ObjectUpdateWorkItem; +import org.apache.qpid.qmf2.console.QmfConsoleData; +import org.apache.qpid.qmf2.util.ConnectionHelper; +import static org.apache.qpid.qmf2.common.WorkItem.WorkItemType.*; + +/** + * This class is the Console part of AgentTest which together provide a test of a number of core Console and + * Agent behaviours such as Schema creation, registration and lookup, Object lookup, method invocation on Objects + * Object refreshing (updating state of local proxy objects from the real Agent). + * + * N.B. AgentTest needs to be running for this test to behave as expected. + * + * @author Fraser Adams + */ +public final class BigPayloadAgentTestConsole implements QmfEventListener +{ + private Console _console; + private Agent _agent; + + public BigPayloadAgentTestConsole(String url) + { + try + { + System.out.println("*** Starting BigPayloadAgentTestConsole used to test basic Console and Agent behaviour ***"); + + Connection connection = ConnectionHelper.createConnection(url, "{reconnect: true}"); + _console = new Console(this); + _console.addConnection(connection, " ; {link: {name:'big-payload-console', x-declare: {arguments: {'qpid.policy_type': ring, 'qpid.max_size': 500000000}}}}"); + + // Wait until the broker Agent has been discovered + _agent = _console.findAgent("big-payload-agent"); + if (_agent == null) + { + System.out.println("Big Payload Agent not found"); + System.exit(1); + } + + List controls = _console.getObjects("com.test.bigagent", "control"); + if (controls.size() > 0) + { + QmfConsoleData control = controls.get(0); + + // ********** Invoke processPayload nethod ********** + System.out.println("Testing invokeMethod(processPayload, args)"); + QmfData inArgs = new QmfData(); + inArgs.setValue("parameter", new byte[150000000]); + + MethodResult results = control.invokeMethod("processPayload", inArgs); + if (!results.succeeded()) + { + System.out.println("processPayload returned an exception object"); + System.exit(1); + } + + if (!results.hasValue("return")) + { + System.out.println("processPayload returned an unexpected value"); + System.exit(1); + } + + byte[] returnVal = results.getValue("return"); + System.out.println("returnVal size = " + returnVal.length); + } + else + { + System.out.println("No control objects returned: BigPayloadAgentTestConsole failed"); + System.exit(1); + } + } + catch (QmfException qmfe) + { + System.err.println("QmfException " + qmfe.getMessage() + ": BigPayloadAgentTestConsole failed"); + System.exit(1); + } + } + + public void onEvent(WorkItem wi) + { + //System.out.println("WorkItem type: " + wi.getType()); + + if (wi.getType() == AGENT_HEARTBEAT) + { + AgentHeartbeatWorkItem item = (AgentHeartbeatWorkItem)wi; + Agent agent = item.getAgent(); + System.out.println(agent.getName()); + } + } + + public static void main(String[] args) + { + //System.out.println ("Setting log level to FATAL"); + System.setProperty("amqj.logging.level", "FATAL"); + + String url = (args.length == 1) ? args[0] : "localhost"; + BigPayloadAgentTestConsole test = new BigPayloadAgentTestConsole(url); + + BufferedReader commandLine = new BufferedReader(new InputStreamReader(System.in)); + try + { // Blocks here until return is pressed + System.out.println("Hit Return to exit"); + String s = commandLine.readLine(); + System.exit(0); + } + catch (IOException e) + { + System.out.println ("BigPayloadAgentTestConsole main(): IOException: " + e.getMessage()); + } + + System.out.println("*** Ending BigPayloadAgentTestConsole ***"); + } +} diff --git a/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/BrokerSubscriptionTestConsole.java b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/BrokerSubscriptionTestConsole.java new file mode 100644 index 0000000000..27eadbaac8 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/BrokerSubscriptionTestConsole.java @@ -0,0 +1,262 @@ +/* + * + * 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.test; + +import javax.jms.Connection; + +// Misc Imports +import java.io.*; +import java.util.HashMap; +import java.util.List; +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.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.WorkItem; +import org.apache.qpid.qmf2.console.Agent; +import org.apache.qpid.qmf2.console.AgentAddedWorkItem; +import org.apache.qpid.qmf2.console.AgentHeartbeatWorkItem; +import org.apache.qpid.qmf2.console.Console; +import org.apache.qpid.qmf2.console.EventReceivedWorkItem; +import org.apache.qpid.qmf2.console.MethodResult; +import org.apache.qpid.qmf2.console.MethodResponseWorkItem; +import org.apache.qpid.qmf2.console.ObjectUpdateWorkItem; +import org.apache.qpid.qmf2.console.QmfConsoleData; +import org.apache.qpid.qmf2.console.SubscribeIndication; +import org.apache.qpid.qmf2.console.SubscribeParams; +import org.apache.qpid.qmf2.console.SubscribeResponseWorkItem; +import org.apache.qpid.qmf2.console.SubscriptionIndicationWorkItem; +import org.apache.qpid.qmf2.util.ConnectionHelper; +import static org.apache.qpid.qmf2.common.WorkItem.WorkItemType.*; + +/** + * This class provides a test of broker subscription behaviour. + *

+ * N.B. That the 0.12 C++ broker does not *actually* 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. One slightly subtle thing to bear in mind however is that the broker + * ManagementAgent separates "properties" and "statistics" so for example a subscription query on say a queue's + * name property will only evaluate true for a properties push and not a statistics push. The most useful usage + * pattern is likely to be a getObjects() call to return all queues, followed by a search for the queue of interest + * then a subscription with a query using the ObjectId of the wanted queue. + * + * @author Fraser Adams + */ +public final class BrokerSubscriptionTestConsole implements QmfEventListener +{ + private Console _console; + private Agent _broker; + private ObjectId _objectId; // Used to test ObjectId Query + + public BrokerSubscriptionTestConsole(String url) + { + try + { + System.out.println("** Starting BrokerSubscriptionTestConsole used to test subscription behaviour **"); + + Connection connection = ConnectionHelper.createConnection(url, "{reconnect: true}"); + _console = new Console(this); + _console.addConnection(connection); + + // Wait until the broker Agent has been discovered + _broker = _console.findAgent("broker"); + if (_broker == null) + { + System.out.println("broker Agent not found"); + System.exit(1); + } + + System.out.println("Creating Query for objects whose name property has a value that starts with 'a'"); + + SubscribeParams params; + QmfQuery query = new QmfQuery(QmfQueryTarget.OBJECT, "['re_match', 'name', ['quote', '^a']]"); + + // Create a subscription, response returned synchronously + params = _console.createSubscription(_broker, query, "consoleHandle1", "{publishInterval:5}"); + System.out.println("duration = " + params.getLifetime()); + System.out.println("interval = " + params.getPublishInterval()); + System.out.println("subscriptionId = " + params.getSubscriptionId()); + System.out.println("consoleHandle = " + params.getConsoleHandle()); + + // Sleep a while, getting query result as they become available + try + { + Thread.sleep(20000); + } + catch (InterruptedException ie) + { + } + + // Refresh the subscription getting results asynchronously, just for variety + System.out.println("Calling refreshSubscription on " + params.getSubscriptionId()); + _console.refreshSubscription(params.getSubscriptionId(), "{replyHandle:ignoredReplyHandle}"); + + + // Create a subscription for _class_name = queue + System.out.println("Creating Query for all queue objects"); + query = new QmfQuery(QmfQueryTarget.OBJECT, "['eq', '_class_name', ['quote', 'queue']]"); + params = _console.createSubscription(_broker, query, "queues"); + + while (_objectId == null) + { + System.out.println("Waiting for ObjectId to be set"); + try + { + Thread.sleep(1000); + } + catch (InterruptedException ie) + { + } + } + + // Cancel the query for all queue objects + System.out.println("Cancelling Query for all queue objects"); + _console.cancelSubscription(params.getSubscriptionId()); + + // Create a subscription for _object_id + System.out.println("Creating Query for _object_id = " + _objectId); + query = new QmfQuery(QmfQueryTarget.OBJECT, _objectId); + params = _console.createSubscription(_broker, query, "queues"); + + } + catch (QmfException qmfe) + { + System.err.println("QmfException " + qmfe.getMessage() + ": BrokerSubscriptionTestConsole failed"); + System.exit(1); + } + } + + public void onEvent(WorkItem wi) + { + System.out.println("WorkItem type: " + wi.getType()); + + if (wi.getType() == AGENT_HEARTBEAT) + { + AgentHeartbeatWorkItem item = (AgentHeartbeatWorkItem)wi; + Agent agent = item.getAgent(); + System.out.println(agent.getName()); + } + + if (wi.getType() == EVENT_RECEIVED) + { + EventReceivedWorkItem item = (EventReceivedWorkItem)wi; + Agent agent = item.getAgent(); + QmfEvent event = item.getEvent(); + + String className = event.getSchemaClassId().getClassName(); + System.out.println("Event: " + className); +//event.listValues(); + } + + if (wi.getType() == METHOD_RESPONSE) + { + MethodResponseWorkItem item = (MethodResponseWorkItem)wi; + MethodResult result = item.getMethodResult(); + String correlationId = item.getHandle().getCorrelationId(); + System.out.println("correlationId = " + correlationId); + System.out.println(result.getStringValue("message")); + } + + if (wi.getType() == OBJECT_UPDATE) + { + ObjectUpdateWorkItem item = (ObjectUpdateWorkItem)wi; + QmfConsoleData object = item.getQmfConsoleData(); + ObjectId objectId = object.getObjectId(); + String correlationId = item.getHandle().getCorrelationId(); + System.out.println("correlationId = " + correlationId); + System.out.println("objectId = " + objectId); + System.out.println("MethodCount = " + object.getLongValue("methodCount")); + } + + if (wi.getType() == SUBSCRIBE_RESPONSE) + { + SubscribeResponseWorkItem item = (SubscribeResponseWorkItem)wi; + SubscribeParams params = item.getSubscribeParams(); + System.out.println("duration = " + params.getLifetime()); + System.out.println("interval = " + params.getPublishInterval()); + System.out.println("subscriptionId = " + params.getSubscriptionId()); + System.out.println("consoleHandle = " + params.getConsoleHandle()); + String correlationId = item.getHandle().getCorrelationId(); + System.out.println("correlationId = " + correlationId); + } + + if (wi.getType() == SUBSCRIPTION_INDICATION) + { + SubscriptionIndicationWorkItem item = (SubscriptionIndicationWorkItem)wi; + SubscribeIndication indication = item.getSubscribeIndication(); + String correlationId = indication.getConsoleHandle(); + System.out.println("correlationId = " + correlationId); + + List objects = indication.getData(); + for (QmfConsoleData object : objects) + { + if (object.isDeleted()) + { + System.out.println("object has been deleted"); + } + String className = object.getSchemaClassId().getClassName(); + System.out.println("object class = " + className); + if (className.equals("queue") || className.equals("exchange")) + { + if (object.hasValue("name")) + { + System.out.println("property update, name = " + object.getStringValue("name")); + } + else + { + _objectId = object.getObjectId(); + System.out.println("statistic update, oid = " + _objectId); + } + } + } + } + } + + public static void main(String[] args) + { + //System.out.println ("Setting log level to FATAL"); + System.setProperty("amqj.logging.level", "FATAL"); + + String url = (args.length == 1) ? args[0] : "localhost"; + BrokerSubscriptionTestConsole test = new BrokerSubscriptionTestConsole(url); + + BufferedReader commandLine = new BufferedReader(new InputStreamReader(System.in)); + // Blocks here until return is pressed + try + { // Blocks here until return is pressed + System.out.println("Hit Return to exit"); + String s = commandLine.readLine(); + System.exit(0); + } + catch (IOException e) + { + System.out.println ("BrokerSubscriptionTestConsole main(): IOException: " + e.getMessage()); + } + + System.out.println("*** Ending BrokerSubscriptionTestConsole ***"); + } +} diff --git a/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/InvokeMethodTest.java b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/InvokeMethodTest.java new file mode 100644 index 0000000000..8b175e3ca6 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/InvokeMethodTest.java @@ -0,0 +1,112 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.qmf2.test; + +import javax.jms.Connection; + +// Misc Imports +import java.io.*; +import java.util.HashMap; +import java.util.List; +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.QmfEvent; +import org.apache.qpid.qmf2.common.QmfEventListener; +import org.apache.qpid.qmf2.common.QmfException; +import org.apache.qpid.qmf2.common.SchemaClass; +import org.apache.qpid.qmf2.common.SchemaClassId; +import org.apache.qpid.qmf2.common.WorkItem; +import org.apache.qpid.qmf2.console.Agent; +import org.apache.qpid.qmf2.console.AgentAddedWorkItem; +import org.apache.qpid.qmf2.console.Console; +import org.apache.qpid.qmf2.console.MethodResult; +import org.apache.qpid.qmf2.console.QmfConsoleData; +import org.apache.qpid.qmf2.util.ConnectionHelper; + +/** + * This class tests invoking a method lots of times. + * + * @author Fraser Adams + */ +public final class InvokeMethodTest implements QmfEventListener +{ + private Console _console; + + public InvokeMethodTest(String url) + { + try + { + System.out.println("*** Starting InvokeMethodTest ctrl^C to exit ***"); + System.out.println("This is intended to soak test QMF2 invokeMethod, it doesn't do anything obvious"); + System.out.println("but attaching jconsole shows memory consumption. It *looks* like it's leaking"); + System.out.println("memory, but it turns out to be due to the use of a SoftReference in Qpid's"); + System.out.println("setReplyTo() method on JMSMessage. Consumption *eventually* flattens out..."); + + Connection connection = ConnectionHelper.createConnection(url, "{reconnect: true}"); + _console = new Console(this); + _console.addConnection(connection); + + // First we create a large number of queues using the QMF2 create method on the broker object + List brokers = _console.getObjects("org.apache.qpid.broker", "broker"); + if (brokers.isEmpty()) + { + System.out.println("No broker QmfConsoleData returned"); + System.exit(1); + } + + QmfConsoleData broker = brokers.get(0); + QmfData arguments = new QmfData(); + + while (true) + { + try + { + MethodResult results = broker.invokeMethod("getLogLevel", arguments); +//System.out.println(results.getStringValue("level")); + } + catch (QmfException e) + { // This may be throw if we've already added the queues, we just catch and ignore for this test. + //System.out.println(e.getMessage()); + } + } + } + catch (QmfException qmfe) + { + System.err.println("QmfException " + qmfe.getMessage() + ": InvokeMethodTest failed"); + System.exit(1); + } + } + + public void onEvent(WorkItem wi) + { + } + + public static void main(String[] args) + { + System.setProperty("amqj.logging.level", "FATAL"); + + String url = (args.length == 1) ? args[0] : "localhost"; + InvokeMethodTest test = new InvokeMethodTest(url); + } +} diff --git a/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/PartialGetObjectsTest.java b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/PartialGetObjectsTest.java new file mode 100644 index 0000000000..e42879947b --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/PartialGetObjectsTest.java @@ -0,0 +1,177 @@ +/* + * + * 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.test; + +import javax.jms.Connection; + +// Misc Imports +import java.io.*; +import java.util.HashMap; +import java.util.List; +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.QmfEvent; +import org.apache.qpid.qmf2.common.QmfEventListener; +import org.apache.qpid.qmf2.common.QmfException; +import org.apache.qpid.qmf2.common.SchemaClass; +import org.apache.qpid.qmf2.common.SchemaClassId; +import org.apache.qpid.qmf2.common.WorkItem; +import org.apache.qpid.qmf2.console.Agent; +import org.apache.qpid.qmf2.console.AgentAddedWorkItem; +import org.apache.qpid.qmf2.console.Console; +import org.apache.qpid.qmf2.console.QmfConsoleData; +import org.apache.qpid.qmf2.util.ConnectionHelper; + +/** + * This class tests the case where getObjects() returns "a lot" of objects. The broker ManagementAgent actually + * sends multiple messages in this case with the initial messages marked with a "partial" header. The Console + * getObjects() method needs to be able to support receiving multiple response messages when the "partial" + * header is set. + * + * @author Fraser Adams + */ +public final class PartialGetObjectsTest implements QmfEventListener +{ + private Console _console; + + public PartialGetObjectsTest(String url) + { + try + { + System.out.println("*** Starting PartialGetObjectsTest used to test schema retrieval ***"); + + Connection connection = ConnectionHelper.createConnection(url, "{reconnect: true}"); + _console = new Console(this); + _console.addConnection(connection); + + // First we create a large number of queues using the QMF2 create method on the broker object + List brokers = _console.getObjects("org.apache.qpid.broker", "broker"); + if (brokers.isEmpty()) + { + System.out.println("No broker QmfConsoleData returned"); + System.exit(1); + } + + QmfConsoleData broker = brokers.get(0); + QmfData arguments = new QmfData(); + arguments.setValue("type", "queue"); + + for (int i = 0; i < 300; i++) + { + arguments.setValue("name", "test " + i); + + try + { + broker.invokeMethod("create", arguments); + } + catch (QmfException e) + { // This may be throw if we've already added the queues, we just catch and ignore for this test. + //System.out.println(e.getMessage()); + } + } + + // After we've created lots of queues we attempt to list them. This list should include all the queues + // we've added irrespective of the number. + List queues = _console.getObjects("org.apache.qpid.broker", "queue"); + System.out.println("Call 1 Returned " + queues.size() + " objects"); + for (QmfConsoleData queue : queues) + { + String name = queue.getStringValue("name"); + System.out.println("Queue: " + name); + } + + // We get the queue objects a second time. If getObjects() correctly handles partial responses then + // this should return the complete list of queue objects, if not it will only return the subset that + // corresponds to the additional partial responses. Not handling these messages is likely to be a very + // bad thing as subsequent QMF requests will then start to receive the wrong responses. + queues = _console.getObjects("org.apache.qpid.broker", "queue"); + System.out.println("Call 2 Returned " + queues.size() + " objects"); + for (QmfConsoleData queue : queues) + { + String name = queue.getStringValue("name"); + System.out.println("Queue: " + name); + } + + } + catch (QmfException qmfe) + { + System.err.println("QmfException " + qmfe.getMessage() + ": PartialGetObjectsTest failed"); + System.exit(1); + } + } + + public void onEvent(WorkItem wi) + { + /* if (wi instanceof AgentAddedWorkItem) + { + AgentAddedWorkItem item = (AgentAddedWorkItem)wi; + Agent agent = item.getAgent(); + System.out.println("\nAgent " + agent.getName() + " added "); + + // Retrieve the List of SchemaClassIds from the Agent, these will be used to retrieve the Schema. + List schemaClassIds = _console.getClasses(agent); + + if (schemaClassIds.size() > 0) + { + // For each retrieved Class retrieve and display the Schema. + for (SchemaClassId schemaClassId : schemaClassIds) + { + List schema = _console.getSchema(schemaClassId, agent); + if (schema.size() == 1) + { + schema.get(0).listValues(); + } + } + } + else + { + System.out.println("No schema information is available for this Agent"); + } + }*/ + + } + + public static void main(String[] args) + { + //System.out.println ("Setting log level to FATAL"); + System.setProperty("amqj.logging.level", "FATAL"); + + String url = (args.length == 1) ? args[0] : "localhost"; + PartialGetObjectsTest test = new PartialGetObjectsTest(url); + + BufferedReader commandLine = new BufferedReader(new InputStreamReader(System.in)); + try + { // Blocks here until return is pressed + System.out.println("Hit Return to exit"); + String s = commandLine.readLine(); + System.exit(0); + } + catch (IOException e) + { + System.out.println ("PartialGetObjectsTest main(): IOException: " + e.getMessage()); + } + + System.out.println("*** Ending PartialGetObjectsTest ***"); + } +} diff --git a/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/SchemaTest.java b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/SchemaTest.java new file mode 100644 index 0000000000..f510bb201c --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/SchemaTest.java @@ -0,0 +1,126 @@ +/* + * + * 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.test; + +import javax.jms.Connection; + +// Misc Imports +import java.io.*; +import java.util.HashMap; +import java.util.List; +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.QmfEvent; +import org.apache.qpid.qmf2.common.QmfEventListener; +import org.apache.qpid.qmf2.common.QmfException; +import org.apache.qpid.qmf2.common.SchemaClass; +import org.apache.qpid.qmf2.common.SchemaClassId; +import org.apache.qpid.qmf2.common.WorkItem; +import org.apache.qpid.qmf2.console.Agent; +import org.apache.qpid.qmf2.console.AgentAddedWorkItem; +import org.apache.qpid.qmf2.console.Console; +import org.apache.qpid.qmf2.console.QmfConsoleData; +import org.apache.qpid.qmf2.util.ConnectionHelper; + +/** + * This class retrieves schema information when it detects that Agents have been added. + * + * @author Fraser Adams + */ +public final class SchemaTest implements QmfEventListener +{ + private Console _console; + + public SchemaTest(String url) + { + try + { + System.out.println("*** Starting SchemaTest used to test schema retrieval ***"); + + Connection connection = ConnectionHelper.createConnection(url, "{reconnect: true}"); + _console = new Console(this); + _console.addConnection(connection); + + } + catch (QmfException qmfe) + { + System.err.println("QmfException " + qmfe.getMessage() + ": SchemaTest failed"); + System.exit(1); + } + } + + public void onEvent(WorkItem wi) + { + if (wi instanceof AgentAddedWorkItem) + { + AgentAddedWorkItem item = (AgentAddedWorkItem)wi; + Agent agent = item.getAgent(); + System.out.println("\nAgent " + agent.getName() + " added "); + + // Retrieve the List of SchemaClassIds from the Agent, these will be used to retrieve the Schema. + List schemaClassIds = _console.getClasses(agent); + + if (schemaClassIds.size() > 0) + { + // For each retrieved Class retrieve and display the Schema. + for (SchemaClassId schemaClassId : schemaClassIds) + { + List schema = _console.getSchema(schemaClassId, agent); + if (schema.size() == 1) + { + schema.get(0).listValues(); + } + } + } + else + { + System.out.println("No schema information is available for this Agent"); + } + } + + } + + public static void main(String[] args) + { + //System.out.println ("Setting log level to FATAL"); + System.setProperty("amqj.logging.level", "FATAL"); + + String url = (args.length == 1) ? args[0] : "localhost"; + SchemaTest test = new SchemaTest(url); + + BufferedReader commandLine = new BufferedReader(new InputStreamReader(System.in)); + try + { // Blocks here until return is pressed + System.out.println("Hit Return to exit"); + String s = commandLine.readLine(); + System.exit(0); + } + catch (IOException e) + { + System.out.println ("SchemaTest main(): IOException: " + e.getMessage()); + } + + System.out.println("*** Ending SchemaTest ***"); + } +} diff --git a/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/Test1.java b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/Test1.java new file mode 100644 index 0000000000..f71f4cdf32 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/Test1.java @@ -0,0 +1,140 @@ +/* + * + * 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.test; + +import javax.jms.Connection; + +// Misc Imports +import java.io.*; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.HashMap; + +// QMF2 Imports +import org.apache.qpid.qmf2.common.BlockingNotifier; +import org.apache.qpid.qmf2.common.Notifier; +import org.apache.qpid.qmf2.common.QmfEventListener; +import org.apache.qpid.qmf2.common.QmfException; +import org.apache.qpid.qmf2.common.WorkItem; +import org.apache.qpid.qmf2.console.Agent; +import org.apache.qpid.qmf2.console.Console; +import org.apache.qpid.qmf2.console.QmfConsoleData; +import org.apache.qpid.qmf2.util.ConnectionHelper; + +/** + * A class used to test the basic Console Agent discovery behaviour + * + * @author Fraser Adams + */ + +public final class Test1 +{ + private Console _console; + + public Test1(String url) + { + try + { + System.out.println("*** Starting Test1 synchronous Agent discovery ***"); + Connection connection = ConnectionHelper.createConnection(url, "{reconnect: true}"); + _console = new Console(); + _console.addConnection(connection); + + System.out.println("*** Test1 testing _console.getAgents(): ***"); + List agents = _console.getAgents(); + + if (agents.size() == 0) + { + System.out.println("*** Test1 failed, _console.getAgents() should return at least Broker Agent ***"); + System.exit(1); + } + + for (Agent agent : agents) + { + agent.listValues(); + } + + System.out.println("*** Test1 testing _console.getAgent(\"broker\"): ***"); + Agent agent = _console.getAgent("broker"); + agent.listValues(); + + System.out.println("*** Test1 testing _console.findAgent(\"broker\"): ***"); + agent = _console.findAgent("broker"); + if (agent == null) + { + System.out.println("*** Test1 _console.findAgent(\"broker\") returned null : Test1 failed ***"); + System.exit(1); + } + else + { + agent.listValues(); + } + + + System.out.println("*** Test1 testing _console.findAgent(\"monkey\"): ***"); + agent = _console.findAgent("monkey"); + if (agent == null) + { + System.out.println("*** Test1 _console.findAgent(\"monkey\") correctly returned null ***"); + } + else + { + agent.listValues(); + } + + + System.out.println("*** Test1 testing _console.getObjects(\"broker\"): ***"); + List brokers = _console.getObjects("broker"); + if (brokers.size() == 0) + { + System.out.println("*** Test1 _console.getObjects(\"broker\") should return at least one object : Test1 failed ***"); + System.exit(1); + } + else + { + for (QmfConsoleData broker : brokers) + { + broker.listValues(); + } + } + + System.out.println("*** Ending Test1 ***"); + } + catch (QmfException qmfe) + { + System.err.println("QmfException " + qmfe.getMessage() + " caught: Test1 failed"); + } + } + + public static void main(String[] args) + { + //System.out.println ("Setting log level to FATAL"); + System.setProperty("amqj.logging.level", "FATAL"); + + // As of Qpid 0.16 the Session Dispatcher Thread is non-Daemon so the JVM gets prevented from exiting. + // Setting the following property to true makes it a Daemon Thread. + System.setProperty("qpid.jms.daemon.dispatcher", "true"); + + String url = (args.length == 1) ? args[0] : "localhost"; + Test1 test1 = new Test1(url); + } +} diff --git a/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/Test2.java b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/Test2.java new file mode 100644 index 0000000000..11924f824d --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/Test2.java @@ -0,0 +1,121 @@ +/* + * + * 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.test; + +import javax.jms.Connection; + +// Misc Imports +import java.io.*; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.HashMap; + +// QMF2 Imports +import org.apache.qpid.qmf2.common.BlockingNotifier; +import org.apache.qpid.qmf2.common.Notifier; +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.WorkItem; +import org.apache.qpid.qmf2.console.Agent; +import org.apache.qpid.qmf2.console.Console; +import org.apache.qpid.qmf2.console.QmfConsoleData; +import org.apache.qpid.qmf2.util.ConnectionHelper; +import static org.apache.qpid.qmf2.common.WorkItem.WorkItemType.*; + +/** + * A class used to test a non-blocking query for all Agents using the Notifier/WorkItem Event API. As most of + * the tests and samples use the alternative QmfEventListener API (because the author prefers that) rather than + * the official QMF2 WorkQueue API it seems sensible to include at least one test illustrating that. + * + * Note that in practice the WorkQueue API can also be used without an explicit notifier as one of the + * overloaded Console getNextWorkitem() methods is a blocking call to the WorkQueue that blocks until a WorkItem + * is available. The approach used in this class is for API illustration purposes and the author is unlikely to + * use it for anything useful. + * + * @author Fraser Adams + */ + +public final class Test2 +{ + private Console _console; + + public Test2(String url) + { + try + { + System.out.println("*** Starting Test2 asynchronous Agent discovery using WorkQueue API ***"); + + BlockingNotifier notifier = new BlockingNotifier(); + + Connection connection = ConnectionHelper.createConnection(url, "{reconnect: true}"); + _console = new Console(notifier); +//console.disableAgentDiscovery(); // To miss all notifications this needs done before addConnection() + _console.addConnection(connection); + + int count = 0; + while (true) + { + notifier.waitForWorkItem(); + System.out.println("WorkItem available, WorkItem count = " + _console.getWorkitemCount()); + + WorkItem wi; + while ((wi = _console.getNextWorkitem(0)) != null) + { + System.out.println("WorkItem type: " + wi.getType()); + if (wi.getType() == AGENT_HEARTBEAT || wi.getType() == AGENT_ADDED || + wi.getType() == AGENT_DELETED || wi.getType() == AGENT_RESTARTED || + wi.getType() == EVENT_RECEIVED) + { + Map p = wi.>getParams(); + Agent agent = (Agent)p.get("agent"); + System.out.println(agent.getName()); + } + } + + count++; + if (count == 10) + { + System.out.println("Applying Agent Discovery Query ['eq', '_product', ['quote', 'gizmo']]"); + System.out.println("This should disable broker Agent events but allow gizmo Agent events"); + System.out.println("Run AgentTest to prove this"); + QmfQuery query = new QmfQuery(QmfQueryTarget.OBJECT, "['eq', '_product', ['quote', 'gizmo']]"); + _console.enableAgentDiscovery(query); + } + } + } + catch (QmfException qmfe) + { + System.err.println("QmfException " + qmfe.getMessage() + " caught: Test2 failed"); + } + } + + public static void main(String[] args) + { + //System.out.println ("Setting log level to FATAL"); + System.setProperty("amqj.logging.level", "FATAL"); + + String url = (args.length == 1) ? args[0] : "localhost"; + Test2 test2 = new Test2(url); + } +} diff --git a/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/Test3.java b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/Test3.java new file mode 100644 index 0000000000..fdb9180d36 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/Test3.java @@ -0,0 +1,137 @@ +/* + * + * 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.test; + +// Misc Imports +import java.io.*; +import java.util.List; + +// QMF2 Imports +import org.apache.qpid.qmf2.common.QmfException; +import org.apache.qpid.qmf2.common.QmfType; +import org.apache.qpid.qmf2.common.SchemaEventClass; +import org.apache.qpid.qmf2.common.SchemaMethod; +import org.apache.qpid.qmf2.common.SchemaObjectClass; +import org.apache.qpid.qmf2.common.SchemaProperty; + +/** + * A class used to test the Schema classes + * + * @author Fraser Adams + */ +public final class Test3 +{ + public Test3() + { + try + { + System.out.println("*** Starting Test3 testing the various Schema classes ***"); + + // Create and register schema for this agent. + String packageName = "com.profitron.gizmo"; + + // Declare a schema for a structured exception that can be used in failed method invocations. + SchemaObjectClass exception = new SchemaObjectClass(packageName, "exception"); + + exception.addProperty(new SchemaProperty("whatHappened", QmfType.TYPE_STRING)); + exception.addProperty(new SchemaProperty("howBad", QmfType.TYPE_INT)); + exception.addProperty(new SchemaProperty("details", QmfType.TYPE_MAP)); + + // Declare a control object to test methods against. + SchemaObjectClass control = new SchemaObjectClass(packageName, "control"); + control.addProperty(new SchemaProperty("state", QmfType.TYPE_STRING)); + control.addProperty(new SchemaProperty("methodCount", QmfType.TYPE_INT)); + + SchemaMethod stopMethod = new SchemaMethod("stop", "Stop Agent"); + stopMethod.addArgument(new SchemaProperty("message", QmfType.TYPE_STRING)); + control.addMethod(stopMethod); + + SchemaMethod echoMethod = new SchemaMethod("echo", "Echo Arguments"); + echoMethod.addArgument(new SchemaProperty("sequence", QmfType.TYPE_INT, "{dir:INOUT}")); + echoMethod.addArgument(new SchemaProperty("map", QmfType.TYPE_MAP, "{dir:INOUT}")); + control.addMethod(echoMethod); + + SchemaMethod eventMethod = new SchemaMethod("event", "Raise an Event"); + eventMethod.addArgument(new SchemaProperty("text", QmfType.TYPE_STRING, "{dir:IN}")); + eventMethod.addArgument(new SchemaProperty("severity", QmfType.TYPE_INT, "{dir:IN}")); + control.addMethod(eventMethod); + + SchemaMethod failMethod = new SchemaMethod("fail", "Expected to Fail"); + failMethod.addArgument(new SchemaProperty("useString", QmfType.TYPE_BOOL, "{dir:IN}")); + failMethod.addArgument(new SchemaProperty("stringVal", QmfType.TYPE_STRING, "{dir:IN}")); + failMethod.addArgument(new SchemaProperty("details", QmfType.TYPE_MAP, "{dir:IN}")); + control.addMethod(failMethod); + + SchemaMethod createMethod = new SchemaMethod("create_child", "Create Child Object"); + createMethod.addArgument(new SchemaProperty("name", QmfType.TYPE_STRING, "{dir:IN}")); + createMethod.addArgument(new SchemaProperty("childAddr", QmfType.TYPE_MAP, "{dir:OUT}")); + control.addMethod(createMethod); + + // Declare the child class + SchemaObjectClass child = new SchemaObjectClass(packageName, "child"); + child.addProperty(new SchemaProperty("name", QmfType.TYPE_STRING)); + + // Declare the event class + SchemaEventClass event = new SchemaEventClass(packageName, "event"); + event.addProperty(new SchemaProperty("text", QmfType.TYPE_STRING)); + + System.out.println("Test3 Schema classes initialised OK"); + + // Now we create new instance of each class from the map encodings and list the values + // to check everything looks OK. + + System.out.println("Test3 testing serialisation of exception schema"); + SchemaObjectClass exceptionFromMap = new SchemaObjectClass(exception.mapEncode()); + exceptionFromMap.listValues(); + System.out.println(); + + System.out.println("Test3 testing serialisation of control schema"); + SchemaObjectClass controlFromMap = new SchemaObjectClass(control.mapEncode()); + controlFromMap.listValues(); + System.out.println(); + + System.out.println("Test3 testing serialisation of child schema"); + SchemaObjectClass childFromMap = new SchemaObjectClass(child.mapEncode()); + childFromMap.listValues(); + System.out.println(); + + System.out.println("Test3 testing serialisation of event schema"); + SchemaEventClass eventFromMap = new SchemaEventClass(event.mapEncode()); + eventFromMap.listValues(); + System.out.println(); + + } + catch (QmfException qmfe) + { + System.err.println("QmfException " + qmfe.getMessage() + " caught: Test3 failed"); + } + } + + public static void main(String[] args) + { + //System.out.println ("Setting log level to FATAL"); + System.setProperty("amqj.logging.level", "FATAL"); + + Test3 Test3 = new Test3(); + + System.out.println("*** Ending Test3 ***"); + } +} diff --git a/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/Test4.java b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/Test4.java new file mode 100644 index 0000000000..29a8ba8f88 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/Test4.java @@ -0,0 +1,339 @@ +/* + * + * 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.test; + +// Misc Imports +import java.io.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +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.QmfQuery; +import org.apache.qpid.qmf2.common.QmfQueryTarget; +import org.apache.qpid.qmf2.common.QmfType; +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.SchemaMethod; +import org.apache.qpid.qmf2.common.SchemaObjectClass; +import org.apache.qpid.qmf2.common.SchemaProperty; + +import org.apache.qpid.qmf2.console.QmfConsoleData; +import org.apache.qpid.qmf2.agent.QmfAgentData; + + +/** + * A class used to test the QmfQuery classes + * + * @author Fraser Adams + */ +public final class Test4 +{ + + private Map _schemaCache = new ConcurrentHashMap(); + private Map _objectIndex = new ConcurrentHashMap(); + + public Test4() + { + try + { + System.out.println("*** Starting Test4 testing the QmfQuery class ***"); + + // Create and register schema for this agent. + String packageName = "com.fadams.qmf2"; + + // Declare a mammal class to test against. + SchemaObjectClass mammal = new SchemaObjectClass(packageName, "mammal"); + mammal.addProperty(new SchemaProperty("name", QmfType.TYPE_STRING)); + mammal.addProperty(new SchemaProperty("legs", QmfType.TYPE_INT)); + mammal.setIdNames("name"); + + // Declare an insect class to test against. + SchemaObjectClass insect = new SchemaObjectClass(packageName, "insect"); + insect.addProperty(new SchemaProperty("name", QmfType.TYPE_STRING)); + insect.addProperty(new SchemaProperty("legs", QmfType.TYPE_INT)); + insect.setIdNames("name"); + + // Declare a reptile class to test against. + SchemaObjectClass reptile = new SchemaObjectClass(packageName, "reptile"); + reptile.addProperty(new SchemaProperty("name", QmfType.TYPE_STRING)); + reptile.addProperty(new SchemaProperty("legs", QmfType.TYPE_INT)); + reptile.setIdNames("name"); + + // Declare a bird class to test against. + SchemaObjectClass bird = new SchemaObjectClass(packageName, "bird"); + bird.addProperty(new SchemaProperty("name", QmfType.TYPE_STRING)); + bird.addProperty(new SchemaProperty("legs", QmfType.TYPE_INT)); + bird.setIdNames("name"); + + + registerObjectClass(mammal); + registerObjectClass(insect); + registerObjectClass(reptile); + registerObjectClass(bird); + + QmfAgentData cat = new QmfAgentData(mammal); + cat.setValue("name", "cat"); + cat.setValue("legs", 4l); + addObject(cat); + + QmfAgentData dog = new QmfAgentData(mammal); + dog.setValue("name", "dog"); + dog.setValue("legs", 4l); + addObject(dog); + + QmfAgentData rabbit = new QmfAgentData(mammal); + rabbit.setValue("name", "rabbit"); + rabbit.setValue("legs", 4); + addObject(rabbit); + + QmfAgentData horse = new QmfAgentData(mammal); + horse.setValue("name", "horse"); + horse.setValue("legs", 4); + addObject(horse); + + QmfAgentData human = new QmfAgentData(mammal); + human.setValue("name", "human"); + human.setValue("legs", 2); + addObject(human); + + + QmfAgentData wasp = new QmfAgentData(insect); + wasp.setValue("name", "wasp"); + wasp.setValue("legs", 6); + addObject(wasp); + + QmfAgentData ant = new QmfAgentData(insect); + ant.setValue("name", "ant"); + ant.setValue("legs", 6); + addObject(ant); + + QmfAgentData crocodile = new QmfAgentData(reptile); + crocodile.setValue("name", "crocodile"); + crocodile.setValue("legs", 4); + addObject(crocodile); + + QmfAgentData gecko = new QmfAgentData(reptile); + gecko.setValue("name", "gecko"); + gecko.setValue("legs", 4); + addObject(gecko); + + QmfAgentData python = new QmfAgentData(reptile); + python.setValue("name", "python"); + python.setValue("legs", 0); + addObject(python); + + QmfAgentData hawk = new QmfAgentData(bird); + hawk.setValue("name", "hawk"); + hawk.setValue("legs", 2); + addObject(hawk); + + QmfAgentData ostrich = new QmfAgentData(bird); + ostrich.setValue("name", "ostrich"); + ostrich.setValue("legs", 2); + addObject(ostrich); + + + System.out.println("total number of objects registered: " + _objectIndex.size()); + + QmfQuery query; + List results; + + System.out.println("looking up wasp object by ID"); + query = new QmfQuery(QmfQueryTarget.OBJECT, wasp.getObjectId()); + results = evaluateDataQuery(query); + displayResults(results); + + System.out.println("\nlooking up mammal objects"); + query = new QmfQuery(QmfQueryTarget.OBJECT, new SchemaClassId("mammal")); + results = evaluateDataQuery(query); + displayResults(results); + + System.out.println("\nlooking up everything in package com.fadams.qmf2"); + query = new QmfQuery(QmfQueryTarget.OBJECT, new SchemaClassId("com.fadams.qmf2", null)); + results = evaluateDataQuery(query); + displayResults(results); + + + System.out.println("\nQuery for all mammals with more than two legs"); + String predicate = "['and', ['eq', '_package_name', ['quote', 'com.fadams.qmf2']], " + + "['eq', '_class_name', ['quote', 'mammal']], " + + "['gt', 'legs', 2]]"; + + //predicate = "['eq', '_package_name', ['quote', 'com.fadams.qmf2']]"; + + //predicate = "[]"; + + query = new QmfQuery(QmfQueryTarget.OBJECT, predicate); + System.out.println(query.getPredicate()); + + results = evaluateDataQuery(query); + displayResults(results); + + + System.out.println("\nQuery for everything with less than four legs"); + predicate = "['lt', 'legs', 4]"; + + query = new QmfQuery(QmfQueryTarget.OBJECT, predicate); + System.out.println(query.getPredicate()); + + results = evaluateDataQuery(query); + displayResults(results); + + + System.out.println("\nQuery for everything with between two and four legs"); + predicate = "['and', ['ge', 'legs', 2], " + + "['le', 'legs', 4]]"; + + query = new QmfQuery(QmfQueryTarget.OBJECT, predicate); + System.out.println(query.getPredicate()); + + results = evaluateDataQuery(query); + displayResults(results); + + + System.out.println("\nQuery for all reptiles or birds"); + predicate = "['or', ['eq', '_class_name', ['quote', 'reptile']], " + + "['eq', '_class_name', ['quote', 'bird']]]"; + + query = new QmfQuery(QmfQueryTarget.OBJECT, predicate); + System.out.println(query.getPredicate()); + + results = evaluateDataQuery(query); + displayResults(results); + + + System.out.println("\nQuery for everything whose name matches the regex ^h"); + predicate = "['re_match', 'name', ['quote', '^h']]"; + + query = new QmfQuery(QmfQueryTarget.OBJECT, predicate); + System.out.println(query.getPredicate()); + + results = evaluateDataQuery(query); + displayResults(results); + + + } + catch (QmfException qmfe) + { + System.err.println("QmfException " + qmfe.getMessage() + " caught: Test4 failed"); + } + } + + public void registerObjectClass(SchemaObjectClass schema) + { + SchemaClassId classId = schema.getClassId(); + _schemaCache.put(classId, schema); + } + + public void addObject(QmfAgentData object) throws QmfException + { + SchemaClassId classId = object.getSchemaClassId(); + SchemaClass schema = _schemaCache.get(classId); + + // Try to create an objectName using the set of property names that have been specified as idNames in the schema + StringBuilder buf = new StringBuilder(); + if (schema != null && schema instanceof SchemaObjectClass) + { + String[] idNames = ((SchemaObjectClass)schema).getIdNames(); + for (String name : idNames) + { + buf.append(object.getStringValue(name)); + } + } + String objectName = buf.toString(); + + // If the schema hasn't given any help we use a UUID + if (objectName.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 it's ObjectId and add that to the object + ObjectId addr = new ObjectId("test"/*name*/, objectName, 0/*epoch*/); + object.setObjectId(addr); + + if (_objectIndex.get(addr) != null) + { + throw new QmfException("Duplicate QmfAgentData Address"); + } + + _objectIndex.put(addr, object); + } + + + public List evaluateDataQuery(QmfQuery query) + { + List results = new ArrayList(); + + 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(new QmfConsoleData(object.mapEncode(), null)); + } + } + else + { + for (QmfAgentData object : _objectIndex.values()) + { + if (!object.isDeleted() && query.evaluate(object)) + { + results.add(new QmfConsoleData(object.mapEncode(), null)); + } + } + } + + return results; + } + + public List evaluateSchemaQuery(QmfQuery query) + { + return null; + } + + + public void displayResults(List values) + { + for (QmfConsoleData object : values) + { + System.out.println("name = " + object.getStringValue("name") + ", legs = " + object.getLongValue("legs")); + } + } + + public static void main(String[] args) + { + //System.out.println ("Setting log level to FATAL"); + System.setProperty("amqj.logging.level", "FATAL"); + + Test4 Test4 = new Test4(); + + System.out.println("*** Ending Test4 ***"); + } +} diff --git a/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/URLTest.java b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/URLTest.java new file mode 100644 index 0000000000..35bd9f537b --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-test/src/main/java/org/apache/qpid/qmf2/test/URLTest.java @@ -0,0 +1,82 @@ +/* + * + * 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.test; + +//import javax.jms.Connection; + +// Misc Imports +import java.io.*; + +// QMF2 Imports +import org.apache.qpid.qmf2.util.ConnectionHelper; + +/** + * A class used to test the ConnectionHelper utility, which provides support for a range of AMQP URL formats + * and a basic wrapper for Connection creation. + * + * @author Fraser Adams + */ + +public final class URLTest +{ + public static void main(String[] args) + { + //System.out.println ("Setting log level to FATAL"); + System.setProperty("amqj.logging.level", "FATAL"); + + System.out.println("Running URLTest, this tests ConnectionHelper.createConnectionURL() for various URL formats."); + System.out.println("It doesn't actually create a connection, it just displays the resulting ConnectionURL"); + + String url; + url = ConnectionHelper.createConnectionURL("localhost"); + System.out.println(url); + + url = ConnectionHelper.createConnectionURL("localhost:5672", "{username: foo, password: bar, reconnect: true, reconnect_timeout: 5, reconnect_limit: 5000, tcp-nodelay: true, heartbeat: 10, protocol: ssl}"); + System.out.println(url); + + url = ConnectionHelper.createConnectionURL("guest/guest@localhost:5672", "{reconnect: true, tcp-nodelay: true}"); + System.out.println(url); + + url = ConnectionHelper.createConnectionURL("amqp:localhost:5672, tcp:localhost:5673", "{reconnect: true, tcp-nodelay: true, failover: roundrobin, cyclecount: 20}"); + System.out.println(url); + + url = ConnectionHelper.createConnectionURL("amqp://foo:bar@host1:1234/vhost?clientid=baz"); + System.out.println(url); + + url = ConnectionHelper.createConnectionURL("amqp://localhost"); + System.out.println(url); + + url = ConnectionHelper.createConnectionURL("amqp://vm:foo:1234,tcp:foo:5678/"); + System.out.println(url); + + url = ConnectionHelper.createConnectionURL("amqp://host1,host2,host3/?retry=2"); + System.out.println(url); + + url = ConnectionHelper.createConnectionURL("amqp://host1,host2?retry=2,host3"); + System.out.println(url); + + url = ConnectionHelper.createConnectionURL("amqp://grok:hostname?flavour=strawberry;frobnication=on/vhost"); + System.out.println(url); + + url = ConnectionHelper.createConnectionURL("amqp://host?ssl=true"); + System.out.println(url); + } +} diff --git a/qpid/tools/src/java/qpid-qmf2-tools/LICENSE b/qpid/tools/src/java/qpid-qmf2-tools/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-tools/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/qpid/tools/src/java/qpid-qmf2-tools/NOTICE b/qpid/tools/src/java/qpid-qmf2-tools/NOTICE new file mode 100644 index 0000000000..10ecbc6884 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-tools/NOTICE @@ -0,0 +1,5 @@ +Apache Qpid QMF2 Tools +Copyright 2012-2014 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/ConnectionAudit.sh b/qpid/tools/src/java/qpid-qmf2-tools/bin/ConnectionAudit.sh new file mode 100755 index 0000000000..69894edc42 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-tools/bin/ConnectionAudit.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# +# 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. +# + +# Test if we're running on Cygwin. +cygwin=false; +case "`uname`" in + CYGWIN*) cygwin=true;; +esac + +WHEREAMI=`dirname $0` +if [ -z "$QMF2_HOME" ]; then + export QMF2_HOME=`cd $WHEREAMI/../ && pwd` +fi + +CLASSPATH=$QMF2_HOME/lib/*:$CLASSPATH + +# If we're on Cygwin we need to convert to Windows path. +if $cygwin; then + CLASSPATH=$(cygpath -wp $CLASSPATH) +fi + +# Get the log level from the AMQJ_LOGGING_LEVEL environment variable. +if [ -n "$AMQJ_LOGGING_LEVEL" ]; then + PROPERTIES=-Damqj.logging.level=$AMQJ_LOGGING_LEVEL +fi + +java -cp "$CLASSPATH" $PROPERTIES org.apache.qpid.qmf2.tools.ConnectionAudit "$@" diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/ConnectionLogger.sh b/qpid/tools/src/java/qpid-qmf2-tools/bin/ConnectionLogger.sh new file mode 100755 index 0000000000..41bc1e5b2f --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-tools/bin/ConnectionLogger.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# +# 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. +# + +# Test if we're running on Cygwin. +cygwin=false; +case "`uname`" in + CYGWIN*) cygwin=true;; +esac + +WHEREAMI=`dirname $0` +if [ -z "$QMF2_HOME" ]; then + export QMF2_HOME=`cd $WHEREAMI/../ && pwd` +fi + +CLASSPATH=$QMF2_HOME/lib/*:$CLASSPATH + +# If we're on Cygwin we need to convert to Windows path. +if $cygwin; then + CLASSPATH=$(cygpath -wp $CLASSPATH) +fi + +# Get the log level from the AMQJ_LOGGING_LEVEL environment variable. +if [ -n "$AMQJ_LOGGING_LEVEL" ]; then + PROPERTIES=-Damqj.logging.level=$AMQJ_LOGGING_LEVEL +fi + +java -cp "$CLASSPATH" $PROPERTIES org.apache.qpid.qmf2.tools.ConnectionLogger "$@" diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/QpidConfig.sh b/qpid/tools/src/java/qpid-qmf2-tools/bin/QpidConfig.sh new file mode 100755 index 0000000000..f6a475f5e7 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-tools/bin/QpidConfig.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# +# 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. +# + +# Test if we're running on Cygwin. +cygwin=false; +case "`uname`" in + CYGWIN*) cygwin=true;; +esac + +WHEREAMI=`dirname $0` +if [ -z "$QMF2_HOME" ]; then + export QMF2_HOME=`cd $WHEREAMI/../ && pwd` +fi + +CLASSPATH=$QMF2_HOME/lib/*:$CLASSPATH + +# If we're on Cygwin we need to convert to Windows path. +if $cygwin; then + CLASSPATH=$(cygpath -wp $CLASSPATH) +fi + +# Get the log level from the AMQJ_LOGGING_LEVEL environment variable. +if [ -n "$AMQJ_LOGGING_LEVEL" ]; then + PROPERTIES=-Damqj.logging.level=$AMQJ_LOGGING_LEVEL +fi + +java -cp "$CLASSPATH" $PROPERTIES org.apache.qpid.qmf2.tools.QpidConfig "$@" diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/QpidCtrl.sh b/qpid/tools/src/java/qpid-qmf2-tools/bin/QpidCtrl.sh new file mode 100755 index 0000000000..760974a2af --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-tools/bin/QpidCtrl.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# +# 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. +# + +# Test if we're running on Cygwin. +cygwin=false; +case "`uname`" in + CYGWIN*) cygwin=true;; +esac + +WHEREAMI=`dirname $0` +if [ -z "$QMF2_HOME" ]; then + export QMF2_HOME=`cd $WHEREAMI/../ && pwd` +fi + +CLASSPATH=$QMF2_HOME/lib/*:$CLASSPATH + +# If we're on Cygwin we need to convert to Windows path. +if $cygwin; then + CLASSPATH=$(cygpath -wp $CLASSPATH) +fi + +# Get the log level from the AMQJ_LOGGING_LEVEL environment variable. +if [ -n "$AMQJ_LOGGING_LEVEL" ]; then + PROPERTIES=-Damqj.logging.level=$AMQJ_LOGGING_LEVEL +fi + +java -cp "$CLASSPATH" $PROPERTIES org.apache.qpid.qmf2.tools.QpidCtrl "$@" diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/QpidPrintEvents.sh b/qpid/tools/src/java/qpid-qmf2-tools/bin/QpidPrintEvents.sh new file mode 100755 index 0000000000..52c21431f3 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-tools/bin/QpidPrintEvents.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# +# 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. +# + +# Test if we're running on Cygwin. +cygwin=false; +case "`uname`" in + CYGWIN*) cygwin=true;; +esac + +WHEREAMI=`dirname $0` +if [ -z "$QMF2_HOME" ]; then + export QMF2_HOME=`cd $WHEREAMI/../ && pwd` +fi + +CLASSPATH=$QMF2_HOME/lib/*:$CLASSPATH + +# If we're on Cygwin we need to convert to Windows path. +if $cygwin; then + CLASSPATH=$(cygpath -wp $CLASSPATH) +fi + +# Get the log level from the AMQJ_LOGGING_LEVEL environment variable. +if [ -n "$AMQJ_LOGGING_LEVEL" ]; then + PROPERTIES=-Damqj.logging.level=$AMQJ_LOGGING_LEVEL +fi + +java -cp "$CLASSPATH" $PROPERTIES org.apache.qpid.qmf2.tools.QpidPrintEvents "$@" diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/QpidQueueStats.sh b/qpid/tools/src/java/qpid-qmf2-tools/bin/QpidQueueStats.sh new file mode 100755 index 0000000000..e63764058e --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-tools/bin/QpidQueueStats.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# +# 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. +# + +# Test if we're running on Cygwin. +cygwin=false; +case "`uname`" in + CYGWIN*) cygwin=true;; +esac + +WHEREAMI=`dirname $0` +if [ -z "$QMF2_HOME" ]; then + export QMF2_HOME=`cd $WHEREAMI/../ && pwd` +fi + +CLASSPATH=$QMF2_HOME/lib/*:$CLASSPATH + +# If we're on Cygwin we need to convert to Windows path. +if $cygwin; then + CLASSPATH=$(cygpath -wp $CLASSPATH) +fi + +# Get the log level from the AMQJ_LOGGING_LEVEL environment variable. +if [ -n "$AMQJ_LOGGING_LEVEL" ]; then + PROPERTIES=-Damqj.logging.level=$AMQJ_LOGGING_LEVEL +fi + +java -cp "$CLASSPATH" $PROPERTIES org.apache.qpid.qmf2.tools.QpidQueueStats "$@" diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/QpidRestAPI.sh b/qpid/tools/src/java/qpid-qmf2-tools/bin/QpidRestAPI.sh new file mode 100755 index 0000000000..5a1254d632 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-tools/bin/QpidRestAPI.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# +# 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. +# + +# Test if we're running on Cygwin. +cygwin=false; +case "`uname`" in + CYGWIN*) cygwin=true;; +esac + +WHEREAMI=`dirname $0` +if [ -z "$QMF2_HOME" ]; then + export QMF2_HOME=`cd $WHEREAMI/../ && pwd` +fi + +CLASSPATH=$QMF2_HOME/lib/*:$CLASSPATH + +# If we're on Cygwin we need to convert to Windows path. +if $cygwin; then + CLASSPATH=$(cygpath -wp $CLASSPATH) +fi + +# Get the log level from the AMQJ_LOGGING_LEVEL environment variable. +if [ -n "$AMQJ_LOGGING_LEVEL" ]; then + PROPERTIES=-Damqj.logging.level=$AMQJ_LOGGING_LEVEL +fi + +java -cp "$CLASSPATH" $PROPERTIES org.apache.qpid.restapi.QpidRestAPI "$@" diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/QueueFuse.sh b/qpid/tools/src/java/qpid-qmf2-tools/bin/QueueFuse.sh new file mode 100755 index 0000000000..c7c8ebed16 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-tools/bin/QueueFuse.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# +# 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. +# + +# Test if we're running on Cygwin. +cygwin=false; +case "`uname`" in + CYGWIN*) cygwin=true;; +esac + +WHEREAMI=`dirname $0` +if [ -z "$QMF2_HOME" ]; then + export QMF2_HOME=`cd $WHEREAMI/../ && pwd` +fi + +CLASSPATH=$QMF2_HOME/lib/*:$CLASSPATH + +# If we're on Cygwin we need to convert to Windows path. +if $cygwin; then + CLASSPATH=$(cygpath -wp $CLASSPATH) +fi + +# Get the log level from the AMQJ_LOGGING_LEVEL environment variable. +if [ -n "$AMQJ_LOGGING_LEVEL" ]; then + PROPERTIES=-Damqj.logging.level=$AMQJ_LOGGING_LEVEL +fi + +java -cp "$CLASSPATH" $PROPERTIES org.apache.qpid.qmf2.tools.QueueFuse "$@" diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/log4j.xml b/qpid/tools/src/java/qpid-qmf2-tools/bin/log4j.xml new file mode 100644 index 0000000000..434bb73259 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-tools/bin/log4j.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/authentication/account.properties b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/authentication/account.properties new file mode 100644 index 0000000000..ff30bdc55c --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/authentication/account.properties @@ -0,0 +1,23 @@ +# +# +# 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. +# +# + +guest=guest +admin=admin diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/apple-touch-icon.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/apple-touch-icon.png new file mode 100644 index 0000000000..ad4478ccac Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/apple-touch-icon.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/favicon.ico b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/favicon.ico new file mode 100644 index 0000000000..1f61b0edd2 Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/favicon.ico differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/index.html b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/index.html new file mode 100755 index 0000000000..e21871b59f --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/index.html @@ -0,0 +1,56 @@ + + + + + + + QMF Console + + + + + + + + + + + + + + +

+ + + diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/css/itablet-ie6.css b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/css/itablet-ie6.css new file mode 100644 index 0000000000..343192efd6 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/css/itablet-ie6.css @@ -0,0 +1,323 @@ +/* + * + * 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. + * + */ + +/** + * Stylesheet to try and make things look not *too* bad in IE6 and below. Supporting IE6 really is a nightmare :-( + */ + +/* Needed to get rid of annoying "permanent" IE6 scrollbar. */ +html +{ + overflow: hidden; +} + +/* Explicitly setting height to 100% avoids some evil IE6 layout bugs */ +html, .sidebar, .main, .popup-window, .popup-container, .popup, .scroll-area +{ + height: 100%; +} + +/* Explicitly setting width to 100% avoids some evil IE6 layout bugs */ +ul.list, .popup, .scroll-area +{ + width: 100%; +} + +#sidebar-wrapper /* Make sure IE "hasLayout" is enabled by doing zoom: 1. */ +{ + zoom: 1; +} + +/* IE6 doesn't support color: inherit so we have to set it explicitly. */ +ul li.grey a /* Grey text generally used to show inactive fields */ +{ + color: #8f8f8f; +} + +ul li a +{ + color: #060606; +} + +ul li.active a +{ + color: #fff; +} + +/* IE6 form has a default non-zero margin, so we need to zero it. */ +form +{ + margin: 0; +} + +.sidebar .scroll-area +{ + border-right: 1px solid #000; +} + +ul.list li.first-child +{ + border-top: 1px groove #fff; +} + +ul.list li.last-child +{ + border-bottom: 2px groove #fff; +} + +ul.list li a p +{ + right: 0; +} + +ul.list li.arrow a p, ul.list li.multiline a div p +{ + right: 18px; +} + + +ul.list li.multiline a.icon p, ul.list li.multiline a.icon div p +{ + right: 4px; +} + +/** + * white-space: nowrap; doesn't work especially well in IE6, so we set it to normal and constrain height. + * Unfortunately with the approach below ellipses aren't displayed, but it's the lesser of the evils. + * white-space: nowrap; does seem to work with an explicit width set, but doing that breaks loads of other things. + */ +ul li a p.sub, ul li a p.title +{ + white-space: normal; +} + +ul li a p.title, ul li a p.sub +{ + height: 16px; +} + +/* For IE6 we need to use a GIF instead of a PNG to make the input background transparent but still receive events. */ +input, textarea +{ + background: url(/itablet/images/ie/transparent.gif) repeat; +} + +/** + * For IE6 button :before and :after don't work so we have to resort to some JavaScript to add extra classes and tags + * IE6 doesn't support PNG images with alpha transparency, so we use gifs, which are OK but a littly more jagged. + */ + +a.button +{ + background: url(/itablet/images/ie/button-sprite.gif) 0px -30px repeat-x; +} + +a.button .before +{ + position: absolute; + top: 0; + left: -5px; + width: 5px; + height: 30px; + background: url(/itablet/images/ie/button-sprite.gif) -18px 0; +} + +a.button .after +{ + position: absolute; + top: 0; + right: -5px; + width: 5px; + height: 30px; + background: url(/itablet/images/ie/button-sprite.gif) -13px 0; +} + +a.button:active +{ + background-color: #766d69; + background-position: 0px -60px; +} + +a.button:active .before +{ + background-position: -41px 0; +} + +a.button:active .after +{ + background-position: -36px 0; +} + +a.button.back .before +{ + position: absolute; + left: -13px; + width: 13px; + height: 30px; + background-position: 0 0; +} + +a.button.back:active .before +{ + background-position: -23px 0; +} + +a.button.blue +{ + background: url(/itablet/images/ie/blue-button-sprite.gif) 0px -30px repeat-x; +} + +a.button.blue .before +{ + background: url(/itablet/images/ie/blue-button-sprite.gif) -18px 0; +} + +a.button.blue .after +{ + background: url(/itablet/images/ie/blue-button-sprite.gif) -13px 0; +} + +a.button.blue-back .before +{ + background-position: 0 0; +} + +a.button.blue:active +{ + background-color: #6b6f76; + background-position: 0px -60px; +} + +a.button.blue:active .before +{ + background-position: -41px 0; +} + +a.button.blue:active .after +{ + background-position: -36px 0; +} + +a.button.blue-back:active .before +{ + background-position: -23px 0; +} + +.popup-window +{ + background: url(/itablet/images/ie/transparent.gif); + filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='/itablet/images/ie/transparent.png', sizingMethod='scale'); +} + +.popup-window.smoked +{ + background: url(/itablet/images/ie/transparent.gif); + filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='/itablet/images/ie/smoked.png', sizingMethod='scale'); +} + +div.mask +{ + background: url(/itablet/images/ie/transparent.gif); + filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='/itablet/images/mask.png', sizingMethod='crop'); +} + +div.onoff +{ + background: url(/itablet/images/ie/transparent.gif); + filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='/itablet/images/on_off.png', sizingMethod='crop'); +} + +/** + * Unfortunately AlphaImageLoader doesn't play nicely with alignment and these images need to be right aligned. + * Fortunately the single level of transparency allowed by gif images is good enough and only a little granier. + */ + +ul.list li.arrow.radio +{ + background: #f7f7f7; +} + +ul.list li.radio label +{ + padding: 0 11px 0 11px; /* top right bottom left */ +} + +ul li.active, ul.list li.radio.active, ul.list li.radio.ie6-checked-active /* Highlight in blue with white text */ +{ + background: #035de7; +} + +ul li.arrow +{ + background: url(/itablet/images/ie/chevron.gif) no-repeat right; +} + +ul.list li.arrow +{ + background: #f7f7f7 url(/itablet/images/ie/chevron.gif) no-repeat right; +} + +ul li.ie6-arrow-active, ul.list li.ie6-arrow-active +{ + background: #035de7 url(/itablet/images/ie/chevron-active.gif) no-repeat right; +} + +ul.list li.radio.checked label +{ + background: url(/itablet/images/ie/tick.gif) no-repeat right; +} + +ul.list li.radio.ie6-checked-active label +{ + background: url(/itablet/images/ie/tick-active.gif) no-repeat right; +} + +ul.list li.ie6-radio-arrow +{ + background: #f7f7f7 url(/itablet/images/ie/blue-chevron.gif) no-repeat right; +} + +ul.list li.ie6-radio-arrow label +{ + padding: 0 11px 0 32px; /* top right bottom left */ +} + +ul.list li.ie6-radio-arrow a +{ + height: 0; + width: 0; +} + +ul.list li.ie6-checked-arrow label +{ + background: url(/itablet/images/ie/tick.gif) no-repeat 11px; +} + +ul.list li.ie6-radio-arrow-active, ul.list li.ie6-checked-arrow-active +{ + background: #035de7 url(/itablet/images/ie/blue-chevron.gif) no-repeat right; +} + +ul.list li.ie6-checked-arrow-active label +{ + background: url(/itablet/images/ie/tick-active.gif) no-repeat 11px; +} + diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/css/itablet-ie7.css b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/css/itablet-ie7.css new file mode 100644 index 0000000000..d8bd020662 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/css/itablet-ie7.css @@ -0,0 +1,214 @@ +/* + * + * 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. + * + */ + +/** + * Stylesheet to try and make things look not *too* bad in IE7. + */ + +/* Needed to get rid of annoying "permanent" IE7 scrollbar. */ +html +{ + overflow: hidden; +} + +#sidebar-wrapper /* Make sure IE "hasLayout" is enabled by doing zoom: 1. */ +{ + zoom: 1; +} + +/* IE7 doesn't correctly size the scrollbar without this, however it causes IE6 problems. */ +.page +{ + height: 100%; +} + +ul.list +{ + width: 100%; +} + +/* IE < 8 doesn't support color: inherit so we have to set it explicitly. */ +ul li.grey a /* Grey text generally used to show inactive fields */ +{ + color: #8f8f8f; +} + +ul li a +{ + color: #060606; +} + +ul li.active a +{ + color: #fff; +} + +/* IE7 gives this anchor a default size which adds extra visible padding. As the navigable radio button has */ +/* reskinned the markup around this li the anchor doesn't actually need to be visible so we can safely hide it. */ +ul.list li.arrow.radio a +{ + display: none; +} + +/* IE < 8 form has a default non-zero margin, so we need to zero it. */ +form +{ + margin: 0; +} + +/* For IE7 :before and :after don't work so we have to resort to some JavaScript to inject extra classes and tags */ +ul.list li:first-child +{ + border-top: 3px groove #fff; /* IE7 without hasLayout set doesn't show the border if it's less than 3px, why??? */ +} + +ul.list li:first-child > .fbefore, /* Use fbefore not before in case first-child and last-child apply to same element. */ +ul.list li:first-child > .fafter /* Use fafter not after in case first-child and last-child apply to same element. */ +{ + position: absolute; + top: -3px; + left: -2px; + width: 10px; + height: 10px; + z-index: 1; + background: url(/itablet/images/ie/radius-10px-sprite.png); +} + +ul.list li:first-child > .fafter /* Use fafter not after in case first-child and last-child apply to same element. */ +{ + left: auto; + right: -2px; + background-position: -10px 0; +} + +/* The fake rounded corners for the IE8 stylesheet more or less work, but the bottom offset in IE7 is different??? */ +ul.list li.last-child +{ + margin-top: -1px; /* Weird IE7 specific bug needs this set to -1px to render it as 0px!!!! */ +} + +ul.list li.last-child > .before, ul.list li.last-child > .after +{ + bottom: -1px; +} + +/* For IE7 button :before and :after don't work so we have to resort to some JavaScript to add extra classes and tags */ + +a.button .before +{ + position: absolute; + top: 0; + left: -5px; + width: 5px; + height: 30px; + background: url(/itablet/images/button-sprite.png) -18px 0; +} + +a.button .after +{ + position: absolute; + top: 0; + right: -5px; + width: 5px; + height: 30px; + background: url(/itablet/images/button-sprite.png) -13px 0; +} + +a.button:active .before +{ + background-position: -41px 0; +} + +a.button:active .after +{ + background-position: -36px 0; +} + +a.button.back .before +{ + position: absolute; + left: -13px; + width: 13px; + height: 30px; + background-position: 0 0; +} + +a.button.back:active .before +{ + background-position: -23px 0; +} + +a.button.blue .before +{ + background: url(/itablet/images/blue-button-sprite.png) -18px 0; +} + +a.button.blue .after +{ + background: url(/itablet/images/blue-button-sprite.png) -13px 0; +} + +a.button.back.blue .before +{ + background-position: 0 0; +} + +a.button.blue:active .before +{ + background-position: -41px 0; +} + +a.button.blue:active .after +{ + background-position: -36px 0; +} + +a.button.back.blue:active .before +{ + background-position: -23px 0; +} + +/* The horiz-checkbox class hasLayout set so the styles below go back to the correct values. */ +/* Unfortunately simply giving ul.list li hasLayout using zoom: 1 above causes the IE7 margin bug to trigger */ +/* so can't default to that hence the weird values for ul.list li.last-child/ul.list li.last-child > .before */ +/* ul.list li.last-child > .after There may be easier ways, but fixing one IE7 bug seems to cause other ones */ +ul.list li.horiz-checkbox:first-child, ul.list li.textarea:first-child +{ + border-top: 1px groove #fff; +} + +ul.list li.horiz-checkbox.last-child, ul.list li.textarea.last-child +{ + margin-top: 0; +} + +ul.list li.horiz-checkbox:first-child > .fbefore, ul.list li.horiz-checkbox:first-child > .fafter, +ul.list li.textarea:first-child > .fbefore, ul.list li.textarea:first-child > .fafter +{ + top: -1px; +} + +ul.list li.horiz-checkbox.last-child > .before, ul.list li.horiz-checkbox.last-child > .after, +ul.list li.textarea.last-child > .before, ul.list li.textarea.last-child > .after +{ + bottom: -2px; +} + diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/css/itablet-ie8.css b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/css/itablet-ie8.css new file mode 100644 index 0000000000..f2cb4d8126 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/css/itablet-ie8.css @@ -0,0 +1,161 @@ +/* + * + * 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. + * + */ + +/** + * Stylesheet to try and make things look not *too* bad in IE8 and below + */ + +/** + * To handle opacity in IE < 9 we animate progid:DXImageTransform.Microsoft.gradient in itablet.js + * We use a transparent background image rather than background: transparent to block mouse events to the main page. + * progid:DXImageTransform messes with the font so we only use it for animating and use a 50% alpha png as end style. + */ +.popup-window, input, textarea +{ + background: url(/itablet/images/ie/transparent.png) repeat; +} + +.popup-window.smoked +{ + background: url(/itablet/images/ie/smoked.png) repeat; +} + +/** + * Create fake top rounded corners using images. Fortunately we can do this using pure CSS as ie8 supports + * :first-child, :before and :after so we supply the images as content. + */ + +.header:before, .header:after +{ + position: absolute; + content: ""; + top: 0; + left: 0; + width: 5px; + height: 5px; + background: url(/itablet/images/ie/radius-5px-sprite.png); +} + +.header:after +{ + left: auto; + right: 0; + background-position: -5px; +} + +ul.list li:first-child:before, ul.list li:first-child:after, +ul.horiz-checkbox li.horiz-checkbox:first-child:before, ul.list li.horiz-checkbox:first-child label.first-child:before, +ul.horiz-checkbox li.horiz-checkbox:first-child:after, ul.list li.horiz-checkbox:first-child label.last-child:after +{ + position: absolute; + content: ""; + top: -2px; + left: -2px; + width: 10px; + height: 10px; + background: url(/itablet/images/ie/radius-10px-sprite.png); +} + +ul.list li:first-child:after, +ul.horiz-checkbox li.horiz-checkbox:first-child:after, ul.list li.horiz-checkbox:first-child label.last-child:after +{ + left: auto; + right: -2px; + background-position: -10px 0; +} + +/** + * Create fake bottom rounded corners using images. Unfortunately this isn't as easy as the top corners because + * a) ie8 doesn't support :last-child and b) if we have a single list item we've already used :before and :after + * Unfortunately we have to resort to some JavaScript so itablet.js has some ie8 specific code to add a + * "last-child" class and prepend/append div elements with "before" and "after" classes. Fortunately this is + * fairly clean to do using jQuery using $("ul.list li:last-child") to find the item that we need to modify. + */ + +ul.list li.last-child +{ + border-bottom: 2px groove #fff; +} + +ul.list li.last-child > .before, ul.list li.last-child > .after +{ + position: absolute; + bottom: -2px; + left: -2px; + width: 10px; + height: 10px; + z-index: 1; + background: url(/itablet/images/ie/radius-10px-sprite.png) 0 -10px; +} + +ul.list li.last-child > .after +{ + left: auto; + right: -2px; + background-position: -10px -10px; +} + +/** + * For IE < 9 use solid background colour instead of a gradient - no bigee, it was a subtle gradient anyway. + */ +ul li.active, ul li.radio.checked.active /* Highlight in blue with white text */ +{ + background: #035de7; +} + +ul li.active.arrow +{ + background: #035de7 url(/itablet/images/chevron-active.png) no-repeat right; +} + +ul.list li.arrow.radio.active +{ + background: #035de7 url(/itablet/images/blue-chevron.png) no-repeat right; +} + +/** + * We need to apply the fake border radius to the labels in the horiz-checkbox too as child elements aren't clipped. + * Unfortunately IE8 doesn't seem to distinguish between the selectors ul.list li:first-child:before and + * ul.list li.horiz-checkbox:first-child:before when horiz-checkbox is dynamically added (though it does if it's + * placed in the static HTML) this stops the
  • fake radiused border being correctly repositioned + * for horiz-checkboxes. By adding horiz-checkbox to the
      too we can use a more explicit rule in the CSS. + */ + +/* First we remove the original borders that were previously added for ul.list li:first-child:before and after. */ +ul.list li.horiz-checkbox:first-child:before, ul.list li.horiz-checkbox:first-child:after +{ + content: "."; +} + +/* The we re-add them in the correct position using the more explicit rule on ul.horiz-checkbox */ +/* We need to add the rounded borders to the labels too as they are positioned above the li in the stack. */ +ul.horiz-checkbox li.horiz-checkbox:first-child:before, ul.list li.horiz-checkbox:first-child label.first-child:before, +ul.horiz-checkbox li.horiz-checkbox:first-child:after, ul.list li.horiz-checkbox:first-child label.last-child:after +{ + top: -1px; +} + +canvas /* For IE < 9 the radiused borders for the canvas are actually rendered on the canvas. */ +{ + border: 0; +} + + diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/css/itablet-ie9.css b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/css/itablet-ie9.css new file mode 100644 index 0000000000..3ad857559e --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/css/itablet-ie9.css @@ -0,0 +1,45 @@ +/* + * + * 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. + * + */ + +/** + * Stylesheet to sort out IE9 specific quirks....... + */ + +/** + * For some reason radiused borders cause IE9 to eat CPU when scrolling or animating in the popup window so remove them. + */ + +.popup, .popup-container +{ + border-radius: 0; +} + +.popup .scroll-area +{ + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +input +{ + padding: 12px 0 12px 0; /* top right bottom left */ +} + diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/css/itablet.css b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/css/itablet.css new file mode 100644 index 0000000000..6ed4d3d7d3 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/css/itablet.css @@ -0,0 +1,993 @@ +/* + * + * 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. + * + */ + +body +{ + font: 13px/1.5 Helvetica, Arial, 'Liberation Sans', FreeSans, sans-serif; + overflow-x: hidden; /* Hide horizontal scrollbar */ + background: #dddddd; +} + +p +{ + margin: 0; + padding: 11px 0 11px 0; /* top right bottom left */ +} + +.sidebar, .main, .popup-window, .popup-container, .popup, .scroll-area +{ + -moz-user-select: -moz-none; /* Disable selection on fields other than input & textarea. */ + -webkit-user-select: none; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + background: #000; +} + +.sidebar +{ + width: 250px; + border-right: 1px solid #000; + z-index: 2; +} + +.main +{ + left: 251px; +} + +.popup-window +{ + overflow: hidden; + background-color: rgba(0, 0, 0, 0.5); + z-index: 5; +} + +.popup, .popup-container +{ + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; +} + +.popup-container +{ + top: 64px; + bottom: 64px; + left: 20%; + right: 20%; + + overflow-x: hidden; + box-shadow: 10px 10px 10px #777; +} + +.scroll-area +{ + top: 44px; + overflow-x: hidden; + overflow-y: auto; + background: #dddddd; +} + +div .mail +{ + background: #f5f5f5; +} + +.popup .scroll-area +{ + -moz-border-bottom-left-radius: 5px; + -webkit-border-bottom-left-radius: 5px; + border-bottom-left-radius: 5px; + -moz-border-bottom-right-radius: 5px; + -webkit-border-bottom-right-radius: 5px; + border-bottom-right-radius: 5px; +} + +.page +{ + padding: 31px 5% 31px 5%; /* top right bottom left */ +} + +.page h1 +{ + color: #666666; + text-shadow: #fff 0 1px 0; + text-indent: 10px; + font-size: 17px; + + margin: 0; + padding: 16px 0 4px 0; /* top right bottom left */ +} + +.page h1.first +{ + padding: 0 0 4px 0; /* top right bottom left */ +} + +.page p.note +{ + color: #717880; + text-shadow: #fff 0 1px 0; + text-align: center; + font-size: 15px; + font-weight: normal; + + margin: 0; + padding: 4px 0 16px 0; /* top right bottom left */ +} + +.page p.note.nopadding +{ + padding-bottom: 0; +} + +/*------------------------------------------- Header ------------------------------------------------*/ + +div.header +{ + left: 0; + right: 0; + height: 43px; + line-height: 43px; + + -moz-border-top-left-radius: 5px; + -webkit-border-top-left-radius: 5px; + border-top-left-radius: 5px; + -moz-border-top-right-radius: 5px; + -webkit-border-top-right-radius: 5px; + border-top-right-radius: 5px; + border-bottom: 1px solid #858b9b; + + background: #bbb url(/itablet/images/ie/header-gradient.png) repeat-x; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), color-stop(1, #999)); + background: -webkit-linear-gradient(top center, #fff 0%, #999 100%); + background: -moz-linear-gradient(top center, #fff 0%, #999 100%); + background: -ms-linear-gradient(top center, #fff 0%, #999 100%); + background: -o-linear-gradient(top center, #fff 0%, #999 100%); + background: linear-gradient(top center, #fff 0%, #999 100%); +} + +div.header h1 +{ + text-align: center; + font-size: 20px; + font-weight: bold; + color: #717880; + text-shadow: #fff 0 1px 0; + + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + margin: 0 12px 0 12px; /* top right bottom left */ +} + +div.header a.back.button + h1 +{ + margin: 0 88px 0 88px; /* top right bottom left */ +} + +div.header a.cancel.button + h1 +{ + margin: 0 62px 0 62px; /* top right bottom left */ +} + +/*--------------------------------------- Header Buttons --------------------------------------------*/ + +a.button +{ + outline: none; + text-decoration: none; + text-shadow: rgba(0, 0, 0, 0.6) 0 -1px 0; + color: #ddd; + background: #777; + font-size: 12px; + font-weight: bold; + line-height: 28px; + + display: block; + position: absolute; + top: 7px; + left: 12px; + height: 30px; + + background: url(/itablet/images/button-sprite.png) 0px -30px repeat-x; +} + +a.button:before +{ + position: absolute; + content: ""; + top: 0; + left: -5px; + width: 5px; + height: 30px; + background: url(/itablet/images/button-sprite.png) -18px 0 no-repeat; +} + +a.button:after +{ + position: absolute; + content: ""; + top: 0; + right: -5px; + width: 5px; + height: 30px; + background: url(/itablet/images/button-sprite.png) -13px 0 no-repeat; +} + +a.button:active +{ + background-color: #766d69; + background-position: 0 -60px; +} + +a.button:active:before +{ + background-position: -41px 0; +} + +a.button:active:after +{ + background-position: -36px 0; +} + +a.button.back +{ + left: 20px; +} + +a.button.back:before +{ + position: absolute; + left: -13px; + width: 13px; + height: 30px; + background-position: 0 0; +} + +a.button.back:active:before +{ + background-position: -23px 0; +} + +a.button.right +{ + left: auto; + right: 12px; +} + +a.button.blue +{ + color: #fff; + background: url(/itablet/images/blue-button-sprite.png) 0px -30px repeat-x; +} + +a.button.blue:before +{ + background: url(/itablet/images/blue-button-sprite.png) -18px 0 no-repeat; +} + +a.button.blue:after +{ + background: url(/itablet/images/blue-button-sprite.png) -13px 0 no-repeat; +} + +a.button.back.blue:before +{ + background-position: 0 0; +} + +a.button.blue:active +{ + background-color: #6b6f76; + background-position: 0px -60px; +} + +a.button.blue:active:before +{ + background-position: -41px 0; +} + +a.button.blue:active:after +{ + background-position: -36px 0; +} + +a.button.back.blue:active:before +{ + background-position: -23px 0; +} + +/*----------------------------------- Toolbar and Stock Buttons -------------------------------------*/ + +span.toolbar +{ + position: absolute; + overflow: hidden; + top: 7px; + left: auto; + right: 12px; +} + +span.toolbar a +{ + outline: none; + text-decoration: none; + display: block; + float: left; + width: 32px; + height: 32px; +} + +a.delete +{ + background: url(/itablet/images/delete.png) no-repeat left; +} + +a.add +{ + background: url(/itablet/images/add.png) no-repeat left; +} + +a.home +{ + background: url(/itablet/images/home.png) no-repeat left; +} + +a.flag +{ + background: url(/itablet/images/flag.png) no-repeat left; +} + +a.move +{ + background: url(/itablet/images/move.png) no-repeat left; +} + +a.bin +{ + background: url(/itablet/images/bin.png) no-repeat left; +} + +a.action +{ + background: url(/itablet/images/action.png) no-repeat left; +} + +a.write +{ + background: url(/itablet/images/write.png) no-repeat left; +} + +/* Add a "glowing" highlight to toolbar and clickable icons */ +span.toolbar a:active:before, +ul.list li.clickable-icon a:active:before +{ + position: absolute; + content: ""; + top: 0; + left: 0; + width: 32px; + height: 100%; + background: url(/itablet/images/ie/radial-gradient.png) no-repeat left; + background: -webkit-gradient(radial, center center, 0, center center, 16, from(rgba(255,255,255,1)), to(rgba(200,200,200,0))); + background: -webkit-radial-gradient(center center, circle cover, rgba(255,255,255,1), rgba(200,200,200,0) 75%); + background: -moz-radial-gradient(center center, circle cover, rgba(255,255,255,1), rgba(200,200,200,0) 75%); + background: -ms-radial-gradient(center center, circle cover, rgba(255,255,255,1), rgba(200,200,200,0) 75%); + background: -o-radial-gradient(center center, circle cover, rgba(255,255,255,1), rgba(200,200,200,0) 75%); + background: radial-gradient(center center, circle cover, rgba(255,255,255,1), rgba(200,200,200,0) 75%); +} + +span.toolbar a:active:before +{ + position: relative; + float: left; +} + +/*---- this is hidden for desktop browsers it is shown by the styles enabled by the media query -----*/ + +div.header a.menu +{ + display: none; +} + +/*------------------------------- Define the styles for list elements -------------------------------*/ + +ul +{ + list-style: none; + margin: 0; + padding: 0; +} + +ul li +{ + position: relative; + line-height: 42px; + padding: 1px 0 0 0; /* top right bottom left */ + + color: #060606; + font-size: 17px; + font-weight: bold; + border-bottom: 1px solid #ccc; +} + +ul.list li +{ + line-height: 21px; + padding: 11px 0 11px 0; + + background-color: #f7f7f7; + border-left: 2px groove #fff; + border-right: 2px groove #fff; +} + +ul.list li.multiline, ul.list li.icon, ul.list li.clickable-icon +{ + padding: 5px 0 5px 0; +} + +ul.list li:first-child +{ + -moz-border-top-left-radius: 10px; + -webkit-border-top-left-radius: 10px; + border-top-left-radius: 10px; + -moz-border-top-right-radius: 10px; + -webkit-border-top-right-radius: 10px; + border-top-right-radius: 10px; + border-top: 2px groove #fff; +} + +ul.list li:last-child +{ + -moz-border-bottom-left-radius: 10px; + -webkit-border-bottom-left-radius: 10px; + border-bottom-left-radius: 10px; + -moz-border-bottom-right-radius: 10px; + -webkit-border-bottom-right-radius: 10px; + border-bottom-right-radius: 10px; + border-bottom: 2px groove #fff; +} + +ul.mail li +{ + line-height: 24px; + padding-bottom: 3px; +} + +ul li.pop +{ + text-align: center; +} + +ul li.radio.checked /* Adjust text colour on selected radio item */ +{ + color: #385487; + background: #f7f7f7; +} + +ul li.grey /* Grey text generally used to show inactive fields */ +{ + color: #8f8f8f; +} + +ul li.active, ul li.radio.checked.active /* Highlight in blue with white text */ +{ + background: #035de7 url(/itablet/images/ie/active-gradient.png) repeat-x; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0, #058cf7), color-stop(1, #035de7)); + background: -webkit-linear-gradient(top center, #058cf7 0%, #035de7 100%); + background: -moz-linear-gradient(top center, #058cf7 0%, #035de7 100%); + background: -ms-linear-gradient(top center, #058cf7 0%, #035de7 100%); + background: -o-linear-gradient(top center, #058cf7 0%, #035de7 100%); + background: linear-gradient(top center, #058cf7 0%, #035de7 100%); + color: #fff; +} + +/*------------------------------ Define the styles for anchor elements ------------------------------*/ + +input, textarea, a +{ + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); /* Hide the tap highlighting by making it transparent. */ +} + +a +{ + -webkit-touch-callout: none; + cursor: default; /* Disable the "hand" cursor over anchors. */ +} + +ul li a +{ + outline: none; + text-decoration: none; /* Hide the underline */ + color: inherit; /* Get the text colour from the item containing the anchor. */ + display: block; /* Treat anchor as a block element - needed to correctly display image. */ +} + + +ul.list li a, label, ul.list li.arrow a, ul.mail li a, ul.list li.multiline a div +{ + position: relative; + padding: 0 11px 0 11px; /* top right bottom left */ + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +ul.list li.arrow a, ul.mail li.arrow a +{ + padding-right: 30px; /* When a list item has a chevron we need to increase anchor right padding past it. */ +} + +ul li.icon a, ul li.clickable-icon a +{ + margin-left: 5px; + text-indent: 40px; +} + +ul.list li.icon a, ul.list li.clickable-icon a +{ + margin-left: 8px; + text-indent: 30px; + + /* For icon/clickable-icon when not a multiline item we need to increase anchor top and bottom padding so + that we don't end up clipping the top and bottom off the icon. */ + padding-top: 5px; + padding-bottom: 5px; +} + +ul.mail li a +{ + left: 8px; + text-indent: 18px; /* Indents the text to the right of the image. */ +} + +ul.mail li.unread a +{ + background: url(/itablet/images/blueball.png) no-repeat left; +} + + +ul li.arrow +{ + text-align: left; /* This resets alignment in case class="pop arrow" is used because pop centre aligns. */ + background: url(/itablet/images/chevron.png) no-repeat right; +} + +ul.list li.arrow +{ + background: #f7f7f7 url(/itablet/images/chevron.png) no-repeat right; +} + +ul li.active.arrow +{ + background: url(/itablet/images/chevron-active.png) no-repeat right, #035de7 url(/itablet/images/ie/active-gradient.png) repeat-x; + background: url(/itablet/images/chevron-active.png) no-repeat right, -webkit-gradient(linear, left top, left bottom, color-stop(0, #058cf7), color-stop(1, #035de7)); + background: url(/itablet/images/chevron-active.png) no-repeat right, -webkit-linear-gradient(top center, #058cf7 0%, #035de7 100%); + background: url(/itablet/images/chevron-active.png) no-repeat right, -moz-linear-gradient(top center, #058cf7 0%, #035de7 100%); + background: url(/itablet/images/chevron-active.png) no-repeat right, -ms-linear-gradient(top center, #058cf7 0%, #035de7 100%); + background: url(/itablet/images/chevron-active.png) no-repeat right, -o-linear-gradient(top center, #058cf7 0%, #035de7 100%); + background: url(/itablet/images/chevron-active.png) no-repeat right, linear-gradient(top center, #058cf7 0%, #035de7 100%); +} + +/*--------------------------- Define the styles for right justified text ----------------------------*/ + +ul li a p +{ + position: absolute; + top: 0; + right: 11px; + + padding: 0; + width: 50%; + text-align: right; + text-indent: 0; + + color: #464646; + font-weight: normal; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +ul li a p.fullwidth +{ + width: 100%; +} + +ul li a p.date +{ + color: #3078da; + font-size: 13px; +} + +ul li.arrow a p +{ + right: 30px; +} + +/*----------------------------- Define the styles for multiline items -------------------------------*/ + +ul.list li.multiline a +{ + padding-top: 0px; + padding-bottom: 0px; +} + +ul.list li.multiline a div +{ + padding: 0; +} + +ul.list li.multiline a div p +{ + right: 0; +} + +ul li a p.sub, ul li a p.title +{ + position: relative; + top: 0; + left: 0; + + width: auto; + text-align: left; + + color: gray; + font-size: 14px; + line-height: 16px; +} + +ul li a p.title +{ + color: #060606; +} + +ul.list li.icon a p.sub, ul.list li.clickable-icon a p.sub, +ul.list li.icon a p.title, ul.list li.clickable-icon a p.title +{ + margin-left: 30px; +} + +ul.mail li a p.sub, ul.mail li a p.title +{ + margin-left: 18px; +} + +ul.mail li a p.sub +{ + font-size: 12px; + height: 33px; + white-space: normal; +} + +ul li.active a p, ul.mail li.active a p +{ + color: #fff; +} + +/*------------------------------- Define the styles for input fields --------------------------------*/ + +label +{ + display: block; +} + +label.input /* Adding the input class to a label will allow overflow truncation to work */ +{ + width: 115px; +} + +input, .placeholder +{ + position: absolute; + top: 0; + left: 115px; + right: 11px; + padding: 9px 0 9px 0; /* top right bottom left */ + border: 0; + font: 17px Helvetica, Arial, 'Liberation Sans', FreeSans, sans-serif; +} + + +.placeholder.textarea +{ + left: 11px; + font-size: 12px +} + +.placeholder /* Used to fake HTML5 input placeholders in browsers that don't have native support. */ +{ + padding: 12px 0 12px 0; /* top right bottom left */ + color: #9f9f9f; +} + +input.error, textarea.error +{ + -webkit-appearance: none; + moz-box-shadow: 0 0 4px red; + webkit-box-shadow: 0 0 4px red; + box-shadow: 0 0 4px red; +} + +textarea +{ + position: relative; /* Needs to be set for fake placeholders to work properly - the z-index needs to be 1 */ + + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + + resize: vertical; + + width: 100%; + height: 100px; + + border: 0; + padding: 0 11px 0 11px; + + overflow: auto; +} + +/* + For fake placeholders to work we want to ensure the input/textarea is in the foreground and receiving events, + but we also need them to be transparent so we can see the placeholder through them. +*/ +input, textarea +{ + background: rgba(0, 0, 0, 0); + z-index: 1; +} + +/*---------------------------- Define the styles for radio button widget ----------------------------*/ + +ul.list li.radio label +{ + padding: 0 32px 0 11px; /* top right bottom left */ +} + +ul.list li.radio.checked label +{ + background: url(/itablet/images/tick.png) no-repeat right; +} + +ul.list li.radio.active.checked label +{ + background: url(/itablet/images/tick-active.png) no-repeat right; +} + +ul.list li.arrow.radio +{ + background: #f7f7f7 url(/itablet/images/blue-chevron.png) no-repeat right; +} + +ul.list li.arrow.radio.active +{ + background: url(/itablet/images/blue-chevron.png) no-repeat right, #035de7 url(/itablet/images/ie/active-gradient.png) repeat-x; + background: url(/itablet/images/blue-chevron.png) no-repeat right, -webkit-gradient(linear, left top, left bottom, color-stop(0, #058cf7), color-stop(1, #035de7)); + background: url(/itablet/images/blue-chevron.png) no-repeat right, -webkit-linear-gradient(top center, #058cf7 0%, #035de7 100%); + background: url(/itablet/images/blue-chevron.png) no-repeat right, -moz-linear-gradient(top center, #058cf7 0%, #035de7 100%); + background: url(/itablet/images/blue-chevron.png) no-repeat right, -ms-linear-gradient(top center, #058cf7 0%, #035de7 100%); + background: url(/itablet/images/blue-chevron.png) no-repeat right, -o-linear-gradient(top center, #058cf7 0%, #035de7 100%); + background: url(/itablet/images/blue-chevron.png) no-repeat right, linear-gradient(top center, #058cf7 0%, #035de7 100%); +} + +ul.list li.arrow.radio label +{ + padding: 0 38px 0 32px; /* top right bottom left */ +} + +ul.list li.arrow.radio.checked label +{ + background: url(/itablet/images/tick.png) no-repeat 11px; +} + +ul.list li.arrow.radio.active.checked label +{ + background: url(/itablet/images/tick-active.png) no-repeat 11px; +} + +/*------------------------------ Define the styles for checkbox widget ------------------------------*/ + +div.checkbox +{ + position: absolute; + overflow: hidden; /* clip the child div to the size of this one */ + top: 7px; + right: 11px; + width: 77px; + height: 27px; +} + +div.mask +{ + position: absolute; + width: 100%; + height: 100%; + z-index: 1; + background: url(/itablet/images/mask.png) no-repeat; +} + +div.onoff +{ + position: absolute; + top: 0; + left: -50px; + width: 127px; + height: 27px; + background: url(/itablet/images/on_off.png) no-repeat; +} + +/*------------------------- Define the styles for horizontal checkbox widget ------------------------*/ + +ul.list li.horiz-checkbox +{ + display: block; + padding: 0; + line-height: 40px; + background: url(/itablet/images/toggle-off.png) repeat-x; +} + +ul.list li.horiz-checkbox label +{ + color: #717880; + text-shadow: #fff 0 1px 0; + + display: inline-block; + vertical-align: bottom; + text-align: center; + padding: 0; + width: 100%; + background: url(/itablet/images/toggle-off.png) repeat-x; +} + +ul.list li.horiz-checkbox label span +{ + position: absolute; + top: 0; + right: 0; + height: 100%; + border-right: 1px solid #828278; +} + +/* Use toggle-on class not checked to avoid IE6 confusing with a radio button as IE6 doesn't support multiple classes. */ +ul.list li.horiz-checkbox.toggle-on, ul.list li.horiz-checkbox label.checked +{ + color: #fff; + text-shadow: 0 0 0 #000; /* passing all the arguments will reset it to nothing */ + background: url(/itablet/images/toggle-on.png) repeat-x; +} + +ul.list li.horiz-checkbox label.checked span +{ + border-right: 0; + width: 5px; + height: 40px; + background: url(/itablet/images/toggle-on-border.png) no-repeat right; +} + +ul.list li.horiz-checkbox:first-child +{ + border-top: 1px groove #fff; +} + +/* Need to apply border radius to the labels in the horiz-checkbox too as child elements aren't clipped. */ + +ul.list li.horiz-checkbox:first-child label.first-child +{ + -moz-border-top-left-radius: 10px; + -webkit-border-top-left-radius: 10px; + border-top-left-radius: 10px; +} + +ul.list li.horiz-checkbox:first-child label.last-child +{ + -moz-border-top-right-radius: 10px; + -webkit-border-top-right-radius: 10px; + border-top-right-radius: 10px; +} + +ul.list li.horiz-checkbox:last-child label.first-child +{ + -moz-border-bottom-left-radius: 10px; + -webkit-border-bottom-left-radius: 10px; + border-bottom-left-radius: 10px; +} + +ul.list li.horiz-checkbox:last-child label.last-child +{ + -moz-border-bottom-right-radius: 10px; + -webkit-border-bottom-right-radius: 10px; + border-bottom-right-radius: 10px; +} + +/*-------------------------------- Style for the HTML5 drawing canvas -------------------------------*/ +/* N.B. By the standard, CSS does not size the canvas coordinate system, it scales the content. This */ +/* means that we can't style canvas width and height, these must be set via Javascript if we wish to */ +/* use relative sizes. */ +/*---------------------------------------------------------------------------------------------------*/ +canvas +{ + border: 2px groove #fff; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + border-radius: 10px; + background-color: #f6f6f6; + display: block; +} + + + + +/*---------------------------- Detect mobile sized screen via media query ---------------------------*/ +/* +@media only screen and (max-width: 480px) +*/ + +@media only screen and (max-device-width: 480px), + only screen and (min-device-width: 640px) and (max-device-width: 1136px) and (-webkit-min-device-pixel-ratio: 2) +{ + .sidebar + { + width: 100%; + } + + .main /* We check the .main left value in JavaScript if we want to detect mobile or non-mobile */ + { + left: 0; + } + + .popup-container + { /* For some reason the Android VM I'm using isn't respecting top & bottom of zero here iPhone is fine!! */ + top: 0; + bottom: 0; + left: 0; + right: 0; + } + + /* For mobile devices display the menu back buttons */ + div.header a.menu + { + display: block; + } + + .sidebar ul li + { + background: url(/itablet/images/chevron.png) no-repeat right; + } + + .sidebar ul li.active + { + background: url(/itablet/images/chevron-active.png) no-repeat right, #035de7 url(/itablet/images/ie/active-gradient.png) repeat-x; + background: url(/itablet/images/chevron-active.png) no-repeat right, -webkit-gradient(linear, left top, left bottom, color-stop(0, #058cf7), color-stop(1, #035de7)); + background: url(/itablet/images/chevron-active.png) no-repeat right, -webkit-linear-gradient(top center, #058cf7 0%, #035de7 100%); + background: url(/itablet/images/chevron-active.png) no-repeat right, -moz-linear-gradient(top center, #058cf7 0%, #035de7 100%); + background: url(/itablet/images/chevron-active.png) no-repeat right, -ms-linear-gradient(top center, #058cf7 0%, #035de7 100%); + background: url(/itablet/images/chevron-active.png) no-repeat right, -o-linear-gradient(top center, #058cf7 0%, #035de7 100%); + background: url(/itablet/images/chevron-active.png) no-repeat right, linear-gradient(top center, #058cf7 0%, #035de7 100%); + } +} + + diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/action.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/action.png new file mode 100644 index 0000000000..af5e0378b0 Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/action.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/add.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/add.png new file mode 100644 index 0000000000..700d9f100e Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/add.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/bin.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/bin.png new file mode 100644 index 0000000000..90923898af Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/bin.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/blue-button-sprite.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/blue-button-sprite.png new file mode 100644 index 0000000000..9fedd12867 Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/blue-button-sprite.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/blue-chevron.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/blue-chevron.png new file mode 100644 index 0000000000..41e539fc10 Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/blue-chevron.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/blueball.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/blueball.png new file mode 100644 index 0000000000..a7c4c68349 Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/blueball.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/button-sprite.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/button-sprite.png new file mode 100644 index 0000000000..c15b4ea217 Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/button-sprite.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/chevron-active.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/chevron-active.png new file mode 100644 index 0000000000..86832ebc7b Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/chevron-active.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/chevron.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/chevron.png new file mode 100644 index 0000000000..6421a16762 Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/chevron.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/delete.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/delete.png new file mode 100644 index 0000000000..74b1ff3ea1 Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/delete.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/flag.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/flag.png new file mode 100644 index 0000000000..0baf2a9986 Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/flag.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/home.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/home.png new file mode 100644 index 0000000000..4f359778a2 Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/home.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/active-gradient.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/active-gradient.png new file mode 100644 index 0000000000..6cd4404965 Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/active-gradient.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/blue-button-sprite.gif b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/blue-button-sprite.gif new file mode 100644 index 0000000000..8edaddedd8 Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/blue-button-sprite.gif differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/blue-chevron.gif b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/blue-chevron.gif new file mode 100644 index 0000000000..bb8f3d51d6 Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/blue-chevron.gif differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/button-sprite.gif b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/button-sprite.gif new file mode 100644 index 0000000000..5e920511d1 Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/button-sprite.gif differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/chevron-active.gif b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/chevron-active.gif new file mode 100644 index 0000000000..f37329bb3b Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/chevron-active.gif differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/chevron.gif b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/chevron.gif new file mode 100644 index 0000000000..dce238269a Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/chevron.gif differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/header-gradient.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/header-gradient.png new file mode 100644 index 0000000000..d4e9a6c9f0 Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/header-gradient.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/radial-gradient.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/radial-gradient.png new file mode 100644 index 0000000000..d9ab361add Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/radial-gradient.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/radius-10px-sprite.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/radius-10px-sprite.png new file mode 100644 index 0000000000..ca91770e2f Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/radius-10px-sprite.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/radius-5px-sprite.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/radius-5px-sprite.png new file mode 100644 index 0000000000..c00e2194db Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/radius-5px-sprite.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/red6.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/red6.png new file mode 100644 index 0000000000..c1c0ba1db9 Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/red6.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/smoked.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/smoked.png new file mode 100644 index 0000000000..ea6b7c587f Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/smoked.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/tick-active.gif b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/tick-active.gif new file mode 100644 index 0000000000..629f6f1eca Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/tick-active.gif differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/tick.gif b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/tick.gif new file mode 100644 index 0000000000..fdbdc1eec2 Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/tick.gif differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/transparent.gif b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/transparent.gif new file mode 100755 index 0000000000..c5b2954ac6 Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/transparent.gif differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/transparent.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/transparent.png new file mode 100644 index 0000000000..8ac7f64f1a Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/ie/transparent.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/mask.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/mask.png new file mode 100644 index 0000000000..f8be0e6f36 Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/mask.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/move.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/move.png new file mode 100644 index 0000000000..80f77eeb72 Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/move.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/on_off.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/on_off.png new file mode 100644 index 0000000000..89243bf27d Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/on_off.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/tick-active.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/tick-active.png new file mode 100644 index 0000000000..16fa8ca71b Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/tick-active.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/tick.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/tick.png new file mode 100644 index 0000000000..783b60fe9a Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/tick.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/toggle-off.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/toggle-off.png new file mode 100644 index 0000000000..474ad3cd63 Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/toggle-off.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/toggle-on-border.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/toggle-on-border.png new file mode 100644 index 0000000000..7680cd7d0a Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/toggle-on-border.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/toggle-on.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/toggle-on.png new file mode 100644 index 0000000000..275aa42114 Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/toggle-on.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/write.png b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/write.png new file mode 100644 index 0000000000..b7329dbf9f Binary files /dev/null and b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/images/write.png differ diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/scripts/LICENCE b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/scripts/LICENCE new file mode 100644 index 0000000000..7d95218a1e --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/scripts/LICENCE @@ -0,0 +1,48 @@ + +/* + * jQuery JavaScript Library v1.7.1 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Mon Nov 21 21:11:03 2011 -0500 + */ + + +/* + * iscroll.js + * + * Copyright (c) 2011 Matteo Spinelli, http://cubiq.org/ + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + + + diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/scripts/iscroll.js b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/scripts/iscroll.js new file mode 100644 index 0000000000..46330e51f7 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/scripts/iscroll.js @@ -0,0 +1,1117 @@ +/*! + * iScroll v4.2 ~ Copyright (c) 2012 Matteo Spinelli, http://cubiq.org + * Released under MIT license, http://cubiq.org/license + */ +(function(window, doc){ +var m = Math, + dummyStyle = doc.createElement('div').style, + vendor = (function () { + var vendors = 't,webkitT,MozT,msT,OT'.split(','), + t, + i = 0, + l = vendors.length; + + for ( ; i < l; i++ ) { + t = vendors[i] + 'ransform'; + if ( t in dummyStyle ) { + return vendors[i].substr(0, vendors[i].length - 1); + } + } + + return false; + })(), + cssVendor = vendor ? '-' + vendor.toLowerCase() + '-' : '', + + // Style properties + transform = prefixStyle('transform'), + transitionProperty = prefixStyle('transitionProperty'), + transitionDuration = prefixStyle('transitionDuration'), + transformOrigin = prefixStyle('transformOrigin'), + transitionTimingFunction = prefixStyle('transitionTimingFunction'), + transitionDelay = prefixStyle('transitionDelay'), + + // Browser capabilities + isAndroid = (/android/gi).test(navigator.appVersion), + isIDevice = (/iphone|ipad/gi).test(navigator.appVersion), + isTouchPad = (/hp-tablet/gi).test(navigator.appVersion), + + has3d = prefixStyle('perspective') in dummyStyle, + hasTouch = 'ontouchstart' in window && !isTouchPad, + hasTransform = !!vendor, + hasTransitionEnd = prefixStyle('transition') in dummyStyle, + + RESIZE_EV = 'onorientationchange' in window ? 'orientationchange' : 'resize', + START_EV = hasTouch ? 'touchstart' : 'mousedown', + MOVE_EV = hasTouch ? 'touchmove' : 'mousemove', + END_EV = hasTouch ? 'touchend' : 'mouseup', + CANCEL_EV = hasTouch ? 'touchcancel' : 'mouseup', + WHEEL_EV = vendor == 'Moz' ? 'DOMMouseScroll' : 'mousewheel', + TRNEND_EV = (function () { + if ( vendor === false ) return false; + + var transitionEnd = { + '' : 'transitionend', + 'webkit' : 'webkitTransitionEnd', + 'Moz' : 'transitionend', + 'O' : 'oTransitionEnd', + 'ms' : 'MSTransitionEnd' + }; + + return transitionEnd[vendor]; + })(), + + nextFrame = (function() { + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { return setTimeout(callback, 1); }; + })(), + cancelFrame = (function () { + return window.cancelRequestAnimationFrame || + window.webkitCancelAnimationFrame || + window.webkitCancelRequestAnimationFrame || + window.mozCancelRequestAnimationFrame || + window.oCancelRequestAnimationFrame || + window.msCancelRequestAnimationFrame || + clearTimeout; + })(), + + // Helpers + translateZ = has3d ? ' translateZ(0)' : '', + + // Constructor + iScroll = function (el, options) { + var that = this, + i; + + that.wrapper = typeof el == 'object' ? el : doc.getElementById(el); + that.wrapper.style.overflow = 'hidden'; + that.scroller = that.wrapper.children[0]; + + // Default options + that.options = { + hScroll: true, + vScroll: true, + x: 0, + y: 0, + bounce: true, + bounceLock: false, + momentum: true, + lockDirection: true, + useTransform: true, + useTransition: false, + topOffset: 0, + checkDOMChanges: false, // Experimental + handleClick: true, + + // Scrollbar + hScrollbar: true, + vScrollbar: true, + fixedScrollbar: isAndroid, + hideScrollbar: isIDevice, + fadeScrollbar: isIDevice && has3d, + scrollbarClass: '', + + // Zoom + zoom: false, + zoomMin: 1, + zoomMax: 4, + doubleTapZoom: 2, + wheelAction: 'scroll', + + // Snap + snap: false, + snapThreshold: 1, + + // Events + onRefresh: null, + onBeforeScrollStart: function (e) { e.preventDefault(); }, + onScrollStart: null, + onBeforeScrollMove: null, + onScrollMove: null, + onBeforeScrollEnd: null, + onScrollEnd: null, + onTouchEnd: null, + onDestroy: null, + onZoomStart: null, + onZoom: null, + onZoomEnd: null + }; + + // User defined options + for (i in options) that.options[i] = options[i]; + + // Set starting position + that.x = that.options.x; + that.y = that.options.y; + + // Normalize options + that.options.useTransform = hasTransform && that.options.useTransform; + that.options.hScrollbar = that.options.hScroll && that.options.hScrollbar; + that.options.vScrollbar = that.options.vScroll && that.options.vScrollbar; + that.options.zoom = that.options.useTransform && that.options.zoom; + that.options.useTransition = hasTransitionEnd && that.options.useTransition; + + // Helpers FIX ANDROID BUG! + // translate3d and scale doesn't work together! + // Ignoring 3d ONLY WHEN YOU SET that.options.zoom + if ( that.options.zoom && isAndroid ){ + translateZ = ''; + } + + // Set some default styles + that.scroller.style[transitionProperty] = that.options.useTransform ? cssVendor + 'transform' : 'top left'; + that.scroller.style[transitionDuration] = '0'; + that.scroller.style[transformOrigin] = '0 0'; + if (that.options.useTransition) that.scroller.style[transitionTimingFunction] = 'cubic-bezier(0.33,0.66,0.66,1)'; + + if (that.options.useTransform) that.scroller.style[transform] = 'translate(' + that.x + 'px,' + that.y + 'px)' + translateZ; + else that.scroller.style.cssText += ';position:absolute;top:' + that.y + 'px;left:' + that.x + 'px'; + + if (that.options.useTransition) that.options.fixedScrollbar = true; + + that.refresh(); + + that._bind(RESIZE_EV, window); + that._bind(START_EV); + if (!hasTouch) { + that._bind('mouseout', that.wrapper); + if (that.options.wheelAction != 'none') + that._bind(WHEEL_EV); + } + + if (that.options.checkDOMChanges) that.checkDOMTime = setInterval(function () { + that._checkDOMChanges(); + }, 500); + }; + +// Prototype +iScroll.prototype = { + enabled: true, + x: 0, + y: 0, + steps: [], + scale: 1, + currPageX: 0, currPageY: 0, + pagesX: [], pagesY: [], + aniTime: null, + wheelZoomCount: 0, + + handleEvent: function (e) { + var that = this; + switch(e.type) { + case START_EV: + if (!hasTouch && e.button !== 0) return; + that._start(e); + break; + case MOVE_EV: that._move(e); break; + case END_EV: + case CANCEL_EV: that._end(e); break; + case RESIZE_EV: that._resize(); break; + case WHEEL_EV: that._wheel(e); break; + case 'mouseout': that._mouseout(e); break; + case TRNEND_EV: that._transitionEnd(e); break; + } + }, + + _checkDOMChanges: function () { + if (this.moved || this.zoomed || this.animating || + (this.scrollerW == this.scroller.offsetWidth * this.scale && this.scrollerH == this.scroller.offsetHeight * this.scale)) return; + + this.refresh(); + }, + + _scrollbar: function (dir) { + var that = this, + bar; + + if (!that[dir + 'Scrollbar']) { + if (that[dir + 'ScrollbarWrapper']) { + if (hasTransform) that[dir + 'ScrollbarIndicator'].style[transform] = ''; + that[dir + 'ScrollbarWrapper'].parentNode.removeChild(that[dir + 'ScrollbarWrapper']); + that[dir + 'ScrollbarWrapper'] = null; + that[dir + 'ScrollbarIndicator'] = null; + } + + return; + } + + if (!that[dir + 'ScrollbarWrapper']) { + // Create the scrollbar wrapper + bar = doc.createElement('div'); + + if (that.options.scrollbarClass) bar.className = that.options.scrollbarClass + dir.toUpperCase(); + else bar.style.cssText = 'position:absolute;z-index:100;' + (dir == 'h' ? 'height:7px;bottom:1px;left:2px;right:' + (that.vScrollbar ? '7' : '2') + 'px' : 'width:7px;bottom:' + (that.hScrollbar ? '7' : '2') + 'px;top:2px;right:1px'); + + bar.style.cssText += ';pointer-events:none;' + cssVendor + 'transition-property:opacity;' + cssVendor + 'transition-duration:' + (that.options.fadeScrollbar ? '350ms' : '0') + ';overflow:hidden;opacity:' + (that.options.hideScrollbar ? '0' : '1'); + + that.wrapper.appendChild(bar); + that[dir + 'ScrollbarWrapper'] = bar; + + // Create the scrollbar indicator + bar = doc.createElement('div'); + if (!that.options.scrollbarClass) { + bar.style.cssText = 'position:absolute;z-index:100;background:rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.9);' + cssVendor + 'background-clip:padding-box;' + cssVendor + 'box-sizing:border-box;' + (dir == 'h' ? 'height:100%' : 'width:100%') + ';' + cssVendor + 'border-radius:3px;border-radius:3px'; + } + bar.style.cssText += ';pointer-events:none;' + cssVendor + 'transition-property:' + cssVendor + 'transform;' + cssVendor + 'transition-timing-function:cubic-bezier(0.33,0.66,0.66,1);' + cssVendor + 'transition-duration:0;' + cssVendor + 'transform: translate(0,0)' + translateZ; + if (that.options.useTransition) bar.style.cssText += ';' + cssVendor + 'transition-timing-function:cubic-bezier(0.33,0.66,0.66,1)'; + + that[dir + 'ScrollbarWrapper'].appendChild(bar); + that[dir + 'ScrollbarIndicator'] = bar; + } + + if (dir == 'h') { + that.hScrollbarSize = that.hScrollbarWrapper.clientWidth; + that.hScrollbarIndicatorSize = m.max(m.round(that.hScrollbarSize * that.hScrollbarSize / that.scrollerW), 8); + that.hScrollbarIndicator.style.width = that.hScrollbarIndicatorSize + 'px'; + that.hScrollbarMaxScroll = that.hScrollbarSize - that.hScrollbarIndicatorSize; + that.hScrollbarProp = that.hScrollbarMaxScroll / that.maxScrollX; + } else { + that.vScrollbarSize = that.vScrollbarWrapper.clientHeight; + that.vScrollbarIndicatorSize = m.max(m.round(that.vScrollbarSize * that.vScrollbarSize / that.scrollerH), 8); + that.vScrollbarIndicator.style.height = that.vScrollbarIndicatorSize + 'px'; + that.vScrollbarMaxScroll = that.vScrollbarSize - that.vScrollbarIndicatorSize; + that.vScrollbarProp = that.vScrollbarMaxScroll / that.maxScrollY; + } + + // Reset position + that._scrollbarPos(dir, true); + }, + + _resize: function () { + var that = this; + setTimeout(function () { that.refresh(); }, isAndroid ? 200 : 0); + }, + + _pos: function (x, y) { + if (this.zoomed) return; + + x = this.hScroll ? x : 0; + y = this.vScroll ? y : 0; + + if (this.options.useTransform) { + this.scroller.style[transform] = 'translate(' + x + 'px,' + y + 'px) scale(' + this.scale + ')' + translateZ; + } else { + x = m.round(x); + y = m.round(y); + this.scroller.style.left = x + 'px'; + this.scroller.style.top = y + 'px'; + } + + this.x = x; + this.y = y; + + this._scrollbarPos('h'); + this._scrollbarPos('v'); + }, + + _scrollbarPos: function (dir, hidden) { + var that = this, + pos = dir == 'h' ? that.x : that.y, + size; + + if (!that[dir + 'Scrollbar']) return; + + pos = that[dir + 'ScrollbarProp'] * pos; + + if (pos < 0) { + if (!that.options.fixedScrollbar) { + size = that[dir + 'ScrollbarIndicatorSize'] + m.round(pos * 3); + if (size < 8) size = 8; + that[dir + 'ScrollbarIndicator'].style[dir == 'h' ? 'width' : 'height'] = size + 'px'; + } + pos = 0; + } else if (pos > that[dir + 'ScrollbarMaxScroll']) { + if (!that.options.fixedScrollbar) { + size = that[dir + 'ScrollbarIndicatorSize'] - m.round((pos - that[dir + 'ScrollbarMaxScroll']) * 3); + if (size < 8) size = 8; + that[dir + 'ScrollbarIndicator'].style[dir == 'h' ? 'width' : 'height'] = size + 'px'; + pos = that[dir + 'ScrollbarMaxScroll'] + (that[dir + 'ScrollbarIndicatorSize'] - size); + } else { + pos = that[dir + 'ScrollbarMaxScroll']; + } + } + + that[dir + 'ScrollbarWrapper'].style[transitionDelay] = '0'; + that[dir + 'ScrollbarWrapper'].style.opacity = hidden && that.options.hideScrollbar ? '0' : '1'; + that[dir + 'ScrollbarIndicator'].style[transform] = 'translate(' + (dir == 'h' ? pos + 'px,0)' : '0,' + pos + 'px)') + translateZ; + }, + + _start: function (e) { + var that = this, + point = hasTouch ? e.touches[0] : e, + matrix, x, y, + c1, c2; + + if (!that.enabled) return; + + if (that.options.onBeforeScrollStart) that.options.onBeforeScrollStart.call(that, e); + + if (that.options.useTransition || that.options.zoom) that._transitionTime(0); + + that.moved = false; + that.animating = false; + that.zoomed = false; + that.distX = 0; + that.distY = 0; + that.absDistX = 0; + that.absDistY = 0; + that.dirX = 0; + that.dirY = 0; + + // Gesture start + if (that.options.zoom && hasTouch && e.touches.length > 1) { + c1 = m.abs(e.touches[0].pageX-e.touches[1].pageX); + c2 = m.abs(e.touches[0].pageY-e.touches[1].pageY); + that.touchesDistStart = m.sqrt(c1 * c1 + c2 * c2); + + that.originX = m.abs(e.touches[0].pageX + e.touches[1].pageX - that.wrapperOffsetLeft * 2) / 2 - that.x; + that.originY = m.abs(e.touches[0].pageY + e.touches[1].pageY - that.wrapperOffsetTop * 2) / 2 - that.y; + + if (that.options.onZoomStart) that.options.onZoomStart.call(that, e); + } + + if (that.options.momentum) { + if (that.options.useTransform) { + // Very lame general purpose alternative to CSSMatrix + matrix = getComputedStyle(that.scroller, null)[transform].replace(/[^0-9\-.,]/g, '').split(','); + x = matrix[4] * 1; + y = matrix[5] * 1; + } else { + x = getComputedStyle(that.scroller, null).left.replace(/[^0-9-]/g, '') * 1; + y = getComputedStyle(that.scroller, null).top.replace(/[^0-9-]/g, '') * 1; + } + + if (x != that.x || y != that.y) { + if (that.options.useTransition) that._unbind(TRNEND_EV); + else cancelFrame(that.aniTime); + that.steps = []; + that._pos(x, y); + } + } + + that.absStartX = that.x; // Needed by snap threshold + that.absStartY = that.y; + + that.startX = that.x; + that.startY = that.y; + that.pointX = point.pageX; + that.pointY = point.pageY; + + that.startTime = e.timeStamp || Date.now(); + + if (that.options.onScrollStart) that.options.onScrollStart.call(that, e); + + that._bind(MOVE_EV); + that._bind(END_EV); + that._bind(CANCEL_EV); + }, + + _move: function (e) { + var that = this, + point = hasTouch ? e.touches[0] : e, + deltaX = point.pageX - that.pointX, + deltaY = point.pageY - that.pointY, + newX = that.x + deltaX, + newY = that.y + deltaY, + c1, c2, scale, + timestamp = e.timeStamp || Date.now(); + + if (that.options.onBeforeScrollMove) that.options.onBeforeScrollMove.call(that, e); + + // Zoom + if (that.options.zoom && hasTouch && e.touches.length > 1) { + c1 = m.abs(e.touches[0].pageX - e.touches[1].pageX); + c2 = m.abs(e.touches[0].pageY - e.touches[1].pageY); + that.touchesDist = m.sqrt(c1*c1+c2*c2); + + that.zoomed = true; + + scale = 1 / that.touchesDistStart * that.touchesDist * this.scale; + + if (scale < that.options.zoomMin) scale = 0.5 * that.options.zoomMin * Math.pow(2.0, scale / that.options.zoomMin); + else if (scale > that.options.zoomMax) scale = 2.0 * that.options.zoomMax * Math.pow(0.5, that.options.zoomMax / scale); + + that.lastScale = scale / this.scale; + + newX = this.originX - this.originX * that.lastScale + this.x, + newY = this.originY - this.originY * that.lastScale + this.y; + + this.scroller.style[transform] = 'translate(' + newX + 'px,' + newY + 'px) scale(' + scale + ')' + translateZ; + + if (that.options.onZoom) that.options.onZoom.call(that, e); + return; + } + + that.pointX = point.pageX; + that.pointY = point.pageY; + + // Slow down if outside of the boundaries + if (newX > 0 || newX < that.maxScrollX) { + newX = that.options.bounce ? that.x + (deltaX / 2) : newX >= 0 || that.maxScrollX >= 0 ? 0 : that.maxScrollX; + } + if (newY > that.minScrollY || newY < that.maxScrollY) { + newY = that.options.bounce ? that.y + (deltaY / 2) : newY >= that.minScrollY || that.maxScrollY >= 0 ? that.minScrollY : that.maxScrollY; + } + + that.distX += deltaX; + that.distY += deltaY; + that.absDistX = m.abs(that.distX); + that.absDistY = m.abs(that.distY); + + if (that.absDistX < 6 && that.absDistY < 6) { + return; + } + + // Lock direction + if (that.options.lockDirection) { + if (that.absDistX > that.absDistY + 5) { + newY = that.y; + deltaY = 0; + } else if (that.absDistY > that.absDistX + 5) { + newX = that.x; + deltaX = 0; + } + } + + that.moved = true; + that._pos(newX, newY); + that.dirX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0; + that.dirY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0; + + if (timestamp - that.startTime > 300) { + that.startTime = timestamp; + that.startX = that.x; + that.startY = that.y; + } + + if (that.options.onScrollMove) that.options.onScrollMove.call(that, e); + }, + + _end: function (e) { + if (hasTouch && e.touches.length !== 0) return; + + var that = this, + point = hasTouch ? e.changedTouches[0] : e, + target, ev, + momentumX = { dist:0, time:0 }, + momentumY = { dist:0, time:0 }, + duration = (e.timeStamp || Date.now()) - that.startTime, + newPosX = that.x, + newPosY = that.y, + distX, distY, + newDuration, + snap, + scale; + + that._unbind(MOVE_EV); + that._unbind(END_EV); + that._unbind(CANCEL_EV); + + if (that.options.onBeforeScrollEnd) that.options.onBeforeScrollEnd.call(that, e); + + if (that.zoomed) { + scale = that.scale * that.lastScale; + scale = Math.max(that.options.zoomMin, scale); + scale = Math.min(that.options.zoomMax, scale); + that.lastScale = scale / that.scale; + that.scale = scale; + + that.x = that.originX - that.originX * that.lastScale + that.x; + that.y = that.originY - that.originY * that.lastScale + that.y; + + that.scroller.style[transitionDuration] = '200ms'; + that.scroller.style[transform] = 'translate(' + that.x + 'px,' + that.y + 'px) scale(' + that.scale + ')' + translateZ; + + that.zoomed = false; + that.refresh(); + + if (that.options.onZoomEnd) that.options.onZoomEnd.call(that, e); + return; + } + + if (!that.moved) { + if (hasTouch) { + if (that.doubleTapTimer && that.options.zoom) { + // Double tapped + clearTimeout(that.doubleTapTimer); + that.doubleTapTimer = null; + if (that.options.onZoomStart) that.options.onZoomStart.call(that, e); + that.zoom(that.pointX, that.pointY, that.scale == 1 ? that.options.doubleTapZoom : 1); + if (that.options.onZoomEnd) { + setTimeout(function() { + that.options.onZoomEnd.call(that, e); + }, 200); // 200 is default zoom duration + } + } else if (this.options.handleClick) { + that.doubleTapTimer = setTimeout(function () { + that.doubleTapTimer = null; + + // Find the last touched element + target = point.target; + while (target.nodeType != 1) target = target.parentNode; + + if (target.tagName != 'SELECT' && target.tagName != 'INPUT' && target.tagName != 'TEXTAREA') { + ev = doc.createEvent('MouseEvents'); + ev.initMouseEvent('click', true, true, e.view, 1, + point.screenX, point.screenY, point.clientX, point.clientY, + e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, + 0, null); + ev._fake = true; + target.dispatchEvent(ev); + } + }, that.options.zoom ? 250 : 0); + } + } + + that._resetPos(200); + + if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e); + return; + } + + if (duration < 300 && that.options.momentum) { + momentumX = newPosX ? that._momentum(newPosX - that.startX, duration, -that.x, that.scrollerW - that.wrapperW + that.x, that.options.bounce ? that.wrapperW : 0) : momentumX; + momentumY = newPosY ? that._momentum(newPosY - that.startY, duration, -that.y, (that.maxScrollY < 0 ? that.scrollerH - that.wrapperH + that.y - that.minScrollY : 0), that.options.bounce ? that.wrapperH : 0) : momentumY; + + newPosX = that.x + momentumX.dist; + newPosY = that.y + momentumY.dist; + + if ((that.x > 0 && newPosX > 0) || (that.x < that.maxScrollX && newPosX < that.maxScrollX)) momentumX = { dist:0, time:0 }; + if ((that.y > that.minScrollY && newPosY > that.minScrollY) || (that.y < that.maxScrollY && newPosY < that.maxScrollY)) momentumY = { dist:0, time:0 }; + } + + if (momentumX.dist || momentumY.dist) { + newDuration = m.max(m.max(momentumX.time, momentumY.time), 10); + + // Do we need to snap? + if (that.options.snap) { + distX = newPosX - that.absStartX; + distY = newPosY - that.absStartY; + if (m.abs(distX) < that.options.snapThreshold && m.abs(distY) < that.options.snapThreshold) { that.scrollTo(that.absStartX, that.absStartY, 200); } + else { + snap = that._snap(newPosX, newPosY); + newPosX = snap.x; + newPosY = snap.y; + newDuration = m.max(snap.time, newDuration); + } + } + + that.scrollTo(m.round(newPosX), m.round(newPosY), newDuration); + + if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e); + return; + } + + // Do we need to snap? + if (that.options.snap) { + distX = newPosX - that.absStartX; + distY = newPosY - that.absStartY; + if (m.abs(distX) < that.options.snapThreshold && m.abs(distY) < that.options.snapThreshold) that.scrollTo(that.absStartX, that.absStartY, 200); + else { + snap = that._snap(that.x, that.y); + if (snap.x != that.x || snap.y != that.y) that.scrollTo(snap.x, snap.y, snap.time); + } + + if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e); + return; + } + + that._resetPos(200); + if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e); + }, + + _resetPos: function (time) { + var that = this, + resetX = that.x >= 0 ? 0 : that.x < that.maxScrollX ? that.maxScrollX : that.x, + resetY = that.y >= that.minScrollY || that.maxScrollY > 0 ? that.minScrollY : that.y < that.maxScrollY ? that.maxScrollY : that.y; + + if (resetX == that.x && resetY == that.y) { + if (that.moved) { + that.moved = false; + if (that.options.onScrollEnd) that.options.onScrollEnd.call(that); // Execute custom code on scroll end + } + + if (that.hScrollbar && that.options.hideScrollbar) { + if (vendor == 'webkit') that.hScrollbarWrapper.style[transitionDelay] = '300ms'; + that.hScrollbarWrapper.style.opacity = '0'; + } + if (that.vScrollbar && that.options.hideScrollbar) { + if (vendor == 'webkit') that.vScrollbarWrapper.style[transitionDelay] = '300ms'; + that.vScrollbarWrapper.style.opacity = '0'; + } + + return; + } + + that.scrollTo(resetX, resetY, time || 0); + }, + + _wheel: function (e) { + var that = this, + wheelDeltaX, wheelDeltaY, + deltaX, deltaY, + deltaScale; + + if ('wheelDeltaX' in e) { + wheelDeltaX = e.wheelDeltaX / 12; + wheelDeltaY = e.wheelDeltaY / 12; + } else if('wheelDelta' in e) { + wheelDeltaX = wheelDeltaY = e.wheelDelta / 12; + } else if ('detail' in e) { + wheelDeltaX = wheelDeltaY = -e.detail * 3; + } else { + return; + } + + if (that.options.wheelAction == 'zoom') { + deltaScale = that.scale * Math.pow(2, 1/3 * (wheelDeltaY ? wheelDeltaY / Math.abs(wheelDeltaY) : 0)); + if (deltaScale < that.options.zoomMin) deltaScale = that.options.zoomMin; + if (deltaScale > that.options.zoomMax) deltaScale = that.options.zoomMax; + + if (deltaScale != that.scale) { + if (!that.wheelZoomCount && that.options.onZoomStart) that.options.onZoomStart.call(that, e); + that.wheelZoomCount++; + + that.zoom(e.pageX, e.pageY, deltaScale, 400); + + setTimeout(function() { + that.wheelZoomCount--; + if (!that.wheelZoomCount && that.options.onZoomEnd) that.options.onZoomEnd.call(that, e); + }, 400); + } + + return; + } + + deltaX = that.x + wheelDeltaX; + deltaY = that.y + wheelDeltaY; + + if (deltaX > 0) deltaX = 0; + else if (deltaX < that.maxScrollX) deltaX = that.maxScrollX; + + if (deltaY > that.minScrollY) deltaY = that.minScrollY; + else if (deltaY < that.maxScrollY) deltaY = that.maxScrollY; + + if (that.maxScrollY < 0) { + that.scrollTo(deltaX, deltaY, 0); + } + }, + + _mouseout: function (e) { + var t = e.relatedTarget; + + if (!t) { + this._end(e); + return; + } + + while (t = t.parentNode) if (t == this.wrapper) return; + + this._end(e); + }, + + _transitionEnd: function (e) { + var that = this; + + if (e.target != that.scroller) return; + + that._unbind(TRNEND_EV); + + that._startAni(); + }, + + + /** + * + * Utilities + * + */ + _startAni: function () { + var that = this, + startX = that.x, startY = that.y, + startTime = Date.now(), + step, easeOut, + animate; + + if (that.animating) return; + + if (!that.steps.length) { + that._resetPos(400); + return; + } + + step = that.steps.shift(); + + if (step.x == startX && step.y == startY) step.time = 0; + + that.animating = true; + that.moved = true; + + if (that.options.useTransition) { + that._transitionTime(step.time); + that._pos(step.x, step.y); + that.animating = false; + if (step.time) that._bind(TRNEND_EV); + else that._resetPos(0); + return; + } + + animate = function () { + var now = Date.now(), + newX, newY; + + if (now >= startTime + step.time) { + that._pos(step.x, step.y); + that.animating = false; + if (that.options.onAnimationEnd) that.options.onAnimationEnd.call(that); // Execute custom code on animation end + that._startAni(); + return; + } + + now = (now - startTime) / step.time - 1; + easeOut = m.sqrt(1 - now * now); + newX = (step.x - startX) * easeOut + startX; + newY = (step.y - startY) * easeOut + startY; + that._pos(newX, newY); + if (that.animating) that.aniTime = nextFrame(animate); + }; + + animate(); + }, + + _transitionTime: function (time) { + time += 'ms'; + this.scroller.style[transitionDuration] = time; + if (this.hScrollbar) this.hScrollbarIndicator.style[transitionDuration] = time; + if (this.vScrollbar) this.vScrollbarIndicator.style[transitionDuration] = time; + }, + + _momentum: function (dist, time, maxDistUpper, maxDistLower, size) { + var deceleration = 0.0006, + speed = m.abs(dist) / time, + newDist = (speed * speed) / (2 * deceleration), + newTime = 0, outsideDist = 0; + + // Proportinally reduce speed if we are outside of the boundaries + if (dist > 0 && newDist > maxDistUpper) { + outsideDist = size / (6 / (newDist / speed * deceleration)); + maxDistUpper = maxDistUpper + outsideDist; + speed = speed * maxDistUpper / newDist; + newDist = maxDistUpper; + } else if (dist < 0 && newDist > maxDistLower) { + outsideDist = size / (6 / (newDist / speed * deceleration)); + maxDistLower = maxDistLower + outsideDist; + speed = speed * maxDistLower / newDist; + newDist = maxDistLower; + } + + newDist = newDist * (dist < 0 ? -1 : 1); + newTime = speed / deceleration; + + return { dist: newDist, time: m.round(newTime) }; + }, + + _offset: function (el) { + var left = -el.offsetLeft, + top = -el.offsetTop; + + while (el = el.offsetParent) { + left -= el.offsetLeft; + top -= el.offsetTop; + } + + if (el != this.wrapper) { + left *= this.scale; + top *= this.scale; + } + + return { left: left, top: top }; + }, + + _snap: function (x, y) { + var that = this, + i, l, + page, time, + sizeX, sizeY; + + // Check page X + page = that.pagesX.length - 1; + for (i=0, l=that.pagesX.length; i= that.pagesX[i]) { + page = i; + break; + } + } + if (page == that.currPageX && page > 0 && that.dirX < 0) page--; + x = that.pagesX[page]; + sizeX = m.abs(x - that.pagesX[that.currPageX]); + sizeX = sizeX ? m.abs(that.x - x) / sizeX * 500 : 0; + that.currPageX = page; + + // Check page Y + page = that.pagesY.length-1; + for (i=0; i= that.pagesY[i]) { + page = i; + break; + } + } + if (page == that.currPageY && page > 0 && that.dirY < 0) page--; + y = that.pagesY[page]; + sizeY = m.abs(y - that.pagesY[that.currPageY]); + sizeY = sizeY ? m.abs(that.y - y) / sizeY * 500 : 0; + that.currPageY = page; + + // Snap with constant speed (proportional duration) + time = m.round(m.max(sizeX, sizeY)) || 200; + + return { x: x, y: y, time: time }; + }, + + _bind: function (type, el, bubble) { + (el || this.scroller).addEventListener(type, this, !!bubble); + }, + + _unbind: function (type, el, bubble) { + (el || this.scroller).removeEventListener(type, this, !!bubble); + }, + + + /** + * + * Public methods + * + */ + destroy: function () { + var that = this; + + that.scroller.style[transform] = ''; + + // Remove the scrollbars + that.hScrollbar = false; + that.vScrollbar = false; + that._scrollbar('h'); + that._scrollbar('v'); + + // Remove the event listeners + that._unbind(RESIZE_EV, window); + that._unbind(START_EV); + that._unbind(MOVE_EV); + that._unbind(END_EV); + that._unbind(CANCEL_EV); + + if (!that.options.hasTouch) { + that._unbind('mouseout', that.wrapper); + that._unbind(WHEEL_EV); + } + + if (that.options.useTransition) that._unbind(TRNEND_EV); + + if (that.options.checkDOMChanges) clearInterval(that.checkDOMTime); + + if (that.options.onDestroy) that.options.onDestroy.call(that); + }, + + refresh: function () { + var that = this, + offset, + i, l, + els, + pos = 0, + page = 0; + + if (that.scale < that.options.zoomMin) that.scale = that.options.zoomMin; + that.wrapperW = that.wrapper.clientWidth || 1; + that.wrapperH = that.wrapper.clientHeight || 1; + + that.minScrollY = -that.options.topOffset || 0; + that.scrollerW = m.round(that.scroller.offsetWidth * that.scale); + that.scrollerH = m.round((that.scroller.offsetHeight + that.minScrollY) * that.scale); + that.maxScrollX = that.wrapperW - that.scrollerW; + that.maxScrollY = that.wrapperH - that.scrollerH + that.minScrollY; + that.dirX = 0; + that.dirY = 0; + + if (that.options.onRefresh) that.options.onRefresh.call(that); + + that.hScroll = that.options.hScroll && that.maxScrollX < 0; + that.vScroll = that.options.vScroll && (!that.options.bounceLock && !that.hScroll || that.scrollerH > that.wrapperH); + + that.hScrollbar = that.hScroll && that.options.hScrollbar; + that.vScrollbar = that.vScroll && that.options.vScrollbar && that.scrollerH > that.wrapperH; + + offset = that._offset(that.wrapper); + that.wrapperOffsetLeft = -offset.left; + that.wrapperOffsetTop = -offset.top; + + // Prepare snap + if (typeof that.options.snap == 'string') { + that.pagesX = []; + that.pagesY = []; + els = that.scroller.querySelectorAll(that.options.snap); + for (i=0, l=els.length; i= that.maxScrollX) { + that.pagesX[page] = pos; + pos = pos - that.wrapperW; + page++; + } + if (that.maxScrollX%that.wrapperW) that.pagesX[that.pagesX.length] = that.maxScrollX - that.pagesX[that.pagesX.length-1] + that.pagesX[that.pagesX.length-1]; + + pos = 0; + page = 0; + that.pagesY = []; + while (pos >= that.maxScrollY) { + that.pagesY[page] = pos; + pos = pos - that.wrapperH; + page++; + } + if (that.maxScrollY%that.wrapperH) that.pagesY[that.pagesY.length] = that.maxScrollY - that.pagesY[that.pagesY.length-1] + that.pagesY[that.pagesY.length-1]; + } + + // Prepare the scrollbars + that._scrollbar('h'); + that._scrollbar('v'); + + if (!that.zoomed) { + that.scroller.style[transitionDuration] = '0'; + that._resetPos(200); + } + }, + + scrollTo: function (x, y, time, relative) { + var that = this, + step = x, + i, l; + + that.stop(); + + if (!step.length) step = [{ x: x, y: y, time: time, relative: relative }]; + + for (i=0, l=step.length; i 0 ? 0 : pos.left < that.maxScrollX ? that.maxScrollX : pos.left; + pos.top = pos.top > that.minScrollY ? that.minScrollY : pos.top < that.maxScrollY ? that.maxScrollY : pos.top; + time = time === undefined ? m.max(m.abs(pos.left)*2, m.abs(pos.top)*2) : time; + + that.scrollTo(pos.left, pos.top, time); + }, + + scrollToPage: function (pageX, pageY, time) { + var that = this, x, y; + + time = time === undefined ? 400 : time; + + if (that.options.onScrollStart) that.options.onScrollStart.call(that); + + if (that.options.snap) { + pageX = pageX == 'next' ? that.currPageX+1 : pageX == 'prev' ? that.currPageX-1 : pageX; + pageY = pageY == 'next' ? that.currPageY+1 : pageY == 'prev' ? that.currPageY-1 : pageY; + + pageX = pageX < 0 ? 0 : pageX > that.pagesX.length-1 ? that.pagesX.length-1 : pageX; + pageY = pageY < 0 ? 0 : pageY > that.pagesY.length-1 ? that.pagesY.length-1 : pageY; + + that.currPageX = pageX; + that.currPageY = pageY; + x = that.pagesX[pageX]; + y = that.pagesY[pageY]; + } else { + x = -that.wrapperW * pageX; + y = -that.wrapperH * pageY; + if (x < that.maxScrollX) x = that.maxScrollX; + if (y < that.maxScrollY) y = that.maxScrollY; + } + + that.scrollTo(x, y, time); + }, + + disable: function () { + this.stop(); + this._resetPos(0); + this.enabled = false; + + // If disabled after touchstart we make sure that there are no left over events + this._unbind(MOVE_EV); + this._unbind(END_EV); + this._unbind(CANCEL_EV); + }, + + enable: function () { + this.enabled = true; + }, + + stop: function () { + if (this.options.useTransition) this._unbind(TRNEND_EV); + else cancelFrame(this.aniTime); + this.steps = []; + this.moved = false; + this.animating = false; + }, + + zoom: function (x, y, scale, time) { + var that = this, + relScale = scale / that.scale; + + if (!that.options.useTransform) return; + + that.zoomed = true; + time = time === undefined ? 200 : time; + x = x - that.wrapperOffsetLeft - that.x; + y = y - that.wrapperOffsetTop - that.y; + that.x = x - x * relScale + that.x; + that.y = y - y * relScale + that.y; + + that.scale = scale; + that.refresh(); + + that.x = that.x > 0 ? 0 : that.x < that.maxScrollX ? that.maxScrollX : that.x; + that.y = that.y > that.minScrollY ? that.minScrollY : that.y < that.maxScrollY ? that.maxScrollY : that.y; + + that.scroller.style[transitionDuration] = time + 'ms'; + that.scroller.style[transform] = 'translate(' + that.x + 'px,' + that.y + 'px) scale(' + scale + ')' + translateZ; + that.zoomed = false; + }, + + isReady: function () { + return !this.moved && !this.zoomed && !this.animating; + } +}; + +function prefixStyle (style) { + if ( vendor === '' ) return style; + + style = style.charAt(0).toUpperCase() + style.substr(1); + return vendor + style; +} + +dummyStyle = null; // for the sake of it + +if (typeof exports !== 'undefined') exports.iScroll = iScroll; +else window.iScroll = iScroll; + +})(this, document); diff --git a/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/scripts/itablet.js b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/scripts/itablet.js new file mode 100644 index 0000000000..c6f84bcf53 --- /dev/null +++ b/qpid/tools/src/java/qpid-qmf2-tools/bin/qpid-web/web/itablet/scripts/itablet.js @@ -0,0 +1,1516 @@ +/* + * + * 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. + * + */ + +/** + * + * This library implements a general user interface look and feel similar to a "well know tablet PC" :-) + * It provides animated page transition eye-candy but is, in essence, really just a fancy tabbed window. + * + * It has dependencies on the following: + * itablet.css + * iscroll.js + * jquery.js (jquery-1.7.1.min.js) + * + * author Fraser Adams + */ + +/** + * Create a Singleton instance of the iTablet user interface generic look and feel. + */ +var iTablet = new function() { + if (!String.prototype.trim) { // Add a String trim method if one doesn't exist (modern browsers should have it.) + String.prototype.trim = function() { + return this.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); + }; + } + + /** + * The Location inner class allows iTablet.location to be used similarly to window.location. + */ + var Location = function(href) { + this.href = href; + var split = href.split("?"); // Split string by query part. + this.hash = split[0]; + this.search = split.length == 2 ? "?" + split[1] : ""; + this.data = null; + + // Populate the data property with key/value pairs extracted from search part of URL. + if (split.length == 2) { + this.data = {}; + var kv = split[1].split("&"); // Split string into key/value pairs using the & is the separator. + var length = kv.length; + for (var i = 0; i < length; i++) { + var s = kv[i].split("="); + if (s.length == 2) { + var key = s[0].trim(); + var value = s[1].trim(); + this.data[key] = value; + } + } + } + + Location.prototype.toString = function() { + return this.href; + }; + + // Location.back() is mapped to the iTablet.goBack() method. + // This allows clients to explicitly return to a previous page, useful for coding submit handlers etc. + Location.prototype.back = goBack; + }; + + /** + * The TextChange inner class adds support for a textchange Event on text input and textarea tags. + * Initialise with: $.event.special.textchange = new TextChange(); + * Uses info derived from from http://benalman.com/news/2010/03/jquery-special-events/ + */ + var TextChange = function() { + /** + * Directly call the triggerIfChanged method on keyup. + */ + var handler = function() { + triggerIfChanged(this); + }; + + /** + * For cut, paste and input handlers we need to call triggerIfChanged from a timeout. + */ + var handlerWithDelay = function() { + var element = this; + setTimeout(function() { + triggerIfChanged(element); + }, 25); + }; + + /** + * Trigger textchange Event handler bound to target element if the text has changed. + */ + var triggerIfChanged = function(domElement) { + var element = $(domElement); + var current = domElement.contentEditable === "true" ? element.html() : element.val(); + if (current !== element.data("lastValue")) { + element.trigger("textchange", [element.data("lastValue")]); + element.data("lastValue", current); + } + }; + + /** + * Called by jQuery when the first event handler is bound to a particular element. + */ + this.setup = function(data) { + var jthis = $(this); + jthis.data("lastValue", this.contentEditable === 'true' ? jthis.html() : jthis.val()); + // Bind keyup, cut, paste and input handlers in the .textchange Event namespace to allow easy unbinding. + jthis.bind("keyup.textchange", handler); + jthis.bind("cut.textchange paste.textchange input.textchange", handlerWithDelay); + }; + + /** + * Called by jQuery when the last event handler is unbound from a particular element. + */ + this.teardown = function (data) { + $(this).unbind('.textchange'); // Unbind the Events linked to the .textchange Event namespace. + } + }; + +//------------------------------------------------------------------------------------------------------------------- + + var TOUCH_ENABLED = 'ontouchstart' in window && !((/hp-tablet/gi).test(navigator.appVersion)); + + // Select start, move and end events based on whether or not the user agent is a touch device. + var START_EV = (TOUCH_ENABLED) ? "touchstart" : "mousedown"; + var MOVE_EV = (TOUCH_ENABLED) ? "touchmove" : "mousemove"; + var END_EV = (TOUCH_ENABLED) ? "touchend" : "mouseup"; + + // Populated in initialiseCSSAnimations() + var ANIMATION_END_EV = "";// = "animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd"; + var TRANSITION_END_EV = "";//"transitionend webkitTransitionEnd MSTransitionEnd oTransitionEnd"; + + /** + * The general perceived wisdom is to use feature detection rather than browser sniffing, which seems a good + * aim, but as it happens *most* of the browser abstraction is happening in jQuery and CSS but there remain + * a few quirks, mostly for IE < 9. IE < 9 and Opera < 12 both appear not to trigger a change when radio or + * checkbox state changes and it's not clear how to "feature detect" this, which is the main reason for + * including the Opera version sniffing. + */ + var IS_IE = (navigator.appName == "Microsoft Internet Explorer"); + var IE_VERSION = IS_IE ? /MSIE (\d+)/.exec(navigator.userAgent)[1] : 1000000; + var IS_OPERA = (navigator.appName == "Opera") && (window.opera.version != null); + var OPERA_VERSION = IS_OPERA ? opera.version() : 1000000; + + var BODY; // jQuery object for body is used in several places so let's cache it (gets set in jQuery.ready()). + var IS_MOBILE = false; // Gets set if we detect a small display (e.g. mobile) device. + + var _mainLeft = 0; // N.B. We need to get the actual value after the DOM has loaded. + var _history = []; // URL history to enable back button behaviour. + var _scrollers = {}; + var _transitions = {}; // Map of transition names to transition functions (populate later after functions defined). + this.location = null; + + /** + * This public helper method renders an HTML list with up to list items using the function. + * If the length of the list is less than the number added by the contents function additional list items are + * appended, conversely if the length of the list is greater than the number added by the contents function then + * the list is truncated. + * + * The contents of the list are populated via the supplied function, which should take an index as a + * parameter and return an
    • with contents or false. If false is returned that item is skipped. + * If an HTML list is supplied without a maxlength and contents function this method will simply carry out + * reskinning of the input widgets for the supplied list (and adding radiused borders for older versions of IE). + * + * For some reason this method seems to run very sluggishly on IE6, is's especially noticable when doing a + * resize. It runs fine on every other browser. I've not noticed any obvious inefficiencies but IE6 is weird.. + * + * @param list jQuery object representing the html list (ul) we wish to populate. + * @param contents a function to populate the list contents, this function should take an index as a parameter. + * and return an
    • with the required contents or false to skip (useful if filtering is needed). + * @param maxlength the maximum number of items that we wish to populate (default is 1). + */ + this.renderList = function(list, contents, maxlength) { + // For IE 6 get the width. Have to use .main or .popup-container because list.innerWidth() may not be set yet. + var listWidth = 0; + + if (IE_VERSION < 7) { // This seems to be very slow on IE6, no idea why??? + var main = list.closest(".main"); + if (main.length == 0) { // It's a list in a popup + listWidth = Math.round(parseInt($(".popup-container").css('width'), 10) * 0.9); + } else { + listWidth = Math.round($(".main").width() * 0.9); + } + } + + var lengthChanged = false; + var items = list.children("li"); + + if (contents == null) { + maxlength = items.length; + } else if (maxlength == null) { + maxlength = 1; // If no maxlength is supplied default to calling the contents function once. + } + + var actualLength = 0; // Actual number of
    • supplied, this caters for the contents function returning false. + for (var i = 0; i < maxlength; i++) { + var li = false; + if (actualLength < items.length) { + if (contents) { // Modify list item with the new contents at index i. + var newItem = contents(i); + if (newItem) { // If the contents function didn't return false add contents to current li. + li = $(items[actualLength]); + actualLength++; + var active = li.hasClass("active") ? "active" : ""; + var newItem = $(newItem).addClass(active); + li.removeClass().addClass(newItem.attr("class")); // Remove existing classes and add new ones. + li.html(newItem.html()); + } + } else { // If contents function not present we simply reskin the current list item. + li = $(items[actualLength]); + actualLength++; + + if (IE_VERSION < 9) { + // Remove any markup used for faking :first-child, :last-child, :before, :after + li.removeClass("first-child last-child"); + li.children("div.before, div.after, div.fbefore, div.fafter").remove(); + } + } + } else { // If there are fewer items in the list than there are contents then append new list items. + var newItem = contents(i); + if (newItem) { // If the contents function didn't return false append contents as new li. + li = $(newItem); + actualLength++; + list.append(li); + lengthChanged = true; + } + } + + if (li) { + // Reskin input widgets. + Radio.reskin(li.children("input:radio")); + Checkbox.reskin(li.children("input:checkbox")); + + // Fix several quirks in early versions of IE. + if (IE_VERSION < 8) { + var anchor = li.children("a"); + anchor.attr("hideFocus", "hidefocus"); // Fix lack of outline: none; in IE < 8 + + if (IE_VERSION < 7) { // IE6 percentage widths are messed up so need to set absolute width. + // 41 comes from padding: 0 30px 0 11px; 22 comes from padding: 0 11px 0 11px; + // 34 comes from margin-left: 5px; text-indent: 40px; minus 11. + var anchorWidth = listWidth - ((li.hasClass("arrow") ? 41 : 22) + + (anchor.hasClass("icon") ? 34 : 11)); + anchor.css({"width": anchorWidth + "px"}); + + // IE6 can't cope with multiple CSS classes so we need to merge these into a single class. + if (li.is(".arrow.radio")) { + li.addClass("ie6-radio-arrow"); + if (li.hasClass("checked")) { + li.addClass("ie6-checked-arrow"); + } + } + } + } + } + } + + // If list is longer than the contents being added then trim the list so it's the same size. + if (actualLength < items.length) { + items.slice(actualLength).remove(); + lengthChanged = true; + } + + // Add radiused borders for IE8 and below. + // We have to do this after completely populating the list so first() and last() are correct. + if (IE_VERSION < 9) { + items = list.children("li"); + var last = items.last(); + last.addClass("last-child").prepend("
      ").append("
      "); + + if (IE_VERSION < 8) { + var first = items.first(); + if (IE_VERSION == 7) { // For IE7 fake :first-child:before and :first-child:after + first.prepend("
      ").append("
      "); + } else if (IE_VERSION < 7) { // For IE6 fake :first-child + // We're not adding radiused borders to IE6, this class provides the proper top border colour. + first.addClass("first-child"); + } + } + } + + // If the length has changed trigger an iScroll refresh on the top level page that contains the list. + if (lengthChanged) { + list.closest(".main").trigger("refresh"); // refresh is a synthetic event handled by parents of scroll-area + } + }; + +//------------------------------------------------------------------------------------------------------------------- +// UI Widgets +//------------------------------------------------------------------------------------------------------------------- + + /** + * Create a Singleton instance of the Radio class used to reskin and manage HTML input type="radio" widgets. + */ + var Radio = new function() { + /** + * "Reskin" HTML input type="radio" items into a tablet "tick" style radio item. Note that the radio item + * needs to have a label sibling and be wrapped in a parent
    • for this to work correctly. + * e.g.
      + */ + this.reskin = function(radios) { + if (radios == null) { // if no input:radio is specified attempt to reskin every one in the document. + radios = $("input:radio"); + } + + // Add classes to container
    • based on "template" radio buttons. This "re-skins" the radio buttons. + radios.each(function() { // Iterate through each radio button. + var jthis = $(this).hide(); + if (!jthis.hasClass("reskinned")) { // If checkbox has already been reskinned move on. + var parent = jthis.parent(); + parent.addClass("radio"); + if (this.checked) { + parent.addClass("checked"); + } + jthis.addClass("reskinned"); // Mark as reskinned to avoid trying to reskin it again. + } + }); + }; + + /** + * Handle radio button state changes. This method is delegated to by the main handlePointer end() method. + * Note that we are actually passing in the $("ul li.radio") jQuery object that the Event was bound to + * rather than the actual Event object as this has already been extracted by handlePointer and is more + * useful for the implementation of this method. + */ + this.handleClick = function(jthis, type) { + var checked = jthis.parent().children(".checked"); + var radio = jthis.children("input:radio"); // Select the template radio button. + + fade(jthis); // Fade out the highlighting of the selected
    • + + if (type != "click") { // If this handler wasn't triggered by a radio button click we synthesise one. + BODY.unbind("click", handlePointer); // Prevent the synthetic click from triggering handlePointer. + radio.click(); // Trigger radio button's click (on modern browsers this triggers change too if changed). + BODY.click(handlePointer); + } + + // We explicitly manipulate input:radio checked attr in the following block in case a name attr wasn't + // specified in the HTML. With reskinned radio buttons the parent
        is the real container. + if (!jthis.is(checked)) { // If the clicked item is not the previously checked item. + // Clear any check mark on the previously selected
      • and the same on the template radio button. + checked.removeClass("checked"); + checked.children("input:radio").attr("checked", false); + + // Mark the current
      • as checked and do the same on the template radio button. + jthis.addClass("checked").change(); + jthis.children("input:radio").attr("checked", true); + + // For older IE/Opera triggering the click as done earlier won't trigger the change event so do it now. + if (IE_VERSION < 9 || OPERA_VERSION < 12) { + radio.change(); + } + } + }; + }; + + /** + * Create a Singleton instance of the Checkbox class used to reskin and manage HTML input type="checkbox" widgets. + */ + var Checkbox = new function() { + /** + * Return the input element associated with the specified label. This is used because the input element + * may be associated to the label in a number of different was (via the "for" attribute, by containment etc.) + */ + var findInputElement = function(label) { + var input = $("#" + label.attr("for")); // First check if label for points to the input. + if (input.length == 0) { // If not check if the label contains the input. + input = label.children("input"); + } + if (input.length == 0) { // Finally check if the label's next sibling is an input. + input = label.next("input"); + } + + return input; + }; + + /** + * "Reskin" HTML input type="checkbox" items into a tablet "switch" style checkbox. Note that the checkbox + * needs to have a label sibling and be wrapped in a parent
      • for this to work correctly. + * e.g.
        + * If multiple elements are placed in an
      • then they will automatically be + * reskinned as a "horiz-checkbox", this can also be done explicitly by doing
      • + */ + this.reskin = function(checkboxes) { + if (checkboxes == null) { // if no input:checkbox is specified attempt to reskin every one in the document. + checkboxes = $("input:checkbox"); + } + + checkboxes.each(function() { // Iterate through each checkbox. + var jthis = $(this).hide(); + if (!jthis.hasClass("reskinned")) { // If checkbox has already been reskinned move on. + // If there are multiple checkboxes in a container create a horizontal checkbox by adding class + // to parent. Note that
      • may also be explicitly set in the HTML. + jthis.siblings("input:checkbox").parent().addClass("horiz-checkbox"); + + // Mop up where checkbox is contained by label e.g. + jthis.parent().siblings("label").parent().addClass("horiz-checkbox"); + + var li = jthis.closest("li"); // Find containing li. + + if (li.hasClass("horiz-checkbox")) { // Reskin horizontal checkbox. + // IE8 doesn't seem to distinguish between the selectors ul.list li:first-child:before and + // ul.list li.horiz-checkbox:first-child:before when horiz-checkbox is dynamically added + // this stops the
      • fake radiused border being correctly positioned for horiz-checkboxes. + // By adding horiz-checkbox class to the
          too we can use a more explicit rule in the CSS. + li.parent().addClass("horiz-checkbox"); + + // As we use inline-block for horiz-checkbox we need to remove any whitespace text nodes. + li.contents().filter(function() { + return (this.nodeType == 3 && $.trim($(this).text()).length == 0); + }).remove(); + + // For horiz-checkbox we set the width of each item and add a span containing the right border. + // The inner span is needed so the border doesn't impact the element width calculation. + li.each(function() { + var buttons = $(this).children("label"); + var width = 100/buttons.length; + + buttons.css("width", width + "%"); // Using the percentage works for browsers > IE7. + // The child span helps position the border - see css (ul.list li.horiz-checkbox label span) + buttons.filter(":not(:last)").append(""); + buttons.first().addClass("first-child"); + buttons.last().addClass("last-child"); + + // Early IE doesn't respect the width so reduce the width of the last item a little. + if (width < 100 && (IE_VERSION < 8 )) { + buttons.last().css("width", width - 1 + "%") + } + + // Find input associated with each label and if it's checked add "checked" class to label. + buttons.each(function() { + var button = $(this); + var input = findInputElement(button); + input.addClass("reskinned"); // Mark as reskinned to avoid trying to reskin it again. + if (input.attr("checked")) { + button.addClass("checked"); + if (button.is(buttons.last())) { + // Use toggle-on not checked to avoid confusing IE6. + button.parent().addClass("toggle-on"); + } + } + }); + }); + } else { // Reskin normal checkbox. + // Add markup to container
        • based on "template" checkboxes. This "re-skins" the checkboxes. + jthis.parent(). + append("
          "); + + // If the checkbox is checked find onoff switch and change its initial position to "on". + jthis.filter(":checked").siblings(".checkbox").children(".onoff").css("left", "0px"); + jthis.addClass("reskinned"); // Mark as reskinned to avoid trying to reskin it again. + } + } + }); + }; + + /** + * Handle checkbox state changes. This method is delegated to by the main handlePointer method. + */ + this.handlePointer = function(e, type) { + // If triggered by a synthetic click the target is an input:checkbox otherwise it's a div.mask + var jthis = (type == "click") ? $(e.target).siblings("div.checkbox") : $(e.target).parent(); + var onoff = jthis.children(".onoff"); + var checkbox = jthis.parent().children("input:checkbox"); // Select the underlying + var offsetX = onoff.offset().left - jthis.offset().left; + var offset = -parseInt(onoff.css("left"), 10); // If we use translate we adjust by the left CSS position. + var startX = (e.pageX != null) ? e.pageX : e.originalEvent.targetTouches[0].pageX; + var clicked = true; // Set to false if move handler is called; + + var move = function(e) { + var newX = (e.pageX != null) ? e.pageX : e.originalEvent.changedTouches[0].pageX; + var diffX = offsetX + newX - startX; + clicked = false; + + if (diffX >= -50 && diffX <= 0) { + setPosition(onoff, offset, diffX); + } + e.stopPropagation(); // Prevent iScroll from trying to scroll when we drag the switch. + }; + + /** + * The pointer up handler is only bound when the pointer down has been triggered and so behaves like a click. + */ + var end = function(e) { + var pos = onoff.offset().left - jthis.offset().left; + var duration = 300; + + if (clicked) { + pos = (pos == 0) ? -50 : 0; + } else { + // Animation duration is between 150ms and 0ms based on position. + duration = (25 - Math.abs(pos + 25)) * 6; + pos = (pos < -25) ? -50 : 0; + + } + + setPosition(onoff, offset, pos, duration); + + BODY.unbind("click", handlePointer); // Prevent the synthetic click from triggering handlePointer. + var currentlyChecked = checkbox[0].checked; + if ((currentlyChecked && pos == -50) || (!currentlyChecked && pos == 0)) { + if (type == "click") { + checkbox.attr("checked", !currentlyChecked); + } else { + checkbox.click(); + // For older IE/Opera triggering the click won't trigger the change event so do it now. + if (IE_VERSION < 9 || OPERA_VERSION < 12) { + checkbox.change(); + } + } + } + BODY.click(handlePointer); + + if (TOUCH_ENABLED) { + jthis.unbind(MOVE_EV + " " + END_EV); + } else { + BODY.unbind(MOVE_EV + " " + END_EV + " mouseleave"); + } + }; + + if (type == "click") { + end(e); + } else { + // Bind move, end and mouseleave events to our internal handlers. + if (TOUCH_ENABLED) { // Touch events track over the whole page by default. + jthis.bind(MOVE_EV, move).bind(END_EV, end); + } else { // Bind mouse events to body so we can track them over the whole page. + BODY.bind(MOVE_EV, move).bind(END_EV + " mouseleave", end); + } + } + }; + + /** + * Handle horiz-checkbox state changes. This method is delegated to by the main handlePointer end() method. + * Note that we are actually passing in the $("ul li.horiz-checkbox label") jQuery object that the Event was + * bound to rather than the actual Event object as this has already been extracted by handlePointer and is + * more useful for the implementation of this method. + */ + this.handleClick = function(jthis, type) { + var parent = jthis.parent(); + var lastChild = parent.children("label").last(); + + if (jthis.hasClass("checked")) { + jthis.removeClass("checked"); + if (jthis.is(lastChild)) { + parent.removeClass("toggle-on"); // Use toggle-on rather than checked to avoid confusing IE6. + } + } else { + jthis.addClass("checked"); + if (jthis.is(lastChild)) { + parent.addClass("toggle-on"); // Use toggle-on rather than checked to avoid confusing IE6. + } + } + + if (type == "click") { + var input = findInputElement(jthis); + input.attr("checked", !input.attr("checked")); + } else { // If this handler wasn't triggered by a checkbox click we synthesise one. + BODY.unbind("click", handlePointer); // Prevent the synthetic click from triggering handlePointer. + var checkbox = findInputElement(jthis); + checkbox.click(); + // For older IE/Opera triggering the click won't trigger the change event so do it now. + if (IE_VERSION < 9 || OPERA_VERSION < 9) { + checkbox.change(); + } + BODY.click(handlePointer); + } + } + }; + +//------------------------------------------------------------------------------------------------------------------- +// Main Event Handler +//------------------------------------------------------------------------------------------------------------------- + + /** + * This method handles the pointer down, move and up events. + * We handle the discrete events because mobile Safari adds a 300ms delay to the click handler, in addition + * we want to be able to un-highlight rows if we move the mouse/finger. Note that this handler is in the form + * of a delegating event handler - we actually bind the events to the html body, this is so that if we modify + * the DOM externally, e.g. via an AJAX update we will trigger from newly created elements too. + * Note that the start(), removeHighlight(), resetHighlight(), touchMove() and end() methods are private. + * This method does actually handle click events but its main job is to prevent the default navigation action + * on href, however if the click event is a synthetic click caused by a jQuery trigger the method behaves like + * a proper click handler. + */ + var handlePointer = function(e) { + // These are the *actual* selectors that we are interested in handling events for. + var selectors = "ul.contents li, ul.mail li, ul li.radio, ul li div.checkbox, ul li.horiz-checkbox label, " + + "ul li.arrow, ul li.pop, div.header a.back, div.header a.done, div.header a.cancel, " + + "div.header a.menu"; + + var target = e.target; + var jthis = $(target); + var parent; + var prev; + var href = jthis.attr("href"); + var chevronClicked = false; // Set true if we've clicked on a chevron (used for navigable radio buttons). + var scrolled; // Gets set if a page has been (touch) scrolled. + var highlighted; // Gets set if a sidebar or mail item has been selected/highlighted. + var startY; // The vertical position when a touchstart gets triggered. + + var start = function(e) { + // This block checks if the event target is one of the selectors, if not it uses jQuery closest to get the + // first element that matches the selector, start at the current element and progress up through the DOM. + if (!jthis.is(selectors)) { + jthis = jthis.closest(selectors); + } + + // If closest object doesn't match any of the selectors return true if it has an href else return false. + if (jthis.length == 0) { + return (!!href); // The href test allows the browser to add the active pseudoclass. + } + + if (jthis.hasClass("checkbox")) { // If a checkbox then delegate to the Checkbox handlePointer() handler. + Checkbox.handlePointer(e); + return false; + } + + parent = jthis.parent(); + + // prev is the previously highlighted item. We search from parent's parent as we may have multiple lists. + prev = parent.parent().find(".active"); + + href = jthis.attr("href"); // Get the href of the element matching the selector. + + var TOUCHED_SIDEBAR = TOUCH_ENABLED && parent.is(".contents, .mail"); // Is this a touch on a sidebar list? + + // If the href isn't directly present then it's an
        • that contains the anchor, so we have to look further. + if (!href) { + var ICON_WIDTH = 45; // The width of the icon image plus some padding. + var offset = Math.ceil(jthis.offset().left); + + // Get the pointer x value relative to the
        • + var x = (e.pageX != null) ? e.pageX - offset : // Mouse position. + (e.originalEvent != null) ? e.originalEvent.targetTouches[0].pageX - offset : 0; // Touch pos. + + // If target is a clickable-icon we return immediately thus preventing any highlighting or navigation. + if (jthis.hasClass("clickable-icon") && (x < ICON_WIDTH)) { + return false; + } + + // If target is a navigable radio button then check if chevron was clicked/tapped. + if (jthis.hasClass("radio") && jthis.hasClass("arrow") && ((jthis.outerWidth() - x) < ICON_WIDTH)) { + chevronClicked = true; + } + + e.preventDefault(); // Stop the anchor default highlighting, we'll add our own prettier highlight. + + // This block highlights the selected
        • on mousedown or touchstart. For touch enabled devices we wait + // for a short time before highlighting in case the touchstart was the start of a scroll rather than a + // selection. If it was a scroll then the scrolled flag will get set by the touchMove handler and the + // highlight is aborted when the timeout gets triggered. + if (!prev[0] || prev[0] != jthis[0]) { + if (TOUCHED_SIDEBAR && !IS_MOBILE) { + // If TOUCH_ENABLED and a sidebar or mail list wait 50ms before highlighting in case the + // touch start is really the start of a touch scroll event. + scrolled = false; + highlighted = false; + + setTimeout(function() { + if (!scrolled) { // scrolled may be set by touchMove if the list has been scrolled. + prev.removeClass("active"); + jthis.addClass("active"); // Highlight the current
        • + highlighted = true; + } + }, 50); + } else { // If not TOUCH_ENABLED or a sidebar or mail list highlight immediately. + prev.removeClass("active"); + // Navigable radio buttons don't highlight when the chevron is clicked. + if (!chevronClicked) { + jthis.addClass("active"); // Highlight the current
        • + } + } + } + + href = jthis.children("a:first").attr("href"); // Get the href from the first anchor enclosed by the
        • + href = (href == null) ? "#" : href; // Create default href of "#" if none has been specified. + href = href.replace(window.location, ""); // Fix "absolute href" bug found in IE7 and below. + } + + BODY.bind(END_EV, end).unbind(START_EV, handlePointer); + + if (parent.hasClass("list") || (TOUCHED_SIDEBAR && IS_MOBILE)) { + // For a small (e.g. mobile) displays the sidebar becomes the main menu page and touches behave + // like normal list touches and become inactive on any touch move. + BODY.bind(MOVE_EV, removeHighlight); + } else if (TOUCHED_SIDEBAR && !IS_MOBILE) { + // For a real sidebar detect touch scrolling on these items. + startY = (e.originalEvent) ? e.originalEvent.targetTouches[0].pageY : 0; + BODY.bind(MOVE_EV, touchMove); + } + }; + + /** + * If we move the mouse or finger in a list item we un-highlight and deactivate. + */ + var removeHighlight = function(e) { + BODY.unbind(MOVE_EV + " " + END_EV).bind(START_EV, handlePointer); + jthis.removeClass("active"); + }; + + /** + * If we move a finger up or down in a sidebar item we reinstate the previous highlight ontouchend. + */ + var resetHighlight = function(e) { + BODY.unbind(END_EV).bind(START_EV, handlePointer); + prev.addClass("active"); + }; + + /** + * This touch move handler is only bound if the initial event is a touch start event bound to a
        • + * with a
            parent that has a .contents or .mail class. These lists need to be touch scrollable, but + * they also need to have a persistent highlight on selected items. This method checks how many vertical + * pixels have been scrolled and if it exceeds a threshold it triggers a scrolled state. Once scrolled it removes + * any new highlighting and binds resetHighlight to touchend, which reinstates the previous highlight. + */ + var touchMove = function(e) { + var newY = e.originalEvent.changedTouches[0].pageY; + if (Math.abs(newY - startY) > 7) { // Only trigger on an up/down finger movement. + if (highlighted) { // If a new item was highlighted set the highlighting back to the previous item. + BODY.unbind(MOVE_EV + " " + END_EV).bind(END_EV, resetHighlight); + } else { + BODY.unbind(MOVE_EV + " " + END_EV).bind(START_EV, handlePointer); + } + + if (prev[0] != jthis[0]) { + jthis.removeClass("active"); + } + scrolled = true; + } + }; + + /** + * The pointer up handler is only bound when the pointer down has been triggered and so behaves like a click + * handler. If we have been triggered by a back selector or if we re-click an already selected sidebar entry + * that has previously transitioned the we call goBack() to transition backwards otherwise we goTo(href). + */ + var end = function(e) { + BODY.unbind(MOVE_EV + " " + END_EV).bind(START_EV, handlePointer); + + if (!IS_MOBILE && (parent.hasClass("contents") || parent.hasClass("mail"))) { + // Handle sidebar transitions. + if (_history.length == 2 && href == _history[1].href) { + goBack(); + } else { + _history = []; + goTo(href); + } + } else if (parent.is("ul")) { + if (jthis.hasClass("radio") && !chevronClicked) { // Delegate radio button state changes. + Radio.handleClick(jthis, e.type); + } else { // Handle goTo page transition. + var classes = jthis.attr("class").split(" "); + var transition = slide; // Default animation. + // Look up the transition animation based upon the item's class + for (var i in classes) { + var current = classes[i]; + var newTransition = _transitions[current]; + if (newTransition != null) { + transition = newTransition; + break; + } + } + goTo(href, transition); + } + } else if (parent.hasClass("horiz-checkbox")) { + Checkbox.handleClick(jthis); + } else if (jthis.is(".back, .done, .cancel")) { + goBack(); + } else if (jthis.hasClass("menu")) { + // For mobile devices the home button allows immediate navigation back to the main menu, which may + // be useful if several pages have been navigated through. + _history = [_history[0], {href:"#menu?", transition:null}]; + goBack(); + } + }; + + if (e.type == "click") { + e.preventDefault(); // Prevent the browser trying to navigate to the href itself onclick. + if (!e.originalEvent) { // If event is triggered by calling the jQuery click() method. + if (jthis.is("li, input:radio")) { + start(e); + end(e); + } else if (jthis.is("input:checkbox")) { + var li = jthis.closest("li.horiz-checkbox"); + + if (li.length == 0) { // For a normal checkbox add pageX to the event and delegate handlePointer() + e.pageX = 0; + Checkbox.handlePointer(e, e.type); + } else { + // It's a horiz-checkbox. We need to find the label associated with the checkbox. + var label = jthis.parent("label"); // First check if the label contains the checkbox. + if (label.length == 0) { // If not look for a label containing a for matching the checkbox ID. + label = $("label[for='" + jthis.attr("id")+"']"); + } + if (label.length == 0) { // If not assume the label is the preceding sibling (a bit fragile..). + var items = li.children(); + label = $(items[items.index(jthis) - 1]); + } + + Checkbox.handleClick(label, e.type); + } + } + } + } else { // Event type is mousedown or touchstart so call main event start handler. + start(e); + } + }; + + /** + * This method handles keyboard input. Its main purpose is to detect the return key being pressed and if it + * has this method will attempt to trigger the handler bound to the right button, which should be a done/submit. + */ + var handleKeyboard = function(e) { + if (e.which == 13) { // Handle return key; + // When the return key is pressed find any right button present in the header and trigger a click + // on it, this should have the effect of triggering the done/submit handler for the form. + var jthis = $(e.target); + var done = jthis.closest(".main, .popup").find(".header a.right.button"); + done.trigger(START_EV).trigger(END_EV); + } + }; + +//------------------------------------------------------------------------------------------------------------------- +// Page Navigation Methods +//------------------------------------------------------------------------------------------------------------------- + + /** + * This event handler handles the synthetic refresh event that may be triggered on a top level page containing + * a scroll-area. The handler uses the id of the page to index the iScroll object then calls its refresh(). + */ + var handleRefresh = function(e) { + var id = $(this).attr("id"); + if (_scrollers[id] != null) { + _scrollers[id].refresh(); + } + } + + /** + * This method transitions to a selected destination. If the destination is "#" it simply returns, if there + * is no history the destination page is shown otherwise we transition using an animation. + * The ".split("?")[0]" blocks are there to cater for the case where the destination URL contains data to + * be passed between page fragments, where the data is delimited by a "?" and separated by "&". + */ + var goTo = function(destination, transition) { + iTablet.location = new Location(destination); + var previous = (_history.length == 0) ? null : _history[0].href; + + if (destination == "#" || destination == previous) { // The second test guards against multiple clicks + return; + } else if (!IS_MOBILE && _history.length == 0) { + var pages = $(".main"); + $(pages).each(function(index) { + var jthis = $(this); + var id = jthis.attr("id"); + if (("#" + id) == destination.split("?")[0]) { + jthis.show().trigger("show").find(".active").removeClass("active"); + _scrollers[id].refresh(); // Refresh the touch scroller on the new page. + _history.unshift({href:destination, transition:null}); + } else { + jthis.hide().trigger("hide"); + } + }); + } else { + var currentPage = $(_history[0].href.split("?")[0]); + var newPage = $(destination.split("?")[0]); + transition(currentPage, newPage, false); + _scrollers[newPage.attr("id")].refresh(); // Refresh the touch scroller on the new page. + _history.unshift({href:destination, transition:transition}); + } + }; + + /** + * This method transitions back to the previous item in the history using an animation. + * The ".split("?")[0]" blocks are there to cater for the case where the fragment URLs contain data to + * be passed between page fragments, where the data is delimited by a "?" and separated by "&". + */ + var goBack = function() { + if (_history.length > 1) { + iTablet.location = new Location(_history[1].href); + var transition = _history[0].transition; + var currentPage = $(_history[0].href.split("?")[0]); + var newPage = $(_history[1].href.split("?")[0]); + transition(currentPage, newPage, true); + _scrollers[newPage.attr("id")].refresh(); // Refresh the touch scroller on the new page. + _history.shift(); + + // Hide virtual keyboard. + document.activeElement.blur(); + } + }; + +//------------------------------------------------------------------------------------------------------------------- +// Animations +//------------------------------------------------------------------------------------------------------------------- + + /** + * This method detects support for CSS3 animations and transitions and uses them if present. It will attempt + * to use translate3d if present as this is most likely to be GPU accelerated and uses translate as fallback. + * In an ideal world this would be set up in a stylesheet using media queries, but unfortunately using + * prefixes for all of the keyframes is rather verbose and untidy and media query of translate3d only seems + * to be supported in WebKit, so scripting is needed whatever. This method "injects" the relevant styles. + */ + var initialiseCSSAnimations = function() { + // TODO It'd be nicer to use feature detection but that can be hard to get right - how do we *reliably" + // detect support for animationend and transitionend events, which are essential for these animations??? + if ((/android/gi).test(navigator.appVersion) || OPERA_VERSION <= 12.01) { + // Android has poor CSS3 animation support so use jQuery. + // Opera <= 12.01 doesn't have animationend which messes up state management, what's worse is that it + // passes the animationSupported test, so just bomb out early Opera 12.01 at least fails that... + return; + } + + var domPrefixes = ["Webkit", "Moz", "O", "ms", "Khtml"]; + + // For the following lookups ensure that prefix key is forced to lower case!! + var transitionEndLookup = { // Lookup transitionend Event. Note prefixes are different to DOM prefixes + "" : "transitionend", + "webkit" : "webkitTransitionEnd", + "moz" : "transitionend", + "o" : "oTransitionEnd", + "ms" : "transitionend" + }; + + var animationEndLookup = { // Lookup transitionend Event. Note prefixes are different to DOM prefixes + "" : "animationend", + "webkit" : "webkitAnimationEnd", + "moz" : "animationend", + "o" : "oAnimationEnd", + "ms" : "transitionend" + }; + + var has3d = false; + var domPrefix = ""; + var prefix = ""; + + var style = $("