diff options
| author | Robert Gemmell <robbie@apache.org> | 2012-06-28 16:46:12 +0000 |
|---|---|---|
| committer | Robert Gemmell <robbie@apache.org> | 2012-06-28 16:46:12 +0000 |
| commit | edb211dc86c6fb1cf64075b20c7e932dd79849de (patch) | |
| tree | 5fc4ebb7dc5ee51a19760a14f45282edeffae01a /java/broker-plugins | |
| parent | c2bbeac3f94cb57e142648ebcac354a41ad5ab6f (diff) | |
| download | qpid-python-edb211dc86c6fb1cf64075b20c7e932dd79849de.tar.gz | |
QPID-3998, QPID-3999, QPID-4093: add new management plugins for jmx/rest/webui functionality, partial merge from the java-config-and-management branch at r1355039
git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk/qpid@1355072 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'java/broker-plugins')
102 files changed, 16671 insertions, 128 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/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/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/jmx/MANIFEST.MF b/java/broker-plugins/jmx/MANIFEST.MF new file mode 100644 index 0000000000..b13ff7f132 --- /dev/null +++ b/java/broker-plugins/jmx/MANIFEST.MF @@ -0,0 +1,65 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Qpid Broker-Plugins JMX +Bundle-SymbolicName: broker-plugins-jmx +Bundle-Description: 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.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/jmx/build.xml b/java/broker-plugins/jmx/build.xml new file mode 100644 index 0000000000..4deb0196e7 --- /dev/null +++ b/java/broker-plugins/jmx/build.xml @@ -0,0 +1,43 @@ +<!-- + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you under the Apache License, Version 2.0 (the + - "License"); you may not use this file except in compliance + - with the License. You may obtain a copy of the License at + - + - http://www.apache.org/licenses/LICENSE-2.0 + - + - Unless required by applicable law or agreed to in writing, + - software distributed under the License is distributed on an + - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + - KIND, either express or implied. See the License for the + - specific language governing permissions and limitations + - under the License. + --> +<project name="Qpid Broker-Plugins 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 broker-plugins broker-plugins-jmx 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" /> + + <import file="../../module.xml" /> + + <target name="bundle" depends="bundle-tasks" /> +</project> diff --git a/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/AMQManagedObject.java b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/AMQManagedObject.java new file mode 100644 index 0000000000..5c39a0c26a --- /dev/null +++ b/java/broker-plugins/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/jmx/src/main/java/org/apache/qpid/server/jmx/DefaultManagedObject.java b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/DefaultManagedObject.java new file mode 100644 index 0000000000..4446f96802 --- /dev/null +++ b/java/broker-plugins/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/jmx/src/main/java/org/apache/qpid/server/jmx/JMXActivator.java b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/JMXActivator.java new file mode 100644 index 0000000000..c588b40de7 --- /dev/null +++ b/java/broker-plugins/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/jmx/src/main/java/org/apache/qpid/server/jmx/JMXConfiguration.java b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/JMXConfiguration.java new file mode 100644 index 0000000000..dc9a712f90 --- /dev/null +++ b/java/broker-plugins/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/jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagedObjectRegistry.java b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagedObjectRegistry.java new file mode 100644 index 0000000000..0648235077 --- /dev/null +++ b/java/broker-plugins/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/jmx/src/main/java/org/apache/qpid/server/jmx/JMXService.java b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/JMXService.java new file mode 100644 index 0000000000..7519cea4db --- /dev/null +++ b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/JMXService.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 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.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.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 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); + } + + 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/jmx/src/main/java/org/apache/qpid/server/jmx/MBeanIntrospector.java b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/MBeanIntrospector.java new file mode 100644 index 0000000000..79ddc8cfc0 --- /dev/null +++ b/java/broker-plugins/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/jmx/src/main/java/org/apache/qpid/server/jmx/MBeanInvocationHandlerImpl.java b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/MBeanInvocationHandlerImpl.java new file mode 100644 index 0000000000..49f06d5121 --- /dev/null +++ b/java/broker-plugins/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/jmx/src/main/java/org/apache/qpid/server/jmx/MBeanProvider.java b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/MBeanProvider.java new file mode 100644 index 0000000000..83909dbe72 --- /dev/null +++ b/java/broker-plugins/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/jmx/src/main/java/org/apache/qpid/server/jmx/ManagedObject.java b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/ManagedObject.java new file mode 100644 index 0000000000..40b778fd93 --- /dev/null +++ b/java/broker-plugins/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/jmx/src/main/java/org/apache/qpid/server/jmx/ManagedObjectRegistry.java b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/ManagedObjectRegistry.java new file mode 100644 index 0000000000..2ae0ac7052 --- /dev/null +++ b/java/broker-plugins/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/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/AbstractStatisticsGatheringMBean.java b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/AbstractStatisticsGatheringMBean.java new file mode 100644 index 0000000000..4115f9f363 --- /dev/null +++ b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/AbstractStatisticsGatheringMBean.java @@ -0,0 +1,196 @@ +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; + +/** + * 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. + */ +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/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ConfigurationManagementMBean.java b/java/broker-plugins/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/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/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ConnectionMBean.java b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ConnectionMBean.java new file mode 100644 index 0000000000..024ee39318 --- /dev/null +++ b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ConnectionMBean.java @@ -0,0 +1,183 @@ +/* + * + * 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) + { + // TODO - Implement setStatisticsEnabled + 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/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ExchangeMBean.java b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ExchangeMBean.java new file mode 100644 index 0000000000..eb7e716af8 --- /dev/null +++ b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ExchangeMBean.java @@ -0,0 +1,323 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.jmx.mbeans; + +import org.apache.qpid.management.common.mbeans.ManagedExchange; +import org.apache.qpid.management.common.mbeans.ManagedQueue; +import org.apache.qpid.management.common.mbeans.annotations.MBeanOperationParameter; +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.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 +{ + + 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()]); + public static final String HEADERS_EXCHANGE_TYPE = "headers"; + + 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".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]); + } + } + } + + Queue queue = null; + VirtualHost vhost = _exchange.getParent(VirtualHost.class); + for(Queue aQueue : vhost.getQueues()) + { + if(aQueue.getName().equals(queueName)) + { + queue = aQueue; + break; + } + } + _exchange.createBinding(binding, queue, arguments, Collections.EMPTY_MAP); + } + + public void removeBinding(String queueName, String bindingKey) + throws IOException, JMException + { + Queue queue = null; + VirtualHost vhost = _exchange.getParent(VirtualHost.class); + for(Queue aQueue : vhost.getQueues()) + { + if(aQueue.getName().equals(queueName)) + { + queue = aQueue; + break; + } + } + + for(Binding binding : _exchange.getBindings()) + { + if(queue.equals(binding.getParent(Queue.class)) && bindingKey.equals(binding.getName())) + { + binding.delete(); + } + } + + } +} diff --git a/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/LoggingManagementMBean.java b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/LoggingManagementMBean.java new file mode 100644 index 0000000000..9ff45979ca --- /dev/null +++ b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/LoggingManagementMBean.java @@ -0,0 +1,832 @@ +/* + * 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.Level; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.xml.Log4jEntityResolver; +import org.apache.log4j.xml.QpidLog4JConfigurator; +import org.apache.log4j.xml.QpidLog4JConfigurator.IllegalLoggerLevelException; +import org.apache.log4j.xml.QpidLog4JConfigurator.QpidLog4JSaxErrorHandler; +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.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; + +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.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import static org.apache.log4j.xml.QpidLog4JConfigurator.LOCK; + + +/** MBean class for BrokerLoggingManagerMBean. It implements all the management features exposed for managing logging. */ +@MBeanDescription("Logging Management Interface") +public class LoggingManagementMBean extends AMQManagedObject implements LoggingManagement +{ + + private static final Logger _logger = Logger.getLogger(LoggingManagementMBean.class); + private String _log4jConfigFileName; + private int _log4jLogWatchInterval; + private static final String INHERITED = "INHERITED"; + private static final String[] LEVELS = new String[]{Level.ALL.toString(), Level.TRACE.toString(), + Level.DEBUG.toString(), Level.INFO.toString(), + Level.WARN.toString(), Level.ERROR.toString(), + Level.FATAL.toString(),Level.OFF.toString(), + INHERITED}; + private static TabularType _loggerLevelTabularType; + private static CompositeType _loggerLevelCompositeType; + + static + { + try + { + OpenType[] loggerLevelItemTypes = new OpenType[]{SimpleType.STRING, SimpleType.STRING}; + + _loggerLevelCompositeType = 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); + + _loggerLevelTabularType = new TabularType("LoggerLevel", "List of loggers with levels", + _loggerLevelCompositeType, + TABULAR_UNIQUE_INDEX.toArray(new String[TABULAR_UNIQUE_INDEX.size()])); + } + catch (OpenDataException e) + { + _logger.error("Tabular data setup for viewing logger levels was incorrect."); + _loggerLevelTabularType = null; + } + } + + public LoggingManagementMBean(String log4jConfigFileName, + int log4jLogWatchInterval, + ManagedObjectRegistry registry) throws JMException + { + super(LoggingManagement.class, LoggingManagement.TYPE, registry); + _log4jConfigFileName = log4jConfigFileName; + _log4jLogWatchInterval = log4jLogWatchInterval; + register(); + } + + public String getObjectInstanceName() + { + return LoggingManagement.TYPE; + } + + public Integer getLog4jLogWatchInterval() + { + return _log4jLogWatchInterval; + } + + public String[] getAvailableLoggerLevels() + { + return LEVELS; + } + @SuppressWarnings("unchecked") + public synchronized boolean setRuntimeLoggerLevel(String logger, String level) + { + //check specified level is valid + Level newLevel; + try + { + newLevel = getLevel(level); + } + catch (Exception e) + { + return false; + } + + //check specified logger exists + Enumeration loggers = LogManager.getCurrentLoggers(); + Boolean loggerExists = false; + + while(loggers.hasMoreElements()) + { + Logger log = (Logger) loggers.nextElement(); + if (log.getName().equals(logger)) + { + loggerExists = true; + break; + } + } + + if(!loggerExists) + { + return false; + } + + //set the logger to the new level + _logger.info("Setting level to " + level + " for logger: " + logger); + + Logger log = Logger.getLogger(logger); + log.setLevel(newLevel); + + return true; + } + + @SuppressWarnings("unchecked") + public synchronized TabularData viewEffectiveRuntimeLoggerLevels() + { + if (_loggerLevelTabularType == null) + { + _logger.warn("TabluarData type not set up correctly"); + return null; + } + + _logger.info("Getting levels for currently active log4j loggers"); + + Enumeration loggers = LogManager.getCurrentLoggers(); + + TabularData loggerLevelList = new TabularDataSupport(_loggerLevelTabularType); + + Logger logger; + String loggerName; + String level; + + try + { + while(loggers.hasMoreElements()){ + logger = (Logger) loggers.nextElement(); + + loggerName = logger.getName(); + level = logger.getEffectiveLevel().toString(); + + Object[] itemData = {loggerName, level}; + CompositeData loggerData = new CompositeDataSupport(_loggerLevelCompositeType, + COMPOSITE_ITEM_NAMES.toArray(new String[COMPOSITE_ITEM_NAMES.size()]), itemData); + loggerLevelList.put(loggerData); + } + } + catch (OpenDataException e) + { + _logger.warn("Unable to create logger level list due to :" + e); + return null; + } + + return loggerLevelList; + + } + + public synchronized String getRuntimeRootLoggerLevel() + { + Logger rootLogger = Logger.getRootLogger(); + + return rootLogger.getLevel().toString(); + } + + public synchronized boolean setRuntimeRootLoggerLevel(String level) + { + Level newLevel; + try + { + newLevel = getLevel(level); + } + catch (Exception e) + { + return false; + } + + if(newLevel == null) + { + //A null Level reference implies inheritance. Setting the runtime RootLogger + //to null is catastrophic (and prevented by Log4J at startup and runtime anyway). + return false; + } + + _logger.info("Setting RootLogger level to " + level); + + Logger log = Logger.getRootLogger(); + log.setLevel(newLevel); + + return true; + } + + //method to convert from a string to a log4j Level, throws exception if the given value is invalid + private Level getLevel(String level) throws Exception + { + if("null".equalsIgnoreCase(level) || INHERITED.equalsIgnoreCase(level)) + { + //the string "null" or "inherited" signals to inherit from a parent logger, + //using a null Level reference for the logger. + return null; + } + + Level newLevel = Level.toLevel(level); + + //above Level.toLevel call returns a DEBUG Level if the request fails. Check the result. + if (newLevel.equals(Level.DEBUG) && !(level.equalsIgnoreCase("debug"))) + { + //received DEBUG but we did not ask for it, the Level request failed. + throw new Exception("Invalid level name"); + } + + return newLevel; + } + + //method to parse the XML configuration file, validating it in the process, and returning a DOM Document of the content. + private static synchronized Document parseConfigFile(String fileName) throws IOException + { + try + { + LOCK.lock(); + + //check file was specified, exists, and is readable + if(fileName == null) + { + _logger.warn("Provided log4j XML configuration filename is null"); + throw new IOException("Provided log4j XML configuration filename is null"); + } + + File configFile = new File(fileName); + + if (!configFile.exists()) + { + _logger.warn("The log4j XML configuration file could not be found: " + fileName); + throw new IOException("The log4j XML configuration file could not be found"); + } + else if (!configFile.canRead()) + { + _logger.warn("The log4j XML configuration file is not readable: " + fileName); + throw new IOException("The log4j XML configuration file is not readable"); + } + + //parse it + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder; + Document doc; + + ErrorHandler errHandler = new QpidLog4JSaxErrorHandler(); + try + { + docFactory.setValidating(true); + docBuilder = docFactory.newDocumentBuilder(); + docBuilder.setErrorHandler(errHandler); + docBuilder.setEntityResolver(new Log4jEntityResolver()); + doc = docBuilder.parse(fileName); + } + catch (ParserConfigurationException e) + { + _logger.warn("Unable to parse the log4j XML file due to possible configuration error: " + e); + //recommended that MBeans should use java.* and javax.* exceptions only + throw new IOException("Unable to parse the log4j XML file due to possible configuration error: " + e.getMessage()); + } + catch (SAXException e) + { + _logger.warn("The specified log4j XML file is invalid: " + e); + //recommended that MBeans should use standard java.* and javax.* exceptions only + throw new IOException("The specified log4j XML file is invalid: " + e.getMessage()); + } + catch (IOException e) + { + _logger.warn("Unable to parse the specified log4j XML file" + e); + throw new IOException("Unable to parse the specified log4j XML file: " + e.getMessage()); + } + + return doc; + } + finally + { + LOCK.unlock(); + } + } + + + private static synchronized boolean writeUpdatedConfigFile(String log4jConfigFileName, Document doc) throws IOException + { + try + { + LOCK.lock(); + + File log4jConfigFile = new File(log4jConfigFileName); + + if (!log4jConfigFile.canWrite()) + { + _logger.warn("Specified log4j XML configuration file is not writable: " + log4jConfigFile); + throw new IOException("Specified log4j XML configuration file is not writable"); + } + + Transformer transformer = null; + try + { + transformer = TransformerFactory.newInstance().newTransformer(); + } + catch (Exception e) + { + _logger.warn("Could not create an XML transformer: " +e); + return false; + } + + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "log4j.dtd"); + DOMSource source = new DOMSource(doc); + + File tmp; + Random r = new Random(); + do + { + tmp = new File(log4jConfigFile.getPath() + r.nextInt() + ".tmp"); + } + while(tmp.exists()); + + tmp.deleteOnExit(); + + try + { + StreamResult result = new StreamResult(tmp); + transformer.transform(source, result); + } + catch (TransformerException e) + { + _logger.warn("Could not transform the XML into new file: " +e); + throw new IOException("Could not transform the XML into new file: " +e); + } + + // Swap temp file in to replace existing configuration file. + File old = new File(log4jConfigFile.getAbsoluteFile() + ".old"); + if (old.exists()) + { + old.delete(); + } + + if(!log4jConfigFile.renameTo(old)) + { + //unable to rename the existing file to the backup name + _logger.error("Could not backup the existing log4j XML file"); + throw new IOException("Could not backup the existing log4j XML file"); + } + + if(!tmp.renameTo(log4jConfigFile)) + { + //failed to rename the new file to the required filename + + if(!old.renameTo(log4jConfigFile)) + { + //unable to return the backup to required filename + _logger.error("Could not rename the new log4j configuration file into place, and unable to restore original file"); + throw new IOException("Could not rename the new log4j configuration file into place, and unable to restore original file"); + } + + _logger.error("Could not rename the new log4j configuration file into place"); + throw new IOException("Could not rename the new log4j configuration file into place"); + } + + return true; + } + finally + { + LOCK.unlock(); + } + } + + + /* The log4j XML configuration file DTD defines three possible element + * combinations for specifying optional logger+level settings. + * Must account for the following: + * + * <category name="x"> <priority value="y"/> </category> OR + * <category name="x"> <level value="y"/> </category> OR + * <logger name="x"> <level value="y"/> </logger> + * + * Noting also that the level/priority child element is optional too, + * and not the only possible child element. + */ + + public static synchronized Map<String,String> retrieveConfigFileLoggersLevels(String fileName) throws IOException + { + try + { + LOCK.lock(); + + Document doc = parseConfigFile(fileName); + + HashMap<String,String> loggerLevelList = new HashMap<String,String>(); + + //retrieve the 'category' element nodes + NodeList categoryElements = doc.getElementsByTagName("category"); + + String categoryName; + String priority = null; + + for (int i = 0; i < categoryElements.getLength(); i++) + { + Element categoryElement = (Element) categoryElements.item(i); + categoryName = categoryElement.getAttribute("name"); + + //retrieve the category's mandatory 'priority' or 'level' element's value. + //It may not be the only child node, so request by tag name. + NodeList priorityElements = categoryElement.getElementsByTagName("priority"); + NodeList levelElements = categoryElement.getElementsByTagName("level"); + + if (priorityElements.getLength() != 0) + { + Element priorityElement = (Element) priorityElements.item(0); + priority = priorityElement.getAttribute("value"); + } + else if (levelElements.getLength() != 0) + { + Element levelElement = (Element) levelElements.item(0); + priority = levelElement.getAttribute("value"); + } + else + { + //there is no exiting priority or level to view, move onto next category/logger + continue; + } + + loggerLevelList.put(categoryName, priority); + } + + //retrieve the 'logger' element nodes + NodeList loggerElements = doc.getElementsByTagName("logger"); + + String loggerName; + String level; + + for (int i = 0; i < loggerElements.getLength(); i++) + { + Element loggerElement = (Element) loggerElements.item(i); + loggerName = loggerElement.getAttribute("name"); + + //retrieve the logger's mandatory 'level' element's value + //It may not be the only child node, so request by tag name. + NodeList levelElements = loggerElement.getElementsByTagName("level"); + + Element levelElement = (Element) levelElements.item(0); + level = levelElement.getAttribute("value"); + + loggerLevelList.put(loggerName, level); + } + + return loggerLevelList; + } + finally + { + LOCK.unlock(); + } + } + + public synchronized TabularData viewConfigFileLoggerLevels() throws IOException + { + try + { + LOCK.lock(); + + if (_loggerLevelTabularType == null) + { + _logger.warn("TabluarData type not set up correctly"); + return null; + } + + _logger.info("Getting logger levels from log4j configuration file"); + + TabularData loggerLevelList = new TabularDataSupport(_loggerLevelTabularType); + + Map<String,String> levels = retrieveConfigFileLoggersLevels(_log4jConfigFileName); + + for (Map.Entry<String,String> entry : levels.entrySet()) + { + String loggerName = entry.getKey(); + String level = entry.getValue(); + + try + { + Object[] itemData = {loggerName, level.toUpperCase()}; + CompositeData loggerData = new CompositeDataSupport(_loggerLevelCompositeType, + COMPOSITE_ITEM_NAMES.toArray(new String[COMPOSITE_ITEM_NAMES.size()]), itemData); + loggerLevelList.put(loggerData); + } + catch (OpenDataException e) + { + _logger.warn("Unable to create logger level list due to :" + e); + return null; + } + } + + return loggerLevelList; + } + finally + { + LOCK.unlock(); + } + } + + public synchronized boolean setConfigFileLoggerLevel(String logger, String level) throws IOException + { + try + { + LOCK.lock(); + + //check that the specified level is a valid log4j Level + try + { + getLevel(level); + } + catch (Exception e) + { + //it isnt a valid level + return false; + } + + _logger.info("Setting level to " + level + " for logger '" + logger + + "' in log4j xml configuration file: " + _log4jConfigFileName); + + Document doc = parseConfigFile(_log4jConfigFileName); + + //retrieve the 'category' and 'logger' element nodes + NodeList categoryElements = doc.getElementsByTagName("category"); + NodeList loggerElements = doc.getElementsByTagName("logger"); + + //collect them into a single elements list + List<Element> logElements = new ArrayList<Element>(); + + for (int i = 0; i < categoryElements.getLength(); i++) + { + logElements.add((Element) categoryElements.item(i)); + } + for (int i = 0; i < loggerElements.getLength(); i++) + { + logElements.add((Element) loggerElements.item(i)); + } + + //try to locate the specified logger/category in the elements retrieved + Element logElement = null; + for (Element e : logElements) + { + if (e.getAttribute("name").equals(logger)) + { + logElement = e; + break; + } + } + + if (logElement == null) + { + //no loggers/categories with given name found, does not exist to update + _logger.warn("Specified logger does not exist in the configuration file: " +logger); + return false; + } + + //retrieve the optional 'priority' or 'level' sub-element value. + //It may not be the only child node, so request by tag name. + NodeList priorityElements = logElement.getElementsByTagName("priority"); + NodeList levelElements = logElement.getElementsByTagName("level"); + + Element levelElement = null; + if (priorityElements.getLength() != 0) + { + levelElement = (Element) priorityElements.item(0); + } + else if (levelElements.getLength() != 0) + { + levelElement = (Element) levelElements.item(0); + } + else + { + //there is no exiting priority or level element to update + return false; + } + + //update the element with the new level/priority + levelElement.setAttribute("value", level.toLowerCase()); + + //output the new file + return writeUpdatedConfigFile(_log4jConfigFileName, doc); + } + finally + { + LOCK.unlock(); + } + } + + + /* The log4j XML configuration file DTD defines 2 possible element + * combinations for specifying the optional root logger level settings + * Must account for the following: + * + * <root> <priority value="y"/> </root> OR + * <root> <level value="y"/> </root> + * + * Noting also that the level/priority child element is optional too, + * and not the only possible child element. + */ + + public static synchronized String retrieveConfigFileRootLoggerLevel(String fileName) throws IOException + { + try + { + LOCK.lock(); + + Document doc = parseConfigFile(fileName); + + //retrieve the optional 'root' element node + NodeList rootElements = doc.getElementsByTagName("root"); + + if (rootElements.getLength() == 0) + { + //there is no root logger definition + return "N/A"; + } + + Element rootElement = (Element) rootElements.item(0); + + //retrieve the optional 'priority' or 'level' element value. + //It may not be the only child node, so request by tag name. + NodeList priorityElements = rootElement.getElementsByTagName("priority"); + NodeList levelElements = rootElement.getElementsByTagName("level"); + String priority = null; + + if (priorityElements.getLength() != 0) + { + Element priorityElement = (Element) priorityElements.item(0); + priority = priorityElement.getAttribute("value"); + } + else if(levelElements.getLength() != 0) + { + Element levelElement = (Element) levelElements.item(0); + priority = levelElement.getAttribute("value"); + } + + if(priority != null) + { + return priority; + } + else + { + return "N/A"; + } + } + finally + { + LOCK.unlock(); + } + } + + public synchronized String getConfigFileRootLoggerLevel() throws IOException + { + return retrieveConfigFileRootLoggerLevel(_log4jConfigFileName).toUpperCase(); + } + + public synchronized boolean setConfigFileRootLoggerLevel(String level) throws IOException + { + try + { + LOCK.lock(); + + //check that the specified level is a valid log4j Level + try + { + Level newLevel = getLevel(level); + if(newLevel == null) + { + //A null Level reference implies inheritance. Setting the config file RootLogger + //to "null" or "inherited" just ensures it defaults to DEBUG at startup as Log4J + //prevents this catastrophic situation at startup and runtime anyway. + return false; + } + } + catch (Exception e) + { + //it isnt a valid level + return false; + } + + _logger.info("Setting level to " + level + " for the Root logger in " + + "log4j xml configuration file: " + _log4jConfigFileName); + + Document doc = parseConfigFile(_log4jConfigFileName); + + //retrieve the optional 'root' element node + NodeList rootElements = doc.getElementsByTagName("root"); + + if (rootElements.getLength() == 0) + { + return false; + } + + Element rootElement = (Element) rootElements.item(0); + + //retrieve the optional 'priority' or 'level' sub-element value. + //It may not be the only child node, so request by tag name. + NodeList priorityElements = rootElement.getElementsByTagName("priority"); + NodeList levelElements = rootElement.getElementsByTagName("level"); + + Element levelElement = null; + if (priorityElements.getLength() != 0) + { + levelElement = (Element) priorityElements.item(0); + } + else if (levelElements.getLength() != 0) + { + levelElement = (Element) levelElements.item(0); + } + else + { + //there is no exiting priority/level to update + return false; + } + + //update the element with the new level/priority + levelElement.setAttribute("value", level); + + //output the new file + return writeUpdatedConfigFile(_log4jConfigFileName, doc); + } + finally + { + LOCK.unlock(); + } + } + + public synchronized void reloadConfigFile() throws IOException + { + try + { + LOCK.lock(); + + QpidLog4JConfigurator.configure(_log4jConfigFileName); + _logger.info("Applied log4j configuration from: " + _log4jConfigFileName); + } + catch (IllegalLoggerLevelException e) + { + _logger.warn("The log4j configuration reload request was aborted: " + e); + //recommended that MBeans should use standard java.* and javax.* exceptions only + throw new IOException("The log4j configuration reload request was aborted: " + e.getMessage()); + } + catch (ParserConfigurationException e) + { + _logger.warn("The log4j configuration reload request was aborted: " + e); + throw new IOException("The log4j configuration reload request was aborted: " + e.getMessage()); + } + catch (SAXException e) + { + _logger.warn("The log4j configuration reload request was aborted: " + e); + //recommended that MBeans should use standard java.* and javax.* exceptions only + throw new IOException("The log4j configuration reload request was aborted: " + e.getMessage()); + } + catch (IOException e) + { + _logger.warn("The log4j configuration reload request was aborted: " + e); + throw new IOException("The log4j configuration reload request was aborted: " + e.getMessage()); + } + finally + { + LOCK.unlock(); + } + } + + @Override + public ManagedObject getParentObject() + { + return null; + } +} diff --git a/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/MBeanUtils.java b/java/broker-plugins/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/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/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/QueueMBean.java b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/QueueMBean.java new file mode 100644 index 0000000000..1416cfdd89 --- /dev/null +++ b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/QueueMBean.java @@ -0,0 +1,663 @@ +/* + * + * 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.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 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 position = 0; + + while(position < bodySize) + { + position += serverMsg.getContent(buf, position); + + } + + 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; + } + + + private 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/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ServerInformationMBean.java b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ServerInformationMBean.java new file mode 100644 index 0000000000..597b98ccaa --- /dev/null +++ b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/ServerInformationMBean.java @@ -0,0 +1,93 @@ +/* + * 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.common.QpidProperties; +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 String _buildVersion; + private String _productVersion; + + public ServerInformationMBean(ManagedObjectRegistry registry, Broker broker) + throws NotCompliantMBeanException, JMException + { + super(ServerInformation.class, ServerInformation.TYPE, registry, broker); + + _buildVersion = QpidProperties.getBuildVersion(); + _productVersion = QpidProperties.getReleaseVersion(); + + 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 _buildVersion; + } + + @Override + public String getProductVersion() throws IOException + { + return _productVersion; + } + + @Override + public boolean isStatisticsEnabled() + { + return false; + } + + @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/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/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/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/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/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/UserManagementMBean.java b/java/broker-plugins/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/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/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostMBean.java b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostMBean.java new file mode 100644 index 0000000000..85f53d9c0d --- /dev/null +++ b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostMBean.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.jmx.mbeans; + +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 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 + { + // TODO + } + + } + catch(JMException e) + { + e.printStackTrace(); //TODO - report error on adding child MBean + } + } + } + + public void childRemoved(ConfiguredObject object, ConfiguredObject child) + { + synchronized (_children) + { + AMQManagedObject mbean = _children.remove(child); + if(mbean != null) + { + try + { + mbean.unregister(); + } + catch(JMException e) + { + e.printStackTrace(); //TODO - report error on removing child MBean + } + } + } + } + + @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/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostManagerMBean.java b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostManagerMBean.java new file mode 100644 index 0000000000..9d12d8a493 --- /dev/null +++ b/java/broker-plugins/jmx/src/main/java/org/apache/qpid/server/jmx/mbeans/VirtualHostManagerMBean.java @@ -0,0 +1,240 @@ +/* + * + * 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.AMQInvalidArgumentException; +import org.apache.qpid.AMQUnknownExchangeType; +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/jmx/src/test/java/org/apache/qpid/server/jmx/NoopManagedObjectRegistry.java b/java/broker-plugins/jmx/src/test/java/org/apache/qpid/server/jmx/NoopManagedObjectRegistry.java new file mode 100644 index 0000000000..a2631bab7f --- /dev/null +++ b/java/broker-plugins/jmx/src/test/java/org/apache/qpid/server/jmx/NoopManagedObjectRegistry.java @@ -0,0 +1,46 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.jmx; + +import javax.management.JMException; + +public class NoopManagedObjectRegistry implements ManagedObjectRegistry +{ + public NoopManagedObjectRegistry() + { + } + + public void start() + { + } + + public void registerObject(ManagedObject managedObject) throws JMException + { + } + + public void unregisterObject(ManagedObject managedObject) throws JMException + { + } + + public void close() + { + } +} diff --git a/java/broker-plugins/jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/ConnectionMBeanTest.java b/java/broker-plugins/jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/ConnectionMBeanTest.java new file mode 100644 index 0000000000..f97c5a7210 --- /dev/null +++ b/java/broker-plugins/jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/ConnectionMBeanTest.java @@ -0,0 +1,232 @@ +/* + * 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 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/jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/LoggingManagementMBeanTest.java b/java/broker-plugins/jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/LoggingManagementMBeanTest.java new file mode 100644 index 0000000000..64b942c9a9 --- /dev/null +++ b/java/broker-plugins/jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/LoggingManagementMBeanTest.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.jmx.mbeans; + +import static org.mockito.Mockito.*; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; + +import org.apache.qpid.server.jmx.ManagedObjectRegistry; +import org.apache.qpid.server.util.InternalBrokerBaseCase; + +import static org.apache.qpid.management.common.mbeans.LoggingManagement.LOGGER_LEVEL; +import static org.apache.qpid.management.common.mbeans.LoggingManagement.LOGGER_NAME; + +import javax.management.JMException; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.TabularDataSupport; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class LoggingManagementMBeanTest extends InternalBrokerBaseCase +{ + + private static final String TEST_LOGGER = "LoggingManagementMBeanTestLogger"; + private static final String TEST_LOGGER_CHILD1 = "LoggingManagementMBeanTestLogger.child1"; + private static final String TEST_LOGGER_CHILD2 = "LoggingManagementMBeanTestLogger.child2"; + + private static final String TEST_CATEGORY_PRIORITY = "LogManMBeanTest.category.priority"; + private static final String TEST_CATEGORY_LEVEL = "LogManMBeanTest.category.level"; + private static final String TEST_LOGGER_LEVEL = "LogManMBeanTest.logger.level"; + + private static final String NEWLINE = System.getProperty("line.separator"); + + private File _testConfigFile; + + private ManagedObjectRegistry _registry = mock(ManagedObjectRegistry.class); + + @Override + public void setUp() throws Exception + { + super.setUp(); + _testConfigFile = createTempTestLog4JConfig(); + } + + @Override + public void tearDown() throws Exception + { + File oldTestConfigFile = new File(_testConfigFile.getAbsolutePath() + ".old"); + if(oldTestConfigFile.exists()) + { + oldTestConfigFile.delete(); + } + + _testConfigFile.delete(); + + super.tearDown(); + } + + private File createTempTestLog4JConfig() + { + File tmpFile = null; + try + { + tmpFile = File.createTempFile("LogManMBeanTestLog4jConfig", ".tmp"); + tmpFile.deleteOnExit(); + + FileWriter fstream = new FileWriter(tmpFile); + BufferedWriter writer = new BufferedWriter(fstream); + + writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+NEWLINE); + writer.write("<!DOCTYPE log4j:configuration SYSTEM \"log4j.dtd\">"+NEWLINE); + + writer.write("<log4j:configuration xmlns:log4j=\"http://jakarta.apache.org/log4j/\" debug=\"null\" " + + "threshold=\"null\">"+NEWLINE); + + writer.write(" <appender class=\"org.apache.log4j.ConsoleAppender\" name=\"STDOUT\">"+NEWLINE); + writer.write(" <layout class=\"org.apache.log4j.PatternLayout\">"+NEWLINE); + writer.write(" <param name=\"ConversionPattern\" value=\"%d %-5p [%t] %C{2} (%F:%L) - %m%n\"/>"+NEWLINE); + writer.write(" </layout>"+NEWLINE); + writer.write(" </appender>"+NEWLINE); + + //Example of a 'category' with a 'priority' + writer.write(" <category additivity=\"true\" name=\"" + TEST_CATEGORY_PRIORITY +"\">"+NEWLINE); + writer.write(" <priority value=\"info\"/>"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </category>"+NEWLINE); + + //Example of a 'category' with a 'level' + writer.write(" <category additivity=\"true\" name=\"" + TEST_CATEGORY_LEVEL +"\">"+NEWLINE); + writer.write(" <level value=\"warn\"/>"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </category>"+NEWLINE); + + //Example of a 'logger' with a 'level' + writer.write(" <logger additivity=\"true\" name=\"" + TEST_LOGGER_LEVEL + "\">"+NEWLINE); + writer.write(" <level value=\"error\"/>"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </logger>"+NEWLINE); + + //'root' logger + writer.write(" <root>"+NEWLINE); + writer.write(" <priority value=\"info\"/>"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </root>"+NEWLINE); + + writer.write("</log4j:configuration>"+NEWLINE); + + writer.flush(); + writer.close(); + } + catch (IOException e) + { + fail("Unable to create temporary test log4j configuration"); + } + + return tmpFile; + } + + + + //******* Test Methods ******* // + + public void testSetRuntimeLoggerLevel() + { + LoggingManagementMBean lm = null; + try + { + lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0, _registry); + } + catch (JMException e) + { + fail("Could not create test LoggingManagementMBean"); + } + + //create a parent test logger, set its level explicitly + Logger log = Logger.getLogger(TEST_LOGGER); + log.setLevel(Level.toLevel("info")); + + //create child1 test logger, check its *effective* level is the same as the parent, "info" + Logger log1 = Logger.getLogger(TEST_LOGGER_CHILD1); + assertTrue("Test logger's level was not the expected value", + log1.getEffectiveLevel().toString().equalsIgnoreCase("info")); + + //now change its level to "warn" + assertTrue("Failed to set logger level", lm.setRuntimeLoggerLevel(TEST_LOGGER_CHILD1, "warn")); + + //check the change, see its actual level is "warn + assertTrue("Test logger's level was not the expected value", + log1.getLevel().toString().equalsIgnoreCase("warn")); + + //try an invalid level + assertFalse("Trying to set an invalid level succeded", lm.setRuntimeLoggerLevel(TEST_LOGGER_CHILD1, "made.up.level")); + } + + public void testSetRuntimeRootLoggerLevel() + { + LoggingManagementMBean lm = null; + try + { + lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0, _registry); + } + catch (JMException e) + { + fail("Could not create test LoggingManagementMBean"); + } + + Logger log = Logger.getRootLogger(); + + //get current root logger level + Level origLevel = log.getLevel(); + + //change level twice to ensure a new level is actually selected + + //set root loggers level to info + assertTrue("Failed to set root logger level", lm.setRuntimeRootLoggerLevel("debug")); + //check it is now actually info + Level currentLevel = log.getLevel(); + assertTrue("Logger level was not expected value", currentLevel.equals(Level.toLevel("debug"))); + + //try an invalid level + assertFalse("Trying to set an invalid level succeded", lm.setRuntimeRootLoggerLevel("made.up.level")); + + //set root loggers level to warn + assertTrue("Failed to set logger level", lm.setRuntimeRootLoggerLevel("info")); + //check it is now actually warn + currentLevel = log.getLevel(); + assertTrue("Logger level was not expected value", currentLevel.equals(Level.toLevel("info"))); + + //restore original level + log.setLevel(origLevel); + } + + public void testGetRuntimeRootLoggerLevel() + { + LoggingManagementMBean lm = null; + try + { + lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0, _registry); + } + catch (JMException e) + { + fail("Could not create test LoggingManagementMBean"); + } + + Logger log = Logger.getRootLogger(); + + //get current root logger level + Level origLevel = log.getLevel(); + + //change level twice to ensure a new level is actually selected + + //set root loggers level to debug + log.setLevel(Level.toLevel("debug")); + //check it is now actually debug + assertTrue("Logger level was not expected value", lm.getRuntimeRootLoggerLevel().equalsIgnoreCase("debug")); + + + //set root loggers level to warn + log.setLevel(Level.toLevel("info")); + //check it is now actually warn + assertTrue("Logger level was not expected value", lm.getRuntimeRootLoggerLevel().equalsIgnoreCase("info")); + + //restore original level + log.setLevel(origLevel); + } + + public void testViewEffectiveRuntimeLoggerLevels() + { + LoggingManagementMBean lm = null; + try + { + lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0, _registry); + } + catch (JMException e) + { + fail("Could not create test LoggingManagementMBean"); + } + + //(re)create a parent test logger, set its level explicitly + Logger log = Logger.getLogger(TEST_LOGGER); + log.setLevel(Level.toLevel("info")); + + //retrieve the current effective runtime logger level values + TabularDataSupport levels = (TabularDataSupport) lm.viewEffectiveRuntimeLoggerLevels(); + Collection<Object> records = levels.values(); + Map<String,String> list = new HashMap<String,String>(); + for (Object o : records) + { + CompositeData data = (CompositeData) o; + list.put(data.get(LOGGER_NAME).toString(), data.get(LOGGER_LEVEL).toString()); + } + + //check child2 does not exist already + assertFalse("Did not expect this logger to exist already", list.containsKey(TEST_LOGGER_CHILD2)); + + //create child2 test logger + Logger log2 = Logger.getLogger(TEST_LOGGER_CHILD2); + + //retrieve the current effective runtime logger level values + levels = (TabularDataSupport) lm.viewEffectiveRuntimeLoggerLevels(); + records = levels.values(); + list = new HashMap<String,String>(); + for (Object o : records) + { + CompositeData data = (CompositeData) o; + list.put(data.get(LOGGER_NAME).toString(), data.get(LOGGER_LEVEL).toString()); + } + + //verify the parent and child2 loggers are present in returned values + assertTrue(TEST_LOGGER + " logger was not in the returned list", list.containsKey(TEST_LOGGER)); + assertTrue(TEST_LOGGER_CHILD2 + " logger was not in the returned list", list.containsKey(TEST_LOGGER_CHILD2)); + + //check child2's effective level is the same as the parent, "info" + assertTrue("Test logger's level was not the expected value", + list.get(TEST_LOGGER_CHILD2).equalsIgnoreCase("info")); + + //now change its level explicitly to "warn" + log2.setLevel(Level.toLevel("warn")); + + //retrieve the current effective runtime logger level values + levels = (TabularDataSupport) lm.viewEffectiveRuntimeLoggerLevels(); + records = levels.values(); + list = new HashMap<String,String>(); + for (Object o : records) + { + CompositeData data = (CompositeData) o; + list.put(data.get(LOGGER_NAME).toString(), data.get(LOGGER_LEVEL).toString()); + } + + //check child2's effective level is now "warn" + assertTrue("Test logger's level was not the expected value", + list.get(TEST_LOGGER_CHILD2).equalsIgnoreCase("warn")); + } + + public void testViewAndSetConfigFileLoggerLevel() throws Exception + { + LoggingManagementMBean lm =null; + try + { + lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0, _registry); + } + catch (JMException e) + { + fail("Could not create test LoggingManagementMBean"); + } + + //retrieve the current values + TabularDataSupport levels = (TabularDataSupport) lm.viewConfigFileLoggerLevels(); + Collection<Object> records = levels.values(); + Map<String,String> list = new HashMap<String,String>(); + for (Object o : records) + { + CompositeData data = (CompositeData) o; + list.put(data.get(LOGGER_NAME).toString(), data.get(LOGGER_LEVEL).toString()); + } + + //check the 3 different types of logger definition are successfully retrieved before update + assertTrue("Wrong number of items in returned list", list.size() == 3); + assertTrue(TEST_CATEGORY_PRIORITY + " logger was not in the returned list", list.containsKey(TEST_CATEGORY_PRIORITY)); + assertTrue(TEST_CATEGORY_LEVEL + " logger was not in the returned list", list.containsKey(TEST_CATEGORY_LEVEL)); + assertTrue(TEST_LOGGER_LEVEL + " logger was not in the returned list", list.containsKey(TEST_LOGGER_LEVEL)); + + //check that their level is as expected + assertTrue(TEST_CATEGORY_PRIORITY + " logger's level was incorrect", list.get(TEST_CATEGORY_PRIORITY).equalsIgnoreCase("info")); + assertTrue(TEST_CATEGORY_LEVEL + " logger's level was incorrect", list.get(TEST_CATEGORY_LEVEL).equalsIgnoreCase("warn")); + assertTrue(TEST_LOGGER_LEVEL + " logger's level was incorrect", list.get(TEST_LOGGER_LEVEL).equalsIgnoreCase("error")); + + //increase their levels a notch to test the 3 different types of logger definition are successfully updated + //change the category+priority to warn + assertTrue("failed to set new level", lm.setConfigFileLoggerLevel(TEST_CATEGORY_PRIORITY, "warn")); + //change the category+level to error + assertTrue("failed to set new level", lm.setConfigFileLoggerLevel(TEST_CATEGORY_LEVEL, "error")); + //change the logger+level to trace + assertTrue("failed to set new level", lm.setConfigFileLoggerLevel(TEST_LOGGER_LEVEL, "trace")); + + //try an invalid level + assertFalse("Use of an invalid logger level was successfull", lm.setConfigFileLoggerLevel(TEST_LOGGER_LEVEL, "made.up.level")); + + //try an invalid logger name + assertFalse("Use of an invalid logger name was successfull", lm.setConfigFileLoggerLevel("made.up.logger.name", "info")); + + //retrieve the new values from the file and check them + levels = (TabularDataSupport) lm.viewConfigFileLoggerLevels(); + records = levels.values(); + list = new HashMap<String,String>(); + for (Object o : records) + { + CompositeData data = (CompositeData) o; + list.put(data.get(LOGGER_NAME).toString(), data.get(LOGGER_LEVEL).toString()); + } + + //check the 3 different types of logger definition are successfully retrieved after update + assertTrue("Wrong number of items in returned list", list.size() == 3); + assertTrue(TEST_CATEGORY_PRIORITY + " logger was not in the returned list", list.containsKey(TEST_CATEGORY_PRIORITY)); + assertTrue(TEST_CATEGORY_LEVEL + " logger was not in the returned list", list.containsKey(TEST_CATEGORY_LEVEL)); + assertTrue(TEST_LOGGER_LEVEL + " logger was not in the returned list", list.containsKey(TEST_LOGGER_LEVEL)); + + //check that their level is as expected after the changes + assertTrue(TEST_CATEGORY_PRIORITY + " logger's level was incorrect", list.get(TEST_CATEGORY_PRIORITY).equalsIgnoreCase("warn")); + assertTrue(TEST_CATEGORY_LEVEL + " logger's level was incorrect", list.get(TEST_CATEGORY_LEVEL).equalsIgnoreCase("error")); + assertTrue(TEST_LOGGER_LEVEL + " logger's level was incorrect", list.get(TEST_LOGGER_LEVEL).equalsIgnoreCase("trace")); + } + + public void testGetAndSetConfigFileRootLoggerLevel() throws Exception + { + LoggingManagementMBean lm =null; + try + { + lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0, _registry); + } + catch (JMException e) + { + fail("Could not create test LoggingManagementMBean"); + } + + //retrieve the current value + String level = lm.getConfigFileRootLoggerLevel(); + + //check the value was successfully retrieved before update + assertTrue("Retrieved RootLogger level was incorrect", level.equalsIgnoreCase("info")); + + //try an invalid level + assertFalse("Use of an invalid RootLogger level was successfull", lm.setConfigFileRootLoggerLevel("made.up.level")); + + //change the level to warn + assertTrue("Failed to set new RootLogger level", lm.setConfigFileRootLoggerLevel("warn")); + + //retrieve the current value + level = lm.getConfigFileRootLoggerLevel(); + + //check the value was successfully retrieved after update + assertTrue("Retrieved RootLogger level was incorrect", level.equalsIgnoreCase("warn")); + } + + public void testGetLog4jLogWatchInterval() + { + LoggingManagementMBean lm =null; + try + { + lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 5000, _registry); + } + catch (JMException e) + { + fail("Could not create test LoggingManagementMBean"); + } + + assertTrue("Wrong value returned for logWatch period", lm.getLog4jLogWatchInterval() == 5000); + } + +} diff --git a/java/broker-plugins/jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/MBeanTestUtils.java b/java/broker-plugins/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/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/jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/QueueMBeanTest.java b/java/broker-plugins/jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/QueueMBeanTest.java new file mode 100644 index 0000000000..2003c12735 --- /dev/null +++ b/java/broker-plugins/jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/QueueMBeanTest.java @@ -0,0 +1,368 @@ +/* + * 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.Matchers.isNull; +import static org.mockito.Matchers.argThat; + +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 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.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 QueueMBeanTest extends TestCase +{ + 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 + { + _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); + } + +} diff --git a/java/broker-plugins/jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/UserManagementMBeanTest.java b/java/broker-plugins/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/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/jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/VirtualHostManagerMBeanTest.java b/java/broker-plugins/jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/VirtualHostManagerMBeanTest.java new file mode 100644 index 0000000000..93a80665a9 --- /dev/null +++ b/java/broker-plugins/jmx/src/test/java/org/apache/qpid/server/jmx/mbeans/VirtualHostManagerMBeanTest.java @@ -0,0 +1,175 @@ +/* + * 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; +import org.mockito.verification.VerificationMode; + +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/jmx/src/test/java/org/apache/qpid/systest/management/jmx/BrokerManagementTest.java b/java/broker-plugins/jmx/src/test/java/org/apache/qpid/systest/management/jmx/BrokerManagementTest.java new file mode 100644 index 0000000000..2c341b7f2e --- /dev/null +++ b/java/broker-plugins/jmx/src/test/java/org/apache/qpid/systest/management/jmx/BrokerManagementTest.java @@ -0,0 +1,128 @@ +/* + * 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; + +import javax.management.MBeanException; +import javax.management.ObjectName; + +/** + * 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 (MBeanException mbe) + { + // PASS + assertEquals("Error in unregistering exchange " + defaultExchangeName, mbe.getMessage()); + assertTrue(mbe.getCause().getMessage().contains("Cannot unregister the default exchange")); + } + final ManagedExchange defaultExchange = _jmxUtils.getManagedExchange(defaultExchangeName); + assertNotNull("Exchange should exist", defaultExchange); + } + +} diff --git a/java/broker-plugins/jmx/src/test/java/org/apache/qpid/systest/management/jmx/ConnectionManagementTest.java b/java/broker-plugins/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/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/jmx/src/test/java/org/apache/qpid/systest/management/jmx/ManagementActorLoggingTest.java b/java/broker-plugins/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/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/jmx/src/test/java/org/apache/qpid/systest/management/jmx/ManagementLoggingTest.java b/java/broker-plugins/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/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/jmx/src/test/java/org/apache/qpid/systest/management/jmx/QueueManagementTest.java b/java/broker-plugins/jmx/src/test/java/org/apache/qpid/systest/management/jmx/QueueManagementTest.java new file mode 100644 index 0000000000..ad6777d0ea --- /dev/null +++ b/java/broker-plugins/jmx/src/test/java/org/apache/qpid/systest/management/jmx/QueueManagementTest.java @@ -0,0 +1,609 @@ +/* + * 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.management.Notification; +import javax.management.NotificationListener; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.TabularData; + +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"; + + _connection = getConnection(); + _connection.start(); + + _session = _connection.createSession(true, Session.SESSION_TRANSACTED); + _sourceQueue = _session.createQueue(_sourceQueueName); + _destinationQueue = _session.createQueue(_destinationQueueName); + createQueueOnBroker(_sourceQueue); + createQueueOnBroker(_destinationQueue); + + _jmxUtils.open(); + + _managedSourceQueue = _jmxUtils.getManagedQueue(_sourceQueueName); + _managedDestinationQueue = _jmxUtils.getManagedQueue(_destinationQueueName); + } + + 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 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 = "amq.fanout"; + 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()); + } + + 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) + { + Message message = consumer.receive(1000); + assertNotNull("Expected message with index " + i, message); + assertEquals("Expected message with index " + i, i, message.getIntProperty(INDEX)); + } + + 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 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/jmx/src/test/java/org/apache/qpid/systest/management/jmx/StatisticsTest.java b/java/broker-plugins/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/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/jmx/src/test/java/org/apache/qpid/systest/management/jmx/UserManagementTest.java b/java/broker-plugins/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/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/jmx/src/test/java/org/apache/qpid/systest/management/jmx/UserManagementWithBase64MD5PasswordsTest.java b/java/broker-plugins/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/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; + } + +} diff --git a/java/broker-plugins/management/MANIFEST.MF b/java/broker-plugins/management/MANIFEST.MF new file mode 100644 index 0000000000..d817ac5302 --- /dev/null +++ b/java/broker-plugins/management/MANIFEST.MF @@ -0,0 +1,66 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Qpid Broker-Plugins Management +Bundle-SymbolicName: broker-plugins-management +Bundle-Description: 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.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.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.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/build.xml b/java/broker-plugins/management/build.xml new file mode 100644 index 0000000000..4a28ff2659 --- /dev/null +++ b/java/broker-plugins/management/build.xml @@ -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. + --> +<project name="Qpid Broker-Plugins Management" default="build"> + <property name="module.depends" value="common broker broker-plugins broker-plugins-management" /> + <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" /> + + <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/src/main/java/org/apache/qpid/server/management/plugin/Management.java b/java/broker-plugins/management/src/main/java/org/apache/qpid/server/management/plugin/Management.java new file mode 100644 index 0000000000..589f46749d --- /dev/null +++ b/java/broker-plugins/management/src/main/java/org/apache/qpid/server/management/plugin/Management.java @@ -0,0 +1,155 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.management.plugin; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.Collection; +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.*; +import org.apache.qpid.server.model.*; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.SessionManager; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; + +public class Management +{ + + private final Logger _logger = Logger.getLogger(Management.class); + + private Broker _broker; + + private Collection<Server> _servers = new ArrayList<Server>(); + + + public Management() + { + _broker = ApplicationRegistry.getInstance().getBroker(); + + Collection<Port> ports = _broker.getPorts(); + for(Port port : ports) + { + // TODO - cover cases where more than just HTTP supported, and SSL as a transport + if(port.getProtocols().contains(Protocol.HTTP)) + { + if(port.getTransports().contains(Transport.TCP)) + { + int portNumber = port.getPort(); + if (_logger.isInfoEnabled()) + { + _logger.info("Creating web server on port " + portNumber); + } + _servers.add(createServer(portNumber)); + } + } + } + + if (_logger.isDebugEnabled()) + { + _logger.info(_servers.size() + " server(s) defined"); + } + + } + + private Server createServer(int port) + { + _logger.info("Starting up web server on port " + port); + + Server server = new Server(port); + SocketAddress socketAddress = new InetSocketAddress(port); + + 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", socketAddress); + addRestServlet(root, "virtualhost", socketAddress, VirtualHost.class); + addRestServlet(root, "authenticationprovider", socketAddress, AuthenticationProvider.class); + addRestServlet(root, "user", socketAddress, AuthenticationProvider.class, User.class); + addRestServlet(root, "exchange", socketAddress, VirtualHost.class, Exchange.class); + addRestServlet(root, "queue", socketAddress, VirtualHost.class, Queue.class); + addRestServlet(root, "connection", socketAddress, VirtualHost.class, Connection.class); + addRestServlet(root, "binding", socketAddress, VirtualHost.class, Exchange.class, Queue.class, Binding.class); + addRestServlet(root, "port", socketAddress, Port.class); + addRestServlet(root, "session", socketAddress, VirtualHost.class, Connection.class, Session.class); + + root.addServlet(new ServletHolder(new StructureServlet(_broker, socketAddress)), "/rest/structure"); + root.addServlet(new ServletHolder(new MessageServlet(_broker, socketAddress)), "/rest/message/*"); + root.addServlet(new ServletHolder(new MessageContentServlet(_broker, socketAddress)), "/rest/message-content/*"); + + root.addServlet(new ServletHolder(new LogRecordsServlet(_broker, socketAddress)), "/rest/logrecords"); + + + root.addServlet(new ServletHolder(new SaslServlet(_broker, socketAddress)), "/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, SocketAddress socketAddress, Class<? extends ConfiguredObject>... hierarchy) + { + root.addServlet(new ServletHolder(new RestServlet(_broker, socketAddress, 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(); + } + } + +} diff --git a/java/broker-plugins/management/src/main/java/org/apache/qpid/server/management/plugin/ManagementActivator.java b/java/broker-plugins/management/src/main/java/org/apache/qpid/server/management/plugin/ManagementActivator.java new file mode 100644 index 0000000000..2600d8a7bf --- /dev/null +++ b/java/broker-plugins/management/src/main/java/org/apache/qpid/server/management/plugin/ManagementActivator.java @@ -0,0 +1,72 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.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()) + { + _logger.info("Management plugin is diabled!"); + 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/src/main/java/org/apache/qpid/server/management/plugin/ManagementConfiguration.java b/java/broker-plugins/management/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/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/src/main/java/org/apache/qpid/server/management/plugin/servlet/DefinedFileServlet.java b/java/broker-plugins/management/src/main/java/org/apache/qpid/server/management/plugin/servlet/DefinedFileServlet.java new file mode 100644 index 0000000000..d66555787f --- /dev/null +++ b/java/broker-plugins/management/src/main/java/org/apache/qpid/server/management/plugin/servlet/DefinedFileServlet.java @@ -0,0 +1,79 @@ +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; + +/** + * 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. + */ +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/src/main/java/org/apache/qpid/server/management/plugin/servlet/FileServlet.java b/java/broker-plugins/management/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/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/src/main/java/org/apache/qpid/server/management/plugin/servlet/api/ExchangesServlet.java b/java/broker-plugins/management/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/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/src/main/java/org/apache/qpid/server/management/plugin/servlet/api/VhostsServlet.java b/java/broker-plugins/management/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/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/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/AbstractServlet.java b/java/broker-plugins/management/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/AbstractServlet.java new file mode 100644 index 0000000000..123f352ec1 --- /dev/null +++ b/java/broker-plugins/management/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/AbstractServlet.java @@ -0,0 +1,215 @@ +/* + * + * 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; + private SocketAddress _socketAddress; + + protected AbstractServlet() + { + super(); + _broker = ApplicationRegistry.getInstance().getBroker(); + _socketAddress = null; + } + + protected AbstractServlet(Broker broker, SocketAddress socketAddress) + { + _broker = broker; + _socketAddress = socketAddress; + } + + @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) + { + if (_socketAddress == null) + { + return InetSocketAddress.createUnresolved(request.getServerName(), request.getServerPort()); + } + return _socketAddress; + } +} diff --git a/java/broker-plugins/management/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/KeyComparator.java b/java/broker-plugins/management/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/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/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogRecordsServlet.java b/java/broker-plugins/management/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogRecordsServlet.java new file mode 100644 index 0000000000..7a4b92f907 --- /dev/null +++ b/java/broker-plugins/management/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/LogRecordsServlet.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.io.PrintWriter; +import java.net.SocketAddress; +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(), null); + } + + public LogRecordsServlet(Broker broker, SocketAddress socketaddress) + { + super(broker, socketaddress); + } + + @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/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MapComparator.java b/java/broker-plugins/management/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/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/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageContentServlet.java b/java/broker-plugins/management/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageContentServlet.java new file mode 100644 index 0000000000..4d58a9f3b0 --- /dev/null +++ b/java/broker-plugins/management/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageContentServlet.java @@ -0,0 +1,177 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.qpid.server.management.plugin.servlet.rest; + +import java.io.IOException; +import java.net.SocketAddress; +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, SocketAddress socketaddress) + { + super(broker, socketaddress); + } + + @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/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageServlet.java b/java/broker-plugins/management/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageServlet.java new file mode 100644 index 0000000000..b47dc8b28e --- /dev/null +++ b/java/broker-plugins/management/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/MessageServlet.java @@ -0,0 +1,503 @@ +/* + * 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.net.SocketAddress; +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, SocketAddress socketaddress) + { + super(broker, socketaddress); + } + + @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/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java b/java/broker-plugins/management/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java new file mode 100644 index 0000000000..96e260da56 --- /dev/null +++ b/java/broker-plugins/management/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java @@ -0,0 +1,576 @@ +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.qpid.AMQSecurityException; +import org.apache.qpid.server.model.*; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.SerializationConfig; + +/** + * 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. + */ +public class RestServlet extends AbstractServlet +{ + /** + * 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, SocketAddress socketaddress, Class<? extends ConfiguredObject>... hierarchy) + { + super(broker, socketaddress); + _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 != 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 + { + // 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/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java b/java/broker-plugins/management/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java new file mode 100644 index 0000000000..4ca2d270dd --- /dev/null +++ b/java/broker-plugins/management/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java @@ -0,0 +1,253 @@ +/* + * + * 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.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.net.SocketAddress; +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 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, SocketAddress socketaddress) + { + super(broker, socketaddress); + } + + 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()); + } + + 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) + { + //TODO + e.printStackTrace(); + throw e; + } + catch(RuntimeException e) + { + //TODO + e.printStackTrace(); + 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/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/StructureServlet.java b/java/broker-plugins/management/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/StructureServlet.java new file mode 100644 index 0000000000..6295d74b42 --- /dev/null +++ b/java/broker-plugins/management/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/StructureServlet.java @@ -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 + * <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.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, SocketAddress socketaddress) + { + super(broker, socketaddress); + } + + @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/src/main/java/resources/addBinding.html b/java/broker-plugins/management/src/main/java/resources/addBinding.html new file mode 100644 index 0000000000..8dbd219c8d --- /dev/null +++ b/java/broker-plugins/management/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/src/main/java/resources/addExchange.html b/java/broker-plugins/management/src/main/java/resources/addExchange.html new file mode 100644 index 0000000000..cccdfbd00c --- /dev/null +++ b/java/broker-plugins/management/src/main/java/resources/addExchange.html @@ -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. + - + --> +<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" /></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/src/main/java/resources/addQueue.html b/java/broker-plugins/management/src/main/java/resources/addQueue.html new file mode 100644 index 0000000000..8154f053cd --- /dev/null +++ b/java/broker-plugins/management/src/main/java/resources/addQueue.html @@ -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. + - + --> +<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="numPriorities" value="3" 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" + 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]+(b|K(b)?|M(b)?|G(b)?)?$)" + 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]+(b|K(b)?|M(b)?|G(b)?)?$)" + 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"> + <tr> + <td valign="top"><strong>Maximum 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]+(s(ec(ond(s)?)?)?|m(in(ute)?(s)?)?|h|d|w|M|y)?$)" + invalidMessage= "Invalid value" /></td> + </tr> + <!-- x-qpid-maximum-message-size --> + <tr> + <td valign="top"><strong>Maximum 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]+(b|K(b)?|M(b)?|G(b)?)?$)" + invalidMessage= "Invalid value" /></td> + </tr> + <!-- x-qpid-maximum-message-count --> + <tr> + <td valign="top"><strong>Maximum Number in Queue: </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-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]+(s(ec(ond(s)?)?)?|m(in(ute)?(s)?)?|h|d|w|M|y)?$)" + 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> + + + </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/src/main/java/resources/authenticationprovider/addUser.html b/java/broker-plugins/management/src/main/java/resources/authenticationprovider/addUser.html new file mode 100644 index 0000000000..785605f694 --- /dev/null +++ b/java/broker-plugins/management/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/src/main/java/resources/authenticationprovider/setPassword.html b/java/broker-plugins/management/src/main/java/resources/authenticationprovider/setPassword.html new file mode 100644 index 0000000000..3d67463abd --- /dev/null +++ b/java/broker-plugins/management/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/src/main/java/resources/authenticationprovider/showPrincipalDatabaseAuthenticationManager.html index 96f97ee437..baadc8c35f 100644 --- a/java/broker-plugins/experimental/shutdown/build.xml +++ b/java/broker-plugins/management/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,14 +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/src/main/java/resources/css/common.css b/java/broker-plugins/management/src/main/java/resources/css/common.css new file mode 100644 index 0000000000..78780edcd9 --- /dev/null +++ b/java/broker-plugins/management/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/management/src/main/java/resources/footer.html b/java/broker-plugins/management/src/main/java/resources/footer.html new file mode 100644 index 0000000000..fa84825e80 --- /dev/null +++ b/java/broker-plugins/management/src/main/java/resources/footer.html @@ -0,0 +1,28 @@ +<!-- + - + - 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="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/src/main/java/resources/js/qpid/authorization/sasl.js b/java/broker-plugins/management/src/main/java/resources/js/qpid/authorization/sasl.js new file mode 100644 index 0000000000..152504da86 --- /dev/null +++ b/java/broker-plugins/management/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/src/main/java/resources/js/qpid/common/UpdatableStore.js b/java/broker-plugins/management/src/main/java/resources/js/qpid/common/UpdatableStore.js new file mode 100644 index 0000000000..f7ede1a7f7 --- /dev/null +++ b/java/broker-plugins/management/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/src/main/java/resources/js/qpid/common/footer.js b/java/broker-plugins/management/src/main/java/resources/js/qpid/common/footer.js new file mode 100644 index 0000000000..ea13b1fc53 --- /dev/null +++ b/java/broker-plugins/management/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/src/main/java/resources/js/qpid/common/formatter.js b/java/broker-plugins/management/src/main/java/resources/js/qpid/common/formatter.js new file mode 100644 index 0000000000..2f8683ee1c --- /dev/null +++ b/java/broker-plugins/management/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/src/main/java/resources/js/qpid/common/properties.js b/java/broker-plugins/management/src/main/java/resources/js/qpid/common/properties.js new file mode 100644 index 0000000000..8d85345b74 --- /dev/null +++ b/java/broker-plugins/management/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/src/main/java/resources/js/qpid/common/updater.js b/java/broker-plugins/management/src/main/java/resources/js/qpid/common/updater.js new file mode 100644 index 0000000000..86bbaa46ba --- /dev/null +++ b/java/broker-plugins/management/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/src/main/java/resources/js/qpid/common/util.js b/java/broker-plugins/management/src/main/java/resources/js/qpid/common/util.js new file mode 100644 index 0000000000..d7917b640e --- /dev/null +++ b/java/broker-plugins/management/src/main/java/resources/js/qpid/common/util.js @@ -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. + * + */ +define([], + function () { + 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] ); + } + } + } + } + }; + return util; + });
\ No newline at end of file diff --git a/java/broker-plugins/management/src/main/java/resources/js/qpid/management/AuthenticationProvider.js b/java/broker-plugins/management/src/main/java/resources/js/qpid/management/AuthenticationProvider.js new file mode 100644 index 0000000000..7613fd5d71 --- /dev/null +++ b/java/broker-plugins/management/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/src/main/java/resources/js/qpid/management/Broker.js b/java/broker-plugins/management/src/main/java/resources/js/qpid/management/Broker.js new file mode 100644 index 0000000000..dcf6711073 --- /dev/null +++ b/java/broker-plugins/management/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/src/main/java/resources/js/qpid/management/Connection.js b/java/broker-plugins/management/src/main/java/resources/js/qpid/management/Connection.js new file mode 100644 index 0000000000..01f9a325c5 --- /dev/null +++ b/java/broker-plugins/management/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/src/main/java/resources/js/qpid/management/Exchange.js b/java/broker-plugins/management/src/main/java/resources/js/qpid/management/Exchange.js new file mode 100644 index 0000000000..0450ef53ac --- /dev/null +++ b/java/broker-plugins/management/src/main/java/resources/js/qpid/management/Exchange.js @@ -0,0 +1,229 @@ +/* + * + * 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", + "dojo/domReady!"], + function (xhr, parser, query, connect, registry, properties, updater, util, formatter, UpdatableStore, addBinding) { + + 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()}); + }); + + }}); + }; + + Exchange.prototype.close = function() { + updater.remove( this.exchangeUpdater ); + }; + + 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%"} + ]); + + }); + + } + + 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) + + }); + }; + + + return Exchange; + }); diff --git a/java/broker-plugins/management/src/main/java/resources/js/qpid/management/Queue.js b/java/broker-plugins/management/src/main/java/resources/js/qpid/management/Queue.js new file mode 100644 index 0000000000..1eb0bfdd79 --- /dev/null +++ b/java/broker-plugins/management/src/main/java/resources/js/qpid/management/Queue.js @@ -0,0 +1,438 @@ +/* + * + * 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()}); + }); + + }}); + + + + }; + + 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 ); + }; + + 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", + "alertRepeatGap", + "alertRepeatGapUnits", + "alertThresholdMessageAge", + "alertThresholdMessageAgeUnits", + "alertThresholdMessageSize", + "alertThresholdMessageSizeUnits", + "alertThresholdQueueDepthBytes", + "alertThresholdQueueDepthBytesUnits", + "alertThresholdQueueDepthMessages", + "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.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 + ")" + + + }; + + 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) + + }); + }; + + + return Queue; + }); diff --git a/java/broker-plugins/management/src/main/java/resources/js/qpid/management/VirtualHost.js b/java/broker-plugins/management/src/main/java/resources/js/qpid/management/VirtualHost.js new file mode 100644 index 0000000000..ce24145930 --- /dev/null +++ b/java/broker-plugins/management/src/main/java/resources/js/qpid/management/VirtualHost.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/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", + "dojo/domReady!"], + function (xhr, parser, query, connect, registry, properties, updater, util, formatter, UpdatableStore, addQueue, addExchange) { + + 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 addExchangeButton = query(".addExchangeButton", contentPane.containerNode)[0]; + connect.connect(registry.byNode(addExchangeButton), "onClick", function(evt){ addExchange.show(that.name) }); + }}); + + }; + + 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 ); + + 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); + }); + } ); + + 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); + }); + } ); + + + 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); + + // update connections + thisObj.connectionsGrid.update(thisObj.vhostData.connections) + + + }); + }; + + + return VirtualHost; + });
\ No newline at end of file diff --git a/java/broker-plugins/management/src/main/java/resources/js/qpid/management/addBinding.js b/java/broker-plugins/management/src/main/java/resources/js/qpid/management/addBinding.js new file mode 100644 index 0000000000..83e724d0e9 --- /dev/null +++ b/java/broker-plugins/management/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/src/main/java/resources/js/qpid/management/addExchange.js b/java/broker-plugins/management/src/main/java/resources/js/qpid/management/addExchange.js new file mode 100644 index 0000000000..f88daa54bb --- /dev/null +++ b/java/broker-plugins/management/src/main/java/resources/js/qpid/management/addExchange.js @@ -0,0 +1,147 @@ +/* + * + * 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/src/main/java/resources/js/qpid/management/addQueue.js b/java/broker-plugins/management/src/main/java/resources/js/qpid/management/addQueue.js new file mode 100644 index 0000000000..4a2dccde5f --- /dev/null +++ b/java/broker-plugins/management/src/main/java/resources/js/qpid/management/addQueue.js @@ -0,0 +1,158 @@ +/* + * + * 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 = { + numPriorities: "priority", + lvqKey: "lvq", + sortKey: "sorted" + }; + + + 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 (!typeSpecificFields.hasOwnProperty(propName) || + formValues.type === typeSpecificFields[ propName ]) { + if(formValues[ propName ] !== "") { + 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 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 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/src/main/java/resources/js/qpid/management/authenticationprovider/PrincipalDatabaseAuthenticationManager.js b/java/broker-plugins/management/src/main/java/resources/js/qpid/management/authenticationprovider/PrincipalDatabaseAuthenticationManager.js new file mode 100644 index 0000000000..ffebc98af7 --- /dev/null +++ b/java/broker-plugins/management/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(this.success === true) { + registry.byId("addUser").hide(); + } else { + alert("Error:" + this.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/src/main/java/resources/js/qpid/management/controller.js b/java/broker-plugins/management/src/main/java/resources/js/qpid/management/controller.js new file mode 100644 index 0000000000..1aa05a5a3c --- /dev/null +++ b/java/broker-plugins/management/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/src/main/java/resources/js/qpid/management/moveCopyMessages.js b/java/broker-plugins/management/src/main/java/resources/js/qpid/management/moveCopyMessages.js new file mode 100644 index 0000000000..8cc488324f --- /dev/null +++ b/java/broker-plugins/management/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/src/main/java/resources/js/qpid/management/showMessage.js b/java/broker-plugins/management/src/main/java/resources/js/qpid/management/showMessage.js new file mode 100644 index 0000000000..b1ccc0ca07 --- /dev/null +++ b/java/broker-plugins/management/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/src/main/java/resources/js/qpid/management/treeView.js b/java/broker-plugins/management/src/main/java/resources/js/qpid/management/treeView.js new file mode 100644 index 0000000000..b1d4abf8c1 --- /dev/null +++ b/java/broker-plugins/management/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/src/main/java/resources/management.html b/java/broker-plugins/management/src/main/java/resources/management.html new file mode 100644 index 0000000000..a8345a8503 --- /dev/null +++ b/java/broker-plugins/management/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/src/main/java/resources/moveCopyMessages.html b/java/broker-plugins/management/src/main/java/resources/moveCopyMessages.html new file mode 100644 index 0000000000..f188c3001c --- /dev/null +++ b/java/broker-plugins/management/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/src/main/java/resources/showAuthProvider.html b/java/broker-plugins/management/src/main/java/resources/showAuthProvider.html new file mode 100644 index 0000000000..c5d4e48a75 --- /dev/null +++ b/java/broker-plugins/management/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/src/main/java/resources/showBroker.html b/java/broker-plugins/management/src/main/java/resources/showBroker.html new file mode 100644 index 0000000000..cb2f4a4b9a --- /dev/null +++ b/java/broker-plugins/management/src/main/java/resources/showBroker.html @@ -0,0 +1,25 @@ +<div class="broker"> + <span>Broker:</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/src/main/java/resources/showConnection.html b/java/broker-plugins/management/src/main/java/resources/showConnection.html new file mode 100644 index 0000000000..84854daf47 --- /dev/null +++ b/java/broker-plugins/management/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/src/main/java/resources/showExchange.html b/java/broker-plugins/management/src/main/java/resources/showExchange.html new file mode 100644 index 0000000000..64f351d218 --- /dev/null +++ b/java/broker-plugins/management/src/main/java/resources/showExchange.html @@ -0,0 +1,46 @@ +<!-- + - + - Licensed to the Apache Software Foundation (ASF) under one + - or more contributor license agreements. See the NOTICE file + - distributed with this work for additional information + - regarding copyright ownership. The ASF licenses this file + - to you under the Apache License, Version 2.0 (the + - "License"); you may not use this file except in compliance + - with the License. You may obtain a copy of the License at + - + - http://www.apache.org/licenses/LICENSE-2.0 + - + - Unless required by applicable law or agreed to in writing, + - software distributed under the License is distributed on an + - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + - KIND, either express or implied. See the License for the + - specific language governing permissions and limitations + - under the License. + - + --> +<div class="exchange"> + <span style="">Exchange:</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> + </div> + <br/> +</div> diff --git a/java/broker-plugins/management/src/main/java/resources/showMessage.html b/java/broker-plugins/management/src/main/java/resources/showMessage.html new file mode 100644 index 0000000000..0dea508c60 --- /dev/null +++ b/java/broker-plugins/management/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/src/main/java/resources/showQueue.html b/java/broker-plugins/management/src/main/java/resources/showQueue.html new file mode 100644 index 0000000000..c87a462760 --- /dev/null +++ b/java/broker-plugins/management/src/main/java/resources/showQueue.html @@ -0,0 +1,97 @@ +<!-- + - + - 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="">Queue:</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/> + <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="">Max. Queue Size:</span> + <span class="alertThresholdQueueDepthMessages" + style="position:absolute; left:8em; width:8em; text-align:right"></span> + <span style="position:absolute; left:16.2em">msgs</span> + + <span class="alertThresholdQueueDepthBytes" + style="position:absolute; left:20em; width:8em; text-align:right"></span> + <span class="alertThresholdQueueDepthBytesUnits" style="position:absolute; left:28.2em"></span> + <br> + <span style="">Max. Message Age:</span> + <span class="alertThresholdMessageAge" + style="position:absolute; left:8em; width:8em; text-align:right"></span> + <span class="alertThresholdMessageAgeUnits" style="position:absolute; left:16.2em"></span> + + <span style="position:absolute; left:21em">Size: </span> + <span class="alertThresholdMessageSize" + style="position:absolute; left:23em; width:5em; text-align:right"></span> + <span class="alertThresholdMessageSizeUnits" style="position:absolute; left:28.2em"></span> + <br/> + <br/> + <span style="">Alert frequency:</span> + <span class="alertRepeatGap" + style="position:absolute; left:8em; width:8em; text-align:right"></span> + <span class="alertRepeatGapUnits" style="position:absolute; left:16.2em"></span> + + + + </div> +</div> + diff --git a/java/broker-plugins/management/src/main/java/resources/showVirtualHost.html b/java/broker-plugins/management/src/main/java/resources/showVirtualHost.html new file mode 100644 index 0000000000..9d16d523d6 --- /dev/null +++ b/java/broker-plugins/management/src/main/java/resources/showVirtualHost.html @@ -0,0 +1,84 @@ +<!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> + </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> + </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="">Max. Queue Size:</span> + <span class="alertThresholdQueueDepthMessages" + style="position:absolute; left:8em; width:8em; text-align:right"></span> + <span style="position:absolute; left:16.2em">msgs</span> + + <span class="alertThresholdQueueDepthBytes" + style="position:absolute; left:20em; width:8em; text-align:right"></span> + <span class="alertThresholdQueueDepthBytesUnits" style="position:absolute; left:28.2em"></span> + <br> + <span style="">Max. Message Age:</span> + <span class="alertThresholdMessageAge" + style="position:absolute; left:8em; width:8em; text-align:right"></span> + <span class="alertThresholdMessageAgeUnits" style="position:absolute; left:16.2em"></span> + + <span style="position:absolute; left:21em">Size: </span> + <span class="alertThresholdMessageSize" + style="position:absolute; left:23em; width:5em; text-align:right"></span> + <span class="alertThresholdMessageSizeUnits" style="position:absolute; left:28.2em"></span> + <br/> + <br/> + <span style="">Alert frequency:</span> + <span class="alertRepeatGap" + style="position:absolute; left:8em; width:8em; text-align:right"></span> + <span class="alertRepeatGapUnits" style="position:absolute; left:16.2em"></span> + </div> +</div> + |
