diff options
| author | Kim van der Riet <kpvdr@apache.org> | 2012-08-03 12:13:32 +0000 |
|---|---|---|
| committer | Kim van der Riet <kpvdr@apache.org> | 2012-08-03 12:13:32 +0000 |
| commit | d43d1912b376322e27fdcda551a73f9ff5487972 (patch) | |
| tree | ce493e10baa95f44be8beb5778ce51783463196d /java/broker-plugins | |
| parent | 04877fec0c6346edec67072d7f2d247740cf2af5 (diff) | |
| download | qpid-python-d43d1912b376322e27fdcda551a73f9ff5487972.tar.gz | |
QPID-3858: Updated branch - merged from trunk r.1368650
git-svn-id: https://svn.apache.org/repos/asf/qpid/branches/asyncstore@1368910 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'java/broker-plugins')
132 files changed, 19488 insertions, 904 deletions
diff --git a/java/broker-plugins/access-control/MANIFEST.MF b/java/broker-plugins/access-control/MANIFEST.MF index 78072850e4..a8fb99995e 100644 --- a/java/broker-plugins/access-control/MANIFEST.MF +++ b/java/broker-plugins/access-control/MANIFEST.MF @@ -13,12 +13,10 @@ Bundle-ActivationPolicy: lazy Import-Package: org.apache.qpid, org.apache.qpid.exchange, org.apache.qpid.framing, - org.apache.qpid.junit.extensions.util, org.apache.qpid.protocol, org.apache.qpid.server.configuration, org.apache.qpid.server.configuration.plugins, org.apache.qpid.server.exchange, - org.apache.qpid.server.management, org.apache.qpid.server.logging, org.apache.qpid.server.logging.actors, org.apache.qpid.server.logging.subjects, diff --git a/java/broker-plugins/access-control/build.xml b/java/broker-plugins/access-control/build.xml index 89f8240fd5..df3346788c 100644 --- a/java/broker-plugins/access-control/build.xml +++ b/java/broker-plugins/access-control/build.xml @@ -17,15 +17,20 @@ - under the License. --> <project name="Qpid Broker-Plugins Access Control" default="build"> - <property name="module.depends" value="common broker broker-plugins" /> + <property name="module.depends" value="common broker" /> <property name="module.test.depends" value="test common/test broker/test management/common systests" /> <property name="module.manifest" value="MANIFEST.MF" /> <property name="module.plugin" value="true" /> + <property name="module.genpom" value="true"/> + <property name="module.genpom.args" value="-Sqpid-common=provided -Sqpid-broker=provided"/> + + <property name="broker-plugins-access-control.libs" value=""/> <import file="../../module.xml" /> <target name="bundle" depends="bundle-tasks"/> <target name="precompile" depends="gen_logging"/> + </project> diff --git a/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/PlainConfigurationTest.java b/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/PlainConfigurationTest.java index 25f903e860..c2282694fb 100644 --- a/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/PlainConfigurationTest.java +++ b/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/PlainConfigurationTest.java @@ -38,7 +38,7 @@ import org.apache.qpid.server.security.access.config.RuleSet; /** * These tests check that the ACL file parsing works correctly. - * + * * For each message that can be returned in a {@link ConfigurationException}, an ACL file is created that should trigger this * particular message. */ @@ -48,7 +48,7 @@ public class PlainConfigurationTest extends TestCase { File acl = File.createTempFile(getClass().getName() + getName(), "acl"); acl.deleteOnExit(); - + // Write ACL file PrintWriter aclWriter = new PrintWriter(new FileWriter(acl)); for (String line : aclData) @@ -70,14 +70,13 @@ public class PlainConfigurationTest extends TestCase // Load ruleset ConfigurationFile configFile = new PlainConfiguration(new File("doesnotexist")); configFile.load(); - + fail("fail"); } catch (ConfigurationException ce) { assertEquals(String.format(PlainConfiguration.CONFIG_NOT_FOUND_MSG, "doesnotexist"), ce.getMessage()); assertTrue(ce.getCause() instanceof FileNotFoundException); - assertEquals("doesnotexist (No such file or directory)", ce.getCause().getMessage()); } } diff --git a/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/RuleSetTest.java b/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/RuleSetTest.java index 4d46a32f45..f7cc60543d 100644 --- a/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/RuleSetTest.java +++ b/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/RuleSetTest.java @@ -40,12 +40,12 @@ import org.apache.qpid.test.utils.QpidTestCase; /** * This test checks that the {@link RuleSet} object which forms the core of the access control plugin performs correctly. - * + * * The ruleset is configured directly rather than using an external file by adding rules individually, calling the * {@link RuleSet#grant(Integer, String, Permission, Operation, ObjectType, ObjectProperties)} method. Then, the * access control mechanism is validated by checking whether operations would be authorised by calling the * {@link RuleSet#check(Principal, Operation, ObjectType, ObjectProperties)} method. - * + * * It ensure that permissions can be granted correctly on users directly, ACL groups (that is those * groups declared directly in the ACL itself), and External groups (that is a group from an External * Authentication Provider, such as an LDAP). @@ -82,11 +82,11 @@ public class RuleSetTest extends QpidTestCase { assertDenyGrantAllow(subject, operation, objectType, ObjectProperties.EMPTY); } - + public void assertDenyGrantAllow(Subject subject, Operation operation, ObjectType objectType, ObjectProperties properties) { - final Principal identity = UsernamePrincipal.getUsernamePrincipalFromSubject(subject); - + final Principal identity = subject.getPrincipals().iterator().next(); + assertEquals(Result.DENIED, _ruleSet.check(subject, operation, objectType, properties)); _ruleSet.grant(0, identity.getName(), Permission.ALLOW, operation, objectType, properties); assertEquals(1, _ruleSet.getRuleCount()); @@ -99,7 +99,7 @@ public class RuleSetTest extends QpidTestCase assertEquals(_ruleSet.getRuleCount(), 0); assertEquals(_ruleSet.getDefault(), _ruleSet.check(_testSubject, Operation.ACCESS, ObjectType.VIRTUALHOST, ObjectProperties.EMPTY)); } - + public void testVirtualHostAccess() throws Exception { assertDenyGrantAllow(_testSubject, Operation.ACCESS, ObjectType.VIRTUALHOST); @@ -114,7 +114,7 @@ public class RuleSetTest extends QpidTestCase { ObjectProperties properties = new ObjectProperties(_queueName); properties.put(ObjectProperties.Property.ROUTING_KEY, (String) null); - + assertDenyGrantAllow(_testSubject, Operation.CREATE, ObjectType.QUEUE, properties); } @@ -122,7 +122,7 @@ public class RuleSetTest extends QpidTestCase { ObjectProperties properties = new ObjectProperties(_exchangeName); properties.put(ObjectProperties.Property.TYPE, _exchangeType.asString()); - + assertDenyGrantAllow(_testSubject, Operation.CREATE, ObjectType.EXCHANGE, properties); } @@ -144,15 +144,15 @@ public class RuleSetTest extends QpidTestCase { ObjectProperties temporary = new ObjectProperties(); temporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE); - + ObjectProperties normal = new ObjectProperties(); normal.put(ObjectProperties.Property.AUTO_DELETE, Boolean.FALSE); - + assertEquals(Result.DENIED, _ruleSet.check(_testSubject, Operation.CONSUME, ObjectType.QUEUE, temporary)); _ruleSet.grant(0, TEST_USER, Permission.ALLOW, Operation.CONSUME, ObjectType.QUEUE, temporary); assertEquals(1, _ruleSet.getRuleCount()); assertEquals(Result.ALLOWED, _ruleSet.check(_testSubject, Operation.CONSUME, ObjectType.QUEUE, temporary)); - + // defer to global if exists, otherwise default answer - this is handled by the security manager assertEquals(Result.DEFER, _ruleSet.check(_testSubject, Operation.CONSUME, ObjectType.QUEUE, normal)); } @@ -164,17 +164,17 @@ public class RuleSetTest extends QpidTestCase { ObjectProperties temporary = new ObjectProperties(_queueName); temporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE); - + ObjectProperties normal = new ObjectProperties(_queueName); normal.put(ObjectProperties.Property.AUTO_DELETE, Boolean.FALSE); - + assertEquals(Result.DENIED, _ruleSet.check(_testSubject, Operation.CONSUME, ObjectType.QUEUE, temporary)); // should not matter if the temporary permission is processed first or last _ruleSet.grant(1, TEST_USER, Permission.ALLOW, Operation.CONSUME, ObjectType.QUEUE, normal); _ruleSet.grant(2, TEST_USER, Permission.ALLOW, Operation.CONSUME, ObjectType.QUEUE, temporary); assertEquals(2, _ruleSet.getRuleCount()); - + assertEquals(Result.ALLOWED, _ruleSet.check(_testSubject, Operation.CONSUME, ObjectType.QUEUE, normal)); assertEquals(Result.ALLOWED, _ruleSet.check(_testSubject, Operation.CONSUME, ObjectType.QUEUE, temporary)); } @@ -186,17 +186,17 @@ public class RuleSetTest extends QpidTestCase { ObjectProperties temporary = new ObjectProperties(_queueName); temporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE); - + ObjectProperties normal = new ObjectProperties(_queueName); normal.put(ObjectProperties.Property.AUTO_DELETE, Boolean.FALSE); - + assertEquals(Result.DENIED, _ruleSet.check(_testSubject, Operation.CONSUME, ObjectType.QUEUE, temporary)); // should not matter if the temporary permission is processed first or last _ruleSet.grant(1, TEST_USER, Permission.ALLOW, Operation.CONSUME, ObjectType.QUEUE, temporary); _ruleSet.grant(2, TEST_USER, Permission.ALLOW, Operation.CONSUME, ObjectType.QUEUE, normal); assertEquals(2, _ruleSet.getRuleCount()); - + assertEquals(Result.ALLOWED, _ruleSet.check(_testSubject, Operation.CONSUME, ObjectType.QUEUE, normal)); assertEquals(Result.ALLOWED, _ruleSet.check(_testSubject, Operation.CONSUME, ObjectType.QUEUE, temporary)); } @@ -204,7 +204,7 @@ public class RuleSetTest extends QpidTestCase /* * Test different rules for temporary queues. */ - + /** * The more generic rule first is used, so both requests are allowed. */ @@ -213,18 +213,18 @@ public class RuleSetTest extends QpidTestCase ObjectProperties named = new ObjectProperties(_queueName); ObjectProperties namedTemporary = new ObjectProperties(_queueName); namedTemporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE); - + assertEquals(Result.DENIED, _ruleSet.check(_testSubject, Operation.CREATE, ObjectType.QUEUE, named)); assertEquals(Result.DENIED, _ruleSet.check(_testSubject, Operation.CREATE, ObjectType.QUEUE, namedTemporary)); _ruleSet.grant(1, TEST_USER, Permission.ALLOW, Operation.CREATE, ObjectType.QUEUE, named); _ruleSet.grant(2, TEST_USER, Permission.DENY, Operation.CREATE, ObjectType.QUEUE, namedTemporary); assertEquals(2, _ruleSet.getRuleCount()); - + assertEquals(Result.ALLOWED, _ruleSet.check(_testSubject, Operation.CREATE, ObjectType.QUEUE, named)); assertEquals(Result.ALLOWED, _ruleSet.check(_testSubject, Operation.CREATE, ObjectType.QUEUE, namedTemporary)); } - + /** * The more specific rule is first, so those requests are denied. */ @@ -233,18 +233,18 @@ public class RuleSetTest extends QpidTestCase ObjectProperties named = new ObjectProperties(_queueName); ObjectProperties namedTemporary = new ObjectProperties(_queueName); namedTemporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE); - + assertEquals(Result.DENIED, _ruleSet.check(_testSubject, Operation.CREATE, ObjectType.QUEUE, named)); assertEquals(Result.DENIED, _ruleSet.check(_testSubject, Operation.CREATE, ObjectType.QUEUE, namedTemporary)); _ruleSet.grant(1, TEST_USER, Permission.DENY, Operation.CREATE, ObjectType.QUEUE, namedTemporary); _ruleSet.grant(2, TEST_USER, Permission.ALLOW, Operation.CREATE, ObjectType.QUEUE, named); assertEquals(2, _ruleSet.getRuleCount()); - + assertEquals(Result.ALLOWED, _ruleSet.check(_testSubject, Operation.CREATE, ObjectType.QUEUE, named)); assertEquals(Result.DENIED, _ruleSet.check(_testSubject, Operation.CREATE, ObjectType.QUEUE, namedTemporary)); } - + /** * The more specific rules are first, so those requests are denied. */ @@ -255,7 +255,7 @@ public class RuleSetTest extends QpidTestCase namedTemporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE); ObjectProperties namedDurable = new ObjectProperties(_queueName); namedDurable.put(ObjectProperties.Property.DURABLE, Boolean.TRUE); - + assertEquals(Result.DENIED, _ruleSet.check(_testSubject, Operation.CREATE, ObjectType.QUEUE, named)); assertEquals(Result.DENIED, _ruleSet.check(_testSubject, Operation.CREATE, ObjectType.QUEUE, namedTemporary)); assertEquals(Result.DENIED, _ruleSet.check(_testSubject, Operation.CREATE, ObjectType.QUEUE, namedDurable)); @@ -264,48 +264,48 @@ public class RuleSetTest extends QpidTestCase _ruleSet.grant(2, TEST_USER, Permission.DENY, Operation.CREATE, ObjectType.QUEUE, namedDurable); _ruleSet.grant(3, TEST_USER, Permission.ALLOW, Operation.CREATE, ObjectType.QUEUE, named); assertEquals(3, _ruleSet.getRuleCount()); - + assertEquals(Result.ALLOWED, _ruleSet.check(_testSubject, Operation.CREATE, ObjectType.QUEUE, named)); assertEquals(Result.DENIED, _ruleSet.check(_testSubject, Operation.CREATE, ObjectType.QUEUE, namedTemporary)); assertEquals(Result.DENIED, _ruleSet.check(_testSubject, Operation.CREATE, ObjectType.QUEUE, namedDurable)); } - + public void testNamedTemporaryQueueAllowed() { ObjectProperties named = new ObjectProperties(_queueName); ObjectProperties namedTemporary = new ObjectProperties(_queueName); namedTemporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE); - + assertEquals(Result.DENIED, _ruleSet.check(_testSubject, Operation.CREATE, ObjectType.QUEUE, named)); assertEquals(Result.DENIED, _ruleSet.check(_testSubject, Operation.CREATE, ObjectType.QUEUE, namedTemporary)); _ruleSet.grant(1, TEST_USER, Permission.ALLOW, Operation.CREATE, ObjectType.QUEUE, namedTemporary); _ruleSet.grant(2, TEST_USER, Permission.ALLOW, Operation.CREATE, ObjectType.QUEUE, named); assertEquals(2, _ruleSet.getRuleCount()); - + assertEquals(Result.ALLOWED, _ruleSet.check(_testSubject, Operation.CREATE, ObjectType.QUEUE, named)); assertEquals(Result.ALLOWED, _ruleSet.check(_testSubject, Operation.CREATE, ObjectType.QUEUE, namedTemporary)); } - + public void testNamedTemporaryQueueDeniedAllowed() { ObjectProperties named = new ObjectProperties(_queueName); ObjectProperties namedTemporary = new ObjectProperties(_queueName); namedTemporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE); - + assertEquals(Result.DENIED, _ruleSet.check(_testSubject, Operation.CREATE, ObjectType.QUEUE, named)); assertEquals(Result.DENIED, _ruleSet.check(_testSubject, Operation.CREATE, ObjectType.QUEUE, namedTemporary)); _ruleSet.grant(1, TEST_USER, Permission.ALLOW, Operation.CREATE, ObjectType.QUEUE, namedTemporary); _ruleSet.grant(2, TEST_USER, Permission.DENY, Operation.CREATE, ObjectType.QUEUE, named); assertEquals(2, _ruleSet.getRuleCount()); - + assertEquals(Result.DENIED, _ruleSet.check(_testSubject, Operation.CREATE, ObjectType.QUEUE, named)); assertEquals(Result.ALLOWED, _ruleSet.check(_testSubject, Operation.CREATE, ObjectType.QUEUE, namedTemporary)); } - /** - * Tests support for the {@link Rule#ALL} keyword. + /** + * Tests support for the {@link Rule#ALL} keyword. */ public void testAllowToAll() { @@ -316,13 +316,13 @@ public class RuleSetTest extends QpidTestCase assertEquals(Result.ALLOWED, _ruleSet.check(TestPrincipalUtils.createTestSubject("userb"),Operation.ACCESS, ObjectType.VIRTUALHOST, ObjectProperties.EMPTY)); } - /** + /** * Tests support for ACL groups (i.e. inline groups declared in the ACL file itself). */ public void testAclGroupsSupported() { - assertTrue(_ruleSet.addGroup("aclgroup", Arrays.asList(new String[] {"usera", "userb"}))); - + assertTrue(_ruleSet.addGroup("aclgroup", Arrays.asList(new String[] {"usera", "userb"}))); + _ruleSet.grant(1, "aclgroup", Permission.ALLOW, Operation.ACCESS, ObjectType.VIRTUALHOST, ObjectProperties.EMPTY); assertEquals(1, _ruleSet.getRuleCount()); @@ -331,14 +331,14 @@ public class RuleSetTest extends QpidTestCase assertEquals(Result.DEFER, _ruleSet.check(TestPrincipalUtils.createTestSubject("userc"),Operation.ACCESS, ObjectType.VIRTUALHOST, ObjectProperties.EMPTY)); } - /** + /** * Tests support for nested ACL groups. */ public void testNestedAclGroupsSupported() { assertTrue(_ruleSet.addGroup("aclgroup1", Arrays.asList(new String[] {"userb"}))); - assertTrue(_ruleSet.addGroup("aclgroup2", Arrays.asList(new String[] {"usera", "aclgroup1"}))); - + assertTrue(_ruleSet.addGroup("aclgroup2", Arrays.asList(new String[] {"usera", "aclgroup1"}))); + _ruleSet.grant(1, "aclgroup2", Permission.ALLOW, Operation.ACCESS, ObjectType.VIRTUALHOST, ObjectProperties.EMPTY); assertEquals(1, _ruleSet.getRuleCount()); @@ -346,7 +346,7 @@ public class RuleSetTest extends QpidTestCase assertEquals(Result.ALLOWED, _ruleSet.check(TestPrincipalUtils.createTestSubject("userb"),Operation.ACCESS, ObjectType.VIRTUALHOST, ObjectProperties.EMPTY)); } - /** + /** * Tests support for nested External groups (i.e. those groups coming from an external source such as an LDAP). */ public void testExternalGroupsSupported() @@ -358,7 +358,7 @@ public class RuleSetTest extends QpidTestCase assertEquals(Result.ALLOWED, _ruleSet.check(TestPrincipalUtils.createTestSubject("usera", "extgroup1"),Operation.ACCESS, ObjectType.VIRTUALHOST, ObjectProperties.EMPTY)); assertEquals(Result.DENIED, _ruleSet.check(TestPrincipalUtils.createTestSubject("userb", "extgroup2"),Operation.ACCESS, ObjectType.VIRTUALHOST, ObjectProperties.EMPTY)); } - + /** * Rule order in the ACL determines the outcome of the check. This test ensures that a user who is * granted explicit permission on an object, is granted that access even although late a group @@ -367,7 +367,7 @@ public class RuleSetTest extends QpidTestCase public void testAllowDeterminedByRuleOrder() { assertTrue(_ruleSet.addGroup("aclgroup", Arrays.asList(new String[] {"usera"}))); - + _ruleSet.grant(1, "usera", Permission.ALLOW, Operation.ACCESS, ObjectType.VIRTUALHOST, ObjectProperties.EMPTY); _ruleSet.grant(2, "aclgroup", Permission.DENY, Operation.ACCESS, ObjectType.VIRTUALHOST, ObjectProperties.EMPTY); assertEquals(2, _ruleSet.getRuleCount()); @@ -382,10 +382,10 @@ public class RuleSetTest extends QpidTestCase public void testDenyDeterminedByRuleOrder() { assertTrue(_ruleSet.addGroup("aclgroup", Arrays.asList(new String[] {"usera"}))); - + _ruleSet.grant(1, "aclgroup", Permission.DENY, Operation.ACCESS, ObjectType.VIRTUALHOST, ObjectProperties.EMPTY); _ruleSet.grant(2, "usera", Permission.ALLOW, Operation.ACCESS, ObjectType.VIRTUALHOST, ObjectProperties.EMPTY); - + assertEquals(2, _ruleSet.getRuleCount()); assertEquals(Result.DENIED, _ruleSet.check(TestPrincipalUtils.createTestSubject("usera"),Operation.ACCESS, ObjectType.VIRTUALHOST, ObjectProperties.EMPTY)); diff --git a/java/broker-plugins/experimental/shutdown/MANIFEST.MF b/java/broker-plugins/experimental/shutdown/MANIFEST.MF deleted file mode 100644 index 0bd0a835e4..0000000000 --- a/java/broker-plugins/experimental/shutdown/MANIFEST.MF +++ /dev/null @@ -1,16 +0,0 @@ -Manifest-Version: 1.0 -Bundle-ManifestVersion: 2 -Bundle-Name: Experimental Shutdown -Bundle-Description: Experimental Qpid Broker Shutdown Plugin -Bundle-License: http://www.apache.org/licenses/LICENSE-2.0.txt -Bundle-DocURL: http://qpid.apache.org/ -Bundle-SymbolicName: broker-plugins-experimental-shutdown;singleton:=true -Bundle-Version: 1.0.0 -Bundle-Activator: org.apache.qpid.shutdown.Activator -Import-Package: javax.management;resolution:=optional, - org.apache.log4j, - org.osgi.framework, - org.apache.qpid.server.management -Bundle-RequiredExecutionEnvironment: J2SE-1.5 -Bundle-ActivationPolicy: lazy - diff --git a/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/Activator.java b/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/Activator.java deleted file mode 100644 index 2b7fa33784..0000000000 --- a/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/Activator.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.shutdown; - - -import org.apache.log4j.Logger; -import org.osgi.framework.BundleActivator; -import org.osgi.framework.BundleContext; - -public class Activator implements BundleActivator -{ - private static final Logger _logger = Logger.getLogger(Activator.class); - - private Shutdown _shutdown = null; - - /** @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext) */ - public void start(BundleContext ctx) throws Exception { - _shutdown = new Shutdown(); - if (ctx != null) - { - ctx.registerService(ShutdownMBean.class.getName(), _shutdown, null); - } - - _shutdown.register(); - - _logger.info("Shutdown plugin MBean registered"); - } - - /** @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) */ - public void stop(BundleContext ctx) throws Exception - { - if (_shutdown != null) - { - _shutdown.unregister(); - _shutdown = null; - } - - _logger.info("Shutdown plugin MBean unregistered"); - } -} diff --git a/java/broker-plugins/experimental/shutdown/src/main/java/shutdown.bnd b/java/broker-plugins/experimental/shutdown/src/main/java/shutdown.bnd deleted file mode 100755 index 60af4b89e8..0000000000 --- a/java/broker-plugins/experimental/shutdown/src/main/java/shutdown.bnd +++ /dev/null @@ -1,25 +0,0 @@ -# -# 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. -# - -ver: 0.17.0 - -Bundle-SymbolicName: qpid-shutdown-plugin -Bundle-Version: ${ver} -Export-Package: *;version=${ver} -Bundle-RequiredExecutionEnvironment: J2SE-1.5 diff --git a/java/broker-plugins/extras/MANIFEST.MF b/java/broker-plugins/extras/MANIFEST.MF deleted file mode 100644 index f4ef6e8178..0000000000 --- a/java/broker-plugins/extras/MANIFEST.MF +++ /dev/null @@ -1,21 +0,0 @@ -Bundle-ManifestVersion: 2 -Bundle-Name: Qpid Broker-Plugins Extras -Bundle-SymbolicName: broker-plugins-extras -Bundle-Description: Extra exchange types plugin for Qpid. -Bundle-License: http://www.apache.org/licenses/LICENSE-2.0.txt -Bundle-DocURL: http://www.apache.org/ -Bundle-Activator: org.apache.qpid.extras.Activator -Private-Package: org.apache.qpid.extras, - org.apache.qpid.extras.exchanges.diagnostic, - org.apache.qpid.extras.exchanges.example -Import-Package: org.apache.qpid, - org.apache.qpid.framing, - org.apache.qpid.junit.extensions.util, - org.apache.qpid.protocol, - org.apache.qpid.server.exchange, - org.apache.qpid.server.management, - org.apache.qpid.server.queue, - org.apache.qpid.server.virtualhost, - javax.management;version=1.0.0, - javax.management.openmbean;version=1.0.0, - org.osgi.framework;version=1.3 diff --git a/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/diagnostic/DiagnosticExchange.java b/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/diagnostic/DiagnosticExchange.java deleted file mode 100644 index af56b50437..0000000000 --- a/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/diagnostic/DiagnosticExchange.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * - * 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.extras.exchanges.diagnostic; - -import org.apache.log4j.Logger; - -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.FieldTable; -import org.apache.qpid.management.common.mbeans.annotations.MBeanConstructor; -import org.apache.qpid.management.common.mbeans.annotations.MBeanDescription; -import org.apache.qpid.server.binding.Binding; -import org.apache.qpid.server.exchange.AbstractExchange; -import org.apache.qpid.server.exchange.AbstractExchangeMBean; -import org.apache.qpid.server.exchange.ExchangeType; -import org.apache.qpid.server.message.InboundMessage; -import org.apache.qpid.server.queue.AMQQueue; -import org.apache.qpid.server.virtualhost.VirtualHost; - -import javax.management.JMException; -import javax.management.openmbean.OpenDataException; -import javax.management.openmbean.TabularData; -import java.util.ArrayList; -import java.util.Map; -import java.util.UUID; - -/** - * This is a special diagnostic exchange type which doesn't actually do anything - * with messages. When it receives a message, it writes information about the - * current memory usage to the "memory" property of the message and places it on the - * diagnosticqueue for retrieval - */ -public class DiagnosticExchange extends AbstractExchange -{ - private static final Logger _logger = Logger.getLogger(DiagnosticExchange.class); - - public static final AMQShortString DIAGNOSTIC_EXCHANGE_CLASS = new AMQShortString("x-diagnostic"); - public static final AMQShortString DIAGNOSTIC_EXCHANGE_NAME = new AMQShortString("diagnostic"); - - /** - * MBean class implementing the management interfaces. - */ - @MBeanDescription("Management Bean for Diagnostic Exchange") - private final class DiagnosticExchangeMBean extends AbstractExchangeMBean<DiagnosticExchange> - { - /** - * Usual constructor. - * - * @throws JMException - */ - @MBeanConstructor("Creates an MBean for AMQ Diagnostic exchange") - public DiagnosticExchangeMBean() throws JMException - { - super(DiagnosticExchange.this); - - init(); - } - - /** - * Returns nothing, there can be no tabular data for this... - * - * @throws OpenDataException - * @returns null - * TODO or can there? Could this actually return all the information in one easy to read table? - */ - public TabularData bindings() throws OpenDataException - { - return null; - } - - /** - * This exchange type doesn't support queues, so this method does - * nothing. - * - * @param queueName the queue you'll fail to create - * @param binding the binding you'll fail to create - * @throws JMException an exception that will never be thrown - */ - @Override - public void createNewBinding(String queueName, String binding) throws JMException - { - // No Op - } - - /** - * This exchange type doesn't support queues. - * - * @see #createNewBinding(String, String) - */ - @Override - public void removeBinding(String queueName, String binding) throws JMException - { - // No Op - } - } - - - public static final ExchangeType<DiagnosticExchange> TYPE = new ExchangeType<DiagnosticExchange>() - { - - public AMQShortString getName() - { - return DIAGNOSTIC_EXCHANGE_CLASS; - } - - public Class<DiagnosticExchange> getExchangeClass() - { - return DiagnosticExchange.class; - } - - public DiagnosticExchange newInstance(UUID id, VirtualHost host, - AMQShortString name, - boolean durable, - int ticket, - boolean autoDelete) throws AMQException - { - DiagnosticExchange exch = new DiagnosticExchange(); - exch.initialise(id, host,name,durable,ticket,autoDelete); - return exch; - } - - public AMQShortString getDefaultExchangeName() - { - return DIAGNOSTIC_EXCHANGE_NAME ; - } - }; - - public DiagnosticExchange() - { - super(TYPE); - } - - /** - * Creates a new MBean instance - * - * @return the newly created MBean - * @throws AMQException - * if something goes wrong - */ - protected AbstractExchangeMBean createMBean() throws JMException - { - return new DiagnosticExchange.DiagnosticExchangeMBean(); - } - - public void registerQueue(String routingKey, AMQQueue queue, Map<String, Object> args) throws AMQException - { - // No op - } - - - public boolean isBound(AMQShortString routingKey, AMQQueue queue) - { - return false; - } - - public boolean isBound(AMQShortString routingKey) - { - return false; - } - - public boolean isBound(AMQQueue queue) - { - return false; - } - - public boolean hasBindings() - { - return false; - } - - public ArrayList<AMQQueue> doRoute(InboundMessage payload) - { - //TODO shouldn't modify messages... perhaps put a new message on the queue? - AMQQueue q = getQueueRegistry().getQueue(new AMQShortString("diagnosticqueue")); - ArrayList<AMQQueue> queues = new ArrayList<AMQQueue>(); - queues.add(q); - return queues; - } - - - public boolean isBound(AMQShortString routingKey, FieldTable arguments, - AMQQueue queue) { - // TODO Auto-generated method stub - return false; - } - - protected void onBind(final Binding binding) - { - // No op - } - - protected void onUnbind(final Binding binding) - { - // No op - } -} diff --git a/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/diagnostic/DiagnosticExchangeType.java b/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/diagnostic/DiagnosticExchangeType.java deleted file mode 100644 index c0f325e42f..0000000000 --- a/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/diagnostic/DiagnosticExchangeType.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * - * 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.extras.exchanges.diagnostic; - -import java.util.UUID; - -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.server.exchange.ExchangeType; -import org.apache.qpid.server.virtualhost.VirtualHost; - -/** - * Exchange type class for getting hold of the exchange. - */ -public final class DiagnosticExchangeType implements ExchangeType<DiagnosticExchange> -{ - - public AMQShortString getName() - { - return DiagnosticExchange.DIAGNOSTIC_EXCHANGE_CLASS; - } - - public Class<DiagnosticExchange> getExchangeClass() - { - return DiagnosticExchange.class; - } - - public DiagnosticExchange newInstance(UUID id, VirtualHost host, AMQShortString name, boolean durable, int ticket, boolean autoDelete) - throws AMQException - { - DiagnosticExchange exch = new DiagnosticExchange(); - exch.initialise(id, host, name, durable, ticket, autoDelete); - return exch; - } - - public AMQShortString getDefaultExchangeName() - { - return DiagnosticExchange.DIAGNOSTIC_EXCHANGE_NAME; - } -} diff --git a/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/example/TestExchange.java b/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/example/TestExchange.java deleted file mode 100644 index 691f4c8d0b..0000000000 --- a/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/example/TestExchange.java +++ /dev/null @@ -1,262 +0,0 @@ -package org.apache.qpid.extras.exchanges.example; -/* - * - * 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. - * - */ - - -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.FieldTable; -import org.apache.qpid.server.binding.Binding; -import org.apache.qpid.server.configuration.ConfiguredObject; -import org.apache.qpid.server.configuration.ExchangeConfigType; -import org.apache.qpid.server.configuration.VirtualHostConfig; -import org.apache.qpid.server.exchange.Exchange; -import org.apache.qpid.server.exchange.ExchangeReferrer; -import org.apache.qpid.server.exchange.ExchangeType; -import org.apache.qpid.server.message.InboundMessage; -import org.apache.qpid.server.queue.AMQQueue; -import org.apache.qpid.server.queue.BaseQueue; -import org.apache.qpid.server.virtualhost.VirtualHost; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Map; -import java.util.UUID; - -public class TestExchange implements Exchange -{ - - public void close() throws AMQException - { - } - - - - public void addBindingListener(final BindingListener listener) - { - //To change body of implemented methods use File | Settings | File Templates. - } - - public void removeBindingListener(final BindingListener listener) - { - //To change body of implemented methods use File | Settings | File Templates. - } - - public AMQShortString getNameShortString() - { - return null; - } - - public AMQShortString getTypeShortString() - { - return null; - } - - public boolean hasBindings() - { - return false; - } - - public boolean isBound(String bindingKey, AMQQueue queue) - { - return false; - } - - public boolean isBound(String bindingKey, Map<String, Object> arguments, AMQQueue queue) - { - return false; - } - - public boolean isBound(String bindingKey) - { - return false; - } - - public void addCloseTask(final Task task) - { - - } - - public void removeCloseTask(final Task task) - { - - } - - public Exchange getAlternateExchange() - { - return null; - } - - public Map<String, Object> getArguments() - { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - public long getBindingCount() - { - return 0; //To change body of implemented methods use File | Settings | File Templates. - } - - public long getBindingCountHigh() - { - return 0; //To change body of implemented methods use File | Settings | File Templates. - } - - public long getMsgReceives() - { - return 0; //To change body of implemented methods use File | Settings | File Templates. - } - - public long getMsgRoutes() - { - return 0; //To change body of implemented methods use File | Settings | File Templates. - } - - public long getByteReceives() - { - return 0; //To change body of implemented methods use File | Settings | File Templates. - } - - public long getByteRoutes() - { - return 0; //To change body of implemented methods use File | Settings | File Templates. - } - - public long getCreateTime() - { - return 0; //To change body of implemented methods use File | Settings | File Templates. - } - - public void setAlternateExchange(Exchange exchange) - { - - } - - public void removeReference(ExchangeReferrer exchange) - { - - } - - public void addReference(ExchangeReferrer exchange) - { - - } - - public boolean hasReferrers() - { - return false; - } - - public void addBinding(final Binding binding) - { - - } - - public void removeBinding(final Binding binding) - { - - } - - public Collection<Binding> getBindings() - { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - public void initialise(VirtualHost host, AMQShortString name, boolean durable, boolean autoDelete) - throws AMQException - { - } - - public VirtualHostConfig getVirtualHost() - { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - public String getName() - { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - public ExchangeType getType() - { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - public boolean isAutoDelete() - { - return false; - } - - public boolean isBound(AMQShortString routingKey, FieldTable arguments, AMQQueue queue) - { - return false; - } - - public boolean isBound(AMQShortString routingKey, AMQQueue queue) - { - return false; - } - - public boolean isBound(AMQShortString routingKey) - { - return false; - } - - public boolean isBound(AMQQueue queue) - { - return false; - } - - public UUID getId() - { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - public ExchangeConfigType getConfigType() - { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - public ConfiguredObject getParent() - { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - public boolean isDurable() - { - return false; - } - - public ArrayList<? extends BaseQueue> route(InboundMessage message) - { - return new ArrayList<AMQQueue>(); - } - - public int getTicket() - { - return 0; - } - - public void initialise(UUID id, VirtualHost arg0, AMQShortString arg1, boolean arg2, int arg3, boolean arg4) - throws AMQException - { - } -} diff --git a/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/example/TestExchangeType.java b/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/example/TestExchangeType.java deleted file mode 100644 index 5c454b9d0e..0000000000 --- a/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/example/TestExchangeType.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * - * 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.extras.exchanges.example; - -import java.util.UUID; - -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.server.exchange.Exchange; -import org.apache.qpid.server.exchange.ExchangeType; -import org.apache.qpid.server.virtualhost.VirtualHost; - -public class TestExchangeType implements ExchangeType -{ - - public Class getExchangeClass() - { - return TestExchange.class; - } - - public AMQShortString getName() - { - return null; - } - - public Exchange newInstance(UUID id, VirtualHost host, AMQShortString name, boolean durable, - int token, boolean autoDelete) - throws AMQException - { - TestExchange ex = new TestExchange(); - ex.initialise(id, host, name, durable, token, autoDelete); - return ex; - } - - public AMQShortString getDefaultExchangeName() - { - return new AMQShortString("test.exchange"); - } - -} diff --git a/java/broker-plugins/extras/src/test/java/org/apache/qpid/server/plugins/ExtrasTest.java b/java/broker-plugins/extras/src/test/java/org/apache/qpid/server/plugins/ExtrasTest.java deleted file mode 100644 index 458f9a1846..0000000000 --- a/java/broker-plugins/extras/src/test/java/org/apache/qpid/server/plugins/ExtrasTest.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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.plugins; - -import junit.framework.TestCase; -import org.apache.commons.configuration.PropertiesConfiguration; - -import org.apache.qpid.server.configuration.ServerConfiguration; -import org.apache.qpid.server.exchange.ExchangeType; -import org.apache.qpid.server.registry.ApplicationRegistry; -import org.apache.qpid.server.registry.IApplicationRegistry; -import org.apache.qpid.server.util.TestApplicationRegistry; - -import java.util.Map; - -public class ExtrasTest extends TestCase -{ - private static final String TEST_EXCHANGE_CLASS = "org.apache.qpid.extras.exchanges.example.TestExchangeType"; - - private static final String PLUGIN_DIRECTORY = System.getProperty("example.plugin.target"); - private static final String CACHE_DIRECTORY = System.getProperty("example.cache.target"); - - private IApplicationRegistry _registry; - - @Override - public void setUp() throws Exception - { - PropertiesConfiguration properties = new PropertiesConfiguration(); - properties.addProperty("plugin-directory", PLUGIN_DIRECTORY); - properties.addProperty("cache-directory", CACHE_DIRECTORY); - ServerConfiguration config = new ServerConfiguration(properties); - - // This Test requires an application Registry - ApplicationRegistry.initialise(new TestApplicationRegistry(config)); - _registry = ApplicationRegistry.getInstance(); - } - - @Override - public void tearDown() throws Exception - { - ApplicationRegistry.remove(); - } - - public void testLoadExchanges() throws Exception - { - PluginManager manager = _registry.getPluginManager(); - Map<String, ExchangeType<?>> exchanges = manager.getExchanges(); - assertNotNull("No exchanges found in " + PLUGIN_DIRECTORY, exchanges); - assertEquals("Wrong number of exchanges found in " + PLUGIN_DIRECTORY, 2, exchanges.size()); - assertNotNull("Wrong exchange found in " + PLUGIN_DIRECTORY, exchanges.get(TEST_EXCHANGE_CLASS)); - } - - public void testNoExchanges() throws Exception - { - PluginManager manager = new PluginManager("/path/to/nowhere", "/tmp", null); - Map<String, ExchangeType<?>> exchanges = manager.getExchanges(); - assertTrue("Exchanges found", exchanges.isEmpty()); - } -} diff --git a/java/broker-plugins/firewall/MANIFEST.MF b/java/broker-plugins/firewall/MANIFEST.MF index 6ceea119da..a302921d03 100644 --- a/java/broker-plugins/firewall/MANIFEST.MF +++ b/java/broker-plugins/firewall/MANIFEST.MF @@ -12,12 +12,10 @@ Bundle-ClassPath: . Bundle-ActivationPolicy: lazy Import-Package: org.apache.qpid, org.apache.qpid.framing, - org.apache.qpid.junit.extensions.util, org.apache.qpid.protocol, org.apache.qpid.server.configuration, org.apache.qpid.server.configuration.plugins, org.apache.qpid.server.exchange, - org.apache.qpid.server.management, org.apache.qpid.server.plugins, org.apache.qpid.server.queue, org.apache.qpid.server.security, diff --git a/java/broker-plugins/firewall/build.xml b/java/broker-plugins/firewall/build.xml index 576435de7f..6ae6a35b89 100644 --- a/java/broker-plugins/firewall/build.xml +++ b/java/broker-plugins/firewall/build.xml @@ -17,13 +17,18 @@ - under the License. --> <project name="Qpid Broker-Plugins Firewall" default="build"> - <property name="module.depends" value="common broker broker-plugins" /> + <property name="module.depends" value="common broker" /> <property name="module.test.depends" value="test broker/test common/test management/common" /> <property name="module.manifest" value="MANIFEST.MF" /> <property name="module.plugin" value="true" /> + <property name="module.genpom" value="true"/> + <property name="module.genpom.args" value="-Sqpid-common=provided -Sqpid-broker=provided"/> + + <property name="broker-plugins-firewall.libs" value=""/> <import file="../../module.xml" /> <target name="bundle" depends="bundle-tasks" /> + </project> diff --git a/java/broker-plugins/management-http/MANIFEST.MF b/java/broker-plugins/management-http/MANIFEST.MF new file mode 100644 index 0000000000..cca10d3f89 --- /dev/null +++ b/java/broker-plugins/management-http/MANIFEST.MF @@ -0,0 +1,70 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Qpid Broker-Plugins Management HTTP +Bundle-SymbolicName: broker-plugins-management-http +Bundle-Description: HTTP management plugin for Qpid. +Bundle-License: http://www.apache.org/licenses/LICENSE-2.0.txt +Bundle-DocURL: http://www.apache.org/ +Bundle-Version: 1.0.0 +Bundle-Activator: org.apache.qpid.server.management.plugin.ManagementActivator +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Bundle-ClassPath: . +Bundle-ActivationPolicy: lazy +Import-Package: org.apache.qpid, + org.apache.qpid.framing, + org.apache.qpid.protocol, + org.apache.qpid.common, + org.apache.qpid.server.security.auth, + org.apache.qpid.server.security.auth.manager, + org.apache.qpid.server.security.auth.sasl, + org.apache.qpid.server.binding, + org.apache.qpid.server.exchange, + org.apache.qpid.server.logging, + org.apache.qpid.server.message, + org.apache.qpid.server.model, + org.apache.qpid.server.model.adapter, + org.apache.qpid.server.model.impl, + org.apache.qpid.server.configuration, + org.apache.qpid.server.configuration.plugins, + org.apache.qpid.server.connection, + org.apache.qpid.server.plugins, + org.apache.qpid.server.protocol, + org.apache.qpid.server.queue, + org.apache.qpid.server.subscription, + org.apache.qpid.server.registry, + org.apache.qpid.server.security, + org.apache.qpid.server.security.access, + org.apache.qpid.server.stats, + org.apache.qpid.server.virtualhost, + org.apache.qpid.util, + org.eclipse.jetty.server;version=7.6.3, + org.eclipse.jetty.server.session;version=7.6.3, + org.eclipse.jetty.server.ssl;version=7.6.3, + org.eclipse.jetty.server.nio;version=7.6.3, + org.eclipse.jetty.security;version=7.6.3, + org.eclipse.jetty.http;version=7.6.3, + org.eclipse.jetty.io;version=7.6.3, + org.eclipse.jetty.io.nio;version=7.6.3, + org.eclipse.jetty.servlet;version=7.6.3, + org.eclipse.jetty.util.ssl;version=7.6.3, + org.apache.commons.codec;version=1.3.0, + org.apache.commons.codec.binary;version=1.3.0, + org.apache.commons.configuration;version=1.0.0, + org.apache.commons.lang;version=1.0.0, + org.apache.commons.lang.builder;version=1.0.0, + org.apache.log4j;version=1.0.0, + org.codehaus.jackson;version=1.9.0, + org.codehaus.jackson.map;version=1.9.0, + javax.crypto, + javax.crypto.spec, + javax.security.auth, + javax.security.auth.callback, + javax.security.sasl, + javax.servlet, + javax.servlet.http, + javax.management;version=1.0.0, + javax.management.openmbean;version=1.0.0, + org.osgi.util.tracker;version=1.0.0, + org.osgi.framework;version=1.3 +Private-Package: org.apache.qpid.server.management.plugin.impl +Export-Package: org.apache.qpid.server.management.plugin;uses:="org.osgi.framework" diff --git a/java/broker-plugins/management-http/build.xml b/java/broker-plugins/management-http/build.xml new file mode 100644 index 0000000000..b792cb292e --- /dev/null +++ b/java/broker-plugins/management-http/build.xml @@ -0,0 +1,57 @@ +<!-- + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you under the Apache License, Version 2.0 (the + - "License"); you may not use this file except in compliance + - with the License. You may obtain a copy of the License at + - + - http://www.apache.org/licenses/LICENSE-2.0 + - + - Unless required by applicable law or agreed to in writing, + - software distributed under the License is distributed on an + - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + - KIND, either express or implied. See the License for the + - specific language governing permissions and limitations + - under the License. + --> +<project name="Qpid Broker-Plugins Management HTTP" default="build"> + + <condition property="systests.optional.depends" value="bdbstore" else=""> + <or> + <and> + <contains string="${modules.opt}" substring="bdbstore"/> + <contains string="${profile}" substring="bdb"/> + </and> + <and> + <istrue value="${optional}"/> + <contains string="${profile}" substring="bdb"/> + </and> + </or> + </condition> + + <property name="module.depends" value="common broker" /> + <property name="module.test.depends" value="systests test broker/test common/test management/common client ${systests.optional.depends}" /> + + <property name="module.manifest" value="MANIFEST.MF" /> + <property name="module.plugin" value="true" /> + <property name="module.genpom" value="true"/> + <property name="module.genpom.args" value="-Sqpid-common=provided -Sqpid-broker=provided"/> + + <property name="broker-plugins-management-http.libs" value=""/> + + <import file="../../module.xml" /> + + <target name="precompile"> + <unwar src="${project.root}/${dojo}" dest="${module.classes}/resources/dojo"> + <patternset> + <exclude name="META-INF/**"/> + <exclude name="WEB-INF/**"/> + <exclude name="**/*.uncompressed.js"/> + </patternset> + </unwar> + </target> + + <target name="bundle" depends="bundle-tasks" /> +</project> diff --git a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/Management.java b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/Management.java new file mode 100644 index 0000000000..c2f9b73b54 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/Management.java @@ -0,0 +1,248 @@ +/* + * + * 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.management.plugin; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.log4j.Logger; +import org.apache.qpid.server.management.plugin.servlet.DefinedFileServlet; +import org.apache.qpid.server.management.plugin.servlet.FileServlet; +import org.apache.qpid.server.management.plugin.servlet.api.ExchangesServlet; +import org.apache.qpid.server.management.plugin.servlet.api.VhostsServlet; +import org.apache.qpid.server.management.plugin.servlet.rest.LogRecordsServlet; +import org.apache.qpid.server.management.plugin.servlet.rest.MessageContentServlet; +import org.apache.qpid.server.management.plugin.servlet.rest.MessageServlet; +import org.apache.qpid.server.management.plugin.servlet.rest.RestServlet; +import org.apache.qpid.server.management.plugin.servlet.rest.SaslServlet; +import org.apache.qpid.server.management.plugin.servlet.rest.StructureServlet; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Binding; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.Connection; +import org.apache.qpid.server.model.Exchange; +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.Session; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.model.User; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.SessionManager; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.eclipse.jetty.server.ssl.SslSocketConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +public class Management +{ + + private final Logger _logger = Logger.getLogger(Management.class); + + private Broker _broker; + + private Collection<Server> _servers = new ArrayList<Server>(); + + public Management() throws ConfigurationException, IOException + { + _broker = ApplicationRegistry.getInstance().getBroker(); + + Collection<Port> ports = _broker.getPorts(); + int httpPort = -1, httpsPort = -1; + for (Port port : ports) + { + if (port.getProtocols().contains(Protocol.HTTP)) + { + if (port.getTransports().contains(Transport.TCP)) + { + httpPort = port.getPort(); + } + } + if (port.getProtocols().contains(Protocol.HTTPS)) + { + if (port.getTransports().contains(Transport.SSL)) + { + httpsPort = port.getPort(); + } + } + } + + if (httpPort != -1 || httpsPort != -1) + { + _servers.add(createServer(httpPort, httpsPort)); + if (_logger.isDebugEnabled()) + { + _logger.debug(_servers.size() + " server(s) defined"); + } + } + else + { + if (_logger.isInfoEnabled()) + { + _logger.info("Cannot create web server as neither HTTP nor HTTPS port specified"); + } + } + } + + @SuppressWarnings("unchecked") + private Server createServer(int port, int sslPort) throws IOException, ConfigurationException + { + if (_logger.isInfoEnabled()) + { + _logger.info("Starting up web server on" + (port == -1 ? "" : " HTTP port " + port) + + (sslPort == -1 ? "" : " HTTPS port " + sslPort)); + } + + Server server = new Server(); + + if (port != -1) + { + SelectChannelConnector connector = new SelectChannelConnector(); + connector.setPort(port); + if (sslPort != -1) + { + connector.setConfidentialPort(sslPort); + } + server.addConnector(connector); + } + + if (sslPort != -1) + { + IApplicationRegistry appRegistry = ApplicationRegistry.getInstance(); + String keyStorePath = getKeyStorePath(appRegistry); + + SslContextFactory factory = new SslContextFactory(); + factory.setKeyStorePath(keyStorePath); + factory.setKeyStorePassword(appRegistry.getConfiguration().getManagementKeyStorePassword()); + + SslSocketConnector connector = new SslSocketConnector(factory); + connector.setPort(sslPort); + server.addConnector(connector); + } + + ServletContextHandler root = new ServletContextHandler(ServletContextHandler.SESSIONS); + root.setContextPath("/"); + server.setHandler(root); + + root.addServlet(new ServletHolder(new VhostsServlet(_broker)), "/api/vhosts/*"); + root.addServlet(new ServletHolder(new ExchangesServlet(_broker)), "/api/exchanges/*"); + + addRestServlet(root, "broker"); + addRestServlet(root, "virtualhost", VirtualHost.class); + addRestServlet(root, "authenticationprovider", AuthenticationProvider.class); + addRestServlet(root, "user", AuthenticationProvider.class, User.class); + addRestServlet(root, "exchange", VirtualHost.class, Exchange.class); + addRestServlet(root, "queue", VirtualHost.class, Queue.class); + addRestServlet(root, "connection", VirtualHost.class, Connection.class); + addRestServlet(root, "binding", VirtualHost.class, Exchange.class, Queue.class, Binding.class); + addRestServlet(root, "port", Port.class); + addRestServlet(root, "session", VirtualHost.class, Connection.class, Session.class); + + root.addServlet(new ServletHolder(new StructureServlet(_broker)), "/rest/structure"); + root.addServlet(new ServletHolder(new MessageServlet(_broker)), "/rest/message/*"); + root.addServlet(new ServletHolder(new MessageContentServlet(_broker)), "/rest/message-content/*"); + + root.addServlet(new ServletHolder(new LogRecordsServlet(_broker)), "/rest/logrecords"); + + root.addServlet(new ServletHolder(new SaslServlet(_broker)), "/rest/sasl"); + + root.addServlet(new ServletHolder(new DefinedFileServlet("management.html")), "/management"); + + root.addServlet(new ServletHolder(FileServlet.INSTANCE), "*.js"); + root.addServlet(new ServletHolder(FileServlet.INSTANCE), "*.css"); + root.addServlet(new ServletHolder(FileServlet.INSTANCE), "*.html"); + root.addServlet(new ServletHolder(FileServlet.INSTANCE), "*.png"); + root.addServlet(new ServletHolder(FileServlet.INSTANCE), "*.gif"); + root.addServlet(new ServletHolder(FileServlet.INSTANCE), "*.jpg"); + root.addServlet(new ServletHolder(FileServlet.INSTANCE), "*.jpeg"); + root.addServlet(new ServletHolder(FileServlet.INSTANCE), "*.json"); + root.addServlet(new ServletHolder(FileServlet.INSTANCE), "*.txt"); + root.addServlet(new ServletHolder(FileServlet.INSTANCE), "*.xsl"); + + final SessionManager sessionManager = root.getSessionHandler().getSessionManager(); + + sessionManager.setMaxInactiveInterval(60 * 15); + + return server; + } + + private void addRestServlet(ServletContextHandler root, String name, Class<? extends ConfiguredObject>... hierarchy) + { + root.addServlet(new ServletHolder(new RestServlet(_broker, hierarchy)), "/rest/" + name + "/*"); + } + + public void start() throws Exception + { + for (Server server : _servers) + { + server.start(); + } + } + + public void stop() throws Exception + { + for (Server server : _servers) + { + server.stop(); + } + } + + private String getKeyStorePath(IApplicationRegistry appRegistry) throws ConfigurationException, FileNotFoundException + { + String keyStorePath = null; + if (System.getProperty("javax.net.ssl.keyStore") != null) + { + keyStorePath = System.getProperty("javax.net.ssl.keyStore"); + } + else + { + keyStorePath = appRegistry.getConfiguration().getManagementKeyStorePath(); + } + + if (keyStorePath == null) + { + throw new ConfigurationException("Management SSL keystore path not defined, unable to start SSL protected HTTP connector"); + } + else + { + File ksf = new File(keyStorePath); + if (!ksf.exists()) + { + throw new FileNotFoundException("Cannot find management SSL keystore file: " + ksf); + } + if (!ksf.canRead()) + { + throw new FileNotFoundException("Cannot read management SSL keystore file: " + ksf + ". Check permissions."); + } + } + return keyStorePath; + } + +} diff --git a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ManagementActivator.java b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ManagementActivator.java new file mode 100644 index 0000000000..09b7e08bfb --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ManagementActivator.java @@ -0,0 +1,73 @@ +/* + * + * 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.management.plugin; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +public class ManagementActivator implements BundleActivator +{ + private static final Logger _logger = Logger.getLogger(ManagementActivator.class); + + + private BundleContext _ctx; + private String _bundleName; + private Management _managementService; + + + public void start(final BundleContext ctx) throws Exception + { + _ctx = ctx; + if (!ApplicationRegistry.getInstance().getConfiguration().getHTTPManagementEnabled() + && !ApplicationRegistry.getInstance().getConfiguration().getHTTPSManagementEnabled()) + { + _logger.info("Management plugin is disabled!"); + ctx.getBundle().uninstall(); + return; + } + _managementService = new Management(); + _managementService.start(); + _bundleName = ctx.getBundle().getSymbolicName(); + + // register the service + _logger.info("Registering management plugin: " + _bundleName); + _ctx.registerService(Management.class.getName(), _managementService, null); + _ctx.registerService(ConfigurationPluginFactory.class.getName(), ManagementConfiguration.FACTORY, null); + } + + public void stop(final BundleContext bundleContext) throws Exception + { + if (_managementService != null) + { + _logger.info("Stopping management plugin: " + _bundleName); + + _managementService.stop(); + + // null object references + _managementService = null; + } + _ctx = null; + } + +} diff --git a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ManagementConfiguration.java b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ManagementConfiguration.java new file mode 100644 index 0000000000..3866da8f89 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/ManagementConfiguration.java @@ -0,0 +1,77 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.management.plugin; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.XMLConfiguration; + +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory; + +import java.util.Arrays; +import java.util.List; + +public class ManagementConfiguration extends ConfigurationPlugin +{ + CompositeConfiguration _finalConfig; + + public static final ConfigurationPluginFactory FACTORY = new ConfigurationPluginFactory() + { + public ConfigurationPlugin newInstance(String path, Configuration config) throws ConfigurationException + { + ConfigurationPlugin instance = new ManagementConfiguration(); + instance.setConfiguration(path, config); + return instance; + } + + public List<String> getParentPaths() + { + return Arrays.asList("management"); + } + }; + + public String[] getElementsProcessed() + { + return new String[] { "" }; + } + + public Configuration getConfiguration() + { + return _finalConfig; + } + + + @Override + public void validateConfiguration() throws ConfigurationException + { + // Valid Configuration either has xml links to new files + _finalConfig = new CompositeConfiguration(getConfig()); + List subFiles = getConfig().getList("xml[@fileName]"); + for (Object subFile : subFiles) + { + _finalConfig.addConfiguration(new XMLConfiguration((String) subFile)); + } + + } + +} diff --git a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/DefinedFileServlet.java b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/DefinedFileServlet.java new file mode 100644 index 0000000000..d8a8395550 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/DefinedFileServlet.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.qpid.server.management.plugin.servlet; + +import java.io.IOException; +import java.io.InputStream; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class DefinedFileServlet extends HttpServlet +{ + + private static final String FILENAME_INIT_PARAMETER = "filename"; + + private String _filename; + + public DefinedFileServlet() + { + super(); + } + + public DefinedFileServlet(String filename) + { + _filename = filename; + } + + @Override + public void init() throws ServletException + { + ServletConfig config = getServletConfig(); + String fileName = config.getInitParameter(FILENAME_INIT_PARAMETER); + if (fileName != null && !"".equals(fileName)) + { + _filename = fileName; + } + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + final ServletOutputStream output = response.getOutputStream(); + InputStream fileInput = getClass().getResourceAsStream("/resources/"+_filename); + + if(fileInput != null) + { + byte[] buffer = new byte[1024]; + response.setStatus(HttpServletResponse.SC_OK); + int read = 0; + + while((read = fileInput.read(buffer)) > 0) + { + output.write(buffer, 0, read); + } + } + else + { + response.sendError(404, "unknown file: "+ _filename); + } + } +} diff --git a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/FileServlet.java b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/FileServlet.java new file mode 100644 index 0000000000..f8ca082d79 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/FileServlet.java @@ -0,0 +1,109 @@ +/* + * + * 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.management.plugin.servlet; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class FileServlet extends HttpServlet +{ + public static final FileServlet INSTANCE = new FileServlet(); + + private static final Map<String, String> CONTENT_TYPES; + + static + { + + Map<String, String> contentTypes = new HashMap<String, String>(); + contentTypes.put("js", "application/javascript"); + contentTypes.put("html", "text/html"); + contentTypes.put("css", "text/css"); + contentTypes.put("json", "application/json"); + contentTypes.put("jpg", "image/jpg"); + contentTypes.put("png", "image/png"); + contentTypes.put("gif", "image/gif"); + CONTENT_TYPES = Collections.unmodifiableMap(contentTypes); + } + + + public FileServlet() + { + } + + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + String filename = request.getServletPath(); + if(filename.contains(".")) + { + String suffix = filename.substring(filename.lastIndexOf('.')+1); + String contentType = CONTENT_TYPES.get(suffix); + if(contentType != null) + { + response.setContentType(contentType); + } + } + URL resourceURL = getClass().getResource("/resources" + filename); + if(resourceURL != null) + { + response.setStatus(HttpServletResponse.SC_OK); + InputStream fileInput = resourceURL.openStream(); + try + { + byte[] buffer = new byte[1024]; + int read = 0; + ServletOutputStream output = response.getOutputStream(); + try + { + while((read = fileInput.read(buffer)) != -1) + { + output.write(buffer, 0, read); + } + } + finally + { + output.close(); + } + } + finally + { + fileInput.close(); + } + } + else + { + response.sendError(404, "unknown file: "+ filename); + } + + } + +} diff --git a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/api/ExchangesServlet.java b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/api/ExchangesServlet.java new file mode 100644 index 0000000000..a3c5ec68a2 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/api/ExchangesServlet.java @@ -0,0 +1,208 @@ +/* + * + * 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.management.plugin.servlet.api; + +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.ObjectReader; + +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.State; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.registry.ApplicationRegistry; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class ExchangesServlet extends HttpServlet +{ + + + private Broker _broker; + + public ExchangesServlet() + { + super(); + _broker = ApplicationRegistry.getInstance().getBroker(); + } + + public ExchangesServlet(Broker broker) + { + _broker = broker; + } + + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + response.setContentType("application/json"); + response.setStatus(HttpServletResponse.SC_OK); + + Collection<VirtualHost> vhosts = _broker.getVirtualHosts(); + Collection<Exchange> exchanges = new ArrayList<Exchange>(); + Collection<Map<String,Object>> outputObject = new ArrayList<Map<String,Object>>(); + + final PrintWriter writer = response.getWriter(); + + ObjectMapper mapper = new ObjectMapper(); + String vhostName = null; + String exchangeName = null; + + if(request.getPathInfo() != null && request.getPathInfo().length()>0) + { + String path = request.getPathInfo().substring(1); + String[] parts = path.split("/"); + vhostName = parts.length == 0 ? "" : parts[0]; + if(parts.length > 1) + { + exchangeName = parts[1]; + } + } + + for(VirtualHost vhost : vhosts) + { + if(vhostName == null || vhostName.equals(vhost.getName())) + { + for(Exchange exchange : vhost.getExchanges()) + { + if(exchangeName == null || exchangeName.equals(exchange.getName())) + { + outputObject.add(convertToObject(exchange)); + if(exchangeName != null) + { + break; + } + } + } + if(vhostName != null) + { + break; + } + } + } + + mapper.writeValue(writer, outputObject); + + } + + private Map<String,Object> convertToObject(final Exchange exchange) + { + Map<String, Object> object = new LinkedHashMap<String, Object>(); + object.put("name",exchange.getName()); + object.put("type", exchange.getExchangeType()); + object.put("durable", exchange.isDurable()); + object.put("auto-delete", exchange.getLifetimePolicy() == LifetimePolicy.AUTO_DELETE); + + Map<String,Object> arguments = new HashMap<String, Object>(); + for(String key : exchange.getAttributeNames()) + { + if(!key.equals(Exchange.TYPE)) + { + arguments.put(key, exchange.getAttribute(key)); + } + } + object.put("arguments", arguments); + return object; + } + + protected void doPut(final HttpServletRequest request, final HttpServletResponse response) + throws ServletException, IOException + { + + response.setContentType("application/json"); + + + String vhostName = null; + String exchangeName = null; + if(request.getPathInfo() != null && request.getPathInfo().length()>0) + { + String path = request.getPathInfo().substring(1); + String[] parts = path.split("/"); + vhostName = parts.length == 0 ? "" : parts[0]; + if(parts.length > 1) + { + exchangeName = parts[1]; + } + } + if(vhostName == null) + { + response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED); + } + else if (exchangeName == null) + { + response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED); + } + else + { + VirtualHost vhost = null; + for(VirtualHost host : _broker.getVirtualHosts()) + { + if(host.getName().equals(vhostName)) + { + vhost = host; + } + } + if(vhost == null) + { + response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED); + } + else + { + response.setStatus(HttpServletResponse.SC_NO_CONTENT); + ObjectMapper mapper = new ObjectMapper(); + Map<String,Object> exchangeObject = mapper.readValue(request.getInputStream(), LinkedHashMap.class); + + final boolean isDurable = exchangeObject.get("durable") instanceof Boolean + && ((Boolean)exchangeObject.get("durable")); + final boolean isAutoDelete = exchangeObject.get("auto_delete") instanceof Boolean + && ((Boolean)exchangeObject.get("auto_delete")); + + final String type = (String) exchangeObject.get("type"); + final Map<String, Object> attributes = new HashMap<String, Object>(exchangeObject); + attributes.remove("durable"); + attributes.remove("auto_delete"); + attributes.remove("type"); + + vhost.createExchange(exchangeName, State.ACTIVE, isDurable, + isAutoDelete ? LifetimePolicy.AUTO_DELETE : LifetimePolicy.PERMANENT, + 0l, + type, + attributes); + } + + + + } + + + + } +} diff --git a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/api/VhostsServlet.java b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/api/VhostsServlet.java new file mode 100644 index 0000000000..b2c0fcfe52 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/api/VhostsServlet.java @@ -0,0 +1,118 @@ +/* + * + * 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.management.plugin.servlet.api; + +import org.codehaus.jackson.map.ObjectMapper; + +import org.apache.qpid.common.QpidProperties; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.protocol.AMQConnectionModel; +import org.apache.qpid.server.registry.ApplicationRegistry; + + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.*; + +public class VhostsServlet extends HttpServlet +{ + + + private Broker _broker; + + public VhostsServlet() + { + super(); + _broker = ApplicationRegistry.getInstance().getBroker(); + } + + public VhostsServlet(Broker broker) + { + _broker = broker; + } + + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { +System.out.println("Get /api/vhosts"); + response.setContentType("application/json"); + response.setStatus(HttpServletResponse.SC_OK); + + Collection<VirtualHost> vhosts = _broker.getVirtualHosts(); + + + + final PrintWriter writer = response.getWriter(); + + ObjectMapper mapper = new ObjectMapper(); + + if(request.getPathInfo() == null || request.getPathInfo().length()==0) + { + + LinkedHashMap<String, Object> vhostObject = new LinkedHashMap<String, Object>(); + List<Map> vhostList = new ArrayList<Map>(); + + for(VirtualHost vhost : vhosts) + { + vhostList.add(Collections.singletonMap("name", vhost.getName())); + } + mapper.writeValue(writer, vhostList); + } + else + { + LinkedHashMap<String, Object> vhostObject = new LinkedHashMap<String, Object>(); + String vhostName = request.getPathInfo().substring(1); + + for(VirtualHost vhost : vhosts) + { + if(vhostName.equals(vhost.getName())) + { + vhostObject.put("name", vhost.getName()); + break; + } + } + mapper.writeValue(writer, vhostObject); + } + } + + + protected void doPut(final HttpServletRequest request, final HttpServletResponse response) + throws ServletException, IOException + { + + response.setContentType("application/json"); + response.setStatus(HttpServletResponse.SC_NO_CONTENT); + + if(request.getPathInfo() != null && request.getPathInfo().length()>0) + { + String vhostName = request.getPathInfo().substring(1); + _broker.createVirtualHost(vhostName, State.ACTIVE, true, LifetimePolicy.PERMANENT, 0L, Collections.EMPTY_MAP); + } + + + } +} diff --git a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/AbstractServlet.java b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/AbstractServlet.java new file mode 100644 index 0000000000..a76bd98179 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/AbstractServlet.java @@ -0,0 +1,208 @@ +/* + * + * 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.management.plugin.servlet.rest; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.security.Principal; +import java.util.Collections; +import javax.security.auth.Subject; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import org.apache.commons.codec.binary.Base64; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.auth.manager.AnonymousAuthenticationManager; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; + +public abstract class AbstractServlet extends HttpServlet +{ + private final Broker _broker; + + protected AbstractServlet() + { + super(); + _broker = ApplicationRegistry.getInstance().getBroker(); + } + + protected AbstractServlet(Broker broker) + { + _broker = broker; + } + + @Override + protected final void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException + { + setAuthorizedSubject(request); + try + { + onGet(request, resp); + } + finally + { + clearAuthorizedSubject(); + } + } + + protected void onGet(HttpServletRequest request, HttpServletResponse resp) throws IOException, ServletException + { + super.doGet(request, resp); + } + + private void clearAuthorizedSubject() + { + org.apache.qpid.server.security.SecurityManager.setThreadSubject(null); + } + + + private void setAuthorizedSubject(HttpServletRequest request) + { + HttpSession session = request.getSession(true); + Subject subject = (Subject) session.getAttribute("subject"); + + if(subject == null) + { + Principal principal = request.getUserPrincipal(); + if(principal != null) + { + subject = new Subject(false, Collections.singleton(principal),Collections.emptySet(), + Collections.emptySet()); + } + else + { + String header = request.getHeader("Authorization"); + + /* + * TODO - Should configure whether basic authentication is allowed... and in particular whether it + * should be allowed over non-ssl connections + * */ + + if (header != null) + { + String[] tokens = header.split("\\s"); + if(tokens.length >= 2 + && "BASIC".equalsIgnoreCase(tokens[0])) + { + String[] credentials = (new String(Base64.decodeBase64(tokens[1].getBytes()))).split(":",2); + if(credentials.length == 2) + { + SocketAddress address = getSocketAddress(request); + AuthenticationManager authenticationManager = + ApplicationRegistry.getInstance().getAuthenticationManager(address); + AuthenticationResult authResult = + authenticationManager.authenticate(credentials[0], credentials[1]); + subject = authResult.getSubject(); + + } + } + } + } + } + if (subject == null) + { + subject = AnonymousAuthenticationManager.ANONYMOUS_SUBJECT; + } + org.apache.qpid.server.security.SecurityManager.setThreadSubject(subject); + + } + + protected Subject getSubject(HttpSession session) + { + return (Subject)session.getAttribute("subject"); + } + + @Override + protected final void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + setAuthorizedSubject(req); + try + { + onPost(req, resp); + } + finally + { + clearAuthorizedSubject(); + } + + } + + protected void onPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + super.doPost(req, resp); + } + + @Override + protected final void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + setAuthorizedSubject(req); + try + { + onPut(req, resp); + + } + finally + { + clearAuthorizedSubject(); + } + } + + protected void onPut(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException + { + super.doPut(req,resp); + } + + @Override + protected final void doDelete(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException + { + setAuthorizedSubject(req); + try + { + onDelete(req, resp); + } + finally + { + clearAuthorizedSubject(); + } + } + + protected void onDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + super.doDelete(req, resp); + } + + + protected Broker getBroker() + { + return _broker; + } + + protected SocketAddress getSocketAddress(HttpServletRequest request) + { + return InetSocketAddress.createUnresolved(request.getServerName(), request.getServerPort()); + } +} diff --git a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/KeyComparator.java b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/KeyComparator.java new file mode 100644 index 0000000000..3d862ce321 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/KeyComparator.java @@ -0,0 +1,53 @@ +/* + * + * 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.management.plugin.servlet.rest; + +import java.util.Comparator; +import java.util.Map; + +class KeyComparator implements Comparator<Map> +{ + private String _key; + + public KeyComparator(final String key) + { + _key = key; + } + + public int compare(final Map o1, final Map o2) + { + Comparable left = (Comparable) o1.get(_key); + Comparable right = (Comparable) o2.get(_key); + + int result; + if(left == null) + { + result = right == null ? 0 : -1; + } + else + { + result = left.compareTo(right); + } + + return result; + + } +} diff --git a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogRecordsServlet.java b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogRecordsServlet.java new file mode 100644 index 0000000000..404793b592 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogRecordsServlet.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.qpid.server.management.plugin.servlet.rest; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.qpid.server.logging.LogRecorder; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.SerializationConfig; + +public class LogRecordsServlet extends AbstractServlet +{ + public LogRecordsServlet() + { + super(ApplicationRegistry.getInstance().getBroker()); + } + + public LogRecordsServlet(Broker broker) + { + super(broker); + } + + @Override + protected void onGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + response.setContentType("application/json"); + response.setStatus(HttpServletResponse.SC_OK); + + response.setHeader("Cache-Control","no-cache"); + response.setHeader("Pragma","no-cache"); + response.setDateHeader ("Expires", 0); + + ApplicationRegistry applicationRegistry = (ApplicationRegistry) ApplicationRegistry.getInstance(); + List<Map<String,Object>> logRecords = new ArrayList<Map<String, Object>>(); + + for(LogRecorder.Record record : applicationRegistry.getLogRecorder()) + { + logRecords.add(logRecordToObject(record)); + } + + final PrintWriter writer = response.getWriter(); + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true); + mapper.writeValue(writer, logRecords); + + response.setStatus(HttpServletResponse.SC_OK); + + } + + private Map<String, Object> logRecordToObject(LogRecorder.Record record) + { + Map<String, Object> recordMap = new LinkedHashMap<String, Object>(); + recordMap.put("id",record.getId()); + recordMap.put("timestamp", record.getTimestamp()); + recordMap.put("level", record.getLevel()); + recordMap.put("thread", record.getThreadName()); + recordMap.put("logger", record.getLogger()); + recordMap.put("message", record.getMessage()); + return recordMap; + } + +} diff --git a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MapComparator.java b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MapComparator.java new file mode 100644 index 0000000000..84d987813b --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MapComparator.java @@ -0,0 +1,74 @@ +/* + * + * 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.management.plugin.servlet.rest; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Map; + +class MapComparator implements Comparator<Map> +{ + private Comparator<Map>[] _sortKeys; + + public MapComparator(final String[] sortKeys) + { + _sortKeys = parseKeys(sortKeys); + } + + private static Comparator<Map>[] parseKeys(final String[] sortKeys) + { + Comparator<Map>[] comparators = new Comparator[sortKeys.length]; + for(int i = 0; i < sortKeys.length; i++) + { + String key = sortKeys[i]; + + if(key.startsWith("+") || key.startsWith(" ")) + { + comparators[i] = new KeyComparator(key.substring(1)); + } + else if(key.startsWith("-")) + { + comparators[i] = Collections.reverseOrder(new KeyComparator(key.substring(1))); + } + else + { + comparators[i] = new KeyComparator(key); + } + } + return comparators; + } + + + public int compare(final Map o1, final Map o2) + { + int result = 0; + for(int i = 0; i < _sortKeys.length; i++) + { + result = _sortKeys[i].compare(o1, o2); + if(result != 0) + { + return result; + } + } + return 0; + } + +} diff --git a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageContentServlet.java b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageContentServlet.java new file mode 100644 index 0000000000..bc87f0bcc5 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageContentServlet.java @@ -0,0 +1,176 @@ +/* + * 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.management.plugin.servlet.rest; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.qpid.server.message.MessageReference; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.queue.QueueEntryVisitor; + +public class MessageContentServlet extends AbstractServlet +{ + public MessageContentServlet() + { + super(); + } + + public MessageContentServlet(Broker broker) + { + super(broker); + } + + @Override + protected void onGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + + if(request.getPathInfo() != null && request.getPathInfo().length()>0 && request.getPathInfo().substring(1).split("/").length > 2) + { + getMessageContent(request, response); + } + + } + + private void getMessageContent(HttpServletRequest request, HttpServletResponse response) throws IOException + { + Queue queue = getQueueFromRequest(request); + String path[] = request.getPathInfo().substring(1).split("/"); + MessageFinder finder = new MessageFinder(Long.parseLong(path[2])); + queue.visit(finder); + if(finder.isFound()) + { + response.setContentType(finder.getMimeType()); + response.setContentLength((int) finder.getSize()); + response.getOutputStream().write(finder.getContent()); + + } + + } + + private Queue getQueueFromRequest(HttpServletRequest request) + { + List<String> names = new ArrayList<String>(); + // TODO - validation that there is a vhost and queue and only those in the path + if(request.getPathInfo() != null && request.getPathInfo().length()>0) + { + String path = request.getPathInfo().substring(1); + names.addAll(Arrays.asList(path.split("/"))); + } + String vhostName = names.get(0); + String queueName = names.get(1); + + VirtualHost vhost = null; + + for(VirtualHost vh : getBroker().getVirtualHosts()) + { + if(vh.getName().equals(vhostName)) + { + vhost = vh; + break; + } + } + + return getQueueFromVirtualHost(queueName, vhost); + } + + private Queue getQueueFromVirtualHost(String queueName, VirtualHost vhost) + { + Queue queue = null; + + for(Queue q : vhost.getQueues()) + { + if(q.getName().equals(queueName)) + { + queue = q; + break; + } + } + return queue; + } + + private class MessageFinder implements QueueEntryVisitor + { + private final long _messageNumber; + private String _mimeType; + private long _size; + private byte[] _content; + private boolean _found; + + private MessageFinder(long messageNumber) + { + _messageNumber = messageNumber; + } + + + public boolean visit(QueueEntry entry) + { + ServerMessage message = entry.getMessage(); + if(message != null) + { + if(_messageNumber == message.getMessageNumber()) + { + MessageReference reference = message.newReference(); + + _mimeType = message.getMessageHeader().getMimeType(); + _size = message.getSize(); + _content = new byte[(int)_size]; + _found = true; + message.getContent(ByteBuffer.wrap(_content),0); + reference.release(); + return true; + } + + } + return false; + } + + public String getMimeType() + { + return _mimeType; + } + + public long getSize() + { + return _size; + } + + public byte[] getContent() + { + return _content; + } + + public boolean isFound() + { + return _found; + } + } + + +} diff --git a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageServlet.java b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageServlet.java new file mode 100644 index 0000000000..6e7bc1d935 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageServlet.java @@ -0,0 +1,502 @@ +/* + * 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.management.plugin.servlet.rest; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.MessageReference; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.queue.QueueEntryVisitor; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.security.access.Operation; +import org.apache.qpid.server.subscription.Subscription; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.SerializationConfig; + +public class MessageServlet extends AbstractServlet +{ + private static final Logger LOGGER = Logger.getLogger(MessageServlet.class); + + public MessageServlet() + { + super(); + } + + public MessageServlet(Broker broker) + { + super(broker); + } + + @Override + protected void onGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + + if(request.getPathInfo() != null && request.getPathInfo().length()>0 && request.getPathInfo().substring(1).split("/").length > 2) + { + getMessageContent(request, response); + } + else + { + getMessageList(request, response); + } + + } + + private void getMessageContent(HttpServletRequest request, HttpServletResponse response) throws IOException + { + Queue queue = getQueueFromRequest(request); + String path[] = request.getPathInfo().substring(1).split("/"); + MessageFinder messageFinder = new MessageFinder(Long.parseLong(path[2])); + queue.visit(messageFinder); + + response.setStatus(HttpServletResponse.SC_OK); + + response.setHeader("Cache-Control","no-cache"); + response.setHeader("Pragma","no-cache"); + response.setDateHeader ("Expires", 0); + + final PrintWriter writer = response.getWriter(); + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true); + mapper.writeValue(writer, messageFinder.getMessageObject()); + } + + private void getMessageList(HttpServletRequest request, HttpServletResponse response) throws IOException + { + Queue queue = getQueueFromRequest(request); + + int first = -1; + int last = -1; + String range = request.getHeader("Range"); + if(range != null) + { + String[] boundaries = range.split("=")[1].split("-"); + first = Integer.parseInt(boundaries[0]); + last = Integer.parseInt(boundaries[1]); + } + final MessageCollector messageCollector = new MessageCollector(first, last); + queue.visit(messageCollector); + + response.setContentType("application/json"); + final List<Map<String, Object>> messages = messageCollector.getMessages(); + int queueSize = ((Number) queue.getStatistics().getStatistic(Queue.QUEUE_DEPTH_MESSAGES)).intValue(); + String min = messages.isEmpty() ? "0" : messages.get(0).get("position").toString(); + String max = messages.isEmpty() ? "0" : messages.get(messages.size()-1).get("position").toString(); + response.setHeader("Content-Range", (min + "-" + max + "/" + queueSize)); + response.setStatus(HttpServletResponse.SC_OK); + + response.setHeader("Cache-Control","no-cache"); + response.setHeader("Pragma","no-cache"); + response.setDateHeader ("Expires", 0); + + final PrintWriter writer = response.getWriter(); + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true); + mapper.writeValue(writer, messages); + } + + private Queue getQueueFromRequest(HttpServletRequest request) + { + List<String> names = new ArrayList<String>(); + // TODO - validation that there is a vhost and queue and only those in the path + if(request.getPathInfo() != null && request.getPathInfo().length()>0) + { + String path = request.getPathInfo().substring(1); + names.addAll(Arrays.asList(path.split("/"))); + } + String vhostName = names.get(0); + String queueName = names.get(1); + + VirtualHost vhost = null; + + for(VirtualHost vh : getBroker().getVirtualHosts()) + { + if(vh.getName().equals(vhostName)) + { + vhost = vh; + break; + } + } + + return getQueueFromVirtualHost(queueName, vhost); + } + + private Queue getQueueFromVirtualHost(String queueName, VirtualHost vhost) + { + Queue queue = null; + + for(Queue q : vhost.getQueues()) + { + + if(q.getName().equals(queueName)) + { + queue = q; + break; + } + } + return queue; + } + + private abstract static class QueueEntryTransaction implements VirtualHost.TransactionalOperation + { + private final Queue _sourceQueue; + private final List _messageIds; + + protected QueueEntryTransaction(Queue sourceQueue, List messageIds) + { + _sourceQueue = sourceQueue; + _messageIds = messageIds; + } + + + public void withinTransaction(final VirtualHost.Transaction txn) + { + + _sourceQueue.visit(new QueueEntryVisitor() + { + + public boolean visit(final QueueEntry entry) + { + final ServerMessage message = entry.getMessage(); + if(message != null) + { + final long messageId = message.getMessageNumber(); + if (_messageIds.remove(messageId) || (messageId <= (long) Integer.MAX_VALUE + && _messageIds.remove(Integer.valueOf((int)messageId)))) + { + updateEntry(entry, txn); + } + } + return _messageIds.isEmpty(); + } + }); + } + + + protected abstract void updateEntry(QueueEntry entry, VirtualHost.Transaction txn); + } + + private static class MoveTransaction extends QueueEntryTransaction + { + private final Queue _destinationQueue; + + public MoveTransaction(Queue sourceQueue, List<Long> messageIds, Queue destinationQueue) + { + super(sourceQueue, messageIds); + _destinationQueue = destinationQueue; + } + + protected void updateEntry(QueueEntry entry, VirtualHost.Transaction txn) + { + txn.move(entry, _destinationQueue); + } + } + + private static class CopyTransaction extends QueueEntryTransaction + { + private final Queue _destinationQueue; + + public CopyTransaction(Queue sourceQueue, List<Long> messageIds, Queue destinationQueue) + { + super(sourceQueue, messageIds); + _destinationQueue = destinationQueue; + } + + protected void updateEntry(QueueEntry entry, VirtualHost.Transaction txn) + { + txn.copy(entry, _destinationQueue); + } + } + + private static class DeleteTransaction extends QueueEntryTransaction + { + public DeleteTransaction(Queue sourceQueue, List<Long> messageIds) + { + super(sourceQueue, messageIds); + } + + protected void updateEntry(QueueEntry entry, VirtualHost.Transaction txn) + { + txn.dequeue(entry); + } + } + + + + private class MessageCollector implements QueueEntryVisitor + { + private final int _first; + private final int _last; + private int _position = -1; + private final List<Map<String, Object>> _messages = new ArrayList<Map<String, Object>>(); + + private MessageCollector(int first, int last) + { + _first = first; + _last = last; + } + + + public boolean visit(QueueEntry entry) + { + + _position++; + if((_first == -1 || _position >= _first) && (_last == -1 || _position <= _last)) + { + final Map<String, Object> messageObject = convertToObject(entry, false); + messageObject.put("position", _position); + _messages.add(messageObject); + } + return _last != -1 && _position > _last; + } + + public List<Map<String, Object>> getMessages() + { + return _messages; + } + } + + + private class MessageFinder implements QueueEntryVisitor + { + private final long _messageNumber; + private Map<String, Object> _messageObject; + + private MessageFinder(long messageNumber) + { + _messageNumber = messageNumber; + } + + + public boolean visit(QueueEntry entry) + { + ServerMessage message = entry.getMessage(); + if(message != null) + { + if(_messageNumber == message.getMessageNumber()) + { + MessageReference reference = message.newReference(); + _messageObject = convertToObject(entry, true); + reference.release(); + return true; + } + } + return false; + } + + public Map<String, Object> getMessageObject() + { + return _messageObject; + } + } + + private Map<String, Object> convertToObject(QueueEntry entry, boolean includeContent) + { + Map<String, Object> object = new LinkedHashMap<String, Object>(); + object.put("size", entry.getSize()); + object.put("deliveryCount", entry.getDeliveryCount()); + object.put("state",entry.isAvailable() + ? "Available" + : entry.isAcquired() + ? "Acquired" + : ""); + final Subscription deliveredSubscription = entry.getDeliveredSubscription(); + object.put("deliveredTo", deliveredSubscription == null ? null : deliveredSubscription.getSubscriptionID()); + ServerMessage message = entry.getMessage(); + + if(message != null) + { + convertMessageProperties(object, message); + if(includeContent) + { + convertMessageHeaders(object, message); + } + } + + return object; + } + + private void convertMessageProperties(Map<String, Object> object, ServerMessage message) + { + object.put("id", message.getMessageNumber()); + object.put("arrivalTime",message.getArrivalTime()); + object.put("persistent", message.isPersistent()); + + final AMQMessageHeader messageHeader = message.getMessageHeader(); + if(messageHeader != null) + { + addIfPresent(object, "messageId", messageHeader.getMessageId()); + addIfPresent(object, "expirationTime", messageHeader.getExpiration()); + addIfPresent(object, "applicationId", messageHeader.getAppId()); + addIfPresent(object, "correlationId", messageHeader.getCorrelationId()); + addIfPresent(object, "encoding", messageHeader.getEncoding()); + addIfPresent(object, "mimeType", messageHeader.getMimeType()); + addIfPresent(object, "priority", messageHeader.getPriority()); + addIfPresent(object, "replyTo", messageHeader.getReplyTo()); + addIfPresent(object, "timestamp", messageHeader.getTimestamp()); + addIfPresent(object, "type", messageHeader.getType()); + addIfPresent(object, "userId", messageHeader.getUserId()); + } + + } + + private void addIfPresent(Map<String, Object> object, String name, Object property) + { + if(property != null) + { + object.put(name, property); + } + } + + private void convertMessageHeaders(Map<String, Object> object, ServerMessage message) + { + final AMQMessageHeader messageHeader = message.getMessageHeader(); + if(messageHeader != null) + { + Map<String, Object> headers = new HashMap<String,Object>(); + for(String headerName : messageHeader.getHeaderNames()) + { + headers.put(headerName, messageHeader.getHeader(headerName)); + } + object.put("headers", headers); + } + } + + /* + * POST moves or copies messages to the given queue from a queue specified in the posted JSON data + */ + @Override + protected void onPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + + try + { + final Queue sourceQueue = getQueueFromRequest(request); + + ObjectMapper mapper = new ObjectMapper(); + + @SuppressWarnings("unchecked") + Map<String,Object> providedObject = mapper.readValue(request.getInputStream(), LinkedHashMap.class); + + String destQueueName = (String) providedObject.get("destinationQueue"); + Boolean move = (Boolean) providedObject.get("move"); + + final VirtualHost vhost = sourceQueue.getParent(VirtualHost.class); + + boolean isMoveTransaction = move != null && Boolean.valueOf(move); + + // FIXME: added temporary authorization check until we introduce management layer + // and review current ACL rules to have common rules for all management interfaces + String methodName = isMoveTransaction? "moveMessages":"copyMessages"; + if (isQueueUpdateMethodAuthorized(methodName, vhost.getName())) + { + final Queue destinationQueue = getQueueFromVirtualHost(destQueueName, vhost); + final List messageIds = new ArrayList((List) providedObject.get("messages")); + QueueEntryTransaction txn = + isMoveTransaction + ? new MoveTransaction(sourceQueue, messageIds, destinationQueue) + : new CopyTransaction(sourceQueue, messageIds, destinationQueue); + vhost.executeTransaction(txn); + response.setStatus(HttpServletResponse.SC_OK); + } + else + { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + } + catch(RuntimeException e) + { + LOGGER.error("Failure to perform message opertion", e); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } + + + /* + * DELETE removes messages from the queue + */ + @Override + protected void onDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + + final Queue sourceQueue = getQueueFromRequest(request); + + final VirtualHost vhost = sourceQueue.getParent(VirtualHost.class); + + + final List<Long> messageIds = new ArrayList<Long>(); + for(String idStr : request.getParameterValues("id")) + { + messageIds.add(Long.valueOf(idStr)); + } + + // FIXME: added temporary authorization check until we introduce management layer + // and review current ACL rules to have common rules for all management interfaces + if (isQueueUpdateMethodAuthorized("deleteMessages", vhost.getName())) + { + vhost.executeTransaction(new DeleteTransaction(sourceQueue, messageIds)); + response.setStatus(HttpServletResponse.SC_OK); + } + else + { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + + } + + private boolean isQueueUpdateMethodAuthorized(String methodName, String virtualHost) + { + SecurityManager securityManager = getSecurityManager(virtualHost); + return securityManager.authoriseMethod(Operation.UPDATE, "VirtualHost.Queue", methodName); + } + + private SecurityManager getSecurityManager(String virtualHost) + { + IApplicationRegistry appRegistry = ApplicationRegistry.getInstance(); + SecurityManager security; + if (virtualHost == null) + { + security = appRegistry.getSecurityManager(); + } + else + { + security = appRegistry.getVirtualHostRegistry().getVirtualHost(virtualHost).getSecurityManager(); + } + return security; + } + +} diff --git a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java new file mode 100644 index 0000000000..593377beed --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java @@ -0,0 +1,586 @@ +/** + * 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 + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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.management.plugin.servlet.rest; + +import java.io.IOException; +import java.io.PrintWriter; +import java.net.SocketAddress; +import java.util.*; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.server.model.*; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.SerializationConfig; + + +public class RestServlet extends AbstractServlet +{ + private static final Logger LOGGER = Logger.getLogger(RestServlet.class); + /** + * An initialization parameter to specify hierarchy + */ + private static final String HIERARCHY_INIT_PARAMETER = "hierarchy"; + + public static final String DEPTH_PARAM = "depth"; + public static final String SORT_PARAM = "sort"; + + public static final Set<String> RESERVED_PARAMS = new HashSet<String>(Arrays.asList(DEPTH_PARAM, SORT_PARAM)); + + private Class<? extends ConfiguredObject>[] _hierarchy; + + private volatile boolean initializationRequired = false; + + public RestServlet() + { + super(); + initializationRequired = true; + } + + public RestServlet(Broker broker, Class<? extends ConfiguredObject>... hierarchy) + { + super(broker); + _hierarchy = hierarchy; + } + + @Override + public void init() throws ServletException + { + if (initializationRequired) + { + doInitialization(); + initializationRequired = false; + } + } + + @SuppressWarnings("unchecked") + private void doInitialization() throws ServletException + { + ServletConfig config = getServletConfig(); + String hierarchy = config.getInitParameter(HIERARCHY_INIT_PARAMETER); + if (hierarchy != null && !"".equals(hierarchy)) + { + List<Class<? extends ConfiguredObject>> classes = new ArrayList<Class<? extends ConfiguredObject>>(); + String[] hierarchyItems = hierarchy.split(","); + for (String item : hierarchyItems) + { + Class<?> itemClass = null; + try + { + itemClass = Class.forName(item); + } + catch (ClassNotFoundException e) + { + try + { + itemClass = Class.forName("org.apache.qpid.server.model." + item); + } + catch (ClassNotFoundException e1) + { + throw new ServletException("Unknown configured object class '" + item + + "' is specified in hierarchy for " + config.getServletName()); + } + } + Class<? extends ConfiguredObject> clazz = (Class<? extends ConfiguredObject>)itemClass; + classes.add(clazz); + } + Class<? extends ConfiguredObject>[] hierachyClasses = (Class<? extends ConfiguredObject>[])new Class[classes.size()]; + _hierarchy = classes.toArray(hierachyClasses); + } + else + { + _hierarchy = (Class<? extends ConfiguredObject>[])new Class[0]; + } + } + + protected Collection<ConfiguredObject> getObjects(HttpServletRequest request) + { + List<String> names = new ArrayList<String>(); + if(request.getPathInfo() != null && request.getPathInfo().length()>0) + { + String path = request.getPathInfo().substring(1); + names.addAll(Arrays.asList(path.split("/"))); + + if(names.size() > _hierarchy.length) + { + throw new IllegalArgumentException("Too many entries in path"); + } + } + + Collection<ConfiguredObject> parents = Collections.singleton((ConfiguredObject) getBroker()); + Collection<ConfiguredObject> children = new ArrayList<ConfiguredObject>(); + + Map<Class<? extends ConfiguredObject>, String> filters = + new HashMap<Class<? extends ConfiguredObject>, String>(); + + for(int i = 0; i < _hierarchy.length; i++) + { + if(i == 0 || Model.getChildTypes(_hierarchy[i - 1]).contains(_hierarchy[i])) + { + + for(ConfiguredObject parent : parents) + { + if(names.size() > i + && names.get(i) != null + && !names.get(i).equals("*") + && names.get(i).trim().length() != 0) + { + for(ConfiguredObject child : parent.getChildren(_hierarchy[i])) + { + if(child.getName().equals(names.get(i))) + { + children.add(child); + } + } + } + else + { + children.addAll(parent.getChildren(_hierarchy[i])); + } + } + } + else + { + children = parents; + if(names.size() > i + && names.get(i) != null + && !names.get(i).equals("*") + && names.get(i).trim().length() != 0) + { + filters.put(_hierarchy[i], names.get(i)); + } + } + + parents = children; + children = new ArrayList<ConfiguredObject>(); + } + + if(!filters.isEmpty()) + { + Collection<ConfiguredObject> potentials = parents; + parents = new ArrayList<ConfiguredObject>(); + + for(ConfiguredObject o : potentials) + { + + boolean match = true; + + for(Map.Entry<Class<? extends ConfiguredObject>, String> entry : filters.entrySet()) + { + Collection<? extends ConfiguredObject> ancestors = + getAncestors(getConfiguredClass(),entry.getKey(), o); + match = false; + for(ConfiguredObject ancestor : ancestors) + { + if(ancestor.getName().equals(entry.getValue())) + { + match = true; + break; + } + } + if(!match) + { + break; + } + } + if(match) + { + parents.add(o); + } + + } + } + + return filter(parents, request); + } + + private Collection<ConfiguredObject> filter(Collection<ConfiguredObject> objects, HttpServletRequest request) + { + + + Map<String, Collection<String>> filters = new HashMap<String, Collection<String>>(); + + for(String param : (Collection<String>) Collections.list(request.getParameterNames())) + { + if(!RESERVED_PARAMS.contains(param)) + { + filters.put(param, Arrays.asList(request.getParameterValues(param))); + } + } + + if(filters.isEmpty()) + { + return objects; + } + + Collection<ConfiguredObject> filteredObj = new ArrayList<ConfiguredObject>(objects); + + Iterator<ConfiguredObject> iter = filteredObj.iterator(); + + while(iter.hasNext()) + { + ConfiguredObject obj = iter.next(); + for(Map.Entry<String, Collection<String>> entry : filters.entrySet()) + { + Object value = obj.getAttribute(entry.getKey()); + if(!entry.getValue().contains(String.valueOf(value))) + { + iter.remove(); + } + } + + } + + return filteredObj; + } + + private Collection<? extends ConfiguredObject> getAncestors(Class<? extends ConfiguredObject> childType, + Class<? extends ConfiguredObject> ancestorType, + ConfiguredObject child) + { + Collection<ConfiguredObject> ancestors = new HashSet<ConfiguredObject>(); + Collection<Class<? extends ConfiguredObject>> parentTypes = Model.getParentTypes(childType); + + for(Class<? extends ConfiguredObject> parentClazz : parentTypes) + { + if(parentClazz == ancestorType) + { + ConfiguredObject parent = child.getParent(parentClazz); + if(parent != null) + { + ancestors.add(parent); + } + } + else + { + ConfiguredObject parent = child.getParent(parentClazz); + if(parent != null) + { + ancestors.addAll(getAncestors(parentClazz, ancestorType, parent)); + } + } + } + + return ancestors; + } + + + protected Map<String, Object> convertObjectToMap(final ConfiguredObject confObject, + Class<? extends ConfiguredObject> clazz, + int depth) + { + Map<String, Object> object = new LinkedHashMap<String, Object>(); + + for(String name : confObject.getAttributeNames()) + { + Object value = confObject.getAttribute(name); + if(value instanceof ConfiguredObject) + { + object.put(name, ((ConfiguredObject) value).getName()); + } + else if(value != null) + { + object.put(name, value); + } + } + + Statistics statistics = confObject.getStatistics(); + Map<String, Object> statMap = new HashMap<String, Object>(); + for(String name : statistics.getStatisticNames()) + { + Object value = statistics.getStatistic(name); + if(value != null) + { + statMap.put(name, value); + } + } + + if(!statMap.isEmpty()) + { + object.put("statistics", statMap); + } + + if(depth > 0) + { + for(Class<? extends ConfiguredObject> childClass : Model.getChildTypes(clazz)) + { + Collection<? extends ConfiguredObject> children = confObject.getChildren(childClass); + if(children != null) + { + List<Map<String, Object>> childObjects = new ArrayList<Map<String, Object>>(); + + for(ConfiguredObject child : children) + { + childObjects.add(convertObjectToMap(child, childClass, depth-1)); + } + + if(!childObjects.isEmpty()) + { + object.put(childClass.getSimpleName().toLowerCase()+"s",childObjects); + } + } + } + } + return object; + } + + @Override + protected void onGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + response.setContentType("application/json"); + response.setStatus(HttpServletResponse.SC_OK); + + response.setHeader("Cache-Control","no-cache"); + response.setHeader("Pragma","no-cache"); + response.setDateHeader ("Expires", 0); + + Collection<ConfiguredObject> allObjects = getObjects(request); + + @SuppressWarnings("unchecked") + Map params = new HashMap(request.getParameterMap()); + + int depth = 1; + try + { + depth = Integer.parseInt(String.valueOf(params.remove("depth"))); + } + catch (NumberFormatException e) + { + // Ignore + } + + List<Map<String, Object>> output = new ArrayList<Map<String, Object>>(); + + // TODO - depth and sort special params, everything else should act as a filter + if(request.getParameter(DEPTH_PARAM)!=null) + { + try + { + depth = Integer.parseInt(request.getParameter(DEPTH_PARAM)); + } + catch (NumberFormatException e) + { + + } + } + + for(ConfiguredObject configuredObject : allObjects) + { + output.add(convertObjectToMap(configuredObject, getConfiguredClass(),depth)); + } + + final PrintWriter writer = response.getWriter(); + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true); + mapper.writeValue(writer, output); + + response.setContentType("application/json"); + response.setStatus(HttpServletResponse.SC_OK); + + } + + private Class<? extends ConfiguredObject> getConfiguredClass() + { + return _hierarchy.length == 0 ? Broker.class : _hierarchy[_hierarchy.length-1]; + } + + @Override + protected void onPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + response.setContentType("application/json"); + + ObjectMapper mapper = new ObjectMapper(); + @SuppressWarnings("unchecked") + Map<String,Object> providedObject = mapper.readValue(request.getInputStream(), LinkedHashMap.class); + + + List<String> names = new ArrayList<String>(); + if(request.getPathInfo() != null && request.getPathInfo().length()>0) + { + String path = request.getPathInfo().substring(1); + names.addAll(Arrays.asList(path.split("/"))); + + if(names.size() != _hierarchy.length) + { + throw new IllegalArgumentException("Path to object to create must be fully specified"); + } + } + + + providedObject.put("name", names.get(names.size()-1)); + + @SuppressWarnings("unchecked") + Collection<ConfiguredObject>[] objects = new Collection[_hierarchy.length]; + if(_hierarchy.length == 1) + { + try + { + getBroker().createChild(_hierarchy[0], providedObject); + } + catch (RuntimeException e) + { + setResponseStatus(response, e); + return; + } + + } + else + { + for(int i = 0; i < _hierarchy.length-1; i++) + { + objects[i] = new HashSet<ConfiguredObject>(); + if(i == 0) + { + for(ConfiguredObject object : getBroker().getChildren(_hierarchy[0])) + { + if(object.getName().equals(names.get(0))) + { + objects[0].add(object); + break; + } + } + } + else + { + for(int j = i-1; j >=0; j--) + { + if(Model.getChildTypes(_hierarchy[j]).contains(_hierarchy[i])) + { + for(ConfiguredObject parent : objects[j]) + { + for(ConfiguredObject object : parent.getChildren(_hierarchy[i])) + { + if(object.getName().equals(names.get(i))) + { + objects[i].add(object); + } + } + } + break; + } + } + } + + } + List<ConfiguredObject> parents = new ArrayList<ConfiguredObject>(); + Class<? extends ConfiguredObject> objClass = getConfiguredClass(); + Collection<Class<? extends ConfiguredObject>> parentClasses = Model.getParentTypes(objClass); + for(int i = _hierarchy.length-2; i >=0 ; i--) + { + if(parentClasses.contains(_hierarchy[i])) + { + if(objects[i].size() == 1) + { + parents.add(objects[i].iterator().next()); + } + else + { + throw new IllegalArgumentException("Cannot deduce parent of class " + + _hierarchy[i].getSimpleName()); + } + } + + } + ConfiguredObject theParent = parents.remove(0); + ConfiguredObject[] otherParents = parents.toArray(new ConfiguredObject[parents.size()]); + + try + { + + Collection<? extends ConfiguredObject> existingChildren = theParent.getChildren(objClass); + for(ConfiguredObject obj: existingChildren) + { + if((providedObject.containsKey("id") && String.valueOf(providedObject.get("id")).equals(obj.getId().toString())) + || (obj.getName().equals(providedObject.get("name")) && equalParents(obj, otherParents))) + { + doUpdate(obj, providedObject); + } + } + theParent.createChild(objClass, providedObject, otherParents); + } + catch (RuntimeException e) + { + setResponseStatus(response, e); + return; + } + + } + response.setStatus(HttpServletResponse.SC_CREATED); + } + + private void doUpdate(ConfiguredObject obj, Map<String, Object> providedObject) + { + for(Map.Entry<String,Object> entry : providedObject.entrySet()) + { + obj.setAttribute(entry.getKey(), obj.getAttribute(entry.getKey()), entry.getValue()); + } + //TODO - Implement. + } + + private boolean equalParents(ConfiguredObject obj, ConfiguredObject[] otherParents) + { + if(otherParents == null || otherParents.length == 0) + { + return true; + } + return false; //TODO - Implement. + } + + private void setResponseStatus(HttpServletResponse response, RuntimeException e) throws IOException + { + if (e.getCause() instanceof AMQSecurityException) + { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + else + { + LOGGER.warn("Unexpected exception is caught", e); + + // TODO + response.setStatus(HttpServletResponse.SC_CONFLICT); + } + } + + @Override + protected void onDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + response.setContentType("application/json"); + response.setStatus(HttpServletResponse.SC_OK); + + response.setHeader("Cache-Control","no-cache"); + response.setHeader("Pragma","no-cache"); + response.setDateHeader ("Expires", 0); + try + { + Collection<ConfiguredObject> allObjects = getObjects(request); + for(ConfiguredObject o : allObjects) + { + o.setDesiredState(o.getActualState(), State.DELETED); + } + + response.setStatus(HttpServletResponse.SC_OK); + } + catch(RuntimeException e) + { + setResponseStatus(response, e); + } + } +} diff --git a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java new file mode 100644 index 0000000000..1b78611a50 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java @@ -0,0 +1,256 @@ +/* + * + * 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.management.plugin.servlet.rest; + +import org.apache.commons.codec.binary.Base64; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.SerializationConfig; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; + +import javax.security.auth.Subject; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.io.PrintWriter; +import java.security.Principal; +import java.security.SecureRandom; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Random; + +public class SaslServlet extends AbstractServlet +{ + private static final Logger LOGGER = Logger.getLogger(SaslServlet.class); + + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + private static final String ATTR_RANDOM = "SaslServlet.Random"; + private static final String ATTR_ID = "SaslServlet.ID"; + private static final String ATTR_SASL_SERVER = "SaslServlet.SaslServer"; + private static final String ATTR_EXPIRY = "SaslServlet.Expiry"; + private static final long SASL_EXCHANGE_EXPIRY = 1000L; + + + public SaslServlet() + { + super(); + } + + public SaslServlet(Broker broker) + { + super(broker); + } + + protected void onGet(HttpServletRequest request, HttpServletResponse response) throws + ServletException, + IOException + { + response.setContentType("application/json"); + response.setStatus(HttpServletResponse.SC_OK); + + response.setHeader("Cache-Control","no-cache"); + response.setHeader("Pragma","no-cache"); + response.setDateHeader ("Expires", 0); + + HttpSession session = request.getSession(); + Random rand = getRandom(session); + + AuthenticationManager authManager = ApplicationRegistry.getInstance().getAuthenticationManager(getSocketAddress(request)); + String[] mechanisms = authManager.getMechanisms().split(" "); + Map<String, Object> outputObject = new LinkedHashMap<String, Object>(); + final Subject subject = (Subject) session.getAttribute("subject"); + if(subject != null) + { + final Principal principal = subject.getPrincipals().iterator().next(); + outputObject.put("user", principal.getName()); + } + else if (request.getRemoteUser() != null) + { + outputObject.put("user", request.getRemoteUser()); + } + + outputObject.put("mechanisms", (Object) mechanisms); + + final PrintWriter writer = response.getWriter(); + + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true); + mapper.writeValue(writer, outputObject); + + } + + private Random getRandom(final HttpSession session) + { + Random rand = (Random) session.getAttribute(ATTR_RANDOM); + if(rand == null) + { + synchronized (SECURE_RANDOM) + { + rand = new Random(SECURE_RANDOM.nextLong()); + } + session.setAttribute(ATTR_RANDOM, rand); + } + return rand; + } + + + @Override + protected void onPost(final HttpServletRequest request, final HttpServletResponse response) + throws ServletException, IOException + { + try + { + response.setContentType("application/json"); + response.setHeader("Cache-Control","no-cache"); + response.setHeader("Pragma","no-cache"); + response.setDateHeader("Expires", 0); + + HttpSession session = request.getSession(); + + String mechanism = request.getParameter("mechanism"); + String id = request.getParameter("id"); + String saslResponse = request.getParameter("response"); + + AuthenticationManager authManager = ApplicationRegistry.getInstance().getAuthenticationManager(getSocketAddress(request)); + + if(mechanism != null) + { + if(id == null) + { + SaslServer saslServer = authManager.createSaslServer(mechanism, request.getServerName(), null/*TODO*/); + evaluateSaslResponse(response, session, saslResponse, saslServer); + } + else + { + response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED); + session.removeAttribute(ATTR_ID); + session.removeAttribute(ATTR_SASL_SERVER); + session.removeAttribute(ATTR_EXPIRY); + + } + + } + else + { + if(id != null) + { + if(id.equals(session.getAttribute(ATTR_ID)) && System.currentTimeMillis() < (Long) session.getAttribute(ATTR_EXPIRY)) + { + SaslServer saslServer = (SaslServer) session.getAttribute(ATTR_SASL_SERVER); + evaluateSaslResponse(response, session, saslResponse, saslServer); + + } + else + { + response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED); + session.removeAttribute(ATTR_ID); + session.removeAttribute(ATTR_SASL_SERVER); + session.removeAttribute(ATTR_EXPIRY); + } + } + else + { + response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED); + session.removeAttribute(ATTR_ID); + session.removeAttribute(ATTR_SASL_SERVER); + session.removeAttribute(ATTR_EXPIRY); + + } + } + } + catch(IOException e) + { + LOGGER.error("Error processing SASL request", e); + throw e; + } + catch(RuntimeException e) + { + LOGGER.error("Error processing SASL request", e); + throw e; + } + + } + + private void evaluateSaslResponse(final HttpServletResponse response, + final HttpSession session, + final String saslResponse, final SaslServer saslServer) throws IOException + { + final String id; + byte[] challenge; + try + { + challenge = saslServer.evaluateResponse(saslResponse == null ? new byte[0] : Base64.decodeBase64(saslResponse.getBytes())); + } + catch(SaslException e) + { + + session.removeAttribute(ATTR_ID); + session.removeAttribute(ATTR_SASL_SERVER); + session.removeAttribute(ATTR_EXPIRY); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + return; + } + + if(saslServer.isComplete()) + { + final Subject subject = new Subject(); + subject.getPrincipals().add(new UsernamePrincipal(saslServer.getAuthorizationID())); + session.setAttribute("subject", subject); + session.removeAttribute(ATTR_ID); + session.removeAttribute(ATTR_SASL_SERVER); + session.removeAttribute(ATTR_EXPIRY); + + response.setStatus(HttpServletResponse.SC_OK); + + + } + else + { + Random rand = getRandom(session); + id = String.valueOf(rand.nextLong()); + session.setAttribute(ATTR_ID, id); + session.setAttribute(ATTR_SASL_SERVER, saslServer); + session.setAttribute(ATTR_EXPIRY, System.currentTimeMillis() + SASL_EXCHANGE_EXPIRY); + + response.setStatus(HttpServletResponse.SC_OK); + + Map<String, Object> outputObject = new LinkedHashMap<String, Object>(); + outputObject.put("id", id); + outputObject.put("challenge", new String(Base64.encodeBase64(challenge))); + + final PrintWriter writer = response.getWriter(); + + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true); + mapper.writeValue(writer, outputObject); + + } + } +} diff --git a/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/StructureServlet.java b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/StructureServlet.java new file mode 100644 index 0000000000..e4ba374f89 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/StructureServlet.java @@ -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 + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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.management.plugin.servlet.rest; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.Model; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.SerializationConfig; + +public class StructureServlet extends AbstractServlet +{ + public StructureServlet() + { + super(); + } + + public StructureServlet(Broker broker) + { + super(broker); + } + + @Override + protected void onGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + response.setContentType("application/json"); + response.setStatus(HttpServletResponse.SC_OK); + + response.setHeader("Cache-Control","no-cache"); + response.setHeader("Pragma","no-cache"); + response.setDateHeader ("Expires", 0); + + Map<String,Object> structure = generateStructure(getBroker(), Broker.class); + + final PrintWriter writer = response.getWriter(); + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true); + mapper.writeValue(writer, structure); + + response.setStatus(HttpServletResponse.SC_OK); + + } + + private Map<String, Object> generateStructure(ConfiguredObject object, Class<? extends ConfiguredObject> clazz) + { + Map<String, Object> structure = new LinkedHashMap<String, Object>(); + structure.put("id", object.getId()); + structure.put("name", object.getName()); + + for(Class<? extends ConfiguredObject> childClass : Model.getChildTypes(clazz)) + { + Collection<? extends ConfiguredObject> children = object.getChildren(childClass); + if(children != null) + { + List<Map<String, Object>> childObjects = new ArrayList<Map<String, Object>>(); + + for(ConfiguredObject child : children) + { + childObjects.add(generateStructure(child, childClass)); + } + + if(!childObjects.isEmpty()) + { + structure.put(pluralize(childClass),childObjects); + } + } + } + + return structure; + } + + private String pluralize(Class<? extends ConfiguredObject> childClass) + { + String name = childClass.getSimpleName().toLowerCase(); + return name + (name.endsWith("s") ? "es" : "s"); + } +} diff --git a/java/broker-plugins/management-http/src/main/java/resources/addBinding.html b/java/broker-plugins/management-http/src/main/java/resources/addBinding.html new file mode 100644 index 0000000000..8dbd219c8d --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/addBinding.html @@ -0,0 +1,42 @@ +<!-- + ~ Licensed to the Apache Software Foundation (ASF) under one or more + ~ contributor license agreements. See the NOTICE file distributed with + ~ this work for additional information regarding copyright ownership. + ~ The ASF licenses this file to You under the Apache License, Version 2.0 + ~ (the "License"); you may not use this file except in compliance with + ~ the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<div class="dijitHidden"> + <div data-dojo-type="dijit.Dialog" style="width:600px;" data-dojo-props="title:'Add Binding'" id="addBinding"> + <form id="formAddBinding" method="post" dojoType="dijit.form.Form"> + <table cellpadding="0" cellspacing="2"> + <tr> + <td valign="top"><strong>Exchange Name*: </strong></td> + <td><div id="addBinding.selectExchangeDiv"></div></td> + </tr> + <tr> + <td valign="top"><strong>Queue Name*: </strong></td> + <td><div id="addBinding.selectQueueDiv"></div></td> + </tr> + <tr> + <td valign="top"><strong>Binding Key*: </strong></td> + <td><input type="text" required="true" name="name" id="formAddbinding.bindingKey" placeholder="Binding Key" + dojoType="dijit.form.ValidationTextBox" missingMessage="A name must be supplied" /></td> + </tr> + </table> + <br/> + + <!-- submit buttons --> + <input type="submit" value="Create Binding" label="Create Binding" dojoType="dijit.form.Button" /> + + </form> + </div> +</div> diff --git a/java/broker-plugins/management-http/src/main/java/resources/addExchange.html b/java/broker-plugins/management-http/src/main/java/resources/addExchange.html new file mode 100644 index 0000000000..10ac5388ff --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/addExchange.html @@ -0,0 +1,54 @@ +<!-- + - + - 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. + - + --> +<div class="dijitHidden"> + <div data-dojo-type="dijit.Dialog" style="width:600px;" data-dojo-props="title:'Add Exchange'" id="addExchange"> + <form id="formAddExchange" method="post" dojoType="dijit.form.Form"> + <table cellpadding="0" cellspacing="2"> + <tr> + <td valign="top"><strong>Exchange Name*: </strong></td> + <td><input type="text" required="true" name="name" id="formAddExchange.name" placeholder="Exchange Name" + dojoType="dijit.form.ValidationTextBox" missingMessage="A name must be supplied" + data-dojo-props="regExp:'^(?!qpid\.|amq\.|\<\<default\>\>).*$', invalidMessage:'Reserved exchange name!'"/></td> + </tr> + <tr> + <td valign="top"><strong>Durable? </strong></td> + <td><input type="checkbox" name="durable" id="formAddExchange.durable" value="durable" checked="checked" dojoType="dijit.form.CheckBox" /></td> + </tr> + <tr> + <td valign="top"><strong>Exchange Type: </strong></td> + <td> + <select name="type" id="formAddExchange.type" dojoType="dijit.form.FilteringSelect"> + <option value="direct">direct</option> + <option value="topic">topic</option> + <option value="headers">headers</option> + <option value="fanout">fanout</option> + </select> + </td> + </tr> + </table> + <br/> + + <!-- submit buttons --> + <input type="submit" value="Create Exchange" label="Create Exchange" dojoType="dijit.form.Button" /> + + </form> + </div> +</div> diff --git a/java/broker-plugins/management-http/src/main/java/resources/addQueue.html b/java/broker-plugins/management-http/src/main/java/resources/addQueue.html new file mode 100644 index 0000000000..d396f28877 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/addQueue.html @@ -0,0 +1,182 @@ +<!-- + - + - 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. + - + --> +<div class="dijitHidden"> + <div data-dojo-type="dijit.Dialog" style="width:600px;" data-dojo-props="title:'Add Queue'" id="addQueue"> + <form id="formAddQueue" method="post" dojoType="dijit.form.Form"> + <table cellpadding="0" cellspacing="2"> + <tr> + <td valign="top"><strong>Queue Name*: </strong></td> + <td><input type="text" required="true" name="name" id="formAddQueue.name" placeholder="Queue Name" + dojoType="dijit.form.ValidationTextBox" missingMessage="A name must be supplied" /></td> + </tr> + <tr> + <td valign="top"><strong>Durable? </strong></td> + <td><input type="checkbox" name="durable" id="formAddQueue.durable" value="durable" checked="checked" dojoType="dijit.form.CheckBox" /></td> + </tr> + <tr> + <td valign="top"><strong>Queue Type: </strong></td> + <td> + <input type="radio" id="formAddQueueTypeStandard" name="type" value="standard" checked="checked" dojoType="dijit.form.RadioButton" /> + <label for="formAddQueueTypeStandard">Standard</label> + + <input type="radio" id="formAddQueueTypePriority" name="type" value="priority" dojoType="dijit.form.RadioButton" /> + <label for="formAddQueueTypePriority">Priority</label> + + <input type="radio" id="formAddQueueTypeLVQ" name="type" value="lvq" dojoType="dijit.form.RadioButton" /> + <label for="formAddQueueTypeLVQ">LVQ</label> + + <input type="radio" id="formAddQueueTypeSorted" name="type" value="sorted" dojoType="dijit.form.RadioButton" /> + <label for="formAddQueueTypeSorted">Sorted</label> + </td> + </tr> + </table> + <br/> + + <div id="formAddQueueTypePriority:fields" style="display:none" + data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Priority Queue Settings'"> + <table cellpadding="0" cellspacing="2"> + <tr> + <td valign="top"><strong>Priorities: </strong></td> + <td><input data-dojo-type="dijit.form.NumberSpinner" id="formAddQueue.priorities" + name="priorities" value="10" smallDelta="1" constraints="{min:1,max:10,places:0}"/> + </tr> + </table> + </div> + + + <div id="formAddQueueTypeLVQ:fields" style="display:none" + data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Last Value Queue Settings'"> + <table cellpadding="0" cellspacing="2"> + <tr> + <td valign="top"><strong>LVQ Message Property: </strong></td> + <td><input type="text" name="lvqKey" id="formAddQueue.lvqkey" + placeholder="qpid.LVQ_key" dojoType="dijit.form.ValidationTextBox" /></td> + </tr> + </table> + </div> + + <div id="formAddQueueTypeSorted:fields" style="display:none" + data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Sorted Queue Settings'"> + <table cellpadding="0" cellspacing="2"> + <tr> + <td valign="top"><strong>Sort Message Property: </strong></td> + <td><input type="text" name="sortKey" id="formAddQueue.sortkey" required="false" + placeholder="" dojoType="dijit.form.ValidationTextBox" /></td> + </tr> + </table> + </div> + + <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Flow Control Settings', open: false"> + <table cellpadding="0" cellspacing="2"> + + <!-- x-qpid-capacity --> + <tr> + <td valign="top"><strong>Capacity: </strong></td> + <td><input type="text" required="false" name="queueFlowControlSizeBytes" id="formAddQueue.capacity" placeholder="Size in bytes" + dojoType="dijit.form.ValidationTextBox" + trim="true" + regexp="[0-9]+" + invalidMessage= "Invalid value"/></td> + </tr> + <!-- x-qpid-flow-resume-capacity --> + <tr> + <td valign="top"><strong>Resume Capacity: </strong></td> + <td><input type="text" required="false" name="queueFlowResumeSizeBytes" id="formAddQueue.flowResumeCapacity" placeholder="Size in bytes" + dojoType="dijit.form.ValidationTextBox" + trim="true" + regexp="[0-9]+" + invalidMessage= "Invalid value"/></td> + </tr> + </table> + </div> + <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Alerting Settings', open: false"> + <table cellpadding="0" cellspacing="2"> + <!-- x-qpid-maximum-message-count --> + <tr> + <td valign="top"><strong>Queue Depth: </strong></td> + <td><input type="text" required="false" name="alertThresholdQueueDepthMessages" id="formAddQueue.maximumMessageCount" placeholder="Count of messages" + dojoType="dijit.form.ValidationTextBox" + trim="true" + regexp="[0-9]+" + invalidMessage= "Invalid value" /></td> + </tr> + <!-- x-qpid-maximum-message-age --> + <tr> + <td valign="top"><strong>Message Age: </strong></td> + <td><input type="text" required="false" name="alertThresholdMessageAge" id="formAddQueue.maximumMessageAge" placeholder="Time in ms" + dojoType="dijit.form.ValidationTextBox" + trim="true" + regexp="[0-9]+" + invalidMessage= "Invalid value" /></td> + </tr> + <!-- x-qpid-maximum-message-size --> + <tr> + <td valign="top"><strong>Message Size: </strong></td> + <td><input type="text" required="false" name="alertThresholdMessageSize" id="formAddQueue.maximumMessageSize" placeholder="Size in bytes" + dojoType="dijit.form.ValidationTextBox" + trim="true" + regexp="[0-9]+" + invalidMessage= "Invalid value"/></td> + </tr> + <!-- x-qpid-minimum-alert-repeat-gap --> + <tr> + <td valign="top"><strong>Gap between alerts: </strong></td> + <td><input type="text" required="false" name="alertRepeatGap" id="formAddQueue.minimumAlertRepeatGap" placeholder="Time in ms" + dojoType="dijit.form.ValidationTextBox" + trim="true" + regexp="[0-9]+" + invalidMessage= "Invalid value" /></td> + </tr> + </table> + </div> + <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Other Settings', open: false"> + <table cellpadding="0" cellspacing="2"> + + <!-- x-qpid-maximum-delivery-count --> + <tr> + <td valign="top"><strong>Maximum Delivery Retries: </strong></td> + <td><input type="text" required="false" name="maximumDeliveryAttempts" id="formAddQueue.maximumDeliveryCount" + dojoType="dijit.form.ValidationTextBox" + trim="true" + regexp="[0-9]+" + invalidMessage= "Invalid value"/></td> + </tr> + <tr> + <td valign="top"><strong>Create DLQ? </strong></td> + <td><input type="checkbox" name="dlqEnabled" id="formAddQueue.dlqEnabled" value="dlqEnabled" dojoType="dijit.form.CheckBox" /></td> + </tr> + </table> + <table cellpadding="0" cellspacing="2"> + <tr> + <td valign="top">NOTE: Configuring maximum delivery retries on a queue which has no DLQ / AlternateExchange will result in messages being discarded after the limit is reached.</td> + </tr> + </table> + </div> + <br/> + <!-- submit buttons --> + <input type="submit" value="Create Queue" label="Create Queue" dojoType="dijit.form.Button" /> + + </form> + </div> +</div> diff --git a/java/broker-plugins/management-http/src/main/java/resources/authenticationprovider/addUser.html b/java/broker-plugins/management-http/src/main/java/resources/authenticationprovider/addUser.html new file mode 100644 index 0000000000..785605f694 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/authenticationprovider/addUser.html @@ -0,0 +1,42 @@ +<!-- + - + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you under the Apache License, Version 2.0 (the + - "License"); you may not use this file except in compliance + - with the License. You may obtain a copy of the License at + - + - http://www.apache.org/licenses/LICENSE-2.0 + - + - Unless required by applicable law or agreed to in writing, + - software distributed under the License is distributed on an + - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + - KIND, either express or implied. See the License for the + - specific language governing permissions and limitations + - under the License. + - + --> +<div class="dijitHidden"> + <div data-dojo-type="dijit.Dialog" style="width:600px;" data-dojo-props="title:'Add User'" id="addUser"> + <form id="formAddUser" method="post" dojoType="dijit.form.Form"> + <table cellpadding="0" cellspacing="2"> + <tr> + <td valign="top"><strong>User Name*: </strong></td> + <td><input type="text" required="true" name="name" id="formAddUser.name" placeholder="User Name" + dojoType="dijit.form.ValidationTextBox" missingMessage="A name must be supplied" /></td> + </tr> + <tr> + <td valign="top"><strong>Password*</strong></td> + <td><input type="password" required="true" name="password" id="formAddUser.password" dojoType="dijit.form.ValidationTextBox"/></td> + </tr> + </table> + <br/> + + <!-- submit buttons --> + <input type="submit" value="Create User" label="Create User" dojoType="dijit.form.Button" /> + + </form> + </div> +</div> diff --git a/java/broker-plugins/management-http/src/main/java/resources/authenticationprovider/setPassword.html b/java/broker-plugins/management-http/src/main/java/resources/authenticationprovider/setPassword.html new file mode 100644 index 0000000000..3d67463abd --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/authenticationprovider/setPassword.html @@ -0,0 +1,42 @@ +<!-- + - + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you under the Apache License, Version 2.0 (the + - "License"); you may not use this file except in compliance + - with the License. You may obtain a copy of the License at + - + - http://www.apache.org/licenses/LICENSE-2.0 + - + - Unless required by applicable law or agreed to in writing, + - software distributed under the License is distributed on an + - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + - KIND, either express or implied. See the License for the + - specific language governing permissions and limitations + - under the License. + - + --> +<div class="dijitHidden"> + <div data-dojo-type="dijit.Dialog" style="width:600px;" data-dojo-props="title:'Set Password'" id="setPassword"> + <form id="formSetPassword" method="post" dojoType="dijit.form.Form"> + <table cellpadding="0" cellspacing="2"> + <tr> + <td valign="top"><strong>User Name: </strong></td> + <td><input type="text" required="true" name="name" id="formSetPassword.name" placeholder="User Name" + dojoType="dijit.form.TextBox" enabled="false" /></td> + </tr> + <tr> + <td valign="top"><strong>Password*</strong></td> + <td><input type="password" required="true" name="password" id="formSetPassword.password" dojoType="dijit.form.ValidationTextBox"/></td> + </tr> + </table> + <br/> + + <!-- submit buttons --> + <input type="submit" value="Set Password" label="Set Password" dojoType="dijit.form.Button" /> + + </form> + </div> +</div> diff --git a/java/broker-plugins/experimental/shutdown/build.xml b/java/broker-plugins/management-http/src/main/java/resources/authenticationprovider/showPrincipalDatabaseAuthenticationManager.html index cb4254806b..baadc8c35f 100644 --- a/java/broker-plugins/experimental/shutdown/build.xml +++ b/java/broker-plugins/management-http/src/main/java/resources/authenticationprovider/showPrincipalDatabaseAuthenticationManager.html @@ -7,9 +7,9 @@ - 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 @@ -18,15 +18,12 @@ - under the License. - --> -<project name="AMQ Broker Shutdown Plugin" default="build"> - - <property name="module.depends" value="common broker management/common broker-plugins"/> - <property name="module.test.depends" value="test broker/test management/common client systests"/> - <property name="module.manifest" value="MANIFEST.MF"/> - <property name="module.plugin" value="true"/> - - <import file="../../../module.xml"/> +<div class="PrincipalDatabaseAuthenticationManager"> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Users'"> + <div class="users"></div> + <button data-dojo-type="dijit.form.Button" class="addUserButton">Add User</button> + <button data-dojo-type="dijit.form.Button" class="deleteUserButton">Delete Users</button> - <target name="bundle" depends="bundle-tasks"/> + </div> -</project> +</div> diff --git a/java/broker-plugins/management-http/src/main/java/resources/css/common.css b/java/broker-plugins/management-http/src/main/java/resources/css/common.css new file mode 100644 index 0000000000..78780edcd9 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/css/common.css @@ -0,0 +1,92 @@ +/* + * + * 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. + * + */ +* { + outline: none !important; +} + +html, body { + height: 100%; + margin: 0; + margin-right: 40px; + padding: 0; + overflow: hidden; + font-family: Lucida Sans,Lucida Grande,Arial !important; + font-size: 13px !important; + background: white; + color: #333; +} + +#pageLayout { + height: 100%; +} +button { + -webkit-transition: background-color 0.2s linear; + border-radius:4px; + -moz-border-radius: 4px 4px 4px 4px; + -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15); + background-color: #E4F2FF; + background-image: url("../dojo/dijit/themes/claro/form/images/button.png"); + background-position: center top; + background-repeat: repeat-x; + border: 1px solid #769DC0; + padding: 2px 8px 4px; + font-size:1em; +} + +button:hover { + background-color: #AFD9FF; + color: #000000; +} + +h1 { + font-size:1.5em; +} + +.header { + height:100px; + background:url("../images/qpid-logo.png") left center no-repeat +} + +.logo { + text-align:left; + vertical-align: top; + font-weight:600; + height: 90px; + padding-left: 200px; + padding-top: 1px; + padding-bottom: 10px; + font-size:14px; + font-family:"Verdana", cursive; +} + +.footer { + color:#000000; + clear:both; + text-align:center; + font-size:11px; + line-height:17px; + +} + +div .messages { + width: 100%; + height: 350px; +}
\ No newline at end of file diff --git a/java/broker-plugins/extras/build.xml b/java/broker-plugins/management-http/src/main/java/resources/footer.html index 7c1d0be49f..fa84825e80 100644 --- a/java/broker-plugins/extras/build.xml +++ b/java/broker-plugins/management-http/src/main/java/resources/footer.html @@ -1,15 +1,15 @@ <!-- - - Licensed to the Apache Software Foundation (ASF) under one -nn - or more contributor license agreements. See the NOTICE file - -n distributed with this work for additional information + - 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 @@ -18,14 +18,11 @@ nn - or more contributor license agreements. See the NOTICE file - under the License. - --> -<project name="Qpid Broker-Plugins Extras" default="build"> - <property name="module.depends" value="common client management/common broker broker-plugins"/> - <property name="module.test.depends" value="test broker/test common/test"/> - <property name="module.manifest" value="MANIFEST.MF"/> - <property name="module.plugin" value="true"/> - - <import file="../../module.xml"/> - - <target name="bundle" depends="bundle-tasks" /> -</project> +<div class="footer"><p>© 2004-<span class="currentYear">2012</span> The Apache Software Foundation. + <br/> + Apache Qpid, Qpid, Apache, the Apache feather logo, and the Apache Qpid project logo are trademarks of + The Apache Software Foundation. + <br/> + All other marks mentioned may be trademarks or registered trademarks of their respective owners. +</div>
\ No newline at end of file diff --git a/java/broker-plugins/management-http/src/main/java/resources/images/qpid-logo.png b/java/broker-plugins/management-http/src/main/java/resources/images/qpid-logo.png Binary files differnew file mode 100644 index 0000000000..95d49ea469 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/images/qpid-logo.png diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/authorization/sasl.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/authorization/sasl.js new file mode 100644 index 0000000000..152504da86 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/authorization/sasl.js @@ -0,0 +1,213 @@ +/* + * + * 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. + * + */ +require(["dijit/form/DropDownButton", "dijit/TooltipDialog", "dijit/form/TextBox", + "dojo/_base/xhr", "dojox/encoding/base64", "dojox/encoding/digests/_base", "dojox/encoding/digests/MD5"]); +var button; +var usernameSpan; + +var encodeUTF8 = function encodeUTF8(str) { + var byteArray = []; + for (var i = 0; i < str.length; i++) { + if (str.charCodeAt(i) <= 0x7F) { + byteArray.push(str.charCodeAt(i)); + } + else { + var h = encodeURIComponent(str.charAt(i)).substr(1).split('%'); + for (var j = 0; j < h.length; j++) + byteArray.push(parseInt(h[j], 16)); + } + } + return byteArray; +}; + +var decodeUTF8 = function decodeUTF8(byteArray) +{ + var str = ''; + for (var i = 0; i < byteArray.length; i++) + str += byteArray[i] <= 0x7F? + byteArray[i] === 0x25 ? "%25" : + String.fromCharCode(byteArray[i]) : + "%" + byteArray[i].toString(16).toUpperCase(); + return decodeURIComponent(str); +}; + + +var saslPlain = function saslPlain(user, password) +{ + var responseArray = [ 0 ].concat(encodeUTF8( user )).concat( [ 0 ] ).concat( encodeUTF8( password ) ); + var plainResponse = dojox.encoding.base64.encode(responseArray); + + // Using dojo.xhrGet, as very little information is being sent + dojo.xhrPost({ + // The URL of the request + url: "rest/sasl", + content: { + mechanism: "PLAIN", + response: plainResponse + }, + handleAs: "json", + failOk: true + }).then(function() + { + updateAuthentication(); + }, + function(error) + { + if(error.status == 401) + { + alert("Authentication Failed"); + } + else + { + alert(error); + } + updateAuthentication(); + }); +}; + +var saslCramMD5 = function saslCramMD5(user, password) +{ + + // Using dojo.xhrGet, as very little information is being sent + dojo.xhrPost({ + // The URL of the request + url: "rest/sasl", + content: { + mechanism: "CRAM-MD5" + }, + handleAs: "json", + failOk: true + }).then(function(data) + { + + var challengeBytes = dojox.encoding.base64.decode(data.challenge); + var wa=[]; + var bitLength = challengeBytes.length*8; + for(var i=0; i<bitLength; i+=8) + { + wa[i>>5] |= (challengeBytes[i/8] & 0xFF)<<(i%32); + } + var challengeStr = dojox.encoding.digests.wordToString(wa).substring(0,challengeBytes.length); + + var digest = user + " " + dojox.encoding.digests.MD5._hmac(challengeStr, password, dojox.encoding.digests.outputTypes.Hex); + var id = data.id; + + var response = dojox.encoding.base64.encode(encodeUTF8( digest )); + + dojo.xhrPost({ + // The URL of the request + url: "rest/sasl", + content: { + id: id, + response: response + }, + handleAs: "json", + failOk: true + }).then(function() + { + updateAuthentication(); + }, + function(error) + { + if(error.status == 401) + { + alert("Authentication Failed"); + } + else + { + alert(error); + } + updateAuthentication(); + }); + + }, + function(error) + { + if(error.status == 401) + { + alert("Authentication Failed"); + } + else + { + alert(error); + } + }); +}; + +var doAuthenticate = function doAuthenticate() +{ + saslCramMD5(dojo.byId("username").value, dojo.byId("pass").value); + updateAuthentication(); +}; + + +var updateAuthentication = function updateAuthentication() +{ + dojo.xhrGet({ + // The URL of the request + url: "rest/sasl", + handleAs: "json" + }).then(function(data) + { + if(data.user) + { + dojo.byId("authenticatedUser").innerHTML = data.user; + dojo.style(button.domNode, {visibility: 'hidden'}); + dojo.style(usernameSpan, {visibility: 'visible'}); + } + else + { + dojo.style(button.domNode, {visibility: 'visible'}); + dojo.style(usernameSpan, {visibility: 'hidden'}); + } + } + ); +}; + +require(["dijit/form/DropDownButton", "dijit/TooltipDialog", "dijit/form/TextBox", "dojo/_base/xhr", "dojo/dom", "dojo/dom-construct", "dojo/domReady!"], + function(DropDownButton, TooltipDialog, TextBox, xhr, dom, domConstruct){ + var dialog = new TooltipDialog({ + content: + '<strong><label for="username" style="display:inline-block;width:100px;">Username:</label></strong>' + + '<div data-dojo-type="dijit.form.TextBox" id="username"></div><br/>' + + '<strong><label for="pass" style="display:inline-block;width:100px;">Password:</label></strong>' + + '<div data-dojo-type="dijit.form.TextBox" type="password" id="pass"></div><br/>' + + '<button data-dojo-type="dijit.form.Button" data-dojo-props="onClick:doAuthenticate" type="submit">Login</button>' + }); + + button = new DropDownButton({ + label: "Login", + dropDown: dialog + }); + + usernameSpan = domConstruct.create("span", { innerHTML: '<strong>User: </strong><span id="authenticatedUser"></span>', + style: { visibility: "hidden" }}); + + + var loginDiv = dom.byId("login"); + loginDiv.appendChild(button.domNode); + loginDiv.appendChild(usernameSpan); + + + + + updateAuthentication(); +});
\ No newline at end of file diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/UpdatableStore.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/UpdatableStore.js new file mode 100644 index 0000000000..f7ede1a7f7 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/UpdatableStore.js @@ -0,0 +1,110 @@ +/* + * + * 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. + * + */ +define(["dojo/store/Memory", + "dojox/grid/DataGrid", + "dojo/data/ObjectStore", + "dojo/store/Observable"], function (Memory, DataGrid, ObjectStore, Observable) { + + function UpdatableStore( data, divName, structure, func, props, Grid ) { + + var that = this; + var GridType = DataGrid; + + that.store = Observable(Memory({data: data, idProperty: "id"})); + that.dataStore = ObjectStore({objectStore: that.store}); + + var gridProperties = { store: that.dataStore, + structure: structure, + autoHeight: true + }; + if(props) { + for(var prop in props) { + if(props.hasOwnProperty(prop)) + { + gridProperties[ prop ] = props[ prop ]; + } + } + } + + if(Grid) + { + GridType = Grid; + } + + that.grid = new GridType(gridProperties, divName); + + // since we created this grid programmatically, call startup to render it + that.grid.startup(); + + if( func ) + { + func(that); + } + + } + + UpdatableStore.prototype.update = function(data) + { + + var store = this.store; + var theItem; + + // handle deletes + // iterate over existing store... if not in new data then remove + store.query({ }).forEach(function(object) { + if(data) { + for(var i=0; i < data.length; i++) { + if(data[i].id == object.id) { + return; + } + } + } + store.remove(object.id); + + }); + + // iterate over data... + if(data) { + for(var i=0; i < data.length; i++) { + if(theItem = store.get(data[i].id)) { + var modified; + for(var propName in data[i]) { + if(data[i].hasOwnProperty(propName)) { + if(theItem[ propName ] != data[i][ propName ]) { + theItem[ propName ] = data[i][ propName ]; + modified = true; + } + } + } + if(modified) { + // ... check attributes for updates + store.notify(theItem, data[i].id); + } + } else { + // ,,, if not in the store then add + store.put(data[i]); + } + } + } + + }; + return UpdatableStore; +}); diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/footer.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/footer.js new file mode 100644 index 0000000000..ea13b1fc53 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/footer.js @@ -0,0 +1,30 @@ +/* + * + * 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. + * + */ +define(["dojo/_base/xhr", "dojo/query", "dojo/domReady!"], function (xhr, query) { + query('div[qpid-type="footer"]').forEach(function(node, index, arr) { + xhr.get({url: "footer.html", + sync: true, + load: function(data) { + node.innerHTML = data; + } }); + }); +}); + diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/formatter.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/formatter.js new file mode 100644 index 0000000000..2f8683ee1c --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/formatter.js @@ -0,0 +1,99 @@ +/* + * + * 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. + * + */ + +define(function () { + return { + + formatBytes: function formatBytes(amount) + { + var returnVal = { units: "B", + value: "0"}; + + + if(amount < 1000) + { + returnVal.value = amount.toPrecision(3);; + } + else if(amount < 1000 * 1024) + { + returnVal.units = "KB"; + returnVal.value = (amount / 1024).toPrecision(3); + } + else if(amount < 1000 * 1024 * 1024) + { + returnVal.units = "MB"; + returnVal.value = (amount / (1024 * 1024)).toPrecision(3); + } + else if(amount < 1000 * 1024 * 1024 * 1024) + { + returnVal.units = "GB"; + returnVal.value = (amount / (1024 * 1024 * 1024)).toPrecision(3); + } + + return returnVal; + + }, + + formatTime: function formatTime(amount) + { + var returnVal = { units: "ms", + value: "0"}; + + if(amount < 1000) + { + returnVal.units = "ms"; + returnVal.value = amount.toString(); + } + else if(amount < 1000 * 60) + { + returnVal.units = "s"; + returnVal.value = (amount / 1000).toPrecision(3); + } + else if(amount < 1000 * 60 * 60) + { + returnVal.units = "min"; + returnVal.value = (amount / (1000 * 60)).toPrecision(3); + } + else if(amount < 1000 * 60 * 60 * 24) + { + returnVal.units = "hr"; + returnVal.value = (amount / (1000 * 60 * 60)).toPrecision(3); + } + else if(amount < 1000 * 60 * 60 * 24 * 7) + { + returnVal.units = "d"; + returnVal.value = (amount / (1000 * 60 * 60 * 24)).toPrecision(3); + } + else if(amount < 1000 * 60 * 60 * 24 * 365) + { + returnVal.units = "wk"; + returnVal.value = (amount / (1000 * 60 * 60 * 24 * 7)).toPrecision(3); + } + else + { + returnVal.units = "yr"; + returnVal.value = (amount / (1000 * 60 * 60 * 24 * 365)).toPrecision(3); + } + + return returnVal; + } + }; +});
\ No newline at end of file diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/properties.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/properties.js new file mode 100644 index 0000000000..8d85345b74 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/properties.js @@ -0,0 +1,26 @@ +/* + * + * 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. + * + */ +define(["dojo/has", "dojo/_base/sniff", "dojo/domReady!"], + function (has) { + var properties = {}; + properties.useSyncGet = (has("ie") <= 8); + return properties; + });
\ No newline at end of file diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/updater.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/updater.js new file mode 100644 index 0000000000..86bbaa46ba --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/updater.js @@ -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. + * + */ +define(function () { + var updateList = new Array(); + + setInterval(function() { + for(var i = 0; i < updateList.length; i++) { + var obj = updateList[i]; + obj.update(); + } + }, 5000); // TODO: Should make this configurable + + return { + add: function(obj) { + updateList.push(obj); + }, + + remove: function(obj) { + for(var i = 0; i < updateList.length; i++) { + if(updateList[i] === obj) { + updateList.splice(i,1); + return; + } + } + } + }; +});
\ No newline at end of file diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js new file mode 100644 index 0000000000..08fdf5c99b --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js @@ -0,0 +1,120 @@ +/* + * + * 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. + * + */ +define(["dojo/_base/xhr"], + function (xhr) { + var util = {}; + if (Array.isArray) { + util.isArray = function (object) { + return Array.isArray(object); + }; + } else { + util.isArray = function (object) { + return object instanceof Array; + }; + } + + util.flattenStatistics = function (data) { + var attrName, stats, propName, theList; + for(attrName in data) { + if(data.hasOwnProperty(attrName)) { + if(attrName == "statistics") { + stats = data.statistics; + for(propName in stats) { + if(stats.hasOwnProperty( propName )) { + data[ propName ] = stats[ propName ]; + } + } + } else if(data[ attrName ] instanceof Array) { + theList = data[ attrName ]; + + for(var i=0; i < theList.length; i++) { + util.flattenStatistics( theList[i] ); + } + } + } + } + }; + + util.isReservedExchangeName = function(exchangeName) + { + return exchangeName == null || exchangeName == "" || "<<default>>" == exchangeName || exchangeName.indexOf("amq.") == 0 || exchangeName.indexOf("qpid.") == 0; + }; + + util.deleteGridSelections = function(updater, gridName, url, confirmationMessageStart) + { + var grid = updater[gridName].grid; + var data = grid.selection.getSelected(); + if(data.length) + { + var confirmationMessage = null; + if (data.length == 1) + { + confirmationMessage = confirmationMessageStart + " '" + data[0].name + "'?"; + } + else + { + var names = ''; + for(var i = 0; i<data.length; i++) + { + if (names) + { + names += ', '; + } + names += "\""+ data[i].name + "\""; + } + confirmationMessage = confirmationMessageStart + "s " + names + "?"; + } + if(confirm(confirmationMessage)) + { + var i, queryParam; + for(i = 0; i<data.length; i++) + { + if(queryParam) + { + queryParam += "&"; + } + else + { + queryParam = "?"; + } + queryParam += "id=" + data[i].id; + } + var query = url + queryParam; + var success = true + var failureReason = ""; + xhr.del({url: query, sync: true, handleAs: "json"}).then( + function(data) + { + grid.setQuery({id: "*"}); + grid.selection.deselectAll(); + updater.update(); + }, + function(error) {success = false; failureReason = error;}); + if(!success ) + { + alert("Error:" + failureReason); + } + } + } + } + + return util; + });
\ No newline at end of file diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/AuthenticationProvider.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/AuthenticationProvider.js new file mode 100644 index 0000000000..7613fd5d71 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/AuthenticationProvider.js @@ -0,0 +1,122 @@ +/* + * + * 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. + * + */ +define(["dojo/_base/xhr", + "dojo/parser", + "dojo/query", + "dojo/_base/connect", + "qpid/common/properties", + "qpid/common/updater", + "qpid/common/util", + "qpid/common/UpdatableStore", + "dojox/grid/EnhancedGrid", + "dojox/grid/enhanced/plugins/Pagination", + "dojox/grid/enhanced/plugins/IndirectSelection", + "dojo/domReady!"], + function (xhr, parser, query, connect, properties, updater, util, UpdatableStore, EnhancedGrid) { + + function AuthenticationProvider(name, parent, controller) { + this.name = name; + this.controller = controller; + this.modelObj = { type: "authenticationprovider", name: name }; + if(parent) { + this.modelObj.parent = {}; + this.modelObj.parent[ parent.type] = parent; + } + } + + AuthenticationProvider.prototype.getTitle = function() { + return "AuthenticationProvider"; + }; + + AuthenticationProvider.prototype.open = function(contentPane) { + var that = this; + this.contentPane = contentPane; + xhr.get({url: "showAuthProvider.html", + sync: true, + load: function(data) { + contentPane.containerNode.innerHTML = data; + parser.parse(contentPane.containerNode); + + that.authProviderAdapter = new AuthProviderUpdater(contentPane.containerNode, that.modelObj, that.controller); + + updater.add( that.authProviderAdapter ); + + that.authProviderAdapter.update(); + + }}); + }; + + AuthenticationProvider.prototype.close = function() { + updater.remove( this.authProviderAdapter ); + }; + + function AuthProviderUpdater(node, authProviderObj, controller) + { + this.controller = controller; + this.name = query(".name", node)[0]; + /*this.state = dom.byId("state"); + this.durable = dom.byId("durable"); + this.lifetimePolicy = dom.byId("lifetimePolicy"); + */ + this.query = "rest/authenticationprovider/"+encodeURIComponent(authProviderObj.name); + + var that = this; + + xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}) + .then(function(data) + { + that.authProviderData = data[0]; + + util.flattenStatistics( that.authProviderData ); + + that.updateHeader(); + + require(["qpid/management/authenticationprovider/"+that.authProviderData.type], + function(SpecificProvider) { + that.details = new SpecificProvider(node, authProviderObj, controller); + that.details.update(); + }); + + }); + + } + + AuthProviderUpdater.prototype.updateHeader = function() + { + this.name.innerHTML = this.authProviderData[ "name" ]; + /* this.state.innerHTML = this.brokerData[ "state" ]; + this.durable.innerHTML = this.brokerData[ "durable" ]; + this.lifetimePolicy.innerHTML = this.brokerData[ "lifetimePolicy" ]; +*/ + }; + + AuthProviderUpdater.prototype.update = function() + { + + var that = this; + + + }; + + + + return AuthenticationProvider; + }); diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js new file mode 100644 index 0000000000..dcf6711073 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js @@ -0,0 +1,205 @@ +/* + * + * 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. + * + */ +define(["dojo/_base/xhr", + "dojo/parser", + "dojo/query", + "dojo/_base/connect", + "qpid/common/properties", + "qpid/common/updater", + "qpid/common/util", + "qpid/common/UpdatableStore", + "dojox/grid/EnhancedGrid", + "dojox/grid/enhanced/plugins/Pagination", + "dojox/grid/enhanced/plugins/IndirectSelection", + "dojo/domReady!"], + function (xhr, parser, query, connect, properties, updater, util, UpdatableStore, EnhancedGrid) { + + function Broker(name, parent, controller) { + this.name = name; + this.controller = controller; + this.modelObj = { type: "broker", name: name }; + if(parent) { + this.modelObj.parent = {}; + this.modelObj.parent[ parent.type] = parent; + } + } + + Broker.prototype.getTitle = function() + { + return "Broker"; + }; + + Broker.prototype.open = function(contentPane) { + var that = this; + this.contentPane = contentPane; + xhr.get({url: "showBroker.html", + sync: true, + load: function(data) { + contentPane.containerNode.innerHTML = data; + parser.parse(contentPane.containerNode); + + that.brokerUpdater = new BrokerUpdater(contentPane.containerNode, that.modelObj, that.controller); + + updater.add( that.brokerUpdater ); + + that.brokerUpdater.update(); + + }}); + }; + + Broker.prototype.close = function() { + updater.remove( this.brokerUpdater ); + }; + + function BrokerUpdater(node, brokerObj, controller) + { + this.controller = controller; + this.name = query(".broker-name", node)[0]; + /*this.state = dom.byId("state"); + this.durable = dom.byId("durable"); + this.lifetimePolicy = dom.byId("lifetimePolicy"); + */ + this.query = "rest/broker"; + + var that = this; + + xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}) + .then(function(data) + { + that.brokerData= data[0]; + + util.flattenStatistics( that.brokerData); + + that.updateHeader(); + that.vhostsGrid = + new UpdatableStore(that.brokerData.vhosts, query(".broker-virtualhosts")[0], + [ { name: "Virtual Host", field: "name", width: "120px"}, + { name: "Connections", field: "connectionCount", width: "80px"}, + { name: "Queues", field: "queueCount", width: "80px"}, + { name: "Exchanges", field: "exchangeCount", width: "100%"} + ], function(obj) { + connect.connect(obj.grid, "onRowDblClick", obj.grid, + function(evt){ + var idx = evt.rowIndex, + theItem = this.getItem(idx); + var name = obj.dataStore.getValue(theItem,"name"); + that.controller.show("virtualhost", name, brokerObj); + }); + }); + + that.portsGrid = + new UpdatableStore(that.brokerData.ports, query(".broker-ports")[0], + [ { name: "Address", field: "bindingAddress", width: "70px"}, + { name: "Port", field: "port", width: "70px"}, + { name: "Transports", field: "transports", width: "150px"}, + { name: "Protocols", field: "protocols", width: "100%"} + ], function(obj) { + connect.connect(obj.grid, "onRowDblClick", obj.grid, + function(evt){ + var idx = evt.rowIndex, + theItem = this.getItem(idx); + var name = obj.dataStore.getValue(theItem,"name"); + that.controller.show("port", name, brokerObj); + }); + }); + + }); + + xhr.get({url: "rest/logrecords", sync: properties.useSyncGet, handleAs: "json"}) + .then(function(data) + { + that.logData = data; + + var gridProperties = { + height: 400, + plugins: { + pagination: { + pageSizes: ["10", "25", "50", "100"], + description: true, + sizeSwitch: true, + pageStepper: true, + gotoButton: true, + maxPageStep: 4, + position: "bottom" + } + }}; + + + that.logfileGrid = + new UpdatableStore(that.logData, query(".broker-logfile")[0], + [ { name: "Timestamp", field: "timestamp", width: "200px", + formatter: function(val) { + var d = new Date(0); + d.setUTCSeconds(val/1000); + + return d.toLocaleString(); + }}, + { name: "Level", field: "level", width: "60px"}, + { name: "Logger", field: "logger", width: "280px"}, + { name: "Thread", field: "thread", width: "120px"}, + { name: "Log Message", field: "message", width: "100%"} + + ], null, gridProperties, EnhancedGrid); + }); + } + + BrokerUpdater.prototype.updateHeader = function() + { + this.name.innerHTML = this.brokerData[ "name" ]; + /* this.state.innerHTML = this.brokerData[ "state" ]; + this.durable.innerHTML = this.brokerData[ "durable" ]; + this.lifetimePolicy.innerHTML = this.brokerData[ "lifetimePolicy" ]; +*/ + }; + + BrokerUpdater.prototype.update = function() + { + + var that = this; + + xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}).then(function(data) + { + that.brokerData = data[0]; + util.flattenStatistics( that.brokerData ); + + that.updateHeader(); + + that.vhostsGrid.update(that.brokerData.virtualhosts); + + that.portsGrid.update(that.brokerData.ports); + + + }); + + + xhr.get({url: "rest/logrecords", sync: properties.useSyncGet, handleAs: "json"}) + .then(function(data) + { + that.logData = data; + that.logfileGrid.update(that.logData); + }); + + }; + + + + return Broker; + }); diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Connection.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Connection.js new file mode 100644 index 0000000000..01f9a325c5 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Connection.js @@ -0,0 +1,213 @@ +/* + * + * 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. + * + */ +define(["dojo/_base/xhr", + "dojo/parser", + "dojo/query", + "dojo/_base/connect", + "qpid/common/properties", + "qpid/common/updater", + "qpid/common/util", + "qpid/common/formatter", + "qpid/common/UpdatableStore", + "dojo/domReady!"], + function (xhr, parser, query, connect, properties, updater, util, formatter, UpdatableStore) { + + function Connection(name, parent, controller) { + this.name = name; + this.controller = controller; + this.modelObj = { type: "exchange", name: name }; + if(parent) { + this.modelObj.parent = {}; + this.modelObj.parent[ parent.type] = parent; + } + } + + Connection.prototype.getTitle = function() + { + return "Connection: " + this.name; + }; + + Connection.prototype.open = function(contentPane) { + var that = this; + this.contentPane = contentPane; + xhr.get({url: "showConnection.html", + sync: true, + load: function(data) { + contentPane.containerNode.innerHTML = data; + parser.parse(contentPane.containerNode); + + that.connectionUpdater = new ConnectionUpdater(contentPane.containerNode, that.modelObj, that.controller); + + updater.add( that.connectionUpdater ); + + that.connectionUpdater.update(); + + }}); + }; + + Connection.prototype.close = function() { + updater.remove( this.connectionUpdater ); + }; + + function ConnectionUpdater(containerNode, connectionObj, controller) + { + var that = this; + + function findNode(name) { + return query("." + name, containerNode)[0]; + } + + function storeNodes(names) + { + for(var i = 0; i < names.length; i++) { + that[names[i]] = findNode(names[i]); + } + } + + storeNodes(["name", + "state", + "durable", + "lifetimePolicy", + "msgInRate", + "bytesInRate", + "bytesInRateUnits", + "msgOutRate", + "bytesOutRate", + "bytesOutRateUnits"]); + + + + this.query = "rest/connection/"+ encodeURIComponent(connectionObj.parent.virtualhost.name) + "/" + encodeURIComponent(connectionObj.name); + + xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}).then(function(data) + { + that.connectionData = data[0]; + + util.flattenStatistics( that.connectionData ); + + that.updateHeader(); + that.sessionsGrid = new UpdatableStore(that.connectionData.sessions, findNode("sessions"), + [ { name: "Name", field: "name", width: "70px"}, + { name: "Mode", field: "distributionMode", width: "70px"}, + { name: "Msgs Rate", field: "msgRate", + width: "150px"}, + { name: "Bytes Rate", field: "bytesRate", + width: "100%"} + ]); + + + }); + + } + + ConnectionUpdater.prototype.updateHeader = function() + { + this.name.innerHTML = this.connectionData[ "name" ]; + this.state.innerHTML = this.connectionData[ "state" ]; + this.durable.innerHTML = this.connectionData[ "durable" ]; + this.lifetimePolicy.innerHTML = this.connectionData[ "lifetimePolicy" ]; + + }; + + ConnectionUpdater.prototype.update = function() + { + + var that = this; + + xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}).then(function(data) + { + that.connectionData = data[0]; + + util.flattenStatistics( that.connectionData ); + + var sessions = that.connectionData[ "sessions" ]; + + that.updateHeader(); + + var sampleTime = new Date(); + var messageIn = that.connectionData["messagesIn"]; + var bytesIn = that.connectionData["bytesIn"]; + var messageOut = that.connectionData["messagesOut"]; + var bytesOut = that.connectionData["bytesOut"]; + + if(that.sampleTime) + { + var samplePeriod = sampleTime.getTime() - that.sampleTime.getTime(); + + var msgInRate = (1000 * (messageIn - that.messageIn)) / samplePeriod; + var msgOutRate = (1000 * (messageOut - that.messageOut)) / samplePeriod; + var bytesInRate = (1000 * (bytesIn - that.bytesIn)) / samplePeriod; + var bytesOutRate = (1000 * (bytesOut - that.bytesOut)) / samplePeriod; + + that.msgInRate.innerHTML = msgInRate.toFixed(0); + var bytesInFormat = formatter.formatBytes( bytesInRate ); + that.bytesInRate.innerHTML = "(" + bytesInFormat.value; + that.bytesInRateUnits.innerHTML = bytesInFormat.units + "/s)"; + + that.msgOutRate.innerHTML = msgOutRate.toFixed(0); + var bytesOutFormat = formatter.formatBytes( bytesOutRate ); + that.bytesOutRate.innerHTML = "(" + bytesOutFormat.value; + that.bytesOutRateUnits.innerHTML = bytesOutFormat.units + "/s)"; + + if(sessions && that.sessions) + { + for(var i=0; i < sessions.length; i++) + { + var session = sessions[i]; + for(var j = 0; j < that.sessions.length; j++) + { + var oldSession = that.sessions[j]; + if(oldSession.id == session.id) + { + var msgRate = (1000 * (session.messagesOut - oldSession.messagesOut)) / + samplePeriod; + session.msgRate = msgRate.toFixed(0) + "msg/s"; + + var bytesRate = (1000 * (session.bytesOut - oldSession.bytesOut)) / + samplePeriod; + var bytesRateFormat = formatter.formatBytes( bytesRate ); + session.bytesRate = bytesRateFormat.value + bytesRateFormat.units + "/s"; + } + + + } + + } + } + + } + + that.sampleTime = sampleTime; + that.messageIn = messageIn; + that.bytesIn = bytesIn; + that.messageOut = messageOut; + that.bytesOut = bytesOut; + that.sessions = sessions; + + + // update sessions + that.sessionsGrid.update(that.connectionData.sessions) + }); + }; + + + return Connection; + });
\ No newline at end of file diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Exchange.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Exchange.js new file mode 100644 index 0000000000..37bae1ef8e --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Exchange.js @@ -0,0 +1,291 @@ +/* + * + * 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. + * + */ +define(["dojo/_base/xhr", + "dojo/parser", + "dojo/query", + "dojo/_base/connect", + "dijit/registry", + "qpid/common/properties", + "qpid/common/updater", + "qpid/common/util", + "qpid/common/formatter", + "qpid/common/UpdatableStore", + "qpid/management/addBinding", + "dojox/grid/EnhancedGrid", + "dojo/domReady!"], + function (xhr, parser, query, connect, registry, properties, updater, util, formatter, UpdatableStore, addBinding, EnhancedGrid) { + + function Exchange(name, parent, controller) { + this.name = name; + this.controller = controller; + this.modelObj = { type: "exchange", name: name }; + if(parent) { + this.modelObj.parent = {}; + this.modelObj.parent[ parent.type] = parent; + } + } + + + Exchange.prototype.getExchangeName = function() + { + return this.name; + }; + + + Exchange.prototype.getVirtualHostName = function() + { + return this.modelObj.parent.virtualhost.name; + }; + + Exchange.prototype.getTitle = function() + { + return "Exchange: " + this.name; + }; + + Exchange.prototype.open = function(contentPane) { + var that = this; + this.contentPane = contentPane; + xhr.get({url: "showExchange.html", + sync: true, + load: function(data) { + contentPane.containerNode.innerHTML = data; + parser.parse(contentPane.containerNode); + + that.exchangeUpdater = new ExchangeUpdater(contentPane.containerNode, that.modelObj, that.controller); + + updater.add( that.exchangeUpdater ); + + that.exchangeUpdater.update(); + + + var addBindingButton = query(".addBindingButton", contentPane.containerNode)[0]; + connect.connect(registry.byNode(addBindingButton), "onClick", + function(evt){ + addBinding.show({ virtualhost: that.getVirtualHostName(), + exchange: that.getExchangeName()}); + }); + + var deleteBindingButton = query(".deleteBindingButton", contentPane.containerNode)[0]; + connect.connect(registry.byNode(deleteBindingButton), "onClick", + function(evt){ + that.deleteBindings(); + }); + + var isStandard = util.isReservedExchangeName(that.name); + var deleteExchangeButton = query(".deleteExchangeButton", contentPane.containerNode)[0]; + var node = registry.byNode(deleteExchangeButton); + if(isStandard) + { + node.set('disabled', true); + } + else + { + connect.connect(node, "onClick", + function(evt){ + that.deleteExchange(); + }); + } + + }}); + }; + + Exchange.prototype.close = function() { + updater.remove( this.exchangeUpdater ); + }; + + Exchange.prototype.deleteBindings = function() + { + util.deleteGridSelections( + this.exchangeUpdater, + "bindingsGrid", + "rest/binding/"+ encodeURIComponent(this.getVirtualHostName()) + "/" + encodeURIComponent(this.name), + "Are you sure you want to delete binding for queue"); + } + + function ExchangeUpdater(containerNode, exchangeObj, controller) + { + var that = this; + + function findNode(name) { + return query("." + name, containerNode)[0]; + } + + function storeNodes(names) + { + for(var i = 0; i < names.length; i++) { + that[names[i]] = findNode(names[i]); + } + } + + storeNodes(["name", + "state", + "durable", + "lifetimePolicy", + "alertRepeatGap", + "alertRepeatGapUnits", + "alertThresholdMessageAge", + "alertThresholdMessageAgeUnits", + "alertThresholdMessageSize", + "alertThresholdMessageSizeUnits", + "alertThresholdQueueDepthBytes", + "alertThresholdQueueDepthBytesUnits", + "alertThresholdQueueDepthMessages", + "msgInRate", + "bytesInRate", + "bytesInRateUnits", + "msgDropRate", + "bytesDropRate", + "bytesDropRateUnits"]); + + + + this.query = "rest/exchange/"+ encodeURIComponent(exchangeObj.parent.virtualhost.name) + "/" + encodeURIComponent(exchangeObj.name); + + xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}).then(function(data) + { + that.exchangeData = data[0]; + util.flattenStatistics( that.exchangeData ); + + that.updateHeader(); + that.bindingsGrid = new UpdatableStore(that.exchangeData.bindings, findNode("bindings"), + [ { name: "Queue", field: "queue", width: "90px"}, + { name: "Binding Key", field: "name", width: "120px"}, + { name: "Arguments", field: "argumentString", width: "100%"} + ], null, { + keepSelection: true, + plugins: { + pagination: { + pageSizes: ["10", "25", "50", "100"], + description: true, + sizeSwitch: true, + pageStepper: true, + gotoButton: true, + maxPageStep: 4, + position: "bottom" + }, + indirectSelection: true + + }}, EnhancedGrid); + + }); + + } + + ExchangeUpdater.prototype.updateHeader = function() + { + this.name.innerHTML = this.exchangeData[ "name" ]; + this.state.innerHTML = this.exchangeData[ "state" ]; + this.durable.innerHTML = this.exchangeData[ "durable" ]; + this.lifetimePolicy.innerHTML = this.exchangeData[ "lifetimePolicy" ]; + + }; + + ExchangeUpdater.prototype.update = function() + { + + var thisObj = this; + + xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}).then(function(data) + { + thisObj.exchangeData = data[0]; + + util.flattenStatistics( thisObj.exchangeData ); + + var bindings = thisObj.exchangeData[ "bindings" ]; + + if(bindings) + { + for(var i=0; i < bindings.length; i++) + { + if(bindings[i].arguments) + { + bindings[i].argumentString = dojo.toJson(bindings[i].arguments); + } + else + { + bindings[i].argumentString = ""; + } + } + } + + + var sampleTime = new Date(); + + thisObj.updateHeader(); + + var messageIn = thisObj.exchangeData["messagesIn"]; + var bytesIn = thisObj.exchangeData["bytesIn"]; + var messageDrop = thisObj.exchangeData["messagesDropped"]; + var bytesDrop = thisObj.exchangeData["bytesDropped"]; + + if(thisObj.sampleTime) + { + var samplePeriod = sampleTime.getTime() - thisObj.sampleTime.getTime(); + + var msgInRate = (1000 * (messageIn - thisObj.messageIn)) / samplePeriod; + var msgDropRate = (1000 * (messageDrop - thisObj.messageDrop)) / samplePeriod; + var bytesInRate = (1000 * (bytesIn - thisObj.bytesIn)) / samplePeriod; + var bytesDropRate = (1000 * (bytesDrop - thisObj.bytesDrop)) / samplePeriod; + + thisObj.msgInRate.innerHTML = msgInRate.toFixed(0); + var bytesInFormat = formatter.formatBytes( bytesInRate ); + thisObj.bytesInRate.innerHTML = "(" + bytesInFormat.value; + thisObj.bytesInRateUnits.innerHTML = bytesInFormat.units + "/s)"; + + thisObj.msgDropRate.innerHTML = msgDropRate.toFixed(0); + var bytesDropFormat = formatter.formatBytes( bytesDropRate ); + thisObj.bytesDropRate.innerHTML = "(" + bytesDropFormat.value; + thisObj.bytesDropRateUnits.innerHTML = bytesDropFormat.units + "/s)" + + } + + thisObj.sampleTime = sampleTime; + thisObj.messageIn = messageIn; + thisObj.bytesIn = bytesIn; + thisObj.messageDrop = messageDrop; + thisObj.bytesDrop = bytesDrop; + + // update bindings + thisObj.bindingsGrid.update(thisObj.exchangeData.bindings) + + }); + }; + + Exchange.prototype.deleteExchange = function() { + if(confirm("Are you sure you want to delete exchange '" +this.name+"'?")) { + var query = "rest/exchange/"+ encodeURIComponent(this.getVirtualHostName()) + "/" + encodeURIComponent(this.name); + this.success = true + var that = this; + xhr.del({url: query, sync: true, handleAs: "json"}).then( + function(data) { + that.contentPane.onClose() + that.controller.tabContainer.removeChild(that.contentPane); + that.contentPane.destroyRecursive(); + }, + function(error) {that.success = false; that.failureReason = error;}); + if(!this.success ) { + alert("Error:" + this.failureReason); + } + } + } + + return Exchange; + }); diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Queue.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Queue.js new file mode 100644 index 0000000000..8b332dbf92 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Queue.js @@ -0,0 +1,485 @@ +/* + * + * 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. + * + */ +define(["dojo/_base/xhr", + "dojo/parser", + "dojo/query", + "dijit/registry", + "dojo/_base/connect", + "dojo/_base/event", + "dojo/json", + "qpid/common/properties", + "qpid/common/updater", + "qpid/common/util", + "qpid/common/formatter", + "qpid/common/UpdatableStore", + "qpid/management/addBinding", + "qpid/management/moveCopyMessages", + "qpid/management/showMessage", + "dojo/store/JsonRest", + "dojox/grid/EnhancedGrid", + "dojo/data/ObjectStore", + "dojox/grid/enhanced/plugins/Pagination", + "dojox/grid/enhanced/plugins/IndirectSelection", + "dojo/domReady!"], + function (xhr, parser, query, registry, connect, event, json, properties, updater, util, formatter, + UpdatableStore, addBinding, moveMessages, showMessage, JsonRest, EnhancedGrid, ObjectStore) { + + function Queue(name, parent, controller) { + this.name = name; + this.controller = controller; + this.modelObj = { type: "queue", name: name }; + if(parent) { + this.modelObj.parent = {}; + this.modelObj.parent[ parent.type] = parent; + } + } + + Queue.prototype.getQueueName = function() + { + return this.name; + }; + + + Queue.prototype.getVirtualHostName = function() + { + return this.modelObj.parent.virtualhost.name; + }; + + Queue.prototype.getTitle = function() + { + return "Queue: " + this.name; + }; + + Queue.prototype.open = function(contentPane) { + var that = this; + this.contentPane = contentPane; + xhr.get({url: "showQueue.html", + sync: true, + load: function(data) { + contentPane.containerNode.innerHTML = data; + parser.parse(contentPane.containerNode); + + that.queueUpdater = new QueueUpdater(contentPane.containerNode, that, that.controller); + + updater.add( that.queueUpdater ); + + that.queueUpdater.update(); + + var myStore = new JsonRest({target:"rest/message/"+ encodeURIComponent(that.getVirtualHostName()) + + "/" + encodeURIComponent(that.getQueueName())}); + var messageGridDiv = query(".messages",contentPane.containerNode)[0]; + that.dataStore = new ObjectStore({objectStore: myStore}); + that.grid = new EnhancedGrid({ + store: that.dataStore, + autoHeight: 10, + keepSelection: true, + structure: [ + {name:"Size", field:"size", width: "60px"}, + {name:"State", field:"state", width: "120px"}, + + {name:"Arrival", field:"arrivalTime", width: "100%", + formatter: function(val) { + var d = new Date(0); + d.setUTCSeconds(val/1000); + + return d.toLocaleString(); + } } + ], + plugins: { + pagination: { + pageSizes: ["10", "25", "50", "100"], + description: true, + sizeSwitch: true, + pageStepper: true, + gotoButton: true, + maxPageStep: 4, + position: "bottom" + }, + indirectSelection: true + } + }, messageGridDiv); + + connect.connect(that.grid, "onRowDblClick", that.grid, + function(evt){ + var idx = evt.rowIndex, + theItem = this.getItem(idx); + var id = that.dataStore.getValue(theItem,"id"); + showMessage.show({ messageNumber: id, + queue: that.getQueueName(), + virtualhost: that.getVirtualHostName() }); + }); + + var deleteMessagesButton = query(".deleteMessagesButton", contentPane.containerNode)[0]; + var deleteWidget = registry.byNode(deleteMessagesButton); + connect.connect(deleteWidget, "onClick", + function(evt){ + event.stop(evt); + that.deleteMessages(); + }); + var moveMessagesButton = query(".moveMessagesButton", contentPane.containerNode)[0]; + connect.connect(registry.byNode(moveMessagesButton), "onClick", + function(evt){ + event.stop(evt); + that.moveOrCopyMessages({move: true}); + }); + + + var copyMessagesButton = query(".copyMessagesButton", contentPane.containerNode)[0]; + connect.connect(registry.byNode(copyMessagesButton), "onClick", + function(evt){ + event.stop(evt); + that.moveOrCopyMessages({move: false}); + }); + + var addBindingButton = query(".addBindingButton", contentPane.containerNode)[0]; + connect.connect(registry.byNode(addBindingButton), "onClick", + function(evt){ + event.stop(evt); + addBinding.show({ virtualhost: that.getVirtualHostName(), + queue: that.getQueueName()}); + }); + + var deleteQueueButton = query(".deleteQueueButton", contentPane.containerNode)[0]; + connect.connect(registry.byNode(deleteQueueButton), "onClick", + function(evt){ + event.stop(evt); + that.deleteQueue(); + }); + }}); + + + + }; + + Queue.prototype.deleteMessages = function() { + var data = this.grid.selection.getSelected(); + if(data.length) { + var that = this; + if(confirm("Delete " + data.length + " messages?")) { + var i, queryParam; + for(i = 0; i<data.length; i++) { + if(queryParam) { + queryParam += "&"; + } else { + queryParam = "?"; + } + + queryParam += "id=" + data[i].id; + } + var query = "rest/message/"+ encodeURIComponent(that.getVirtualHostName()) + + "/" + encodeURIComponent(that.getQueueName()) + queryParam; + that.success = true + xhr.del({url: query, sync: true, handleAs: "json"}).then( + function(data) { + that.grid.setQuery({id: "*"}); + that.grid.selection.deselectAll(); + that.queueUpdater.update(); + }, + function(error) {that.success = false; that.failureReason = error;}); + if(!that.success ) { + alert("Error:" + this.failureReason); + } + } + } + }; + + Queue.prototype.moveOrCopyMessages = function(obj) { + var that = this; + var move = obj.move; + var data = this.grid.selection.getSelected(); + if(data.length) { + var that = this; + var i, putData = { messages:[] }; + if(move) { + putData.move = true; + } + for(i = 0; i<data.length; i++) { + putData.messages.push(data[i].id); + } + moveMessages.show({ virtualhost: this.getVirtualHostName(), + queue: this.getQueueName(), + data: putData}, function() { + if(move) + { + that.grid.setQuery({id: "*"}); + that.grid.selection.deselectAll(); + } + }); + + } + + + + }; + + Queue.prototype.startup = function() { + this.grid.startup(); + }; + + Queue.prototype.close = function() { + updater.remove( this.queueUpdater ); + }; + + var queueTypeKeys = { + priority: "priorities", + lvq: "lvqKey", + sorted: "sortKey" + }; + + var queueTypeKeyNames = { + priority: "Number of priorities", + lvq: "LVQ key", + sorted: "Sort key" + }; + + function QueueUpdater(containerNode, queueObj, controller) + { + var that = this; + + function findNode(name) { + return query("." + name, containerNode)[0]; + } + + function storeNodes(names) + { + for(var i = 0; i < names.length; i++) { + that[names[i]] = findNode(names[i]); + } + } + + storeNodes(["name", + "state", + "durable", + "lifetimePolicy", + "type", + "typeQualifier", + "alertRepeatGap", + "alertRepeatGapUnits", + "alertThresholdMessageAge", + "alertThresholdMessageAgeUnits", + "alertThresholdMessageSize", + "alertThresholdMessageSizeUnits", + "alertThresholdQueueDepthBytes", + "alertThresholdQueueDepthBytesUnits", + "alertThresholdQueueDepthMessages", + "alternateExchange", + "queueDepthMessages", + "queueDepthBytes", + "queueDepthBytesUnits", + "unacknowledgedMessages", + "unacknowledgedBytes", + "unacknowledgedBytesUnits", + "msgInRate", + "bytesInRate", + "bytesInRateUnits", + "msgOutRate", + "bytesOutRate", + "bytesOutRateUnits"]); + + + + this.query = "rest/queue/"+ encodeURIComponent(queueObj.getVirtualHostName()) + "/" + encodeURIComponent(queueObj.getQueueName()); + + xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}).then(function(data) + { + that.queueData = data[0]; + + util.flattenStatistics( that.queueData ); + + that.updateHeader(); + that.bindingsGrid = new UpdatableStore(that.queueData.bindings, findNode("bindings"), + [ { name: "Exchange", field: "exchange", width: "90px"}, + { name: "Binding Key", field: "name", width: "120px"}, + { name: "Arguments", field: "argumentString", width: "100%"} + ]); + + that.consumersGrid = new UpdatableStore(that.queueData.consumers, findNode("consumers"), + [ { name: "Name", field: "name", width: "70px"}, + { name: "Mode", field: "distributionMode", width: "70px"}, + { name: "Msgs Rate", field: "msgRate", + width: "150px"}, + { name: "Bytes Rate", field: "bytesRate", + width: "100%"} + ]); + + + + + }); + + } + + QueueUpdater.prototype.updateHeader = function() + { + + var bytesDepth; + this.name.innerHTML = this.queueData[ "name" ]; + this.state.innerHTML = this.queueData[ "state" ]; + this.durable.innerHTML = this.queueData[ "durable" ]; + this.lifetimePolicy.innerHTML = this.queueData[ "lifetimePolicy" ]; + this.alternateExchange.innerHTML = this.queueData[ "alternateExchange" ] ? this.queueData[ "alternateExchange" ]: "" ; + + this.queueDepthMessages.innerHTML = this.queueData["queueDepthMessages"]; + bytesDepth = formatter.formatBytes( this.queueData["queueDepthBytes"] ); + this.queueDepthBytes.innerHTML = "(" + bytesDepth.value; + this.queueDepthBytesUnits.innerHTML = bytesDepth.units + ")"; + + this.unacknowledgedMessages.innerHTML = this.queueData["unacknowledgedMessages"]; + bytesDepth = formatter.formatBytes( this.queueData["unacknowledgedBytes"] ); + this.unacknowledgedBytes.innerHTML = "(" + bytesDepth.value; + this.unacknowledgedBytesUnits.innerHTML = bytesDepth.units + ")"; + this.type.innerHTML = this.queueData[ "type" ]; + if (this.queueData.type == "standard") + { + this.typeQualifier.style.display = "none"; + } + else + { + this.typeQualifier.innerHTML = "(" + queueTypeKeyNames[this.queueData.type] + ": " + this.queueData[queueTypeKeys[this.queueData.type]] + ")"; + } + + }; + + QueueUpdater.prototype.update = function() + { + + var thisObj = this; + + xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}).then(function(data) { + var i,j; + thisObj.queueData = data[0]; + util.flattenStatistics( thisObj.queueData ); + + var bindings = thisObj.queueData[ "bindings" ]; + var consumers = thisObj.queueData[ "consumers" ]; + + for(i=0; i < bindings.length; i++) { + bindings[i].argumentString = json.stringify(bindings[i].arguments); + } + + thisObj.updateHeader(); + + + // update alerting info + var alertRepeatGap = formatter.formatTime( thisObj.queueData["alertRepeatGap"] ); + + thisObj.alertRepeatGap.innerHTML = alertRepeatGap.value; + thisObj.alertRepeatGapUnits.innerHTML = alertRepeatGap.units; + + + var alertMsgAge = formatter.formatTime( thisObj.queueData["alertThresholdMessageAge"] ); + + thisObj.alertThresholdMessageAge.innerHTML = alertMsgAge.value; + thisObj.alertThresholdMessageAgeUnits.innerHTML = alertMsgAge.units; + + var alertMsgSize = formatter.formatBytes( thisObj.queueData["alertThresholdMessageSize"] ); + + thisObj.alertThresholdMessageSize.innerHTML = alertMsgSize.value; + thisObj.alertThresholdMessageSizeUnits.innerHTML = alertMsgSize.units; + + var alertQueueDepth = formatter.formatBytes( thisObj.queueData["alertThresholdQueueDepthBytes"] ); + + thisObj.alertThresholdQueueDepthBytes.innerHTML = alertQueueDepth.value; + thisObj.alertThresholdQueueDepthBytesUnits.innerHTML = alertQueueDepth.units; + + thisObj.alertThresholdQueueDepthMessages.innerHTML = thisObj.queueData["alertThresholdQueueDepthMessages"]; + + var sampleTime = new Date(); + var messageIn = thisObj.queueData["totalEnqueuedMessages"]; + var bytesIn = thisObj.queueData["totalEnqueuedBytes"]; + var messageOut = thisObj.queueData["totalDequeuedMessages"]; + var bytesOut = thisObj.queueData["totalDequeuedBytes"]; + + if(thisObj.sampleTime) { + var samplePeriod = sampleTime.getTime() - thisObj.sampleTime.getTime(); + + var msgInRate = (1000 * (messageIn - thisObj.messageIn)) / samplePeriod; + var msgOutRate = (1000 * (messageOut - thisObj.messageOut)) / samplePeriod; + var bytesInRate = (1000 * (bytesIn - thisObj.bytesIn)) / samplePeriod; + var bytesOutRate = (1000 * (bytesOut - thisObj.bytesOut)) / samplePeriod; + + thisObj.msgInRate.innerHTML = msgInRate.toFixed(0); + var bytesInFormat = formatter.formatBytes( bytesInRate ); + thisObj.bytesInRate.innerHTML = "(" + bytesInFormat.value; + thisObj.bytesInRateUnits.innerHTML = bytesInFormat.units + "/s)"; + + thisObj.msgOutRate.innerHTML = msgOutRate.toFixed(0); + var bytesOutFormat = formatter.formatBytes( bytesOutRate ); + thisObj.bytesOutRate.innerHTML = "(" + bytesOutFormat.value; + thisObj.bytesOutRateUnits.innerHTML = bytesOutFormat.units + "/s)"; + + if(consumers && thisObj.consumers) { + for(i=0; i < consumers.length; i++) { + var consumer = consumers[i]; + for(j = 0; j < thisObj.consumers.length; j++) { + var oldConsumer = thisObj.consumers[j]; + if(oldConsumer.id == consumer.id) { + var msgRate = (1000 * (consumer.messagesOut - oldConsumer.messagesOut)) / + samplePeriod; + consumer.msgRate = msgRate.toFixed(0) + "msg/s"; + + var bytesRate = (1000 * (consumer.bytesOut - oldConsumer.bytesOut)) / + samplePeriod; + var bytesRateFormat = formatter.formatBytes( bytesRate ); + consumer.bytesRate = bytesRateFormat.value + bytesRateFormat.units + "/s"; + } + } + } + } + + } + + thisObj.sampleTime = sampleTime; + thisObj.messageIn = messageIn; + thisObj.bytesIn = bytesIn; + thisObj.messageOut = messageOut; + thisObj.bytesOut = bytesOut; + thisObj.consumers = consumers; + + // update bindings + thisObj.bindingsGrid.update(thisObj.queueData.bindings); + + // update consumers + thisObj.consumersGrid.update(thisObj.queueData.consumers) + + }); + }; + + Queue.prototype.deleteQueue = function() { + if(confirm("Are you sure you want to delete queue '" +this.name+"'?")) { + var query = "rest/queue/"+ encodeURIComponent(this.getVirtualHostName()) + "/" + encodeURIComponent(this.name); + this.success = true + var that = this; + xhr.del({url: query, sync: true, handleAs: "json"}).then( + function(data) { + that.contentPane.onClose() + that.controller.tabContainer.removeChild(that.contentPane); + that.contentPane.destroyRecursive(); + }, + function(error) {that.success = false; that.failureReason = error;}); + if(!this.success ) { + alert("Error:" + this.failureReason); + } + } + } + + return Queue; + }); diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/VirtualHost.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/VirtualHost.js new file mode 100644 index 0000000000..957f2381cf --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/VirtualHost.js @@ -0,0 +1,378 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +define(["dojo/_base/xhr", + "dojo/parser", + "dojo/query", + "dojo/_base/connect", + "dijit/registry", + "qpid/common/properties", + "qpid/common/updater", + "qpid/common/util", + "qpid/common/formatter", + "qpid/common/UpdatableStore", + "qpid/management/addQueue", + "qpid/management/addExchange", + "dojox/grid/EnhancedGrid", + "dojo/domReady!"], + function (xhr, parser, query, connect, registry, properties, updater, util, formatter, UpdatableStore, addQueue, addExchange, EnhancedGrid) { + + function VirtualHost(name, parent, controller) { + this.name = name; + this.controller = controller; + this.modelObj = { type: "virtualhost", name: name}; + if(parent) { + this.modelObj.parent = {}; + this.modelObj.parent[ parent.type] = parent; + } + } + + VirtualHost.prototype.getTitle = function() + { + return "VirtualHost: " + this.name; + }; + + VirtualHost.prototype.open = function(contentPane) { + var that = this; + this.contentPane = contentPane; + xhr.get({url: "showVirtualHost.html", + sync: true, + load: function(data) { + contentPane.containerNode.innerHTML = data; + parser.parse(contentPane.containerNode); + + that.vhostUpdater = new Updater(contentPane.containerNode, that.modelObj, that.controller); + + updater.add( that.vhostUpdater ); + + that.vhostUpdater.update(); + + var addQueueButton = query(".addQueueButton", contentPane.containerNode)[0]; + connect.connect(registry.byNode(addQueueButton), "onClick", function(evt){ addQueue.show(that.name) }); + + var deleteQueueButton = query(".deleteQueueButton", contentPane.containerNode)[0]; + connect.connect(registry.byNode(deleteQueueButton), "onClick", + function(evt){ + util.deleteGridSelections( + that.vhostUpdater, + "queuesGrid", + "rest/queue/"+ encodeURIComponent(that.name), + "Are you sure you want to delete queue"); + } + ); + + var addExchangeButton = query(".addExchangeButton", contentPane.containerNode)[0]; + connect.connect(registry.byNode(addExchangeButton), "onClick", function(evt){ addExchange.show(that.name) }); + + var deleteExchangeButton = query(".deleteExchangeButton", contentPane.containerNode)[0]; + connect.connect(registry.byNode(deleteExchangeButton), "onClick", + function(evt) + { + util.deleteGridSelections( + that.vhostUpdater, + "exchangesGrid", + "rest/exchange/"+ encodeURIComponent(that.name), + "Are you sure you want to delete exchange"); + } + ); + }}); + + }; + + VirtualHost.prototype.close = function() { + updater.remove( this.vhostUpdater ); + }; + + function Updater(node, vhost, controller) + { + + var that = this; + + function findNode(name) { + return query("." + name, node)[0]; + } + + function storeNodes(names) + { + for(var i = 0; i < names.length; i++) { + that[names[i]] = findNode(names[i]); + } + } + + storeNodes(["name", + "state", + "durable", + "lifetimePolicy", + "alertRepeatGap", + "alertRepeatGapUnits", + "alertThresholdMessageAge", + "alertThresholdMessageAgeUnits", + "alertThresholdMessageSize", + "alertThresholdMessageSizeUnits", + "alertThresholdQueueDepthBytes", + "alertThresholdQueueDepthBytesUnits", + "alertThresholdQueueDepthMessages", + "msgInRate", + "bytesInRate", + "bytesInRateUnits", + "msgOutRate", + "bytesOutRate", + "bytesOutRateUnits"]); + + this.query = "rest/virtualhost/"+ encodeURIComponent(vhost.name); + + var that = this; + + xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}).then(function(data) { + that.vhostData = data[0]; + + // flatten statistics into attributes + util.flattenStatistics( that.vhostData ); + + var gridProperties = { + keepSelection: true, + plugins: { + pagination: { + pageSizes: ["10", "25", "50", "100"], + description: true, + sizeSwitch: true, + pageStepper: true, + gotoButton: true, + maxPageStep: 4, + position: "bottom" + }, + indirectSelection: true + + }}; + + that.updateHeader(); + that.queuesGrid = new UpdatableStore(that.vhostData.queues, findNode("queues"), + [ { name: "Name", field: "name", width: "90px"}, + { name: "Messages", field: "queueDepthMessages", width: "90px"}, + { name: "Arguments", field: "arguments", width: "100%"} + ], + function(obj) + { + connect.connect(obj.grid, "onRowDblClick", obj.grid, + function(evt){ + var idx = evt.rowIndex, + theItem = this.getItem(idx); + var queueName = obj.dataStore.getValue(theItem,"name"); + controller.show("queue", queueName, vhost); + }); + } , gridProperties, EnhancedGrid); + + that.exchangesGrid = new UpdatableStore(that.vhostData.exchanges, findNode("exchanges"), + [ + { name: "Name", field: "name", width: "120px"}, + { name: "Type", field: "type", width: "120px"}, + { name: "Binding Count", field: "bindingCount", width: "100%"} + ], + function(obj) + { + connect.connect(obj.grid, "onRowDblClick", obj.grid, + function(evt){ + var idx = evt.rowIndex, + theItem = this.getItem(idx); + var exchangeName = obj.dataStore.getValue(theItem,"name"); + controller.show("exchange", exchangeName, vhost); + }); + } , gridProperties, EnhancedGrid); + + + that.connectionsGrid = new UpdatableStore(that.vhostData.connections, + findNode("connections"), + [ { name: "Name", field: "name", width: "150px"}, + { name: "Sessions", field: "sessionCount", width: "70px"}, + { name: "Msgs In", field: "msgInRate", + width: "80px"}, + { name: "Bytes In", field: "bytesInRate", + width: "80px"}, + { name: "Msgs Out", field: "msgOutRate", + width: "80px"}, + { name: "Bytes Out", field: "bytesOutRate", + width: "100%"} + ], + function(obj) + { + connect.connect(obj.grid, "onRowDblClick", obj.grid, + function(evt){ + var idx = evt.rowIndex, + theItem = this.getItem(idx); + var connectionName = obj.dataStore.getValue(theItem,"name"); + controller.show("connection", connectionName, vhost); + }); + } ); + + + + }); + + } + + Updater.prototype.updateHeader = function() + { + this.name.innerHTML = this.vhostData[ "name" ]; + this.state.innerHTML = this.vhostData[ "state" ]; + this.durable.innerHTML = this.vhostData[ "durable" ]; + this.lifetimePolicy.innerHTML = this.vhostData[ "lifetimePolicy" ]; + + + }; + + Updater.prototype.update = function() + { + + var thisObj = this; + + xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}) + .then(function(data) { + thisObj.vhostData = data[0]; + util.flattenStatistics( thisObj.vhostData ); + var connections = thisObj.vhostData[ "connections" ]; + var queues = thisObj.vhostData[ "queues" ]; + var exchanges = thisObj.vhostData[ "exchanges" ]; + + thisObj.updateHeader(); + + + // update alerting info + var alertRepeatGap = formatter.formatTime( thisObj.vhostData["alertRepeatGap"] ); + + thisObj.alertRepeatGap.innerHTML = alertRepeatGap.value; + thisObj.alertRepeatGapUnits.innerHTML = alertRepeatGap.units; + + + var alertMsgAge = formatter.formatTime( thisObj.vhostData["alertThresholdMessageAge"] ); + + thisObj.alertThresholdMessageAge.innerHTML = alertMsgAge.value; + thisObj.alertThresholdMessageAgeUnits.innerHTML = alertMsgAge.units; + + var alertMsgSize = formatter.formatBytes( thisObj.vhostData["alertThresholdMessageSize"] ); + + thisObj.alertThresholdMessageSize.innerHTML = alertMsgSize.value; + thisObj.alertThresholdMessageSizeUnits.innerHTML = alertMsgSize.units; + + var alertQueueDepth = formatter.formatBytes( thisObj.vhostData["alertThresholdQueueDepthBytes"] ); + + thisObj.alertThresholdQueueDepthBytes.innerHTML = alertQueueDepth.value; + thisObj.alertThresholdQueueDepthBytesUnits.innerHTML = alertQueueDepth.units; + + thisObj.alertThresholdQueueDepthMessages.innerHTML = thisObj.vhostData["alertThresholdQueueDepthMessages"]; + + var stats = thisObj.vhostData[ "statistics" ]; + + var sampleTime = new Date(); + var messageIn = stats["messagesIn"]; + var bytesIn = stats["bytesIn"]; + var messageOut = stats["messagesOut"]; + var bytesOut = stats["bytesOut"]; + + if(thisObj.sampleTime) + { + var samplePeriod = sampleTime.getTime() - thisObj.sampleTime.getTime(); + + var msgInRate = (1000 * (messageIn - thisObj.messageIn)) / samplePeriod; + var msgOutRate = (1000 * (messageOut - thisObj.messageOut)) / samplePeriod; + var bytesInRate = (1000 * (bytesIn - thisObj.bytesIn)) / samplePeriod; + var bytesOutRate = (1000 * (bytesOut - thisObj.bytesOut)) / samplePeriod; + + thisObj.msgInRate.innerHTML = msgInRate.toFixed(0); + var bytesInFormat = formatter.formatBytes( bytesInRate ); + thisObj.bytesInRate.innerHTML = "(" + bytesInFormat.value; + thisObj.bytesInRateUnits.innerHTML = bytesInFormat.units + "/s)"; + + thisObj.msgOutRate.innerHTML = msgOutRate.toFixed(0); + var bytesOutFormat = formatter.formatBytes( bytesOutRate ); + thisObj.bytesOutRate.innerHTML = "(" + bytesOutFormat.value; + thisObj.bytesOutRateUnits.innerHTML = bytesOutFormat.units + "/s)"; + + if(connections && thisObj.connections) + { + for(var i=0; i < connections.length; i++) + { + var connection = connections[i]; + for(var j = 0; j < thisObj.connections.length; j++) + { + var oldConnection = thisObj.connections[j]; + if(oldConnection.id == connection.id) + { + msgOutRate = (1000 * (connection.messagesOut - oldConnection.messagesOut)) / + samplePeriod; + connection.msgOutRate = msgOutRate.toFixed(0) + "msg/s"; + + bytesOutRate = (1000 * (connection.bytesOut - oldConnection.bytesOut)) / + samplePeriod; + var bytesOutRateFormat = formatter.formatBytes( bytesOutRate ); + connection.bytesOutRate = bytesOutRateFormat.value + bytesOutRateFormat.units + "/s"; + + + msgInRate = (1000 * (connection.messagesIn - oldConnection.messagesIn)) / + samplePeriod; + connection.msgInRate = msgInRate.toFixed(0) + "msg/s"; + + bytesInRate = (1000 * (connection.bytesIn - oldConnection.bytesIn)) / + samplePeriod; + var bytesInRateFormat = formatter.formatBytes( bytesInRate ); + connection.bytesInRate = bytesInRateFormat.value + bytesInRateFormat.units + "/s"; + } + + + } + + } + } + } + + thisObj.sampleTime = sampleTime; + thisObj.messageIn = messageIn; + thisObj.bytesIn = bytesIn; + thisObj.messageOut = messageOut; + thisObj.bytesOut = bytesOut; + thisObj.connections = connections; + + // update queues + thisObj.queuesGrid.update(thisObj.vhostData.queues); + + // update exchanges + thisObj.exchangesGrid.update(thisObj.vhostData.exchanges); + + var exchangesGrid = thisObj.exchangesGrid.grid; + for(var i=0; i< thisObj.vhostData.exchanges.length; i++) + { + var data = exchangesGrid.getItem(i); + var isStandard = false; + if (data && data.name) + { + isStandard = util.isReservedExchangeName(data.name); + } + exchangesGrid.rowSelectCell.setDisabled(i, isStandard); + } + + // update connections + thisObj.connectionsGrid.update(thisObj.vhostData.connections) + + + }); + }; + + + return VirtualHost; + });
\ No newline at end of file diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addBinding.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addBinding.js new file mode 100644 index 0000000000..83e724d0e9 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addBinding.js @@ -0,0 +1,223 @@ +/* + * 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. + */ +define(["dojo/_base/xhr", + "dojo/dom", + "dojo/dom-construct", + "dojo/_base/window", + "dijit/registry", + "dojo/parser", + "dojo/_base/array", + "dojo/_base/event", + 'dojo/_base/json', + "dojo/store/Memory", + "dijit/form/FilteringSelect", + "dijit/form/NumberSpinner", // required by the form + /* dojox/ validate resources */ + "dojox/validate/us", "dojox/validate/web", + /* basic dijit classes */ + "dijit/Dialog", + "dijit/form/CheckBox", "dijit/form/Textarea", + "dijit/form/FilteringSelect", "dijit/form/TextBox", + "dijit/form/ValidationTextBox", "dijit/form/DateTextBox", + "dijit/form/TimeTextBox", "dijit/form/Button", + "dijit/form/RadioButton", "dijit/form/Form", + "dijit/form/DateTextBox", + /* basic dojox classes */ + "dojox/form/BusyButton", "dojox/form/CheckedMultiSelect", + "dojo/domReady!"], + function (xhr, dom, construct, win, registry, parser, array, event, json, Memory, FilteringSelect) { + + var addBinding = {}; + + var node = construct.create("div", null, win.body(), "last"); + + var convertToBinding = function convertToBinding(formValues) + { + var newBinding = {}; + + newBinding.name = formValues.name; + for(var propName in formValues) + { + if(formValues.hasOwnProperty(propName)) + { + if(propName === "durable") + { + if (formValues.durable[0] && formValues.durable[0] == "durable") { + newBinding.durable = true; + } + } else { + if(formValues[ propName ] !== "") { + newBinding[ propName ] = formValues[propName]; + } + } + + } + } + if(addBinding.queue) { + newBinding.queue = addBinding.queue; + } + if(addBinding.exchange) { + newBinding.exchange = addBinding.exchange; + } + return newBinding; + }; + + + xhr.get({url: "addBinding.html", + sync: true, + load: function(data) { + var theForm; + node.innerHTML = data; + addBinding.dialogNode = dom.byId("addBinding"); + parser.instantiate([addBinding.dialogNode]); + + theForm = registry.byId("formAddBinding"); + array.forEach(theForm.getDescendants(), function(widget) + { + if(widget.name === "type") { + widget.on("change", function(isChecked) { + + var obj = registry.byId(widget.id + ":fields"); + if(obj) { + if(isChecked) { + obj.domNode.style.display = "block"; + obj.resize(); + } else { + obj.domNode.style.display = "none"; + obj.resize(); + } + } + }) + } + + }); + + theForm.on("submit", function(e) { + + event.stop(e); + if(theForm.validate()){ + + var newBinding = convertToBinding(theForm.getValues()); + var that = this; + + xhr.put({url: "rest/binding/"+encodeURIComponent(addBinding.vhost) + +"/"+encodeURIComponent(newBinding.exchange) + +"/"+encodeURIComponent(newBinding.queue) + +"/"+encodeURIComponent(newBinding.name), + sync: true, handleAs: "json", + headers: { "Content-Type": "application/json"}, + putData: json.toJson(newBinding), + load: function(x) {that.success = true; }, + error: function(error) {that.success = false; that.failureReason = error;}}); + + if(this.success === true) + { + registry.byId("addBinding").hide(); + } + else + { + alert("Error:" + this.failureReason); + } + + return false; + + + }else{ + alert('Form contains invalid data. Please correct first'); + return false; + } + + }); + }}); + + addBinding.show = function(obj) { + var that = this; + + addBinding.vhost = obj.virtualhost; + addBinding.queue = obj.queue; + addBinding.exchange = obj.exchange; + registry.byId("formAddBinding").reset(); + + + + xhr.get({url: "rest/queue/" + encodeURIComponent(obj.virtualhost) + "?depth=0", + handleAs: "json"}).then( + function(data) { + var queues = []; + for(var i=0; i < data.length; i++) { + queues[i] = {id: data[i].name, name: data[i].name}; + } + var queueStore = new Memory({ data: queues }); + + + if(that.queueChooser) { + that.queueChooser.destroy( false ); + } + var queueDiv = dom.byId("addBinding.selectQueueDiv"); + var input = construct.create("input", {id: "addBindingSelectQueue"}, queueDiv); + + that.queueChooser = new FilteringSelect({ id: "addBindingSelectQueue", + name: "queue", + store: queueStore, + searchAttr: "name"}, input); + + if(obj.queue) + { + that.queueChooser.set("value", obj.queue); + that.queueChooser.set("disabled", true); + } + + xhr.get({url: "rest/exchange/" + encodeURIComponent(obj.virtualhost) + "?depth=0", + handleAs: "json"}).then( + function(data) { + + var exchanges = []; + for(var i=0; i < data.length; i++) { + exchanges[i] = {id: data[i].name, name: data[i].name}; + } + var exchangeStore = new Memory({ data: exchanges }); + + + if(that.exchangeChooser) { + that.exchangeChooser.destroy( false ); + } + var exchangeDiv = dom.byId("addBinding.selectExchangeDiv"); + var input = construct.create("input", {id: "addBindingSelectExchange"}, exchangeDiv); + + that.exchangeChooser = new FilteringSelect({ id: "addBindingSelectExchange", + name: "exchange", + store: exchangeStore, + searchAttr: "name"}, input); + + if(obj.exchange) + { + that.exchangeChooser.set("value", obj.exchange); + that.exchangeChooser.set("disabled", true); + } + + + registry.byId("addBinding").show(); + }); + + + }); + + + }; + + return addBinding; + }); diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addExchange.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addExchange.js new file mode 100644 index 0000000000..915092a9d1 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addExchange.js @@ -0,0 +1,146 @@ +/* + * + * 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. + * + */ +define(["dojo/_base/xhr", + "dojo/dom", + "dojo/dom-construct", + "dojo/_base/window", + "dijit/registry", + "dojo/parser", + "dojo/_base/array", + "dojo/_base/event", + 'dojo/_base/json', + "dijit/form/NumberSpinner", // required by the form + /* dojox/ validate resources */ + "dojox/validate/us", "dojox/validate/web", + /* basic dijit classes */ + "dijit/Dialog", + "dijit/form/CheckBox", "dijit/form/Textarea", + "dijit/form/FilteringSelect", "dijit/form/TextBox", + "dijit/form/ValidationTextBox", "dijit/form/DateTextBox", + "dijit/form/TimeTextBox", "dijit/form/Button", + "dijit/form/RadioButton", "dijit/form/Form", + "dijit/form/DateTextBox", + /* basic dojox classes */ + "dojox/form/BusyButton", "dojox/form/CheckedMultiSelect", + "dojo/domReady!"], + function (xhr, dom, construct, win, registry, parser, array, event, json) { + + var addExchange = {}; + + var node = construct.create("div", null, win.body(), "last"); + + var convertToExchange = function convertToExchange(formValues) + { + var newExchange = {}; + newExchange.name = formValues.name; + for(var propName in formValues) + { + if(formValues.hasOwnProperty(propName)) + { + if(propName === "durable") + { + if (formValues.durable[0] && formValues.durable[0] == "durable") { + newExchange.durable = true; + } + } else { + if(formValues[ propName ] !== "") { + newExchange[ propName ] = formValues[propName]; + } + } + + } + } + + return newExchange; + }; + + + xhr.get({url: "addExchange.html", + sync: true, + load: function(data) { + var theForm; + node.innerHTML = data; + addExchange.dialogNode = dom.byId("addExchange"); + parser.instantiate([addExchange.dialogNode]); + + theForm = registry.byId("formAddExchange"); + array.forEach(theForm.getDescendants(), function(widget) + { + if(widget.name === "type") { + widget.on("change", function(isChecked) { + + var obj = registry.byId(widget.id + ":fields"); + if(obj) { + if(isChecked) { + obj.domNode.style.display = "block"; + obj.resize(); + } else { + obj.domNode.style.display = "none"; + obj.resize(); + } + } + }) + } + + }); + + theForm.on("submit", function(e) { + + event.stop(e); + if(theForm.validate()){ + + var newExchange = convertToExchange(theForm.getValues()); + var that = this; + xhr.put({url: "rest/exchange/"+encodeURIComponent(addExchange.vhost) + + "/"+encodeURIComponent(newExchange.name), sync: true, handleAs: "json", + headers: { "Content-Type": "application/json"}, + putData: json.toJson(newExchange), + load: function(x) {that.success = true; }, + error: function(error) {that.success = false; that.failureReason = error;}}); + + if(this.success === true) + { + registry.byId("addExchange").hide(); + } + else + { + alert("Error:" + this.failureReason); + } + + return false; + + + }else{ + alert('Form contains invalid data. Please correct first'); + return false; + } + + }); + }}); + + addExchange.show = function(vhost) { + addExchange.vhost = vhost; + registry.byId("formAddExchange").reset(); + registry.byId("addExchange").show(); + }; + + return addExchange; + });
\ No newline at end of file diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addQueue.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addQueue.js new file mode 100644 index 0000000000..53f82aff48 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addQueue.js @@ -0,0 +1,191 @@ +/* + * + * 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. + * + */ +define(["dojo/_base/xhr", + "dojo/dom", + "dojo/dom-construct", + "dojo/_base/window", + "dijit/registry", + "dojo/parser", + "dojo/_base/array", + "dojo/_base/event", + 'dojo/_base/json', + "dijit/form/NumberSpinner", // required by the form + /* dojox/ validate resources */ + "dojox/validate/us", "dojox/validate/web", + /* basic dijit classes */ + "dijit/Dialog", + "dijit/form/CheckBox", "dijit/form/Textarea", + "dijit/form/FilteringSelect", "dijit/form/TextBox", + "dijit/form/ValidationTextBox", "dijit/form/DateTextBox", + "dijit/form/TimeTextBox", "dijit/form/Button", + "dijit/form/RadioButton", "dijit/form/Form", + "dijit/form/DateTextBox", + /* basic dojox classes */ + "dojox/form/BusyButton", "dojox/form/CheckedMultiSelect", + "dojo/domReady!"], + function (xhr, dom, construct, win, registry, parser, array, event, json) { + + var addQueue = {}; + + var node = construct.create("div", null, win.body(), "last"); + + var typeSpecificFields = { + priorities: "priority", + lvqKey: "lvq", + sortKey: "sorted" + }; + + var requiredFields = { + priority: "priorities", + sorted: "sortkey" + }; + + var fieldConverters = { + queueFlowControlSizeBytes: parseInt, + queueFlowResumeSizeBytes: parseInt, + alertThresholdMessageSize: parseInt, + alertThresholdQueueDepthMessages: parseInt, + maximumDeliveryAttempts: parseInt, + alertThresholdMessageAge: parseInt, + alertRepeatGap: parseInt + } + + var convertToQueue = function convertToQueue(formValues) + { + var newQueue = {}; + newQueue.name = formValues.name; + for(var propName in formValues) + { + if(formValues.hasOwnProperty(propName)) + { + if(propName === "durable") + { + if (formValues.durable[0] && formValues.durable[0] == "durable") { + newQueue.durable = true; + } + } + else if(propName === "dlqEnabled") + { + if (formValues.dlqEnabled[0] && formValues.dlqEnabled[0] == "dlqEnabled") { + newQueue["x-qpid-dlq-enabled"] = true; + } + } + else if (!typeSpecificFields.hasOwnProperty(propName) || + formValues.type === typeSpecificFields[ propName ]) { + if(formValues[ propName ] !== "") { + if (fieldConverters.hasOwnProperty(propName)) + { + newQueue[ propName ] = fieldConverters[propName](formValues[propName]); + } + else + { + newQueue[ propName ] = formValues[propName]; + } + } + } + + } + } + + return newQueue; + }; + + + xhr.get({url: "addQueue.html", + sync: true, + load: function(data) { + var theForm; + node.innerHTML = data; + addQueue.dialogNode = dom.byId("addQueue"); + parser.instantiate([addQueue.dialogNode]); + + // for children which have name type, add a function to make all the associated rows + // visible / invisible as the radio button is checked / unchecked + + theForm = registry.byId("formAddQueue"); + array.forEach(theForm.getDescendants(), function(widget) + { + if(widget.name === "type") { + widget.on("change", function(isChecked) { + + var objId = widget.id + ":fields"; + var obj = registry.byId(objId); + if(obj) { + if(isChecked) { + obj.domNode.style.display = "block"; + } else { + obj.domNode.style.display = "none"; + } + obj.resize(); + var widgetValue = widget.value; + if (requiredFields.hasOwnProperty(widgetValue)) + { + dijit.byId('formAddQueue.' + requiredFields[widgetValue]).required = isChecked; + } + } + }) + } + + }); + + theForm.on("submit", function(e) { + + event.stop(e); + if(theForm.validate()){ + + var newQueue = convertToQueue(theForm.getValues()); + var that = this; + + xhr.put({url: "rest/queue/"+encodeURIComponent(addQueue.vhost) + +"/"+encodeURIComponent(newQueue.name), sync: true, handleAs: "json", + headers: { "Content-Type": "application/json"}, + putData: json.toJson(newQueue), + load: function(x) {that.success = true; }, + error: function(error) {that.success = false; that.failureReason = error;}}); + + if(this.success === true) + { + registry.byId("addQueue").hide(); + } + else + { + alert("Error:" + this.failureReason); + } + + return false; + + + }else{ + alert('Form contains invalid data. Please correct first'); + return false; + } + + }); + }}); + + addQueue.show = function(vhost) { + addQueue.vhost = vhost; + registry.byId("formAddQueue").reset(); + registry.byId("addQueue").show(); + }; + + return addQueue; + });
\ No newline at end of file diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/PrincipalDatabaseAuthenticationManager.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/PrincipalDatabaseAuthenticationManager.js new file mode 100644 index 0000000000..8e5ac862bd --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/authenticationprovider/PrincipalDatabaseAuthenticationManager.js @@ -0,0 +1,327 @@ +/* + * + * 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. + * + */ +define(["dojo/_base/xhr", + "dojo/dom", + "dojo/parser", + "dojo/query", + "dojo/dom-construct", + "dojo/_base/connect", + "dojo/_base/window", + "dojo/_base/event", + "dojo/_base/json", + "dijit/registry", + "qpid/common/util", + "qpid/common/properties", + "qpid/common/updater", + "qpid/common/UpdatableStore", + "dojox/grid/EnhancedGrid", + "dojox/grid/enhanced/plugins/Pagination", + "dojox/grid/enhanced/plugins/IndirectSelection", + "dojox/validate/us", "dojox/validate/web", + "dijit/Dialog", + "dijit/form/TextBox", + "dijit/form/ValidationTextBox", + "dijit/form/TimeTextBox", "dijit/form/Button", + "dijit/form/Form", + "dijit/form/DateTextBox", + "dojo/domReady!"], + function (xhr, dom, parser, query, construct, connect, win, event, json, registry, util, properties, updater, UpdatableStore, EnhancedGrid) { + function DatabaseAuthManager(containerNode, authProviderObj, controller) { + var node = construct.create("div", null, containerNode, "last"); + var that = this; + this.name = authProviderObj.name; + xhr.get({url: "authenticationprovider/showPrincipalDatabaseAuthenticationManager.html", + sync: true, + load: function(data) { + node.innerHTML = data; + parser.parse(node); + + + that.authDatabaseUpdater= new AuthProviderUpdater(node, authProviderObj, controller); + + updater.add( that.authDatabaseUpdater); + + that.authDatabaseUpdater.update(); + + + }}); + } + + DatabaseAuthManager.prototype.update = function() { + this.authDatabaseUpdater.update(); + }; + + DatabaseAuthManager.prototype.close = function() { + updater.remove( this.authDatabaseUpdater ); + }; + + function AuthProviderUpdater(node, authProviderObj, controller) + { + this.controller = controller; + this.query = "rest/authenticationprovider/"+encodeURIComponent(authProviderObj.name); + this.name = authProviderObj.name; + var that = this; + + xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}) + .then(function(data) { + that.authProviderData = data[0]; + + util.flattenStatistics( that.authProviderData ); + + var userDiv = query(".users")[0]; + + var gridProperties = { + height: 400, + keepSelection: true, + plugins: { + pagination: { + pageSizes: ["10", "25", "50", "100"], + description: true, + sizeSwitch: true, + pageStepper: true, + gotoButton: true, + maxPageStep: 4, + position: "bottom" + }, + indirectSelection: true + + }}; + + + that.usersGrid = + new UpdatableStore(that.authProviderData.users, userDiv, + [ { name: "User Name", field: "name", width: "100%" } + ], function(obj) { + connect.connect(obj.grid, "onRowDblClick", obj.grid, + function(evt){ + var idx = evt.rowIndex, + theItem = this.getItem(idx); + var name = obj.dataStore.getValue(theItem,"name"); + var id = obj.dataStore.getValue(theItem,"id"); + setPassword.show(authProviderObj.name, {name: name, id: id}); + }); + }, gridProperties, EnhancedGrid); + + + var addUserButton = query(".addUserButton", node)[0]; + connect.connect(registry.byNode(addUserButton), "onClick", function(evt){ addUser.show(authProviderObj.name) }); + + var deleteMessagesButton = query(".deleteUserButton", node)[0]; + var deleteWidget = registry.byNode(deleteMessagesButton); + connect.connect(deleteWidget, "onClick", + function(evt){ + event.stop(evt); + that.deleteUsers(); + }); + }); + } + + AuthProviderUpdater.prototype.deleteUsers = function() + { + var grid = this.usersGrid.grid; + var data = grid.selection.getSelected(); + if(data.length) { + var that = this; + if(confirm("Delete " + data.length + " users?")) { + var i, queryParam; + for(i = 0; i<data.length; i++) { + if(queryParam) { + queryParam += "&"; + } else { + queryParam = "?"; + } + + queryParam += "id=" + data[i].id; + } + var query = "rest/user/"+ encodeURIComponent(that.name) + + queryParam; + that.success = true + xhr.del({url: query, sync: true, handleAs: "json"}).then( + function(data) { + grid.setQuery({id: "*"}); + grid.selection.deselectAll(); + that.update(); + }, + function(error) {that.success = false; that.failureReason = error;}); + if(!that.success ) { + alert("Error:" + this.failureReason); + } + } +} + }; + + AuthProviderUpdater.prototype.update = function() + { + + var that = this; + + xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}) + .then(function(data) { + that.authProviderData = data[0]; + util.flattenStatistics( that.authProviderData ); + + that.usersGrid.update(that.authProviderData.users); + + }); + + + }; + + var addUser = {}; + + var node = construct.create("div", null, win.body(), "last"); + + var convertToUser = function convertToUser(formValues) { + var newUser = {}; + newUser.name = formValues.name; + for(var propName in formValues) + { + if(formValues.hasOwnProperty(propName)) { + if(formValues[ propName ] !== "") { + newUser[ propName ] = formValues[propName]; + } + } + } + + return newUser; + }; + + + xhr.get({url: "authenticationprovider/addUser.html", + sync: true, + load: function(data) { + var theForm; + node.innerHTML = data; + addUser.dialogNode = dom.byId("addUser"); + parser.instantiate([addUser.dialogNode]); + + var that = this; + + theForm = registry.byId("formAddUser"); + theForm.on("submit", function(e) { + + event.stop(e); + if(theForm.validate()){ + + var newUser = convertToUser(theForm.getValues()); + + + var url = "rest/user/"+encodeURIComponent(addUser.authProvider) + + "/"+encodeURIComponent(newUser.name); + + xhr.put({url: url, sync: true, handleAs: "json", + headers: { "Content-Type": "application/json"}, + putData: json.toJson(newUser), + load: function(x) {that.success = true; }, + error: function(error) {that.success = false; that.failureReason = error;}}); + + if(that.success === true) { + registry.byId("addUser").hide(); + } else { + alert("Error:" + that.failureReason); + } + + return false; + + + }else{ + alert('Form contains invalid data. Please correct first'); + return false; + } + + }); + }}); + + addUser.show = function(authProvider) { + addUser.authProvider = authProvider; + registry.byId("formAddUser").reset(); + registry.byId("addUser").show(); + }; + + + var setPassword = {}; + + var setPasswordNode = construct.create("div", null, win.body(), "last"); + + xhr.get({url: "authenticationprovider/setPassword.html", + sync: true, + load: function(data) { + var theForm; + setPasswordNode.innerHTML = data; + setPassword.dialogNode = dom.byId("setPassword"); + parser.instantiate([setPassword.dialogNode]); + + var that = this; + + theForm = registry.byId("formSetPassword"); + theForm.on("submit", function(e) { + + event.stop(e); + if(theForm.validate()){ + + var newUser = convertToUser(theForm.getValues()); + newUser.name = setPassword.name; + newUser.id = setPassword.id; + + var url = "rest/user/"+encodeURIComponent(setPassword.authProvider) + + "/"+encodeURIComponent(newUser.name); + + xhr.put({url: url, sync: true, handleAs: "json", + headers: { "Content-Type": "application/json"}, + putData: json.toJson(newUser), + load: function(x) {that.success = true; }, + error: function(error) {that.success = false; that.failureReason = error;}}); + + if(that.success === true) { + registry.byId("setPassword").hide(); + } else { + alert("Error:" + that.failureReason); + } + + return false; + + + }else{ + alert('Form contains invalid data. Please correct first'); + return false; + } + + }); + }}); + + setPassword.show = function(authProvider, user) { + setPassword.authProvider = authProvider; + setPassword.name = user.name; + setPassword.id = user.id; + registry.byId("formSetPassword").reset(); + + var namebox = registry.byId("formSetPassword.name"); + namebox.set("value", user.name); + namebox.set("disabled", true); + + registry.byId("setPassword").show(); + + }; + + + + return DatabaseAuthManager; + }); diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/controller.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/controller.js new file mode 100644 index 0000000000..1aa05a5a3c --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/controller.js @@ -0,0 +1,104 @@ +/* + * + * 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. + * + */ +define(["dojo/dom", + "dijit/registry", + "dijit/layout/ContentPane", + "qpid/management/Broker", + "qpid/management/VirtualHost", + "qpid/management/Exchange", + "qpid/management/Queue", + "qpid/management/Connection", + "qpid/management/AuthenticationProvider", + "dojo/ready", + "dojo/domReady!"], + function (dom, registry, ContentPane, Broker, VirtualHost, Exchange, Queue, Connection, AuthProvider, ready) { + var controller = {}; + + var constructors = { broker: Broker, virtualhost: VirtualHost, exchange: Exchange, + queue: Queue, connection: Connection, authenticationprovider: AuthProvider }; + + var tabDiv = dom.byId("managedViews"); + + ready(function() { + controller.tabContainer = registry.byId("managedViews"); + }); + + + controller.viewedObjects = {}; + + controller.show = function(objType, name, parent) { + + function generateName(obj) + { + if(obj) { + var name = ""; + if(obj.parent) + { + for(var prop in obj.parent) { + if(obj.parent.hasOwnProperty(prop)) { + name = name + generateName( obj.parent[ prop ]); + } + } + + } + return name + parent.type +":" + parent.name + "/" + } + } + + var that = this; + var objId = generateName(parent) + objType+":"+name; + if( this.viewedObjects[ objId ] ) { + this.tabContainer.selectChild(this.viewedObjects[ objId ].contentPane); + } else { + var Constructor = constructors[ objType ]; + if(Constructor) { + var obj = new Constructor(name, parent, this); + this.viewedObjects[ objId ] = obj; + + var contentPane = new ContentPane({ region: "center" , + title: obj.getTitle(), + closable: true, + onClose: function() { + obj.close(); + delete that.viewedObjects[ objId ]; + return true; + } + }); + this.tabContainer.addChild( contentPane ); + obj.open(contentPane); + contentPane.startup(); + if(obj.startup) { + obj.startup(); + } + this.tabContainer.selectChild( contentPane ); + } + + } + + }; + + ready(function() { + controller.show("broker",""); + }); + + + return controller; + });
\ No newline at end of file diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/moveCopyMessages.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/moveCopyMessages.js new file mode 100644 index 0000000000..8cc488324f --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/moveCopyMessages.js @@ -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. + */ + +define(["dojo/_base/xhr", + "dojo/dom", + "dojo/dom-construct", + "dojo/_base/window", + "dijit/registry", + "dojo/parser", + "dojo/_base/array", + "dojo/_base/event", + 'dojo/_base/json', + "dojo/store/Memory", + "dijit/form/FilteringSelect", + "dojo/query", + "dojo/_base/connect", + "dojo/domReady!"], + function (xhr, dom, construct, win, registry, parser, array, event, json, Memory, FilteringSelect, query, connect) { + + var moveMessages = {}; + + var node = construct.create("div", null, win.body(), "last"); + + xhr.get({url: "moveCopyMessages.html", + sync: true, + load: function(data) { + var theForm; + node.innerHTML = data; + moveMessages.dialogNode = dom.byId("moveMessages"); + parser.instantiate([moveMessages.dialogNode]); + + theForm = registry.byId("formMoveMessages"); + + + var cancelButton = query(".moveMessageCancel")[0]; + connect.connect(registry.byNode(cancelButton), "onClick", + function(evt){ + event.stop(evt); + registry.byId("moveMessages").hide(); + }); + + + theForm.on("submit", function(e) { + + event.stop(e); + if(theForm.validate()){ + + moveMessages.data.destinationQueue = theForm.getValues()["queue"]; + var that = this; + + xhr.post({url: "rest/message/"+encodeURIComponent(moveMessages.vhost) + +"/"+encodeURIComponent(moveMessages.queue), + sync: true, handleAs: "json", + headers: { "Content-Type": "application/json"}, + postData: json.toJson(moveMessages.data), + load: function(x) {that.success = true; }, + error: function(error) {that.success = false; that.failureReason = error;}}); + + if(this.success === true) { + registry.byId("moveMessages").hide(); + if(moveMessages.next) { + moveMessages.next(); + } + } else { + alert("Error:" + this.failureReason); + } + + return false; + + + }else{ + alert('Form contains invalid data. Please correct first'); + return false; + } + + }); + + }}); + + moveMessages.show = function(obj, next) { + var that = this; + + moveMessages.vhost = obj.virtualhost; + moveMessages.queue = obj.queue; + moveMessages.data = obj.data; + moveMessages.next = next; + registry.byId("formMoveMessages").reset(); + + + + xhr.get({url: "rest/queue/" + encodeURIComponent(obj.virtualhost) + "?depth=0", + handleAs: "json"}).then( + function(data) { + var queues = []; + for(var i=0; i < data.length; i++) { + queues[i] = {id: data[i].name, name: data[i].name}; + } + var queueStore = new Memory({ data: queues }); + + + if(that.queueChooser) { + that.queueChooser.destroy( false ); + } + var queueDiv = dom.byId("moveMessages.selectQueueDiv"); + var input = construct.create("input", {id: "moveMessagesSelectQueue"}, queueDiv); + + that.queueChooser = new FilteringSelect({ id: "moveMessagesSelectQueue", + name: "queue", + store: queueStore, + searchAttr: "name"}, input); + + + + registry.byId("moveMessages").show(); + + + }); + + + }; + + return moveMessages; + }); diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/showMessage.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/showMessage.js new file mode 100644 index 0000000000..b1ccc0ca07 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/showMessage.js @@ -0,0 +1,142 @@ +/* + * 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. + */ + +define(["dojo/_base/xhr", + "dojo/dom", + "dojo/dom-construct", + "dojo/dom-class", + "dojo/_base/window", + "dijit/registry", + "dojo/parser", + "dojo/_base/array", + "dojo/_base/event", + 'dojo/_base/json', + "dojo/query", + "dojo/_base/connect", + "qpid/common/properties", + "dojox/html/entities", + "dojo/domReady!"], + function (xhr, dom, construct, domClass, win, registry, parser, array, event, json, query, connect, properties, entities) { + + + function encode(val){ + return typeof val === 'string' ? entities.encode(val) : val; + } + + var showMessage = {}; + + showMessage.hide = function () { + if(this.populatedFields) { + for(var i = 0 ; i < this.populatedFields.length; i++) { + this.populatedFields[i].innerHTML = ""; + } + this.populatedFields = []; + } + registry.byId("showMessage").hide(); + }; + + showMessage.loadViewMessage = function(data) { + var that = this; + node.innerHTML = data; + showMessage.dialogNode = dom.byId("showMessage"); + parser.instantiate([showMessage.dialogNode]); + + var closeButton = query(".closeViewMessage")[0]; + connect.connect(closeButton, "onclick", + function (evt) { + event.stop(evt); + showMessage.hide(); + }); + }; + + showMessage.populateShowMessage = function(data) { + + this.populatedFields = []; + + for(var attrName in data) { + if(data.hasOwnProperty(attrName)) { + var fields = query(".message-"+attrName, this.dialogNode); + if(fields && fields.length != 0) { + var field = fields[0]; + this.populatedFields.push(field); + var val = data[attrName]; + if(val) { + if(domClass.contains(field,"map")) { + var tableStr = "<table style='border: 1pt'><tr><th style='width: 6em; font-weight: bold'>Header</th><th style='font-weight: bold'>Value</th></tr>"; + for(var name in val) { + if(val.hasOwnProperty(name)) { + + tableStr += "<tr><td>"+encode(name)+"</td>"; + tableStr += "<td>"+encode(val[ name ])+"</td></tr>"; + } + field.innerHTML = tableStr; + } + tableStr += "</table>"; + } else if(domClass.contains(field,"datetime")) { + var d = new Date(0); + d.setUTCSeconds(val/1000); + field.innerHTML = d.toLocaleString(); + } else { + field.innerHTML = encode(val); + } + } + } + } + } + var contentField = query(".message-content", this.dialogNode)[0]; + + if(data.mimeType && data.mimeType.match(/text\/.*/)) { + xhr.get({url: "rest/message-content/" + encodeURIComponent(showMessage.virtualhost) + + "/" + encodeURIComponent(showMessage.queue) + + "/" + encodeURIComponent(showMessage.messageNumber), + sync: true + + }).then(function(obj) { contentField.innerHTML = encode(obj) }); + } else { + contentField.innerHTML = "<a href=\"" + "rest/message-content/" + encodeURIComponent(showMessage.virtualhost) + + "/" + encodeURIComponent(showMessage.queue) + + "/" + encodeURIComponent(showMessage.messageNumber) + + "\" target=\"_blank\">Download</a>"; + } + this.populatedFields.push(contentField); + + registry.byId("showMessage").show(); + }; + + showMessage.show = function(obj) { + showMessage.virtualhost = obj.virtualhost; + showMessage.queue = obj.queue; + showMessage.messageNumber = obj.messageNumber; + + xhr.get({url: "rest/message/" + encodeURIComponent(obj.virtualhost) + + "/" + encodeURIComponent(obj.queue) + + "/" + encodeURIComponent(obj.messageNumber), + sync: properties.useSyncGet, + handleAs: "json", + load: this.populateShowMessage + }); + }; + + var node = construct.create("div", null, win.body(), "last"); + + xhr.get({url: "showMessage.html", + sync: true, + load: showMessage.loadViewMessage + }); + + return showMessage; + }); diff --git a/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/treeView.js b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/treeView.js new file mode 100644 index 0000000000..b1d4abf8c1 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/treeView.js @@ -0,0 +1,313 @@ +/* + * + * 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. + * + */ +define(["dojo/_base/xhr", + "dojo/query", + "dojo/io-query", + "dijit/Tree", + "qpid/common/util", + "qpid/common/updater", + "qpid/management/controller", + "dojo/domReady!"], + function (xhr, query, ioQuery, Tree, util, updater, controller) { + + function TreeViewModel(queryString) { + this.query = queryString; + + this.onChildrenChange = function (parent, children) { + // fired when the set of children for an object change + }; + + this.onChange = function (object) { + // fired when the properties of an object change + }; + + this.onDelete = function (object) { + // fired when an object is deleted + }; + + } + + + TreeViewModel.prototype.buildModel = function (data) { + this.model = data; + + }; + + TreeViewModel.prototype.updateModel = function (data) { + var that = this; + + function checkForChanges(oldData, data) { + var propName; + if (oldData.name != data.name) { + that.onChange(data); + } + + var childChanges = false; + // Iterate over old childTypes, check all are in new + for (propName in oldData) { + if (oldData.hasOwnProperty(propName)) { + var oldChildren = oldData[ propName ]; + if (util.isArray(oldChildren)) { + + var newChildren = data[ propName ]; + + if (!(newChildren && util.isArray(newChildren))) { + childChanges = true; + } else { + var subChanges = false; + // iterate over elements in array, make sure in both, in which case recurse + for (var i = 0; i < oldChildren.length; i++) { + var matched = false; + for (var j = 0; j < newChildren.length; j++) { + if (oldChildren[i].id == newChildren[j].id) { + checkForChanges(oldChildren[i], newChildren[j]); + matched = true; + break; + } + } + if (!matched) { + subChanges = true; + } + } + if (subChanges == true || oldChildren.length != newChildren.length) { + that.onChildrenChange({ id:data.id + propName, _dummyChild:propName, data:data }, + newChildren); + } + } + } + } + } + + for (propName in data) { + if (data.hasOwnProperty(propName)) { + var prop = data[ propName ]; + if (util.isArray(prop)) { + if (!(oldData[ propName ] && util.isArray(oldData[propName]))) { + childChanges = true; + } + } + } + } + + if (childChanges) { + var children = []; + that.getChildren(data, function (theChildren) { + children = theChildren + }); + that.onChildrenChange(data, children); + } + } + + var oldData = this.model; + this.model = data; + + checkForChanges(oldData, data); + }; + + + TreeViewModel.prototype.fetchItemByIdentity = function (id) { + + function fetchItem(id, data) { + var propName; + + if (data.id == id) { + return data; + } else if (id.indexOf(data.id) == 0) { + return { id:id, _dummyChild:id.substring(id.length), data:data }; + } else { + for (propName in data) { + if (data.hasOwnProperty(propName)) { + var prop = data[ propName ]; + if (util.isArray(prop)) { + for (var i = 0; i < prop.length; i++) { + var theItem = fetchItem(id, prop[i]); + if (theItem) { + return theItem; + } + } + } + } + } + return null; + } + } + + return fetchItem(id, this.model); + }; + + TreeViewModel.prototype.getChildren = function (parentItem, onComplete) { + + if (parentItem) { + if (parentItem._dummyChild) { + onComplete(parentItem.data[ parentItem._dummyChild ]); + } else { + var children = []; + for (var propName in parentItem) { + if (parentItem.hasOwnProperty(propName)) { + var prop = parentItem[ propName ]; + + if (util.isArray(prop)) { + children.push({ id:parentItem.id + + propName, _dummyChild:propName, data:parentItem }); + } + } + } + onComplete(children); + } + } else { + onComplete([]); + } + }; + + TreeViewModel.prototype.getIdentity = function (theItem) { + if (theItem) { + return theItem.id; + } + + }; + + TreeViewModel.prototype.getLabel = function (theItem) { + if (theItem) { + if (theItem._dummyChild) { + return theItem._dummyChild; + } else { + return theItem.name; + } + } else { + return ""; + } + }; + + TreeViewModel.prototype.getRoot = function (onItem) { + onItem(this.model); + }; + + TreeViewModel.prototype.mayHaveChildren = function (theItem) { + if (theItem) { + if (theItem._dummyChild) { + return true; + } else { + for (var propName in theItem) { + if (theItem.hasOwnProperty(propName)) { + var prop = theItem[ propName ]; + if (util.isArray(prop)) { + return true; + } + } + } + return false; + } + } else { + return false; + } + }; + + TreeViewModel.prototype.relocate = function (theItem) { + + function findItemDetails(theItem, details, type, object) { + if (theItem.id == object.id) { + details.type = type; + details[ type ] = object.name; + } else { + details[ type ] = object.name; + + // iterate over children + for (var propName in object) { + if (object.hasOwnProperty(propName)) { + var prop = object[ propName ]; + if (util.isArray(prop)) { + for (var i = 0; i < prop.length; i++) { + findItemDetails(theItem, details, propName.substring(0, propName.length - 1), + prop[i]); + + if (details.type) { + break; + } + } + } + if (details.type) { + break; + } + } + } + + if (!details.type) { + details[ type ] = null; + } + } + } + + var details = new Object(); + + findItemDetails(theItem, details, "broker", this.model); + + if (details.type == "broker") { + controller.show("broker", ""); + } else if (details.type == "virtualhost") { + controller.show("virtualhost", details.virtualhost, {type:"broker", name:""}); + } else if (details.type == "exchange") { + controller.show("exchange", details.exchange, { type: "virtualhost", name: details.virtualhost, parent: {broker: {type:"broker", name:""}}}); + } else if (details.type == "queue") { + controller.show("queue", details.queue, { type: "virtualhost", name: details.virtualhost, parent: {broker: {type:"broker", name:""}}}); + } else if (details.type == "connection") { + controller.show("connection", details.connection, { type: "virtualhost", name: details.virtualhost, parent: {broker: {type:"broker", name:""}}}); + } else if (details.type == 'port') { + controller.show("port", details.port, { type: "virtualhost", name: details.virtualhost, parent: {broker: {type:"broker", name:""}}}); + } else if (details.type == 'authenticationprovider') { + controller.show("authenticationprovider", details.authenticationprovider, {broker: {type:"broker", name:""}}); + } + + + + }; + + TreeViewModel.prototype.update = function () { + var thisObj = this; + + xhr.get({url:this.query, sync: true, handleAs:"json"}) + .then(function (data) { + if (thisObj.model) { + thisObj.updateModel(data); + } + else { + thisObj.buildModel(data); + } + }); + + }; + + query('div[qpid-type="treeView"]').forEach(function(node, index, arr) { + var treeModel = new TreeViewModel("rest/structure"); + treeModel.update(); + var tree = new Tree({ model: treeModel }, node); + tree.on("dblclick", + function (object) { + if (object && !object._dummyChild) { + treeModel.relocate(object); + } + + }, true); + tree.startup(); + updater.add( treeModel ); + }); + + return TreeViewModel; + });
\ No newline at end of file diff --git a/java/broker-plugins/management-http/src/main/java/resources/management.html b/java/broker-plugins/management-http/src/main/java/resources/management.html new file mode 100644 index 0000000000..a8345a8503 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/management.html @@ -0,0 +1,92 @@ +<!DOCTYPE HTML> +<!-- + ~ 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. + --> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>Qpid Management</title> + <link rel="stylesheet" href="dojo/dojo/resources/dojo.css"> + <link rel="stylesheet" href="dojo/dijit/themes/claro/claro.css"> + <link rel="stylesheet" href="dojo/dojox/grid/resources/claroGrid.css"> + <link rel="stylesheet" href="dojo/dojox/grid/enhanced/resources/claro/EnhancedGrid.css"> + <link rel="stylesheet" href="dojo/dojox/grid/enhanced/resources/EnhancedGrid_rtl.css"> + <link rel="stylesheet" href="css/common.css" media="screen"> + <script> + function getContextPath() + { + var contextPath = "/"; + var documentURL = document.URL; + var managementPageStart = documentURL.lastIndexOf("/"); + var firstSlashPos = documentURL.indexOf("/", documentURL.indexOf("//") + 2); + if (managementPageStart > firstSlashPos) + { + contextPath = documentURL.substring(firstSlashPos, managementPageStart); + } + return contextPath; + } + + var dojoConfig = { + tlmSiblingOfDojo:false, + parseOnLoad:true, + async:true, + baseUrl: getContextPath(), + packages:[ + { name:"dojo", location:"dojo/dojo" }, + { name:"dijit", location:"dojo/dijit" }, + { name:"dojox", location:"dojo/dojox" }, + { name:"qpid", location:"js/qpid" } + ] + }; + + </script> + <script src="dojo/dojo/dojo.js"> + </script> + + <script> + require(["dijit/layout/BorderContainer", + "dijit/layout/TabContainer", + "dijit/layout/ContentPane", + "dijit/TitlePane", + "dojo/parser", + "qpid/management/treeView", + "qpid/management/controller", + "qpid/common/footer", + "qpid/authorization/sasl"]); + </script> + +</head> +<body class="claro"> + +<div id="pageLayout" data-dojo-type="dijit.layout.BorderContainer" data-dojo-props="design: 'headline', gutters: false"> + <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region:'top'"> + <div id="header" class="header"></div> + </div> + <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region:'top'"> + <div id="login"></div> + </div> + <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region:'leading', splitter: true"> + <div qpid-type="treeView" qpid-props="query: 'rest/structure'" ></div> + </div> + <div id="managedViews" data-dojo-type="dijit.layout.TabContainer" data-dojo-props="region:'center', tabPosition: 'top'"> + </div> + <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="region:'bottom'"> + <div qpid-type="footer"></div> + </div> +</div> + +</body> +</html>
\ No newline at end of file diff --git a/java/broker-plugins/management-http/src/main/java/resources/moveCopyMessages.html b/java/broker-plugins/management-http/src/main/java/resources/moveCopyMessages.html new file mode 100644 index 0000000000..f188c3001c --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/moveCopyMessages.html @@ -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. + --> + +<div class="dijitHidden"> + <div data-dojo-type="dijit.Dialog" style="width:600px;" data-dojo-props="title:'Move/Copy Messages'" id="moveMessages"> + <form id="formMoveMessages" method="post" dojoType="dijit.form.Form"> + <table cellpadding="0" cellspacing="2"> + <tr> + <td valign="top"><strong>Queue: </strong></td> + <td><div id="moveMessages.selectQueueDiv"></div></td> + </tr> + </table> + <br/> + + <!-- submit buttons --> + + <input type="button" value="Cancel" label="Cancel" dojoType="dijit.form.Button" class="moveMessageCancel"/> + <input type="submit" value="Move Messages" label="Move Messages" dojoType="dijit.form.Button" /> + + </form> + </div> +</div> diff --git a/java/broker-plugins/management-http/src/main/java/resources/showAuthProvider.html b/java/broker-plugins/management-http/src/main/java/resources/showAuthProvider.html new file mode 100644 index 0000000000..c5d4e48a75 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/showAuthProvider.html @@ -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. + - + --> +<div class="authorizationProvider"> + <span style="">Name:</span><span class="name" style="position:absolute; left:6em"></span> + <br/> + <span style="">Type:</span><span class="type" style="position:absolute; left:6em"></span> +</div>
\ No newline at end of file diff --git a/java/broker-plugins/management-http/src/main/java/resources/showBroker.html b/java/broker-plugins/management-http/src/main/java/resources/showBroker.html new file mode 100644 index 0000000000..a39e334c40 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/showBroker.html @@ -0,0 +1,25 @@ +<div class="broker"> + <span>Name:</span><span class="broker-name" style="position:absolute; left:6em"></span> + <br/> +<!-- <span>State:</span><span class="broker-state" style="position:absolute; left:6em"></span> + <br/> + <span>Durable:</span><span class="broker-durable" style="position:absolute; left:6em"></span> + <br/> + <span>Lifespan:</span><span class="broker-lifetimePolicy" style="position:absolute; left:6em" ></span> + <br/> --> + <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Virtual Hosts'"> + <div class="broker-virtualhosts"></div> + </div> + <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Ports'"> + <div class="broker-ports"></div> + </div> + <br/> + + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Log File', open: false"> + <div class="broker-logfile"></div> + </div> + <br/> +</div> + diff --git a/java/broker-plugins/management-http/src/main/java/resources/showConnection.html b/java/broker-plugins/management-http/src/main/java/resources/showConnection.html new file mode 100644 index 0000000000..84854daf47 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/showConnection.html @@ -0,0 +1,47 @@ +<!-- + - + - 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. + - + --> +<div class="connection"> + <span style="">Name:</span><span class="name" style="position:absolute; left:6em"></span> + <br/> + <span style="">State:</span><span class="state" style="position:absolute; left:6em"></span> + <span style="position:absolute; left:26em">Pre-fetched:</span> + <br/> + <span style="">Durable:</span><span class="durable" style="position:absolute; left:6em"></span> + <span style="position:absolute; left:26em">Inbound:</span> + <span class="msgInRate" style="position:absolute; right:9.5em"></span> + <span style="position:absolute; right: 5em; width: 4em"> msg/s</span> + <span class="bytesInRate" style="position:absolute; right: 3.3em"></span> + <span class="bytesInRateUnits" style="position:absolute; right: 0em; width: 3em"></span> + <br/> + <span style="">Lifespan:</span><span style="position:absolute; left:6em" class="lifetimePolicy"></span> + <span style="position:absolute; left:26em">Outbound:</span> + <span class="msgOutRate" style="position:absolute; right:9.5em"></span> + <span style="position:absolute; right: 5em; width: 4em"> msg/s</span> + <span class="bytesOutRate" style="position:absolute; right: 3.3em"></span> + <span class="bytesOutRateUnits" style="position:absolute; right: 0em; width: 3em"></span> + <br/> + <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Sessions'"> + <div class="sessions"></div> + </div> + <br/> + +</div> diff --git a/java/broker-plugins/management-http/src/main/java/resources/showExchange.html b/java/broker-plugins/management-http/src/main/java/resources/showExchange.html new file mode 100644 index 0000000000..f33b029026 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/showExchange.html @@ -0,0 +1,50 @@ +<!-- + - + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you under the Apache License, Version 2.0 (the + - "License"); you may not use this file except in compliance + - with the License. You may obtain a copy of the License at + - + - http://www.apache.org/licenses/LICENSE-2.0 + - + - Unless required by applicable law or agreed to in writing, + - software distributed under the License is distributed on an + - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + - KIND, either express or implied. See the License for the + - specific language governing permissions and limitations + - under the License. + - + --> +<div class="exchange"> + <span style="">Name:</span><span class="name" style="position:absolute; left:6em"></span> + <br/> + <span style="">State:</span><span class="state" style="position:absolute; left:6em"></span> + <br/> + <span style="">Durable:</span><span class="durable" style="position:absolute; left:6em"></span> + <span style="position:absolute; left:26em">Inbound:</span> + <span class="msgInRate" style="position:absolute; right:9.5em"></span> + <span style="position:absolute; right: 5em; width: 4em"> msg/s</span> + <span class="bytesInRate" style="position:absolute; right: 3.3em"></span> + <span class="bytesInRateUnits" style="position:absolute; right: 0em; width: 3em"></span> + <br/> + <span style="">Lifespan:</span><span style="position:absolute; left:6em" class="lifetimePolicy"></span> + <span style="position:absolute; left:26em">Dropped:</span> + <span class="msgDropRate" style="position:absolute; right:9.5em"></span> + <span style="position:absolute; right: 5em; width: 4em"> msg/s</span> + <span class="bytesDropRate" style="position:absolute; right: 3.3em"></span> + <span class="bytesDropRateUnits" style="position:absolute; right: 0em; width: 3em"></span> + <br/> + <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Bindings'"> + <div class="bindings"></div> + <button data-dojo-type="dijit.form.Button" class="addBindingButton">Add Binding</button> + <button data-dojo-type="dijit.form.Button" class="deleteBindingButton">Delete Binding</button> + </div> + <br/> + <div class="dijitDialogPaneActionBar"> + <button data-dojo-type="dijit.form.Button" class="deleteExchangeButton" type="button">Delete Exchange</button> + </div> +</div> diff --git a/java/broker-plugins/management-http/src/main/java/resources/showMessage.html b/java/broker-plugins/management-http/src/main/java/resources/showMessage.html new file mode 100644 index 0000000000..0dea508c60 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/showMessage.html @@ -0,0 +1,73 @@ +<!-- + ~ 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. + --> + +<div class="dijitHidden"> + <div data-dojo-type="dijit.Dialog" style="width:600px;" data-dojo-props="title:'View Message'" id="showMessage"> + + <table style="border: 0;"> + <tr style="margin-bottom: 4pt"> + <td style="width: 10em; vertical-align: top"><span style="font-weight: bold;">Message Number:</span></td> + <td><span class="message-id"></span></td> + </tr> + <tr style="margin-bottom: 4pt"> + <td style="width: 10em; vertical-align: top"><span style="font-weight: bold;">Message Id:</span></td> + <td><span class="message-messageId"></span></td> + </tr> + <tr style="margin-bottom: 4pt"> + <td style="width: 10em; vertical-align: top"><span style="font-weight: bold;">State:</span></td> + <td><span class="message-state"></span></td> + </tr> + <tr style="margin-bottom: 4pt"> + <td style="width: 10em; vertical-align: top"><span style="font-weight: bold;">Persistent:</span></td> + <td><span class="message-persistent boolean"></span></td> + </tr> + <tr style="margin-bottom: 4pt"> + <td style="width: 10em; vertical-align: top"><span style="font-weight: bold;">Priority:</span></td> + <td><span class="message-priority"></span></td> + </tr> + <tr style="margin-bottom: 4pt"> + <td style="width: 10em; vertical-align: top"><span style="font-weight: bold;">Arrival Time:</span> + </td><td><span class="message-arrivalTime datetime"></span></td> + </tr> + <tr style="margin-bottom: 4pt"> + <td style="width: 10em; vertical-align: top"><span style="font-weight: bold;">Expiration:</span></td> + <td><span class="message-expiration datetime"></span></td> + </tr> + <tr style="margin-bottom: 4pt"> + <td style="width: 10em; vertical-align: top"><span style="font-weight: bold;">MIME Type:</span></td> + <td><span class="message-mimeType"></span></td> + </tr> + <tr style="margin-bottom: 4pt"> + <td style="width: 10em; vertical-align: top"><span style="font-weight: bold;">User:</span></td> + <td><span class="message-userId"></span></td> + </tr> + <tr style="margin-bottom: 4pt"> + <td style="width: 10em; vertical-align: top"><span style="font-weight: bold;">Headers:</span></td> + <td><div class="message-headers map"></div></td> + </tr> + + <tr style="margin-bottom: 4pt"> + <td style="width: 10em; vertical-align: top"><span style="font-weight: bold;">Content:</span></td> + <td><div class="message-content"></div></td> + </tr> + </table> + <br/> + <input type="button" value="Close" label="Close" dojoType="dijit.form.Button" class="closeViewMessage"/> + + </div> +</div> + diff --git a/java/broker-plugins/management-http/src/main/java/resources/showQueue.html b/java/broker-plugins/management-http/src/main/java/resources/showQueue.html new file mode 100644 index 0000000000..929e1d1f23 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/showQueue.html @@ -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. + - + --> +<div class="queue"> + <span style="">Name:</span><span class="name" style="position:absolute; left:6em"></span> + <span style="position:absolute; left:26em">Size:</span> + <span class="queueDepthMessages" style="position:absolute; right:9.5em"></span> + <span style="position:absolute; right: 5em; width: 4em"> msgs</span> + <span class="queueDepthBytes" style="position:absolute; right: 3.3em">(</span> + <span class="queueDepthBytesUnits" style="position:absolute; right: 0em; width: 3em">)</span> + <br/> + <span style="">State:</span><span class="state" style="position:absolute; left:6em"></span> + <span style="position:absolute; left:26em">Pre-fetched:</span> + <span class="unacknowledgedMessages" style="position:absolute; right:9.5em"></span> + <span style="position:absolute; right: 5em; width: 4em"> msgs</span> + <span class="unacknowledgedBytes" style="position:absolute; right: 3.3em"></span> + <span class="unacknowledgedBytesUnits" style="position:absolute; right: 0em; width: 3em"></span> + <br/> + <span style="">Durable:</span><span class="durable" style="position:absolute; left:6em"></span> + <span style="position:absolute; left:26em">Inbound:</span> + <span class="msgInRate" style="position:absolute; right:9.5em"></span> + <span style="position:absolute; right: 5em; width: 4em"> msg/s</span> + <span class="bytesInRate" style="position:absolute; right: 3.3em"></span> + <span class="bytesInRateUnits" style="position:absolute; right: 0em; width: 3em"></span> + <br/> + <span style="">Lifespan:</span><span style="position:absolute; left:6em" class="lifetimePolicy"></span> + <span style="position:absolute; left:26em">Outbound:</span> + <span class="msgOutRate" style="position:absolute; right:9.5em"></span> + <span style="position:absolute; right: 5em; width: 4em"> msg/s</span> + <span class="bytesOutRate" style="position:absolute; right: 3.3em"></span> + <span class="bytesOutRateUnits" style="position:absolute; right: 0em; width: 3em"></span> + <br/> + <span style="">AlternateExchange:</span><span class="alternateExchange" style="position:absolute; left:10em"></span> + <span style="position:absolute; left:26em">Type:</span><span style="position:absolute; left:29em" class="type"></span> + <span style="position:absolute; right:1em" class="typeQualifier"></span> + <br/> + <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Bindings'"> + <div class="bindings"></div> + <button data-dojo-type="dijit.form.Button" class="addBindingButton" type="button">Add Binding</button> + </div> + <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Consumers'"> + <div class="consumers"></div> + </div> + <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Messages'"> + <div class="messages"></div> + <button data-dojo-type="dijit.form.Button" class="deleteMessagesButton" type="button">Delete Messages</button> + <button data-dojo-type="dijit.form.Button" class="moveMessagesButton" type="button">Move Messages</button> + <button data-dojo-type="dijit.form.Button" class="copyMessagesButton" type="button">Copy Messages</button> + </div> + <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Alerting Thresholds', open: false"> + <span style="">Queue Depth:</span> + <span class="alertThresholdQueueDepthMessages" + style="position:absolute; left:4em; width:8em; text-align:right"></span> + <span style="position:absolute; left:12.2em">msgs</span> + + <span style="position:absolute; left:21em">Queue Depth:</span> + <span class="alertThresholdQueueDepthBytes" + style="position:absolute; left:22em; width:8em; text-align:right"></span> + <span class="alertThresholdQueueDepthBytesUnits" style="position:absolute; left:30.2em"></span> + <br> + <span style="">Message Age:</span> + <span class="alertThresholdMessageAge" + style="position:absolute; left:4em; width:8em; text-align:right"></span> + <span class="alertThresholdMessageAgeUnits" style="position:absolute; left:12.2em"></span> + + <span style="position:absolute; left:21em">Message Size: </span> + <span class="alertThresholdMessageSize" + style="position:absolute; left:25em; width:5em; text-align:right"></span> + <span class="alertThresholdMessageSizeUnits" style="position:absolute; left:30.2em"></span> + <br/> + <br/> + <span style="">Alert frequency:</span> + <span class="alertRepeatGap" + style="position:absolute; left:4em; width:8em; text-align:right"></span> + <span class="alertRepeatGapUnits" style="position:absolute; left:12.2em"></span> + </div> + + <div class="dijitDialogPaneActionBar"> + <button data-dojo-type="dijit.form.Button" class="deleteQueueButton" type="button">Delete Queue</button> + </div> +</div> + diff --git a/java/broker-plugins/management-http/src/main/java/resources/showVirtualHost.html b/java/broker-plugins/management-http/src/main/java/resources/showVirtualHost.html new file mode 100644 index 0000000000..73c912e0d4 --- /dev/null +++ b/java/broker-plugins/management-http/src/main/java/resources/showVirtualHost.html @@ -0,0 +1,87 @@ +<!DOCTYPE HTML> +<!-- + - + - 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. + - + --> + +<div class="virtualhost"> + <span style="">Name:</span><span class="name" style="position:absolute; left:6em"></span> + <br/> + <span style="">State:</span><span class="state" style="position:absolute; left:6em"></span> + <br/> + <span style="">Durable:</span><span class="durable" style="position:absolute; left:6em"></span> + <span style="position:absolute; left:26em">Inbound:</span> + <span class="msgInRate" style="position:absolute; right:9.5em"></span> + <span style="position:absolute; right: 5em; width: 4em"> msg/s</span> + <span class="bytesInRate" style="position:absolute; right: 3.3em"></span> + <span class="bytesInRateUnits" style="position:absolute; right: 0em; width: 3em"></span> + <br/> + <span style="">Lifespan:</span><span style="position:absolute; left:6em" class="lifetimePolicy"></span> + <span style="position:absolute; left:26em">Outbound:</span> + <span class="msgOutRate" style="position:absolute; right:9.5em"></span> + <span style="position:absolute; right: 5em; width: 4em"> msg/s</span> + <span class="bytesOutRate" style="position:absolute; right: 3.3em"></span> + <span class="bytesOutRateUnits" style="position:absolute; right: 0em; width: 3em"></span> + <br/> + <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Exchanges'"> + <div class="exchanges"></div> + <button data-dojo-type="dijit.form.Button" class="addExchangeButton">Add Exchange</button> + <button data-dojo-type="dijit.form.Button" class="deleteExchangeButton">Delete Exchange</button> + </div> + <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Queues'"> + <div class="queues"></div> + <button data-dojo-type="dijit.form.Button" class="addQueueButton">Add Queue</button> + <button data-dojo-type="dijit.form.Button" class="deleteQueueButton">Delete Queue</button> + </div> + <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Connections'"> + <div class="connections"></div> + </div> + <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Alerting Thresholds', open: false"> + <span style="">Queue Depth:</span> + <span class="alertThresholdQueueDepthMessages" + style="position:absolute; left:4em; width:8em; text-align:right"></span> + <span style="position:absolute; left:12.2em">msgs</span> + + <span style="position:absolute; left:21em">Queue Depth:</span> + <span class="alertThresholdQueueDepthBytes" + style="position:absolute; left:22em; width:8em; text-align:right"></span> + <span class="alertThresholdQueueDepthBytesUnits" style="position:absolute; left:30.2em"></span> + <br> + <span style="">Message Age:</span> + <span class="alertThresholdMessageAge" + style="position:absolute; left:4em; width:8em; text-align:right"></span> + <span class="alertThresholdMessageAgeUnits" style="position:absolute; left:12.2em"></span> + + <span style="position:absolute; left:21em">Message Size: </span> + <span class="alertThresholdMessageSize" + style="position:absolute; left:25em; width:5em; text-align:right"></span> + <span class="alertThresholdMessageSizeUnits" style="position:absolute; left:30.2em"></span> + <br/> + <br/> + <span style="">Alert frequency:</span> + <span class="alertRepeatGap" + style="position:absolute; left:4em; width:8em; text-align:right"></span> + <span class="alertRepeatGapUnits" style="position:absolute; left:12.2em"></span> + </div> +</div> + diff --git a/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/Asserts.java b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/Asserts.java new file mode 100644 index 0000000000..2595007574 --- /dev/null +++ b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/Asserts.java @@ -0,0 +1,249 @@ +/* + * + * 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.management.plugin.servlet.rest; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; + +import javax.jms.JMSException; + +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.server.model.Binding; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Connection; +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.Queue; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.VirtualHost; + +public class Asserts +{ + public static final String STATISTICS_ATTRIBUTE = "statistics"; + + public static void assertVirtualHost(String virtualHostName, Map<String, Object> virtualHost) + { + assertNotNull("Virtualhost " + virtualHostName + " data are not found", virtualHost); + assertAttributesPresent(virtualHost, VirtualHost.AVAILABLE_ATTRIBUTES, VirtualHost.TIME_TO_LIVE, + VirtualHost.CREATED, VirtualHost.UPDATED, VirtualHost.SUPPORTED_QUEUE_TYPES, VirtualHost.STORE_CONFIGURATION); + + assertEquals("Unexpected value of attribute " + VirtualHost.NAME, virtualHostName, virtualHost.get(VirtualHost.NAME)); + assertNotNull("Unexpected value of attribute " + VirtualHost.ID, virtualHost.get(VirtualHost.ID)); + assertEquals("Unexpected value of attribute " + VirtualHost.STATE, State.ACTIVE.name(), + virtualHost.get(VirtualHost.STATE)); + assertEquals("Unexpected value of attribute " + VirtualHost.DURABLE, Boolean.TRUE, + virtualHost.get(VirtualHost.DURABLE)); + assertEquals("Unexpected value of attribute " + VirtualHost.LIFETIME_POLICY, LifetimePolicy.PERMANENT.name(), + virtualHost.get(VirtualHost.LIFETIME_POLICY)); + assertEquals("Unexpected value of attribute " + VirtualHost.DEAD_LETTER_QUEUE_ENABLED, Boolean.FALSE, + virtualHost.get(VirtualHost.DEAD_LETTER_QUEUE_ENABLED)); + + @SuppressWarnings("unchecked") + Collection<String> exchangeTypes = (Collection<String>) virtualHost.get(VirtualHost.SUPPORTED_EXCHANGE_TYPES); + assertEquals("Unexpected value of attribute " + VirtualHost.SUPPORTED_EXCHANGE_TYPES, + new HashSet<String>(Arrays.asList("headers", "topic", "direct", "fanout", "management")), + new HashSet<String>(exchangeTypes)); + + @SuppressWarnings("unchecked") + Map<String, Object> statistics = (Map<String, Object>) virtualHost.get(STATISTICS_ATTRIBUTE); + Asserts.assertAttributesPresent(statistics, VirtualHost.AVAILABLE_STATISTICS, VirtualHost.BYTES_RETAINED, + VirtualHost.LOCAL_TRANSACTION_BEGINS, VirtualHost.LOCAL_TRANSACTION_ROLLBACKS, + VirtualHost.MESSAGES_RETAINED, VirtualHost.STATE_CHANGED, VirtualHost.XA_TRANSACTION_BRANCH_ENDS, + VirtualHost.XA_TRANSACTION_BRANCH_STARTS, VirtualHost.XA_TRANSACTION_BRANCH_SUSPENDS); + + } + + public static void assertQueue(String queueName, String queueType, Map<String, Object> queueData) + { + assertQueue(queueName, queueType, queueData, null); + } + + public static void assertQueue(String queueName, String queueType, Map<String, Object> queueData, Map<String, Object> expectedAttributes) + { + assertNotNull("Queue " + queueName + " is not found!", queueData); + Asserts.assertAttributesPresent(queueData, Queue.AVAILABLE_ATTRIBUTES, Queue.CREATED, Queue.UPDATED, + Queue.DESCRIPTION, Queue.TIME_TO_LIVE, Queue.ALTERNATE_EXCHANGE, Queue.OWNER, Queue.NO_LOCAL, Queue.LVQ_KEY, + Queue.SORT_KEY, Queue.MESSAGE_GROUP_KEY, Queue.MESSAGE_GROUP_DEFAULT_GROUP, + Queue.MESSAGE_GROUP_SHARED_GROUPS, Queue.PRIORITIES); + + assertEquals("Unexpected value of queue attribute " + Queue.NAME, queueName, queueData.get(Queue.NAME)); + assertNotNull("Unexpected value of queue attribute " + Queue.ID, queueData.get(Queue.ID)); + assertEquals("Unexpected value of queue attribute " + Queue.STATE, State.ACTIVE.name(), queueData.get(Queue.STATE)); + assertEquals("Unexpected value of queue attribute " + Queue.LIFETIME_POLICY, LifetimePolicy.PERMANENT.name(), + queueData.get(Queue.LIFETIME_POLICY)); + assertEquals("Unexpected value of queue attribute " + Queue.TYPE, queueType, queueData.get(Queue.TYPE)); + if (expectedAttributes == null) + { + assertEquals("Unexpected value of queue attribute " + Queue.EXCLUSIVE, Boolean.FALSE, queueData.get(Queue.EXCLUSIVE)); + assertEquals("Unexpected value of queue attribute " + Queue.MAXIMUM_DELIVERY_ATTEMPTS, 0, + queueData.get(Queue.MAXIMUM_DELIVERY_ATTEMPTS)); + assertEquals("Unexpected value of queue attribute " + Queue.QUEUE_FLOW_CONTROL_SIZE_BYTES, 0, + queueData.get(Queue.QUEUE_FLOW_CONTROL_SIZE_BYTES)); + assertEquals("Unexpected value of queue attribute " + Queue.QUEUE_FLOW_RESUME_SIZE_BYTES, 0, + queueData.get(Queue.QUEUE_FLOW_RESUME_SIZE_BYTES)); + assertEquals("Unexpected value of queue attribute " + Queue.QUEUE_FLOW_STOPPED, Boolean.FALSE, + queueData.get(Queue.QUEUE_FLOW_STOPPED)); + } + else + { + for (Map.Entry<String, Object> attribute : expectedAttributes.entrySet()) + { + assertEquals("Unexpected value of " + queueName + " queue attribute " + attribute.getKey(), + attribute.getValue(), queueData.get(attribute.getKey())); + } + } + + assertNotNull("Unexpected value of queue attribute statistics", queueData.get(Asserts.STATISTICS_ATTRIBUTE)); + @SuppressWarnings("unchecked") + Map<String, Object> statistics = (Map<String, Object>) queueData.get(Asserts.STATISTICS_ATTRIBUTE); + Asserts.assertAttributesPresent(statistics, Queue.AVAILABLE_STATISTICS, Queue.DISCARDS_TTL_BYTES, + Queue.DISCARDS_TTL_MESSAGES, Queue.STATE_CHANGED); + } + + public static void assertAttributesPresent(Map<String, Object> data, String[] attributes) + { + for (String name : attributes) + { + assertNotNull("Attribute " + name + " is not present", data.get(name)); + } + } + + public static void assertAttributesPresent(Map<String, Object> data, Collection<String> attributes, + String... unsupportedAttributes) + { + for (String name : attributes) + { + boolean unsupported = false; + for (String unsupportedAttribute : unsupportedAttributes) + { + if (unsupportedAttribute.equals(name)) + { + unsupported = true; + break; + } + } + if (unsupported) + { + continue; + } + assertNotNull("Attribute " + name + " is not present", data.get(name)); + } + } + + public static void assertConnection(Map<String, Object> connectionData, AMQConnection connection) throws JMSException + { + assertNotNull("Unexpected connection data", connectionData); + assertAttributesPresent(connectionData, Connection.AVAILABLE_ATTRIBUTES, Connection.STATE, Connection.DURABLE, + Connection.LIFETIME_POLICY, Connection.TIME_TO_LIVE, Connection.CREATED, Connection.UPDATED, + Connection.INCOMING, Connection.REMOTE_PROCESS_NAME, Connection.REMOTE_PROCESS_PID, + Connection.LOCAL_ADDRESS, Connection.PROPERTIES); + + assertEquals("Unexpected value of connection attribute " + Connection.SESSION_COUNT_LIMIT, + (int) connection.getMaximumChannelCount(), connectionData.get(Connection.SESSION_COUNT_LIMIT)); + assertEquals("Unexpected value of connection attribute " + Connection.CLIENT_ID, "clientid", + connectionData.get(Connection.CLIENT_ID)); + assertEquals("Unexpected value of connection attribute " + Connection.PRINCIPAL, "guest", + connectionData.get(Connection.PRINCIPAL)); + + @SuppressWarnings("unchecked") + Map<String, Object> statistics = (Map<String, Object>) connectionData.get(STATISTICS_ATTRIBUTE); + assertAttributesPresent(statistics, Connection.AVAILABLE_STATISTICS, Connection.LOCAL_TRANSACTION_BEGINS, + Connection.LOCAL_TRANSACTION_ROLLBACKS, Connection.STATE_CHANGED, Connection.XA_TRANSACTION_BRANCH_ENDS, + Connection.XA_TRANSACTION_BRANCH_STARTS, Connection.XA_TRANSACTION_BRANCH_SUSPENDS); + assertEquals("Unexpected value of connection statistics attribute " + Connection.SESSION_COUNT, 1, + statistics.get(Connection.SESSION_COUNT)); + } + + public static void assertPortAttributes(Map<String, Object> port) + { + assertAttributesPresent(port, Port.AVAILABLE_ATTRIBUTES, Port.CREATED, Port.UPDATED); + + assertNotNull("Unexpected value of attribute " + Port.ID, port.get(Port.ID)); + assertEquals("Unexpected value of attribute " + Port.DURABLE, Boolean.FALSE, port.get(Port.DURABLE)); + assertEquals("Unexpected value of attribute " + Port.LIFETIME_POLICY, LifetimePolicy.PERMANENT.name(), + port.get(Broker.LIFETIME_POLICY)); + assertEquals("Unexpected value of attribute " + Port.STATE, State.ACTIVE.name(), port.get(Port.STATE)); + assertEquals("Unexpected value of attribute " + Port.TIME_TO_LIVE, 0, port.get(Port.TIME_TO_LIVE)); + assertNotNull("Unexpected value of attribute " + Port.BINDING_ADDRESS, port.get(Port.BINDING_ADDRESS)); + assertNotNull("Unexpected value of attribute " + Port.PROTOCOLS, port.get(Port.PROTOCOLS)); + assertNotNull("Unexpected value of attribute " + Port.NAME, port.get(Port.NAME)); + + @SuppressWarnings("unchecked") + Collection<String> transports = (Collection<String>) port.get(Port.TRANSPORTS); + assertEquals("Unexpected value of attribute " + Port.TRANSPORTS, new HashSet<String>(Arrays.asList("TCP")), + new HashSet<String>(transports)); + } + + public static void assertDurableExchange(String exchangeName, String type, Map<String, Object> exchangeData) + { + assertExchange(exchangeName, type, exchangeData); + + assertEquals("Unexpected value of exchange attribute " + Exchange.DURABLE, Boolean.TRUE, + exchangeData.get(Exchange.DURABLE)); + } + + public static void assertExchange(String exchangeName, String type, Map<String, Object> exchangeData) + { + assertNotNull("Exchange " + exchangeName + " is not found!", exchangeData); + assertAttributesPresent(exchangeData, Exchange.AVAILABLE_ATTRIBUTES, Exchange.CREATED, Exchange.UPDATED, + Exchange.ALTERNATE_EXCHANGE, Exchange.TIME_TO_LIVE); + + assertEquals("Unexpected value of exchange attribute " + Exchange.NAME, exchangeName, + exchangeData.get(Exchange.NAME)); + assertNotNull("Unexpected value of exchange attribute " + Exchange.ID, exchangeData.get(VirtualHost.ID)); + assertEquals("Unexpected value of exchange attribute " + Exchange.STATE, State.ACTIVE.name(), + exchangeData.get(Exchange.STATE)); + + assertEquals("Unexpected value of exchange attribute " + Exchange.LIFETIME_POLICY, LifetimePolicy.PERMANENT.name(), + exchangeData.get(Exchange.LIFETIME_POLICY)); + assertEquals("Unexpected value of exchange attribute " + Exchange.TYPE, type, exchangeData.get(Exchange.TYPE)); + assertNotNull("Unexpected value of exchange attribute statistics", exchangeData.get(STATISTICS_ATTRIBUTE)); + + @SuppressWarnings("unchecked") + Map<String, Object> statistics = (Map<String, Object>) exchangeData.get(STATISTICS_ATTRIBUTE); + assertAttributesPresent(statistics, Exchange.AVAILABLE_STATISTICS, Exchange.STATE_CHANGED, Exchange.PRODUCER_COUNT); + } + + public static void assertBinding(String bindingName, String queueName, String exchange, Map<String, Object> binding) + { + assertNotNull("Binding map should not be null", binding); + assertAttributesPresent(binding, Binding.AVAILABLE_ATTRIBUTES, Binding.STATE, Binding.TIME_TO_LIVE, + Binding.CREATED, Binding.UPDATED); + + assertEquals("Unexpected binding attribute " + Binding.NAME, bindingName, binding.get(Binding.NAME)); + assertEquals("Unexpected binding attribute " + Binding.QUEUE, queueName, binding.get(Binding.QUEUE)); + assertEquals("Unexpected binding attribute " + Binding.EXCHANGE, exchange, binding.get(Binding.EXCHANGE)); + assertEquals("Unexpected binding attribute " + Binding.LIFETIME_POLICY, LifetimePolicy.PERMANENT.name(), + binding.get(Binding.LIFETIME_POLICY)); + } + + public static void assertBinding(String queueName, String exchange, Map<String, Object> binding) + { + assertBinding(queueName, queueName, exchange, binding); + } + +} diff --git a/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/AuthenticationProviderRestTest.java b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/AuthenticationProviderRestTest.java new file mode 100644 index 0000000000..37bc2733b0 --- /dev/null +++ b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/AuthenticationProviderRestTest.java @@ -0,0 +1,53 @@ +package org.apache.qpid.server.management.plugin.servlet.rest; + +import java.util.List; +import java.util.Map; + +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.User; + +public class AuthenticationProviderRestTest extends QpidRestTestCase +{ + + public void testGet() throws Exception + { + List<Map<String, Object>> providerDetails = getJsonAsList("/rest/authenticationprovider"); + assertNotNull("Providers details cannot be null", providerDetails); + assertEquals("Unexpected number of providers", 1, providerDetails.size()); + for (Map<String, Object> provider : providerDetails) + { + assertProvider("PrincipalDatabaseAuthenticationManager", provider); + Map<String, Object> data = getJsonAsSingletonList("/rest/authenticationprovider/" + + provider.get(AuthenticationProvider.NAME)); + assertNotNull("Cannot load data for " + provider.get(AuthenticationProvider.NAME), data); + assertProvider("PrincipalDatabaseAuthenticationManager", data); + } + } + + private void assertProvider(String type, Map<String, Object> provider) + { + Asserts.assertAttributesPresent(provider, AuthenticationProvider.AVAILABLE_ATTRIBUTES, + AuthenticationProvider.CREATED, AuthenticationProvider.UPDATED, AuthenticationProvider.DESCRIPTION, + AuthenticationProvider.TIME_TO_LIVE); + assertEquals("Unexpected value of provider attribute " + AuthenticationProvider.STATE, State.ACTIVE.name(), + provider.get(AuthenticationProvider.STATE)); + assertEquals("Unexpected value of provider attribute " + AuthenticationProvider.LIFETIME_POLICY, + LifetimePolicy.PERMANENT.name(), provider.get(AuthenticationProvider.LIFETIME_POLICY)); + assertEquals("Unexpected value of provider attribute " + AuthenticationProvider.DURABLE, Boolean.TRUE, + provider.get(AuthenticationProvider.DURABLE)); + assertEquals("Unexpected value of provider attribute " + AuthenticationProvider.TYPE, type, + provider.get(AuthenticationProvider.TYPE)); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> users = (List<Map<String, Object>>) provider.get("users"); + assertNotNull("Users are not found", users); + assertTrue("Unexpected number of users", users.size() > 1); + for (Map<String, Object> user : users) + { + assertNotNull("Attribute " + User.ID, user.get(User.ID)); + assertNotNull("Attribute " + User.NAME, user.get(User.NAME)); + } + } +} diff --git a/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/BindingRestTest.java b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/BindingRestTest.java new file mode 100644 index 0000000000..527eb16927 --- /dev/null +++ b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/BindingRestTest.java @@ -0,0 +1,109 @@ +package org.apache.qpid.server.management.plugin.servlet.rest; + +import java.net.HttpURLConnection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.qpid.server.model.Binding; + +public class BindingRestTest extends QpidRestTestCase +{ + + public void testGetAllBindings() throws Exception + { + List<Map<String, Object>> bindings = getJsonAsList("/rest/binding"); + assertNotNull("Bindings cannot be null", bindings); + assertTrue("Unexpected number of bindings", bindings.size() >= EXPECTED_HOSTS.length * EXPECTED_QUEUES.length); + for (Map<String, Object> binding : bindings) + { + Asserts.assertBinding((String) binding.get(Binding.NAME), (String) binding.get(Binding.EXCHANGE), binding); + } + } + + public void testGetVirtualHostBindings() throws Exception + { + List<Map<String, Object>> bindings = getJsonAsList("/rest/binding/test"); + assertNotNull("Bindings cannot be null", bindings); + assertEquals("Unexpected number of bindings", EXPECTED_QUEUES.length * 2, bindings.size()); + for (String queueName : EXPECTED_QUEUES) + { + Map<String, Object> searchAttributes = new HashMap<String, Object>(); + searchAttributes.put(Binding.NAME, queueName); + searchAttributes.put(Binding.EXCHANGE, "amq.direct"); + + Map<String, Object> binding = find(searchAttributes, bindings); + Asserts.assertBinding(queueName, "amq.direct", binding); + + searchAttributes.put(Binding.EXCHANGE, "<<default>>"); + + binding = find(searchAttributes, bindings); + Asserts.assertBinding(queueName, "<<default>>", binding); + } + } + + public void testGetVirtualHostExchangeBindings() throws Exception + { + List<Map<String, Object>> bindings = getJsonAsList("/rest/binding/test/amq.direct"); + assertNotNull("Bindings cannot be null", bindings); + assertEquals("Unexpected number of bindings", EXPECTED_QUEUES.length, bindings.size()); + for (String queueName : EXPECTED_QUEUES) + { + Map<String, Object> binding = find(Binding.NAME, queueName, bindings); + Asserts.assertBinding(queueName, "amq.direct", binding); + } + } + + public void testGetVirtualHostExchangeQueueBindings() throws Exception + { + List<Map<String, Object>> bindings = getJsonAsList("/rest/binding/test/amq.direct/queue"); + assertNotNull("Bindings cannot be null", bindings); + assertEquals("Unexpected number of bindings", 1, bindings.size()); + Asserts.assertBinding("queue", "amq.direct", bindings.get(0)); + } + + + public void testDeleteBinding() throws Exception + { + List<Map<String, Object>> bindings = getJsonAsList("/rest/binding/test/amq.direct/queue/queue"); + assertEquals("Unexpected number of bindings", 1, bindings.size()); + Asserts.assertBinding("queue", "amq.direct", bindings.get(0)); + + HttpURLConnection connection = openManagementConection("/rest/binding/test/amq.direct/queue/queue", "DELETE"); + connection.connect(); + assertEquals("Unexpected response code", 200, connection.getResponseCode()); + + bindings = getJsonAsList("/rest/binding/test/amq.direct/queue/queue"); + assertEquals("Binding should be deleted", 0, bindings.size()); + } + + public void testDeleteBindingById() throws Exception + { + Map<String, Object> binding = getJsonAsSingletonList("/rest/binding/test/amq.direct/queue"); + HttpURLConnection connection = openManagementConection("/rest/binding/test/amq.direct?id=" + binding.get(Binding.ID), "DELETE"); + connection.connect(); + assertEquals("Unexpected response code", 200, connection.getResponseCode()); + List<Map<String, Object>> bindings = getJsonAsList("/rest/binding/test/amq.direct/queue"); + assertEquals("Binding should be deleted", 0, bindings.size()); + } + + public void testCreateBinding() throws Exception + { + String bindingName = getTestName(); + Map<String, Object> bindingData = new HashMap<String, Object>(); + bindingData.put(Binding.NAME, bindingName); + bindingData.put(Binding.QUEUE, "queue"); + bindingData.put(Binding.EXCHANGE, "amq.direct"); + + HttpURLConnection connection = openManagementConection("/rest/binding/test/amq.direct/queue/" + bindingName, "PUT"); + connection.connect(); + writeJsonRequest(connection, bindingData); + int responseCode = connection.getResponseCode(); + connection.disconnect(); + assertEquals("Unexpected response code", 201, responseCode); + Map<String, Object> binding = getJsonAsSingletonList("/rest/binding/test/amq.direct/queue/" + bindingName); + + Asserts.assertBinding(bindingName, "queue", "amq.direct", binding); + } + +} diff --git a/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/BrokerRestHttpsTest.java b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/BrokerRestHttpsTest.java new file mode 100644 index 0000000000..4bbe9155cd --- /dev/null +++ b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/BrokerRestHttpsTest.java @@ -0,0 +1,86 @@ +/* + * + * 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.management.plugin.servlet.rest; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Map; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSocketFactory; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.model.Broker; + +public class BrokerRestHttpsTest extends QpidRestTestCase +{ + private static final String TRUSTSTORE = "test-profiles/test_resources/ssl/java_client_truststore.jks"; + private static final String TRUSTSTORE_PASSWORD = "password"; + + @Override + public void setUp() throws Exception + { + setSystemProperty("javax.net.debug", "ssl"); + super.setUp(); + setSystemProperty("javax.net.ssl.trustStore", TRUSTSTORE); + setSystemProperty("javax.net.ssl.trustStorePassword", TRUSTSTORE_PASSWORD); + } + + @Override + protected void customizeConfiguration() throws ConfigurationException, IOException + { + setConfigurationProperty("management.enabled", "true"); + setConfigurationProperty("management.http.enabled", "false"); + setConfigurationProperty("management.https.enabled", "true"); + setConfigurationProperty("management.https.port", Integer.toString(getHttpPort())); + } + + @Override + protected String getHostName() + { + return "localhost"; + } + + @Override + protected String getProtocol() + { + return "https"; + } + + @Override + protected HttpURLConnection openManagementConection(String path) throws IOException + { + URL url = getManagementURL(path); + HttpURLConnection httpCon = (HttpURLConnection) url.openConnection(); + ((HttpsURLConnection) httpCon).setSSLSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault()); + httpCon.setDoOutput(true); + return httpCon; + } + + public void testGetWithHttps() throws Exception + { + Map<String, Object> brokerDetails = getJsonAsSingletonList("/rest/broker"); + + Asserts.assertAttributesPresent(brokerDetails, Broker.AVAILABLE_ATTRIBUTES, Broker.BYTES_RETAINED, + Broker.PROCESS_PID, Broker.SUPPORTED_STORE_TYPES, Broker.CREATED, Broker.TIME_TO_LIVE, Broker.UPDATED); + } +} diff --git a/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/BrokerRestTest.java b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/BrokerRestTest.java new file mode 100644 index 0000000000..f2970e2ba9 --- /dev/null +++ b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/BrokerRestTest.java @@ -0,0 +1,118 @@ +/* + * + * 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.management.plugin.servlet.rest; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import org.apache.qpid.common.QpidProperties; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.VirtualHost; + +public class BrokerRestTest extends QpidRestTestCase +{ + + private static final String BROKER_AUTHENTICATIONPROVIDERS_ATTRIBUTE = "authenticationproviders"; + private static final String BROKER_PORTS_ATTRIBUTE = "ports"; + private static final String BROKER_VIRTUALHOSTS_ATTRIBUTE = "virtualhosts"; + private static final String BROKER_STATISTICS_ATTRIBUTE = "statistics"; + + public void testGet() throws Exception + { + Map<String, Object> brokerDetails = getJsonAsSingletonList("/rest/broker"); + + assertBrokerAttributes(brokerDetails); + + @SuppressWarnings("unchecked") + Map<String, Object> statistics = (Map<String, Object>) brokerDetails.get(BROKER_STATISTICS_ATTRIBUTE); + Asserts.assertAttributesPresent(statistics, new String[]{ "bytesIn", "messagesOut", "bytesOut", "messagesIn" }); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> virtualhosts = (List<Map<String, Object>>) brokerDetails.get(BROKER_VIRTUALHOSTS_ATTRIBUTE); + assertEquals("Unexpected number of virtual hosts", 3, virtualhosts.size()); + + Asserts.assertVirtualHost("development", find(VirtualHost.NAME, "development", virtualhosts)); + Asserts.assertVirtualHost("localhost", find(VirtualHost.NAME, "localhost", virtualhosts)); + Asserts.assertVirtualHost("test", find(VirtualHost.NAME, "test", virtualhosts)); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> ports = (List<Map<String, Object>>) brokerDetails.get(BROKER_PORTS_ATTRIBUTE); + assertEquals("Unexpected number of ports", 2, ports.size()); + + for (Map<String, Object> port : ports) + { + Asserts.assertPortAttributes(port); + } + + String bindingAddress = (String)ports.get(0).get(Port.BINDING_ADDRESS); + + Map<String, Object> amqpPort = find(Port.NAME, bindingAddress + ":" + getPort(), ports); + Map<String, Object> httpPort = find(Port.NAME, bindingAddress + ":" + getHttpPort(), ports); + + assertNotNull("Cannot find AMQP port", amqpPort); + assertNotNull("Cannot find HTTP port", httpPort); + + @SuppressWarnings("unchecked") + Collection<String> port1Protocols = (Collection<String>) amqpPort.get(Port.PROTOCOLS); + assertFalse("AMQP protocol list cannot contain HTTP", port1Protocols.contains("HTTP")); + + @SuppressWarnings("unchecked") + Collection<String> port2Protocols = (Collection<String>) httpPort.get(Port.PROTOCOLS); + assertEquals("Unexpected value of attribute " + Port.PROTOCOLS, new HashSet<String>(Arrays.asList("HTTP")), + new HashSet<String>(port2Protocols)); + } + + protected void assertBrokerAttributes(Map<String, Object> brokerDetails) + { + Asserts.assertAttributesPresent(brokerDetails, Broker.AVAILABLE_ATTRIBUTES, + Broker.BYTES_RETAINED, Broker.PROCESS_PID, Broker.SUPPORTED_STORE_TYPES, + Broker.CREATED, Broker.TIME_TO_LIVE, Broker.UPDATED); + + assertEquals("Unexpected value of attribute " + Broker.BUILD_VERSION, QpidProperties.getBuildVersion(), + brokerDetails.get(Broker.BUILD_VERSION)); + assertEquals("Unexpected value of attribute " + Broker.OPERATING_SYSTEM, System.getProperty("os.name") + " " + + System.getProperty("os.version") + " " + System.getProperty("os.arch"), + brokerDetails.get(Broker.OPERATING_SYSTEM)); + assertEquals( + "Unexpected value of attribute " + Broker.PLATFORM, + System.getProperty("java.vendor") + " " + + System.getProperty("java.runtime.version", System.getProperty("java.version")), + brokerDetails.get(Broker.PLATFORM)); + assertEquals("Unexpected value of attribute " + Broker.DURABLE, Boolean.TRUE, brokerDetails.get(Broker.DURABLE)); + assertEquals("Unexpected value of attribute " + Broker.LIFETIME_POLICY, LifetimePolicy.PERMANENT.name(), + brokerDetails.get(Broker.LIFETIME_POLICY)); + assertEquals("Unexpected value of attribute " + Broker.NAME, "Broker", brokerDetails.get(Broker.NAME)); + assertEquals("Unexpected value of attribute " + Broker.STATE, State.ACTIVE.name(), brokerDetails.get(Broker.STATE)); + + assertNotNull("Unexpected value of attribute " + Broker.ID, brokerDetails.get(Broker.ID)); + assertNotNull("Unexpected value of attribute statistics", brokerDetails.get(BROKER_STATISTICS_ATTRIBUTE)); + assertNotNull("Unexpected value of attribute virtualhosts", brokerDetails.get(BROKER_VIRTUALHOSTS_ATTRIBUTE)); + assertNotNull("Unexpected value of attribute ports", brokerDetails.get(BROKER_PORTS_ATTRIBUTE)); + assertNotNull("Unexpected value of attribute authenticationproviders", brokerDetails.get(BROKER_AUTHENTICATIONPROVIDERS_ATTRIBUTE)); + } + +} diff --git a/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/ConnectionRestTest.java b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/ConnectionRestTest.java new file mode 100644 index 0000000000..3661b94a7c --- /dev/null +++ b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/ConnectionRestTest.java @@ -0,0 +1,213 @@ +/* + * + * 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.management.plugin.servlet.rest; + +import java.io.IOException; +import java.net.URLDecoder; +import java.util.List; +import java.util.Map; + +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; + +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.client.AMQSession; +import org.apache.qpid.server.model.Connection; +import org.apache.qpid.server.model.Session; + +public class ConnectionRestTest extends QpidRestTestCase +{ + /** + * Message number to publish into queue + */ + private static final int MESSAGE_NUMBER = 5; + private static final int MESSAGE_SIZE = 6; + + private static final String SESSIONS_ATTRIBUTE = "sessions"; + + private javax.jms.Connection _connection; + private javax.jms.Session _session; + + public void setUp() throws Exception + { + super.setUp(); + + _connection = getConnection(); + _session = _connection.createSession(true, javax.jms.Session.SESSION_TRANSACTED); + String queueName = getTestQueueName(); + Destination queue = _session.createQueue(queueName); + MessageConsumer consumer = _session.createConsumer(queue); + MessageProducer producer = _session.createProducer(queue); + _connection.start(); + + // send messages + for (int i = 0; i < MESSAGE_NUMBER; i++) + { + producer.send(_session.createTextMessage("Test-" + i)); + } + _session.commit(); + + Message m = consumer.receive(1000l); + assertNotNull("Message was not received", m); + _session.commit(); + + // receive the rest of messages for rollback + for (int i = 0; i < MESSAGE_NUMBER - 1; i++) + { + m = consumer.receive(1000l); + assertNotNull("Message was not received", m); + } + _session.rollback(); + + // receive them again + for (int i = 0; i < MESSAGE_NUMBER - 1; i++) + { + m = consumer.receive(1000l); + assertNotNull("Message was not received", m); + } + } + + public void testGetAllConnections() throws Exception + { + List<Map<String, Object>> connections = getJsonAsList("/rest/connection"); + assertEquals("Unexpected number of connections", 1, connections.size()); + Asserts.assertConnection(connections.get(0), (AMQConnection) _connection); + } + + public void testGetVirtualHostConnections() throws Exception + { + List<Map<String, Object>> connections = getJsonAsList("/rest/connection/test"); + assertEquals("Unexpected number of connections", 1, connections.size()); + Asserts.assertConnection(connections.get(0), (AMQConnection) _connection); + } + + public void testGetConnectionByName() throws Exception + { + // get connection name + String connectionName = getConnectionName(); + + Map<String, Object> connectionDetails = getJsonAsSingletonList("/rest/connection/test/" + + URLDecoder.decode(connectionName, "UTF-8")); + assertConnection(connectionDetails); + } + + public void testGetAllSessions() throws Exception + { + List<Map<String, Object>> sessions = getJsonAsList("/rest/session"); + assertEquals("Unexpected number of sessions", 1, sessions.size()); + assertSession(sessions.get(0), (AMQSession<?, ?>) _session); + } + + public void testGetVirtualHostSessions() throws Exception + { + List<Map<String, Object>> sessions = getJsonAsList("/rest/session/test"); + assertEquals("Unexpected number of sessions", 1, sessions.size()); + assertSession(sessions.get(0), (AMQSession<?, ?>) _session); + } + + public void testGetConnectionSessions() throws Exception + { + // get connection name + String connectionName = getConnectionName(); + + List<Map<String, Object>> sessions = getJsonAsList("/rest/session/test/" + + URLDecoder.decode(connectionName, "UTF-8")); + assertEquals("Unexpected number of sessions", 1, sessions.size()); + assertSession(sessions.get(0), (AMQSession<?, ?>) _session); + } + + public void testGetSessionByName() throws Exception + { + // get connection name + String connectionName = getConnectionName(); + + List<Map<String, Object>> sessions = getJsonAsList("/rest/session/test/" + + URLDecoder.decode(connectionName, "UTF-8") + "/" + ((AMQSession<?, ?>) _session).getChannelId()); + assertEquals("Unexpected number of sessions", 1, sessions.size()); + assertSession(sessions.get(0), (AMQSession<?, ?>) _session); + } + + private void assertConnection(Map<String, Object> connectionDetails) throws JMSException + { + Asserts.assertConnection(connectionDetails, (AMQConnection) _connection); + + @SuppressWarnings("unchecked") + Map<String, Object> statistics = (Map<String, Object>) connectionDetails.get(Asserts.STATISTICS_ATTRIBUTE); + assertEquals("Unexpected value of connection statistics attribute " + Connection.BYTES_IN, MESSAGE_NUMBER + * MESSAGE_SIZE, statistics.get(Connection.BYTES_IN)); + assertEquals("Unexpected value of connection statistics attribute " + Connection.BYTES_OUT, MESSAGE_SIZE + + ((MESSAGE_NUMBER - 1) * MESSAGE_SIZE) * 2, statistics.get(Connection.BYTES_OUT)); + assertEquals("Unexpected value of connection statistics attribute " + Connection.MESSAGES_IN, MESSAGE_NUMBER, + statistics.get(Connection.MESSAGES_IN)); + assertEquals("Unexpected value of connection statistics attribute " + Connection.MESSAGES_OUT, + MESSAGE_NUMBER * 2 - 1, statistics.get(Connection.MESSAGES_OUT)); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> sessions = (List<Map<String, Object>>) connectionDetails.get(SESSIONS_ATTRIBUTE); + assertNotNull("Sessions cannot be found", sessions); + assertEquals("Unexpected number of sessions", 1, sessions.size()); + assertSession(sessions.get(0), (AMQSession<?, ?>) _session); + } + + private void assertSession(Map<String, Object> sessionData, AMQSession<?, ?> session) + { + assertNotNull("Session map cannot be null", sessionData); + Asserts.assertAttributesPresent(sessionData, Session.AVAILABLE_ATTRIBUTES, Session.STATE, Session.DURABLE, + Session.LIFETIME_POLICY, Session.TIME_TO_LIVE, Session.CREATED, Session.UPDATED); + assertEquals("Unexpecte value of attribute " + Session.NAME, session.getChannelId() + "", + sessionData.get(Session.NAME)); + assertEquals("Unexpecte value of attribute " + Session.PRODUCER_FLOW_BLOCKED, Boolean.FALSE, + sessionData.get(Session.PRODUCER_FLOW_BLOCKED)); + assertEquals("Unexpecte value of attribute " + Session.CHANNEL_ID, session.getChannelId(), + sessionData.get(Session.CHANNEL_ID)); + + @SuppressWarnings("unchecked") + Map<String, Object> statistics = (Map<String, Object>) sessionData.get(Asserts.STATISTICS_ATTRIBUTE); + Asserts.assertAttributesPresent(statistics, Session.AVAILABLE_STATISTICS, Session.BYTES_IN, Session.BYTES_OUT, + Session.STATE_CHANGED, Session.UNACKNOWLEDGED_BYTES, Session.LOCAL_TRANSACTION_OPEN, + Session.XA_TRANSACTION_BRANCH_ENDS, Session.XA_TRANSACTION_BRANCH_STARTS, + Session.XA_TRANSACTION_BRANCH_SUSPENDS); + + assertEquals("Unexpecte value of statistic attribute " + Session.UNACKNOWLEDGED_MESSAGES, MESSAGE_NUMBER - 1, + statistics.get(Session.UNACKNOWLEDGED_MESSAGES)); + assertEquals("Unexpecte value of statistic attribute " + Session.LOCAL_TRANSACTION_BEGINS, 4, + statistics.get(Session.LOCAL_TRANSACTION_BEGINS)); + assertEquals("Unexpecte value of statistic attribute " + Session.LOCAL_TRANSACTION_ROLLBACKS, 1, + statistics.get(Session.LOCAL_TRANSACTION_ROLLBACKS)); + assertEquals("Unexpecte value of statistic attribute " + Session.CONSUMER_COUNT, 1, + statistics.get(Session.CONSUMER_COUNT)); + } + + private String getConnectionName() throws IOException + { + Map<String, Object> hostDetails = getJsonAsSingletonList("/rest/virtualhost/test"); + @SuppressWarnings("unchecked") + List<Map<String, Object>> connections = (List<Map<String, Object>>) hostDetails + .get(VirtualHostRestTest.VIRTUALHOST_CONNECTIONS_ATTRIBUTE); + assertEquals("Unexpected number of connections", 1, connections.size()); + Map<String, Object> connection = connections.get(0); + String connectionName = (String) connection.get(Connection.NAME); + return connectionName; + } +} diff --git a/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/ExchangeRestTest.java b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/ExchangeRestTest.java new file mode 100644 index 0000000000..59936427f9 --- /dev/null +++ b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/ExchangeRestTest.java @@ -0,0 +1,67 @@ +package org.apache.qpid.server.management.plugin.servlet.rest; + +import java.net.URLDecoder; +import java.util.List; +import java.util.Map; + +import org.apache.qpid.server.model.Binding; +import org.apache.qpid.server.model.Exchange; + +public class ExchangeRestTest extends QpidRestTestCase +{ + public void testGet() throws Exception + { + List<Map<String, Object>> exchanges = getJsonAsList("/rest/exchange"); + assertNotNull("Exchanges cannot be null", exchanges); + assertTrue("Unexpected number of exchanges", exchanges.size() >= EXPECTED_HOSTS.length * EXPECTED_EXCHANGES.length); + for (Map<String, Object> exchange : exchanges) + { + Asserts.assertExchange((String) exchange.get(Exchange.NAME), (String) exchange.get(Exchange.TYPE), exchange); + } + } + + public void testGetHostExchanges() throws Exception + { + List<Map<String, Object>> exchanges = getJsonAsList("/rest/exchange/test"); + assertNotNull("Users cannot be null", exchanges); + assertEquals("Unexpected number of exchanges", 6, EXPECTED_EXCHANGES.length); + for (String exchangeName : EXPECTED_EXCHANGES) + { + Map<String, Object> exchange = find(Exchange.NAME, exchangeName, exchanges); + assertExchange(exchangeName, exchange); + } + } + + public void testGetHostExchangeByName() throws Exception + { + for (String exchangeName : EXPECTED_EXCHANGES) + { + Map<String, Object> exchange = getJsonAsSingletonList("/rest/exchange/test/" + + URLDecoder.decode(exchangeName, "UTF-8")); + assertExchange(exchangeName, exchange); + } + } + + private void assertExchange(String exchangeName, Map<String, Object> exchange) + { + assertNotNull("Exchange with name " + exchangeName + " is not found", exchange); + String type = (String) exchange.get(Exchange.TYPE); + Asserts.assertExchange(exchangeName, type, exchange); + if ("direct".equals(type)) + { + assertBindings(exchange); + } + } + + private void assertBindings(Map<String, Object> exchange) + { + @SuppressWarnings("unchecked") + List<Map<String, Object>> bindings = (List<Map<String, Object>>) exchange.get("bindings"); + for (String queueName : EXPECTED_QUEUES) + { + Map<String, Object> binding = find(Binding.NAME, queueName, bindings); + Asserts.assertBinding(queueName, (String) exchange.get(Exchange.NAME), binding); + } + } + +} diff --git a/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/LogRecordsRestTest.java b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/LogRecordsRestTest.java new file mode 100644 index 0000000000..c64fd6e1da --- /dev/null +++ b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/LogRecordsRestTest.java @@ -0,0 +1,42 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.management.plugin.servlet.rest; + +import java.util.List; +import java.util.Map; + +public class LogRecordsRestTest extends QpidRestTestCase +{ + public void testGet() throws Exception + { + List<Map<String, Object>> logs = getJsonAsList("/rest/logrecords"); + assertNotNull("Logs data cannot be null", logs); + assertTrue("Logs are not found", logs.size() > 0); + Map<String, Object> record = find("message", "[Broker] BRK-1004 : Qpid Broker Ready", logs); + + assertNotNull("BRK-1004 message is not found", record); + assertNotNull("Message id cannot be null", record.get("id")); + assertNotNull("Message timestamp cannot be null", record.get("timestamp")); + assertEquals("Unexpected log level", "INFO", record.get("level")); + assertEquals("Unexpected thread", "main", record.get("thread")); + assertEquals("Unexpected logger", "qpid.message.broker.ready", record.get("logger")); + } +} diff --git a/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/MessagesRestTest.java b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/MessagesRestTest.java new file mode 100644 index 0000000000..492df43957 --- /dev/null +++ b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/MessagesRestTest.java @@ -0,0 +1,354 @@ +/* + * + * 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.management.plugin.servlet.rest; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import javax.jms.BytesMessage; +import javax.jms.Connection; +import javax.jms.DeliveryMode; +import javax.jms.Destination; +import javax.jms.Message; +import javax.jms.MessageProducer; +import javax.jms.Session; + +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.map.JsonMappingException; + +public class MessagesRestTest extends QpidRestTestCase +{ + + /** + * Message number to publish into queue + */ + private static final int MESSAGE_NUMBER = 12; + + private Connection _connection; + private Session _session; + private MessageProducer _producer; + private long _startTime; + private long _ttl; + + public void setUp() throws Exception + { + super.setUp(); + _startTime = System.currentTimeMillis(); + _connection = getConnection(); + _session = _connection.createSession(true, Session.SESSION_TRANSACTED); + String queueName = getTestQueueName(); + Destination queue = _session.createQueue(queueName); + _session.createConsumer(queue); + _producer = _session.createProducer(queue); + + _ttl = TimeUnit.DAYS.toMillis(1); + for (int i = 0; i < MESSAGE_NUMBER; i++) + { + Message m = _session.createTextMessage("Test-" + i); + m.setIntProperty("index", i); + if (i % 2 == 0) + { + _producer.send(m); + } + else + { + _producer.send(m, DeliveryMode.NON_PERSISTENT, 5, _ttl); + } + } + _session.commit(); + } + + public void testGet() throws Exception + { + String queueName = getTestQueueName(); + List<Map<String, Object>> messages = getJsonAsList("/rest/message/test/" + queueName); + assertNotNull("Messages are not found", messages); + assertEquals("Unexpected number of messages", MESSAGE_NUMBER, messages.size()); + int position = 0; + for (Map<String, Object> message : messages) + { + assertMessage(position, message); + position++; + } + } + + public void testGetMessageContent() throws Exception + { + String queueName = getTestQueueName(); + + // add bytes message + BytesMessage byteMessage = _session.createBytesMessage(); + byte[] messageBytes = "Test".getBytes(); + byteMessage.writeBytes(messageBytes); + byteMessage.setStringProperty("test", "value"); + _producer.send(byteMessage); + _session.commit(); + + // get message IDs + List<Long> ids = getMesssageIds(queueName); + + Map<String, Object> message = getJsonAsMap("/rest/message/test/" + queueName + "/" + ids.get(0)); + assertMessageAttributes(message); + assertMessageAttributeValues(message, true); + + @SuppressWarnings("unchecked") + Map<String, Object> headers = (Map<String, Object>) message.get("headers"); + assertNotNull("Message headers are not found", headers); + assertEquals("Unexpected message header", 0, headers.get("index")); + + Long lastMessageId = ids.get(ids.size() - 1); + message = getJsonAsMap("/rest/message/test/" + queueName + "/" + lastMessageId); + assertMessageAttributes(message); + assertEquals("Unexpected message attribute mimeType", "application/octet-stream", message.get("mimeType")); + assertEquals("Unexpected message attribute size", 4, message.get("size")); + + @SuppressWarnings("unchecked") + Map<String, Object> bytesMessageHeader = (Map<String, Object>) message.get("headers"); + assertNotNull("Message headers are not found", bytesMessageHeader); + assertEquals("Unexpected message header", "value", bytesMessageHeader.get("test")); + + // get content + HttpURLConnection connection = openManagementConection("/rest/message-content/test/" + queueName + "/" + + lastMessageId, "GET"); + connection.connect(); + byte[] data = readConnectionInputStream(connection); + assertTrue("Unexpected message", Arrays.equals(messageBytes, data)); + + } + + public void testPostMoveMessages() throws Exception + { + String queueName = getTestQueueName(); + String queueName2 = queueName + "_2"; + Destination queue2 = _session.createQueue(queueName2); + _session.createConsumer(queue2); + + // get message IDs + List<Long> ids = getMesssageIds(queueName); + + // move half of the messages + int movedNumber = ids.size() / 2; + List<Long> movedMessageIds = new ArrayList<Long>(); + for (int i = 0; i < movedNumber; i++) + { + movedMessageIds.add(ids.remove(i)); + } + + // move messages + HttpURLConnection connection = openManagementConection("/rest/message/test/" + queueName, "POST"); + + Map<String, Object> messagesData = new HashMap<String, Object>(); + messagesData.put("messages", movedMessageIds); + messagesData.put("destinationQueue", queueName2); + messagesData.put("move", Boolean.TRUE); + + writeJsonRequest(connection, messagesData); + assertEquals("Unexpected response code", 200, connection.getResponseCode()); + + // check messages on target queue + List<Map<String, Object>> messages = getJsonAsList("/rest/message/test/" + queueName2); + assertNotNull("Messages are not found", messages); + assertEquals("Unexpected number of messages", movedMessageIds.size(), messages.size()); + for (Long id : movedMessageIds) + { + Map<String, Object> message = find("id", id.intValue(), messages); + assertMessageAttributes(message); + } + + // check messages on original queue + messages = getJsonAsList("/rest/message/test/" + queueName); + assertNotNull("Messages are not found", messages); + assertEquals("Unexpected number of messages", ids.size(), messages.size()); + for (Long id : ids) + { + Map<String, Object> message = find("id", id.intValue(), messages); + assertMessageAttributes(message); + } + for (Long id : movedMessageIds) + { + Map<String, Object> message = find("id", id.intValue(), messages); + assertNull("Moved message " + id + " is found on original queue", message); + } + } + + public void testPostCopyMessages() throws Exception + { + String queueName = getTestQueueName(); + String queueName2 = queueName + "_2"; + Destination queue2 = _session.createQueue(queueName2); + _session.createConsumer(queue2); + + // get message IDs + List<Long> ids = getMesssageIds(queueName); + + // copy half of the messages + int copyNumber = ids.size() / 2; + List<Long> copyMessageIds = new ArrayList<Long>(); + for (int i = 0; i < copyNumber; i++) + { + copyMessageIds.add(ids.remove(i)); + } + + // copy messages + HttpURLConnection connection = openManagementConection("/rest/message/test/" + queueName, "POST"); + + Map<String, Object> messagesData = new HashMap<String, Object>(); + messagesData.put("messages", copyMessageIds); + messagesData.put("destinationQueue", queueName2); + + writeJsonRequest(connection, messagesData); + assertEquals("Unexpected response code", 200, connection.getResponseCode()); + + // check messages on target queue + List<Map<String, Object>> messages = getJsonAsList("/rest/message/test/" + queueName2); + assertNotNull("Messages are not found", messages); + assertEquals("Unexpected number of messages", copyMessageIds.size(), messages.size()); + for (Long id : copyMessageIds) + { + Map<String, Object> message = find("id", id.intValue(), messages); + assertMessageAttributes(message); + } + + // check messages on original queue + messages = getJsonAsList("/rest/message/test/" + queueName); + assertNotNull("Messages are not found", messages); + assertEquals("Unexpected number of messages", MESSAGE_NUMBER, messages.size()); + for (Long id : ids) + { + Map<String, Object> message = find("id", id.intValue(), messages); + assertMessageAttributes(message); + } + for (Long id : copyMessageIds) + { + Map<String, Object> message = find("id", id.intValue(), messages); + assertMessageAttributes(message); + } + } + + public void testDeleteMessages() throws Exception + { + String queueName = getTestQueueName(); + + // get message IDs + List<Long> ids = getMesssageIds(queueName); + + // delete half of the messages + int deleteNumber = ids.size() / 2; + StringBuilder queryString = new StringBuilder(); + List<Long> deleteMessageIds = new ArrayList<Long>(); + for (int i = 0; i < deleteNumber; i++) + { + Long id = ids.remove(i); + deleteMessageIds.add(id); + if (queryString.length() > 0) + { + queryString.append("&"); + } + queryString.append("id=").append(id); + } + + // delete messages + HttpURLConnection connection = openManagementConection( + "/rest/message/test/" + queueName + "?" + queryString.toString(), "DELETE"); + connection.connect(); + assertEquals("Unexpected response code", 200, connection.getResponseCode()); + + // check messages on queue + List<Map<String, Object>> messages = getJsonAsList("/rest/message/test/" + queueName); + assertNotNull("Messages are not found", messages); + assertEquals("Unexpected number of messages", ids.size(), messages.size()); + for (Long id : ids) + { + Map<String, Object> message = find("id", id.intValue(), messages); + assertMessageAttributes(message); + } + for (Long id : deleteMessageIds) + { + Map<String, Object> message = find("id", id.intValue(), messages); + assertNull("Message with id " + id + " was not deleted", message); + } + } + + private List<Long> getMesssageIds(String queueName) throws IOException, JsonParseException, JsonMappingException + { + List<Map<String, Object>> messages = getJsonAsList("/rest/message/test/" + queueName); + List<Long> ids = new ArrayList<Long>(); + for (Map<String, Object> message : messages) + { + ids.add(((Number) message.get("id")).longValue()); + } + return ids; + } + + private void assertMessage(int position, Map<String, Object> message) + { + assertMessageAttributes(message); + + assertEquals("Unexpected message attribute position", position, message.get("position")); + assertEquals("Unexpected message attribute size", position < 10 ? 6 : 7, message.get("size")); + boolean even = position % 2 == 0; + assertMessageAttributeValues(message, even); + } + + private void assertMessageAttributeValues(Map<String, Object> message, boolean even) + { + if (even) + { + assertEquals("Unexpected message attribute expirationTime", 0, message.get("expirationTime")); + assertEquals("Unexpected message attribute priority", 4, message.get("priority")); + assertEquals("Unexpected message attribute persistent", Boolean.TRUE, message.get("persistent")); + } + else + { + assertEquals("Unexpected message attribute expirationTime", ((Number) message.get("timestamp")).longValue() + + _ttl, message.get("expirationTime")); + assertEquals("Unexpected message attribute priority", 5, message.get("priority")); + assertEquals("Unexpected message attribute persistent", Boolean.FALSE, message.get("persistent")); + } + assertEquals("Unexpected message attribute mimeType", "text/plain", message.get("mimeType")); + assertEquals("Unexpected message attribute userId", "guest", message.get("userId")); + assertEquals("Unexpected message attribute deliveryCount", 0, message.get("deliveryCount")); + assertEquals("Unexpected message attribute state", "Available", message.get("state")); + } + + private void assertMessageAttributes(Map<String, Object> message) + { + assertNotNull("Message map cannot be null", message); + assertNotNull("Unexpected message attribute deliveryCount", message.get("deliveryCount")); + assertNotNull("Unexpected message attribute state", message.get("state")); + assertNotNull("Unexpected message attribute id", message.get("id")); + assertNotNull("Message arrivalTime cannot be null", message.get("arrivalTime")); + assertNotNull("Message timestamp cannot be null", message.get("timestamp")); + assertTrue("Message arrivalTime cannot be null", ((Number) message.get("arrivalTime")).longValue() > _startTime); + assertNotNull("Message messageId cannot be null", message.get("messageId")); + assertNotNull("Unexpected message attribute mimeType", message.get("mimeType")); + assertNotNull("Unexpected message attribute userId", message.get("userId")); + assertNotNull("Message priority cannot be null", message.get("priority")); + assertNotNull("Message expirationTime cannot be null", message.get("expirationTime")); + assertNotNull("Message persistent cannot be null", message.get("persistent")); + } +} diff --git a/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/PortRestTest.java b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/PortRestTest.java new file mode 100644 index 0000000000..49f163baae --- /dev/null +++ b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/PortRestTest.java @@ -0,0 +1,41 @@ +package org.apache.qpid.server.management.plugin.servlet.rest; + +import java.net.URLDecoder; +import java.util.List; +import java.util.Map; + +import org.apache.qpid.server.model.Port; + +public class PortRestTest extends QpidRestTestCase +{ + public void testGet() throws Exception + { + List<Map<String, Object>> ports = getJsonAsList("/rest/port/"); + assertNotNull("Port data cannot be null", ports); + assertEquals("Unexpected number of ports", 2, ports.size()); + int[] expectedPorts = { getPort(), getHttpPort() }; + for (int port : expectedPorts) + { + String portName = "0.0.0.0:" + port; + Map<String, Object> portData = find(Port.NAME, portName, ports); + assertNotNull("Port " + portName + " is not found", portData); + Asserts.assertPortAttributes(portData); + } + } + + public void testGetPort() throws Exception + { + List<Map<String, Object>> ports = getJsonAsList("/rest/port/"); + assertNotNull("Ports data cannot be null", ports); + assertEquals("Unexpected number of ports", 2, ports.size()); + for (Map<String, Object> portMap : ports) + { + String portName = (String) portMap.get(Port.NAME); + assertNotNull("Port name attribute is not found", portName); + Map<String, Object> portData = getJsonAsSingletonList("/rest/port/" + URLDecoder.decode(portName, "UTF-8")); + assertNotNull("Port " + portName + " is not found", portData); + Asserts.assertPortAttributes(portData); + } + } + +} diff --git a/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/QpidRestTestCase.java b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/QpidRestTestCase.java new file mode 100644 index 0000000000..e83341de80 --- /dev/null +++ b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/QpidRestTestCase.java @@ -0,0 +1,245 @@ +/* + * + * 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.management.plugin.servlet.rest; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.log4j.Logger; +import org.apache.qpid.test.utils.QpidBrokerTestCase; +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.map.JsonMappingException; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.type.TypeReference; + +public class QpidRestTestCase extends QpidBrokerTestCase +{ + private static final Logger LOGGER = Logger.getLogger(QpidRestTestCase.class); + + public static final String[] EXPECTED_HOSTS = { "development", "test", "localhost" }; + public static final String[] EXPECTED_QUEUES = { "queue", "ping" }; + public static final String[] EXPECTED_EXCHANGES = { "amq.fanout", "amq.match", "amq.direct", "amq.topic", + "qpid.management", "<<default>>" }; + + private int _httpPort; + private String _hostName; + private List<HttpURLConnection> _httpConnections; + + @Override + public void setUp() throws Exception + { + _httpConnections = new ArrayList<HttpURLConnection>(); + _hostName = InetAddress.getLocalHost().getHostName(); + _httpPort = findFreePort(); + customizeConfiguration(); + super.setUp(); + + } + + protected void customizeConfiguration() throws ConfigurationException, IOException + { + setConfigurationProperty("management.enabled", "false"); + setConfigurationProperty("management.http.enabled", "true"); + setConfigurationProperty("management.https.enabled", "false"); + setConfigurationProperty("management.http.port", Integer.toString(_httpPort)); + } + + public void teearDown() throws Exception + { + for (HttpURLConnection connection : _httpConnections) + { + try + { + connection.disconnect(); + } + catch (Exception e) + { + // ignore + } + } + super.tearDown(); + } + + protected int getHttpPort() + { + return _httpPort; + } + + protected String getHostName() + { + return _hostName; + } + + protected String getProtocol() + { + return "http"; + } + + protected String getManagementURL() + { + return getProtocol() + "://" + getHostName() + ":" + getHttpPort(); + } + + protected URL getManagementURL(String path) throws MalformedURLException + { + return new URL(getManagementURL() + path); + } + + protected HttpURLConnection openManagementConection(String path) throws IOException + { + URL url = getManagementURL(path); + HttpURLConnection httpCon = (HttpURLConnection) url.openConnection(); + httpCon.setDoOutput(true); + return httpCon; + } + + protected HttpURLConnection openManagementConection(String path, String method) throws IOException + { + HttpURLConnection httpCon = openManagementConection(path); + httpCon.setRequestMethod(method); + return httpCon; + } + + protected List<Map<String, Object>> readJsonResponseAsList(HttpURLConnection connection) throws IOException, + JsonParseException, JsonMappingException + { + byte[] data = readConnectionInputStream(connection); + + ObjectMapper mapper = new ObjectMapper(); + + TypeReference<List<LinkedHashMap<String, Object>>> typeReference = new TypeReference<List<LinkedHashMap<String, Object>>>() + { + }; + List<Map<String, Object>> providedObject = mapper.readValue(new ByteArrayInputStream(data), typeReference); + return providedObject; + } + + protected Map<String, Object> readJsonResponseAsMap(HttpURLConnection connection) throws IOException, + JsonParseException, JsonMappingException + { + byte[] data = readConnectionInputStream(connection); + + ObjectMapper mapper = new ObjectMapper(); + + TypeReference<LinkedHashMap<String, Object>> typeReference = new TypeReference<LinkedHashMap<String, Object>>() + { + }; + Map<String, Object> providedObject = mapper.readValue(new ByteArrayInputStream(data), typeReference); + return providedObject; + } + + protected byte[] readConnectionInputStream(HttpURLConnection connection) throws IOException + { + InputStream is = connection.getInputStream(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len = -1; + while ((len = is.read(buffer)) != -1) + { + baos.write(buffer, 0, len); + } + if (LOGGER.isTraceEnabled()) + { + LOGGER.trace("RESPONSE:" + new String(baos.toByteArray())); + } + return baos.toByteArray(); + } + + protected void writeJsonRequest(HttpURLConnection connection, Map<String, Object> data) throws JsonGenerationException, + JsonMappingException, IOException + { + ObjectMapper mapper = new ObjectMapper(); + mapper.writeValue(connection.getOutputStream(), data); + } + + protected Map<String, Object> find(String name, Object value, List<Map<String, Object>> data) + { + for (Map<String, Object> map : data) + { + Object mapValue = map.get(name); + if (value.equals(mapValue)) + { + return map; + } + } + return null; + } + + protected Map<String, Object> find(Map<String, Object> searchAttributes, List<Map<String, Object>> data) + { + for (Map<String, Object> map : data) + { + boolean equals = true; + for (Map.Entry<String, Object> entry : searchAttributes.entrySet()) + { + Object mapValue = map.get(entry.getKey()); + if (!entry.getValue().equals(mapValue)) + { + equals = false; + break; + } + } + if (equals) + { + return map; + } + } + return null; + } + + protected Map<String, Object> getJsonAsSingletonList(String path) throws IOException + { + List<Map<String, Object>> response = getJsonAsList(path); + + assertNotNull("Response cannot be null", response); + assertEquals("Unexpected response", 1, response.size()); + return response.get(0); + } + + protected List<Map<String, Object>> getJsonAsList(String path) throws IOException, JsonParseException, + JsonMappingException + { + HttpURLConnection connection = openManagementConection(path, "GET"); + connection.connect(); + List<Map<String, Object>> response = readJsonResponseAsList(connection); + return response; + } + + protected Map<String, Object> getJsonAsMap(String path) throws IOException + { + HttpURLConnection connection = openManagementConection(path, "GET"); + connection.connect(); + Map<String, Object> response = readJsonResponseAsMap(connection); + return response; + } +} diff --git a/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/QueueRestTest.java b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/QueueRestTest.java new file mode 100644 index 0000000000..5f11b3fb1d --- /dev/null +++ b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/QueueRestTest.java @@ -0,0 +1,225 @@ +/* + * + * 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.management.plugin.servlet.rest; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URLDecoder; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.jms.Connection; +import javax.jms.Destination; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Session; + +import org.apache.qpid.server.model.Binding; +import org.apache.qpid.server.model.Consumer; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.Queue; + +public class QueueRestTest extends QpidRestTestCase +{ + private static final String QUEUE_ATTRIBUTE_CONSUMERS = "consumers"; + private static final String QUEUE_ATTRIBUTE_BINDINGS = "bindings"; + + /** + * Message number to publish into queue + */ + private static final int MESSAGE_NUMBER = 2; + private static final int MESSAGE_PAYLOAD_SIZE = 6; + private static final int ENQUEUED_MESSAGES = 1; + private static final int DEQUEUED_MESSAGES = 1; + private static final int ENQUEUED_BYTES = MESSAGE_PAYLOAD_SIZE; + private static final int DEQUEUED_BYTES = MESSAGE_PAYLOAD_SIZE; + + private Connection _connection; + + public void setUp() throws Exception + { + super.setUp(); + _connection = getConnection(); + Session session = _connection.createSession(true, Session.SESSION_TRANSACTED); + String queueName = getTestQueueName(); + Destination queue = session.createQueue(queueName); + MessageConsumer consumer = session.createConsumer(queue); + MessageProducer producer = session.createProducer(queue); + + for (int i = 0; i < MESSAGE_NUMBER; i++) + { + producer.send(session.createTextMessage("Test-" + i)); + } + session.commit(); + _connection.start(); + Message m = consumer.receive(1000l); + assertNotNull("Message is not received", m); + session.commit(); + } + + public void testGetVirtualHostQueues() throws Exception + { + String queueName = getTestQueueName(); + List<Map<String, Object>> queues = getJsonAsList("/rest/queue/test"); + assertEquals("Unexpected number of queues", EXPECTED_QUEUES.length + 1, queues.size()); + String[] expectedQueues = new String[EXPECTED_QUEUES.length + 1]; + System.arraycopy(EXPECTED_QUEUES, 0, expectedQueues, 0, EXPECTED_QUEUES.length); + expectedQueues[EXPECTED_QUEUES.length] = queueName; + + for (String name : expectedQueues) + { + Map<String, Object> queueDetails = find(Queue.NAME, name, queues); + Asserts.assertQueue(name, "standard", queueDetails); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> bindings = (List<Map<String, Object>>) queueDetails.get(QUEUE_ATTRIBUTE_BINDINGS); + assertNotNull("Queue bindings are not found", bindings); + assertEquals("Unexpected number of bindings", 2, bindings.size()); + + Map<String, Object> defaultExchangeBinding = find(Binding.EXCHANGE, "<<default>>", bindings); + Map<String, Object> directExchangeBinding = find(Binding.EXCHANGE, "amq.direct", bindings); + Asserts.assertBinding(name, "<<default>>", defaultExchangeBinding); + Asserts.assertBinding(name, "amq.direct", directExchangeBinding); + } + } + + public void testGetByName() throws Exception + { + String queueName = getTestQueueName(); + Map<String, Object> queueDetails = getJsonAsSingletonList("/rest/queue/test/" + queueName); + Asserts.assertQueue(queueName, "standard", queueDetails); + assertStatistics(queueDetails); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> bindings = (List<Map<String, Object>>) queueDetails.get(QUEUE_ATTRIBUTE_BINDINGS); + assertNotNull("Queue bindings are not found", bindings); + assertEquals("Unexpected number of bindings", 2, bindings.size()); + + Map<String, Object> defaultExchangeBinding = find(Binding.EXCHANGE, "<<default>>", bindings); + Map<String, Object> directExchangeBinding = find(Binding.EXCHANGE, "amq.direct", bindings); + Asserts.assertBinding(queueName, "<<default>>", defaultExchangeBinding); + Asserts.assertBinding(queueName, "amq.direct", directExchangeBinding); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> consumers = (List<Map<String, Object>>) queueDetails.get(QUEUE_ATTRIBUTE_CONSUMERS); + assertNotNull("Queue consumers are not found", consumers); + assertEquals("Unexpected number of consumers", 1, consumers.size()); + assertConsumer(consumers.get(0)); + } + + public void testPutCreateBinding() throws Exception + { + String queueName = getTestQueueName(); + String bindingName = queueName + 2; + String[] exchanges = { "amq.direct", "amq.fanout", "amq.topic", "amq.match", "qpid.management", "<<default>>" }; + + for (int i = 0; i < exchanges.length; i++) + { + createBinding(bindingName, exchanges[i], queueName); + } + + Map<String, Object> queueDetails = getJsonAsSingletonList("/rest/queue/test/" + queueName); + Asserts.assertQueue(queueName, "standard", queueDetails); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> bindings = (List<Map<String, Object>>) queueDetails.get(QUEUE_ATTRIBUTE_BINDINGS); + assertNotNull("Queue bindings are not found", bindings); + assertEquals("Unexpected number of bindings", exchanges.length + 2, bindings.size()); + + Map<String, Object> searchAttributes = new HashMap<String, Object>(); + searchAttributes.put(Binding.NAME, bindingName); + + for (int i = 0; i < exchanges.length; i++) + { + searchAttributes.put(Binding.EXCHANGE, exchanges[i]); + Map<String, Object> binding = find(searchAttributes, bindings); + Asserts.assertBinding(bindingName, queueName, exchanges[i], binding); + } + } + + private void createBinding(String bindingName, String exchangeName, String queueName) throws IOException + { + HttpURLConnection connection = openManagementConection( + "/rest/binding/test/" + URLDecoder.decode(exchangeName, "UTF-8") + "/" + queueName + "/" + bindingName, + "PUT"); + + Map<String, Object> bindingData = new HashMap<String, Object>(); + bindingData.put(Binding.NAME, bindingName); + bindingData.put(Binding.EXCHANGE, exchangeName); + bindingData.put(Binding.QUEUE, queueName); + + writeJsonRequest(connection, bindingData); + assertEquals("Unexpected response code", 201, connection.getResponseCode()); + + connection.disconnect(); + } + + private void assertConsumer(Map<String, Object> consumer) + { + assertNotNull("Consumer map should not be null", consumer); + Asserts.assertAttributesPresent(consumer, Consumer.AVAILABLE_ATTRIBUTES, Consumer.STATE, Consumer.TIME_TO_LIVE, + Consumer.CREATED, Consumer.UPDATED, Consumer.SETTLEMENT_MODE, Consumer.EXCLUSIVE, Consumer.SELECTOR, + Consumer.NO_LOCAL); + + assertEquals("Unexpected binding attribute " + Consumer.NAME, "1", consumer.get(Consumer.NAME)); + assertEquals("Unexpected binding attribute " + Consumer.DURABLE, Boolean.FALSE, consumer.get(Consumer.DURABLE)); + assertEquals("Unexpected binding attribute " + Consumer.LIFETIME_POLICY, LifetimePolicy.AUTO_DELETE.name(), + consumer.get(Consumer.LIFETIME_POLICY)); + assertEquals("Unexpected binding attribute " + Consumer.DISTRIBUTION_MODE, "MOVE", + consumer.get(Consumer.DISTRIBUTION_MODE)); + + @SuppressWarnings("unchecked") + Map<String, Object> statistics = (Map<String, Object>) consumer.get(Asserts.STATISTICS_ATTRIBUTE); + assertNotNull("Consumer statistics is not present", statistics); + Asserts.assertAttributesPresent(statistics, Consumer.AVAILABLE_STATISTICS, Consumer.STATE_CHANGED); + } + + private void assertStatistics(Map<String, Object> queueDetails) + { + @SuppressWarnings("unchecked") + Map<String, Object> statistics = (Map<String, Object>) queueDetails.get(Asserts.STATISTICS_ATTRIBUTE); + assertEquals("Unexpected queue statistics attribute " + Queue.PERSISTENT_DEQUEUED_MESSAGES, DEQUEUED_MESSAGES, + statistics.get(Queue.PERSISTENT_DEQUEUED_MESSAGES)); + assertEquals("Unexpected queue statistics attribute " + Queue.QUEUE_DEPTH_MESSAGES, ENQUEUED_MESSAGES, + statistics.get(Queue.QUEUE_DEPTH_MESSAGES)); + assertEquals("Unexpected queue statistics attribute " + Queue.CONSUMER_COUNT, 1, + statistics.get(Queue.CONSUMER_COUNT)); + assertEquals("Unexpected queue statistics attribute " + Queue.CONSUMER_COUNT_WITH_CREDIT, 1, + statistics.get(Queue.CONSUMER_COUNT_WITH_CREDIT)); + assertEquals("Unexpected queue statistics attribute " + Queue.BINDING_COUNT, 2, statistics.get(Queue.BINDING_COUNT)); + assertEquals("Unexpected queue statistics attribute " + Queue.PERSISTENT_DEQUEUED_MESSAGES, DEQUEUED_MESSAGES, + statistics.get(Queue.PERSISTENT_DEQUEUED_MESSAGES)); + assertEquals("Unexpected queue statistics attribute " + Queue.TOTAL_DEQUEUED_MESSAGES, DEQUEUED_MESSAGES, + statistics.get(Queue.TOTAL_DEQUEUED_MESSAGES)); + assertEquals("Unexpected queue statistics attribute " + Queue.TOTAL_DEQUEUED_BYTES, DEQUEUED_BYTES, + statistics.get(Queue.TOTAL_DEQUEUED_BYTES)); + assertEquals("Unexpected queue statistics attribute " + Queue.PERSISTENT_DEQUEUED_BYTES, DEQUEUED_BYTES, + statistics.get(Queue.TOTAL_DEQUEUED_BYTES)); + assertEquals("Unexpected queue statistics attribute " + Queue.PERSISTENT_ENQUEUED_BYTES, ENQUEUED_BYTES + + DEQUEUED_BYTES, statistics.get(Queue.PERSISTENT_ENQUEUED_BYTES)); + assertEquals("Unexpected queue statistics attribute " + Queue.TOTAL_ENQUEUED_BYTES, ENQUEUED_BYTES + DEQUEUED_BYTES, + statistics.get(Queue.TOTAL_ENQUEUED_BYTES)); + assertEquals("Unexpected queue statistics attribute " + Queue.QUEUE_DEPTH_BYTES, ENQUEUED_BYTES, + statistics.get(Queue.QUEUE_DEPTH_BYTES)); + } +} diff --git a/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslRestTest.java b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslRestTest.java new file mode 100644 index 0000000000..943466eda7 --- /dev/null +++ b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslRestTest.java @@ -0,0 +1,22 @@ +package org.apache.qpid.server.management.plugin.servlet.rest; + +import java.util.List; +import java.util.Map; + +public class SaslRestTest extends QpidRestTestCase +{ + public void testGet() throws Exception + { + Map<String, Object> saslData = getJsonAsMap("/rest/sasl"); + assertNotNull("mechanisms attribute is not found", saslData.get("mechanisms")); + + @SuppressWarnings("unchecked") + List<String> mechanisms = (List<String>) saslData.get("mechanisms"); + String[] expectedMechanisms = { "AMQPLAIN", "PLAIN", "CRAM-MD5" }; + for (String mechanism : expectedMechanisms) + { + assertTrue("Mechanism " + mechanism + " is not found", mechanisms.contains(mechanism)); + } + } + +} diff --git a/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/StructureRestTest.java b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/StructureRestTest.java new file mode 100644 index 0000000000..b01e1d44b8 --- /dev/null +++ b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/StructureRestTest.java @@ -0,0 +1,115 @@ +/* + * + * 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.management.plugin.servlet.rest; + +import java.util.List; +import java.util.Map; + +public class StructureRestTest extends QpidRestTestCase +{ + + public void testGet() throws Exception + { + Map<String, Object> structure = getJsonAsMap("/rest/structure"); + assertNotNull("Structure data cannot be null", structure); + assertNode(structure, "Broker"); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> virtualhosts = (List<Map<String, Object>>) structure.get("virtualhosts"); + assertEquals("Unexpected number of virtual hosts", 3, virtualhosts.size()); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> ports = (List<Map<String, Object>>) structure.get("ports"); + assertEquals("Unexpected number of ports", 2, ports.size()); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> providers = (List<Map<String, Object>>) structure.get("authenticationproviders"); + assertEquals("Unexpected number of authentication providers", 1, providers.size()); + + for (String hostName : EXPECTED_HOSTS) + { + Map<String, Object> host = find("name", hostName, virtualhosts); + assertNotNull("Host " + hostName + " is not found ", host); + assertNode(host, hostName); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> queues = (List<Map<String, Object>>) host.get("queues"); + assertNotNull("Host " + hostName + " queues are not found ", queues); + for (String queueName : EXPECTED_QUEUES) + { + Map<String, Object> queue = find("name", queueName, queues); + assertNotNull(hostName + " queue " + queueName + " is not found ", queue); + assertNode(queue, queueName); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> bindings = (List<Map<String, Object>>) queue.get("bindings"); + assertNotNull(hostName + " queue " + queueName + " bindings are not found ", queues); + for (Map<String, Object> binding : bindings) + { + assertNode(binding, queueName); + } + } + + @SuppressWarnings("unchecked") + List<Map<String, Object>> exchanges = (List<Map<String, Object>>) host.get("exchanges"); + assertNotNull("Host " + hostName + " exchanges are not found ", exchanges); + for (String exchangeName : EXPECTED_EXCHANGES) + { + Map<String, Object> exchange = find("name", exchangeName, exchanges); + assertNotNull("Exchange " + exchangeName + " is not found ", exchange); + assertNode(exchange, exchangeName); + if ("amq.direct".equalsIgnoreCase(exchangeName) || "<<default>>".equalsIgnoreCase(exchangeName)) + { + @SuppressWarnings("unchecked") + List<Map<String, Object>> bindings = (List<Map<String, Object>>) exchange.get("bindings"); + assertNotNull(hostName + " exchange " + exchangeName + " bindings are not found ", bindings); + for (String queueName : EXPECTED_QUEUES) + { + Map<String, Object> binding = find("name", queueName, bindings); + assertNotNull(hostName + " exchange " + exchangeName + " binding " + queueName + " is not found", binding); + assertNode(binding, queueName); + } + } + } + + @SuppressWarnings("unchecked") + List<Map<String, Object>> aliases = (List<Map<String, Object>>) host.get("virtualhostaliases"); + assertNotNull("Host " + hostName + " aliaces are not found ", aliases); + assertEquals("Unexpected aliaces size", 1, aliases.size()); + assertNode(aliases.get(0), hostName); + } + + int[] expectedPorts = { getPort(), getHttpPort() }; + for (int port : expectedPorts) + { + String portName = "0.0.0.0:" + port; + Map<String, Object> portData = find("name", portName, ports); + assertNotNull("Port " + portName + " is not found ", portData); + assertNode(portData, portName); + } + } + + private void assertNode(Map<String, Object> node, String name) + { + assertEquals("Unexpected name", name, node.get("name")); + assertNotNull("Unexpected id", node.get("id")); + } +} diff --git a/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/UserRestTest.java b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/UserRestTest.java new file mode 100644 index 0000000000..378b349a99 --- /dev/null +++ b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/UserRestTest.java @@ -0,0 +1,92 @@ +package org.apache.qpid.server.management.plugin.servlet.rest; + +import java.net.HttpURLConnection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.qpid.server.model.User; + +public class UserRestTest extends QpidRestTestCase +{ + public void testGet() throws Exception + { + List<Map<String, Object>> users = getJsonAsList("/rest/user"); + assertNotNull("Users cannot be null", users); + assertTrue("Unexpected number of users", users.size() > 1); + for (Map<String, Object> user : users) + { + assertUser(user); + } + } + + public void testGetUserByName() throws Exception + { + List<Map<String, Object>> users = getJsonAsList("/rest/user"); + assertNotNull("Users cannot be null", users); + assertTrue("Unexpected number of users", users.size() > 1); + for (Map<String, Object> user : users) + { + assertNotNull("Attribute " + User.ID, user.get(User.ID)); + String userName = (String) user.get(User.NAME); + assertNotNull("Attribute " + User.NAME, userName); + Map<String, Object> userDetails = getJsonAsSingletonList("/rest/user/PrincipalDatabaseAuthenticationManager/" + + userName); + assertUser(userDetails); + assertEquals("Unexpected user name", userName, userDetails.get(User.NAME)); + } + } + + public void testPut() throws Exception + { + String userName = getTestName(); + HttpURLConnection connection = openManagementConection("/rest/user/PrincipalDatabaseAuthenticationManager/" + + userName, "PUT"); + + Map<String, Object> userData = new HashMap<String, Object>(); + userData.put(User.NAME, userName); + userData.put(User.PASSWORD, userName); + + writeJsonRequest(connection, userData); + assertEquals("Unexpected response code", 201, connection.getResponseCode()); + + connection.disconnect(); + + Map<String, Object> userDetails = getJsonAsSingletonList("/rest/user/PrincipalDatabaseAuthenticationManager/" + + userName); + assertUser(userDetails); + assertEquals("Unexpected user name", userName, userDetails.get(User.NAME)); + } + + public void testDelete() throws Exception + { + // add user + String userName = getTestName(); + HttpURLConnection connection = openManagementConection("/rest/user/PrincipalDatabaseAuthenticationManager/" + + userName, "PUT"); + + Map<String, Object> userData = new HashMap<String, Object>(); + userData.put(User.NAME, userName); + userData.put(User.PASSWORD, userName); + + writeJsonRequest(connection, userData); + assertEquals("Unexpected response code", 201, connection.getResponseCode()); + connection.disconnect(); + + Map<String, Object> userDetails = getJsonAsSingletonList("/rest/user/PrincipalDatabaseAuthenticationManager/" + + userName); + String id = (String) userDetails.get(User.ID); + + connection = openManagementConection("/rest/user/PrincipalDatabaseAuthenticationManager?id=" + id, "DELETE"); + connection.connect(); + assertEquals("Unexpected response code", 200, connection.getResponseCode()); + List<Map<String, Object>> users = getJsonAsList("/rest/user/PrincipalDatabaseAuthenticationManager/" + userName); + assertEquals("User should be deleted", 0, users.size()); + } + + private void assertUser(Map<String, Object> user) + { + assertNotNull("Attribute " + User.ID, user.get(User.ID)); + assertNotNull("Attribute " + User.NAME, user.get(User.NAME)); + } +} diff --git a/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/VirtualHostRestTest.java b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/VirtualHostRestTest.java new file mode 100644 index 0000000000..17f1aaaf7b --- /dev/null +++ b/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/servlet/rest/VirtualHostRestTest.java @@ -0,0 +1,434 @@ +/* + * + * 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.management.plugin.servlet.rest; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.jms.Session; + +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.server.model.Exchange; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.map.JsonMappingException; + +public class VirtualHostRestTest extends QpidRestTestCase +{ + private static final String VIRTUALHOST_EXCHANGES_ATTRIBUTE = "exchanges"; + public static final String VIRTUALHOST_QUEUES_ATTRIBUTE = "queues"; + public static final String VIRTUALHOST_CONNECTIONS_ATTRIBUTE = "connections"; + + private AMQConnection _connection; + + public void testGet() throws Exception + { + List<Map<String, Object>> hosts = getJsonAsList("/rest/virtualhost/"); + assertNotNull("Hosts data cannot be null", hosts); + assertEquals("Unexpected number of hosts", 3, hosts.size()); + for (String hostName : EXPECTED_HOSTS) + { + Map<String, Object> host = find("name", hostName, hosts); + Asserts.assertVirtualHost(hostName, host); + } + } + + public void testGetHost() throws Exception + { + // create AMQP connection to get connection JSON details + _connection = (AMQConnection) getConnection(); + _connection.createSession(true, Session.SESSION_TRANSACTED); + + Map<String, Object> hostDetails = getJsonAsSingletonList("/rest/virtualhost/test"); + Asserts.assertVirtualHost("test", hostDetails); + + @SuppressWarnings("unchecked") + Map<String, Object> statistics = (Map<String, Object>) hostDetails.get(Asserts.STATISTICS_ATTRIBUTE); + assertEquals("Unexpected number of exchanges in statistics", 6, statistics.get(VirtualHost.EXCHANGE_COUNT)); + assertEquals("Unexpected number of queues in statistics", 2, statistics.get(VirtualHost.QUEUE_COUNT)); + assertEquals("Unexpected number of connections in statistics", 1, statistics.get(VirtualHost.CONNECTION_COUNT)); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> exchanges = (List<Map<String, Object>>) hostDetails.get(VIRTUALHOST_EXCHANGES_ATTRIBUTE); + assertEquals("Unexpected number of exchanges", 6, exchanges.size()); + Asserts.assertDurableExchange("amq.fanout", "fanout", find(Exchange.NAME, "amq.fanout", exchanges)); + Asserts.assertDurableExchange("qpid.management", "management", find(Exchange.NAME, "qpid.management", exchanges)); + Asserts.assertDurableExchange("amq.topic", "topic", find(Exchange.NAME, "amq.topic", exchanges)); + Asserts.assertDurableExchange("amq.direct", "direct", find(Exchange.NAME, "amq.direct", exchanges)); + Asserts.assertDurableExchange("amq.match", "headers", find(Exchange.NAME, "amq.match", exchanges)); + Asserts.assertDurableExchange("<<default>>", "direct", find(Exchange.NAME, "<<default>>", exchanges)); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> queues = (List<Map<String, Object>>) hostDetails.get(VIRTUALHOST_QUEUES_ATTRIBUTE); + assertEquals("Unexpected number of queues", 2, queues.size()); + Map<String, Object> queue = find(Queue.NAME, "queue", queues); + Map<String, Object> ping = find(Queue.NAME, "ping", queues); + Asserts.assertQueue("queue", "standard", queue); + Asserts.assertQueue("ping", "standard", ping); + assertEquals("Unexpected value of queue attribute " + Queue.DURABLE, Boolean.FALSE, queue.get(Queue.DURABLE)); + assertEquals("Unexpected value of queue attribute " + Queue.DURABLE, Boolean.FALSE, ping.get(Queue.DURABLE)); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> connections = (List<Map<String, Object>>) hostDetails + .get(VIRTUALHOST_CONNECTIONS_ATTRIBUTE); + assertEquals("Unexpected number of connections", 1, connections.size()); + Asserts.assertConnection(connections.get(0), _connection); + } + + public void testPutCreateQueue() throws Exception + { + String queueName = getTestQueueName(); + + createQueue(queueName + "-standard", "standard", null); + + Map<String, Object> sortedQueueAttributes = new HashMap<String, Object>(); + sortedQueueAttributes.put(Queue.SORT_KEY, "sortme"); + createQueue(queueName + "-sorted", "sorted", sortedQueueAttributes); + + Map<String, Object> priorityQueueAttributes = new HashMap<String, Object>(); + priorityQueueAttributes.put(Queue.PRIORITIES, 10); + createQueue(queueName + "-priority", "priority", priorityQueueAttributes); + + Map<String, Object> lvqQueueAttributes = new HashMap<String, Object>(); + lvqQueueAttributes.put(Queue.LVQ_KEY, "LVQ"); + createQueue(queueName + "-lvq", "lvq", lvqQueueAttributes); + + Map<String, Object> hostDetails = getJsonAsSingletonList("/rest/virtualhost/test"); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> queues = (List<Map<String, Object>>) hostDetails.get(VirtualHostRestTest.VIRTUALHOST_QUEUES_ATTRIBUTE); + Map<String, Object> standardQueue = find(Queue.NAME, queueName + "-standard" , queues); + Map<String, Object> sortedQueue = find(Queue.NAME, queueName + "-sorted" , queues); + Map<String, Object> priorityQueue = find(Queue.NAME, queueName + "-priority" , queues); + Map<String, Object> lvqQueue = find(Queue.NAME, queueName + "-lvq" , queues); + + Asserts.assertQueue(queueName + "-standard", "standard", standardQueue); + Asserts.assertQueue(queueName + "-sorted", "sorted", sortedQueue); + Asserts.assertQueue(queueName + "-priority", "priority", priorityQueue); + Asserts.assertQueue(queueName + "-lvq", "lvq", lvqQueue); + + assertEquals("Unexpected value of queue attribute " + Queue.DURABLE, Boolean.TRUE, standardQueue.get(Queue.DURABLE)); + assertEquals("Unexpected value of queue attribute " + Queue.DURABLE, Boolean.TRUE, sortedQueue.get(Queue.DURABLE)); + assertEquals("Unexpected value of queue attribute " + Queue.DURABLE, Boolean.TRUE, priorityQueue.get(Queue.DURABLE)); + assertEquals("Unexpected value of queue attribute " + Queue.DURABLE, Boolean.TRUE, lvqQueue.get(Queue.DURABLE)); + + assertEquals("Unexpected sorted key attribute", "sortme", sortedQueue.get(Queue.SORT_KEY)); + assertEquals("Unexpected lvq key attribute", "LVQ", lvqQueue.get(Queue.LVQ_KEY)); + assertEquals("Unexpected priorities key attribute", 10, priorityQueue.get(Queue.PRIORITIES)); + } + + public void testPutCreateExchange() throws Exception + { + String exchangeName = getTestName(); + + createExchange(exchangeName + "-direct", "direct"); + createExchange(exchangeName + "-topic", "topic"); + createExchange(exchangeName + "-headers", "headers"); + createExchange(exchangeName + "-fanout", "fanout"); + + Map<String, Object> hostDetails = getJsonAsSingletonList("/rest/virtualhost/test"); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> exchanges = (List<Map<String, Object>>) hostDetails.get(VirtualHostRestTest.VIRTUALHOST_EXCHANGES_ATTRIBUTE); + Map<String, Object> directExchange = find(Queue.NAME, exchangeName + "-direct" , exchanges); + Map<String, Object> topicExchange = find(Queue.NAME, exchangeName + "-topic" , exchanges); + Map<String, Object> headersExchange = find(Queue.NAME, exchangeName + "-headers" , exchanges); + Map<String, Object> fanoutExchange = find(Queue.NAME, exchangeName + "-fanout" , exchanges); + + Asserts.assertDurableExchange(exchangeName + "-direct", "direct", directExchange); + Asserts.assertDurableExchange(exchangeName + "-topic", "topic", topicExchange); + Asserts.assertDurableExchange(exchangeName + "-headers", "headers", headersExchange); + Asserts.assertDurableExchange(exchangeName + "-fanout", "fanout", fanoutExchange); + + assertEquals("Unexpected value of queue attribute " + Queue.DURABLE, Boolean.TRUE, directExchange.get(Queue.DURABLE)); + assertEquals("Unexpected value of queue attribute " + Queue.DURABLE, Boolean.TRUE, topicExchange.get(Queue.DURABLE)); + assertEquals("Unexpected value of queue attribute " + Queue.DURABLE, Boolean.TRUE, headersExchange.get(Queue.DURABLE)); + assertEquals("Unexpected value of queue attribute " + Queue.DURABLE, Boolean.TRUE, fanoutExchange.get(Queue.DURABLE)); + + } + + public void testPutCreateLVQWithoutKey() throws Exception + { + String queueName = getTestQueueName()+ "-lvq"; + createQueue(queueName, "lvq", null); + + Map<String, Object> hostDetails = getJsonAsSingletonList("/rest/virtualhost/test"); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> queues = (List<Map<String, Object>>) hostDetails.get(VirtualHostRestTest.VIRTUALHOST_QUEUES_ATTRIBUTE); + Map<String, Object> lvqQueue = find(Queue.NAME, queueName , queues); + + Asserts.assertQueue(queueName , "lvq", lvqQueue); + assertEquals("Unexpected value of queue attribute " + Queue.DURABLE, Boolean.TRUE, lvqQueue.get(Queue.DURABLE)); + assertEquals("Unexpected lvq key attribute", AMQQueueFactory.QPID_LVQ_KEY, lvqQueue.get(Queue.LVQ_KEY)); + } + + public void testPutCreateSortedQueueWithoutKey() throws Exception + { + String queueName = getTestQueueName() + "-sorted"; + int responseCode = tryCreateQueue(queueName, "sorted", null); + assertEquals("Unexpected response code", 409, responseCode); + + Map<String, Object> hostDetails = getJsonAsSingletonList("/rest/virtualhost/test"); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> queues = (List<Map<String, Object>>) hostDetails.get(VirtualHostRestTest.VIRTUALHOST_QUEUES_ATTRIBUTE); + Map<String, Object> testQueue = find(Queue.NAME, queueName , queues); + + assertNull("Sorted queue without a key was created ", testQueue); + } + + public void testPutCreatePriorityQueueWithoutKey() throws Exception + { + String queueName = getTestQueueName()+ "-priority"; + createQueue(queueName, "priority", null); + + Map<String, Object> hostDetails = getJsonAsSingletonList("/rest/virtualhost/test"); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> queues = (List<Map<String, Object>>) hostDetails.get(VirtualHostRestTest.VIRTUALHOST_QUEUES_ATTRIBUTE); + Map<String, Object> priorityQueue = find(Queue.NAME, queueName , queues); + + Asserts.assertQueue(queueName , "priority", priorityQueue); + assertEquals("Unexpected value of queue attribute " + Queue.DURABLE, Boolean.TRUE, priorityQueue.get(Queue.DURABLE)); + assertEquals("Unexpected number of priorities", 10, priorityQueue.get(Queue.PRIORITIES)); + } + + public void testPutCreateStandardQueueWithoutType() throws Exception + { + String queueName = getTestQueueName(); + createQueue(queueName, null, null); + + Map<String, Object> hostDetails = getJsonAsSingletonList("/rest/virtualhost/test"); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> queues = (List<Map<String, Object>>) hostDetails.get(VirtualHostRestTest.VIRTUALHOST_QUEUES_ATTRIBUTE); + Map<String, Object> queue = find(Queue.NAME, queueName , queues); + + Asserts.assertQueue(queueName , "standard", queue); + } + + public void testPutCreateQueueOfUnsupportedType() throws Exception + { + String queueName = getTestQueueName(); + int responseCode = tryCreateQueue(queueName, "unsupported", null); + assertEquals("Unexpected response code", 409, responseCode); + + Map<String, Object> hostDetails = getJsonAsSingletonList("/rest/virtualhost/test"); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> queues = (List<Map<String, Object>>) hostDetails.get(VirtualHostRestTest.VIRTUALHOST_QUEUES_ATTRIBUTE); + Map<String, Object> queue = find(Queue.NAME, queueName , queues); + + assertNull("Queue of unsupported type was created", queue); + } + + public void testDeleteQueue() throws Exception + { + String queueName = getTestQueueName(); + createQueue(queueName, null, null); + + HttpURLConnection connection = openManagementConection("/rest/queue/test/" + queueName, "DELETE"); + connection.connect(); + assertEquals("Unexpected response code", 200, connection.getResponseCode()); + List<Map<String, Object>> queues = getJsonAsList("/rest/queue/test/" + queueName); + assertEquals("Queue should be deleted", 0, queues.size()); + } + + public void testDeleteQueueById() throws Exception + { + String queueName = getTestQueueName(); + createQueue(queueName, null, null); + Map<String, Object> queueDetails = getJsonAsSingletonList("/rest/queue/test/" + queueName); + + HttpURLConnection connection = openManagementConection("/rest/queue/test?id=" + queueDetails.get(Queue.ID), "DELETE"); + connection.connect(); + assertEquals("Unexpected response code", 200, connection.getResponseCode()); + List<Map<String, Object>> queues = getJsonAsList("/rest/queue/test/" + queueName); + assertEquals("Queue should be deleted", 0, queues.size()); + } + + public void testDeleteExchange() throws Exception + { + String exchangeName = getTestName(); + createExchange(exchangeName, "direct"); + + HttpURLConnection connection = openManagementConection("/rest/exchange/test/" + exchangeName, "DELETE"); + connection.connect(); + assertEquals("Unexpected response code", 200, connection.getResponseCode()); + List<Map<String, Object>> queues = getJsonAsList("/rest/exchange/test/" + exchangeName); + assertEquals("Exchange should be deleted", 0, queues.size()); + } + + public void testDeleteExchangeById() throws Exception + { + String exchangeName = getTestName(); + createExchange(exchangeName, "direct"); + Map<String, Object> echangeDetails = getJsonAsSingletonList("/rest/exchange/test/" + exchangeName); + + HttpURLConnection connection = openManagementConection("/rest/exchange/test?id=" + echangeDetails.get(Exchange.ID), "DELETE"); + connection.connect(); + assertEquals("Unexpected response code", 200, connection.getResponseCode()); + List<Map<String, Object>> queues = getJsonAsList("/rest/exchange/test/" + exchangeName); + assertEquals("Exchange should be deleted", 0, queues.size()); + } + + public void testPutCreateQueueWithAttributes() throws Exception + { + String queueName = getTestQueueName(); + + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(Queue.ALERT_REPEAT_GAP, 1000); + attributes.put(Queue.ALERT_THRESHOLD_MESSAGE_AGE, 3600000); + attributes.put(Queue.ALERT_THRESHOLD_MESSAGE_SIZE, 1000000000); + attributes.put(Queue.ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES, 800); + attributes.put(Queue.MAXIMUM_DELIVERY_ATTEMPTS, 15); + attributes.put(Queue.QUEUE_FLOW_CONTROL_SIZE_BYTES, 2000000000); + attributes.put(Queue.QUEUE_FLOW_RESUME_SIZE_BYTES, 1500000000); + + createQueue(queueName + "-standard", "standard", attributes); + + Map<String, Object> sortedQueueAttributes = new HashMap<String, Object>(); + sortedQueueAttributes.putAll(attributes); + sortedQueueAttributes.put(Queue.SORT_KEY, "sortme"); + createQueue(queueName + "-sorted", "sorted", sortedQueueAttributes); + + Map<String, Object> priorityQueueAttributes = new HashMap<String, Object>(); + priorityQueueAttributes.putAll(attributes); + priorityQueueAttributes.put(Queue.PRIORITIES, 10); + createQueue(queueName + "-priority", "priority", priorityQueueAttributes); + + Map<String, Object> lvqQueueAttributes = new HashMap<String, Object>(); + lvqQueueAttributes.putAll(attributes); + lvqQueueAttributes.put(Queue.LVQ_KEY, "LVQ"); + createQueue(queueName + "-lvq", "lvq", lvqQueueAttributes); + + Map<String, Object> hostDetails = getJsonAsSingletonList("/rest/virtualhost/test"); + + @SuppressWarnings("unchecked") + List<Map<String, Object>> queues = (List<Map<String, Object>>) hostDetails.get(VirtualHostRestTest.VIRTUALHOST_QUEUES_ATTRIBUTE); + Map<String, Object> standardQueue = find(Queue.NAME, queueName + "-standard" , queues); + Map<String, Object> sortedQueue = find(Queue.NAME, queueName + "-sorted" , queues); + Map<String, Object> priorityQueue = find(Queue.NAME, queueName + "-priority" , queues); + Map<String, Object> lvqQueue = find(Queue.NAME, queueName + "-lvq" , queues); + + attributes.put(Queue.DURABLE, Boolean.TRUE); + Asserts.assertQueue(queueName + "-standard", "standard", standardQueue, attributes); + Asserts.assertQueue(queueName + "-sorted", "sorted", sortedQueue, attributes); + Asserts.assertQueue(queueName + "-priority", "priority", priorityQueue, attributes); + Asserts.assertQueue(queueName + "-lvq", "lvq", lvqQueue, attributes); + + assertEquals("Unexpected sorted key attribute", "sortme", sortedQueue.get(Queue.SORT_KEY)); + assertEquals("Unexpected lvq key attribute", "LVQ", lvqQueue.get(Queue.LVQ_KEY)); + assertEquals("Unexpected priorities key attribute", 10, priorityQueue.get(Queue.PRIORITIES)); + } + + @SuppressWarnings("unchecked") + public void testCreateQueueWithDLQEnabled() throws Exception + { + String queueName = getTestQueueName(); + + Map<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(AMQQueueFactory.X_QPID_DLQ_ENABLED, true); + + //verify the starting state + Map<String, Object> hostDetails = getJsonAsSingletonList("/rest/virtualhost/test"); + List<Map<String, Object>> queues = (List<Map<String, Object>>) hostDetails.get(VirtualHostRestTest.VIRTUALHOST_QUEUES_ATTRIBUTE); + List<Map<String, Object>> exchanges = (List<Map<String, Object>>) hostDetails.get(VirtualHostRestTest.VIRTUALHOST_EXCHANGES_ATTRIBUTE); + + assertNull("queue should not have already been present", find(Queue.NAME, queueName , queues)); + assertNull("queue should not have already been present", find(Queue.NAME, queueName + "_DLQ" , queues)); + assertNull("exchange should not have already been present", find(Exchange.NAME, queueName + "_DLE" , exchanges)); + + //create the queue + createQueue(queueName, "standard", attributes); + + //verify the new queue, as well as the DLQueue and DLExchange have been created + hostDetails = getJsonAsSingletonList("/rest/virtualhost/test"); + queues = (List<Map<String, Object>>) hostDetails.get(VirtualHostRestTest.VIRTUALHOST_QUEUES_ATTRIBUTE); + exchanges = (List<Map<String, Object>>) hostDetails.get(VirtualHostRestTest.VIRTUALHOST_EXCHANGES_ATTRIBUTE); + + Map<String, Object> queue = find(Queue.NAME, queueName , queues); + Map<String, Object> dlqQueue = find(Queue.NAME, queueName + "_DLQ" , queues); + Map<String, Object> dlExchange = find(Exchange.NAME, queueName + "_DLE" , exchanges); + assertNotNull("queue should not have been present", queue); + assertNotNull("queue should not have been present", dlqQueue); + assertNotNull("exchange should not have been present", dlExchange); + + //verify that the alternate exchange is set as expected on the new queue + Map<String, Object> queueAttributes = new HashMap<String, Object>(); + queueAttributes.put(Queue.ALTERNATE_EXCHANGE, queueName + "_DLE"); + + Asserts.assertQueue(queueName, "standard", queue, queueAttributes); + Asserts.assertQueue(queueName, "standard", queue, null); + } + + private void createExchange(String exchangeName, String exchangeType) throws IOException + { + HttpURLConnection connection = openManagementConection("/rest/exchange/test/" + exchangeName, "PUT"); + + Map<String, Object> queueData = new HashMap<String, Object>(); + queueData.put(Exchange.NAME, exchangeName); + queueData.put(Exchange.DURABLE, Boolean.TRUE); + queueData.put(Exchange.TYPE, exchangeType); + + writeJsonRequest(connection, queueData); + assertEquals("Unexpected response code", 201, connection.getResponseCode()); + + connection.disconnect(); + } + + private void createQueue(String queueName, String queueType, Map<String, Object> attributes) throws IOException, + JsonGenerationException, JsonMappingException + { + int responseCode = tryCreateQueue(queueName, queueType, attributes); + assertEquals("Unexpected response code", 201, responseCode); + } + + private int tryCreateQueue(String queueName, String queueType, Map<String, Object> attributes) throws IOException, + JsonGenerationException, JsonMappingException + { + HttpURLConnection connection = openManagementConection("/rest/queue/test/" + queueName, "PUT"); + + Map<String, Object> queueData = new HashMap<String, Object>(); + queueData.put(Queue.NAME, queueName); + queueData.put(Queue.DURABLE, Boolean.TRUE); + if (queueType != null) + { + queueData.put(Queue.TYPE, queueType); + } + if (attributes != null) + { + queueData.putAll(attributes); + } + + writeJsonRequest(connection, queueData); + int responseCode = connection.getResponseCode(); + connection.disconnect(); + return responseCode; + } + +} diff --git a/java/broker-plugins/management-jmx/MANIFEST.MF b/java/broker-plugins/management-jmx/MANIFEST.MF new file mode 100644 index 0000000000..b18ec1ace7 --- /dev/null +++ b/java/broker-plugins/management-jmx/MANIFEST.MF @@ -0,0 +1,66 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Qpid Broker-Plugins Management JMX +Bundle-SymbolicName: broker-plugins-management-jmx +Bundle-Description: JMX management plugin for Qpid. +Bundle-License: http://www.apache.org/licenses/LICENSE-2.0.txt +Bundle-DocURL: http://www.apache.org/ +Bundle-Version: 1.0.0 +Bundle-Activator: org.apache.qpid.server.jmx.JMXActivator +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Bundle-ClassPath: . +Bundle-ActivationPolicy: lazy +Import-Package: org.apache.qpid, + org.apache.qpid.framing, + org.apache.qpid.protocol, + org.apache.qpid.common, + org.apache.qpid.management.common.mbeans, + org.apache.qpid.management.common.mbeans.annotations, + org.apache.qpid.server.security.auth, + org.apache.qpid.server.security.auth.manager, + org.apache.qpid.server.security.auth.rmi, + org.apache.qpid.server.security.auth.sasl, + org.apache.qpid.server.binding, + org.apache.qpid.server.exchange, + org.apache.qpid.server.logging, + org.apache.qpid.server.logging.log4j, + org.apache.qpid.server.logging.actors, + org.apache.qpid.server.logging.messages, + org.apache.qpid.server.message, + org.apache.qpid.server.model, + org.apache.qpid.server.model.adapter, + org.apache.qpid.server.model.impl, + org.apache.qpid.server.configuration, + org.apache.qpid.server.configuration.plugins, + org.apache.qpid.server.connection, + org.apache.qpid.server.plugins, + org.apache.qpid.server.protocol, + org.apache.qpid.server.queue, + org.apache.qpid.server.registry, + org.apache.qpid.server.security, + org.apache.qpid.server.security.access, + org.apache.qpid.server.stats, + org.apache.qpid.server.virtualhost, + org.apache.qpid.util, + org.apache.commons.codec;version=1.3.0, + org.apache.commons.codec.binary;version=1.3.0, + org.apache.commons.configuration;version=1.0.0, + org.apache.commons.lang;version=1.0.0, + org.apache.commons.lang.builder;version=1.0.0, + org.apache.commons.lang.time;version=1.0.0, + org.apache.log4j;version=1.2.16, + org.codehaus.jackson;version=1.9.0, + org.codehaus.jackson.map;version=1.9.0, + javax.management.remote.rmi, + javax.management.remote, + javax.servlet, + javax.servlet.http, + javax.management;version=1.0.0, + javax.management.monitor;version=1.0.0, + javax.management.openmbean;version=1.0.0, + javax.security.auth.login;version=1.0.0, + javax.security.auth;version=1.0.0, + javax.rmi.ssl;version=1.0.0, + org.osgi.util.tracker;version=1.0.0, + org.osgi.framework;version=1.3 +Export-Package: org.apache.qpid.server.jmx;uses:="org.osgi.framework" diff --git a/java/broker-plugins/management-jmx/build.xml b/java/broker-plugins/management-jmx/build.xml new file mode 100644 index 0000000000..fa50b8467d --- /dev/null +++ b/java/broker-plugins/management-jmx/build.xml @@ -0,0 +1,47 @@ +<!-- + - 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. + --> +<project name="Qpid Broker-Plugins Management JMX" default="build"> + + <condition property="systests.optional.depends" value="bdbstore" else=""> + <or> + <and> + <contains string="${modules.opt}" substring="bdbstore"/> + <contains string="${profile}" substring="bdb"/> + </and> + <and> + <istrue value="${optional}"/> + <contains string="${profile}" substring="bdb"/> + </and> + </or> + </condition> + + <property name="module.depends" value="common broker management/common" /> + <property name="module.test.depends" value="systests test broker/test common/test management/common client ${systests.optional.depends}" /> + + <property name="module.manifest" value="MANIFEST.MF" /> + <property name="module.plugin" value="true" /> + <property name="module.genpom" value="true"/> + <property name="module.genpom.args" value="-Sqpid-common=provided -Sqpid-broker=provided -Sqpid-management-common=provided"/> + + <property name="broker-plugins-management-jmx.libs" value=""/> + + <import file="../../module.xml" /> + + <target name="bundle" depends="bundle-tasks" /> +</project> diff --git a/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/AMQManagedObject.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/AMQManagedObject.java new file mode 100644 index 0000000000..5c39a0c26a --- /dev/null +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/AMQManagedObject.java @@ -0,0 +1,84 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.jmx; + +import java.util.concurrent.atomic.AtomicLong; + +import javax.management.ListenerNotFoundException; +import javax.management.NotCompliantMBeanException; +import javax.management.NotificationBroadcaster; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationFilter; +import javax.management.NotificationListener; + +/** + * This class provides additional feature of Notification Broadcaster to the + * DefaultManagedObject. + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +public abstract class AMQManagedObject extends DefaultManagedObject + implements NotificationBroadcaster +{ + private final NotificationBroadcasterSupport _broadcaster = new NotificationBroadcasterSupport(); + + private AtomicLong _notificationSequenceNumber = new AtomicLong(); + + protected AMQManagedObject(Class<?> managementInterface, String typeName, ManagedObjectRegistry registry) + throws NotCompliantMBeanException + { + super(managementInterface, typeName, registry); + // CurrentActor will be defined as these objects are created during + // broker startup. + + } + + // notification broadcaster implementation + + public void addNotificationListener(NotificationListener listener, + NotificationFilter filter, + Object handback) + { + _broadcaster.addNotificationListener(listener, filter, handback); + } + + public void removeNotificationListener(NotificationListener listener) + throws ListenerNotFoundException + { + _broadcaster.removeNotificationListener(listener); + } + + + /** + * broadcaster support class + */ + protected NotificationBroadcasterSupport getBroadcaster() + { + return _broadcaster; + } + + protected long incrementAndGetSequenceNumber() + { + return _notificationSequenceNumber.incrementAndGet(); + } + + +} diff --git a/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/DefaultManagedObject.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/DefaultManagedObject.java new file mode 100644 index 0000000000..4446f96802 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/DefaultManagedObject.java @@ -0,0 +1,189 @@ +/* + * + * 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.jmx; + +import org.apache.log4j.Logger; + +import javax.management.JMException; +import javax.management.MBeanInfo; +import javax.management.MBeanNotificationInfo; +import javax.management.MalformedObjectNameException; +import javax.management.NotCompliantMBeanException; +import javax.management.ObjectName; +import javax.management.StandardMBean; + +/** + * Provides implementation of the boilerplate ManagedObject interface. Most managed objects should find it useful + * to extend this class rather than implementing ManagedObject from scratch. + * + */ +public abstract class DefaultManagedObject extends StandardMBean implements ManagedObject +{ + private static final Logger LOGGER = Logger.getLogger(DefaultManagedObject.class); + + private final Class<?> _managementInterface; + + private final String _typeName; + + private final MBeanInfo _mbeanInfo; + + private ManagedObjectRegistry _registry; + + protected DefaultManagedObject(Class<?> managementInterface, String typeName, ManagedObjectRegistry registry) + throws NotCompliantMBeanException + { + super(managementInterface); + _registry = registry; + _managementInterface = managementInterface; + _typeName = typeName; + _mbeanInfo = buildMBeanInfo(); + } + + public ManagedObjectRegistry getRegistry() + { + return _registry; + } + + @Override + public MBeanInfo getMBeanInfo() + { + return _mbeanInfo; + } + + public String getType() + { + return _typeName; + } + + public Class<?> getManagementInterface() + { + return _managementInterface; + } + + public abstract ManagedObject getParentObject(); + + + public void register() throws JMException + { + _registry.registerObject(this); + } + + public void unregister() throws JMException + { + try + { + if(_registry != null) + { + _registry.unregisterObject(this); + } + } + finally + { + _registry = null; + } + } + + public String toString() + { + return getObjectInstanceName() + "[" + getType() + "]"; + } + + /** + * Created the ObjectName as per the JMX Specs + * @return ObjectName + * @throws javax.management.MalformedObjectNameException + */ + public ObjectName getObjectName() throws MalformedObjectNameException + { + String name = getObjectInstanceName(); + StringBuffer objectName = new StringBuffer(ManagedObject.DOMAIN); + + objectName.append(":type="); + objectName.append(getHierarchicalType(this)); + + objectName.append(","); + objectName.append(getHierarchicalName(this)); + objectName.append("name=").append(name); + + return new ObjectName(objectName.toString()); + } + + protected ObjectName getObjectNameForSingleInstanceMBean() throws MalformedObjectNameException + { + StringBuffer objectName = new StringBuffer(ManagedObject.DOMAIN); + + objectName.append(":type="); + objectName.append(getHierarchicalType(this)); + + String hierarchyName = getHierarchicalName(this); + if (hierarchyName != null) + { + objectName.append(","); + objectName.append(hierarchyName.substring(0, hierarchyName.lastIndexOf(","))); + } + + return new ObjectName(objectName.toString()); + } + + protected String getHierarchicalType(ManagedObject obj) + { + if (obj.getParentObject() != null) + { + String parentType = getHierarchicalType(obj.getParentObject()).toString(); + return parentType + "." + obj.getType(); + } + else + { + return obj.getType(); + } + } + + protected String getHierarchicalName(ManagedObject obj) + { + if (obj.getParentObject() != null) + { + String parentName = obj.getParentObject().getType() + "=" + + obj.getParentObject().getObjectInstanceName() + ","+ + getHierarchicalName(obj.getParentObject()); + + return parentName; + } + else + { + return ""; + } + } + + private MBeanInfo buildMBeanInfo() throws NotCompliantMBeanException + { + return new MBeanInfo(this.getClass().getName(), + MBeanIntrospector.getMBeanDescription(this.getClass()), + MBeanIntrospector.getMBeanAttributesInfo(getManagementInterface()), + MBeanIntrospector.getMBeanConstructorsInfo(this.getClass()), + MBeanIntrospector.getMBeanOperationsInfo(getManagementInterface()), + this.getNotificationInfo()); + } + + public MBeanNotificationInfo[] getNotificationInfo() + { + return null; + } +} diff --git a/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXActivator.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXActivator.java new file mode 100644 index 0000000000..c588b40de7 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXActivator.java @@ -0,0 +1,136 @@ +/* + * + * 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.jmx; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; + +public class JMXActivator implements BundleActivator +{ + private static final Logger LOGGER = Logger.getLogger(JMXActivator.class); + + private String _bundleName; + private JMXService _jmxService; + + private List<ServiceRegistration> _registeredServices; + + + public void start(final BundleContext ctx) throws Exception + { + boolean jmxManagementEnabled = ApplicationRegistry.getInstance().getConfiguration().getJMXManagementEnabled(); + + if (jmxManagementEnabled) + { + _jmxService = new JMXService(); + startJmsService(_jmxService); + + _bundleName = ctx.getBundle().getSymbolicName(); + + _registeredServices = registerServices(ctx); + } + else + { + LOGGER.debug("Skipping registration of JMX plugin as JMX Management disabled in config. "); + } + } + + public void stop(final BundleContext bundleContext) throws Exception + { + try + { + if (_jmxService != null) + { + if (LOGGER.isInfoEnabled()) + { + LOGGER.info("Stopping jmx plugin: " + _bundleName); + } + _jmxService.close(); + } + + if (_registeredServices != null) + { + unregisterServices(); + } + } + finally + { + _jmxService = null; + _registeredServices = null; + } + } + + + private List<ServiceRegistration> registerServices(BundleContext ctx) + { + if (LOGGER.isInfoEnabled()) + { + LOGGER.info("Registering jmx plugin: " + _bundleName); + } + + List<ServiceRegistration> serviceRegistrations = new ArrayList<ServiceRegistration>(); + + ServiceRegistration jmxServiceRegistration = ctx.registerService(JMXService.class.getName(), _jmxService, null); + ServiceRegistration jmxConfigFactoryRegistration = ctx.registerService(ConfigurationPluginFactory.class.getName(), JMXConfiguration.FACTORY, null); + + serviceRegistrations.add(jmxServiceRegistration); + serviceRegistrations.add(jmxConfigFactoryRegistration); + return serviceRegistrations; + } + + private void startJmsService(JMXService jmxService) throws Exception + { + if (LOGGER.isInfoEnabled()) + { + LOGGER.info("Starting JMX service"); + } + boolean startedSuccessfully = false; + try + { + jmxService.start(); + startedSuccessfully = true; + } + finally + { + if (!startedSuccessfully) + { + LOGGER.error("JMX failed to start normally, closing service"); + jmxService.close(); + } + } + } + + private void unregisterServices() + { + for (Iterator<ServiceRegistration> iterator = _registeredServices.iterator(); iterator.hasNext();) + { + ServiceRegistration service = iterator.next(); + service.unregister(); + } + } +} diff --git a/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXConfiguration.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXConfiguration.java new file mode 100644 index 0000000000..dc9a712f90 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXConfiguration.java @@ -0,0 +1,76 @@ +/* + * + * 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.jmx; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory; + +import java.util.Arrays; +import java.util.List; + +public class JMXConfiguration extends ConfigurationPlugin +{ + CompositeConfiguration _finalConfig; + + public static final ConfigurationPluginFactory FACTORY = new ConfigurationPluginFactory() + { + public ConfigurationPlugin newInstance(String path, Configuration config) throws ConfigurationException + { + ConfigurationPlugin instance = new JMXConfiguration(); + instance.setConfiguration(path, config); + return instance; + } + + public List<String> getParentPaths() + { + return Arrays.asList("jmx"); + } + }; + + public String[] getElementsProcessed() + { + return new String[] { "" }; + } + + public Configuration getConfiguration() + { + return _finalConfig; + } + + + @Override + public void validateConfiguration() throws ConfigurationException + { + // Valid Configuration either has xml links to new files + _finalConfig = new CompositeConfiguration(getConfig()); + List subFiles = getConfig().getList("xml[@fileName]"); + for (Object subFile : subFiles) + { + _finalConfig.addConfiguration(new XMLConfiguration((String) subFile)); + } + + } + +} diff --git a/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagedObjectRegistry.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagedObjectRegistry.java new file mode 100644 index 0000000000..0648235077 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagedObjectRegistry.java @@ -0,0 +1,499 @@ +/* + * + * 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.jmx; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.ManagementConsoleMessages; + +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; + +import org.apache.qpid.server.security.auth.rmi.RMIPasswordAuthenticator; +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; + +import javax.management.JMException; +import javax.management.MBeanServer; +import javax.management.MBeanServerFactory; +import javax.management.Notification; +import javax.management.NotificationFilterSupport; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.remote.JMXConnectionNotification; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXServiceURL; +import javax.management.remote.MBeanServerForwarder; +import javax.management.remote.rmi.RMIConnection; +import javax.management.remote.rmi.RMIConnectorServer; +import javax.management.remote.rmi.RMIJRMPServerImpl; +import javax.management.remote.rmi.RMIServerImpl; +import javax.rmi.ssl.SslRMIClientSocketFactory; +import javax.rmi.ssl.SslRMIServerSocketFactory; +import javax.security.auth.Subject; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.lang.reflect.Proxy; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; +import java.rmi.AlreadyBoundException; +import java.rmi.NoSuchObjectException; +import java.rmi.NotBoundException; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.rmi.server.RMIClientSocketFactory; +import java.rmi.server.RMIServerSocketFactory; +import java.rmi.server.UnicastRemoteObject; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * This class starts up an MBeanserver. If out of the box agent has been enabled then there are no + * security features implemented like user authentication and authorisation. + */ +public class JMXManagedObjectRegistry implements ManagedObjectRegistry +{ + private static final Logger _log = Logger.getLogger(JMXManagedObjectRegistry.class); + + private final MBeanServer _mbeanServer; + private JMXConnectorServer _cs; + private Registry _rmiRegistry; + private boolean _useCustomSocketFactory; + + private final int _jmxPortRegistryServer; + private final int _jmxPortConnectorServer; + + public JMXManagedObjectRegistry() throws AMQException + { + _log.info("Initialising managed object registry using platform MBean server"); + IApplicationRegistry appRegistry = ApplicationRegistry.getInstance(); + + // Retrieve the config parameters + _useCustomSocketFactory = appRegistry.getConfiguration().getUseCustomRMISocketFactory(); + boolean platformServer = appRegistry.getConfiguration().getPlatformMbeanserver(); + + _mbeanServer = + platformServer ? ManagementFactory.getPlatformMBeanServer() + : MBeanServerFactory.createMBeanServer(ManagedObject.DOMAIN); + + _jmxPortRegistryServer = appRegistry.getConfiguration().getJMXPortRegistryServer(); + _jmxPortConnectorServer = appRegistry.getConfiguration().getJMXConnectorServerPort(); + + } + + public void start() throws IOException, ConfigurationException + { + + CurrentActor.get().message(ManagementConsoleMessages.STARTUP()); + + //check if system properties are set to use the JVM's out-of-the-box JMXAgent + if (areOutOfTheBoxJMXOptionsSet()) + { + CurrentActor.get().message(ManagementConsoleMessages.READY(true)); + return; + } + + IApplicationRegistry appRegistry = ApplicationRegistry.getInstance(); + + + //Socket factories for the RMIConnectorServer, either default or SLL depending on configuration + RMIClientSocketFactory csf; + RMIServerSocketFactory ssf; + + //check ssl enabled option in config, default to true if option is not set + boolean sslEnabled = appRegistry.getConfiguration().getManagementSSLEnabled(); + + if (sslEnabled) + { + //set the SSL related system properties used by the SSL RMI socket factories to the values + //given in the configuration file, unless command line settings have already been specified + String keyStorePath; + + if(System.getProperty("javax.net.ssl.keyStore") != null) + { + keyStorePath = System.getProperty("javax.net.ssl.keyStore"); + } + else + { + keyStorePath = appRegistry.getConfiguration().getManagementKeyStorePath(); + } + + //check the keystore path value is valid + if (keyStorePath == null) + { + throw new ConfigurationException("JMX management SSL keystore path not defined, " + + "unable to start SSL protected JMX ConnectorServer"); + } + else + { + //ensure the system property is set + System.setProperty("javax.net.ssl.keyStore", keyStorePath); + + //check the file is usable + File ksf = new File(keyStorePath); + + if (!ksf.exists()) + { + throw new FileNotFoundException("Cannot find JMX management SSL keystore file: " + ksf); + } + if (!ksf.canRead()) + { + throw new FileNotFoundException("Cannot read JMX management SSL keystore file: " + + ksf + ". Check permissions."); + } + + CurrentActor.get().message(ManagementConsoleMessages.SSL_KEYSTORE(ksf.getAbsolutePath())); + } + + //check the key store password is set + if (System.getProperty("javax.net.ssl.keyStorePassword") == null) + { + + if (appRegistry.getConfiguration().getManagementKeyStorePassword() == null) + { + throw new ConfigurationException("JMX management SSL keystore password not defined, " + + "unable to start requested SSL protected JMX server"); + } + else + { + System.setProperty("javax.net.ssl.keyStorePassword", + appRegistry.getConfiguration().getManagementKeyStorePassword()); + } + } + + //create the SSL RMI socket factories + csf = new SslRMIClientSocketFactory(); + ssf = new SslRMIServerSocketFactory(); + } + else + { + //Do not specify any specific RMI socket factories, resulting in use of the defaults. + csf = null; + ssf = null; + } + + //add a JMXAuthenticator implementation the env map to authenticate the RMI based JMX connector server + RMIPasswordAuthenticator rmipa = new RMIPasswordAuthenticator(new InetSocketAddress(_jmxPortRegistryServer)); + HashMap<String,Object> env = new HashMap<String,Object>(); + env.put(JMXConnectorServer.AUTHENTICATOR, rmipa); + + /* + * Start a RMI registry on the management port, to hold the JMX RMI ConnectorServer stub. + * Using custom socket factory to prevent anyone (including us unfortunately) binding to the registry using RMI. + * As a result, only binds made using the object reference will succeed, thus securing it from external change. + */ + System.setProperty("java.rmi.server.randomIDs", "true"); + if(_useCustomSocketFactory) + { + _rmiRegistry = LocateRegistry.createRegistry(_jmxPortRegistryServer, null, new CustomRMIServerSocketFactory()); + } + else + { + _rmiRegistry = LocateRegistry.createRegistry(_jmxPortRegistryServer, null, null); + } + + CurrentActor.get().message(ManagementConsoleMessages.LISTENING("RMI Registry", _jmxPortRegistryServer)); + + /* + * We must now create the RMI ConnectorServer manually, as the JMX Factory methods use RMI calls + * to bind the ConnectorServer to the registry, which will now fail as for security we have + * locked it from any RMI based modifications, including our own. Instead, we will manually bind + * the RMIConnectorServer stub to the registry using its object reference, which will still succeed. + * + * The registry is exported on the defined management port 'port'. We will export the RMIConnectorServer + * on 'port +1'. Use of these two well-defined ports will ease any navigation through firewall's. + */ + final Map<String, String> connectionIdUsernameMap = new ConcurrentHashMap<String, String>(); + final RMIServerImpl rmiConnectorServerStub = new RMIJRMPServerImpl(_jmxPortConnectorServer, csf, ssf, env) + { + + /** + * Override makeClient so we can cache the username of the client in a Map keyed by connectionId. + * ConnectionId is guaranteed to be unique per client connection, according to the JMS spec. + * An instance of NotificationListener (mapCleanupListener) will be responsible for removing these Map + * entries. + * + * @see javax.management.remote.rmi.RMIJRMPServerImpl#makeClient(String, javax.security.auth.Subject) + */ + @Override + protected RMIConnection makeClient(String connectionId, Subject subject) throws IOException + { + final RMIConnection makeClient = super.makeClient(connectionId, subject); + final UsernamePrincipal usernamePrincipalFromSubject = UsernamePrincipal.getUsernamePrincipalFromSubject(subject); + connectionIdUsernameMap.put(connectionId, usernamePrincipalFromSubject.getName()); + return makeClient; + } + }; + + // Create a Listener responsible for removing the map entries add by the #makeClient entry above. + final NotificationListener mapCleanupListener = new NotificationListener() + { + + public void handleNotification(Notification notification, Object handback) + { + final String connectionId = ((JMXConnectionNotification) notification).getConnectionId(); + connectionIdUsernameMap.remove(connectionId); + } + }; + + String localHost; + try + { + localHost = InetAddress.getLocalHost().getHostName(); + } + catch(UnknownHostException ex) + { + localHost="127.0.0.1"; + } + final String hostname = localHost; + final JMXServiceURL externalUrl = new JMXServiceURL( + "service:jmx:rmi://"+hostname+":"+(_jmxPortConnectorServer)+"/jndi/rmi://"+hostname+":"+_jmxPortRegistryServer+"/jmxrmi"); + + final JMXServiceURL internalUrl = new JMXServiceURL("rmi", hostname, _jmxPortConnectorServer); + _cs = new RMIConnectorServer(internalUrl, env, rmiConnectorServerStub, _mbeanServer) + { + @Override + public synchronized void start() throws IOException + { + try + { + //manually bind the connector server to the registry at key 'jmxrmi', like the out-of-the-box agent + _rmiRegistry.bind("jmxrmi", rmiConnectorServerStub); + } + catch (AlreadyBoundException abe) + { + //key was already in use. shouldnt happen here as its a new registry, unbindable by normal means. + + //IOExceptions are the only checked type throwable by the method, wrap and rethrow + IOException ioe = new IOException(abe.getMessage()); + ioe.initCause(abe); + throw ioe; + } + + //now do the normal tasks + super.start(); + } + + @Override + public synchronized void stop() throws IOException + { + try + { + if (_rmiRegistry != null) + { + _rmiRegistry.unbind("jmxrmi"); + } + } + catch (NotBoundException nbe) + { + // TODO consider if we want to keep new logging + _log.error("Failed to unbind jmxrmi", nbe); + //ignore + } + + //now do the normal tasks + super.stop(); + } + + @Override + public JMXServiceURL getAddress() + { + //must return our pre-crafted url that includes the full details, inc JNDI details + return externalUrl; + } + + }; + + + //Add the custom invoker as an MBeanServerForwarder, and start the RMIConnectorServer. + MBeanServerForwarder mbsf = MBeanInvocationHandlerImpl.newProxyInstance(); + _cs.setMBeanServerForwarder(mbsf); + + + // Get the handler that is used by the above MBInvocationHandler Proxy. + // which is the MBeanInvocationHandlerImpl and so also a NotificationListener. + final NotificationListener invocationHandler = (NotificationListener) Proxy.getInvocationHandler(mbsf); + + // Install a notification listener on OPENED, CLOSED, and FAILED, + // passing the map of connection-ids to usernames as hand-back data. + final NotificationFilterSupport invocationHandlerFilter = new NotificationFilterSupport(); + invocationHandlerFilter.enableType(JMXConnectionNotification.OPENED); + invocationHandlerFilter.enableType(JMXConnectionNotification.CLOSED); + invocationHandlerFilter.enableType(JMXConnectionNotification.FAILED); + _cs.addNotificationListener(invocationHandler, invocationHandlerFilter, connectionIdUsernameMap); + + // Install a second notification listener on CLOSED AND FAILED only to remove the entry from the + // Map. Here we rely on the fact that JMX will call the listeners in the order in which they are + // installed. + final NotificationFilterSupport mapCleanupHandlerFilter = new NotificationFilterSupport(); + mapCleanupHandlerFilter.enableType(JMXConnectionNotification.CLOSED); + mapCleanupHandlerFilter.enableType(JMXConnectionNotification.FAILED); + _cs.addNotificationListener(mapCleanupListener, mapCleanupHandlerFilter, null); + + _cs.start(); + + String connectorServer = (sslEnabled ? "SSL " : "") + "JMX RMIConnectorServer"; + CurrentActor.get().message(ManagementConsoleMessages.LISTENING(connectorServer, _jmxPortConnectorServer)); + + CurrentActor.get().message(ManagementConsoleMessages.READY(false)); + } + + /* + * Custom RMIServerSocketFactory class, used to prevent updates to the RMI registry. + * Supplied to the registry at creation, this will prevent RMI-based operations on the + * registry such as attempting to bind a new object, thereby securing it from tampering. + * This is accomplished by always returning null when attempting to determine the address + * of the caller, thus ensuring the registry will refuse the attempt. Calls to bind etc + * made using the object reference will not be affected and continue to operate normally. + */ + + private static class CustomRMIServerSocketFactory implements RMIServerSocketFactory + { + + public ServerSocket createServerSocket(int port) throws IOException + { + return new NoLocalAddressServerSocket(port); + } + + private static class NoLocalAddressServerSocket extends ServerSocket + { + NoLocalAddressServerSocket(int port) throws IOException + { + super(port); + } + + @Override + public Socket accept() throws IOException + { + Socket s = new NoLocalAddressSocket(); + super.implAccept(s); + return s; + } + } + + private static class NoLocalAddressSocket extends Socket + { + @Override + public InetAddress getInetAddress() + { + return null; + } + } + } + + + public void registerObject(ManagedObject managedObject) throws JMException + { + _mbeanServer.registerMBean(managedObject, managedObject.getObjectName()); + } + + public void unregisterObject(ManagedObject managedObject) throws JMException + { + _mbeanServer.unregisterMBean(managedObject.getObjectName()); + } + + // checks if the system properties are set which enable the JVM's out-of-the-box JMXAgent. + private boolean areOutOfTheBoxJMXOptionsSet() + { + if (System.getProperty("com.sun.management.jmxremote") != null) + { + return true; + } + + if (System.getProperty("com.sun.management.jmxremote.port") != null) + { + return true; + } + + return false; + } + + //Stops the JMXConnectorServer and RMIRegistry, then unregisters any remaining MBeans from the MBeanServer + public void close() + { + _log.debug("close() called"); + + if (_cs != null) + { + // Stopping the JMX ConnectorServer + try + { + CurrentActor.get().message(ManagementConsoleMessages.SHUTTING_DOWN("JMX RMIConnectorServer", _cs.getAddress().getPort())); + _cs.stop(); + } + catch (IOException e) + { + _log.error("Exception while closing the JMX ConnectorServer: ", e); + } + } + + if (_rmiRegistry != null) + { + // Stopping the RMI registry + CurrentActor.get().message(ManagementConsoleMessages.SHUTTING_DOWN("RMI Registry", _jmxPortRegistryServer)); + try + { + boolean success = UnicastRemoteObject.unexportObject(_rmiRegistry, false); + if (!success) + { + _log.warn("Failed to unexport object " + _rmiRegistry); + } + } + catch (NoSuchObjectException e) + { + _log.error("Exception while closing the RMI Registry: ", e); + } + } + + //ObjectName query to gather all Qpid related MBeans + ObjectName mbeanNameQuery = null; + try + { + mbeanNameQuery = new ObjectName(ManagedObject.DOMAIN + ":*"); + } + catch (Exception e1) + { + _log.warn("Unable to generate MBean ObjectName query for close operation"); + } + + for (ObjectName name : _mbeanServer.queryNames(mbeanNameQuery, null)) + { + try + { + _mbeanServer.unregisterMBean(name); + } + catch (JMException e) + { + _log.error("Exception unregistering MBean '"+ name +"': " + e.getMessage()); + } + } + + CurrentActor.get().message(ManagementConsoleMessages.STOPPED()); + } + +} diff --git a/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXService.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXService.java new file mode 100644 index 0000000000..7a232d2584 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXService.java @@ -0,0 +1,193 @@ +/* + * + * 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.jmx; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.ServiceLoader; + +import javax.management.JMException; +import javax.management.StandardMBean; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.jmx.mbeans.LoggingManagementMBean; +import org.apache.qpid.server.jmx.mbeans.UserManagementMBean; +import org.apache.qpid.server.jmx.mbeans.ConfigurationManagementMBean; +import org.apache.qpid.server.jmx.mbeans.ServerInformationMBean; +import org.apache.qpid.server.jmx.mbeans.Shutdown; +import org.apache.qpid.server.jmx.mbeans.VirtualHostMBean; +import org.apache.qpid.server.logging.log4j.LoggingFacade; +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.PasswordCredentialManagingAuthenticationProvider; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.registry.ApplicationRegistry; + + +public class JMXService implements ConfigurationChangeListener +{ + private static final ClassLoader BUNDLE_CLASSLOADER = JMXService.class.getClassLoader(); + + private static final Logger LOGGER = Logger.getLogger(JMXService.class); + + private final Broker _broker; + private final JMXManagedObjectRegistry _objectRegistry; + private final Shutdown _shutdown; + private final ServerInformationMBean _serverInfo; + private final ConfigurationManagementMBean _configManagement; + private final LoggingManagementMBean _loggingManagement; + + private final Map<ConfiguredObject, AMQManagedObject> _children = new HashMap<ConfiguredObject, AMQManagedObject>(); + + public JMXService() throws AMQException, JMException + { + _broker = ApplicationRegistry.getInstance().getBroker(); + _objectRegistry = new JMXManagedObjectRegistry(); + + _broker.addChangeListener(this); + synchronized (_children) + { + for(VirtualHost virtualHost : _broker.getVirtualHosts()) + { + if(!_children.containsKey(virtualHost)) + { + _children.put(virtualHost, new VirtualHostMBean(virtualHost, _objectRegistry)); + } + } + } + _shutdown = new Shutdown(_objectRegistry); + _serverInfo = new ServerInformationMBean(_objectRegistry, _broker); + _configManagement = new ConfigurationManagementMBean(_objectRegistry); + _loggingManagement = new LoggingManagementMBean(LoggingFacade.getCurrentInstance(), _objectRegistry); + } + + public void start() throws IOException, ConfigurationException + { + _objectRegistry.start(); + } + + public void close() + { + _broker.removeChangeListener(this); + + _objectRegistry.close(); + } + + public void stateChanged(ConfiguredObject object, State oldState, State newState) + { + + } + + public void childAdded(ConfiguredObject object, ConfiguredObject child) + { + synchronized (_children) + { + try + { + AMQManagedObject mbean; + if(child instanceof VirtualHost) + { + VirtualHost vhostChild = (VirtualHost)child; + mbean = new VirtualHostMBean(vhostChild, _objectRegistry); + } + else if(child instanceof PasswordCredentialManagingAuthenticationProvider) + { + mbean = new UserManagementMBean((PasswordCredentialManagingAuthenticationProvider) child, _objectRegistry); + } + else + { + mbean = null; + } + + if (mbean != null) + { + createAdditionalMBeansFromProviders(child, mbean); + } + } + catch(JMException e) + { + LOGGER.error("Error creating mbean", e); + // TODO - Implement error reporting on mbean creation + } + } + } + + + public void childRemoved(ConfiguredObject object, ConfiguredObject child) + { + // TODO - implement vhost removal (possibly just removing the instanceof check below) + + synchronized (_children) + { + if(child instanceof PasswordCredentialManagingAuthenticationProvider) + { + AMQManagedObject mbean = _children.remove(child); + if(mbean != null) + { + try + { + mbean.unregister(); + } + catch(JMException e) + { + LOGGER.error("Error creating mbean", e); + //TODO - report error on removing child MBean + } + } + } + + } + } + + private void createAdditionalMBeansFromProviders(ConfiguredObject child, AMQManagedObject mbean) throws JMException + { + _children.put(child, mbean); + + for (Iterator<MBeanProvider> iterator = getMBeanProviderIterator(); iterator.hasNext();) + { + MBeanProvider provider = iterator.next(); + LOGGER.debug("Consulting mbean provider : " + provider + " for child : " + child); + if (provider.isChildManageableByMBean(child)) + { + LOGGER.debug("Provider will create mbean "); + StandardMBean bean = provider.createMBean(child, mbean); + // TODO track the mbeans that have been created on behalf of a child in a map, then + // if the child is ever removed, destroy these beans too. + } + } + } + + /** + * Finds all classes implementing the {@link MBeanProvider} interface. This will find + * <b>only</b> those classes which are visible to the classloader of this OSGI bundle. + */ + private Iterator<MBeanProvider> getMBeanProviderIterator() + { + return ServiceLoader.load(MBeanProvider.class, BUNDLE_CLASSLOADER).iterator(); + } +} diff --git a/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/MBeanIntrospector.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/MBeanIntrospector.java new file mode 100644 index 0000000000..79ddc8cfc0 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/MBeanIntrospector.java @@ -0,0 +1,400 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.jmx; + +import org.apache.qpid.management.common.mbeans.annotations.MBeanAttribute; +import org.apache.qpid.management.common.mbeans.annotations.MBeanConstructor; +import org.apache.qpid.management.common.mbeans.annotations.MBeanDescription; +import org.apache.qpid.management.common.mbeans.annotations.MBeanOperation; +import org.apache.qpid.management.common.mbeans.annotations.MBeanOperationParameter; + +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanConstructorInfo; +import javax.management.MBeanOperationInfo; +import javax.management.MBeanParameterInfo; +import javax.management.NotCompliantMBeanException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +/** + * This class is a utility class to introspect the MBean class and the management + * interface class for various purposes. + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +class MBeanIntrospector +{ + + private static final String _defaultAttributeDescription = "Management attribute"; + private static final String _defaultOerationDescription = "Management operation"; + private static final String _defaultConstructorDescription = "MBean constructor"; + private static final String _defaultMbeanDescription = "Management interface of the MBean"; + + private MBeanIntrospector() + { + } + + /** + * Introspects the management interface class for MBean attributes. + * @param interfaceClass + * @return MBeanAttributeInfo[] + * @throws javax.management.NotCompliantMBeanException + */ + static MBeanAttributeInfo[] getMBeanAttributesInfo(Class interfaceClass) + throws NotCompliantMBeanException + { + List<MBeanAttributeInfo> attributesList = new ArrayList<MBeanAttributeInfo>(); + + /** + * Using reflection, all methods of the managemetn interface will be analysed, + * and MBeanInfo will be created. + */ + for (Method method : interfaceClass.getMethods()) + { + String name = method.getName(); + Class<?> resultType = method.getReturnType(); + MBeanAttributeInfo attributeInfo = null; + + if (isAttributeGetterMethod(method)) + { + String desc = getAttributeDescription(method); + attributeInfo = new MBeanAttributeInfo(name.substring(3), + resultType.getName(), + desc, + true, + false, + false); + int index = getIndexIfAlreadyExists(attributeInfo, attributesList); + if (index == -1) + { + attributesList.add(attributeInfo); + } + else + { + attributeInfo = new MBeanAttributeInfo(name.substring(3), + resultType.getName(), + desc, + true, + true, + false); + attributesList.set(index, attributeInfo); + } + } + else if (isAttributeSetterMethod(method)) + { + String desc = getAttributeDescription(method); + attributeInfo = new MBeanAttributeInfo(name.substring(3), + method.getParameterTypes()[0].getName(), + desc, + false, + true, + false); + int index = getIndexIfAlreadyExists(attributeInfo, attributesList); + if (index == -1) + { + attributesList.add(attributeInfo); + } + else + { + attributeInfo = new MBeanAttributeInfo(name.substring(3), + method.getParameterTypes()[0].getName(), + desc, + true, + true, + false); + attributesList.set(index, attributeInfo); + } + } + else if (isAttributeBoolean(method)) + { + attributeInfo = new MBeanAttributeInfo(name.substring(2), + resultType.getName(), + getAttributeDescription(method), + true, + false, + true); + attributesList.add(attributeInfo); + } + } + + return attributesList.toArray(new MBeanAttributeInfo[0]); + } + + /** + * Introspects the management interface class for management operations. + * @param interfaceClass + * @return MBeanOperationInfo[] + */ + static MBeanOperationInfo[] getMBeanOperationsInfo(Class interfaceClass) + { + List<MBeanOperationInfo> operationsList = new ArrayList<MBeanOperationInfo>(); + + for (Method method : interfaceClass.getMethods()) + { + if (!isAttributeGetterMethod(method) && + !isAttributeSetterMethod(method) && + !isAttributeBoolean(method)) + { + operationsList.add(getOperationInfo(method)); + } + } + + return operationsList.toArray(new MBeanOperationInfo[0]); + } + + /** + * Checks if the method is an attribute getter method. + * @param method + * @return true if the method is an attribute getter method. + */ + private static boolean isAttributeGetterMethod(Method method) + { + if (!(method.getName().equals("get")) && + method.getName().startsWith("get") && + method.getParameterTypes().length == 0 && + !method.getReturnType().equals(void.class)) + { + return true; + } + + return false; + } + + /** + * Checks if the method is an attribute setter method. + * @param method + * @return true if the method is an attribute setter method. + */ + private static boolean isAttributeSetterMethod(Method method) + { + if (!(method.getName().equals("set")) && + method.getName().startsWith("set") && + method.getParameterTypes().length == 1 && + method.getReturnType().equals(void.class)) + { + return true; + } + + return false; + } + + /** + * Checks if the attribute is a boolean and the method is a isX kind og method. + * @param method + * @return true if the method is an attribute isX type of method + */ + private static boolean isAttributeBoolean(Method method) + { + if (!(method.getName().equals("is")) && + method.getName().startsWith("is") && + method.getParameterTypes().length == 0 && + method.getReturnType().equals(boolean.class)) + { + return true; + } + + return false; + } + + /** + * Helper method to retrieve the attribute index from the list of attributes. + * @param attribute + * @param list + * @return attribute index no. -1 if attribtue doesn't exist + * @throws javax.management.NotCompliantMBeanException + */ + private static int getIndexIfAlreadyExists(MBeanAttributeInfo attribute, + List<MBeanAttributeInfo> list) + throws NotCompliantMBeanException + { + String exceptionMsg = "Conflicting attribute methods for attribute " + attribute.getName(); + + for (MBeanAttributeInfo memberAttribute : list) + { + if (attribute.getName().equals(memberAttribute.getName())) + { + if (!attribute.getType().equals(memberAttribute.getType())) + { + throw new NotCompliantMBeanException(exceptionMsg); + } + if (attribute.isReadable() && memberAttribute.isReadable()) + { + if (attribute.isIs() != memberAttribute.isIs()) + { + throw new NotCompliantMBeanException(exceptionMsg); + } + } + + return list.indexOf(memberAttribute); + } + } + + return -1; + } + + /** + * Retrieves the attribute description from annotation + * @param attributeMethod + * @return attribute description + */ + private static String getAttributeDescription(Method attributeMethod) + { + MBeanAttribute anno = attributeMethod.getAnnotation(MBeanAttribute.class); + if (anno != null) + { + return anno.description(); + } + return _defaultAttributeDescription; + } + + /** + * Introspects the method to retrieve the operation information. + * @param operation + * @return MBeanOperationInfo + */ + private static MBeanOperationInfo getOperationInfo(Method operation) + { + MBeanOperationInfo operationInfo = null; + Class<?> returnType = operation.getReturnType(); + + MBeanParameterInfo[] paramsInfo = getParametersInfo(operation.getParameterAnnotations(), + operation.getParameterTypes()); + + String operationDesc = _defaultOerationDescription; + int impact = MBeanOperationInfo.UNKNOWN; + + if (operation.getAnnotation(MBeanOperation.class) != null) + { + operationDesc = operation.getAnnotation(MBeanOperation.class).description(); + impact = operation.getAnnotation(MBeanOperation.class).impact(); + } + operationInfo = new MBeanOperationInfo(operation.getName(), + operationDesc, + paramsInfo, + returnType.getName(), + impact); + + return operationInfo; + } + + /** + * Constructs the parameter info. + * @param paramsAnno + * @param paramTypes + * @return MBeanParameterInfo[] + */ + private static MBeanParameterInfo[] getParametersInfo(Annotation[][] paramsAnno, + Class<?>[] paramTypes) + { + int noOfParams = paramsAnno.length; + + MBeanParameterInfo[] paramsInfo = new MBeanParameterInfo[noOfParams]; + + for (int i = 0; i < noOfParams; i++) + { + MBeanParameterInfo paramInfo = null; + String type = paramTypes[i].getName(); + for (Annotation anno : paramsAnno[i]) + { + String name,desc; + if (MBeanOperationParameter.class.isInstance(anno)) + { + name = MBeanOperationParameter.class.cast(anno).name(); + desc = MBeanOperationParameter.class.cast(anno).description(); + paramInfo = new MBeanParameterInfo(name, type, desc); + } + } + + + if (paramInfo == null) + { + paramInfo = new MBeanParameterInfo("p " + (i + 1), type, "parameter " + (i + 1)); + } + if (paramInfo != null) + { + paramsInfo[i] = paramInfo; + } + } + + return paramsInfo; + } + + /** + * Introspects the MBean class for constructors + * @param implClass + * @return MBeanConstructorInfo[] + */ + static MBeanConstructorInfo[] getMBeanConstructorsInfo(Class implClass) + { + List<MBeanConstructorInfo> constructors = new ArrayList<MBeanConstructorInfo>(); + + for (Constructor cons : implClass.getConstructors()) + { + MBeanConstructorInfo constructorInfo = getMBeanConstructorInfo(cons); + if (constructorInfo != null) + { + constructors.add(constructorInfo); + } + } + + return constructors.toArray(new MBeanConstructorInfo[0]); + } + + /** + * Retrieves the constructor info from given constructor. + * @param cons + * @return MBeanConstructorInfo + */ + private static MBeanConstructorInfo getMBeanConstructorInfo(Constructor cons) + { + String desc = _defaultConstructorDescription; + Annotation anno = cons.getAnnotation(MBeanConstructor.class); + if (anno != null && MBeanConstructor.class.isInstance(anno)) + { + desc = MBeanConstructor.class.cast(anno).value(); + if(desc == null) + { + desc = _defaultConstructorDescription; + } + } + + return new MBeanConstructorInfo(cons.getName(), desc, null); + } + + /** + * Retrieves the description from the annotations of given class + * @param annotatedClass + * @return class description + */ + static String getMBeanDescription(Class annotatedClass) + { + Annotation anno = annotatedClass.getAnnotation(MBeanDescription.class); + if (anno != null && MBeanDescription.class.isInstance(anno)) + { + return MBeanDescription.class.cast(anno).value(); + } + return _defaultMbeanDescription; + } + +} diff --git a/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/MBeanInvocationHandlerImpl.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/MBeanInvocationHandlerImpl.java new file mode 100644 index 0000000000..49f06d5121 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/MBeanInvocationHandlerImpl.java @@ -0,0 +1,382 @@ +/* + * + * 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.jmx; + +import org.apache.log4j.Logger; + +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.ManagementActor; +import org.apache.qpid.server.logging.messages.ManagementConsoleMessages; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.security.access.Operation; + +import javax.management.Attribute; +import javax.management.JMException; +import javax.management.MBeanInfo; +import javax.management.MBeanOperationInfo; +import javax.management.MBeanServer; +import javax.management.Notification; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.remote.JMXConnectionNotification; +import javax.management.remote.JMXPrincipal; +import javax.management.remote.MBeanServerForwarder; +import javax.security.auth.Subject; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.util.Map; +import java.util.Set; + +/** + * This class can be used by the JMXConnectorServer as an InvocationHandler for the mbean operations. It delegates + * JMX access decisions to the SecurityPlugin. + */ +public class MBeanInvocationHandlerImpl implements InvocationHandler, NotificationListener +{ + private static final Logger _logger = Logger.getLogger(MBeanInvocationHandlerImpl.class); + + private final IApplicationRegistry _appRegistry = ApplicationRegistry.getInstance(); + private final static String DELEGATE = "JMImplementation:type=MBeanServerDelegate"; + private MBeanServer _mbs; + private final ManagementActor _logActor = new ManagementActor(_appRegistry.getRootMessageLogger()); + private final boolean _managementRightsInferAllAccess = + _appRegistry.getConfiguration().getManagementRightsInferAllAccess(); + + public static MBeanServerForwarder newProxyInstance() + { + final InvocationHandler handler = new MBeanInvocationHandlerImpl(); + final Class<?>[] interfaces = new Class[] { MBeanServerForwarder.class }; + + Object proxy = Proxy.newProxyInstance(MBeanServerForwarder.class.getClassLoader(), interfaces, handler); + return MBeanServerForwarder.class.cast(proxy); + } + + private boolean invokeDirectly(String methodName, Object[] args, Subject subject) + { + // Allow operations performed locally on behalf of the connector server itself + if (subject == null) + { + return true; + } + + if (args == null || DELEGATE.equals(args[0])) + { + return true; + } + + // Allow querying available object names and mbeans + if (methodName.equals("queryNames") || methodName.equals("queryMBeans")) + { + return true; + } + + if (args[0] instanceof ObjectName) + { + ObjectName mbean = (ObjectName) args[0]; + + if(!DefaultManagedObject.DOMAIN.equalsIgnoreCase(mbean.getDomain())) + { + return true; + } + } + + return false; + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable + { + String methodName = method.getName(); + + if (methodName.equals("getMBeanServer")) + { + return _mbs; + } + + if (methodName.equals("setMBeanServer")) + { + if (args[0] == null) + { + throw new IllegalArgumentException("Null MBeanServer"); + } + if (_mbs != null) + { + throw new IllegalArgumentException("MBeanServer object already initialized"); + } + _mbs = (MBeanServer) args[0]; + return null; + } + + // Restrict access to "createMBean" and "unregisterMBean" to any user + if (methodName.equals("createMBean") || methodName.equals("unregisterMBean")) + { + _logger.debug("User trying to create or unregister an MBean"); + throw new SecurityException("Access denied: " + methodName); + } + + // Retrieve Subject from current AccessControlContext + AccessControlContext acc = AccessController.getContext(); + Subject subject = Subject.getSubject(acc); + + try + { + if(invokeDirectly(methodName, args, subject)) + { + return method.invoke(_mbs, args); + } + + // Retrieve JMXPrincipal from Subject + Set<JMXPrincipal> principals = subject.getPrincipals(JMXPrincipal.class); + if (principals == null || principals.isEmpty()) + { + throw new SecurityException("Access denied: no JMX principal"); + } + + // Save the subject + SecurityManager.setThreadSubject(subject); + + // Get the component, type and impact, which may be null + String type = getType(method, args); + String vhost = getVirtualHost(method, args); + int impact = getImpact(method, args); + + // Get the security manager for the virtual host (if set) + SecurityManager security; + if (vhost == null) + { + security = _appRegistry.getSecurityManager(); + } + else + { + security = _appRegistry.getVirtualHostRegistry().getVirtualHost(vhost).getSecurityManager(); + } + + methodName = getMethodName(method, args); + if (isAccessMethod(methodName) || impact == MBeanOperationInfo.INFO) + { + // Check for read-only method invocation permission + if (!security.authoriseMethod(Operation.ACCESS, type, methodName)) + { + throw new SecurityException("Permission denied: Access " + methodName); + } + } + else + { + // Check for setting properties permission + if (!security.authoriseMethod(Operation.UPDATE, type, methodName)) + { + throw new SecurityException("Permission denied: Update " + methodName); + } + } + + boolean oldAccessChecksDisabled = false; + if(_managementRightsInferAllAccess) + { + oldAccessChecksDisabled = SecurityManager.setAccessChecksDisabled(true); + } + + try + { + return doInvokeWrappingWithManagementActor(method, args); + } + finally + { + if(_managementRightsInferAllAccess) + { + SecurityManager.setAccessChecksDisabled(oldAccessChecksDisabled); + } + } + } + catch (InvocationTargetException e) + { + throw e.getTargetException(); + } + } + + private Object doInvokeWrappingWithManagementActor(Method method, + Object[] args) throws IllegalAccessException, + InvocationTargetException + { + try + { + CurrentActor.set(_logActor); + return method.invoke(_mbs, args); + } + finally + { + CurrentActor.remove(); + } + } + + private String getType(Method method, Object[] args) + { + if (args[0] instanceof ObjectName) + { + ObjectName object = (ObjectName) args[0]; + String type = object.getKeyProperty("type"); + + return type; + } + return null; + } + + private String getVirtualHost(Method method, Object[] args) + { + if (args[0] instanceof ObjectName) + { + ObjectName object = (ObjectName) args[0]; + String vhost = object.getKeyProperty("VirtualHost"); + + if(vhost != null) + { + try + { + //if the name is quoted in the ObjectName, unquote it + vhost = ObjectName.unquote(vhost); + } + catch(IllegalArgumentException e) + { + //ignore, this just means the name is not quoted + //and can be left unchanged + } + } + + return vhost; + } + return null; + } + + private String getMethodName(Method method, Object[] args) + { + String methodName = method.getName(); + + // if arguments are set, try and work out real method name + if (args != null && args.length >= 1 && args[0] instanceof ObjectName) + { + if (methodName.equals("getAttribute")) + { + methodName = "get" + (String) args[1]; + } + else if (methodName.equals("setAttribute")) + { + methodName = "set" + ((Attribute) args[1]).getName(); + } + else if (methodName.equals("invoke")) + { + methodName = (String) args[1]; + } + } + + return methodName; + } + + private int getImpact(Method method, Object[] args) + { + //handle invocation of other methods on mbeans + if ((args[0] instanceof ObjectName) && (method.getName().equals("invoke"))) + { + //get invoked method name + String mbeanMethod = (args.length > 1) ? (String) args[1] : null; + if (mbeanMethod == null) + { + return -1; + } + + try + { + //Get the impact attribute + MBeanInfo mbeanInfo = _mbs.getMBeanInfo((ObjectName) args[0]); + if (mbeanInfo != null) + { + MBeanOperationInfo[] opInfos = mbeanInfo.getOperations(); + for (MBeanOperationInfo opInfo : opInfos) + { + if (opInfo.getName().equals(mbeanMethod)) + { + return opInfo.getImpact(); + } + } + } + } + catch (JMException ex) + { + _logger.error("Unable to determine mbean impact for method : " + mbeanMethod, ex); + } + } + + return -1; + } + + private boolean isAccessMethod(String methodName) + { + //handle standard get/query/is methods from MBeanServer + return (methodName.startsWith("query") || methodName.startsWith("get") || methodName.startsWith("is")); + } + + /** + * Receives notifications from the MBeanServer. + */ + public void handleNotification(final Notification notification, final Object handback) + { + assert notification instanceof JMXConnectionNotification; + + final String connectionId = ((JMXConnectionNotification) notification).getConnectionId(); + final String type = notification.getType(); + + if (_logger.isDebugEnabled()) + { + _logger.debug("Notification connectionId : " + connectionId + " type : " + type + + " Notification handback : " + handback); + } + + // Normally JMXManagedObjectRegistry provides a Map as handback data containing a map + // between connection id and username. + String user = null; + if (handback instanceof Map) + { + final Map<String, String> connectionIdUsernameMap = (Map<String, String>) handback; + user = connectionIdUsernameMap.get(connectionId); + } + + // If user is still null, fallback to an unordered list of Principals from the connection id. + if (user == null) + { + final String[] splitConnectionId = connectionId.split(" "); + user = splitConnectionId[1]; + } + + if (JMXConnectionNotification.OPENED.equals(type)) + { + _logActor.message(ManagementConsoleMessages.OPEN(user)); + } + else if (JMXConnectionNotification.CLOSED.equals(type) || + JMXConnectionNotification.FAILED.equals(type)) + { + _logActor.message(ManagementConsoleMessages.CLOSE(user)); + } + } +} + diff --git a/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/MBeanProvider.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/MBeanProvider.java new file mode 100644 index 0000000000..83909dbe72 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/MBeanProvider.java @@ -0,0 +1,52 @@ +/* + * + * 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.jmx; + +import java.util.ServiceLoader; + +import javax.management.JMException; +import javax.management.StandardMBean; + +import org.apache.qpid.server.model.ConfiguredObject; + +/** + * A provider of an mbean implementation. + * + * Provider implementations are advertised as services and loaded via {@link ServiceLoader}. + */ +public interface MBeanProvider +{ + /** + * Tests whether a <code>child</code> can be managed by the mbean + * provided by this provider. + */ + boolean isChildManageableByMBean(ConfiguredObject child); + + /** + * Creates a mbean for this child. This method should only be called if + * {@link #isChildManageableByMBean(ConfiguredObject)} has previously returned true. + * + * @return newly created mbean + */ + StandardMBean createMBean(ConfiguredObject child, StandardMBean parent) throws JMException; + +} diff --git a/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/ManagedObject.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/ManagedObject.java new file mode 100644 index 0000000000..40b778fd93 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/ManagedObject.java @@ -0,0 +1,57 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.jmx; + +import javax.management.JMException; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; + +/** + * This should be implemented by all Managable objects. + */ +public interface ManagedObject +{ + static final String DOMAIN = "org.apache.qpid"; + + /** + * @return the name that uniquely identifies this object instance. It must be + * unique only among objects of this type at this level in the hierarchy so + * the uniqueness should not be too difficult to ensure. + */ + String getObjectInstanceName(); + + String getType(); + + Class<?> getManagementInterface(); + + ManagedObject getParentObject(); + + void register() throws JMException; + + void unregister() throws JMException; + + /** + * Returns the ObjectName required for the mbeanserver registration. + * @return ObjectName + * @throws javax.management.MalformedObjectNameException + */ + ObjectName getObjectName() throws MalformedObjectNameException; +} diff --git a/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/ManagedObjectRegistry.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/ManagedObjectRegistry.java new file mode 100644 index 0000000000..2ae0ac7052 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/ManagedObjectRegistry.java @@ -0,0 +1,48 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.jmx; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.common.Closeable; + +import javax.management.JMException; +import java.io.IOException; + +/** + * Handles the registration (and unregistration and so on) of managed objects. + * + * Managed objects are responsible for exposting attributes, operations and notifications. They will expose + * these outside the JVM therefore it is important not to use implementation objects directly as managed objects. + * Instead, creating inner classes and exposing those is an effective way of exposing internal state in a + * controlled way. + * + * Although we do not explictly use them while targetting Java 5, the enhanced MXBean approach in Java 6 will + * be the obvious choice for managed objects. + * + */ +public interface ManagedObjectRegistry extends Closeable +{ + void start() throws IOException, ConfigurationException; + + void registerObject(ManagedObject managedObject) throws JMException; + + void unregisterObject(ManagedObject managedObject) throws JMException; +} diff --git a/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/AbstractStatisticsGatheringMBean.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/AbstractStatisticsGatheringMBean.java new file mode 100644 index 0000000000..6ab7db3629 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/AbstractStatisticsGatheringMBean.java @@ -0,0 +1,200 @@ +/* + * + * 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.jmx.mbeans; + +import javax.management.NotCompliantMBeanException; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.Connection; +import org.apache.qpid.server.jmx.AMQManagedObject; +import org.apache.qpid.server.jmx.ManagedObjectRegistry; +import org.apache.qpid.server.model.VirtualHost; + +abstract class AbstractStatisticsGatheringMBean<T extends ConfiguredObject> extends AMQManagedObject +{ + private long _lastStatUpdateTime; + private long _statUpdatePeriod = 5000L; + private long _lastMessagesReceived; + private long _lastMessagesSent; + private long _lastBytesReceived; + private long _lastBytesSent; + private double _messageReceivedRate; + private double _messageSentRate; + private double _bytesReceivedRate; + private double _bytesSentRate; + private double _peakMessageReceivedRate; + private double _peakMessageSentRate; + private double _peakBytesReceivedRate; + private double _peakBytesSentRate; + private final T _configuredObject; + + protected AbstractStatisticsGatheringMBean(Class<?> managementInterface, + String typeName, + ManagedObjectRegistry registry, + T object) throws NotCompliantMBeanException + { + super(managementInterface, typeName, registry); + _configuredObject = object; + initStats(); + } + + protected void initStats() + { + _lastStatUpdateTime = System.currentTimeMillis(); + } + + protected synchronized void updateStats() + { + long time = System.currentTimeMillis(); + final long period = time - _lastStatUpdateTime; + if(period > _statUpdatePeriod) + { + long messagesReceived = getStatistic(VirtualHost.MESSAGES_IN); + long messagesSent = getStatistic(VirtualHost.MESSAGES_OUT); + long bytesReceived = getStatistic(VirtualHost.BYTES_IN); + long bytesSent = getStatistic(VirtualHost.BYTES_OUT); + + double messageReceivedRate = (double)(messagesReceived - _lastMessagesReceived) / (double)period; + double messageSentRate = (double)(messagesSent - _lastMessagesSent) / (double)period; + double bytesReceivedRate = (double)(bytesReceived - _lastBytesReceived) / (double)period; + double bytesSentRate = (double)(bytesSent - _lastBytesSent) / (double)period; + + _lastMessagesReceived = messagesReceived; + _lastMessagesSent = messagesSent; + _lastBytesReceived = bytesReceived; + _lastBytesSent = bytesSent; + + _messageReceivedRate = messageReceivedRate; + _messageSentRate = messageSentRate; + _bytesReceivedRate = bytesReceivedRate; + _bytesSentRate = bytesSentRate; + + if(messageReceivedRate > _peakMessageReceivedRate) + { + _peakMessageReceivedRate = messageReceivedRate; + } + + if(messageSentRate > _peakMessageSentRate) + { + _peakMessageSentRate = messageSentRate; + } + + if(bytesReceivedRate > _peakBytesReceivedRate) + { + _peakBytesReceivedRate = bytesReceivedRate; + } + + if(bytesSentRate > _peakBytesSentRate) + { + _peakBytesSentRate = bytesSentRate; + } + + } + } + + private long getStatistic(String name) + { + return (Long) getConfiguredObject().getStatistics().getStatistic(name); + } + + public synchronized void resetStatistics() throws Exception + { + updateStats(); + //TODO - implement resetStatistics() + } + + public synchronized double getPeakMessageDeliveryRate() + { + updateStats(); + return _peakMessageSentRate; + } + + public synchronized double getPeakDataDeliveryRate() + { + updateStats(); + return _peakBytesSentRate; + } + + public synchronized double getMessageDeliveryRate() + { + updateStats(); + return _messageSentRate; + } + + public synchronized double getDataDeliveryRate() + { + updateStats(); + return _bytesSentRate; + } + + public synchronized long getTotalMessagesDelivered() + { + updateStats(); + return getStatistic(Connection.MESSAGES_OUT); + } + + public synchronized long getTotalDataDelivered() + { + updateStats(); + return getStatistic(Connection.BYTES_OUT); + } + + protected final T getConfiguredObject() + { + return _configuredObject; + } + + public synchronized double getPeakMessageReceiptRate() + { + updateStats(); + return _peakMessageReceivedRate; + } + + public synchronized double getPeakDataReceiptRate() + { + updateStats(); + return _peakBytesReceivedRate; + } + + public synchronized double getMessageReceiptRate() + { + updateStats(); + return _messageReceivedRate; + } + + public synchronized double getDataReceiptRate() + { + updateStats(); + return _bytesReceivedRate; + } + + public synchronized long getTotalMessagesReceived() + { + updateStats(); + return getStatistic(Connection.MESSAGES_IN); + } + + public synchronized long getTotalDataReceived() + { + updateStats(); + return getStatistic(Connection.BYTES_IN); + } + +} diff --git a/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ConfigurationManagementMBean.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ConfigurationManagementMBean.java new file mode 100644 index 0000000000..beffb4eaa9 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ConfigurationManagementMBean.java @@ -0,0 +1,56 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.jmx.mbeans; + +import org.apache.qpid.management.common.mbeans.ConfigurationManagement; +import org.apache.qpid.server.jmx.AMQManagedObject; +import org.apache.qpid.server.jmx.ManagedObject; +import org.apache.qpid.server.jmx.ManagedObjectRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; + +import javax.management.JMException; +import javax.management.NotCompliantMBeanException; + +public class ConfigurationManagementMBean extends AMQManagedObject implements ConfigurationManagement +{ + + public ConfigurationManagementMBean(ManagedObjectRegistry registry) throws JMException + { + super(ConfigurationManagement.class, ConfigurationManagement.TYPE, registry); + register(); + } + + public String getObjectInstanceName() + { + return ConfigurationManagement.TYPE; + } + + public void reloadSecurityConfiguration() throws Exception + { + ApplicationRegistry.getInstance().getConfiguration().reparseConfigFileSecuritySections(); + } + + @Override + public ManagedObject getParentObject() + { + return null; + } +} diff --git a/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ConnectionMBean.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ConnectionMBean.java new file mode 100644 index 0000000000..d0c0d5e73f --- /dev/null +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ConnectionMBean.java @@ -0,0 +1,182 @@ +/* + * + * 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.jmx.mbeans; + +import java.io.IOException; +import java.util.Collection; +import java.util.Date; +import javax.management.JMException; +import javax.management.ObjectName; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; + +import org.apache.qpid.management.common.mbeans.ManagedConnection; +import org.apache.qpid.server.jmx.ManagedObject; +import org.apache.qpid.server.model.Connection; +import org.apache.qpid.server.model.Session; +import org.apache.qpid.server.model.Statistics; + +public class ConnectionMBean extends AbstractStatisticsGatheringMBean<Connection> implements ManagedConnection +{ + private static final OpenType[] CHANNEL_ATTRIBUTE_TYPES = + { SimpleType.INTEGER, SimpleType.BOOLEAN, SimpleType.STRING, SimpleType.INTEGER, SimpleType.BOOLEAN }; + private static final CompositeType CHANNEL_TYPE; + private static final TabularType CHANNELS_TYPE; + + static + { + try + { + CHANNEL_TYPE = new CompositeType("Channel", "Channel Details", COMPOSITE_ITEM_NAMES_DESC.toArray(new String[COMPOSITE_ITEM_NAMES_DESC.size()]), + COMPOSITE_ITEM_NAMES_DESC.toArray(new String[COMPOSITE_ITEM_NAMES_DESC.size()]), + CHANNEL_ATTRIBUTE_TYPES); + CHANNELS_TYPE = new TabularType("Channels", "Channels", CHANNEL_TYPE, (String[]) TABULAR_UNIQUE_INDEX.toArray(new String[TABULAR_UNIQUE_INDEX.size()])); + } + catch (JMException ex) + { + // This is not expected to ever occur. + throw new RuntimeException("Got JMException in static initializer.", ex); + } + } + + + private final VirtualHostMBean _virtualHostMBean; + + public ConnectionMBean(Connection conn, VirtualHostMBean virtualHostMBean) throws JMException + { + super(ManagedConnection.class, ManagedConnection.TYPE, virtualHostMBean.getRegistry(), conn); + _virtualHostMBean = virtualHostMBean; + register(); + } + + public String getObjectInstanceName() + { + return ObjectName.quote(getRemoteAddress()); + } + + @Override + public ManagedObject getParentObject() + { + return _virtualHostMBean; + } + + public String getClientId() + { + return (String) getConfiguredObject().getAttribute(Connection.CLIENT_ID); + } + + public String getAuthorizedId() + { + return (String) getConfiguredObject().getAttribute(Connection.PRINCIPAL); + } + + public String getVersion() + { + return (String) getConfiguredObject().getAttribute(Connection.CLIENT_VERSION); + } + + public String getRemoteAddress() + { + return (String) getConfiguredObject().getAttribute(Connection.REMOTE_ADDRESS); + } + + public Date getLastIoTime() + { + Long lastIo = (Long) getConfiguredObject().getStatistics().getStatistic(Connection.LAST_IO_TIME); + return new Date(lastIo); + } + + public Long getMaximumNumberOfChannels() + { + return (Long) getConfiguredObject().getAttribute(Connection.SESSION_COUNT_LIMIT); + } + + public TabularData channels() throws IOException, JMException + { + TabularDataSupport sessionTable = new TabularDataSupport(CHANNELS_TYPE); + Collection<Session> list = getConfiguredObject().getSessions(); + + for (Session session : list) + { + Statistics statistics = session.getStatistics(); + Long txnBegins = (Long) statistics.getStatistic(Session.LOCAL_TRANSACTION_BEGINS); + Integer channelId = (Integer) session.getAttribute(Session.CHANNEL_ID); + int unacknowledgedSize = ((Number) statistics.getStatistic(Session.UNACKNOWLEDGED_MESSAGES)).intValue(); + boolean blocked = (Boolean) session.getAttribute(Session.PRODUCER_FLOW_BLOCKED); + boolean isTransactional = (txnBegins>0l); + + Object[] itemValues = + { + channelId, + isTransactional, + null, // TODO - default queue (which is meaningless) + unacknowledgedSize, + blocked + }; + + CompositeData sessionData = new CompositeDataSupport(CHANNEL_TYPE, + COMPOSITE_ITEM_NAMES_DESC.toArray(new String[COMPOSITE_ITEM_NAMES_DESC.size()]), itemValues); + sessionTable.put(sessionData); + } + + return sessionTable; + } + + public void commitTransactions(int channelId) throws JMException + { + throw buildUnsupportedException(); + } + + public void rollbackTransactions(int channelId) throws JMException + { + throw buildUnsupportedException(); + } + + public void closeConnection() throws Exception + { + getConfiguredObject().delete(); + } + + public boolean isStatisticsEnabled() + { + return true; + } + + public void setStatisticsEnabled(boolean enabled) + { + updateStats(); + } + + private JMException buildUnsupportedException() throws JMException + { + String msg = "Operation not supported"; + JMException jmException = new JMException(msg); + jmException.initCause(new UnsupportedOperationException(msg)); + return jmException; + } +} diff --git a/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ExchangeMBean.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ExchangeMBean.java new file mode 100644 index 0000000000..56802d0403 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ExchangeMBean.java @@ -0,0 +1,313 @@ +/* + * + * 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.jmx.mbeans; + +import org.apache.qpid.management.common.mbeans.ManagedExchange; +import org.apache.qpid.server.jmx.AMQManagedObject; +import org.apache.qpid.server.jmx.ManagedObject; +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.Queue; +import org.apache.qpid.server.model.VirtualHost; + +import javax.management.JMException; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; +import javax.management.OperationsException; +import javax.management.openmbean.ArrayType; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ExchangeMBean extends AMQManagedObject implements ManagedExchange +{ + + public static final String FANOUT_EXCHANGE_TYPE = "fanout"; + public static final String HEADERS_EXCHANGE_TYPE = "headers"; + + private static final String[] TABULAR_UNIQUE_INDEX_ARRAY = + TABULAR_UNIQUE_INDEX.toArray(new String[TABULAR_UNIQUE_INDEX.size()]); + + private static final String[] COMPOSITE_ITEM_NAMES_ARRAY = + COMPOSITE_ITEM_NAMES.toArray(new String[COMPOSITE_ITEM_NAMES.size()]); + + private static final String[] COMPOSITE_ITEM_DESCRIPTIONS_ARRAY = + COMPOSITE_ITEM_DESCRIPTIONS.toArray(new String[COMPOSITE_ITEM_DESCRIPTIONS.size()]); + + private static final OpenType[] BINDING_ITEM_TYPES; + private static final CompositeType BINDING_DATA_TYPE; + private static final OpenType[] HEADERS_BINDING_ITEM_TYPES; + + + private static final CompositeType HEADERS_BINDING_DATA_TYPE; + + private static final String[] HEADERS_COMPOSITE_ITEM_NAMES_ARRAY = + HEADERS_COMPOSITE_ITEM_NAMES.toArray(new String[HEADERS_COMPOSITE_ITEM_NAMES.size()]); + + private static final String[] HEADERS_COMPOSITE_ITEM_DESCS_ARRAY = + HEADERS_COMPOSITE_ITEM_DESC.toArray(new String[HEADERS_COMPOSITE_ITEM_DESC.size()]); + private static final String[] HEADERS_TABULAR_UNIQUE_INDEX_ARRAY = + HEADERS_TABULAR_UNIQUE_INDEX.toArray(new String[HEADERS_TABULAR_UNIQUE_INDEX.size()]); + + static + { + try + { + BINDING_ITEM_TYPES = new OpenType[] {SimpleType.STRING, new ArrayType(1, SimpleType.STRING)}; + + BINDING_DATA_TYPE= new CompositeType("Exchange Binding", "Binding key and Queue names", + COMPOSITE_ITEM_NAMES_ARRAY, + COMPOSITE_ITEM_DESCRIPTIONS_ARRAY, + BINDING_ITEM_TYPES); + + HEADERS_BINDING_ITEM_TYPES = new OpenType[] {SimpleType.INTEGER, + SimpleType.STRING, + new ArrayType(1, SimpleType.STRING)}; + + HEADERS_BINDING_DATA_TYPE = new CompositeType("Exchange Binding", "Queue name and header bindings", + HEADERS_COMPOSITE_ITEM_NAMES_ARRAY, + HEADERS_COMPOSITE_ITEM_DESCS_ARRAY, + HEADERS_BINDING_ITEM_TYPES); + + + } + catch(OpenDataException e) + { + throw new RuntimeException("Unexpected Error creating ArrayType", e); + } + } + + + private final Exchange _exchange; + private final VirtualHostMBean _vhostMBean; + + protected ExchangeMBean(Exchange exchange, VirtualHostMBean virtualHostMBean) + throws JMException + { + super(ManagedExchange.class, ManagedExchange.TYPE, virtualHostMBean.getRegistry()); + _exchange = exchange; + _vhostMBean = virtualHostMBean; + + register(); + } + + public String getObjectInstanceName() + { + return ObjectName.quote(getName()); + } + + @Override + public ManagedObject getParentObject() + { + return _vhostMBean; + } + + public ObjectName getObjectName() throws MalformedObjectNameException + { + String objNameString = super.getObjectName().toString(); + objNameString = objNameString + ",ExchangeType=" + getExchangeType(); + return new ObjectName(objNameString); + } + + + public String getName() + { + return _exchange.getName(); + } + + public String getExchangeType() + { + return _exchange.getExchangeType(); + } + + public Integer getTicketNo() + { + return 0; + } + + public boolean isDurable() + { + return _exchange.isDurable(); + } + + public boolean isAutoDelete() + { + return _exchange.getLifetimePolicy() == LifetimePolicy.AUTO_DELETE; + } + + public TabularData bindings() throws IOException, JMException + { + if(HEADERS_EXCHANGE_TYPE.equals(_exchange.getExchangeType())) + { + return getHeadersBindings(_exchange.getBindings()); + } + else + { + return getNonHeadersBindings(_exchange.getBindings()); + } + } + + private TabularData getHeadersBindings(Collection<Binding> bindings) throws OpenDataException + { + TabularType bindinglistDataType = + new TabularType("Exchange Bindings", "List of exchange bindings for " + getName(), + HEADERS_BINDING_DATA_TYPE, + HEADERS_TABULAR_UNIQUE_INDEX_ARRAY); + + TabularDataSupport bindingList = new TabularDataSupport(bindinglistDataType); + int count = 1; + for (Binding binding : bindings) + { + + String queueName = binding.getParent(Queue.class).getName(); + + + Map<String,Object> headerMappings = binding.getArguments(); + + final List<String> mappingList = new ArrayList<String>(); + + if(headerMappings != null) + { + for(Map.Entry<String,Object> entry : headerMappings.entrySet()) + { + + mappingList.add(entry.getKey() + "=" + entry.getValue()); + } + } + + + Object[] bindingItemValues = {count++, queueName, mappingList.toArray(new String[0])}; + CompositeData bindingData = new CompositeDataSupport(HEADERS_BINDING_DATA_TYPE, + HEADERS_COMPOSITE_ITEM_NAMES_ARRAY, + bindingItemValues); + bindingList.put(bindingData); + } + + return bindingList; + + } + + private TabularData getNonHeadersBindings(Collection<Binding> bindings) throws OpenDataException + { + + TabularType bindinglistDataType = + new TabularType("Exchange Bindings", "Exchange Bindings for " + getName(), + BINDING_DATA_TYPE, + TABULAR_UNIQUE_INDEX_ARRAY); + + TabularDataSupport bindingList = new TabularDataSupport(bindinglistDataType); + + Map<String, List<String>> bindingMap = new HashMap<String, List<String>>(); + + for (Binding binding : bindings) + { + String key = FANOUT_EXCHANGE_TYPE.equals(_exchange.getExchangeType()) ? "*" : binding.getName(); + List<String> queueList = bindingMap.get(key); + if(queueList == null) + { + queueList = new ArrayList<String>(); + bindingMap.put(key, queueList); + } + queueList.add(binding.getParent(Queue.class).getName()); + + } + + for(Map.Entry<String, List<String>> entry : bindingMap.entrySet()) + { + Object[] bindingItemValues = {entry.getKey(), entry.getValue().toArray(new String[0])}; + CompositeData bindingData = new CompositeDataSupport(BINDING_DATA_TYPE, + COMPOSITE_ITEM_NAMES_ARRAY, + bindingItemValues); + bindingList.put(bindingData); + } + + return bindingList; + } + + public void createNewBinding(String queueName, String binding) throws JMException + { + final Map<String,Object> arguments = new HashMap<String, Object>(); + + if(HEADERS_EXCHANGE_TYPE.equals(_exchange.getExchangeType())) + { + final String[] bindings = binding.split(","); + for (int i = 0; i < bindings.length; i++) + { + final String[] keyAndValue = bindings[i].split("="); + if (keyAndValue == null || keyAndValue.length == 0 || keyAndValue.length > 2 || keyAndValue[0].length() == 0) + { + throw new JMException("Format for headers binding should be \"<attribute1>=<value1>,<attribute2>=<value2>\""); + } + + if(keyAndValue.length == 1) + { + //no value was given, only a key. Use an empty value to signal match on key presence alone + arguments.put(keyAndValue[0], ""); + } + else + { + arguments.put(keyAndValue[0], keyAndValue[1]); + } + } + } + + VirtualHost virtualHost = _exchange.getParent(VirtualHost.class); + Queue queue = MBeanUtils.findQueueFromQueueName(virtualHost, queueName); + _exchange.createBinding(binding, queue, arguments, Collections.EMPTY_MAP); + } + + public void removeBinding(String queueName, String bindingKey) + throws IOException, JMException + { + VirtualHost virtualHost = _exchange.getParent(VirtualHost.class); + Queue queue = MBeanUtils.findQueueFromQueueName(virtualHost, queueName);; + + boolean deleted = false; + for(Binding binding : _exchange.getBindings()) + { + if(queue.equals(binding.getParent(Queue.class)) && bindingKey.equals(binding.getName())) + { + binding.delete(); + deleted = true; + } + } + + if (!deleted) + { + throw new OperationsException("No such binding \"" + bindingKey + "\" on queue \"" + queueName + "\""); + } + } +} diff --git a/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/LoggingManagementMBean.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/LoggingManagementMBean.java new file mode 100644 index 0000000000..0dac8ebe37 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/LoggingManagementMBean.java @@ -0,0 +1,324 @@ +/* + * 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.jmx.mbeans; + +import org.apache.log4j.Logger; +import org.apache.qpid.management.common.mbeans.LoggingManagement; +import org.apache.qpid.management.common.mbeans.annotations.MBeanDescription; +import org.apache.qpid.server.jmx.AMQManagedObject; +import org.apache.qpid.server.jmx.ManagedObject; +import org.apache.qpid.server.jmx.ManagedObjectRegistry; +import org.apache.qpid.server.logging.log4j.LoggingFacade; +import org.apache.qpid.server.logging.log4j.LoggingFacadeException; + +import javax.management.JMException; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + + +/** MBean class for LoggingManagement. It implements all the management features exposed for managing logging. */ +@MBeanDescription("Logging Management Interface") +public class LoggingManagementMBean extends AMQManagedObject implements LoggingManagement +{ + public static final String INHERITED_PSUEDO_LOG_LEVEL = "INHERITED"; + private static final Logger LOGGER = Logger.getLogger(LoggingManagementMBean.class); + private static final TabularType LOGGER_LEVEL_TABULAR_TYE; + private static final CompositeType LOGGER_LEVEL_COMPOSITE_TYPE; + + private final LoggingFacade _configurator; + + static + { + try + { + OpenType[] loggerLevelItemTypes = new OpenType[]{SimpleType.STRING, SimpleType.STRING}; + + LOGGER_LEVEL_COMPOSITE_TYPE = new CompositeType("LoggerLevelList", "Logger Level Data", + COMPOSITE_ITEM_NAMES.toArray(new String[COMPOSITE_ITEM_NAMES.size()]), + COMPOSITE_ITEM_DESCRIPTIONS.toArray(new String[COMPOSITE_ITEM_DESCRIPTIONS.size()]), + loggerLevelItemTypes); + + LOGGER_LEVEL_TABULAR_TYE = new TabularType("LoggerLevel", "List of loggers with levels", + LOGGER_LEVEL_COMPOSITE_TYPE, + TABULAR_UNIQUE_INDEX.toArray(new String[TABULAR_UNIQUE_INDEX.size()])); + } + catch (OpenDataException e) + { + throw new ExceptionInInitializerError(e); + } + } + + public LoggingManagementMBean(LoggingFacade configurator, ManagedObjectRegistry registry) throws JMException + { + super(LoggingManagement.class, LoggingManagement.TYPE, registry); + register(); + _configurator = configurator; + } + + @Override + public String getObjectInstanceName() + { + return LoggingManagement.TYPE; + } + + @Override + public ManagedObject getParentObject() + { + return null; + } + + @Override + public Integer getLog4jLogWatchInterval() + { + return _configurator.getLog4jLogWatchInterval(); + } + + @Override + public String[] getAvailableLoggerLevels() + { + List<String> levels = _configurator.getAvailableLoggerLevels(); + List<String> mbeanLevels = new ArrayList<String>(levels); + mbeanLevels.add(INHERITED_PSUEDO_LOG_LEVEL); + + return mbeanLevels.toArray(new String[mbeanLevels.size()]); + } + + @Override + public TabularData viewEffectiveRuntimeLoggerLevels() + { + Map<String, String> levels = _configurator.retrieveRuntimeLoggersLevels(); + return createTabularDataFromLevelsMap(levels); + } + + @Override + public String getRuntimeRootLoggerLevel() + { + return _configurator.retrieveRuntimeRootLoggerLevel(); + } + + @Override + public boolean setRuntimeRootLoggerLevel(String level) + { + try + { + validateLevelNotAllowingInherited(level); + } + catch (IllegalArgumentException iae) + { + LOGGER.warn(level + " is not a known level"); + return false; + } + + _configurator.setRuntimeRootLoggerLevel(level); + return true; + } + + @Override + public boolean setRuntimeLoggerLevel(String logger, String level) + { + String validatedLevel; + try + { + validatedLevel = getValidateLevelAllowingInherited(level); + } + catch (IllegalArgumentException iae) + { + LOGGER.warn(level + " is not a known level"); + return false; + } + + try + { + _configurator.setRuntimeLoggerLevel(logger, validatedLevel); + } + catch (LoggingFacadeException e) + { + LOGGER.error("Cannot set runtime logging level", e); + return false; + } + return true; + } + + @Override + public TabularData viewConfigFileLoggerLevels() + { + Map<String,String> levels; + try + { + levels = _configurator.retrieveConfigFileLoggersLevels(); + } + catch (LoggingFacadeException e) + { + LOGGER.error("Cannot determine logging levels", e); + return null; + } + + return createTabularDataFromLevelsMap(levels); + } + + @Override + public String getConfigFileRootLoggerLevel()throws IOException + { + try + { + return _configurator.retrieveConfigFileRootLoggerLevel().toUpperCase(); + } + catch (LoggingFacadeException e) + { + LOGGER.warn("The log4j configuration get config request was aborted: ", e); + throw new IOException("The log4j configuration get config request was aborted: " + e.getMessage()); + } + } + + @Override + public boolean setConfigFileLoggerLevel(String logger, String level) + { + String validatedLevel; + try + { + validatedLevel = getValidateLevelAllowingInherited(level); + } + catch (IllegalArgumentException iae) + { + LOGGER.warn(level + " is not a known level"); + return false; + } + + try + { + _configurator.setConfigFileLoggerLevel(logger, validatedLevel); + } + catch (LoggingFacadeException e) + { + LOGGER.warn("The log4j configuration set config request was aborted: ", e); + return false; + } + return true; + } + + @Override + public boolean setConfigFileRootLoggerLevel(String level) + { + try + { + validateLevelNotAllowingInherited(level); + } + catch (IllegalArgumentException iae) + { + LOGGER.warn(level + " is not a known level"); + return false; + } + + try + { + _configurator.setConfigFileRootLoggerLevel(level); + return true; + } + catch (LoggingFacadeException e) + { + LOGGER.warn("The log4j configuration set config request was aborted: ", e); + return false; + } + } + + @Override + public void reloadConfigFile() throws IOException + { + try + { + + _configurator.reload(); + } + catch (LoggingFacadeException e) + { + LOGGER.warn("The log4j configuration reload request was aborted: ", e); + throw new IOException("The log4j configuration reload request was aborted: " + e.getMessage()); + } + } + + private String getValidateLevelAllowingInherited(String level) + { + if(level == null + || "null".equalsIgnoreCase(level) + || INHERITED_PSUEDO_LOG_LEVEL.equalsIgnoreCase(level)) + { + //the string "null" or "inherited" signals to inherit from a parent logger, + //using a null Level reference for the logger. + return null; + } + + validateLevelNotAllowingInherited(level); + return level; + } + + private void validateLevelNotAllowingInherited(String level) + { + final List<String> availableLoggerLevels = _configurator.getAvailableLoggerLevels(); + if (!availableLoggerLevels.contains(level) + && !availableLoggerLevels.contains(String.valueOf(level).toUpperCase())) + { + throw new IllegalArgumentException(level + " not known"); + } + } + + private TabularData createTabularDataFromLevelsMap(Map<String, String> levels) + { + TabularData loggerLevelList = new TabularDataSupport(LOGGER_LEVEL_TABULAR_TYE); + for (Map.Entry<String,String> entry : levels.entrySet()) + { + String loggerName = entry.getKey(); + String level = entry.getValue(); + + CompositeData loggerData = createRow(loggerName, level); + loggerLevelList.put(loggerData); + } + return loggerLevelList; + } + + + private CompositeData createRow(String loggerName, String level) + { + Object[] itemData = {loggerName, level.toUpperCase()}; + try + { + CompositeData loggerData = new CompositeDataSupport(LOGGER_LEVEL_COMPOSITE_TYPE, + COMPOSITE_ITEM_NAMES.toArray(new String[COMPOSITE_ITEM_NAMES.size()]), itemData); + return loggerData; + } + catch (OpenDataException ode) + { + // Should not happen + throw new RuntimeException(ode); + } + } +} diff --git a/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/MBeanUtils.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/MBeanUtils.java new file mode 100644 index 0000000000..97e84d4796 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/MBeanUtils.java @@ -0,0 +1,57 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.jmx.mbeans; + +import javax.management.OperationsException; + +import org.apache.qpid.server.model.ConfiguredObjectFinder; +import org.apache.qpid.server.model.Exchange; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.VirtualHost; + +public class MBeanUtils +{ + public static Queue findQueueFromQueueName(VirtualHost virtualHost, String queueName) throws OperationsException + { + Queue queue = ConfiguredObjectFinder.findConfiguredObjectByName(virtualHost.getQueues(), queueName); + if (queue == null) + { + throw new OperationsException("No such queue \""+queueName+"\""); + } + else + { + return queue; + } + } + + public static Exchange findExchangeFromExchangeName(VirtualHost virtualHost, String exchangeName) throws OperationsException + { + Exchange exchange = ConfiguredObjectFinder.findConfiguredObjectByName(virtualHost.getExchanges(), exchangeName); + if (exchange == null) + { + throw new OperationsException("No such exchange \""+exchangeName+"\""); + } + else + { + return exchange; + } + } +} diff --git a/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/QueueMBean.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/QueueMBean.java new file mode 100644 index 0000000000..5c8b0f7194 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/QueueMBean.java @@ -0,0 +1,668 @@ +/* + * + * 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.jmx.mbeans; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import javax.management.JMException; +import javax.management.MBeanNotificationInfo; +import javax.management.Notification; +import javax.management.ObjectName; +import javax.management.OperationsException; +import javax.management.monitor.MonitorNotification; +import javax.management.openmbean.ArrayType; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; +import org.apache.commons.lang.time.FastDateFormat; +import org.apache.log4j.Logger; +import org.apache.qpid.management.common.mbeans.ManagedQueue; +import org.apache.qpid.server.jmx.AMQManagedObject; +import org.apache.qpid.server.jmx.ManagedObject; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.model.Exchange; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.QueueNotificationListener; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.queue.NotificationCheck; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.queue.QueueEntryVisitor; + +public class QueueMBean extends AMQManagedObject implements ManagedQueue, QueueNotificationListener +{ + private static final Logger LOGGER = Logger.getLogger(QueueMBean.class); + + private static final String[] VIEW_MSGS_COMPOSITE_ITEM_NAMES_DESC_ARRAY = + VIEW_MSGS_COMPOSITE_ITEM_NAMES_DESC.toArray(new String[VIEW_MSGS_COMPOSITE_ITEM_NAMES_DESC.size()]); + + private static final OpenType[] MSG_ATTRIBUTE_TYPES; + private static final CompositeType MSG_DATA_TYPE; + private static final TabularType MSG_LIST_DATA_TYPE; + private static final CompositeType MSG_CONTENT_TYPE; + private static final String[] VIEW_MSG_COMPOSIT_ITEM_NAMES_ARRAY = VIEW_MSG_CONTENT_COMPOSITE_ITEM_NAMES_DESC.toArray( + new String[VIEW_MSG_CONTENT_COMPOSITE_ITEM_NAMES_DESC.size()]); + + static + { + + try + { + MSG_ATTRIBUTE_TYPES = new OpenType[] { + SimpleType.LONG, // For message id + new ArrayType(1, SimpleType.STRING), // For header attributes + SimpleType.LONG, // For size + SimpleType.BOOLEAN, // For redelivered + SimpleType.LONG, // For queue position + SimpleType.INTEGER // For delivery count} + }; + + MSG_DATA_TYPE = new CompositeType("Message", "AMQ Message", + VIEW_MSGS_COMPOSITE_ITEM_NAMES_DESC_ARRAY, + VIEW_MSGS_COMPOSITE_ITEM_NAMES_DESC_ARRAY, MSG_ATTRIBUTE_TYPES); + + MSG_LIST_DATA_TYPE = new TabularType("Messages", "List of messages", MSG_DATA_TYPE, + VIEW_MSGS_TABULAR_UNIQUE_INDEX.toArray(new String[VIEW_MSGS_TABULAR_UNIQUE_INDEX.size()])); + + OpenType[] msgContentAttrs = new OpenType[] { + SimpleType.LONG, // For message id + SimpleType.STRING, // For MimeType + SimpleType.STRING, // For MimeType + new ArrayType(SimpleType.BYTE, true) // For message content + }; + + + MSG_CONTENT_TYPE = new CompositeType("Message Content", "AMQ Message Content", + VIEW_MSG_CONTENT_COMPOSITE_ITEM_NAMES_DESC.toArray(new String[VIEW_MSG_CONTENT_COMPOSITE_ITEM_NAMES_DESC.size()]), + VIEW_MSG_CONTENT_COMPOSITE_ITEM_NAMES_DESC.toArray(new String[VIEW_MSG_CONTENT_COMPOSITE_ITEM_NAMES_DESC.size()]), + msgContentAttrs); + + } + catch (OpenDataException e) + { + throw new RuntimeException(e); + } + } + + private final Queue _queue; + private final VirtualHostMBean _vhostMBean; + + /** Date/time format used for message expiration and message timestamp formatting */ + public static final String JMSTIMESTAMP_DATETIME_FORMAT = "MM-dd-yy HH:mm:ss.SSS z"; + + private static final FastDateFormat FAST_DATE_FORMAT = FastDateFormat.getInstance(JMSTIMESTAMP_DATETIME_FORMAT); + + public QueueMBean(Queue queue, VirtualHostMBean virtualHostMBean) throws JMException + { + super(ManagedQueue.class, ManagedQueue.TYPE, virtualHostMBean.getRegistry()); + _queue = queue; + _vhostMBean = virtualHostMBean; + register(); + _queue.setNotificationListener(this); + } + + public ManagedObject getParentObject() + { + return _vhostMBean; + } + + public String getObjectInstanceName() + { + return ObjectName.quote(getName()); + } + + public String getName() + { + return _queue.getName(); + } + + public Integer getMessageCount() + { + return getStatisticValue(Queue.QUEUE_DEPTH_MESSAGES).intValue(); + } + + public Integer getMaximumDeliveryCount() + { + return (Integer) _queue.getAttribute(Queue.MAXIMUM_DELIVERY_ATTEMPTS); + } + + public Long getReceivedMessageCount() + { + return getStatisticValue(Queue.TOTAL_ENQUEUED_MESSAGES).longValue(); + } + + public Long getQueueDepth() + { + return getStatisticValue(Queue.QUEUE_DEPTH_BYTES).longValue(); + } + + public Integer getActiveConsumerCount() + { + return getStatisticValue(Queue.CONSUMER_COUNT_WITH_CREDIT).intValue(); + } + + public Integer getConsumerCount() + { + return getStatisticValue(Queue.CONSUMER_COUNT).intValue(); + } + + public String getOwner() + { + return (String) _queue.getAttribute(Queue.OWNER); + } + + @Override + public String getQueueType() + { + return (String) _queue.getAttribute(Queue.TYPE); + } + + public boolean isDurable() + { + return _queue.isDurable(); + } + + public boolean isAutoDelete() + { + return _queue.getLifetimePolicy() == LifetimePolicy.AUTO_DELETE; + } + + public Long getMaximumMessageAge() + { + return (Long) _queue.getAttribute(Queue.ALERT_THRESHOLD_MESSAGE_AGE); + } + + public void setMaximumMessageAge(Long age) + { + _queue.setAttribute(Queue.ALERT_THRESHOLD_MESSAGE_AGE, getMaximumMessageAge(), age); + } + + public Long getMaximumMessageSize() + { + return (Long) _queue.getAttribute(Queue.ALERT_THRESHOLD_MESSAGE_SIZE); + } + + public void setMaximumMessageSize(Long size) + { + _queue.setAttribute(Queue.ALERT_THRESHOLD_MESSAGE_SIZE, getMaximumMessageSize(), size); + } + + public Long getMaximumMessageCount() + { + return (Long) _queue.getAttribute(Queue.ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES); + } + + public void setMaximumMessageCount(Long value) + { + _queue.setAttribute(Queue.ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES, getMaximumMessageCount(), value); + } + + public Long getMaximumQueueDepth() + { + return (Long) _queue.getAttribute(Queue.ALERT_THRESHOLD_QUEUE_DEPTH_BYTES); + } + + public void setMaximumQueueDepth(Long value) + { + _queue.setAttribute(Queue.ALERT_THRESHOLD_QUEUE_DEPTH_BYTES, getMaximumQueueDepth(), value); + } + + public Long getCapacity() + { + return (Long) _queue.getAttribute(Queue.QUEUE_FLOW_CONTROL_SIZE_BYTES); + } + + public void setCapacity(Long value) + { + _queue.setAttribute(Queue.QUEUE_FLOW_CONTROL_SIZE_BYTES, getCapacity(), value); + } + + public Long getFlowResumeCapacity() + { + return (Long) _queue.getAttribute(Queue.QUEUE_FLOW_RESUME_SIZE_BYTES); + } + + public void setFlowResumeCapacity(Long value) + { + _queue.setAttribute(Queue.QUEUE_FLOW_RESUME_SIZE_BYTES, getFlowResumeCapacity(), value); + } + + public boolean isFlowOverfull() + { + return (Boolean)_queue.getAttribute(Queue.QUEUE_FLOW_STOPPED); + } + + public boolean isExclusive() + { + return (Boolean) _queue.getAttribute(Queue.EXCLUSIVE); + } + + public void setExclusive(boolean exclusive) + { + _queue.setAttribute(Queue.EXCLUSIVE, isExclusive(), exclusive); + } + + public void setAlternateExchange(String exchangeName) throws OperationsException + { + if (exchangeName == null || "".equals(exchangeName)) + { + _queue.setAttribute(Queue.ALTERNATE_EXCHANGE, getAlternateExchange(), null); + } + else + { + VirtualHost virtualHost = _queue.getParent(VirtualHost.class); + Exchange exchange = MBeanUtils.findExchangeFromExchangeName(virtualHost, exchangeName); + + _queue.setAttribute(Queue.ALTERNATE_EXCHANGE, getAlternateExchange(), exchange); + } + } + + public String getAlternateExchange() + { + Exchange alternateExchange = (Exchange) _queue.getAttribute(Queue.ALTERNATE_EXCHANGE); + return alternateExchange == null ? null : alternateExchange.getName(); + } + + public TabularData viewMessages(int fromIndex, int toIndex) + throws IOException, JMException + { + return viewMessages((long)fromIndex, (long)toIndex); + } + + public TabularData viewMessages(long startPosition, long endPosition) + throws IOException, JMException + { + if ((startPosition > endPosition) || (startPosition < 1)) + { + throw new OperationsException("From Index = " + startPosition + ", To Index = " + endPosition + + "\n\"From Index\" should be greater than 0 and less than \"To Index\""); + } + + if ((endPosition - startPosition) > Integer.MAX_VALUE) + { + throw new OperationsException("Specified MessageID interval is too large. Intervals must be less than 2^31 in size"); + } + + + List<QueueEntry> messages = getMessages(startPosition, endPosition); + + TabularDataSupport messageTable = new TabularDataSupport(MSG_LIST_DATA_TYPE); + + + // Create the tabular list of message header contents + long position = startPosition; + + for (QueueEntry queueEntry : messages) + { + ServerMessage serverMsg = queueEntry.getMessage(); + AMQMessageHeader header = serverMsg.getMessageHeader(); + String[] headerAttributes = + {"reply-to = " + header.getReplyTo(), + "propertyFlags = ", + "ApplicationID = " + header.getAppId(), + "ClusterID = ", + "UserId = " + header.getUserId(), + "JMSMessageID = " + header.getMessageId(), + "JMSCorrelationID = " + header.getCorrelationId(), + "JMSDeliveryMode = " + (serverMsg.isPersistent() ? "Persistent" : "Non_Persistent"), + "JMSPriority = " + header.getPriority(), + "JMSType = " + header.getType(), + "JMSExpiration = " + (header.getExpiration() == 0 ? null : FAST_DATE_FORMAT.format(header.getExpiration())), + "JMSTimestamp = " + (header.getTimestamp() == 0 ? null : FAST_DATE_FORMAT.format(header.getTimestamp())) + }; + + Object[] itemValues = new Object[]{ serverMsg.getMessageNumber(), + headerAttributes, + serverMsg.getSize(), + queueEntry.isRedelivered(), + position, + queueEntry.getDeliveryCount()}; + + position++; + + CompositeData messageData = + new CompositeDataSupport(MSG_DATA_TYPE, VIEW_MSGS_COMPOSITE_ITEM_NAMES_DESC_ARRAY, itemValues); + messageTable.put(messageData); + } + + return messageTable; + + } + + public CompositeData viewMessageContent(long messageId) + throws IOException, JMException + { + QueueEntry entry = getMessage(messageId); + if(entry == null) + { + throw new OperationsException("AMQMessage with message id = " + messageId + " is not in the " + _queue.getName()); + } + + ServerMessage serverMsg = entry.getMessage(); + final int bodySize = (int) serverMsg.getSize(); + + byte[] msgContent = new byte[bodySize]; + + ByteBuffer buf = ByteBuffer.wrap(msgContent); + int stored = serverMsg.getContent(buf, 0); + + if(bodySize != stored) + { + LOGGER.error(String.format("An unexpected amount of content was retrieved " + + "(expected %d, got %d bytes) when viewing content for message with ID %d " + + "on queue '%s' in virtual host '%s'", + bodySize, stored, messageId, _queue.getName(), _vhostMBean.getName())); + } + + AMQMessageHeader header = serverMsg.getMessageHeader(); + + String mimeType = null, encoding = null; + if (header != null) + { + mimeType = header.getMimeType(); + + encoding = header.getEncoding(); + } + + + Object[] itemValues = { messageId, mimeType, encoding, msgContent }; + + return new CompositeDataSupport(MSG_CONTENT_TYPE, VIEW_MSG_COMPOSIT_ITEM_NAMES_ARRAY, itemValues); + + + } + + private QueueEntry getMessage(long messageId) + { + GetMessageVisitor visitor = new GetMessageVisitor(messageId); + _queue.visit(visitor); + return visitor.getEntry(); + } + + public void deleteMessageFromTop() throws IOException, JMException + { + VirtualHost vhost = _queue.getParent(VirtualHost.class); + vhost.executeTransaction(new VirtualHost.TransactionalOperation() + { + public void withinTransaction(final VirtualHost.Transaction txn) + { + _queue.visit(new QueueEntryVisitor() + { + + public boolean visit(final QueueEntry entry) + { + if(entry.acquire()) + { + txn.dequeue(entry); + return true; + } + return false; + } + }); + + } + }); + + } + + public Long clearQueue() throws IOException, JMException + { + VirtualHost vhost = _queue.getParent(VirtualHost.class); + final AtomicLong count = new AtomicLong(); + + vhost.executeTransaction(new VirtualHost.TransactionalOperation() + { + public void withinTransaction(final VirtualHost.Transaction txn) + { + _queue.visit(new QueueEntryVisitor() + { + + public boolean visit(final QueueEntry entry) + { + final ServerMessage message = entry.getMessage(); + if(message != null) + { + txn.dequeue(entry); + count.incrementAndGet(); + + } + return false; + } + }); + + } + }); + return count.get(); + } + + public void moveMessages(final long fromMessageId, final long toMessageId, String toQueue) + throws IOException, JMException + { + if ((fromMessageId > toMessageId) || (fromMessageId < 1)) + { + throw new OperationsException("\"From MessageId\" should be greater than 0 and less than \"To MessageId\""); + } + + VirtualHost vhost = _queue.getParent(VirtualHost.class); + final Queue destinationQueue = MBeanUtils.findQueueFromQueueName(vhost, toQueue); + + vhost.executeTransaction(new VirtualHost.TransactionalOperation() + { + public void withinTransaction(final VirtualHost.Transaction txn) + { + _queue.visit(new QueueEntryVisitor() + { + + public boolean visit(final QueueEntry entry) + { + final ServerMessage message = entry.getMessage(); + if(message != null) + { + final long messageId = message.getMessageNumber(); + + if ((messageId >= fromMessageId) + && (messageId <= toMessageId)) + { + txn.move(entry, destinationQueue); + } + + } + return false; + } + }); + } + }); + } + + public void deleteMessages(final long fromMessageId, final long toMessageId) + throws IOException, JMException + { + VirtualHost vhost = _queue.getParent(VirtualHost.class); + vhost.executeTransaction(new VirtualHost.TransactionalOperation() + { + public void withinTransaction(final VirtualHost.Transaction txn) + { + _queue.visit(new QueueEntryVisitor() + { + + public boolean visit(final QueueEntry entry) + { + final ServerMessage message = entry.getMessage(); + if(message != null) + { + final long messageId = message.getMessageNumber(); + + if ((messageId >= fromMessageId) + && (messageId <= toMessageId)) + { + txn.dequeue(entry); + return true; + } + return false; + } + return true; + } + }); + } + }); + } + + public void copyMessages(final long fromMessageId, final long toMessageId, String toQueue) + throws IOException, JMException + { + if ((fromMessageId > toMessageId) || (fromMessageId < 1)) + { + throw new OperationsException("\"From MessageId\" should be greater than 0 and less than \"To MessageId\""); + } + + VirtualHost vhost = _queue.getParent(VirtualHost.class); + final Queue destinationQueue = MBeanUtils.findQueueFromQueueName(vhost, toQueue); + + vhost.executeTransaction(new VirtualHost.TransactionalOperation() + { + public void withinTransaction(final VirtualHost.Transaction txn) + { + _queue.visit(new QueueEntryVisitor() + { + + public boolean visit(final QueueEntry entry) + { + final ServerMessage message = entry.getMessage(); + if(message != null) + { + final long messageId = message.getMessageNumber(); + + if ((messageId >= fromMessageId) + && (messageId <= toMessageId)) + { + txn.copy(entry, destinationQueue); + } + + } + return false; + } + }); + } + }); + } + + private List<QueueEntry> getMessages(final long first, final long last) + { + final List<QueueEntry> messages = new ArrayList<QueueEntry>((int)(last-first)+1); + _queue.visit(new QueueEntryVisitor() + { + private long position = 1; + + public boolean visit(QueueEntry entry) + { + if(position >= first && position <= last) + { + messages.add(entry); + } + position++; + return position > last; + } + }); + return messages; + } + + + protected static class GetMessageVisitor implements QueueEntryVisitor + { + + private final long _messageNumber; + private QueueEntry _entry; + + public GetMessageVisitor(long messageId) + { + _messageNumber = messageId; + } + + public boolean visit(QueueEntry entry) + { + if(entry.getMessage().getMessageNumber() == _messageNumber) + { + _entry = entry; + return true; + } + return false; + } + + public QueueEntry getEntry() + { + return _entry; + } + } + + @Override + public void notifyClients(NotificationCheck notification, Queue queue, String notificationMsg) + { + notificationMsg = notification.name() + " " + notificationMsg; + + Notification note = new Notification(MonitorNotification.THRESHOLD_VALUE_EXCEEDED, this, + incrementAndGetSequenceNumber(), System.currentTimeMillis(), notificationMsg); + + getBroadcaster().sendNotification(note); + } + + /** + * returns Notifications sent by this MBean. + */ + @Override + public MBeanNotificationInfo[] getNotificationInfo() + { + String[] notificationTypes = new String[] { MonitorNotification.THRESHOLD_VALUE_EXCEEDED }; + String name = MonitorNotification.class.getName(); + String description = "Either Message count or Queue depth or Message size has reached threshold high value"; + MBeanNotificationInfo info1 = new MBeanNotificationInfo(notificationTypes, name, description); + + return new MBeanNotificationInfo[] { info1 }; + } + + @Override + public String getDescription() + { + return (String) _queue.getAttribute(Queue.DESCRIPTION); + } + + @Override + public void setDescription(String description) + { + _queue.setAttribute(Queue.DESCRIPTION, getDescription(), description); + } + + private Number getStatisticValue(String name) + { + final Number statistic = (Number) _queue.getStatistics().getStatistic(name); + return statistic == null ? Integer.valueOf(0) : statistic; + } +} diff --git a/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ServerInformationMBean.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ServerInformationMBean.java new file mode 100644 index 0000000000..67d5861dec --- /dev/null +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ServerInformationMBean.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.server.jmx.mbeans; + +import java.io.IOException; + +import javax.management.JMException; +import javax.management.NotCompliantMBeanException; + +import org.apache.qpid.management.common.mbeans.ServerInformation; +import org.apache.qpid.management.common.mbeans.annotations.MBeanDescription; +import org.apache.qpid.server.jmx.ManagedObject; +import org.apache.qpid.server.jmx.ManagedObjectRegistry; +import org.apache.qpid.server.model.Broker; + +@MBeanDescription("Server Information Interface") +public class ServerInformationMBean extends AbstractStatisticsGatheringMBean<Broker> implements ServerInformation +{ + private final Broker _broker; + + public ServerInformationMBean(ManagedObjectRegistry registry, Broker broker) + throws NotCompliantMBeanException, JMException + { + super(ServerInformation.class, ServerInformation.TYPE, registry, broker); + _broker = broker; + + register(); + } + + @Override + public String getObjectInstanceName() + { + return ServerInformation.TYPE; + } + + @Override + public Integer getManagementApiMajorVersion() throws IOException + { + return QPID_JMX_API_MAJOR_VERSION; + } + + @Override + public Integer getManagementApiMinorVersion() throws IOException + { + return QPID_JMX_API_MINOR_VERSION; + } + + @Override + public String getBuildVersion() throws IOException + { + return (String) _broker.getAttribute(Broker.BUILD_VERSION); + } + + @Override + public String getProductVersion() throws IOException + { + return (String) _broker.getAttribute(Broker.PRODUCT_VERSION); + } + + @Override + public boolean isStatisticsEnabled() + { + return true; + } + + @Override + public ManagedObject getParentObject() + { + // does not have a parent + return null; + } +} diff --git a/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/Shutdown.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/Shutdown.java index cef4a42b64..62733168ef 100644 --- a/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/Shutdown.java +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/Shutdown.java @@ -17,8 +17,14 @@ * under the License. * */ -package org.apache.qpid.shutdown; +package org.apache.qpid.server.jmx.mbeans; +import org.apache.log4j.Logger; +import org.apache.qpid.server.jmx.DefaultManagedObject; +import org.apache.qpid.server.jmx.ManagedObject; +import org.apache.qpid.server.jmx.ManagedObjectRegistry; + +import javax.management.JMException; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -27,11 +33,6 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import javax.management.NotCompliantMBeanException; - -import org.apache.log4j.Logger; -import org.apache.qpid.server.management.DefaultManagedObject; - /** * Implementation of the JMX broker shutdown plugin. */ @@ -46,9 +47,10 @@ public class Shutdown extends DefaultManagedObject implements ShutdownMBean private final Runnable _shutdown = new SystemExiter(); - public Shutdown() throws NotCompliantMBeanException + public Shutdown(ManagedObjectRegistry registry) throws JMException { - super(ShutdownMBean.class, ShutdownMBean.TYPE); + super(ShutdownMBean.class, ShutdownMBean.TYPE, registry); + register(); } /** @see ShutdownMBean#shutdown() */ @@ -108,6 +110,12 @@ public class Shutdown extends DefaultManagedObject implements ShutdownMBean EXECUTOR.schedule(_shutdown, delay, TimeUnit.MILLISECONDS); } + @Override + public ManagedObject getParentObject() + { + return null; + } + /** * Shutting down the system in another thread to avoid JMX exceptions being thrown. */ @@ -120,9 +128,8 @@ public class Shutdown extends DefaultManagedObject implements ShutdownMBean } /** - * @see org.apache.qpid.server.management.ManagedObject#getObjectInstanceName() + * @see ManagedObject#getObjectInstanceName() */ - @Override public String getObjectInstanceName() { return "Shutdown"; diff --git a/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/ShutdownMBean.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ShutdownMBean.java index 5c54fb3e21..ed69c351f7 100644 --- a/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/ShutdownMBean.java +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ShutdownMBean.java @@ -17,13 +17,13 @@ * under the License. * */ -package org.apache.qpid.shutdown; - -import javax.management.MBeanOperationInfo; +package org.apache.qpid.server.jmx.mbeans; import org.apache.qpid.management.common.mbeans.annotations.MBeanOperation; import org.apache.qpid.management.common.mbeans.annotations.MBeanOperationParameter; +import javax.management.MBeanOperationInfo; + /** * Shutdown plugin JMX MBean interface. * @@ -45,7 +45,7 @@ public interface ShutdownMBean * @param delay the number of ms to wait */ @MBeanOperation(name="shutdown", description="Shutdown after the specified delay (ms)", impact = MBeanOperationInfo.ACTION) - public void shutdown(@MBeanOperationParameter(name="when", description="delay (ms)")long delay); + public void shutdown(@MBeanOperationParameter(name = "when", description = "delay (ms)") long delay); /** * Broker will be shutdown at the specified date and time. @@ -53,5 +53,6 @@ public interface ShutdownMBean * @param when the date and time to shutdown */ @MBeanOperation(name="shutdownAt", description="Shutdown at the specified date and time (yyyy/MM/dd HH:mm:ss)", impact = MBeanOperationInfo.ACTION) - public void shutdownAt(@MBeanOperationParameter(name="when", description="shutdown date/time (yyyy/MM/dd HH:mm:ss)")String when); + public void shutdownAt(@MBeanOperationParameter(name = "when", + description = "shutdown date/time (yyyy/MM/dd HH:mm:ss)") String when); } diff --git a/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/UserManagementMBean.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/UserManagementMBean.java new file mode 100644 index 0000000000..c7aade34b4 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/UserManagementMBean.java @@ -0,0 +1,179 @@ +/* + * 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.jmx.mbeans; + +import org.apache.log4j.Logger; + +import org.apache.qpid.management.common.mbeans.UserManagement; +import org.apache.qpid.management.common.mbeans.annotations.MBeanDescription; +import org.apache.qpid.server.jmx.AMQManagedObject; +import org.apache.qpid.server.jmx.ManagedObject; +import org.apache.qpid.server.jmx.ManagedObjectRegistry; +import org.apache.qpid.server.model.PasswordCredentialManagingAuthenticationProvider; + +import javax.management.JMException; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; +import javax.security.auth.login.AccountNotFoundException; + +import java.io.IOException; +import java.util.Map; + +@MBeanDescription("User Management Interface") +public class UserManagementMBean extends AMQManagedObject implements UserManagement +{ + private static final Logger _logger = Logger.getLogger(UserManagementMBean.class); + + private PasswordCredentialManagingAuthenticationProvider _authProvider; + + // Setup for the TabularType + private static final TabularType _userlistDataType; // Datatype for representing User Lists + private static final CompositeType _userDataType; // Composite type for representing User + + static + { + OpenType[] userItemTypes = new OpenType[4]; // User item types. + userItemTypes[0] = SimpleType.STRING; // For Username + userItemTypes[1] = SimpleType.BOOLEAN; // For Rights - Read - No longer in use + userItemTypes[2] = SimpleType.BOOLEAN; // For Rights - Write - No longer in use + userItemTypes[3] = SimpleType.BOOLEAN; // For Rights - Admin - No longer is use + + try + { + _userDataType = + new CompositeType("User", "User Data", COMPOSITE_ITEM_NAMES.toArray(new String[COMPOSITE_ITEM_NAMES.size()]), + COMPOSITE_ITEM_DESCRIPTIONS.toArray(new String[COMPOSITE_ITEM_DESCRIPTIONS.size()]), userItemTypes); + + _userlistDataType = new TabularType("Users", "List of users", _userDataType, TABULAR_UNIQUE_INDEX.toArray(new String[TABULAR_UNIQUE_INDEX.size()])); + } + catch (OpenDataException e) + { + _logger.error("Tabular data setup for viewing users incorrect.", e); + throw new ExceptionInInitializerError("Tabular data setup for viewing users incorrect"); + } + } + + public UserManagementMBean(PasswordCredentialManagingAuthenticationProvider provider, ManagedObjectRegistry registry) throws JMException + { + super(UserManagement.class, UserManagement.TYPE, registry); + register(); + _authProvider = provider; + } + + @Override + public String getObjectInstanceName() + { + return UserManagement.TYPE; + } + + @Override + public boolean setPassword(String username, String password) + { + try + { + _authProvider.setPassword(username, password); + } + catch (AccountNotFoundException e) + { + _logger.warn("Attempt to set password of non-existent user '" + username + "'"); + return false; + } + return true; + } + + @Override + public boolean createUser(String username, String password) + { + return _authProvider.createUser(username, password, null); + } + + @Override + public boolean deleteUser(String username) + { + try + { + _authProvider.deleteUser(username); + } + catch (AccountNotFoundException e) + { + _logger.warn("Attempt to delete user (" + username + ") that doesn't exist"); + return false; + } + + return true; + } + + @Override + public boolean reloadData() + { + try + { + _authProvider.reload(); + return true; + } + catch (IOException e) + { + _logger.error("Unable to reload user data", e); + return false; + } + } + + @Override + public TabularData viewUsers() + { + Map<String, Map<String, String>> users = _authProvider.getUsers(); + + TabularDataSupport userList = new TabularDataSupport(_userlistDataType); + + try + { + // Create the tabular list of message header contents + for (String user : users.keySet()) + { + // Create header attributes list + // Read,Write,Admin items are deprecated and we return always false. + Object[] itemData = {user, false, false, false}; + CompositeData messageData = new CompositeDataSupport(_userDataType, COMPOSITE_ITEM_NAMES.toArray(new String[COMPOSITE_ITEM_NAMES.size()]), itemData); + userList.put(messageData); + } + } + catch (OpenDataException e) + { + _logger.warn("Unable to create user list due to :", e); + return null; + } + + return userList; + } + + @Override + public ManagedObject getParentObject() + { + return null; + } +} diff --git a/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostMBean.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostMBean.java new file mode 100644 index 0000000000..6990a40dee --- /dev/null +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostMBean.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.jmx.mbeans; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.jmx.AMQManagedObject; +import org.apache.qpid.server.jmx.ManagedObject; +import org.apache.qpid.server.jmx.ManagedObjectRegistry; +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.Exchange; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.virtualhost.ManagedVirtualHost; + +import javax.management.JMException; +import javax.management.ObjectName; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class VirtualHostMBean extends AMQManagedObject implements ManagedVirtualHost, ConfigurationChangeListener +{ + private static final Logger LOGGER = Logger.getLogger(VirtualHostMBean.class); + + private final VirtualHost _virtualHost; + + private final Map<ConfiguredObject, AMQManagedObject> _children = + new HashMap<ConfiguredObject, AMQManagedObject>(); + private VirtualHostManagerMBean _managerMBean; + + public VirtualHostMBean(VirtualHost virtualHost, ManagedObjectRegistry registry) throws JMException + { + super(ManagedVirtualHost.class, ManagedVirtualHost.TYPE, registry); + _virtualHost = virtualHost; + virtualHost.addChangeListener(this); + + initQueues(); + initExchanges(); + initConnections(); + + //This is the actual JMX bean for this 'VirtualHostMBean', leave it alone. + _managerMBean = new VirtualHostManagerMBean(this); + } + + private void initQueues() throws JMException + { + synchronized (_children) + { + for(Queue queue : _virtualHost.getQueues()) + { + if(!_children.containsKey(queue)) + { + _children.put(queue, new QueueMBean(queue, this)); + } + } + } + } + + private void initExchanges() throws JMException + { + synchronized (_children) + { + for(Exchange exchange : _virtualHost.getExchanges()) + { + if(!_children.containsKey(exchange)) + { + _children.put(exchange, new ExchangeMBean(exchange, this)); + } + } + } + } + + private void initConnections() throws JMException + { + synchronized (_children) + { + for(Connection conn : _virtualHost.getConnections()) + { + if(!_children.containsKey(conn)) + { + _children.put(conn, new ConnectionMBean(conn, this)); + } + } + } + } + + public String getObjectInstanceName() + { + return ObjectName.quote(_virtualHost.getName()); + } + + public String getName() + { + return _virtualHost.getName(); + } + + public void stateChanged(ConfiguredObject object, State oldState, State newState) + { + // ignore + } + + public void childAdded(ConfiguredObject object, ConfiguredObject child) + { + synchronized (_children) + { + try + { + if(child instanceof Queue) + { + QueueMBean queueMB = new QueueMBean((Queue)child, this); + _children.put(child, queueMB); + + } + else if(child instanceof Exchange) + { + ExchangeMBean exchangeMBean = new ExchangeMBean((Exchange)child, this); + _children.put(child, exchangeMBean); + + } + else if(child instanceof Connection) + { + ConnectionMBean connectionMBean = new ConnectionMBean((Connection)child, this); + _children.put(child, connectionMBean); + + } + else + { + LOGGER.debug("Unsupported child : " + child.getName() + " type : " + child.getClass()); + } + + } + catch(JMException e) + { + LOGGER.error("Failed to add mbean for child : " + child.getName(), e); + } + } + } + + public void childRemoved(ConfiguredObject object, ConfiguredObject child) + { + synchronized (_children) + { + AMQManagedObject mbean = _children.remove(child); + if(mbean != null) + { + try + { + mbean.unregister(); + } + catch(JMException e) + { + LOGGER.error("Failed to remove mbean for child : " + child.getName(), e); + } + } + } + } + + @Override + public ManagedObject getParentObject() + { + return null; + } + + protected VirtualHost getVirtualHost() + { + return _virtualHost; + } + + public Collection<QueueMBean> getQueues() + { + Collection<AMQManagedObject> children; + synchronized (_children) + { + children = new ArrayList<AMQManagedObject>(_children.values()); + } + Collection<QueueMBean> queues = new ArrayList<QueueMBean>(); + + for(AMQManagedObject child : children) + { + if(child instanceof QueueMBean) + { + queues.add((QueueMBean) child); + } + } + + return queues; + } +} diff --git a/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostManagerMBean.java b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostManagerMBean.java new file mode 100644 index 0000000000..b3dbbc424a --- /dev/null +++ b/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostManagerMBean.java @@ -0,0 +1,238 @@ +/* + * + * 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.jmx.mbeans; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.management.JMException; +import javax.management.MBeanException; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; +import javax.management.OperationsException; + +import org.apache.log4j.Logger; +import org.apache.qpid.management.common.mbeans.ManagedBroker; +import org.apache.qpid.management.common.mbeans.ManagedQueue; +import org.apache.qpid.management.common.mbeans.annotations.MBeanConstructor; +import org.apache.qpid.management.common.mbeans.annotations.MBeanDescription; +import org.apache.qpid.management.common.mbeans.annotations.MBeanOperationParameter; +import org.apache.qpid.server.jmx.ManagedObject; +import org.apache.qpid.server.model.Exchange; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.queue.AMQQueueFactory; + +@MBeanDescription("This MBean exposes the broker level management features") +public class VirtualHostManagerMBean extends AbstractStatisticsGatheringMBean<VirtualHost> implements ManagedBroker +{ + private static final Logger LOGGER = Logger.getLogger(VirtualHostManagerMBean.class); + + private static final boolean _moveNonExclusiveQueueOwnerToDescription = Boolean.parseBoolean(System.getProperty("qpid.move_non_exclusive_queue_owner_to_description", Boolean.TRUE.toString())); + + private final VirtualHostMBean _virtualHostMBean; + + @MBeanConstructor("Creates the Broker Manager MBean") + public VirtualHostManagerMBean(VirtualHostMBean virtualHostMBean) throws JMException + { + super(ManagedBroker.class, ManagedBroker.TYPE, virtualHostMBean.getRegistry(), virtualHostMBean.getVirtualHost()); + _virtualHostMBean = virtualHostMBean; + register(); + } + + + @Override + public String getObjectInstanceName() + { + return ObjectName.quote(_virtualHostMBean.getName()); + } + + @Override + public ManagedObject getParentObject() + { + return _virtualHostMBean; + } + + @Override + public String[] getExchangeTypes() throws IOException + { + Collection<String> exchangeTypes = _virtualHostMBean.getVirtualHost().getExchangeTypes(); + return exchangeTypes.toArray(new String[exchangeTypes.size()]); + } + + @Override + public List<String> retrieveQueueAttributeNames() throws IOException + { + return ManagedQueue.QUEUE_ATTRIBUTES; + } + + @Override + public List<List<Object>> retrieveQueueAttributeValues( + @MBeanOperationParameter(name = "attributes", description = "Attributes to retrieve") String[] attributes) + throws IOException + { + int attributesLength = attributes.length; + + List<List<Object>> queueAttributesList = new ArrayList<List<Object>>(); + + for(QueueMBean queue : _virtualHostMBean.getQueues()) + { + + if(queue == null) + { + continue; + } + + List<Object> attributeValues = new ArrayList<Object>(attributesLength); + + for(int i=0; i < attributesLength; i++) + { + try + { + attributeValues.add(queue.getAttribute(attributes[i])); + } + catch (Exception e) + { + attributeValues.add("-"); + } + } + + queueAttributesList.add(attributeValues); + } + + return queueAttributesList; + + } + + @Override + public void createNewExchange(String name, String type, boolean durable) + throws IOException, JMException, MBeanException + { + if (!getConfiguredObject().getExchangeTypes().contains(type)) + { + throw new OperationsException("No such exchange type \""+type+"\""); + } + + try + { + getConfiguredObject().createExchange(name, State.ACTIVE, durable, + LifetimePolicy.PERMANENT, 0l, type, Collections.EMPTY_MAP); + } + catch (IllegalArgumentException iae) + { + JMException jme = new JMException(iae.toString()); + throw new MBeanException(jme, "Error in creating exchange " + name); + } + + } + + @Override + public void unregisterExchange(String exchangeName) + throws IOException, JMException, MBeanException + { + Exchange theExchange = MBeanUtils.findExchangeFromExchangeName(_virtualHostMBean.getVirtualHost(), exchangeName); + try + { + theExchange.delete(); + } + catch (IllegalStateException ex) + { + final JMException jme = new JMException(ex.toString()); + throw new MBeanException(jme, "Error in unregistering exchange " + exchangeName); + } + } + + @Override + public void createNewQueue(String queueName, String owner, boolean durable) + throws IOException, JMException, MBeanException + { + createNewQueue(queueName, owner, durable, Collections.EMPTY_MAP); + } + + @Override + public void createNewQueue(String queueName, String owner, boolean durable, Map<String, Object> originalArguments) + throws IOException, JMException + { + final Map<String, Object> createArgs = processNewQueueArguments(queueName, owner, originalArguments); + getConfiguredObject().createQueue(queueName, State.ACTIVE, durable, false, LifetimePolicy.PERMANENT, 0l, createArgs); + } + + + /** + * Some users have been abusing the owner field to store a queue description. As the owner field + * only makes sense if exclusive=true, and it is currently impossible to create an exclusive queue via + * the JMX interface, if the user specifies a owner, then we assume that they actually mean to pass a description. + */ + private Map<String, Object> processNewQueueArguments(String queueName, + String owner, Map<String, Object> arguments) + { + final Map<String, Object> argumentsCopy; + if (_moveNonExclusiveQueueOwnerToDescription && owner != null) + { + argumentsCopy = new HashMap<String, Object>(arguments == null ? new HashMap<String, Object>() : arguments); + if (!argumentsCopy.containsKey(AMQQueueFactory.X_QPID_DESCRIPTION)) + { + LOGGER.warn("Non-exclusive owner " + owner + " for new queue " + queueName + " moved to " + AMQQueueFactory.X_QPID_DESCRIPTION); + + argumentsCopy.put(AMQQueueFactory.X_QPID_DESCRIPTION, owner); + } + else + { + LOGGER.warn("Non-exclusive owner " + owner + " for new queue " + queueName + " ignored."); + } + } + else + { + argumentsCopy = arguments; + } + return argumentsCopy; + } + + @Override + public void deleteQueue( + @MBeanOperationParameter(name = ManagedQueue.TYPE, description = "Queue Name") String queueName) + throws IOException, JMException, MBeanException + { + Queue theQueue = MBeanUtils.findQueueFromQueueName(_virtualHostMBean.getVirtualHost(), queueName); + theQueue.delete(); + } + + @Override + public ObjectName getObjectName() throws MalformedObjectNameException + { + return getObjectNameForSingleInstanceMBean(); + } + + public synchronized boolean isStatisticsEnabled() + { + updateStats(); + return false; //TODO - implement isStatisticsEnabled + } + +} diff --git a/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/Activator.java b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/NoopManagedObjectRegistry.java index fa0ffb5045..a2631bab7f 100644 --- a/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/Activator.java +++ b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/NoopManagedObjectRegistry.java @@ -7,9 +7,9 @@ * 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 @@ -18,32 +18,29 @@ * under the License. * */ -package org.apache.qpid.extras; +package org.apache.qpid.server.jmx; -import org.osgi.framework.BundleActivator; -import org.osgi.framework.BundleContext; +import javax.management.JMException; -import org.apache.qpid.extras.exchanges.diagnostic.DiagnosticExchangeType; -import org.apache.qpid.extras.exchanges.example.TestExchangeType; -import org.apache.qpid.server.exchange.ExchangeType; +public class NoopManagedObjectRegistry implements ManagedObjectRegistry +{ + public NoopManagedObjectRegistry() + { + } -/** - * - * @author aidan - * - * Dummy class, used by PluginTest - */ + public void start() + { + } -public class Activator implements BundleActivator -{ + public void registerObject(ManagedObject managedObject) throws JMException + { + } - public void start(BundleContext ctx) throws Exception + public void unregisterObject(ManagedObject managedObject) throws JMException { - ctx.registerService(ExchangeType.class.getName(), new TestExchangeType(), null); - ctx.registerService(ExchangeType.class.getName(), new DiagnosticExchangeType(), null); } - public void stop(BundleContext ctx) throws Exception + public void close() { } } diff --git a/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/ConnectionMBeanTest.java b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/ConnectionMBeanTest.java new file mode 100644 index 0000000000..0c5fb0bf1f --- /dev/null +++ b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/ConnectionMBeanTest.java @@ -0,0 +1,237 @@ +/* + * 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.jmx.mbeans; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.Date; + +import javax.management.JMException; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.TabularData; + +import junit.framework.TestCase; + +import org.apache.qpid.management.common.mbeans.ManagedConnection; +import org.apache.qpid.server.jmx.ManagedObject; +import org.apache.qpid.server.jmx.ManagedObjectRegistry; +import org.apache.qpid.server.model.Connection; +import org.apache.qpid.server.model.Session; +import org.apache.qpid.server.model.Statistics; + +public class ConnectionMBeanTest extends TestCase +{ + private ConnectionMBean _connectionMBean; + private Connection _mockConnection; + private VirtualHostMBean _mockVirtualHostMBean; + private ManagedObjectRegistry _mockManagedObjectRegistry; + + @Override + protected void setUp() throws Exception + { + _mockConnection = mock(Connection.class); + _mockVirtualHostMBean = mock(VirtualHostMBean.class); + + _mockManagedObjectRegistry = mock(ManagedObjectRegistry.class); + when(_mockVirtualHostMBean.getRegistry()).thenReturn(_mockManagedObjectRegistry); + + _connectionMBean = new ConnectionMBean(_mockConnection, _mockVirtualHostMBean); + } + + public void testMBeanRegistersItself() throws Exception + { + ConnectionMBean connectionMBean = new ConnectionMBean(_mockConnection, _mockVirtualHostMBean); + verify(_mockManagedObjectRegistry).registerObject(connectionMBean); + } + + public void testCloseConnection() throws Exception + { + _connectionMBean.closeConnection(); + verify(_mockConnection).delete(); + } + + public void testCommitTransactions() + { + try + { + _connectionMBean.commitTransactions(0); + fail("Exception not thrown"); + } + catch(JMException e) + { + assertTrue("Cause should be an UnsupportedOperationException", e.getCause() instanceof UnsupportedOperationException); + } + } + + public void testRollbackTransactions() + { + try + { + _connectionMBean.rollbackTransactions(0); + fail("Exception not thrown"); + } + catch(JMException e) + { + assertTrue("Cause should be an UnsupportedOperationException", e.getCause() instanceof UnsupportedOperationException); + } + } + + public void testChannelsWithSingleTransactionalSession() throws Exception + { + int channelId = 10; + int unacknowledgedMessages = 2; + long localTransactionBegins = 1; + boolean transactional = true; + boolean blocked = false; + + Session mockSession = createMockedSession(channelId, unacknowledgedMessages, localTransactionBegins, blocked); + + when(_mockConnection.getSessions()).thenReturn(Collections.singletonList(mockSession)); + + TabularData table = _connectionMBean.channels(); + assertEquals("Unexpected number of rows in table", 1, table.size()); + + final CompositeData row = table.get(new Integer[] {channelId} ); + assertChannelRow(row, channelId, unacknowledgedMessages, transactional, blocked); + } + + public void testChannelsWithSingleNonTransactionalSession() throws Exception + { + int channelId = 10; + int unacknowledgedMessages = 2; + long localTransactionBegins = 0; + boolean transactional = false; + boolean blocked = false; + + Session mockSession = createMockedSession(channelId, unacknowledgedMessages, localTransactionBegins, blocked); + + when(_mockConnection.getSessions()).thenReturn(Collections.singletonList(mockSession)); + + TabularData table = _connectionMBean.channels(); + assertEquals("Unexpected number of rows in table", 1, table.size()); + + final CompositeData row = table.get(new Integer[] {channelId} ); + assertChannelRow(row, channelId, unacknowledgedMessages, transactional, blocked); + } + + public void testChannelsWithSessionBlocked() throws Exception + { + int channelId = 10; + int unacknowledgedMessages = 2; + long localTransactionBegins = 0; + boolean transactional = false; + boolean blocked = true; + + Session mockSession = createMockedSession(channelId, unacknowledgedMessages, localTransactionBegins, blocked); + + when(_mockConnection.getSessions()).thenReturn(Collections.singletonList(mockSession)); + + TabularData table = _connectionMBean.channels(); + assertEquals("Unexpected number of rows in table", 1, table.size()); + + final CompositeData row = table.get(new Integer[] {channelId} ); + assertChannelRow(row, channelId, unacknowledgedMessages, transactional, blocked); + } + + public void testParentObjectIsVirtualHost() + { + ManagedObject parent = _connectionMBean.getParentObject(); + assertEquals(_mockVirtualHostMBean, parent); + } + + public void testGetObjectInstanceName() + { + String remoteAddress = "testRemoteAddress"; + String quotedRemoteAddress = "\"testRemoteAddress\""; + when(_mockConnection.getAttribute(Connection.REMOTE_ADDRESS)).thenReturn(remoteAddress); + String objectInstanceName = _connectionMBean.getObjectInstanceName(); + assertEquals(quotedRemoteAddress, objectInstanceName); + } + + public void testGetAuthorizedId() throws Exception + { + assertAttribute("authorizedId", "testAuthorizedId", Connection.PRINCIPAL); + } + + public void testGetClientId() throws Exception + { + assertAttribute("clientId", "testClientId", Connection.CLIENT_ID); + } + + public void testGetVersion() throws Exception + { + assertAttribute("version", "testVersion", Connection.CLIENT_VERSION); + } + + public void testGetRemoteAddress() throws Exception + { + assertAttribute("remoteAddress", "testRemoteAddress", Connection.REMOTE_ADDRESS); + } + + public void testGetLastIoTime() + { + Statistics mockStatistics = mock(Statistics.class); + when(_mockConnection.getStatistics()).thenReturn(mockStatistics); + when(mockStatistics.getStatistic(Connection.LAST_IO_TIME)).thenReturn(1L); + + Object actualValue = _connectionMBean.getLastIoTime(); + assertEquals("Unexpected lastIoTime", new Date(1L), actualValue); + } + + public void testGetMaximumNumberOfChannels() throws Exception + { + assertAttribute("maximumNumberOfChannels", 10l, Connection.SESSION_COUNT_LIMIT); + } + + public void testIsStatisticsEnabledAlwaysTrue() throws Exception + { + assertTrue(_connectionMBean.isStatisticsEnabled()); + } + + private void assertAttribute(String jmxAttributeName, Object expectedValue, String underlyingAttributeName) throws Exception + { + when(_mockConnection.getAttribute(underlyingAttributeName)).thenReturn(expectedValue); + MBeanTestUtils.assertMBeanAttribute(_connectionMBean, jmxAttributeName, expectedValue); + } + + private void assertChannelRow(final CompositeData row, int channelId, int unacknowledgedMessages, boolean isTransactional, boolean flowBlocked) + { + assertNotNull("No row for channel id " + channelId, row); + assertEquals("Unexpected channel id", channelId, row.get(ManagedConnection.CHAN_ID)); + assertEquals("Unexpected transactional flag", isTransactional, row.get(ManagedConnection.TRANSACTIONAL)); + assertEquals("Unexpected unacknowledged message count", unacknowledgedMessages, row.get(ManagedConnection.UNACKED_COUNT)); + assertEquals("Unexpected flow blocked", flowBlocked, row.get(ManagedConnection.FLOW_BLOCKED)); + } + + private Session createMockedSession(int channelId, int unacknowledgedMessages, long localTransactionBegins, boolean blocked) + { + Session mockSession = mock(Session.class); + Statistics mockSessionStatistics = mock(Statistics.class); + when(mockSessionStatistics.getStatistic(Session.LOCAL_TRANSACTION_BEGINS)).thenReturn(localTransactionBegins); + when(mockSessionStatistics.getStatistic(Session.UNACKNOWLEDGED_MESSAGES)).thenReturn(unacknowledgedMessages); + + when(mockSession.getStatistics()).thenReturn(mockSessionStatistics); + when(mockSession.getAttribute(Session.CHANNEL_ID)).thenReturn(channelId); + when(mockSession.getAttribute(Session.PRODUCER_FLOW_BLOCKED)).thenReturn(blocked); + return mockSession; + } +} diff --git a/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/ExchangeMBeanTest.java b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/ExchangeMBeanTest.java new file mode 100644 index 0000000000..e350f80a25 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/ExchangeMBeanTest.java @@ -0,0 +1,234 @@ +/* + * 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.jmx.mbeans; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.never; +import static org.mockito.Matchers.isNull; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.anyMap; + + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; + +import javax.management.JMException; +import javax.management.ListenerNotFoundException; +import javax.management.Notification; +import javax.management.NotificationListener; +import javax.management.OperationsException; + +import org.apache.qpid.server.jmx.ManagedObjectRegistry; +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.Queue; +import org.apache.qpid.server.model.Statistics; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.queue.NotificationCheck; +import org.mockito.ArgumentMatcher; + +import junit.framework.TestCase; + +public class ExchangeMBeanTest extends TestCase +{ + private static final String EXCHANGE_NAME = "EXCHANGE_NAME"; + private static final String EXCHANGE_TYPE = "EXCHANGE_TYPE"; + private static final String QUEUE1_NAME = "QUEUE1_NAME"; + private static final String QUEUE2_NAME = "QUEUE2_NAME"; + private static final String BINDING1 = "BINDING1"; + private static final String BINDING2 = "BINDING2"; + + private Exchange _mockExchange; + private VirtualHostMBean _mockVirtualHostMBean; + private ManagedObjectRegistry _mockManagedObjectRegistry; + private ExchangeMBean _exchangeMBean; + private Queue _mockQueue1; + private Queue _mockQueue2; + private Exchange _mockHeadersExchange; + + @Override + protected void setUp() throws Exception + { + _mockExchange = mock(Exchange.class); + when(_mockExchange.getName()).thenReturn(EXCHANGE_NAME); + when(_mockExchange.getExchangeType()).thenReturn(EXCHANGE_TYPE); + _mockVirtualHostMBean = mock(VirtualHostMBean.class); + + _mockManagedObjectRegistry = mock(ManagedObjectRegistry.class); + when(_mockVirtualHostMBean.getRegistry()).thenReturn(_mockManagedObjectRegistry); + + _mockQueue1 = createMockQueue(QUEUE1_NAME); + _mockQueue2 = createMockQueue(QUEUE2_NAME); + + VirtualHost mockVirtualHost = mock(VirtualHost.class); + when(mockVirtualHost.getQueues()).thenReturn(Arrays.asList(new Queue[] {_mockQueue1, _mockQueue2})); + when(_mockExchange.getParent(VirtualHost.class)).thenReturn(mockVirtualHost); + + _exchangeMBean = new ExchangeMBean(_mockExchange, _mockVirtualHostMBean); + + _mockHeadersExchange = mock(Exchange.class); + when(_mockHeadersExchange.getExchangeType()).thenReturn(ExchangeMBean.HEADERS_EXCHANGE_TYPE); + when(_mockHeadersExchange.getParent(VirtualHost.class)).thenReturn(mockVirtualHost); + + } + + public void testExchangeName() + { + assertEquals(EXCHANGE_NAME, _exchangeMBean.getName()); + } + + public void testExchangeType() + { + assertEquals(EXCHANGE_TYPE, _exchangeMBean.getExchangeType()); + } + + public void testNonHeadersExchangeCreateNewBinding() throws Exception + { + _exchangeMBean.createNewBinding(QUEUE1_NAME, BINDING1); + verify(_mockExchange).createBinding(BINDING1, _mockQueue1, Collections.EMPTY_MAP, Collections.EMPTY_MAP); + } + + public void testCreateNewBindingWhereQueueIsUnknown() throws Exception + { + try + { + _exchangeMBean.createNewBinding("unknown", BINDING1); + } + catch (OperationsException oe) + { + // PASS + assertEquals("No such queue \"unknown\"", oe.getMessage()); + } + verify(_mockExchange, never()).createBinding(anyString(), any(Queue.class), anyMap(), anyMap()); + } + + public void testRemoveBinding() throws Exception + { + Binding mockBinding1 = createBindingOnQueue(BINDING1, _mockQueue1); + Binding mockBinding2 = createBindingOnQueue(BINDING2, _mockQueue1); + when(_mockExchange.getBindings()).thenReturn(Arrays.asList(new Binding[] {mockBinding1, mockBinding2})); + + _exchangeMBean.removeBinding(QUEUE1_NAME, BINDING1); + verify(mockBinding1).delete(); + } + + public void testRemoveBindingWhereQueueIsUnknown() throws Exception + { + Binding mockBinding1 = createBindingOnQueue(BINDING1, _mockQueue1); + when(_mockExchange.getBindings()).thenReturn(Arrays.asList(new Binding[] {mockBinding1})); + + try + { + _exchangeMBean.removeBinding("unknown", BINDING1); + fail("Exception not thrown"); + } + catch (OperationsException oe) + { + // PASS + assertEquals("No such queue \"unknown\"", oe.getMessage()); + } + verify(mockBinding1, never()).delete(); + } + + public void testRemoveBindingWhereBindingNameIsUnknown() throws Exception + { + Binding mockBinding1 = createBindingOnQueue(BINDING1, _mockQueue1); + when(_mockExchange.getBindings()).thenReturn(Arrays.asList(new Binding[] {mockBinding1})); + + try + { + _exchangeMBean.removeBinding(QUEUE1_NAME, "unknown"); + fail("Exception not thrown"); + } + catch (OperationsException oe) + { + // PASS + assertEquals("No such binding \"unknown\" on queue \"" + QUEUE1_NAME + "\"", oe.getMessage()); + } + verify(mockBinding1, never()).delete(); + } + + public void testHeadersExchangeCreateNewBinding() throws Exception + { + String binding = "key1=binding1,key2=binding2"; + Map<String, Object> expectedBindingMap = new HashMap<String, Object>() + {{ + put("key1", "binding1"); + put("key2", "binding2"); + }}; + _exchangeMBean = new ExchangeMBean(_mockHeadersExchange, _mockVirtualHostMBean); + + _exchangeMBean.createNewBinding(QUEUE1_NAME, binding); + verify(_mockHeadersExchange).createBinding(binding, _mockQueue1, expectedBindingMap, Collections.EMPTY_MAP); + } + + public void testHeadersExchangeCreateNewBindingWithFieldWithoutValue() throws Exception + { + String binding = "key1=binding1,key2="; + Map<String, Object> expectedBindingMap = new HashMap<String, Object>() + {{ + put("key1", "binding1"); + put("key2", ""); + }}; + _exchangeMBean = new ExchangeMBean(_mockHeadersExchange, _mockVirtualHostMBean); + + _exchangeMBean.createNewBinding(QUEUE1_NAME, binding); + verify(_mockHeadersExchange).createBinding(binding, _mockQueue1, expectedBindingMap, Collections.EMPTY_MAP); + } + + public void testHeadersExchangeCreateNewBindingMalformed() throws Exception + { + String binding = "=binding1,="; + _exchangeMBean = new ExchangeMBean(_mockHeadersExchange, _mockVirtualHostMBean); + + try + { + _exchangeMBean.createNewBinding(QUEUE1_NAME, binding); + fail("Exception not thrown"); + } + catch (JMException e) + { + assertEquals("Format for headers binding should be \"<attribute1>=<value1>,<attribute2>=<value2>\"", e.getMessage()); + } + } + + private Binding createBindingOnQueue(String bindingName, Queue queue) + { + Binding mockBinding = mock(Binding.class); + when(mockBinding.getParent(Queue.class)).thenReturn(queue); + when(mockBinding.getName()).thenReturn(bindingName); + return mockBinding; + } + + private Queue createMockQueue(String queueName) + { + Queue mockQueue = mock(Queue.class); + when(mockQueue.getName()).thenReturn(queueName); + return mockQueue; + } + +} diff --git a/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/LoggingManagementMBeanTest.java b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/LoggingManagementMBeanTest.java new file mode 100644 index 0000000000..ae1be5db00 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/LoggingManagementMBeanTest.java @@ -0,0 +1,245 @@ +/* + * 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.jmx.mbeans; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.Matchers.anyString; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.TabularData; + +import junit.framework.TestCase; + +import org.apache.qpid.management.common.mbeans.LoggingManagement; +import org.apache.qpid.server.jmx.ManagedObjectRegistry; +import org.apache.qpid.server.logging.log4j.LoggingFacade; + +public class LoggingManagementMBeanTest extends TestCase +{ + private static final String TEST_LEVEL1 = "LEVEL1"; + private static final String TEST_LEVEL2 = "LEVEL2"; + + private LoggingManagementMBean _loggingMBean; + private LoggingFacade _mockLoggingFacade; + private ManagedObjectRegistry _mockManagedObjectRegistry; + + @Override + protected void setUp() throws Exception + { + _mockLoggingFacade = mock(LoggingFacade.class); + final List<String> listOfLevels = new ArrayList<String>() + {{ + add(TEST_LEVEL1); + add(TEST_LEVEL2); + }}; + when(_mockLoggingFacade.getAvailableLoggerLevels()).thenReturn(listOfLevels); + + _mockManagedObjectRegistry = mock(ManagedObjectRegistry.class); + + _loggingMBean = new LoggingManagementMBean(_mockLoggingFacade, _mockManagedObjectRegistry); + } + + public void testMBeanRegistersItself() throws Exception + { + LoggingManagementMBean connectionMBean = new LoggingManagementMBean(_mockLoggingFacade, _mockManagedObjectRegistry); + verify(_mockManagedObjectRegistry).registerObject(connectionMBean); + } + + public void testLog4jLogWatchInterval() throws Exception + { + final Integer value = 5000; + when(_mockLoggingFacade.getLog4jLogWatchInterval()).thenReturn(value); + + assertEquals("Unexpected watch interval",value, _loggingMBean.getLog4jLogWatchInterval()); + } + + public void testGetAvailableLoggerLevels() throws Exception + { + String[] actualLevels = _loggingMBean.getAvailableLoggerLevels(); + assertEquals(3, actualLevels.length); + assertEquals(TEST_LEVEL1, actualLevels[0]); + assertEquals(TEST_LEVEL2, actualLevels[1]); + assertEquals(LoggingManagementMBean.INHERITED_PSUEDO_LOG_LEVEL, actualLevels[2]); + } + + public void testViewEffectiveRuntimeLoggerLevels() throws Exception + { + Map<String, String> loggerLevels = new TreeMap<String, String>(); + loggerLevels.put("a.b.D", TEST_LEVEL2); + loggerLevels.put("a.b.C", TEST_LEVEL1); + loggerLevels.put("a.b.c.E", TEST_LEVEL2); + + when(_mockLoggingFacade.retrieveRuntimeLoggersLevels()).thenReturn(loggerLevels ); + + TabularData table = _loggingMBean.viewEffectiveRuntimeLoggerLevels(); + assertEquals(3, table.size()); + + final CompositeData row1 = table.get(new String[] {"a.b.C"} ); + final CompositeData row2 = table.get(new String[] {"a.b.D"} ); + final CompositeData row3 = table.get(new String[] {"a.b.c.E"} ); + assertChannelRow(row1, "a.b.C", TEST_LEVEL1); + assertChannelRow(row2, "a.b.D", TEST_LEVEL2); + assertChannelRow(row3, "a.b.c.E", TEST_LEVEL2); + } + + public void testGetRuntimeRootLoggerLevel() throws Exception + { + when(_mockLoggingFacade.retrieveRuntimeRootLoggerLevel()).thenReturn(TEST_LEVEL1); + + assertEquals(TEST_LEVEL1, _loggingMBean.getRuntimeRootLoggerLevel()); + } + + public void testSetRuntimeRootLoggerLevel() throws Exception + { + _loggingMBean.setRuntimeRootLoggerLevel(TEST_LEVEL1); + verify(_mockLoggingFacade).setRuntimeRootLoggerLevel(TEST_LEVEL1); + } + + public void testSetRuntimeRootLoggerLevelWhenLoggingLevelUnknown() throws Exception + { + boolean result = _loggingMBean.setRuntimeRootLoggerLevel("unknown"); + assertFalse(result); + verify(_mockLoggingFacade, never()).setRuntimeRootLoggerLevel("unknown"); + } + + public void testSetRuntimeRootLoggerLevelWhenLoggingLevelInherited() throws Exception + { + boolean result = _loggingMBean.setRuntimeRootLoggerLevel(LoggingManagementMBean.INHERITED_PSUEDO_LOG_LEVEL); + assertFalse(result); + verify(_mockLoggingFacade, never()).setRuntimeRootLoggerLevel(anyString()); + } + + public void testSetRuntimeLoggerLevel() throws Exception + { + _loggingMBean.setRuntimeLoggerLevel("a.b.c.D", TEST_LEVEL1); + verify(_mockLoggingFacade).setRuntimeLoggerLevel("a.b.c.D", TEST_LEVEL1); + } + + public void testSetRuntimeLoggerLevelWhenLoggingLevelUnknown() throws Exception + { + boolean result = _loggingMBean.setRuntimeLoggerLevel("a.b.c.D", "unknown"); + assertFalse(result); + verify(_mockLoggingFacade, never()).setRuntimeLoggerLevel(anyString(), anyString()); + } + + public void testSetRuntimeLoggerLevelWhenLoggingLevelInherited() throws Exception + { + boolean result = _loggingMBean.setRuntimeLoggerLevel("a.b.c.D", LoggingManagementMBean.INHERITED_PSUEDO_LOG_LEVEL); + assertTrue(result); + verify(_mockLoggingFacade).setRuntimeLoggerLevel("a.b.c.D", null); + } + + public void testViewEffectiveConfigFileLoggerLevels() throws Exception + { + Map<String, String> loggerLevels = new TreeMap<String, String>(); + loggerLevels.put("a.b.D", "level2"); + loggerLevels.put("a.b.C", TEST_LEVEL1); + loggerLevels.put("a.b.c.E", "level2"); + + when(_mockLoggingFacade.retrieveConfigFileLoggersLevels()).thenReturn(loggerLevels ); + + TabularData table = _loggingMBean.viewConfigFileLoggerLevels(); + assertEquals(3, table.size()); + + final CompositeData row1 = table.get(new String[] {"a.b.C"} ); + final CompositeData row2 = table.get(new String[] {"a.b.D"} ); + final CompositeData row3 = table.get(new String[] {"a.b.c.E"} ); + assertChannelRow(row1, "a.b.C", TEST_LEVEL1); + assertChannelRow(row2, "a.b.D", TEST_LEVEL2); + assertChannelRow(row3, "a.b.c.E", TEST_LEVEL2); + } + + public void testGetConfigFileRootLoggerLevel() throws Exception + { + when(_mockLoggingFacade.retrieveConfigFileRootLoggerLevel()).thenReturn(TEST_LEVEL1); + + assertEquals(TEST_LEVEL1, _loggingMBean.getConfigFileRootLoggerLevel()); + } + + public void testSetConfigFileRootLoggerLevel() throws Exception + { + when(_mockLoggingFacade.getAvailableLoggerLevels()).thenReturn(Collections.singletonList(TEST_LEVEL1)); + _loggingMBean.setConfigFileRootLoggerLevel(TEST_LEVEL1); + verify(_mockLoggingFacade).setConfigFileRootLoggerLevel(TEST_LEVEL1); + } + + public void testSetConfigFileRootLoggerLevelWhenLoggingLevelUnknown() throws Exception + { + when(_mockLoggingFacade.getAvailableLoggerLevels()).thenReturn(Collections.singletonList(TEST_LEVEL1)); + boolean result = _loggingMBean.setConfigFileRootLoggerLevel("unknown"); + assertFalse(result); + verify(_mockLoggingFacade, never()).setConfigFileRootLoggerLevel("unknown"); + } + + public void testSetConfigFileRootLoggerLevelWhenLoggingLevelInherited() throws Exception + { + when(_mockLoggingFacade.getAvailableLoggerLevels()).thenReturn(Collections.singletonList(TEST_LEVEL1)); + boolean result = _loggingMBean.setConfigFileRootLoggerLevel(LoggingManagementMBean.INHERITED_PSUEDO_LOG_LEVEL); + assertFalse(result); + verify(_mockLoggingFacade, never()).setConfigFileRootLoggerLevel(anyString()); + } + + public void testSetConfigFileLoggerLevel() throws Exception + { + when(_mockLoggingFacade.getAvailableLoggerLevels()).thenReturn(Collections.singletonList(TEST_LEVEL1)); + _loggingMBean.setConfigFileLoggerLevel("a.b.c.D", TEST_LEVEL1); + verify(_mockLoggingFacade).setConfigFileLoggerLevel("a.b.c.D", TEST_LEVEL1); + } + + public void testSetConfigFileLoggerLevelWhenLoggingLevelUnknown() throws Exception + { + when(_mockLoggingFacade.getAvailableLoggerLevels()).thenReturn(Collections.singletonList(TEST_LEVEL1)); + boolean result = _loggingMBean.setConfigFileLoggerLevel("a.b.c.D", "unknown"); + assertFalse(result); + verify(_mockLoggingFacade, never()).setConfigFileLoggerLevel("a.b.c.D", "unknown"); + } + + public void testSetConfigFileLoggerLevelWhenLoggingLevelInherited() throws Exception + { + when(_mockLoggingFacade.getAvailableLoggerLevels()).thenReturn(Collections.singletonList(TEST_LEVEL1)); + boolean result = _loggingMBean.setConfigFileLoggerLevel("a.b.c.D", LoggingManagementMBean.INHERITED_PSUEDO_LOG_LEVEL); + assertTrue(result); + verify(_mockLoggingFacade).setConfigFileLoggerLevel("a.b.c.D", null); + } + + public void testReloadConfigFile() throws Exception + { + _loggingMBean.reloadConfigFile(); + + verify(_mockLoggingFacade).reload(); + } + + private void assertChannelRow(final CompositeData row, String logger, String level) + { + assertNotNull("No row for " + logger, row); + assertEquals("Unexpected logger name", logger, row.get(LoggingManagement.LOGGER_NAME)); + assertEquals("Unexpected level", level, row.get(LoggingManagement.LOGGER_LEVEL)); + } +} diff --git a/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/MBeanTestUtils.java b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/MBeanTestUtils.java new file mode 100644 index 0000000000..5f913e5f33 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/MBeanTestUtils.java @@ -0,0 +1,40 @@ +/* + * 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.jmx.mbeans; + +import junit.framework.TestCase; + +import org.apache.commons.beanutils.PropertyUtils; +import org.apache.qpid.server.jmx.DefaultManagedObject; + +public class MBeanTestUtils +{ + + public static void assertMBeanAttribute(DefaultManagedObject managedObject, String jmxAttributeName, Object expectedValue) throws Exception + { + Object actualValue = PropertyUtils.getSimpleProperty(managedObject, jmxAttributeName); + TestCase.assertEquals("Attribute " + jmxAttributeName + " has unexpected value", expectedValue, actualValue); + } + + public static void setMBeanAttribute(DefaultManagedObject managedObject, String jmxAttributeName, Object newValue) throws Exception + { + PropertyUtils.setSimpleProperty(managedObject, jmxAttributeName, newValue); + } +} diff --git a/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/QueueMBeanTest.java b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/QueueMBeanTest.java new file mode 100644 index 0000000000..f2663bca4e --- /dev/null +++ b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/QueueMBeanTest.java @@ -0,0 +1,439 @@ +/* + * 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.jmx.mbeans; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verify; +import static org.mockito.Matchers.isNull; +import static org.mockito.Matchers.argThat; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Collections; + +import javax.management.ListenerNotFoundException; +import javax.management.Notification; +import javax.management.NotificationListener; +import javax.management.OperationsException; +import javax.management.openmbean.CompositeDataSupport; + +import org.apache.qpid.management.common.mbeans.ManagedQueue; +import org.apache.qpid.server.jmx.ManagedObjectRegistry; +import org.apache.qpid.server.jmx.mbeans.QueueMBean.GetMessageVisitor; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.model.Exchange; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.Statistics; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.queue.NotificationCheck; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.test.utils.QpidTestCase; +import org.mockito.ArgumentMatcher; +import org.mockito.Matchers; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +public class QueueMBeanTest extends QpidTestCase +{ + private static final String QUEUE_NAME = "QUEUE_NAME"; + private static final String QUEUE_DESCRIPTION = "QUEUE_DESCRIPTION"; + private static final String QUEUE_TYPE = "QUEUE_TYPE"; + private static final String QUEUE_ALTERNATE_EXCHANGE = "QUEUE_ALTERNATE_EXCHANGE"; + + private Queue _mockQueue; + private Statistics _mockQueueStatistics; + private VirtualHostMBean _mockVirtualHostMBean; + private ManagedObjectRegistry _mockManagedObjectRegistry; + private QueueMBean _queueMBean; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + _mockQueue = mock(Queue.class); + _mockQueueStatistics = mock(Statistics.class); + when(_mockQueue.getName()).thenReturn(QUEUE_NAME); + when(_mockQueue.getStatistics()).thenReturn(_mockQueueStatistics); + _mockVirtualHostMBean = mock(VirtualHostMBean.class); + + _mockManagedObjectRegistry = mock(ManagedObjectRegistry.class); + when(_mockVirtualHostMBean.getRegistry()).thenReturn(_mockManagedObjectRegistry); + + _queueMBean = new QueueMBean(_mockQueue, _mockVirtualHostMBean); + } + + public void testQueueName() + { + assertEquals(QUEUE_NAME, _queueMBean.getName()); + } + + /********** Statistics **********/ + + public void testGetMessageCount() throws Exception + { + assertStatistic("messageCount", 1000, Queue.QUEUE_DEPTH_MESSAGES); + } + + public void testGetReceivedMessageCount() throws Exception + { + assertStatistic("receivedMessageCount", 1000l, Queue.TOTAL_ENQUEUED_MESSAGES); + } + + public void testQueueDepth() throws Exception + { + assertStatistic("queueDepth", 4096l, Queue.QUEUE_DEPTH_BYTES); + } + + public void testActiveConsumerCount() throws Exception + { + assertStatistic("activeConsumerCount", 3, Queue.CONSUMER_COUNT_WITH_CREDIT); + } + + public void testConsumerCount() throws Exception + { + assertStatistic("consumerCount", 3, Queue.CONSUMER_COUNT); + } + + /********** Simple Attributes **********/ + + public void testGetQueueDescription() throws Exception + { + assertAttribute("description", QUEUE_DESCRIPTION, Queue.DESCRIPTION); + } + + public void testSetQueueDescription() throws Exception + { + testSetAttribute("description", Queue.DESCRIPTION, "descriptionold", "descriptionnew"); + } + + public void testQueueType() throws Exception + { + assertAttribute("queueType", QUEUE_TYPE, Queue.TYPE); + } + + public void testMaximumDeliveryCount() throws Exception + { + assertAttribute("maximumDeliveryCount", 5, Queue.MAXIMUM_DELIVERY_ATTEMPTS); + } + + public void testOwner() throws Exception + { + assertAttribute("owner", "testOwner", Queue.OWNER); + } + + public void testIsDurable() throws Exception + { + when(_mockQueue.isDurable()).thenReturn(true); + assertTrue(_queueMBean.isDurable()); + } + + public void testIsNotDurable() throws Exception + { + when(_mockQueue.isDurable()).thenReturn(false); + assertFalse(_queueMBean.isDurable()); + } + + public void testIsAutoDelete() throws Exception + { + when(_mockQueue.getLifetimePolicy()).thenReturn(LifetimePolicy.AUTO_DELETE); + assertTrue(_queueMBean.isAutoDelete()); + } + + public void testIsNotAutoDelete() throws Exception + { + when(_mockQueue.getLifetimePolicy()).thenReturn(LifetimePolicy.PERMANENT); + assertFalse(_queueMBean.isAutoDelete()); + } + + public void testGetMaximumMessageAge() throws Exception + { + assertAttribute("maximumMessageAge", 10000l, Queue.ALERT_THRESHOLD_MESSAGE_AGE); + } + + public void testSetMaximumMessageAge() throws Exception + { + testSetAttribute("maximumMessageAge", Queue.ALERT_THRESHOLD_MESSAGE_AGE, 1000l, 10000l); + } + + public void testGetMaximumMessageSize() throws Exception + { + assertAttribute("maximumMessageSize", 1024l, Queue.ALERT_THRESHOLD_MESSAGE_SIZE); + } + + public void testSetMaximumMessageSize() throws Exception + { + testSetAttribute("maximumMessageSize", Queue.ALERT_THRESHOLD_MESSAGE_SIZE, 1024l, 2048l); + } + + public void testGetMaximumMessageCount() throws Exception + { + assertAttribute("maximumMessageCount", 5000l, Queue.ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES); + } + + public void testSetMaximumMessageCount() throws Exception + { + testSetAttribute("maximumMessageCount", Queue.ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES, 4000l, 5000l); + } + + public void testGetMaximumQueueDepth() throws Exception + { + assertAttribute("maximumQueueDepth", 1048576l, Queue.ALERT_THRESHOLD_QUEUE_DEPTH_BYTES); + } + + public void testSetMaximumQueueDepth() throws Exception + { + testSetAttribute("maximumQueueDepth", Queue.ALERT_THRESHOLD_QUEUE_DEPTH_BYTES,1048576l , 2097152l); + } + + public void testGetCapacity() throws Exception + { + assertAttribute("capacity", 1048576l, Queue.QUEUE_FLOW_CONTROL_SIZE_BYTES); + } + + public void testSetCapacity() throws Exception + { + testSetAttribute("capacity", Queue.QUEUE_FLOW_CONTROL_SIZE_BYTES,1048576l , 2097152l); + } + + public void testGetFlowResumeCapacity() throws Exception + { + assertAttribute("flowResumeCapacity", 1048576l, Queue.QUEUE_FLOW_RESUME_SIZE_BYTES); + } + + public void testSetFlowResumeCapacity() throws Exception + { + testSetAttribute("flowResumeCapacity", Queue.QUEUE_FLOW_RESUME_SIZE_BYTES,1048576l , 2097152l); + } + + public void testIsExclusive() throws Exception + { + assertAttribute("exclusive", Boolean.TRUE, Queue.EXCLUSIVE); + } + + public void testIsNotExclusive() throws Exception + { + assertAttribute("exclusive", Boolean.FALSE, Queue.EXCLUSIVE); + } + + public void testSetExclusive() throws Exception + { + testSetAttribute("exclusive", Queue.EXCLUSIVE, Boolean.FALSE , Boolean.TRUE); + } + + /********** Other attributes **********/ + + public void testGetAlternateExchange() + { + Exchange mockAlternateExchange = mock(Exchange.class); + when(mockAlternateExchange.getName()).thenReturn(QUEUE_ALTERNATE_EXCHANGE); + + when(_mockQueue.getAttribute(Queue.ALTERNATE_EXCHANGE)).thenReturn(mockAlternateExchange); + + assertEquals(QUEUE_ALTERNATE_EXCHANGE, _queueMBean.getAlternateExchange()); + } + + public void testGetAlternateExchangeWhenQueueHasNone() + { + when(_mockQueue.getAttribute(Queue.ALTERNATE_EXCHANGE)).thenReturn(null); + + assertNull(_queueMBean.getAlternateExchange()); + } + + public void testSetAlternateExchange() throws Exception + { + Exchange mockExchange1 = mock(Exchange.class); + when(mockExchange1.getName()).thenReturn("exchange1"); + + Exchange mockExchange2 = mock(Exchange.class); + when(mockExchange2.getName()).thenReturn("exchange2"); + + Exchange mockExchange3 = mock(Exchange.class); + when(mockExchange3.getName()).thenReturn("exchange3"); + + VirtualHost mockVirtualHost = mock(VirtualHost.class); + when(mockVirtualHost.getExchanges()).thenReturn(Arrays.asList(new Exchange[] {mockExchange1, mockExchange2, mockExchange3})); + when(_mockQueue.getParent(VirtualHost.class)).thenReturn(mockVirtualHost); + + _queueMBean.setAlternateExchange("exchange2"); + verify(_mockQueue).setAttribute(Queue.ALTERNATE_EXCHANGE, null, mockExchange2); + } + + public void testSetAlternateExchangeWithUnknownExchangeName() throws Exception + { + Exchange mockExchange = mock(Exchange.class); + when(mockExchange.getName()).thenReturn("exchange1"); + + VirtualHost mockVirtualHost = mock(VirtualHost.class); + when(mockVirtualHost.getExchanges()).thenReturn(Collections.singletonList(mockExchange)); + when(_mockQueue.getParent(VirtualHost.class)).thenReturn(mockVirtualHost); + + try + { + _queueMBean.setAlternateExchange("notknown"); + fail("Exception not thrown"); + } + catch(OperationsException oe) + { + // PASS + } + } + + public void testRemoveAlternateExchange() throws Exception + { + _queueMBean.setAlternateExchange(""); + verify(_mockQueue).setAttribute(Queue.ALTERNATE_EXCHANGE, null, null); + } + + /********** Operations **********/ + + /********** Notifications **********/ + + public void testNotificationListenerCalled() throws Exception + { + NotificationListener listener = mock(NotificationListener.class); + _queueMBean.addNotificationListener(listener, null, null); + + NotificationCheck notification = mock(NotificationCheck.class); + String notificationMsg = "Test notification message"; + + _queueMBean.notifyClients(notification, _mockQueue, notificationMsg); + verify(listener).handleNotification(isNotificationWithMessage(notificationMsg), + isNull()); + } + + public void testAddRemoveNotificationListener() throws Exception + { + NotificationListener listener1 = mock(NotificationListener.class); + _queueMBean.addNotificationListener(listener1, null, null); + _queueMBean.removeNotificationListener(listener1); + } + + public void testRemoveUnknownNotificationListener() throws Exception + { + NotificationListener listener1 = mock(NotificationListener.class); + try + { + _queueMBean.removeNotificationListener(listener1); + fail("Exception not thrown"); + } + catch (ListenerNotFoundException e) + { + // PASS + } + } + + private Notification isNotificationWithMessage(final String expectedMessage) + { + return argThat( new ArgumentMatcher<Notification>() + { + @Override + public boolean matches(Object argument) + { + Notification actual = (Notification) argument; + return actual.getMessage().endsWith(expectedMessage); + } + }); + } + + private void assertStatistic(String jmxAttributeName, Object expectedValue, String underlyingAttributeName) throws Exception + { + when(_mockQueueStatistics.getStatistic(underlyingAttributeName)).thenReturn(expectedValue); + MBeanTestUtils.assertMBeanAttribute(_queueMBean, jmxAttributeName, expectedValue); + } + + private void assertAttribute(String jmxAttributeName, Object expectedValue, String underlyingAttributeName) throws Exception + { + when(_mockQueue.getAttribute(underlyingAttributeName)).thenReturn(expectedValue); + MBeanTestUtils.assertMBeanAttribute(_queueMBean, jmxAttributeName, expectedValue); + } + + private void testSetAttribute(String jmxAttributeName, String underlyingAttributeName, Object originalAttributeValue, Object newAttributeValue) throws Exception + { + when(_mockQueue.getAttribute(underlyingAttributeName)).thenReturn(originalAttributeValue); + + MBeanTestUtils.setMBeanAttribute(_queueMBean, jmxAttributeName, newAttributeValue); + + verify(_mockQueue).setAttribute(underlyingAttributeName, originalAttributeValue, newAttributeValue); + } + + public void testViewMessageContent() throws Exception + { + viewMessageContentTestImpl(16L, 1000, 1000); + } + + public void testViewMessageContentWithMissingPayload() throws Exception + { + viewMessageContentTestImpl(16L, 1000, 0); + } + + private void viewMessageContentTestImpl(final long messageNumber, + final int messageSize, + final int messageContentSize) throws Exception + { + final byte[] content = new byte[messageContentSize]; + + //mock message and queue entry to return a given message size, and have a given content + final ServerMessage<?> serverMessage = mock(ServerMessage.class); + when(serverMessage.getMessageNumber()).thenReturn(messageNumber); + when(serverMessage.getSize()).thenReturn((long)messageSize); + doAnswer(new Answer<Object>() + { + public Object answer(InvocationOnMock invocation) + { + Object[] args = invocation.getArguments(); + + //verify the arg types / expected values + assertEquals(2, args.length); + assertTrue(args[0] instanceof ByteBuffer); + assertTrue(args[1] instanceof Integer); + + ByteBuffer dest = (ByteBuffer) args[0]; + int offset = (Integer) args[1]; + assertEquals(0, offset); + + dest.put(content); + return messageContentSize; + } + }).when(serverMessage).getContent(Matchers.any(ByteBuffer.class), Matchers.anyInt()); + + final QueueEntry entry = mock(QueueEntry.class); + when(entry.getMessage()).thenReturn(serverMessage); + + //mock the queue.visit() method to ensure we match the mock message + doAnswer(new Answer<Object>() + { + public Object answer(InvocationOnMock invocation) + { + Object[] args = invocation.getArguments(); + GetMessageVisitor visitor = (GetMessageVisitor) args[0]; + visitor.visit(entry); + return null; + } + }).when(_mockQueue).visit(Matchers.any(GetMessageVisitor.class)); + + //now retrieve the content and verify its size + CompositeDataSupport comp = (CompositeDataSupport) _queueMBean.viewMessageContent(messageNumber); + assertNotNull(comp); + byte[] data = (byte[]) comp.get(ManagedQueue.CONTENT); + assertEquals(messageSize, data.length); + } +} diff --git a/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/ServerInformationMBeanTest.java b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/ServerInformationMBeanTest.java new file mode 100644 index 0000000000..86eab0245e --- /dev/null +++ b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/ServerInformationMBeanTest.java @@ -0,0 +1,94 @@ +/* + * 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.jmx.mbeans; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.qpid.server.jmx.ManagedObjectRegistry; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Connection; +import org.apache.qpid.server.model.Statistics; + +import junit.framework.TestCase; + +public class ServerInformationMBeanTest extends TestCase +{ + private ManagedObjectRegistry _mockManagedObjectRegistry; + private Broker _mockBroker; + private Statistics _mockBrokerStatistics; + private ServerInformationMBean _mbean; + + @Override + protected void setUp() throws Exception + { + _mockManagedObjectRegistry = mock(ManagedObjectRegistry.class); + _mockBroker = mock(Broker.class); + _mockBrokerStatistics = mock(Statistics.class); + when(_mockBroker.getStatistics()).thenReturn(_mockBrokerStatistics); + + _mbean = new ServerInformationMBean(_mockManagedObjectRegistry, _mockBroker); + } + + public void testMBeanRegistersItself() throws Exception + { + ServerInformationMBean mbean = new ServerInformationMBean(_mockManagedObjectRegistry, _mockBroker); + verify(_mockManagedObjectRegistry).registerObject(mbean); + } + + /********** Statistics **********/ + + public void testGetMessageCount() throws Exception + { + assertStatistic("totalDataDelivered", 16384l, Connection.BYTES_OUT); + } + + /********** Attributes **********/ + + public void testBuildVersion() throws Exception + { + assertAttribute("buildVersion", "0.0.1", Broker.BUILD_VERSION); + } + + public void testProductVersion() throws Exception + { + assertAttribute("productVersion", "0.0.1", Broker.PRODUCT_VERSION); + } + + /********** Other Attributes **********/ + + public void testIsStatisticsEnabled() throws Exception + { + assertTrue("isStatisticsEnabled", _mbean.isStatisticsEnabled()); + } + + private void assertStatistic(String jmxAttributeName, Object expectedValue, String underlyingAttributeName) throws Exception + { + when(_mockBrokerStatistics.getStatistic(underlyingAttributeName)).thenReturn(expectedValue); + MBeanTestUtils.assertMBeanAttribute(_mbean, jmxAttributeName, expectedValue); + } + + private void assertAttribute(String jmxAttributeName, Object expectedValue, String underlyingAttributeName) throws Exception + { + when(_mockBroker.getAttribute(underlyingAttributeName)).thenReturn(expectedValue); + MBeanTestUtils.assertMBeanAttribute(_mbean, jmxAttributeName, expectedValue); + } +} diff --git a/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/UserManagementMBeanTest.java b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/UserManagementMBeanTest.java new file mode 100644 index 0000000000..8ca6c521eb --- /dev/null +++ b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/UserManagementMBeanTest.java @@ -0,0 +1,157 @@ +/* + * + * 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.jmx.mbeans; + +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; + +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.TabularData; +import javax.security.auth.login.AccountNotFoundException; + +import junit.framework.TestCase; + +import org.apache.qpid.management.common.mbeans.UserManagement; +import org.apache.qpid.server.jmx.ManagedObjectRegistry; +import org.apache.qpid.server.model.PasswordCredentialManagingAuthenticationProvider; + +public class UserManagementMBeanTest extends TestCase +{ + private UserManagementMBean _userManagement; + private ManagedObjectRegistry _mockRegistry; + private PasswordCredentialManagingAuthenticationProvider _mockProvider; + + private static final String TEST_USERNAME = "testuser"; + private static final String TEST_PASSWORD = "password"; + + @Override + public void setUp() throws Exception + { + super.setUp(); + + _mockProvider = mock(PasswordCredentialManagingAuthenticationProvider.class); + _mockRegistry = mock(ManagedObjectRegistry.class); + _userManagement = new UserManagementMBean(_mockProvider, _mockRegistry); + } + + public void testMBeanRegistersItself() throws Exception + { + UserManagementMBean userManagementMBean = new UserManagementMBean(_mockProvider, _mockRegistry); + verify(_mockRegistry).registerObject(userManagementMBean); + } + + public void testDeleteUser() throws Exception + { + boolean deleteSuccess = _userManagement.deleteUser(TEST_USERNAME); + assertTrue("Expected successful delete", deleteSuccess); + + verify(_mockProvider).deleteUser(TEST_USERNAME); + } + + public void testDeleteUserWhereUserDoesNotExist() throws Exception + { + doThrow(AccountNotFoundException.class).when(_mockProvider).deleteUser(TEST_USERNAME); + + boolean deleteSuccess = _userManagement.deleteUser(TEST_USERNAME); + assertFalse("Expected unsuccessful delete", deleteSuccess); + } + + public void testCreateUser() throws Exception + { + when(_mockProvider.createUser(TEST_USERNAME, TEST_PASSWORD, null)).thenReturn(true); + + boolean createSuccess = _userManagement.createUser(TEST_USERNAME, TEST_PASSWORD); + assertTrue(createSuccess); + } + + public void testCreateUserWhereUserAlreadyExists() + { + when(_mockProvider.createUser(TEST_USERNAME, TEST_PASSWORD, null)).thenReturn(false); + + boolean createSuccess = _userManagement.createUser(TEST_USERNAME, TEST_PASSWORD); + assertFalse(createSuccess); + } + + public void testSetPassword() throws Exception + { + boolean setPasswordSuccess = _userManagement.setPassword(TEST_USERNAME, TEST_PASSWORD); + assertTrue(setPasswordSuccess); + + assertTrue("Set password should return true to flag successful change", setPasswordSuccess); + + verify(_mockProvider).setPassword(TEST_USERNAME, TEST_PASSWORD); + } + + public void testSetPasswordWhereUserDoesNotExist() throws Exception + { + doThrow(AccountNotFoundException.class).when(_mockProvider).setPassword(TEST_USERNAME, TEST_PASSWORD); + + boolean setPasswordSuccess = _userManagement.setPassword(TEST_USERNAME, TEST_PASSWORD); + + assertFalse("Set password should return false to flag unsuccessful change", setPasswordSuccess); + } + + public void testReload() throws Exception + { + boolean reloadSuccess = _userManagement.reloadData(); + + assertTrue("Reload should return true to flag succesful update", reloadSuccess); + + verify(_mockProvider).reload(); + } + + public void testReloadFails() throws Exception + { + doThrow(IOException.class).when(_mockProvider).reload(); + + boolean reloadSuccess = _userManagement.reloadData(); + + assertFalse("Expected reload to fail", reloadSuccess); + } + + public void testViewUsers() throws Exception + { + Map<String,String> args = Collections.emptyMap(); + when(_mockProvider.getUsers()).thenReturn(Collections.singletonMap(TEST_USERNAME, args)); + + TabularData userList = _userManagement.viewUsers(); + + assertNotNull(userList); + assertEquals("Unexpected number of users in user list", 1, userList.size()); + assertTrue(userList.containsKey(new Object[]{TEST_USERNAME})); + + // Check the deprecated read, write and admin items continue to exist but return false. + CompositeData userRec = userList.get(new Object[]{TEST_USERNAME}); + assertTrue(userRec.containsKey(UserManagement.RIGHTS_READ_ONLY)); + assertEquals(false, userRec.get(UserManagement.RIGHTS_READ_ONLY)); + assertEquals(false, userRec.get(UserManagement.RIGHTS_READ_WRITE)); + assertTrue(userRec.containsKey(UserManagement.RIGHTS_READ_WRITE)); + assertTrue(userRec.containsKey(UserManagement.RIGHTS_ADMIN)); + assertEquals(false, userRec.get(UserManagement.RIGHTS_ADMIN)); + } +} diff --git a/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/VirtualHostManagerMBeanTest.java b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/VirtualHostManagerMBeanTest.java new file mode 100644 index 0000000000..e3fac9f711 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/VirtualHostManagerMBeanTest.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.server.jmx.mbeans; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.Map; + +import javax.management.OperationsException; + +import junit.framework.TestCase; + +import org.apache.qpid.server.jmx.ManagedObjectRegistry; +import org.apache.qpid.server.model.Exchange; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.queue.AMQQueueFactory; + +public class VirtualHostManagerMBeanTest extends TestCase +{ + private static final String TEST_QUEUE_NAME = "QUEUE_NAME"; + private static final String TEST_EXCHANGE_NAME = "EXCHANGE_NAME"; + private static final String TEST_OWNER = "OWNER"; + private static final String TEST_DESCRIPTION = "DESCRIPTION"; + private static final String TEST_EXCHANGE_TYPE = "EXCHANGE_TYPE"; + + private static final Map<String, Object> EMPTY_ARGUMENT_MAP = Collections.emptyMap(); + + private VirtualHost _mockVirtualHost; + private ManagedObjectRegistry _mockManagedObjectRegistry; + private VirtualHostManagerMBean _virtualHostManagerMBean; + + @Override + protected void setUp() throws Exception + { + _mockVirtualHost = mock(VirtualHost.class); + when(_mockVirtualHost.getExchangeTypes()).thenReturn(Collections.singletonList(TEST_EXCHANGE_TYPE)); + + _mockManagedObjectRegistry = mock(ManagedObjectRegistry.class); + + _virtualHostManagerMBean = new VirtualHostManagerMBean(new VirtualHostMBean(_mockVirtualHost, _mockManagedObjectRegistry)); + } + + public void testCreateQueueWithNoOwner() throws Exception + { + _virtualHostManagerMBean.createNewQueue(TEST_QUEUE_NAME, null, true); + + verify(_mockVirtualHost).createQueue(TEST_QUEUE_NAME, State.ACTIVE, true, false, LifetimePolicy.PERMANENT, 0, EMPTY_ARGUMENT_MAP); + } + + /** + * Some users have been abusing the owner parameter as a description. Decision has been taken to map this parameter + * through to the description field (if the description field is passed, the owner is discarded). + */ + public void testCreateQueueWithOwnerMappedThroughToDescription() throws Exception + { + _virtualHostManagerMBean.createNewQueue(TEST_QUEUE_NAME, TEST_OWNER, true); + + Map<String, Object> expectedArguments = Collections.singletonMap(AMQQueueFactory.X_QPID_DESCRIPTION, (Object)TEST_OWNER); + verify(_mockVirtualHost).createQueue(TEST_QUEUE_NAME, State.ACTIVE, true, false, LifetimePolicy.PERMANENT, 0, expectedArguments); + } + + public void testCreateQueueWithOwnerAndDescriptionDiscardsOwner() throws Exception + { + Map<String, Object> arguments = Collections.singletonMap(AMQQueueFactory.X_QPID_DESCRIPTION, (Object)TEST_DESCRIPTION); + _virtualHostManagerMBean.createNewQueue(TEST_QUEUE_NAME, TEST_OWNER, true, arguments); + + Map<String, Object> expectedArguments = Collections.singletonMap(AMQQueueFactory.X_QPID_DESCRIPTION, (Object)TEST_DESCRIPTION); + verify(_mockVirtualHost).createQueue(TEST_QUEUE_NAME, State.ACTIVE, true, false, LifetimePolicy.PERMANENT, 0, expectedArguments); + } + + public void testDeleteQueue() throws Exception + { + Queue mockQueue = mock(Queue.class); + when(mockQueue.getName()).thenReturn("queue1"); + when(_mockVirtualHost.getQueues()).thenReturn(Collections.singletonList(mockQueue)); + + _virtualHostManagerMBean.deleteQueue("queue1"); + verify(mockQueue).delete(); + } + + public void testDeleteQueueWhenQueueDoesNotExist() throws Exception + { + Queue mockQueue = mock(Queue.class); + when(mockQueue.getName()).thenReturn("queue1"); + when(_mockVirtualHost.getQueues()).thenReturn(Collections.singletonList(mockQueue)); + + try + { + _virtualHostManagerMBean.deleteQueue("unknownqueue"); + fail("Exception not thrown"); + } + catch(OperationsException oe) + { + // PASS + assertEquals("No such queue \"unknownqueue\"", oe.getMessage()); + } + verify(mockQueue, never()).delete(); + } + + public void testCreateNewDurableExchange() throws Exception + { + _virtualHostManagerMBean.createNewExchange(TEST_EXCHANGE_NAME, TEST_EXCHANGE_TYPE, true); + verify(_mockVirtualHost).createExchange(TEST_EXCHANGE_NAME, State.ACTIVE, true, LifetimePolicy.PERMANENT, 0, TEST_EXCHANGE_TYPE, EMPTY_ARGUMENT_MAP); + } + + public void testCreateNewExchangeWithUnknownExchangeType() throws Exception + { + String exchangeType = "notknown"; + try + { + _virtualHostManagerMBean.createNewExchange(TEST_EXCHANGE_NAME, exchangeType, true); + fail("Exception not thrown"); + } + catch (OperationsException oe) + { + // PASS + } + verify(_mockVirtualHost, never()).createExchange(TEST_EXCHANGE_NAME, State.ACTIVE, true, LifetimePolicy.PERMANENT, 0, exchangeType, EMPTY_ARGUMENT_MAP); + } + + public void testUnregisterExchange() throws Exception + { + Exchange mockExchange = mock(Exchange.class); + when(mockExchange.getName()).thenReturn("exchange1"); + when(_mockVirtualHost.getExchanges()).thenReturn(Collections.singletonList(mockExchange)); + + _virtualHostManagerMBean.unregisterExchange("exchange1"); + verify(mockExchange).delete(); + } + + public void testUnregisterExchangeWhenExchangeDoesNotExist() throws Exception + { + Exchange mockExchange = mock(Exchange.class); + when(mockExchange.getName()).thenReturn("exchange1"); + when(_mockVirtualHost.getExchanges()).thenReturn(Collections.singletonList(mockExchange)); + + try + { + _virtualHostManagerMBean.unregisterExchange("unknownexchange"); + fail("Exception not thrown"); + } + catch(OperationsException oe) + { + // PASS + assertEquals("No such exchange \"unknownexchange\"", oe.getMessage()); + } + + verify(mockExchange, never()).delete(); + } +} diff --git a/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/BrokerManagementTest.java b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/BrokerManagementTest.java new file mode 100644 index 0000000000..7473a4d3e7 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/BrokerManagementTest.java @@ -0,0 +1,124 @@ +/* + * 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.systest.management.jmx; + +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.management.common.mbeans.ManagedBroker; +import org.apache.qpid.management.common.mbeans.ManagedExchange; +import org.apache.qpid.test.utils.JMXTestUtils; +import org.apache.qpid.test.utils.QpidBrokerTestCase; + +/** + * Tests the JMX API for the Managed Broker. + * + */ +public class BrokerManagementTest extends QpidBrokerTestCase +{ + private static final String VIRTUAL_HOST = "test"; + + /** + * JMX helper. + */ + private JMXTestUtils _jmxUtils; + private ManagedBroker _managedBroker; + + public void setUp() throws Exception + { + _jmxUtils = new JMXTestUtils(this); + _jmxUtils.setUp(); + super.setUp(); + _jmxUtils.open(); + _managedBroker = _jmxUtils.getManagedBroker(VIRTUAL_HOST); + } + + public void tearDown() throws Exception + { + if (_jmxUtils != null) + { + _jmxUtils.close(); + } + super.tearDown(); + } + + /** + * Tests queue creation/deletion also verifying the automatic binding to the default exchange. + */ + public void testCreateQueueAndDeletion() throws Exception + { + final String queueName = getTestQueueName(); + final ManagedExchange defaultExchange = _jmxUtils.getManagedExchange(ExchangeDefaults.DEFAULT_EXCHANGE_NAME.asString()); + + // Check that bind does not exist before queue creation + assertFalse("Binding to " + queueName + " should not exist in default exchange before queue creation", + defaultExchange.bindings().containsKey(new String[] {queueName})); + + _managedBroker.createNewQueue(queueName, "testowner", true); + + // Ensure the queue exists + assertNotNull("Queue object name expected to exist", _jmxUtils.getQueueObjectName(VIRTUAL_HOST, queueName)); + assertNotNull("Manager queue expected to be available", _jmxUtils.getManagedQueue(queueName)); + + // Now verify that the default exchange has been bound. + assertTrue("Binding to " + queueName + " should exist in default exchange after queue creation", + defaultExchange.bindings().containsKey(new String[] {queueName})); + + // Now delete the queue + _managedBroker.deleteQueue(queueName); + + // Finally ensure that the binding has been removed. + assertFalse("Binding to " + queueName + " should not exist in default exchange after queue deletion", + defaultExchange.bindings().containsKey(new String[] {queueName})); + } + + /** + * Tests exchange creation/deletion via JMX API. + */ + public void testCreateExchangeAndUnregister() throws Exception + { + String exchangeName = getTestName(); + _managedBroker.createNewExchange(exchangeName, "topic", true); + + ManagedExchange exchange = _jmxUtils.getManagedExchange(exchangeName); + assertNotNull("Exchange should exist", exchange); + + _managedBroker.unregisterExchange(exchangeName); + } + + /** + * Tests that it is disallowed to unregister the default exchange. + */ + public void testUnregisterOfDefaultExchangeDisallowed() throws Exception + { + String defaultExchangeName = ExchangeDefaults.DEFAULT_EXCHANGE_NAME.asString(); + + try + { + _managedBroker.unregisterExchange(defaultExchangeName); + fail("Exception not thrown"); + } + catch (UnsupportedOperationException e) + { + // PASS + assertEquals("'<<default>>' is a reserved exchange and can't be deleted", e.getMessage()); + } + final ManagedExchange defaultExchange = _jmxUtils.getManagedExchange(defaultExchangeName); + assertNotNull("Exchange should exist", defaultExchange); + } + +} diff --git a/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/ConnectionManagementTest.java b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/ConnectionManagementTest.java new file mode 100644 index 0000000000..28d7bf4aed --- /dev/null +++ b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/ConnectionManagementTest.java @@ -0,0 +1,283 @@ +/* + * 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.systest.management.jmx; + +import java.io.IOException; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import javax.jms.Connection; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.Queue; +import javax.jms.Session; +import javax.management.JMException; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.TabularData; + +import org.apache.commons.lang.StringUtils; +import org.apache.qpid.common.QpidProperties; +import org.apache.qpid.management.common.mbeans.ManagedConnection; +import org.apache.qpid.management.common.mbeans.ManagedQueue; +import org.apache.qpid.test.utils.JMXTestUtils; +import org.apache.qpid.test.utils.QpidBrokerTestCase; + +public class ConnectionManagementTest extends QpidBrokerTestCase +{ + private static final String VIRTUAL_HOST_NAME = "test"; + + private JMXTestUtils _jmxUtils; + private Connection _connection; + + public void setUp() throws Exception + { + _jmxUtils = new JMXTestUtils(this); + _jmxUtils.setUp(); // modifies broker config therefore must be done before super.setUp() + super.setUp(); + _jmxUtils.open(); + } + + public void tearDown() throws Exception + { + try + { + if (_jmxUtils != null) + { + _jmxUtils.close(); + } + } + finally + { + super.tearDown(); + } + } + + public void testNumberOfManagedConnectionsMatchesNumberOfClientConnections() throws Exception + { + assertEquals("Expected no managed connections", 0, getManagedConnections().size()); + + _connection = getConnection(); + assertEquals("Expected one managed connection", 1, getManagedConnections().size()); + + _connection.close(); + assertEquals("Expected no managed connections after client connection closed", 0, getManagedConnections().size()); + } + + public void testGetAttributes() throws Exception + { + _connection = getConnection(); + final ManagedConnection mBean = getConnectionMBean(); + + checkAuthorisedId(mBean); + checkClientVersion(mBean); + checkClientId(mBean); + } + + public void testNonTransactedSession() throws Exception + { + _connection = getConnection(); + + boolean transactional = false; + boolean flowBlocked = false; + + _connection.createSession(transactional, Session.AUTO_ACKNOWLEDGE); + + final ManagedConnection mBean = getConnectionMBean(); + final CompositeDataSupport row = getTheOneChannelRow(mBean); + assertChannelRowData(row, 0, transactional, flowBlocked); + } + + public void testTransactedSessionWithUnackMessages() throws Exception + { + _connection = getConnection(); + _connection.start(); + + boolean transactional = true; + int numberOfMessages = 2; + final Session session = _connection.createSession(transactional, Session.SESSION_TRANSACTED); + final Destination destination = session.createQueue(getTestQueueName()); + final MessageConsumer consumer = session.createConsumer(destination); + + sendMessage(session, destination, numberOfMessages); + receiveMessagesWithoutCommit(consumer, numberOfMessages); + + final ManagedConnection mBean = getConnectionMBean(); + final CompositeDataSupport row = getTheOneChannelRow(mBean); + boolean flowBlocked = false; + assertChannelRowData(row, numberOfMessages, transactional, flowBlocked); + + // check that commit advances the lastIoTime + final Date initialLastIOTime = mBean.getLastIoTime(); + session.commit(); + assertTrue("commit should have caused last IO time to advance", mBean.getLastIoTime().after(initialLastIOTime)); + + // check that channels() now returns one session with no unacknowledged messages + final CompositeDataSupport rowAfterCommit = getTheOneChannelRow(mBean); + final Number unackCountAfterCommit = (Number) rowAfterCommit.get(ManagedConnection.UNACKED_COUNT); + assertEquals("Unexpected number of unacknowledged messages", 0, unackCountAfterCommit); + } + + + public void testProducerFlowBlocked() throws Exception + { + _connection = getConnection(); + _connection.start(); + + String queueName = getTestQueueName(); + Session session = _connection.createSession(true, Session.SESSION_TRANSACTED); + Queue queue = session.createQueue(queueName); + createQueueOnBroker(session, queue); + + ManagedQueue managedQueue = _jmxUtils.getManagedQueue(queueName); + managedQueue.setFlowResumeCapacity(DEFAULT_MESSAGE_SIZE * 2l); + managedQueue.setCapacity(DEFAULT_MESSAGE_SIZE * 3l); + + final ManagedConnection managedConnection = getConnectionMBean(); + + // Check that producer flow is not block before test + final CompositeDataSupport rowBeforeSend = getTheOneChannelRow(managedConnection); + assertFlowBlocked(rowBeforeSend, false); + + + // Check that producer flow does not become block too soon + sendMessage(session, queue, 3); + final CompositeDataSupport rowBeforeFull = getTheOneChannelRow(managedConnection); + assertFlowBlocked(rowBeforeFull, false); + + // Fourth message will over-fill the queue (but as we are not sending more messages, client thread wont't block) + sendMessage(session, queue, 1); + final CompositeDataSupport rowAfterFull = getTheOneChannelRow(managedConnection); + assertFlowBlocked(rowAfterFull, true); + + // Consume two to bring the queue down to the resume capacity + MessageConsumer consumer = session.createConsumer(queue); + assertNotNull("Could not receive first message", consumer.receive(1000)); + assertNotNull("Could not receive second message", consumer.receive(1000)); + session.commit(); + + // Check that producer flow is no longer blocked + final CompositeDataSupport rowAfterReceive = getTheOneChannelRow(managedConnection); + assertFlowBlocked(rowAfterReceive, false); + } + + private void createQueueOnBroker(Session session, Destination destination) throws JMSException + { + session.createConsumer(destination).close(); // Create a consumer only to cause queue creation + } + + private void assertChannelRowData(final CompositeData row, int unacknowledgedMessages, boolean isTransactional, boolean flowBlocked) + { + assertNotNull(row); + assertEquals("Unexpected transactional flag", isTransactional, row.get(ManagedConnection.TRANSACTIONAL)); + assertEquals("Unexpected unacknowledged message count", unacknowledgedMessages, row.get(ManagedConnection.UNACKED_COUNT)); + assertEquals("Unexpected flow blocked", flowBlocked, row.get(ManagedConnection.FLOW_BLOCKED)); + } + + private void assertFlowBlocked(final CompositeData row, boolean flowBlocked) + { + assertNotNull(row); + assertEquals("Unexpected flow blocked", flowBlocked, row.get(ManagedConnection.FLOW_BLOCKED)); + } + + private void checkAuthorisedId(ManagedConnection mBean) throws Exception + { + assertEquals("Unexpected authorized id", GUEST_USERNAME, mBean.getAuthorizedId()); + } + + private void checkClientVersion(ManagedConnection mBean) throws Exception + { + String expectedVersion = QpidProperties.getReleaseVersion(); + assertTrue(StringUtils.isNotBlank(expectedVersion)); + + assertEquals("Unexpected version", expectedVersion, mBean.getVersion()); + } + + private void checkClientId(ManagedConnection mBean) throws Exception + { + String expectedClientId = _connection.getClientID(); + assertTrue(StringUtils.isNotBlank(expectedClientId)); + + assertEquals("Unexpected ClientId", expectedClientId, mBean.getClientId()); + } + + private ManagedConnection getConnectionMBean() + { + List<ManagedConnection> connections = getManagedConnections(); + assertNotNull("Connection MBean is not found", connections); + assertEquals("Unexpected number of connection mbeans", 1, connections.size()); + final ManagedConnection mBean = connections.get(0); + assertNotNull("Connection MBean is null", mBean); + return mBean; + } + + private List<ManagedConnection> getManagedConnections() + { + return _jmxUtils.getManagedConnections(VIRTUAL_HOST_NAME); + } + + private CompositeDataSupport getTheOneChannelRow(final ManagedConnection mBean) throws Exception + { + TabularData channelsData = getChannelsDataWithRetry(mBean); + + assertEquals("Unexpected number of rows in channel table", 1, channelsData.size()); + + @SuppressWarnings("unchecked") + final Iterator<CompositeDataSupport> rowItr = (Iterator<CompositeDataSupport>) channelsData.values().iterator(); + final CompositeDataSupport row = rowItr.next(); + return row; + } + + private void receiveMessagesWithoutCommit(final MessageConsumer consumer, int numberOfMessages) throws Exception + { + for (int i = 0; i < numberOfMessages; i++) + { + final Message m = consumer.receive(1000l); + assertNotNull("Message " + i + " is not received", m); + } + } + + private TabularData getChannelsDataWithRetry(final ManagedConnection mBean) + throws IOException, JMException + { + TabularData channelsData = mBean.channels(); + int retries = 0; + while(channelsData.size() == 0 && retries < 5) + { + sleep(); + channelsData = mBean.channels(); + retries++; + } + return channelsData; + } + + private void sleep() + { + try + { + Thread.sleep(50); + } + catch (InterruptedException ie) + { + Thread.currentThread().interrupt(); + } + }} diff --git a/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/LoggingManagementTest.java b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/LoggingManagementTest.java new file mode 100644 index 0000000000..ac6730638e --- /dev/null +++ b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/LoggingManagementTest.java @@ -0,0 +1,149 @@ +/* + * 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.systest.management.jmx; + +import java.io.File; +import java.util.List; + +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.TabularData; + +import org.apache.qpid.management.common.mbeans.LoggingManagement; +import org.apache.qpid.server.jmx.mbeans.LoggingManagementMBeanTest; +import org.apache.qpid.server.logging.log4j.LoggingFacadeTest; +import org.apache.qpid.test.utils.JMXTestUtils; +import org.apache.qpid.test.utils.QpidBrokerTestCase; +import org.apache.qpid.util.FileUtils; +import org.apache.qpid.util.LogMonitor; + +/** + * System test for Logging Management. <b>These tests rely on value set within + * test-profiles/log4j-test.xml</b>. + * + * @see LoggingManagementMBeanTest + * @see LoggingFacadeTest + * + */ +public class LoggingManagementTest extends QpidBrokerTestCase +{ + private JMXTestUtils _jmxUtils; + private LoggingManagement _loggingManagement; + private LogMonitor _monitor; + + public void setUp() throws Exception + { + _jmxUtils = new JMXTestUtils(this); + _jmxUtils.setUp(); + + // System test normally run with log for4j test config from beneath test-profiles. We need to + // copy it as some of our tests write to this file. + + File tmpLogFile = File.createTempFile("log4j" + "." + getName(), ".xml"); + tmpLogFile.deleteOnExit(); + FileUtils.copy(_logConfigFile, tmpLogFile); + + _logConfigFile = tmpLogFile; + + super.setUp(); + _jmxUtils.open(); + + _loggingManagement = _jmxUtils.getLoggingManagement(); + _monitor = new LogMonitor(_outputFile); + } + + public void tearDown() throws Exception + { + try + { + if (_jmxUtils != null) + { + _jmxUtils.close(); + } + } + finally + { + super.tearDown(); + } + } + + public void testViewEffectiveRuntimeLoggerLevels() throws Exception + { + final String qpidMainLogger = "org.apache.qpid"; + + TabularData table = _loggingManagement.viewEffectiveRuntimeLoggerLevels(); + final CompositeData row = table.get(new String[] {qpidMainLogger} ); + assertChannelRow(row, qpidMainLogger, "DEBUG"); + } + + public void testViewConfigFileLoggerLevels() throws Exception + { + final String operationalLoggingLogger = "qpid.message"; + + TabularData table = _loggingManagement.viewConfigFileLoggerLevels(); + final CompositeData row = table.get(new String[] {operationalLoggingLogger} ); + assertChannelRow(row, operationalLoggingLogger, "INFO"); + } + + public void testTurnOffOrgApacheQpidAtRuntime() throws Exception + { + final String logger = "org.apache.qpid"; + _monitor.markDiscardPoint(); + _loggingManagement.setRuntimeLoggerLevel(logger, "OFF"); + + List<String> matches = _monitor.findMatches("Setting level to OFF for logger 'org.apache.qpid'"); + assertEquals(1, matches.size()); + + TabularData table = _loggingManagement.viewEffectiveRuntimeLoggerLevels(); + final CompositeData row1 = table.get(new String[] {logger} ); + assertChannelRow(row1, logger, "OFF"); + } + + public void testChangesToConfigFileBecomeEffectiveAfterReload() throws Exception + { + final String operationalLoggingLogger = "qpid.message"; + assertEffectiveLoggingLevel(operationalLoggingLogger, "INFO"); + + _monitor.markDiscardPoint(); + _loggingManagement.setConfigFileLoggerLevel(operationalLoggingLogger, "OFF"); + + List<String> matches = _monitor.findMatches("Setting level to OFF for logger 'qpid.message'"); + assertEquals(1, matches.size()); + + assertEffectiveLoggingLevel(operationalLoggingLogger, "INFO"); + + _loggingManagement.reloadConfigFile(); + + assertEffectiveLoggingLevel(operationalLoggingLogger, "OFF"); + } + + private void assertEffectiveLoggingLevel(String operationalLoggingLogger, String expectedLevel) + { + TabularData table = _loggingManagement.viewEffectiveRuntimeLoggerLevels(); + final CompositeData row1 = table.get(new String[] {operationalLoggingLogger} ); + assertChannelRow(row1, operationalLoggingLogger, expectedLevel); + } + + private void assertChannelRow(final CompositeData row, String logger, String level) + { + assertNotNull("No row for " + logger, row); + assertEquals("Unexpected logger name", logger, row.get(LoggingManagement.LOGGER_NAME)); + assertEquals("Unexpected level", level, row.get(LoggingManagement.LOGGER_LEVEL)); + } + +} diff --git a/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/ManagementActorLoggingTest.java b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/ManagementActorLoggingTest.java new file mode 100644 index 0000000000..47b38381c5 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/ManagementActorLoggingTest.java @@ -0,0 +1,480 @@ +/* + * + * 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.systest.management.jmx; + +import org.apache.qpid.management.common.mbeans.ManagedBroker; +import org.apache.qpid.management.common.mbeans.ManagedConnection; +import org.apache.qpid.management.common.mbeans.ManagedExchange; +import org.apache.qpid.server.logging.AbstractTestLogging; +import org.apache.qpid.server.logging.subjects.AbstractTestLogSubject; +import org.apache.qpid.test.utils.JMXTestUtils; + +import javax.jms.Connection; +import javax.jms.ExceptionListener; +import javax.jms.JMSException; +import javax.management.JMException; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Test class to test if any change in the broker JMX code is affesting the management console + * There are some hardcoding of management feature names and parameter names to create a customized + * look in the console. + */ +public class ManagementActorLoggingTest extends AbstractTestLogging +{ + private JMXTestUtils _jmxUtils; + private boolean _closed = false; + + @Override + public void setUp() throws Exception + { + _jmxUtils = new JMXTestUtils(this); + _jmxUtils.setUp(); + super.setUp(); + _jmxUtils.open(); + } + + @Override + public void tearDown() throws Exception + { + if(!_closed) + { + _jmxUtils.close(); + } + super.tearDown(); + } + + /** + * Description: + * When a connected client has its connection closed via the Management Console this will be logged as a CON-1002 message. + * Input: + * + * 1. Running Broker + * 2. Connected Client + * 3. Connection is closed via Management Console + * Output: + * + * <date> CON-1002 : Close + * + * Validation Steps: + * 4. The CON ID is correct + * 5. This must be the last CON message for the Connection + * 6. It must be preceded by a CON-1001 for this Connection + * + * @throws Exception - {@see ManagedConnection.closeConnection and #getConnection} + * @throws java.io.IOException - if there is a problem reseting the log monitor + */ + public void testConnectionCloseViaManagement() throws IOException, Exception + { + //Create a connection to the broker + Connection connection = getConnection(); + + // Monitor the connection for an exception being thrown + // this should be a DisconnectionException but it is not this tests + // job to valiate that. Only use the exception as a synchronisation + // to check the log file for the Close message + final CountDownLatch exceptionReceived = new CountDownLatch(1); + connection.setExceptionListener(new ExceptionListener() + { + public void onException(JMSException e) + { + //Failover being attempted. + exceptionReceived.countDown(); + } + }); + + //Remove the connection close from any 0-10 connections + _monitor.markDiscardPoint(); + + // Get a managedConnection + ManagedConnection mangedConnection = _jmxUtils.getManagedObject(ManagedConnection.class, "org.apache.qpid:type=VirtualHost.Connection,*"); + + //Close the connection + mangedConnection.closeConnection(); + + //Wait for the connection to close + assertTrue("Timed out waiting for conneciton to report close", + exceptionReceived.await(2, TimeUnit.SECONDS)); + + //Validate results + List<String> results = waitAndFindMatches("CON-1002"); + + assertEquals("Unexpected Connection Close count", 1, results.size()); + } + + /** + * Description: + * Exchange creation is possible from the Management Console. + * When an exchanged is created in this way then a EXH-1001 create message + * is expected to be logged. + * Input: + * + * 1. Running broker + * 2. Connected Management Console + * 3. Exchange Created via Management Console + * Output: + * + * EXH-1001 : Create : [Durable] Type:<value> Name:<value> + * + * Validation Steps: + * 4. The EXH ID is correct + * 5. The correct tags are present in the message based on the create options + * + * @throws java.io.IOException - if there is a problem reseting the log monitor + * @throws javax.management.JMException - {@see #createQueue and ManagedExchange.deleteQueue} + */ + public void testCreateExchangeDirectTransientViaManagementConsole() throws IOException, JMException + { + _monitor.markDiscardPoint(); + + _jmxUtils.createExchange("test", getName(), "direct", false); + + // Validate + + //1 - ID is correct + List<String> results = waitAndFindMatches("EXH-1001"); + + assertEquals("More than one exchange creation found", 1, results.size()); + + String log = getLogMessage(results, 0); + + // Validate correct exchange name + assertTrue("Incorrect exchange name created:" + log, log.endsWith(getName())); + + // Validate it was a management actor. + String actor = fromActor(log); + assertTrue("Actor is not a manangement actor:" + actor, actor.startsWith("mng")); + } + + public void testCreateExchangeTopicTransientViaManagementConsole() throws IOException, JMException + { + //Remove any previous exchange declares + _monitor.markDiscardPoint(); + + _jmxUtils.createExchange("test", getName(), "topic", false); + + // Validate + + //1 - ID is correct + List<String> results = waitAndFindMatches("EXH-1001"); + + assertEquals("More than one exchange creation found", 1, results.size()); + + String log = getLogMessage(results, 0); + + // Validate correct exchange name + assertTrue("Incorrect exchange name created:" + log, log.endsWith(getName())); + + // Validate it was a management actor. + String actor = fromActor(log); + assertTrue("Actor is not a manangement actor:" + actor, actor.startsWith("mng")); + + } + + public void testCreateExchangeFanoutTransientViaManagementConsole() throws IOException, JMException + { + //Remove any previous exchange declares + _monitor.markDiscardPoint(); + + _jmxUtils.createExchange("test", getName(), "fanout", false); + + // Validate + + //1 - ID is correct + List<String> results = waitAndFindMatches("EXH-1001"); + + assertEquals("More than one exchange creation found", 1, results.size()); + + String log = getLogMessage(results, 0); + + // Validate correct exchange name + assertTrue("Incorrect exchange name created:" + log, log.endsWith(getName())); + + // Validate it was a management actor. + String actor = fromActor(log); + assertTrue("Actor is not a manangement actor:" + actor, actor.startsWith("mng")); + + } + + public void testCreateExchangeHeadersTransientViaManagementConsole() throws IOException, JMException + { + //Remove any previous exchange declares + _monitor.markDiscardPoint(); + + _jmxUtils.createExchange("test", getName(), "headers", false); + + // Validate + + //1 - ID is correct + List<String> results = waitAndFindMatches("EXH-1001"); + + assertEquals("More than one exchange creation found", 1, results.size()); + + String log = getLogMessage(results, 0); + + // Validate correct exchange name + assertTrue("Incorrect exchange name created:" + log, log.endsWith(getName())); + + // Validate it was a management actor. + String actor = fromActor(log); + assertTrue("Actor is not a manangement actor:" + actor, actor.startsWith("mng")); + + } + + /** + * Description: + * Queue creation is possible from the Management Console. When a queue is created in this way then a QUE-1001 create message is expected to be logged. + * Input: + * + * 1. Running broker + * 2. Connected Management Console + * 3. Queue Created via Management Console + * Output: + * + * <date> QUE-1001 : Create : Transient Owner:<name> + * + * Validation Steps: + * 4. The QUE ID is correct + * 5. The correct tags are present in the message based on the create options + * + * @throws java.io.IOException - if there is a problem reseting the log monitor + * @throws javax.management.JMException - {@see #createQueue and ManagedExchange.deleteQueue} + */ + public void testCreateQueueTransientViaManagementConsole() throws IOException, JMException + { + //Remove any previous queue declares + _monitor.markDiscardPoint(); + + _jmxUtils.createQueue("test", getName(), null, false); + + // Validate + + List<String> results = waitAndFindMatches("QUE-1001"); + + assertEquals("More than one queue creation found", 1, results.size()); + + String log = getLogMessage(results, 0); + + // Validate correct queue name + String subject = fromSubject(log); + assertEquals("Incorrect queue name created", getName(), AbstractTestLogSubject.getSlice("qu", subject)); + + // Validate it was a management actor. + String actor = fromActor(log); + assertTrue("Actor is not a manangement actor:" + actor, actor.startsWith("mng")); + } + + /** + * Description: + * The ManagementConsole can be used to delete a queue. When this is done a QUE-1002 Deleted message must be logged. + * Input: + * + * 1. Running Broker + * 2. Queue created on the broker with no subscribers + * 3. Management Console connected + * 4. Queue is deleted via Management Console + * Output: + * + * <date> QUE-1002 : Deleted + * + * Validation Steps: + * 5. The QUE ID is correct + * + * @throws java.io.IOException - if there is a problem reseting the log monitor + * @throws javax.management.JMException - {@see #createQueue and ManagedExchange.deleteQueue} + */ + public void testQueueDeleteViaManagementConsole() throws IOException, JMException + { + //Remove any previous queue declares + _monitor.markDiscardPoint(); + + _jmxUtils.createQueue("test", getName(), null, false); + + ManagedBroker managedBroker = _jmxUtils.getManagedBroker("test"); + + managedBroker.deleteQueue(getName()); + + List<String> results = waitAndFindMatches("QUE-1002"); + + assertEquals("More than one queue deletion found", 1, results.size()); + + String log = getLog(results.get(0)); + + // Validate correct binding + String subject = fromSubject(log); + assertEquals("Incorrect queue named in delete", getName(), AbstractTestLogSubject.getSlice("qu", subject)); + + // Validate it was a management actor. + String actor = fromActor(log); + assertTrue("Actor is not a manangement actor:" + actor, actor.startsWith("mng")); + + } + + /** + * Description: + * The binding of a Queue and an Exchange is done via a Binding. When this Binding is created via the Management Console a BND-1001 Create message will be logged. + * Input: + * + * 1. Running Broker + * 2. Connected Management Console + * 3. Use Management Console to perform binding + * Output: + * + * <date> BND-1001 : Create + * + * Validation Steps: + * 4. The BND ID is correct + * 5. This will be the first message for the given binding + * + * @throws java.io.IOException - if there is a problem reseting the log monitor + * @throws javax.management.JMException - {@see #createQueue and ManagedExchange.createNewBinding} + */ + public void testBindingCreateOnDirectViaManagementConsole() throws IOException, JMException + { + //Remove any previous queue declares + _monitor.markDiscardPoint(); + + _jmxUtils.createQueue("test", getName(), null, false); + + ManagedExchange managedExchange = _jmxUtils.getManagedExchange("amq.direct"); + + managedExchange.createNewBinding(getName(), getName()); + + List<String> results = waitAndFindMatches("BND-1001"); + + assertEquals("Unexpected number of bindings logged", 2, results.size()); + + String log = getLogMessage(results, 0); + + // Validate correct binding + String subject = fromSubject(log); + assertEquals("Incorrect queue named in create", getName(), AbstractTestLogSubject.getSlice("qu", subject)); + assertEquals("Incorrect routing key in create", getName(), AbstractTestLogSubject.getSlice("rk", subject)); + + // Validate it was a management actor. + String actor = fromActor(log); + assertTrue("Actor is not a manangement actor:" + actor, actor.startsWith("mng")); + } + + public void testBindingCreateOnTopicViaManagementConsole() throws IOException, JMException + { + //Remove any previous queue declares + _monitor.markDiscardPoint(); + + _jmxUtils.createQueue("test", getName(), null, false); + + ManagedExchange managedExchange = _jmxUtils.getManagedExchange("amq.topic"); + + managedExchange.createNewBinding(getName(), getName()); + + List<String> results = waitAndFindMatches("BND-1001"); + + assertEquals("Unexpected number of bindings logged", 2, results.size()); + + String log = getLogMessage(results, 0); + + // Validate correct binding + String subject = fromSubject(log); + assertEquals("Incorrect queue named in create", getName(), AbstractTestLogSubject.getSlice("qu", subject)); + assertEquals("Incorrect routing key in create", getName(), AbstractTestLogSubject.getSlice("rk", subject)); + + // Validate it was a management actor. + String actor = fromActor(log); + assertTrue("Actor is not a manangement actor:" + actor, actor.startsWith("mng")); + } + + public void testBindingCreateOnFanoutViaManagementConsole() throws IOException, JMException + { + //Remove any previous queue declares + _monitor.markDiscardPoint(); + + _jmxUtils.createQueue("test", getName(), null, false); + + ManagedExchange managedExchange = _jmxUtils.getManagedExchange("amq.fanout"); + + managedExchange.createNewBinding(getName(), getName()); + + List<String> results = waitAndFindMatches("BND-1001"); + + assertEquals("Unexpected number of bindings logged", 2, results.size()); + + String log = getLogMessage(results, 0); + + // Validate correct binding + String subject = fromSubject(log); + assertEquals("Incorrect queue named in create", getName(), AbstractTestLogSubject.getSlice("qu", subject)); + assertEquals("Incorrect routing key in create", getName(), AbstractTestLogSubject.getSlice("rk", subject)); + + // Validate it was a management actor. + String actor = fromActor(log); + assertTrue("Actor is not a manangement actor:" + actor, actor.startsWith("mng")); + } + + /** + * Description: + * Bindings can be deleted so that a queue can be rebound with a different set of values. This can be performed via the Management Console + * Input: + * + * 1. Running Broker + * 2. Management Console connected + * 3. Management Console is used to perform unbind. + * Output: + * + * <date> BND-1002 : Deleted + * + * Validation Steps: + * 4. The BND ID is correct + * 5. There must have been a BND-1001 Create message first. + * 6. This will be the last message for the given binding + * + * @throws java.io.IOException - if there is a problem reseting the log monitor or an issue with the JMX Connection + * @throws javax.management.JMException - {@see #createExchange and ManagedBroker.unregisterExchange} + */ + public void testUnRegisterExchangeViaManagementConsole() throws IOException, JMException + { + //Remove any previous queue declares + _monitor.markDiscardPoint(); + + _jmxUtils.createExchange("test", getName(), "direct", false); + + ManagedBroker managedBroker = _jmxUtils.getManagedBroker("test"); + + managedBroker.unregisterExchange(getName()); + + List<String> results = waitAndFindMatches("EXH-1002"); + + assertEquals("More than one exchange deletion found", 1, results.size()); + + String log = getLog(results.get(0)); + + // Validate correct binding + String subject = fromSubject(log); + assertEquals("Incorrect exchange named in delete", "direct/" + getName(), AbstractTestLogSubject.getSlice("ex", subject)); + + // Validate it was a management actor. + String actor = fromActor(log); + assertTrue("Actor is not a manangement actor:" + actor, actor.startsWith("mng")); + } + +} diff --git a/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/ManagementLoggingTest.java b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/ManagementLoggingTest.java new file mode 100644 index 0000000000..6100d5a23e --- /dev/null +++ b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/ManagementLoggingTest.java @@ -0,0 +1,317 @@ +/* + * + * 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.systest.management.jmx; + + +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.logging.AbstractTestLogging; +import org.apache.qpid.test.utils.JMXTestUtils; +import org.apache.qpid.util.LogMonitor; + +import java.io.File; +import java.util.List; + +/** + * Management Console Test Suite + * + * The Management Console test suite validates that the follow log messages as specified in the Functional Specification. + * + * This suite of tests validate that the management console messages occur correctly and according to the following format: + * + * MNG-1001 : Startup + * MNG-1002 : Starting : <service> : Listening on port <Port> + * MNG-1003 : Shutting down : <service> : port <Port> + * MNG-1004 : Ready + * MNG-1005 : Stopped + * MNG-1006 : Using SSL Keystore : <path> + * MNG-1007 : Open : User <username> + * MNG-1008 : Close : User <username> + */ +public class ManagementLoggingTest extends AbstractTestLogging +{ + private static final String MNG_PREFIX = "MNG-"; + + public void setUp() throws Exception + { + setLogMessagePrefix(); + + // We either do this here or have a null check in tearDown. + // As when this test is run against profiles other than java it will NPE + _monitor = new LogMonitor(_outputFile); + //We explicitly do not call super.setUp as starting up the broker is + //part of the test case. + + } + + /** + * Description: + * Using the startup configuration validate that the management startup + * message is logged correctly. + * Input: + * Standard configuration with management enabled + * Output: + * + * <date> MNG-1001 : Startup + * + * Constraints: + * This is the FIRST message logged by MNG + * Validation Steps: + * + * 1. The BRK ID is correct + * 2. This is the FIRST message logged by MNG + */ + public void testManagementStartupEnabled() throws Exception + { + // This test only works on java brokers + if (isJavaBroker()) + { + startBrokerAndCreateMonitor(true, false); + + // Ensure we have received the MNG log msg. + waitForMessage("MNG-1001"); + + List<String> results = findMatches(MNG_PREFIX); + // Validation + + assertTrue("MNGer message not logged", results.size() > 0); + + String log = getLogMessage(results, 0); + + //1 + validateMessageID("MNG-1001", log); + + //2 + //There will be 2 copies of the startup message (one via SystemOut, and one via Log4J) + results = findMatches("MNG-1001"); + assertEquals("Unexpected startup message count.", + 2, results.size()); + + //3 + assertEquals("Startup log message is not 'Startup'.", "Startup", + getMessageString(log)); + } + } + + /** + * Description: + * Verify that when management is disabled in the configuration file the + * startup message is not logged. + * Input: + * Standard configuration with management disabled + * Output: + * NO MNG messages + * Validation Steps: + * + * 1. Validate that no MNG messages are produced. + */ + public void testManagementStartupDisabled() throws Exception + { + if (isJavaBroker()) + { + startBrokerAndCreateMonitor(false, false); + + List<String> results = findMatches(MNG_PREFIX); + // Validation + + assertEquals("MNGer messages logged", 0, results.size()); + } + } + + /** + * The two MNG-1002 messages are logged at the same time so lets test them + * at the same time. + * + * Description: + * Using the default configuration validate that the RMI Registry socket is + * correctly reported as being opened + * + * Input: + * The default configuration file + * Output: + * + * <date> MESSAGE MNG-1002 : Starting : RMI Registry : Listening on port 8999 + * + * Constraints: + * The RMI ConnectorServer and Registry log messages do not have a prescribed order + * Validation Steps: + * + * 1. The MNG ID is correct + * 2. The specified port is the correct '8999' + * + * Description: + * Using the default configuration validate that the RMI ConnectorServer + * socket is correctly reported as being opened + * + * Input: + * The default configuration file + * Output: + * + * <date> MESSAGE MNG-1002 : Starting : RMI ConnectorServer : Listening on port 9099 + * + * Constraints: + * The RMI ConnectorServer and Registry log messages do not have a prescribed order + * Validation Steps: + * + * 1. The MNG ID is correct + * 2. The specified port is the correct '9099' + */ + public void testManagementStartupRMIEntries() throws Exception + { + if (isJavaBroker()) + { + startBrokerAndCreateMonitor(true, false); + + List<String> results = waitAndFindMatches("MNG-1002"); + // Validation + + //There will be 4 startup messages (two via SystemOut, and two via Log4J) + assertEquals("Unexpected MNG-1002 message count", 4, results.size()); + + String log = getLogMessage(results, 0); + + //1 + validateMessageID("MNG-1002", log); + + //Check the RMI Registry port is as expected + int mPort = getManagementPort(getPort()); + assertTrue("RMI Registry port not as expected(" + mPort + ").:" + getMessageString(log), + getMessageString(log).endsWith(String.valueOf(mPort))); + + log = getLogMessage(results, 2); + + //1 + validateMessageID("MNG-1002", log); + + // We expect the RMI Registry port (the defined 'management port') to be + // 100 lower than the JMX RMIConnector Server Port (the actual JMX server) + int jmxPort = mPort + ServerConfiguration.JMXPORT_CONNECTORSERVER_OFFSET; + assertTrue("JMX RMIConnectorServer port not as expected(" + jmxPort + ").:" + getMessageString(log), + getMessageString(log).endsWith(String.valueOf(jmxPort))); + } + } + + /** + * Description: + * Using the default configuration with SSL enabled for the management port the SSL Keystore path should be reported via MNG-1006 + * Input: + * Management SSL enabled default configuration. + * Output: + * + * <date> MESSAGE MNG-1006 : Using SSL Keystore : test_resources/ssl/keystore.jks + * + * Validation Steps: + * + * 1. The MNG ID is correct + * 2. The keystore path is as specified in the configuration + */ + public void testManagementStartupSSLKeystore() throws Exception + { + if (isJavaBroker()) + { + startBrokerAndCreateMonitor(true, true); + + List<String> results = waitAndFindMatches("MNG-1006"); + + assertTrue("MNGer message not logged", results.size() > 0); + + String log = getLogMessage(results, 0); + + //1 + validateMessageID("MNG-1006", log); + + // Validate we only have two MNG-1002 (one via stdout, one via log4j) + results = findMatches("MNG-1006"); + assertEquals("Upexpected SSL Keystore message count", + 2, results.size()); + + // Validate the keystore path is as expected + assertTrue("SSL Keystore entry expected.:" + getMessageString(log), + getMessageString(log).endsWith(new File(getConfigurationStringProperty("management.ssl.keyStorePath")).getName())); + } + } + + /** + * Description: Tests the management connection open/close are logged correctly. + * + * Output: + * + * <date> MESSAGE MNG-1007 : Open : User <username> + * <date> MESSAGE MNG-1008 : Close : User <username> + * + * Validation Steps: + * + * 1. The MNG ID is correct + * 2. The message and username are correct + */ + public void testManagementUserOpenClose() throws Exception + { + if (isJavaBroker()) + { + startBrokerAndCreateMonitor(true, false); + + final JMXTestUtils jmxUtils = new JMXTestUtils(this); + List<String> openResults = null; + List<String> closeResults = null; + try + { + jmxUtils.setUp(); + jmxUtils.open(); + openResults = waitAndFindMatches("MNG-1007"); + } + finally + { + if (jmxUtils != null) + { + jmxUtils.close(); + closeResults = waitAndFindMatches("MNG-1008"); + } + } + + assertNotNull("Management Open results null", openResults.size()); + assertEquals("Management Open logged unexpected number of times", 1, openResults.size()); + + assertNotNull("Management Close results null", closeResults.size()); + assertEquals("Management Close logged unexpected number of times", 1, closeResults.size()); + + final String openMessage = getMessageString(getLogMessage(openResults, 0)); + assertTrue("Unexpected open message " + openMessage, openMessage.endsWith("Open : User admin")); + final String closeMessage = getMessageString(getLogMessage(closeResults, 0)); + assertTrue("Unexpected close message " + closeMessage, closeMessage.endsWith("Close : User admin")); + } + } + + private void startBrokerAndCreateMonitor(boolean managementEnabled, boolean useManagementSSL) throws Exception + { + //Ensure management is on + setConfigurationProperty("management.enabled", String.valueOf(managementEnabled)); + + if(useManagementSSL) + { + // This test requires we have an ssl connection + setConfigurationProperty("management.ssl.enabled", "true"); + } + + startBroker(); + + // Now we can create the monitor as _outputFile will now be defined + _monitor = new LogMonitor(_outputFile); + } +} diff --git a/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/QueueManagementTest.java b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/QueueManagementTest.java new file mode 100644 index 0000000000..79d04b239e --- /dev/null +++ b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/QueueManagementTest.java @@ -0,0 +1,696 @@ +/* + * 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.systest.management.jmx; + +import org.apache.commons.lang.time.FastDateFormat; + +import org.apache.log4j.Logger; +import org.apache.qpid.client.AMQSession; +import org.apache.qpid.configuration.ClientProperties; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.management.common.mbeans.ManagedBroker; +import org.apache.qpid.management.common.mbeans.ManagedQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.NotificationCheckTest; +import org.apache.qpid.server.queue.SimpleAMQQueueTest; +import org.apache.qpid.test.client.destination.AddressBasedDestinationTest; +import org.apache.qpid.test.utils.JMXTestUtils; +import org.apache.qpid.test.utils.QpidBrokerTestCase; + +import javax.jms.Connection; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageListener; +import javax.jms.Queue; +import javax.jms.Session; +import javax.jms.TextMessage; +import javax.management.Notification; +import javax.management.NotificationListener; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.TabularData; +import javax.naming.NamingException; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Tests the JMX API for the Managed Queue. + * + */ +public class QueueManagementTest extends QpidBrokerTestCase +{ + + private static final Logger LOGGER = Logger.getLogger(QueueManagementTest.class); + + private static final String VIRTUAL_HOST = "test"; + private static final String TEST_QUEUE_DESCRIPTION = "my description"; + + private JMXTestUtils _jmxUtils; + private Connection _connection; + private Session _session; + + private String _sourceQueueName; + private String _destinationQueueName; + private Destination _sourceQueue; + private Destination _destinationQueue; + private ManagedQueue _managedSourceQueue; + private ManagedQueue _managedDestinationQueue; + + public void setUp() throws Exception + { + _jmxUtils = new JMXTestUtils(this); + _jmxUtils.setUp(); + + super.setUp(); + _sourceQueueName = getTestQueueName() + "_src"; + _destinationQueueName = getTestQueueName() + "_dest"; + + createConnectionAndSession(); + + _sourceQueue = _session.createQueue(_sourceQueueName); + _destinationQueue = _session.createQueue(_destinationQueueName); + createQueueOnBroker(_sourceQueue); + createQueueOnBroker(_destinationQueue); + + _jmxUtils.open(); + + createManagementInterfacesForQueues(); + } + + public void tearDown() throws Exception + { + if (_jmxUtils != null) + { + _jmxUtils.close(); + } + super.tearDown(); + } + + public void testQueueAttributes() throws Exception + { + Queue queue = _session.createQueue(getTestQueueName()); + createQueueOnBroker(queue); + + final String queueName = queue.getQueueName(); + + final ManagedQueue managedQueue = _jmxUtils.getManagedQueue(queueName); + assertEquals("Unexpected name", queueName, managedQueue.getName()); + assertEquals("Unexpected queue type", "standard", managedQueue.getQueueType()); + } + + public void testExclusiveQueueHasJmsClientIdAsOwner() throws Exception + { + Queue tmpQueue = _session.createTemporaryQueue(); + + final String queueName = tmpQueue.getQueueName(); + + final ManagedQueue managedQueue = _jmxUtils.getManagedQueue(queueName); + assertNotNull(_connection.getClientID()); + assertEquals("Unexpected owner", _connection.getClientID(), managedQueue.getOwner()); + } + + public void testNonExclusiveQueueHasNoOwner() throws Exception + { + Queue nonExclusiveQueue = _session.createQueue(getTestQueueName()); + createQueueOnBroker(nonExclusiveQueue); + + final String queueName = nonExclusiveQueue.getQueueName(); + + final ManagedQueue managedQueue = _jmxUtils.getManagedQueue(queueName); + assertNull("Unexpected owner", managedQueue.getOwner()); + } + + public void testSetNewQueueDescriptionOnExistingQueue() throws Exception + { + Queue queue = _session.createQueue(getTestQueueName()); + createQueueOnBroker(queue); + + final String queueName = queue.getQueueName(); + + final ManagedQueue managedQueue = _jmxUtils.getManagedQueue(queueName); + assertNull("Unexpected description", managedQueue.getDescription()); + + managedQueue.setDescription(TEST_QUEUE_DESCRIPTION); + assertEquals(TEST_QUEUE_DESCRIPTION, managedQueue.getDescription()); + } + + public void testNewQueueWithDescription() throws Exception + { + String queueName = getTestQueueName(); + Map<String, Object> arguments = Collections.singletonMap(AMQQueueFactory.X_QPID_DESCRIPTION, (Object)TEST_QUEUE_DESCRIPTION); + ((AMQSession<?, ?>)_session).createQueue(AMQShortString.valueOf(queueName), false, true, false, arguments); + + final ManagedQueue managedQueue = _jmxUtils.getManagedQueue(queueName); + assertEquals(TEST_QUEUE_DESCRIPTION, managedQueue.getDescription()); + } + + /** + * Requires persistent store. + */ + public void testQueueDescriptionSurvivesRestart() throws Exception + { + String queueName = getTestQueueName(); + Map<String, Object> arguments = Collections.singletonMap(AMQQueueFactory.X_QPID_DESCRIPTION, (Object)TEST_QUEUE_DESCRIPTION); + + ((AMQSession<?, ?>)_session).createQueue(AMQShortString.valueOf(queueName), false, true, false, arguments); + + ManagedQueue managedQueue = _jmxUtils.getManagedQueue(queueName); + assertEquals(TEST_QUEUE_DESCRIPTION, managedQueue.getDescription()); + + restartBroker(); + + managedQueue = _jmxUtils.getManagedQueue(queueName); + assertEquals(TEST_QUEUE_DESCRIPTION, managedQueue.getDescription()); + } + + /** + * Tests queue creation with {@link AMQQueueFactory#X_QPID_MAXIMUM_DELIVERY_COUNT} argument. Also tests + * that the attribute is exposed correctly through {@link ManagedQueue#getMaximumDeliveryCount()}. + */ + public void testCreateQueueWithMaximumDeliveryCountSet() throws Exception + { + final String queueName = getName(); + final ManagedBroker managedBroker = _jmxUtils.getManagedBroker(VIRTUAL_HOST); + + final Integer deliveryCount = 1; + final Map<String, Object> arguments = Collections.singletonMap(AMQQueueFactory.X_QPID_MAXIMUM_DELIVERY_COUNT, (Object)deliveryCount); + managedBroker.createNewQueue(queueName, null, true, arguments); + + // Ensure the queue exists + assertNotNull("Queue object name expected to exist", _jmxUtils.getQueueObjectName("test", queueName)); + assertNotNull("Manager queue expected to be available", _jmxUtils.getManagedQueue(queueName)); + + final ManagedQueue managedQueue = _jmxUtils.getManagedQueue(queueName); + assertEquals("Unexpected maximum delivery count", deliveryCount, managedQueue.getMaximumDeliveryCount()); + } + + /** + * Requires 0-10 as relies on ADDR addresses. + * @see AddressBasedDestinationTest for the testing of message routing to the alternate exchange + */ + public void testGetSetAlternateExchange() throws Exception + { + String queueName = getTestQueueName(); + String altExchange = "amq.fanout"; + String addrWithAltExch = String.format("ADDR:%s;{create:always,node:{type:queue,x-declare:{alternate-exchange:'%s'}}}", queueName, altExchange); + Queue queue = _session.createQueue(addrWithAltExch); + + createQueueOnBroker(queue); + + final ManagedQueue managedQueue = _jmxUtils.getManagedQueue(queueName); + assertEquals("Newly created queue does not have expected alternate exchange", altExchange, managedQueue.getAlternateExchange()); + + String newAltExch = "amq.topic"; + managedQueue.setAlternateExchange(newAltExch); + assertEquals("Unexpected alternate exchange after set", newAltExch, managedQueue.getAlternateExchange()); + } + + /** + * Requires 0-10 as relies on ADDR addresses. + */ + public void testRemoveAlternateExchange() throws Exception + { + String queueName = getTestQueueName(); + String altExchange = "amq.fanout"; + String addrWithAltExch = String.format("ADDR:%s;{create:always,node:{type:queue,x-declare:{alternate-exchange:'%s'}}}", queueName, altExchange); + Queue queue = _session.createQueue(addrWithAltExch); + + createQueueOnBroker(queue); + + final ManagedQueue managedQueue = _jmxUtils.getManagedQueue(queueName); + assertEquals("Newly created queue does not have expected alternate exchange", altExchange, managedQueue.getAlternateExchange()); + + managedQueue.setAlternateExchange(""); + assertNull("Unexpected alternate exchange after set", managedQueue.getAlternateExchange()); + } + + /** + * Requires persistent store + * Requires 0-10 as relies on ADDR addresses. + */ + public void testAlternateExchangeSurvivesRestart() throws Exception + { + String nonMandatoryExchangeName = "exch" + getName(); + + final ManagedBroker managedBroker = _jmxUtils.getManagedBroker(VIRTUAL_HOST); + managedBroker.createNewExchange(nonMandatoryExchangeName, "fanout", true); + + String queueName1 = getTestQueueName() + "1"; + String altExchange1 = "amq.fanout"; + String addr1WithAltExch = String.format("ADDR:%s;{create:always,node:{durable: true,type:queue,x-declare:{alternate-exchange:'%s'}}}", queueName1, altExchange1); + Queue queue1 = _session.createQueue(addr1WithAltExch); + + String queueName2 = getTestQueueName() + "2"; + String addr2WithoutAltExch = String.format("ADDR:%s;{create:always,node:{durable: true,type:queue}}", queueName2); + Queue queue2 = _session.createQueue(addr2WithoutAltExch); + + createQueueOnBroker(queue1); + createQueueOnBroker(queue2); + + ManagedQueue managedQueue1 = _jmxUtils.getManagedQueue(queueName1); + assertEquals("Newly created queue1 does not have expected alternate exchange", altExchange1, managedQueue1.getAlternateExchange()); + + ManagedQueue managedQueue2 = _jmxUtils.getManagedQueue(queueName2); + assertNull("Newly created queue2 does not have expected alternate exchange", managedQueue2.getAlternateExchange()); + + String altExchange2 = nonMandatoryExchangeName; + managedQueue2.setAlternateExchange(altExchange2); + + restartBroker(); + + managedQueue1 = _jmxUtils.getManagedQueue(queueName1); + assertEquals("Queue1 does not have expected alternate exchange after restart", altExchange1, managedQueue1.getAlternateExchange()); + + managedQueue2 = _jmxUtils.getManagedQueue(queueName2); + assertEquals("Queue2 does not have expected updated alternate exchange after restart", altExchange2, managedQueue2.getAlternateExchange()); + } + + /** + * Tests the ability to receive queue alerts as JMX notifications. + * + * @see NotificationCheckTest + * @see SimpleAMQQueueTest#testNotificationFiredAsync() + * @see SimpleAMQQueueTest#testNotificationFiredOnEnqueue() + */ + public void testQueueNotification() throws Exception + { + final String queueName = getName(); + final long maximumMessageCount = 3; + + Queue queue = _session.createQueue(queueName); + createQueueOnBroker(queue); + + ManagedQueue managedQueue = _jmxUtils.getManagedQueue(queueName); + managedQueue.setMaximumMessageCount(maximumMessageCount); + + RecordingNotificationListener listener = new RecordingNotificationListener(1); + + _jmxUtils.addNotificationListener(_jmxUtils.getQueueObjectName(VIRTUAL_HOST, queueName), listener, null, null); + + // Send two messages - this should *not* trigger the notification + sendMessage(_session, queue, 2); + + assertEquals("Premature notification received", 0, listener.getNumberOfNotificationsReceived()); + + // A further message should trigger the message count alert + sendMessage(_session, queue, 1); + + listener.awaitExpectedNotifications(5, TimeUnit.SECONDS); + + assertEquals("Unexpected number of JMX notifications received", 1, listener.getNumberOfNotificationsReceived()); + + Notification notification = listener.getLastNotification(); + assertEquals("Unexpected notification message", "MESSAGE_COUNT_ALERT 3: Maximum count on queue threshold (3) breached.", notification.getMessage()); + } + + /** + * Tests {@link ManagedQueue#viewMessages(long, long)} interface. + */ + public void testViewSingleMessage() throws Exception + { + final List<Message> sentMessages = sendMessage(_session, _sourceQueue, 1); + syncSession(_session); + final Message sentMessage = sentMessages.get(0); + + assertEquals("Unexpected queue depth", 1, _managedSourceQueue.getMessageCount().intValue()); + + // Check the contents of the message + final TabularData tab = _managedSourceQueue.viewMessages(1l, 1l); + assertEquals("Unexpected number of rows in table", 1, tab.size()); + final Iterator<CompositeData> rowItr = (Iterator<CompositeData>) tab.values().iterator(); + + final CompositeData row1 = rowItr.next(); + assertNotNull("Message should have AMQ message id", row1.get(ManagedQueue.MSG_AMQ_ID)); + assertEquals("Unexpected queue position", 1l, row1.get(ManagedQueue.MSG_QUEUE_POS)); + assertEquals("Unexpected redelivered flag", Boolean.FALSE, row1.get(ManagedQueue.MSG_REDELIVERED)); + + // Check the contents of header (encoded in a string array) + final String[] headerArray = (String[]) row1.get(ManagedQueue.MSG_HEADER); + assertNotNull("Expected message header array", headerArray); + final Map<String, String> headers = headerArrayToMap(headerArray); + + final String expectedJMSMessageID = isBroker010() ? sentMessage.getJMSMessageID().replace("ID:", "") : sentMessage.getJMSMessageID(); + final String expectedFormattedJMSTimestamp = FastDateFormat.getInstance(ManagedQueue.JMSTIMESTAMP_DATETIME_FORMAT).format(sentMessage.getJMSTimestamp()); + assertEquals("Unexpected JMSMessageID within header", expectedJMSMessageID, headers.get("JMSMessageID")); + assertEquals("Unexpected JMSPriority within header", String.valueOf(sentMessage.getJMSPriority()), headers.get("JMSPriority")); + assertEquals("Unexpected JMSTimestamp within header", expectedFormattedJMSTimestamp, headers.get("JMSTimestamp")); + } + + /** + * Tests {@link ManagedQueue#moveMessages(long, long, String)} interface. + */ + public void testMoveMessagesBetweenQueues() throws Exception + { + final int numberOfMessagesToSend = 10; + + sendMessage(_session, _sourceQueue, numberOfMessagesToSend); + syncSession(_session); + assertEquals("Unexpected queue depth after send", numberOfMessagesToSend, _managedSourceQueue.getMessageCount().intValue()); + + List<Long> amqMessagesIds = getAMQMessageIdsOn(_managedSourceQueue, 1, numberOfMessagesToSend); + + // Move first three messages to destination + long fromMessageId = amqMessagesIds.get(0); + long toMessageId = amqMessagesIds.get(2); + _managedSourceQueue.moveMessages(fromMessageId, toMessageId, _destinationQueueName); + + assertEquals("Unexpected queue depth on destination queue after first move", 3, _managedDestinationQueue.getMessageCount().intValue()); + assertEquals("Unexpected queue depth on source queue after first move", 7, _managedSourceQueue.getMessageCount().intValue()); + + // Now move a further two messages to destination + fromMessageId = amqMessagesIds.get(7); + toMessageId = amqMessagesIds.get(8); + _managedSourceQueue.moveMessages(fromMessageId, toMessageId, _destinationQueueName); + assertEquals("Unexpected queue depth on destination queue after second move", 5, _managedDestinationQueue.getMessageCount().intValue()); + assertEquals("Unexpected queue depth on source queue after second move", 5, _managedSourceQueue.getMessageCount().intValue()); + + assertMessageIndicesOn(_destinationQueue, 0, 1, 2, 7, 8); + } + + /** + * Tests {@link ManagedQueue#copyMessages(long, long, String)} interface. + */ + public void testCopyMessagesBetweenQueues() throws Exception + { + final int numberOfMessagesToSend = 10; + sendMessage(_session, _sourceQueue, numberOfMessagesToSend); + syncSession(_session); + assertEquals("Unexpected queue depth after send", numberOfMessagesToSend, _managedSourceQueue.getMessageCount().intValue()); + + List<Long> amqMessagesIds = getAMQMessageIdsOn(_managedSourceQueue, 1, numberOfMessagesToSend); + + // Copy first three messages to destination + long fromMessageId = amqMessagesIds.get(0); + long toMessageId = amqMessagesIds.get(2); + _managedSourceQueue.copyMessages(fromMessageId, toMessageId, _destinationQueueName); + + assertEquals("Unexpected queue depth on destination queue after first copy", 3, _managedDestinationQueue.getMessageCount().intValue()); + assertEquals("Unexpected queue depth on source queue after first copy", numberOfMessagesToSend, _managedSourceQueue.getMessageCount().intValue()); + + // Now copy a further two messages to destination + fromMessageId = amqMessagesIds.get(7); + toMessageId = amqMessagesIds.get(8); + _managedSourceQueue.copyMessages(fromMessageId, toMessageId, _destinationQueueName); + assertEquals("Unexpected queue depth on destination queue after second copy", 5, _managedDestinationQueue.getMessageCount().intValue()); + assertEquals("Unexpected queue depth on source queue after second copy", numberOfMessagesToSend, _managedSourceQueue.getMessageCount().intValue()); + + assertMessageIndicesOn(_destinationQueue, 0, 1, 2, 7, 8); + } + + public void testMoveMessagesBetweenQueuesWithActiveConsumerOnSourceQueue() throws Exception + { + setTestClientSystemProperty(ClientProperties.MAX_PREFETCH_PROP_NAME, new Integer(1).toString()); + Connection asyncConnection = getConnection(); + asyncConnection.start(); + + final int numberOfMessagesToSend = 50; + sendMessage(_session, _sourceQueue, numberOfMessagesToSend); + syncSession(_session); + assertEquals("Unexpected queue depth after send", numberOfMessagesToSend, _managedSourceQueue.getMessageCount().intValue()); + + List<Long> amqMessagesIds = getAMQMessageIdsOn(_managedSourceQueue, 1, numberOfMessagesToSend); + + long fromMessageId = amqMessagesIds.get(0); + long toMessageId = amqMessagesIds.get(numberOfMessagesToSend - 1); + + CountDownLatch consumerReadToHalfwayLatch = new CountDownLatch(numberOfMessagesToSend / 2); + AtomicInteger totalConsumed = new AtomicInteger(0); + startAsyncConsumerOn(_sourceQueue, asyncConnection, consumerReadToHalfwayLatch, totalConsumed); + + boolean halfwayPointReached = consumerReadToHalfwayLatch.await(5000, TimeUnit.MILLISECONDS); + assertTrue("Did not read half of messages within time allowed", halfwayPointReached); + + _managedSourceQueue.moveMessages(fromMessageId, toMessageId, _destinationQueueName); + + asyncConnection.stop(); + + // The exact number of messages moved will be non deterministic, as the number of messages processed + // by the consumer cannot be predicted. There is also the possibility that a message can remain + // on the source queue. This situation will arise if a message has been acquired by the consumer, but not + // yet delivered to the client application (i.e. MessageListener#onMessage()) when the Connection#stop() occurs. + // + // The number of messages moved + the number consumed + any messages remaining on source should + // *always* be equal to the number we originally sent. + + int numberOfMessagesReadByConsumer = totalConsumed.intValue(); + int numberOfMessagesOnDestinationQueue = _managedDestinationQueue.getMessageCount().intValue(); + int numberOfMessagesRemainingOnSourceQueue = _managedSourceQueue.getMessageCount().intValue(); + + LOGGER.debug("Async consumer read : " + numberOfMessagesReadByConsumer + + " Number of messages moved to destination : " + numberOfMessagesOnDestinationQueue + + " Number of messages remaining on source : " + numberOfMessagesRemainingOnSourceQueue); + assertEquals("Unexpected number of messages after move", numberOfMessagesToSend, numberOfMessagesReadByConsumer + numberOfMessagesOnDestinationQueue + numberOfMessagesRemainingOnSourceQueue); + } + + public void testMoveMessagesBetweenQueuesWithActiveConsumerOnDestinationQueue() throws Exception + { + setTestClientSystemProperty(ClientProperties.MAX_PREFETCH_PROP_NAME, new Integer(1).toString()); + Connection asyncConnection = getConnection(); + asyncConnection.start(); + + final int numberOfMessagesToSend = 50; + sendMessage(_session, _sourceQueue, numberOfMessagesToSend); + syncSession(_session); + assertEquals("Unexpected queue depth after send", numberOfMessagesToSend, _managedSourceQueue.getMessageCount().intValue()); + + List<Long> amqMessagesIds = getAMQMessageIdsOn(_managedSourceQueue, 1, numberOfMessagesToSend); + long fromMessageId = amqMessagesIds.get(0); + long toMessageId = amqMessagesIds.get(numberOfMessagesToSend - 1); + + AtomicInteger totalConsumed = new AtomicInteger(0); + CountDownLatch allMessagesConsumedLatch = new CountDownLatch(numberOfMessagesToSend); + startAsyncConsumerOn(_destinationQueue, asyncConnection, allMessagesConsumedLatch, totalConsumed); + + _managedSourceQueue.moveMessages(fromMessageId, toMessageId, _destinationQueueName); + + allMessagesConsumedLatch.await(5000, TimeUnit.MILLISECONDS); + assertEquals("Did not consume all messages from destination queue", numberOfMessagesToSend, totalConsumed.intValue()); + } + + /** + * Tests {@link ManagedQueue#moveMessages(long, long, String)} interface. + */ + public void testMoveMessageBetweenQueuesWithBrokerRestart() throws Exception + { + final int numberOfMessagesToSend = 1; + + sendMessage(_session, _sourceQueue, numberOfMessagesToSend); + syncSession(_session); + assertEquals("Unexpected queue depth after send", numberOfMessagesToSend, _managedSourceQueue.getMessageCount().intValue()); + + restartBroker(); + + createManagementInterfacesForQueues(); + createConnectionAndSession(); + + List<Long> amqMessagesIds = getAMQMessageIdsOn(_managedSourceQueue, 1, numberOfMessagesToSend); + + // Move messages to destination + long messageId = amqMessagesIds.get(0); + _managedSourceQueue.moveMessages(messageId, messageId, _destinationQueueName); + + assertEquals("Unexpected queue depth on destination queue after move", 1, _managedDestinationQueue.getMessageCount().intValue()); + assertEquals("Unexpected queue depth on source queue after move", 0, _managedSourceQueue.getMessageCount().intValue()); + + assertMessageIndicesOn(_destinationQueue, 0); + } + + /** + * Tests {@link ManagedQueue#copyMessages(long, long, String)} interface. + */ + public void testCopyMessageBetweenQueuesWithBrokerRestart() throws Exception + { + final int numberOfMessagesToSend = 1; + + sendMessage(_session, _sourceQueue, numberOfMessagesToSend); + syncSession(_session); + assertEquals("Unexpected queue depth after send", numberOfMessagesToSend, _managedSourceQueue.getMessageCount().intValue()); + + restartBroker(); + + createManagementInterfacesForQueues(); + createConnectionAndSession(); + + List<Long> amqMessagesIds = getAMQMessageIdsOn(_managedSourceQueue, 1, numberOfMessagesToSend); + + // Move messages to destination + long messageId = amqMessagesIds.get(0); + _managedSourceQueue.copyMessages(messageId, messageId, _destinationQueueName); + + assertEquals("Unexpected queue depth on destination queue after copy", 1, _managedDestinationQueue.getMessageCount().intValue()); + assertEquals("Unexpected queue depth on source queue after copy", 1, _managedSourceQueue.getMessageCount().intValue()); + + assertMessageIndicesOn(_destinationQueue, 0); + } + + @Override + public Message createNextMessage(Session session, int messageNumber) throws JMSException + { + Message message = session.createTextMessage(getContentForMessageNumber(messageNumber)); + message.setIntProperty(INDEX, messageNumber); + return message; + } + + private void startAsyncConsumerOn(Destination queue, Connection asyncConnection, + final CountDownLatch requiredNumberOfMessagesRead, final AtomicInteger totalConsumed) throws Exception + { + Session session = asyncConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); + MessageConsumer consumer = session.createConsumer(queue); + consumer.setMessageListener(new MessageListener() + { + + @Override + public void onMessage(Message arg0) + { + totalConsumed.incrementAndGet(); + requiredNumberOfMessagesRead.countDown(); + } + }); + } + + private void assertMessageIndicesOn(Destination queue, int... expectedIndices) throws Exception + { + MessageConsumer consumer = _session.createConsumer(queue); + + for (int i : expectedIndices) + { + TextMessage message = (TextMessage)consumer.receive(1000); + assertNotNull("Expected message with index " + i, message); + assertEquals("Expected message with index " + i, i, message.getIntProperty(INDEX)); + assertEquals("Expected message content", getContentForMessageNumber(i), message.getText()); + } + + assertNull("Unexpected message encountered", consumer.receive(1000)); + } + + private List<Long> getAMQMessageIdsOn(ManagedQueue managedQueue, long startIndex, long endIndex) throws Exception + { + final SortedSet<Long> messageIds = new TreeSet<Long>(); + + final TabularData tab = managedQueue.viewMessages(startIndex, endIndex); + final Iterator<CompositeData> rowItr = (Iterator<CompositeData>) tab.values().iterator(); + while(rowItr.hasNext()) + { + final CompositeData row = rowItr.next(); + long amqMessageId = (Long)row.get(ManagedQueue.MSG_AMQ_ID); + messageIds.add(amqMessageId); + } + + return new ArrayList<Long>(messageIds); + } + + /** + * + * Utility method to convert array of Strings in the form x = y into a + * map with key/value x => y. + * + */ + private Map<String,String> headerArrayToMap(final String[] headerArray) + { + final Map<String, String> headerMap = new HashMap<String, String>(); + final List<String> headerList = Arrays.asList(headerArray); + for (Iterator<String> iterator = headerList.iterator(); iterator.hasNext();) + { + final String nameValuePair = iterator.next(); + final String[] nameValue = nameValuePair.split(" *= *", 2); + headerMap.put(nameValue[0], nameValue[1]); + } + return headerMap; + } + + private void createQueueOnBroker(Destination destination) throws JMSException + { + _session.createConsumer(destination).close(); // Create a consumer only to cause queue creation + } + + private void syncSession(Session session) throws Exception + { + ((AMQSession<?,?>)session).sync(); + } + + private void createConnectionAndSession() throws JMSException, + NamingException + { + _connection = getConnection(); + _connection.start(); + _session = _connection.createSession(true, Session.SESSION_TRANSACTED); + } + + private void createManagementInterfacesForQueues() + { + _managedSourceQueue = _jmxUtils.getManagedQueue(_sourceQueueName); + _managedDestinationQueue = _jmxUtils.getManagedQueue(_destinationQueueName); + } + + private String getContentForMessageNumber(int msgCount) + { + return "Message count " + msgCount; + } + + private final class RecordingNotificationListener implements NotificationListener + { + private final CountDownLatch _notificationReceivedLatch; + private final AtomicInteger _numberOfNotifications; + private final AtomicReference<Notification> _lastNotification; + + private RecordingNotificationListener(int expectedNumberOfNotifications) + { + _notificationReceivedLatch = new CountDownLatch(expectedNumberOfNotifications); + _numberOfNotifications = new AtomicInteger(0); + _lastNotification = new AtomicReference<Notification>(); + } + + @Override + public void handleNotification(Notification notification, Object handback) + { + _lastNotification.set(notification); + _numberOfNotifications.incrementAndGet(); + _notificationReceivedLatch.countDown(); + } + + public int getNumberOfNotificationsReceived() + { + return _numberOfNotifications.get(); + } + + public Notification getLastNotification() + { + return _lastNotification.get(); + } + + public void awaitExpectedNotifications(long timeout, TimeUnit timeunit) throws InterruptedException + { + _notificationReceivedLatch.await(timeout, timeunit); + } + } + +} diff --git a/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/StatisticsTest.java b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/StatisticsTest.java new file mode 100644 index 0000000000..c3fff94923 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/StatisticsTest.java @@ -0,0 +1,204 @@ +/* + * 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.systest.management.jmx; + +import java.util.List; + +import javax.jms.Connection; +import javax.jms.MessageConsumer; +import javax.jms.Queue; +import javax.jms.Session; + +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.client.AMQSession; +import org.apache.qpid.management.common.mbeans.ManagedBroker; +import org.apache.qpid.management.common.mbeans.ManagedConnection; +import org.apache.qpid.management.common.mbeans.ServerInformation; +import org.apache.qpid.test.utils.JMXTestUtils; +import org.apache.qpid.test.utils.QpidBrokerTestCase; + +public class StatisticsTest extends QpidBrokerTestCase +{ + private static final String TEST_USER = "admin"; + private static final String TEST_PASSWORD = "admin"; + private static final int MESSAGE_COUNT_TEST = 5; + private static final int MESSAGE_COUNT_DEV = 9; + + private JMXTestUtils _jmxUtils; + private Connection _test1, _dev; + private Session _testSession, _developmentSession; + private Queue _developmentQueue, _testQueue; + protected String _brokerUrl; + + @Override + public void setUp() throws Exception + { + _jmxUtils = new JMXTestUtils(this, TEST_USER, TEST_PASSWORD); + _jmxUtils.setUp(); + + super.setUp(); + + _brokerUrl = getBroker().toString(); + _test1 = new AMQConnection(_brokerUrl, TEST_USER, TEST_PASSWORD, "clientid", "test"); + _dev = new AMQConnection(_brokerUrl, TEST_USER, TEST_PASSWORD, "clientid", "development"); + _test1.start(); + _dev.start(); + + _testSession = _test1.createSession(true, Session.SESSION_TRANSACTED); + _developmentSession = _dev.createSession(true, Session.SESSION_TRANSACTED); + + _developmentQueue = _developmentSession.createQueue(getTestQueueName()); + _testQueue = _testSession.createQueue(getTestQueueName()); + + //Create queues by opening and closing consumers + final MessageConsumer testConsumer = _testSession.createConsumer(_testQueue); + testConsumer.close(); + final MessageConsumer developmentConsumer = _developmentSession.createConsumer(_developmentQueue); + developmentConsumer.close(); + + _jmxUtils.open(); + } + + @Override + public void tearDown() throws Exception + { + _jmxUtils.close(); + + super.tearDown(); + } + + public void testInitialStatisticValues() throws Exception + { + //Check initial values + checkSingleConnectionOnVHostStatistics("test", 0, 0, 0, 0); + checkVHostStatistics("test", 0, 0, 0, 0); + checkSingleConnectionOnVHostStatistics("development", 0, 0, 0, 0); + checkVHostStatistics("development", 0, 0, 0, 0); + checkBrokerStatistics(0, 0, 0, 0); + } + + public void testSendOnSingleVHost() throws Exception + { + sendMessagesAndSync(_testSession, _testQueue, MESSAGE_COUNT_TEST); + + //Check values + checkSingleConnectionOnVHostStatistics("test", MESSAGE_COUNT_TEST, 0, MESSAGE_COUNT_TEST * DEFAULT_MESSAGE_SIZE, 0); + checkVHostStatistics("test", MESSAGE_COUNT_TEST, 0, MESSAGE_COUNT_TEST * DEFAULT_MESSAGE_SIZE, 0); + checkSingleConnectionOnVHostStatistics("development", 0, 0, 0, 0); + checkVHostStatistics("development", 0, 0, 0, 0); + checkBrokerStatistics(MESSAGE_COUNT_TEST, 0, MESSAGE_COUNT_TEST * DEFAULT_MESSAGE_SIZE, 0); + } + + public void testSendOnTwoVHosts() throws Exception + { + sendMessagesAndSync(_testSession, _testQueue, MESSAGE_COUNT_TEST); + sendMessagesAndSync(_developmentSession, _developmentQueue, MESSAGE_COUNT_DEV); + + //Check values + checkSingleConnectionOnVHostStatistics("test", MESSAGE_COUNT_TEST, 0, MESSAGE_COUNT_TEST * DEFAULT_MESSAGE_SIZE, 0); + checkVHostStatistics("test", MESSAGE_COUNT_TEST, 0, MESSAGE_COUNT_TEST * DEFAULT_MESSAGE_SIZE, 0); + checkSingleConnectionOnVHostStatistics("development", MESSAGE_COUNT_DEV, 0, MESSAGE_COUNT_DEV * DEFAULT_MESSAGE_SIZE, 0); + checkVHostStatistics("development", MESSAGE_COUNT_DEV, 0, MESSAGE_COUNT_DEV * DEFAULT_MESSAGE_SIZE, 0); + checkBrokerStatistics(MESSAGE_COUNT_TEST + MESSAGE_COUNT_DEV, 0, (MESSAGE_COUNT_TEST + MESSAGE_COUNT_DEV) * DEFAULT_MESSAGE_SIZE, 0); + } + + public void testSendAndConsumeOnSingleVHost() throws Exception + { + sendMessagesAndSync(_testSession, _testQueue, MESSAGE_COUNT_TEST); + consumeMessages(_testSession, _testQueue, MESSAGE_COUNT_TEST); + + //Check values + checkSingleConnectionOnVHostStatistics("test", MESSAGE_COUNT_TEST, MESSAGE_COUNT_TEST, MESSAGE_COUNT_TEST * DEFAULT_MESSAGE_SIZE, MESSAGE_COUNT_TEST * DEFAULT_MESSAGE_SIZE); + checkVHostStatistics("test", MESSAGE_COUNT_TEST, MESSAGE_COUNT_TEST, MESSAGE_COUNT_TEST * DEFAULT_MESSAGE_SIZE, MESSAGE_COUNT_TEST * DEFAULT_MESSAGE_SIZE); + checkSingleConnectionOnVHostStatistics("development", 0, 0, 0, 0); + checkVHostStatistics("development", 0, 0, 0, 0); + checkBrokerStatistics(MESSAGE_COUNT_TEST, MESSAGE_COUNT_TEST, MESSAGE_COUNT_TEST * DEFAULT_MESSAGE_SIZE, MESSAGE_COUNT_TEST * DEFAULT_MESSAGE_SIZE); + } + + public void testSendAndConsumeOnTwoVHosts() throws Exception + { + sendMessagesAndSync(_testSession, _testQueue, MESSAGE_COUNT_TEST); + sendMessagesAndSync(_developmentSession, _developmentQueue, MESSAGE_COUNT_DEV); + consumeMessages(_testSession, _testQueue, MESSAGE_COUNT_TEST); + consumeMessages(_developmentSession, _developmentQueue, MESSAGE_COUNT_DEV); + + //Check values + checkSingleConnectionOnVHostStatistics("test", MESSAGE_COUNT_TEST, MESSAGE_COUNT_TEST, MESSAGE_COUNT_TEST * DEFAULT_MESSAGE_SIZE, MESSAGE_COUNT_TEST * DEFAULT_MESSAGE_SIZE); + checkVHostStatistics("test", MESSAGE_COUNT_TEST, MESSAGE_COUNT_TEST, MESSAGE_COUNT_TEST * DEFAULT_MESSAGE_SIZE, MESSAGE_COUNT_TEST * DEFAULT_MESSAGE_SIZE); + checkSingleConnectionOnVHostStatistics("development", MESSAGE_COUNT_DEV, MESSAGE_COUNT_DEV, MESSAGE_COUNT_DEV * DEFAULT_MESSAGE_SIZE, MESSAGE_COUNT_DEV * DEFAULT_MESSAGE_SIZE); + checkVHostStatistics("development", MESSAGE_COUNT_DEV, MESSAGE_COUNT_DEV, MESSAGE_COUNT_DEV * DEFAULT_MESSAGE_SIZE, MESSAGE_COUNT_DEV * DEFAULT_MESSAGE_SIZE); + checkBrokerStatistics(MESSAGE_COUNT_TEST + MESSAGE_COUNT_DEV, MESSAGE_COUNT_TEST + MESSAGE_COUNT_DEV, (MESSAGE_COUNT_TEST + MESSAGE_COUNT_DEV) * DEFAULT_MESSAGE_SIZE, (MESSAGE_COUNT_TEST + MESSAGE_COUNT_DEV) * DEFAULT_MESSAGE_SIZE); + } + + private void sendMessagesAndSync(Session session, Queue queue, int numberOfMessages) throws Exception + { + //Send messages via connection on and sync + sendMessage(session, queue, numberOfMessages); + ((AMQSession<?,?>)session).sync(); + } + + private void consumeMessages(Session session, Queue queue, int numberOfMessages) throws Exception + { + //consume the messages on the virtual host + final MessageConsumer consumer = session.createConsumer(queue); + for (int i = 0 ; i < numberOfMessages ; i++) + { + assertNotNull("an expected message was not received", consumer.receive(1500)); + } + session.commit(); + consumer.close(); + } + + private void checkSingleConnectionOnVHostStatistics(String vHostName, long messagesSent, long messagesReceived, long dataSent, long dataReceived) + { + List<ManagedConnection> managedConnections = _jmxUtils.getManagedConnections(vHostName); + assertEquals(1, managedConnections.size()); + + ManagedConnection managedConnection = managedConnections.get(0); + + assertEquals(messagesSent, managedConnection.getTotalMessagesReceived()); + assertEquals(messagesReceived, managedConnection.getTotalMessagesDelivered()); + + assertEquals(dataSent, managedConnection.getTotalDataReceived()); + assertEquals(dataReceived, managedConnection.getTotalDataDelivered()); + } + + private void checkVHostStatistics(String vHostName, long messagesSent, long messagesReceived, long dataSent, long dataReceived) + { + ManagedBroker vhost = _jmxUtils.getManagedBroker(vHostName); + + assertEquals(messagesSent, vhost.getTotalMessagesReceived()); + assertEquals(messagesReceived, vhost.getTotalMessagesDelivered()); + + assertEquals(dataSent, vhost.getTotalDataReceived()); + assertEquals(dataReceived, vhost.getTotalDataDelivered()); + } + + private void checkBrokerStatistics(long messagesSent, long messagesReceived, long dataSent, long dataReceived) + { + ServerInformation broker = _jmxUtils.getServerInformation(); + + assertEquals(messagesSent, broker.getTotalMessagesReceived()); + assertEquals(messagesReceived, broker.getTotalMessagesDelivered()); + + assertEquals(dataSent, broker.getTotalDataReceived()); + assertEquals(dataReceived, broker.getTotalDataDelivered()); + } +} diff --git a/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/UserManagementTest.java b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/UserManagementTest.java new file mode 100644 index 0000000000..62b1b554a9 --- /dev/null +++ b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/UserManagementTest.java @@ -0,0 +1,251 @@ +/* + * 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.systest.management.jmx; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; + +import javax.jms.Connection; +import javax.jms.JMSException; + +import org.apache.qpid.management.common.mbeans.UserManagement; +import org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.test.utils.JMXTestUtils; +import org.apache.qpid.test.utils.QpidBrokerTestCase; +import org.apache.qpid.tools.security.Passwd; + +/** + * System test for User Management. + * + */ +public class UserManagementTest extends QpidBrokerTestCase +{ + private static final String TEST_NEWPASSWORD = "newpassword"; + private static final String TEST_PASSWORD = "password"; + private JMXTestUtils _jmxUtils; + private String _testUserName; + private File _passwordFile; + private UserManagement _userManagement; + private Passwd _passwd; + + public void setUp() throws Exception + { + _passwd = createPasswordEncodingUtility(); + _passwordFile = createTemporaryPasswordFileWithJmxAdminUser(); + + setConfigurationProperty("security.pd-auth-manager.principal-database.class", getPrincipalDatabaseImplClass().getName()); + setConfigurationProperty("security.pd-auth-manager.principal-database.attributes.attribute.name", "passwordFile"); + setConfigurationProperty("security.pd-auth-manager.principal-database.attributes.attribute.value", _passwordFile.getAbsolutePath()); + + _jmxUtils = new JMXTestUtils(this); + _jmxUtils.setUp(); + + super.setUp(); + _jmxUtils.open(); + + _testUserName = getTestName() + System.currentTimeMillis(); + + _userManagement = _jmxUtils.getUserManagement(); + } + + + public void tearDown() throws Exception + { + try + { + if (_jmxUtils != null) + { + _jmxUtils.close(); + } + } + finally + { + super.tearDown(); + } + } + + public void testCreateUser() throws Exception + { + final int initialNumberOfUsers = _userManagement.viewUsers().size(); + assertFileDoesNotContainsPasswordForUser(_testUserName); + + boolean success = _userManagement.createUser(_testUserName, TEST_PASSWORD); + assertTrue("Should have been able to create new user " + _testUserName, success); + assertEquals("Unexpected number of users after add", initialNumberOfUsers + 1, _userManagement.viewUsers().size()); + + assertFileContainsPasswordForUser(_testUserName); + } + + public void testJmsLoginForNewUser() throws Exception + { + assertJmsConnectionFails(_testUserName, TEST_PASSWORD); + testCreateUser(); + + assertJmsConnectionSucceeds(_testUserName, TEST_PASSWORD); + } + + public void testDeleteUser() throws Exception + { + final int initialNumberOfUsers = _userManagement.viewUsers().size(); + + testCreateUser(); + + boolean success = _userManagement.deleteUser(_testUserName); + assertTrue("Should have been able to delete new user " + _testUserName, success); + assertEquals("Unexpected number of users after delete", initialNumberOfUsers, _userManagement.viewUsers().size()); + assertFileDoesNotContainsPasswordForUser(_testUserName); + } + + public void testJmsLoginNotPossibleForDeletedUser() throws Exception + { + testDeleteUser(); + + assertJmsConnectionFails(_testUserName, TEST_PASSWORD); + } + + public void testSetPassword() throws Exception + { + testCreateUser(); + + _userManagement.setPassword(_testUserName, TEST_NEWPASSWORD); + + assertFileContainsPasswordForUser(_testUserName); + } + + public void testJmsLoginForPasswordChangedUser() throws Exception + { + testSetPassword(); + + assertJmsConnectionSucceeds(_testUserName, TEST_NEWPASSWORD); + assertJmsConnectionFails(_testUserName, TEST_PASSWORD); + } + + public void testReload() throws Exception + { + writePasswordFile(_passwordFile, JMXTestUtils.DEFAULT_USERID, JMXTestUtils.DEFAULT_PASSWORD, _testUserName, TEST_PASSWORD); + + assertJmsConnectionFails(_testUserName, TEST_PASSWORD); + + _userManagement.reloadData(); + + assertJmsConnectionSucceeds(_testUserName, TEST_PASSWORD); + } + + protected Passwd createPasswordEncodingUtility() + { + return new Passwd() + { + @Override + public String getOutput(String username, String password) + { + return username + ":" + password; + } + }; + } + + protected Class<? extends PrincipalDatabase> getPrincipalDatabaseImplClass() + { + return PlainPasswordFilePrincipalDatabase.class; + } + + private File createTemporaryPasswordFileWithJmxAdminUser() throws Exception + { + File passwordFile = File.createTempFile("passwd", "pwd"); + passwordFile.deleteOnExit(); + writePasswordFile(passwordFile, JMXTestUtils.DEFAULT_USERID, JMXTestUtils.DEFAULT_PASSWORD); + return passwordFile; + } + + private void writePasswordFile(File passwordFile, String... userNamePasswordPairs) throws Exception + { + FileWriter writer = null; + try + { + writer = new FileWriter(passwordFile); + for (int i = 0; i < userNamePasswordPairs.length; i=i+2) + { + String username = userNamePasswordPairs[i]; + String password = userNamePasswordPairs[i+1]; + writer.append(_passwd.getOutput(username, password) + "\n"); + } + } + finally + { + writer.close(); + } + } + + + private void assertFileContainsPasswordForUser(String username) throws IOException + { + assertTrue("Could not find password for user " + username + " within " + _passwordFile, passwordFileContainsUser(username)); + } + + private void assertFileDoesNotContainsPasswordForUser(String username) throws IOException + { + assertFalse("Could not find password for user " + username + " within " + _passwordFile, passwordFileContainsUser(username)); + } + + private boolean passwordFileContainsUser(String username) throws IOException + { + BufferedReader reader = null; + try + { + reader = new BufferedReader(new FileReader(_passwordFile)); + String line = reader.readLine(); + while(line != null) + { + if (line.startsWith(username)) + { + return true; + } + line = reader.readLine(); + } + + return false; + } + finally + { + reader.close(); + } + } + + private void assertJmsConnectionSucceeds(String username, String password) throws Exception + { + Connection connection = getConnection(username, password); + assertNotNull(connection); + } + + private void assertJmsConnectionFails(String username, String password) throws Exception + { + try + { + getConnection(username, password); + fail("Exception not thrown"); + } + catch (JMSException e) + { + // PASS + } + } +} diff --git a/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/UserManagementWithBase64MD5PasswordsTest.java b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/UserManagementWithBase64MD5PasswordsTest.java new file mode 100644 index 0000000000..84a66232ce --- /dev/null +++ b/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/systest/management/jmx/UserManagementWithBase64MD5PasswordsTest.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.qpid.systest.management.jmx; + +import org.apache.qpid.server.security.auth.database.Base64MD5PasswordFilePrincipalDatabase; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.tools.security.Passwd; + +public class UserManagementWithBase64MD5PasswordsTest extends UserManagementTest +{ + @Override + protected Passwd createPasswordEncodingUtility() + { + return new Passwd(); + } + + @Override + protected Class<? extends PrincipalDatabase> getPrincipalDatabaseImplClass() + { + return Base64MD5PasswordFilePrincipalDatabase.class; + } + +} |
