summaryrefslogtreecommitdiff
path: root/qpid/java/broker-plugins
diff options
context:
space:
mode:
Diffstat (limited to 'qpid/java/broker-plugins')
-rw-r--r--qpid/java/broker-plugins/access-control/MANIFEST.MF42
-rw-r--r--qpid/java/broker-plugins/access-control/build.xml31
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AbstractConfiguration.java77
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Action.java192
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/ConfigurationFile.java56
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/PlainConfiguration.java323
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Rule.java170
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/RuleSet.java489
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/XMLConfiguration.java31
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/logging/AccessControl_logmessages.properties28
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControl.java116
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlActivator.java42
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlConfiguration.java83
-rw-r--r--qpid/java/broker-plugins/access-control/src/main/resources/acl.xsd29
-rw-r--r--qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/AccessControlTest.java195
-rw-r--r--qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/RuleSetTest.java289
-rw-r--r--qpid/java/broker-plugins/experimental/info/MANIFEST.MF16
-rw-r--r--qpid/java/broker-plugins/experimental/info/build.properties31
-rw-r--r--qpid/java/broker-plugins/experimental/info/build.xml32
-rw-r--r--qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/Activator.java204
-rw-r--r--qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/AppInfo.java94
-rw-r--r--qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/Info.java143
-rw-r--r--qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/InfoService.java30
-rw-r--r--qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/InfoServiceImpl.java66
-rw-r--r--qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/SystemInfo.java91
-rw-r--r--qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/HttpPoster.java130
-rw-r--r--qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/IniFileReader.java193
-rw-r--r--qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/SoapClient.java155
-rw-r--r--qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/XMLWriter.java100
-rw-r--r--qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/systest/InfoPluginTest.java276
-rw-r--r--qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/HttpPosterTest.java107
-rw-r--r--qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/InfoServiceImplTest.java63
-rw-r--r--qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/InfoServlet.java57
-rw-r--r--qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/InfoTest.java112
-rw-r--r--qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/IniFileReaderTest.java136
-rw-r--r--qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/SoapClientTest.java208
-rw-r--r--qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/SystemInfoTest.java56
-rw-r--r--qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/XMLWriterTest.java132
-rw-r--r--qpid/java/broker-plugins/experimental/shutdown/MANIFEST.MF15
-rw-r--r--qpid/java/broker-plugins/experimental/shutdown/build.xml32
-rw-r--r--qpid/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/Activator.java71
-rw-r--r--qpid/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/Shutdown.java104
-rw-r--r--qpid/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/ShutdownMBean.java47
-rwxr-xr-xqpid/java/broker-plugins/experimental/shutdown/src/main/java/shutdown.bnd25
-rw-r--r--qpid/java/broker-plugins/extras/MANIFEST.MF21
-rw-r--r--qpid/java/broker-plugins/extras/build.xml31
-rw-r--r--qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/Activator.java48
-rw-r--r--qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/diagnostic/DiagnosticExchange.java229
-rw-r--r--qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/diagnostic/DiagnosticExchangeType.java57
-rw-r--r--qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/example/TestExchange.java257
-rw-r--r--qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/example/TestExchangeType.java57
-rw-r--r--qpid/java/broker-plugins/extras/src/test/java/org/apache/qpid/server/plugins/ExtrasTest.java74
-rw-r--r--qpid/java/broker-plugins/firewall/MANIFEST.MF36
-rw-r--r--qpid/java/broker-plugins/firewall/build.xml29
-rw-r--r--qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/config/FirewallException.java46
-rw-r--r--qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/config/FirewallRule.java137
-rw-r--r--qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/plugins/Firewall.java136
-rw-r--r--qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/plugins/FirewallActivator.java42
-rw-r--r--qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/plugins/FirewallConfiguration.java102
-rw-r--r--qpid/java/broker-plugins/firewall/src/test/java/org/apache/qpid/server/security/access/FirewallConfigurationTest.java355
-rw-r--r--qpid/java/broker-plugins/firewall/src/test/java/org/apache/qpid/server/security/access/FirewallPluginTest.java282
-rw-r--r--qpid/java/broker-plugins/simple-xml/MANIFEST.MF36
-rw-r--r--qpid/java/broker-plugins/simple-xml/build.xml29
-rwxr-xr-xqpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/config/PrincipalPermissions.java687
-rw-r--r--qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXML.java427
-rw-r--r--qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXMLActivator.java42
-rw-r--r--qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXMLConfiguration.java57
-rw-r--r--qpid/java/broker-plugins/simple-xml/src/test/java/org/apache/qpid/server/security/access/PrincipalPermissionsTest.java209
68 files changed, 8345 insertions, 0 deletions
diff --git a/qpid/java/broker-plugins/access-control/MANIFEST.MF b/qpid/java/broker-plugins/access-control/MANIFEST.MF
new file mode 100644
index 0000000000..1cd285ba20
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/MANIFEST.MF
@@ -0,0 +1,42 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Qpid Broker-Plugins Access Control
+Bundle-SymbolicName: broker-plugins-access-control
+Bundle-Description: Access control plugin for Qpid.
+Bundle-License: http://www.apache.org/licenses/LICENSE-2.0.txt
+Bundle-DocURL: http://qpid.apache.org/acl.html
+Bundle-Version: 1.0.0
+Bundle-Activator: org.apache.qpid.server.security.access.plugins.AccessControlActivator
+Bundle-RequiredExecutionEnvironment: JavaSE-1.5
+Bundle-ClassPath: .
+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,
+ org.apache.qpid.server.plugins,
+ 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.virtualhost,
+ org.apache.qpid.util,
+ 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,
+ 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.security.access.config,
+ org.apache.qpid.server.security.access.logging
+Export-Package: org.apache.qpid.server.security.access.plugins
diff --git a/qpid/java/broker-plugins/access-control/build.xml b/qpid/java/broker-plugins/access-control/build.xml
new file mode 100644
index 0000000000..89f8240fd5
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/build.xml
@@ -0,0 +1,31 @@
+<!--
+ - Licensed to the Apache Software Foundation (ASF) under one
+ - or more contributor license agreements. See the NOTICE file
+ - distributed with this work for additional information
+ - regarding copyright ownership. The ASF licenses this file
+ - to you under the Apache License, Version 2.0 (the
+ - "License"); you may not use this file except in compliance
+ - with the License. You may obtain a copy of the License at
+ -
+ - http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing,
+ - software distributed under the License is distributed on an
+ - "AS IS" BASIS, WITHOUT 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 Access Control" default="build">
+ <property name="module.depends" value="common broker broker-plugins" />
+ <property name="module.test.depends" value="test common/test broker/test management/common systests" />
+
+ <property name="module.manifest" value="MANIFEST.MF" />
+ <property name="module.plugin" value="true" />
+
+ <import file="../../module.xml" />
+
+ <target name="bundle" depends="bundle-tasks"/>
+
+ <target name="precompile" depends="gen_logging"/>
+</project>
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AbstractConfiguration.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AbstractConfiguration.java
new file mode 100644
index 0000000000..a684e52ce4
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/AbstractConfiguration.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.security.access.config;
+
+import java.io.File;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.log4j.Logger;
+
+public abstract class AbstractConfiguration implements ConfigurationFile
+{
+ protected static final Logger _logger = Logger.getLogger(ConfigurationFile.class);
+
+ protected File _file;
+ protected RuleSet _config;
+
+ public AbstractConfiguration(File file)
+ {
+ _file = file;
+ }
+
+ public File getFile()
+ {
+ return _file;
+ }
+
+ public RuleSet load() throws ConfigurationException
+ {
+ _config = new RuleSet();
+ return _config;
+ }
+
+ public RuleSet getConfiguration()
+ {
+ return _config;
+ }
+
+ public boolean save(RuleSet configuration)
+ {
+ return true;
+ }
+
+ public RuleSet reload()
+ {
+ RuleSet oldRules = _config;
+
+ try
+ {
+ RuleSet newRules = load();
+ _config = newRules;
+ }
+ catch (Exception e)
+ {
+ _config = oldRules;
+ }
+
+ return _config;
+ }
+}
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Action.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Action.java
new file mode 100644
index 0000000000..fdbd96e63e
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Action.java
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.access.config;
+
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.apache.qpid.server.security.access.ObjectProperties;
+import org.apache.qpid.server.security.access.ObjectType;
+import org.apache.qpid.server.security.access.Operation;
+
+/**
+ * An access control v2 rule action.
+ *
+ * An action consists of an {@link Operation} on an {@link ObjectType} with certain properties, stored in a {@link Map}.
+ * The operation and object should be an allowable combination, based on the {@link ObjectType#isAllowed(Operation)}
+ * method of the object, which is exposed as the {@link #isAllowed()} method here. The internal {@link #propertiesMatch(Map)}
+ * and {@link #valueMatches(String, String)} methods are used to determine wildcarded matching of properties, with
+ * the empty string or "*" matching all values, and "*" at the end of a rule value indicating prefix matching.
+ * <p>
+ * The {@link #matches(Action)} method is intended to be used when determining precedence of rules, and
+ * {@link #equals(Object)} and {@link #hashCode()} are intended for use in maps. This is due to the wildcard matching
+ * described above.
+ */
+public class Action
+{
+ private Operation _operation;
+ private ObjectType _object;
+ private ObjectProperties _properties;
+
+ public Action(Operation operation)
+ {
+ this(operation, ObjectType.ALL);
+ }
+
+ public Action(Operation operation, ObjectType object, String name)
+ {
+ this(operation, object, new ObjectProperties(name));
+ }
+
+ public Action(Operation operation, ObjectType object)
+ {
+ this(operation, object, ObjectProperties.EMPTY);
+ }
+
+ public Action(Operation operation, ObjectType object, ObjectProperties properties)
+ {
+ setOperation(operation);
+ setObjectType(object);
+ setProperties(properties);
+ }
+
+ public Operation getOperation()
+ {
+ return _operation;
+ }
+
+ public void setOperation(Operation operation)
+ {
+ _operation = operation;
+ }
+
+ public ObjectType getObjectType()
+ {
+ return _object;
+ }
+
+ public void setObjectType(ObjectType object)
+ {
+ _object = object;
+ }
+
+ public ObjectProperties getProperties()
+ {
+ return _properties;
+ }
+
+ public void setProperties(ObjectProperties properties)
+ {
+ _properties = properties;
+ }
+
+ public boolean isAllowed()
+ {
+ return _object.isAllowed(_operation);
+ }
+
+ /** @see Comparable#compareTo(Object) */
+ public boolean matches(Action a)
+ {
+ return (Operation.ALL == a.getOperation()
+ || (getOperation() == a.getOperation()
+ && getObjectType() == a.getObjectType()
+ && _properties.matches(a.getProperties())));
+ }
+
+ /**
+ * An ordering based on specificity
+ *
+ * @see Comparator#compare(Object, Object)
+ */
+ public class Specificity implements Comparator<Action>
+ {
+ public int compare(Action a, Action b)
+ {
+ if (a.getOperation() == Operation.ALL && b.getOperation() != Operation.ALL)
+ {
+ return 1; // B is more specific
+ }
+ else if (b.getOperation() == Operation.ALL && a.getOperation() != Operation.ALL)
+ {
+ return 1; // A is more specific
+ }
+ else if (a.getOperation() == b.getOperation())
+ {
+ // Same operator, compare rest of action
+
+// || (getOperation() == a.getOperation()
+// && getObjectType() == a.getObjectType()
+// && _properties.matches(a.getProperties())));
+
+ return 1; // b is more specific
+ }
+ else // Different operations
+ {
+ return a.getOperation().compareTo(b.getOperation()); // Arbitrary
+ }
+ }
+ }
+
+ /** @see Object#equals(Object) */
+ @Override
+ public boolean equals(Object o)
+ {
+ if (!(o instanceof Action))
+ {
+ return false;
+ }
+ Action a = (Action) o;
+
+ return new EqualsBuilder()
+ .append(_operation, a.getOperation())
+ .append(_object, a.getObjectType())
+ .appendSuper(_properties.equals(a.getProperties()))
+ .isEquals();
+ }
+
+ /** @see Object#hashCode() */
+ @Override
+ public int hashCode()
+ {
+ return new HashCodeBuilder()
+ .append(_operation)
+ .append(_operation)
+ .append(_properties)
+ .toHashCode();
+ }
+
+ /** @see Object#toString() */
+ @Override
+ public String toString()
+ {
+ return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
+ .append("operation", _operation)
+ .append("objectType", _object)
+ .append("properties", _properties)
+ .toString();
+ }
+}
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/ConfigurationFile.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/ConfigurationFile.java
new file mode 100644
index 0000000000..8b1a00259b
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/ConfigurationFile.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.security.access.config;
+
+import java.io.File;
+
+import org.apache.commons.configuration.ConfigurationException;
+
+public interface ConfigurationFile
+{
+ /**
+ * Return the actual {@link File} object containing the configuration.
+ */
+ File getFile();
+
+ /**
+ * Load this configuration file's contents into a {@link RuleSet}.
+ *
+ * @throws ConfigurationException if the configuration file has errors.
+ * @throws IllegalArgumentException if individual tokens cannot be parsed.
+ */
+ RuleSet load() throws ConfigurationException;
+
+ /**
+ * Reload this configuration file's contents.
+ *
+ * @throws ConfigurationException if the configuration file has errors.
+ * @throws IllegalArgumentException if individual tokens cannot be parsed.
+ */
+ RuleSet reload() throws ConfigurationException;
+
+ RuleSet getConfiguration();
+
+ /**
+ * TODO document me.
+ */
+ boolean save(RuleSet configuration);
+}
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/PlainConfiguration.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/PlainConfiguration.java
new file mode 100644
index 0000000000..9f2168a31c
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/PlainConfiguration.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.security.access.config;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.StreamTokenizer;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.lang.StringUtils;
+import org.apache.qpid.server.security.access.ObjectProperties;
+import org.apache.qpid.server.security.access.ObjectType;
+import org.apache.qpid.server.security.access.Operation;
+import org.apache.qpid.server.security.access.Permission;
+
+public class PlainConfiguration extends AbstractConfiguration
+{
+ public static final Character COMMENT = '#';
+ public static final Character CONTINUATION = '\\';
+
+ public static final String GROUP = "group";
+ public static final String ACL = "acl";
+ public static final String CONFIG = "config";
+
+ public static final String UNRECOGNISED_INITIAL_MSG = "Unrecognised initial token '%s' at line %d";
+ public static final String NOT_ENOUGH_TOKENS_MSG = "Not enough tokens at line %d";
+ public static final String NUMBER_NOT_ALLOWED_MSG = "Number not allowed before '%s' at line %d";
+ public static final String CANNOT_LOAD_MSG = "Cannot load config file %s";
+ public static final String PREMATURE_CONTINUATION_MSG = "Premature continuation character at line %d";
+ public static final String PREMATURE_EOF_MSG = "Premature end of file reached at line %d";
+ public static final String PARSE_TOKEN_FAILED_MSG = "Failed to parse token at line %d";
+ public static final String CONFIG_NOT_FOUND_MSG = "Cannot find config file %s";
+ public static final String NOT_ENOUGH_GROUP_MSG = "Not enough data for a group at line %d";
+ public static final String NOT_ENOUGH_ACL_MSG = "Not enough data for an acl at line %d";
+ public static final String NOT_ENOUGH_CONFIG_MSG = "Not enough data for config at line %d";
+ public static final String BAD_ACL_RULE_NUMBER_MSG = "Invalid rule number at line %d";
+ public static final String PROPERTY_KEY_ONLY_MSG = "Incomplete property (key only) at line %d";
+ public static final String PROPERTY_NO_EQUALS_MSG = "Incomplete property (no equals) at line %d";
+ public static final String PROPERTY_NO_VALUE_MSG = "Incomplete property (no value) at line %d";
+
+ private StreamTokenizer _st;
+
+ public PlainConfiguration(File file)
+ {
+ super(file);
+ }
+
+ @Override
+ public RuleSet load() throws ConfigurationException
+ {
+ RuleSet ruleSet = super.load();
+
+ try
+ {
+ _st = new StreamTokenizer(new BufferedReader(new FileReader(_file)));
+ _st.resetSyntax(); // setup the tokenizer
+
+ _st.commentChar(COMMENT); // single line comments
+ _st.eolIsSignificant(true); // return EOL as a token
+ _st.lowerCaseMode(true); // case insensitive tokens
+ _st.ordinaryChar('='); // equals is a token
+ _st.ordinaryChar(CONTINUATION); // continuation character (when followed by EOL)
+ _st.quoteChar('"'); // double quote
+ _st.quoteChar('\''); // single quote
+ _st.whitespaceChars('\u0000', '\u0020'); // whitespace (to be ignored) TODO properly
+ _st.wordChars('a', 'z'); // unquoted token characters [a-z]
+ _st.wordChars('A', 'Z'); // [A-Z]
+ _st.wordChars('0', '9'); // [0-9]
+ _st.wordChars('_', '_'); // underscore
+ _st.wordChars('-', '-'); // dash
+ _st.wordChars('.', '.'); // dot
+ _st.wordChars('*', '*'); // star
+ _st.wordChars('@', '@'); // at
+ _st.wordChars(':', ':'); // colon
+
+ // parse the acl file lines
+ Stack<String> stack = new Stack<String>();
+ int current;
+ do {
+ current = _st.nextToken();
+ switch (current)
+ {
+ case StreamTokenizer.TT_EOF:
+ case StreamTokenizer.TT_EOL:
+ if (stack.isEmpty())
+ {
+ break; // blank line
+ }
+
+ // pull out the first token from the bottom of the stack and check arguments exist
+ String first = stack.firstElement();
+ stack.removeElementAt(0);
+ if (stack.isEmpty())
+ {
+ throw new ConfigurationException(String.format(NOT_ENOUGH_TOKENS_MSG, getLine()));
+ }
+
+ // check for and parse optional initial number for ACL lines
+ Integer number = null;
+ if (StringUtils.isNumeric(first))
+ {
+ // set the acl number and get the next element
+ number = Integer.valueOf(first);
+ first = stack.firstElement();
+ stack.removeElementAt(0);
+ }
+
+ if (StringUtils.equalsIgnoreCase(ACL, first))
+ {
+ parseAcl(number, stack);
+ }
+ else if (number == null)
+ {
+ if (StringUtils.equalsIgnoreCase(GROUP, first))
+ {
+ parseGroup(stack);
+ }
+ else if (StringUtils.equalsIgnoreCase(CONFIG, first))
+ {
+ parseConfig(stack);
+ }
+ else
+ {
+ throw new ConfigurationException(String.format(UNRECOGNISED_INITIAL_MSG, first, getLine()));
+ }
+ }
+ else
+ {
+ throw new ConfigurationException(String.format(NUMBER_NOT_ALLOWED_MSG, first, getLine()));
+ }
+
+ // reset stack, start next line
+ stack.clear();
+ break;
+ case StreamTokenizer.TT_NUMBER:
+ stack.push(Integer.toString(Double.valueOf(_st.nval).intValue()));
+ break;
+ case StreamTokenizer.TT_WORD:
+ stack.push(_st.sval); // token
+ break;
+ default:
+ if (_st.ttype == CONTINUATION)
+ {
+ int next = _st.nextToken();
+ if (next == StreamTokenizer.TT_EOL)
+ {
+ break; // continue reading next line
+ }
+
+ // invalid location for continuation character (add one to line beacuse we ate the EOL)
+ throw new ConfigurationException(String.format(PREMATURE_CONTINUATION_MSG, getLine() + 1));
+ }
+ else if (_st.ttype == '\'' || _st.ttype == '"')
+ {
+ stack.push(_st.sval); // quoted token
+ }
+ else
+ {
+ stack.push(Character.toString((char) _st.ttype)); // single character
+ }
+ }
+ } while (current != StreamTokenizer.TT_EOF);
+
+ if (!stack.isEmpty())
+ {
+ throw new ConfigurationException(String.format(PREMATURE_EOF_MSG, getLine()));
+ }
+ }
+ catch (IllegalArgumentException iae)
+ {
+ throw new ConfigurationException(String.format(PARSE_TOKEN_FAILED_MSG, getLine()), iae);
+ }
+ catch (FileNotFoundException fnfe)
+ {
+ throw new ConfigurationException(String.format(CONFIG_NOT_FOUND_MSG, getFile().getName()), fnfe);
+ }
+ catch (IOException ioe)
+ {
+ throw new ConfigurationException(String.format(CANNOT_LOAD_MSG, getFile().getName()), ioe);
+ }
+
+ return ruleSet;
+ }
+
+ private void parseGroup(List<String> args) throws ConfigurationException
+ {
+ if (args.size() < 2)
+ {
+ throw new ConfigurationException(String.format(NOT_ENOUGH_GROUP_MSG, getLine()));
+ }
+
+ getConfiguration().addGroup(args.get(0), args.subList(1, args.size()));
+ }
+
+ private void parseAcl(Integer number, List<String> args) throws ConfigurationException
+ {
+ if (args.size() < 3)
+ {
+ throw new ConfigurationException(String.format(NOT_ENOUGH_ACL_MSG, getLine()));
+ }
+
+ Permission permission = Permission.parse(args.get(0));
+ String identity = args.get(1);
+ Operation operation = Operation.parse(args.get(2));
+
+ if (number != null && !getConfiguration().isValidNumber(number))
+ {
+ throw new ConfigurationException(String.format(BAD_ACL_RULE_NUMBER_MSG, getLine()));
+ }
+
+ if (args.size() == 3)
+ {
+ getConfiguration().grant(number, identity, permission, operation);
+ }
+ else
+ {
+ ObjectType object = ObjectType.parse(args.get(3));
+ ObjectProperties properties = toObjectProperties(args.subList(4, args.size()));
+
+ getConfiguration().grant(number, identity, permission, operation, object, properties);
+ }
+ }
+
+ private void parseConfig(List<String> args) throws ConfigurationException
+ {
+ if (args.size() < 3)
+ {
+ throw new ConfigurationException(String.format(NOT_ENOUGH_CONFIG_MSG, getLine()));
+ }
+
+ Map<String, Boolean> properties = toPluginProperties(args);
+
+ getConfiguration().configure(properties);
+ }
+
+ /** Converts a {@link List} of "name", "=", "value" tokens into a {@link Map}. */
+ protected ObjectProperties toObjectProperties(List<String> args) throws ConfigurationException
+ {
+ ObjectProperties properties = new ObjectProperties();
+ Iterator<String> i = args.iterator();
+ while (i.hasNext())
+ {
+ String key = i.next();
+ if (!i.hasNext())
+ {
+ throw new ConfigurationException(String.format(PROPERTY_KEY_ONLY_MSG, getLine()));
+ }
+ if (!"=".equals(i.next()))
+ {
+ throw new ConfigurationException(String.format(PROPERTY_NO_EQUALS_MSG, getLine()));
+ }
+ if (!i.hasNext())
+ {
+ throw new ConfigurationException(String.format(PROPERTY_NO_VALUE_MSG, getLine()));
+ }
+ String value = i.next();
+
+ // parse property key
+ ObjectProperties.Property property = ObjectProperties.Property.parse(key);
+ properties.put(property, value);
+ }
+ return properties;
+ }
+
+ /** Converts a {@link List} of "name", "=", "value" tokens into a {@link Map}. */
+ protected Map<String, Boolean> toPluginProperties(List<String> args) throws ConfigurationException
+ {
+ Map<String, Boolean> properties = new HashMap<String, Boolean>();
+ Iterator<String> i = args.iterator();
+ while (i.hasNext())
+ {
+ String key = i.next().toLowerCase();
+ if (!i.hasNext())
+ {
+ throw new ConfigurationException(String.format(PROPERTY_KEY_ONLY_MSG, getLine()));
+ }
+ if (!"=".equals(i.next()))
+ {
+ throw new ConfigurationException(String.format(PROPERTY_NO_EQUALS_MSG, getLine()));
+ }
+ if (!i.hasNext())
+ {
+ throw new ConfigurationException(String.format(PROPERTY_NO_VALUE_MSG, getLine()));
+ }
+
+ // parse property value and save
+ Boolean value = Boolean.valueOf(i.next());
+ properties.put(key, value);
+ }
+ return properties;
+ }
+
+ protected int getLine()
+ {
+ return _st.lineno() - 1;
+ }
+}
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Rule.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Rule.java
new file mode 100644
index 0000000000..15d6b67192
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/Rule.java
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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.security.access.config;
+
+import org.apache.commons.lang.builder.CompareToBuilder;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.apache.qpid.server.security.access.Permission;
+
+/**
+ * An access control v2 rule.
+ *
+ * A rule consists of {@link Permission} for a particular identity to perform an {@link Action}. The identity
+ * may be either a user or a group.
+ */
+public class Rule implements Comparable<Rule>
+{
+ /** String indicating all identitied. */
+ public static final String ALL = "all";
+
+ private Integer _number;
+ private Boolean _enabled = Boolean.TRUE;
+ private String _identity;
+ private Action _action;
+ private Permission _permission;
+
+ public Rule(Integer number, String identity, Action action, Permission permission)
+ {
+ setNumber(number);
+ setIdentity(identity);
+ setAction(action);
+ setPermission(permission);
+ }
+
+ public Rule(String identity, Action action, Permission permission)
+ {
+ this(null, identity, action, permission);
+ }
+
+ public boolean isEnabled()
+ {
+ return _enabled;
+ }
+
+ public void setEnabled(boolean enabled)
+ {
+ _enabled = enabled;
+ }
+
+ public void enable()
+ {
+ _enabled = Boolean.TRUE;
+ }
+
+ public void disable()
+ {
+ _enabled = Boolean.FALSE;
+ }
+
+ public Integer getNumber()
+ {
+ return _number;
+ }
+
+ public void setNumber(Integer number)
+ {
+ _number = number;
+ }
+
+ public String getIdentity()
+ {
+ return _identity;
+ }
+
+ public void setIdentity(String identity)
+ {
+ _identity = identity;
+ }
+
+ public Action getAction()
+ {
+ return _action;
+ }
+
+ public void setAction(Action action)
+ {
+ _action = action;
+ }
+
+ public Permission getPermission()
+ {
+ return _permission;
+ }
+
+ public void setPermission(Permission permission)
+ {
+ _permission = permission;
+ }
+
+ /** @see Comparable#compareTo(Object) */
+ public int compareTo(Rule r)
+ {
+ return new CompareToBuilder()
+ .append(getAction(), r.getAction())
+ .append(getIdentity(), r.getIdentity())
+ .append(getPermission(), r.getPermission())
+ .toComparison();
+ }
+
+ /** @see Object#equals(Object) */
+ @Override
+ public boolean equals(Object o)
+ {
+ if (!(o instanceof Rule))
+ {
+ return false;
+ }
+ Rule r = (Rule) o;
+
+ return new EqualsBuilder()
+ .append(getIdentity(), r.getIdentity())
+ .append(getAction(), r.getAction())
+ .append(getPermission(), r.getPermission())
+ .isEquals();
+ }
+
+ /** @see Object#hashCode() */
+ @Override
+ public int hashCode()
+ {
+ return new HashCodeBuilder()
+ .append(getIdentity())
+ .append(getAction())
+ .append(getPermission())
+ .toHashCode();
+ }
+
+ /** @see Object#toString() */
+ @Override
+ public String toString()
+ {
+ return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
+ .append("#", getNumber())
+ .append("identity", getIdentity())
+ .append("action", getAction())
+ .append("permission", getPermission())
+ .append("enabled", isEnabled())
+ .toString();
+ }
+}
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/RuleSet.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/RuleSet.java
new file mode 100644
index 0000000000..ebc73440ed
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/RuleSet.java
@@ -0,0 +1,489 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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.security.access.config;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.WeakHashMap;
+
+import org.apache.commons.lang.BooleanUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.server.logging.actors.CurrentActor;
+import org.apache.qpid.server.security.Result;
+import org.apache.qpid.server.security.access.ObjectProperties;
+import org.apache.qpid.server.security.access.ObjectType;
+import org.apache.qpid.server.security.access.Operation;
+import org.apache.qpid.server.security.access.Permission;
+import org.apache.qpid.server.security.access.logging.AccessControlMessages;
+
+/**
+ * Models the rule configuration for the access control plugin.
+ *
+ * The access control rule definitions are loaded from an external configuration file, passed in as the
+ * target to the {@link load(ConfigurationFile)} method. The file specified
+ */
+public class RuleSet
+{
+ private static final Logger _logger = Logger.getLogger(RuleSet.class);
+
+ private static final String AT = "@";
+ private static final String SLASH = "/";
+
+ public static final String DEFAULT_ALLOW = "defaultallow";
+ public static final String DEFAULT_DENY = "defaultdeny";
+ public static final String TRANSITIVE = "transitive";
+ public static final String EXPAND = "expand";
+ public static final String AUTONUMBER = "autonumber";
+ public static final String CONTROLLED = "controlled";
+ public static final String VALIDATE = "validate";
+
+ public static final List<String> CONFIG_PROPERTIES = Arrays.asList(
+ DEFAULT_ALLOW, DEFAULT_DENY, TRANSITIVE, EXPAND, AUTONUMBER, CONTROLLED
+ );
+
+ private static final Integer _increment = 10;
+
+ private final Map<String, List<String>> _groups = new HashMap<String, List<String>>();
+ private final SortedMap<Integer, Rule> _rules = new TreeMap<Integer, Rule>();
+ private final Map<String, Map<Operation, Map<ObjectType, List<Rule>>>> _cache =
+ new WeakHashMap<String, Map<Operation, Map<ObjectType, List<Rule>>>>();
+ private final Map<String, Boolean> _config = new HashMap<String, Boolean>();
+
+ public RuleSet()
+ {
+ // set some default configuration properties
+ configure(DEFAULT_DENY, Boolean.TRUE);
+ configure(TRANSITIVE, Boolean.TRUE);
+ }
+
+ /**
+ * Clear the contents, invluding groups, rules and configuration.
+ */
+ public void clear()
+ {
+ _rules.clear();
+ _cache.clear();
+ _config.clear();
+ _groups.clear();
+ }
+
+ public int getRuleCount()
+ {
+ return _rules.size();
+ }
+
+ /**
+ * Filtered rules list based on an identity and operation.
+ *
+ * Allows only enabled rules with identity equal to all, the same, or a group with identity as a member,
+ * and operation is either all or the same operation.
+ */
+ public List<Rule> getRules(String identity, Operation operation, ObjectType objectType)
+ {
+ // Lookup identity in cache and create empty operation map if required
+ Map<Operation, Map<ObjectType, List<Rule>>> operations = _cache.get(identity);
+ if (operations == null)
+ {
+ operations = new EnumMap<Operation, Map<ObjectType, List<Rule>>>(Operation.class);
+ _cache.put(identity, operations);
+ }
+
+ // Lookup operation and create empty object type map if required
+ Map<ObjectType, List<Rule>> objects = operations.get(operation);
+ if (objects == null)
+ {
+ objects = new EnumMap<ObjectType, List<Rule>>(ObjectType.class);
+ operations.put(operation, objects);
+ }
+
+ // Lookup object type rules for the operation
+ if (!objects.containsKey(objectType))
+ {
+ boolean controlled = false;
+ List<Rule> filtered = new LinkedList<Rule>();
+ for (Rule rule : _rules.values())
+ {
+ if (rule.isEnabled()
+ && (rule.getAction().getOperation() == Operation.ALL || rule.getAction().getOperation() == operation)
+ && (rule.getAction().getObjectType() == ObjectType.ALL || rule.getAction().getObjectType() == objectType))
+ {
+ controlled = true;
+
+ if (rule.getIdentity().equalsIgnoreCase(Rule.ALL)
+ || rule.getIdentity().equalsIgnoreCase(identity)
+ || (_groups.containsKey(rule.getIdentity()) && _groups.get(rule.getIdentity()).contains(identity)))
+ {
+ filtered.add(rule);
+ }
+ }
+ }
+
+ // Return null if there are no rules at all for this operation and object type
+ if (filtered.isEmpty() && controlled == false)
+ {
+ filtered = null;
+ }
+
+ // Save the rules we selected
+ objects.put(objectType, filtered);
+ }
+
+ // Return the cached rules
+ return objects.get(objectType);
+ }
+
+ public boolean isValidNumber(Integer number)
+ {
+ return !_rules.containsKey(number);
+ }
+
+ public void grant(Integer number, String identity, Permission permission, Operation operation)
+ {
+ Action action = new Action(operation);
+ addRule(number, identity, permission, action);
+ }
+
+ public void grant(Integer number, String identity, Permission permission, Operation operation, ObjectType object, ObjectProperties properties)
+ {
+ Action action = new Action(operation, object, properties);
+ addRule(number, identity, permission, action);
+ }
+
+ public boolean ruleExists(String identity, Action action)
+ {
+ for (Rule rule : _rules.values())
+ {
+ if (rule.getIdentity().equals(identity) && rule.getAction().equals(action))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private Permission noLog(Permission permission)
+ {
+ switch (permission)
+ {
+ case ALLOW:
+ case ALLOW_LOG:
+ return Permission.ALLOW;
+ case DENY:
+ case DENY_LOG:
+ default:
+ return Permission.DENY;
+ }
+ }
+
+ // TODO make this work when group membership is not known at file parse time
+ public void addRule(Integer number, String identity, Permission permission, Action action)
+ {
+ if (!action.isAllowed())
+ {
+ throw new IllegalArgumentException("Action is not allowd: " + action);
+ }
+ if (ruleExists(identity, action))
+ {
+ return;
+ }
+
+ // expand actions - possibly multiply number by
+ if (isSet(EXPAND))
+ {
+ if (action.getOperation() == Operation.CREATE && action.getObjectType() == ObjectType.TOPIC)
+ {
+ addRule(null, identity, noLog(permission), new Action(Operation.BIND, ObjectType.EXCHANGE,
+ new ObjectProperties("amq.topic", action.getProperties().get(ObjectProperties.Property.NAME))));
+ ObjectProperties topicProperties = new ObjectProperties();
+ topicProperties.put(ObjectProperties.Property.DURABLE, true);
+ addRule(null, identity, permission, new Action(Operation.CREATE, ObjectType.QUEUE, topicProperties));
+ return;
+ }
+ if (action.getOperation() == Operation.DELETE && action.getObjectType() == ObjectType.TOPIC)
+ {
+ addRule(null, identity, noLog(permission), new Action(Operation.UNBIND, ObjectType.EXCHANGE,
+ new ObjectProperties("amq.topic", action.getProperties().get(ObjectProperties.Property.NAME))));
+ ObjectProperties topicProperties = new ObjectProperties();
+ topicProperties.put(ObjectProperties.Property.DURABLE, true);
+ addRule(null, identity, permission, new Action(Operation.DELETE, ObjectType.QUEUE, topicProperties));
+ return;
+ }
+ }
+
+ // transitive action dependencies
+ if (isSet(TRANSITIVE))
+ {
+ if (action.getOperation() == Operation.CREATE && action.getObjectType() == ObjectType.QUEUE)
+ {
+ ObjectProperties exchProperties = new ObjectProperties(action.getProperties());
+ exchProperties.setName(ExchangeDefaults.DEFAULT_EXCHANGE_NAME);
+ exchProperties.put(ObjectProperties.Property.ROUTING_KEY, action.getProperties().get(ObjectProperties.Property.NAME));
+ addRule(null, identity, noLog(permission), new Action(Operation.BIND, ObjectType.EXCHANGE, exchProperties));
+ if (action.getProperties().isSet(ObjectProperties.Property.AUTO_DELETE))
+ {
+ addRule(null, identity, noLog(permission), new Action(Operation.DELETE, ObjectType.QUEUE, action.getProperties()));
+ }
+ }
+ else if (action.getOperation() == Operation.DELETE && action.getObjectType() == ObjectType.QUEUE)
+ {
+ ObjectProperties exchProperties = new ObjectProperties(action.getProperties());
+ exchProperties.setName(ExchangeDefaults.DEFAULT_EXCHANGE_NAME);
+ exchProperties.put(ObjectProperties.Property.ROUTING_KEY, action.getProperties().get(ObjectProperties.Property.NAME));
+ addRule(null, identity, noLog(permission), new Action(Operation.UNBIND, ObjectType.EXCHANGE, exchProperties));
+ }
+ else if (action.getOperation() != Operation.ACCESS && action.getObjectType() != ObjectType.VIRTUALHOST)
+ {
+ addRule(null, identity, noLog(permission), new Action(Operation.ACCESS, ObjectType.VIRTUALHOST));
+ }
+ }
+
+ // set rule number if needed
+ Rule rule = new Rule(number, identity, action, permission);
+ if (rule.getNumber() == null)
+ {
+ if (_rules.isEmpty())
+ {
+ rule.setNumber(0);
+ }
+ else
+ {
+ rule.setNumber(_rules.lastKey() + _increment);
+ }
+ }
+
+ // save rule
+ _cache.remove(identity);
+ _rules.put(rule.getNumber(), rule);
+ }
+
+ public void enableRule(int ruleNumber)
+ {
+ _rules.get(Integer.valueOf(ruleNumber)).enable();
+ }
+
+ public void disableRule(int ruleNumber)
+ {
+ _rules.get(Integer.valueOf(ruleNumber)).disable();
+ }
+
+ public boolean addGroup(String group, List<String> constituents)
+ {
+ if (_groups.containsKey(group))
+ {
+ // cannot redefine
+ return false;
+ }
+ else
+ {
+ _groups.put(group, new ArrayList<String>());
+ }
+
+ for (String name : constituents)
+ {
+ if (name.equalsIgnoreCase(group))
+ {
+ // recursive definition
+ return false;
+ }
+
+ if (!checkName(name))
+ {
+ // invalid name
+ return false;
+ }
+
+ if (_groups.containsKey(name))
+ {
+ // is a group
+ _groups.get(group).addAll(_groups.get(name));
+ }
+ else
+ {
+ // is a user
+ if (!isvalidUserName(name))
+ {
+ // invalid username
+ return false;
+ }
+ _groups.get(group).add(name);
+ }
+ }
+ return true;
+ }
+
+ /** Return true if the name is well-formed (contains legal characters). */
+ protected boolean checkName(String name)
+ {
+ for (int i = 0; i < name.length(); i++)
+ {
+ Character c = name.charAt(i);
+ if (!Character.isLetterOrDigit(c) && c != '-' && c != '_' && c != '@' && c != '.' && c != '/')
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** Returns true if a username has the name[@domain][/realm] format */
+ protected boolean isvalidUserName(String name)
+ {
+ // check for '@' and '/' in namne
+ int atPos = name.indexOf(AT);
+ int slashPos = name.indexOf(SLASH);
+ boolean atFound = atPos != StringUtils.INDEX_NOT_FOUND && atPos == name.lastIndexOf(AT);
+ boolean slashFound = slashPos != StringUtils.INDEX_NOT_FOUND && slashPos == name.lastIndexOf(SLASH);
+
+ // must be at least one character after '@' or '/'
+ if (atFound && atPos > name.length() - 2)
+ {
+ return false;
+ }
+ if (slashFound && slashPos > name.length() - 2)
+ {
+ return false;
+ }
+
+ // must be at least one character between '@' and '/'
+ if (atFound && slashFound)
+ {
+ return (atPos < (slashPos - 1));
+ }
+
+ // otherwise all good
+ return true;
+ }
+
+ // C++ broker authorise function prototype
+ // virtual bool authorise(const std::string& id, const Action& action, const ObjectType& objType,
+ // const std::string& name, std::map<Property, std::string>* params=0);
+
+ // Possibly add a String name paramater?
+
+ /**
+ * Check the authorisation granted to a particular identity for an operation on an object type with
+ * specific properties.
+ *
+ * Looks up the entire ruleset, whcih may be cached, for the user and operation and goes through the rules
+ * in order to find the first one that matches. Either defers if there are no rules, returns the result of
+ * the first match found, or denies access if there are no matching rules. Normally, it would be expected
+ * to have a default deny or allow rule at the end of an access configuration however.
+ */
+ public Result check(String identity, Operation operation, ObjectType objectType, ObjectProperties properties)
+ {
+ // Create the action to check
+ Action action = new Action(operation, objectType, properties);
+
+ // get the list of rules relevant for this request
+ List<Rule> rules = getRules(identity, operation, objectType);
+ if (rules == null)
+ {
+ if (isSet(CONTROLLED))
+ {
+ // Abstain if there are no rules for this operation
+ return Result.ABSTAIN;
+ }
+ else
+ {
+ return getDefault();
+ }
+ }
+
+ // Iterate through a filtered set of rules dealing with this identity and operation
+ for (Rule current : rules)
+ {
+ // Check if action matches
+ if (action.matches(current.getAction()))
+ {
+ Permission permission = current.getPermission();
+
+ switch (permission)
+ {
+ case ALLOW_LOG:
+ CurrentActor.get().message(AccessControlMessages.ALLOWED(
+ action.getOperation().toString(), action.getObjectType().toString(), action.getProperties().toString()));
+ case ALLOW:
+ return Result.ALLOWED;
+ case DENY_LOG:
+ CurrentActor.get().message(AccessControlMessages.DENIED(
+ action.getOperation().toString(), action.getObjectType().toString(), action.getProperties().toString()));
+ case DENY:
+ return Result.DENIED;
+ }
+
+ return Result.DENIED;
+ }
+ }
+
+ // Defer to the next plugin of this type, if it exists
+ return Result.DEFER;
+ }
+
+ /** Default deny. */
+ public Result getDefault()
+ {
+ if (isSet(DEFAULT_ALLOW))
+ {
+ return Result.ALLOWED;
+ }
+ if (isSet(DEFAULT_DENY))
+ {
+ return Result.DENIED;
+ }
+ return Result.ABSTAIN;
+ }
+
+ /**
+ * Check if a configuration property is set.
+ */
+ protected boolean isSet(String key)
+ {
+ return BooleanUtils.isTrue(_config.get(key));
+ }
+
+ /**
+ * Configure properties for the plugin instance.
+ *
+ * @param properties
+ */
+ public void configure(Map<String, Boolean> properties)
+ {
+ _config.putAll(properties);
+ }
+
+ /**
+ * Configure a single property for the plugin instance.
+ *
+ * @param key
+ * @param value
+ */
+ public void configure(String key, Boolean value)
+ {
+ _config.put(key, value);
+ }
+}
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/XMLConfiguration.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/XMLConfiguration.java
new file mode 100644
index 0000000000..a4f6f8b65a
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/config/XMLConfiguration.java
@@ -0,0 +1,31 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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.security.access.config;
+
+import java.io.File;
+
+public class XMLConfiguration extends AbstractConfiguration
+{
+ public XMLConfiguration(File file)
+ {
+ super(file);
+ }
+}
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/logging/AccessControl_logmessages.properties b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/logging/AccessControl_logmessages.properties
new file mode 100644
index 0000000000..bf80df3722
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/logging/AccessControl_logmessages.properties
@@ -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.
+#
+
+# org.apache.qpid.server.security.access.logging.AccessControl
+
+# Access Control logging message i18n strings.
+
+# 'accept-log' rule message
+ALLOWED = ACL-1001 : Allowed : {0} {1} {2}
+
+# 'deny-log' rule message
+DENIED = ACL-1002 : Denied : {0} {1} {2} \ No newline at end of file
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControl.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControl.java
new file mode 100644
index 0000000000..69cfa173bd
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControl.java
@@ -0,0 +1,116 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.access.plugins;
+
+import java.security.Principal;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.log4j.Logger;
+import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin;
+import org.apache.qpid.server.security.AbstractPlugin;
+import org.apache.qpid.server.security.Result;
+import org.apache.qpid.server.security.SecurityManager;
+import org.apache.qpid.server.security.SecurityPluginFactory;
+import org.apache.qpid.server.security.access.ObjectProperties;
+import org.apache.qpid.server.security.access.ObjectType;
+import org.apache.qpid.server.security.access.Operation;
+import org.apache.qpid.server.security.access.config.RuleSet;
+
+/**
+ * This access control plugin implements version two plain text access control.
+ */
+public class AccessControl extends AbstractPlugin
+{
+ public static final Logger _logger = Logger.getLogger(AccessControl.class);
+
+ private RuleSet _ruleSet;
+
+ public static final SecurityPluginFactory<AccessControl> FACTORY = new SecurityPluginFactory<AccessControl>()
+ {
+ public Class<AccessControl> getPluginClass()
+ {
+ return AccessControl.class;
+ }
+
+ public String getPluginName()
+ {
+ return AccessControl.class.getName();
+ }
+
+ public AccessControl newInstance(ConfigurationPlugin config) throws ConfigurationException
+ {
+ AccessControlConfiguration configuration = config.getConfiguration(AccessControlConfiguration.class.getName());
+
+ // If there is no configuration for this plugin then don't load it.
+ if (configuration == null)
+ {
+ return null;
+ }
+
+ AccessControl plugin = new AccessControl();
+ plugin.configure(configuration);
+ return plugin;
+ }
+ };
+
+ public Result getDefault()
+ {
+ return _ruleSet.getDefault();
+ }
+
+ /**
+ * Object instance access authorisation.
+ *
+ * Delegate to the {@link #authorise(Operation, ObjectType, ObjectProperties)} method, with
+ * the operation set to ACCESS and no object properties.
+ */
+ public Result access(ObjectType objectType, Object instance)
+ {
+ return authorise(Operation.ACCESS, objectType, ObjectProperties.EMPTY);
+ }
+
+ /**
+ * Check if an operation is authorised by asking the configuration object about the access
+ * control rules granted to the current thread's {@link Principal}. If there is no current
+ * user the plugin will abstain.
+ */
+ public Result authorise(Operation operation, ObjectType objectType, ObjectProperties properties)
+ {
+ Principal principal = SecurityManager.getThreadPrincipal();
+
+ // Abstain if there is no user associated with this thread
+ if (principal == null)
+ {
+ return Result.ABSTAIN;
+ }
+
+ return _ruleSet.check(principal.getName(), operation, objectType, properties);
+ }
+
+ public void configure(ConfigurationPlugin config)
+ {
+ super.configure(config);
+
+ AccessControlConfiguration accessConfig = (AccessControlConfiguration) _config;
+
+ _ruleSet = accessConfig.getRuleSet();
+ }
+}
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlActivator.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlActivator.java
new file mode 100644
index 0000000000..72eac7dbe6
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlActivator.java
@@ -0,0 +1,42 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.access.plugins;
+
+import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory;
+import org.apache.qpid.server.security.SecurityPluginActivator;
+import org.apache.qpid.server.security.SecurityPluginFactory;
+import org.osgi.framework.BundleActivator;
+
+/**
+ * The OSGi {@link BundleActivator} for {@link AccessControl}.
+ */
+public class AccessControlActivator extends SecurityPluginActivator
+{
+ public SecurityPluginFactory getFactory()
+ {
+ return AccessControl.FACTORY;
+ }
+
+ public ConfigurationPluginFactory getConfigurationFactory()
+ {
+ return AccessControlConfiguration.FACTORY;
+ }
+}
diff --git a/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlConfiguration.java b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlConfiguration.java
new file mode 100644
index 0000000000..f7db740ebc
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/java/org/apache/qpid/server/security/access/plugins/AccessControlConfiguration.java
@@ -0,0 +1,83 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.access.plugins;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin;
+import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory;
+import org.apache.qpid.server.security.access.config.ConfigurationFile;
+import org.apache.qpid.server.security.access.config.PlainConfiguration;
+import org.apache.qpid.server.security.access.config.RuleSet;
+
+public class AccessControlConfiguration extends ConfigurationPlugin
+{
+ public static final ConfigurationPluginFactory FACTORY = new ConfigurationPluginFactory()
+ {
+ public ConfigurationPlugin newInstance(String path, Configuration config) throws ConfigurationException
+ {
+ ConfigurationPlugin instance = new AccessControlConfiguration();
+ instance.setConfiguration(path, config);
+ return instance;
+ }
+
+ public List<String> getParentPaths()
+ {
+ return Arrays.asList("security.aclv2", "virtualhosts.virtualhost.security.aclv2");
+ }
+ };
+
+ private RuleSet _ruleSet;
+
+ public String[] getElementsProcessed()
+ {
+ return new String[] { "" };
+ }
+
+ public String getFileName()
+ {
+ return _configuration.getString("");
+ }
+
+ public void validateConfiguration() throws ConfigurationException
+ {
+ String filename = getFileName();
+ if (filename == null)
+ {
+ throw new ConfigurationException("No ACL file name specified");
+ }
+
+ File aclFile = new File(filename);
+
+ ConfigurationFile configFile = new PlainConfiguration(aclFile);
+ _ruleSet = configFile.load();
+ }
+
+ public RuleSet getRuleSet()
+ {
+ return _ruleSet;
+ }
+
+}
diff --git a/qpid/java/broker-plugins/access-control/src/main/resources/acl.xsd b/qpid/java/broker-plugins/access-control/src/main/resources/acl.xsd
new file mode 100644
index 0000000000..9a165b50b8
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/main/resources/acl.xsd
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ -
+ - Licensed to the Apache Software Foundation (ASF) under one
+ - or more contributor license agreements. See the NOTICE file
+ - distributed with this work for additional information
+ - regarding copyright ownership. The ASF licenses this file
+ - to you under the Apache License, Version 2.0 (the
+ - "License"); you may not use this file except in compliance
+ - with the License. You may obtain a copy of the License at
+ -
+ - http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing,
+ - software distributed under the License is distributed on an
+ - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ - KIND, either express or implied. See the License for the
+ - specific language governing permissions and limitations
+ - under the License.
+ -
+ -->
+<xs:schema
+ xmlns="http://qpid.apache.org/schema/qpid/broker/security/acl.xsd"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ targetNamespace="http://qpid.apache.org/schema/qpid/broker/security/acl.xsd"
+ elementFormDefault="qualified">
+ <xs:element name="aclv2" type="xs:string" />
+</xs:schema>
diff --git a/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/AccessControlTest.java b/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/AccessControlTest.java
new file mode 100644
index 0000000000..309a3aeb2c
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/AccessControlTest.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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.security.access.plugins;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.qpid.server.security.access.config.ConfigurationFile;
+import org.apache.qpid.server.security.access.config.PlainConfiguration;
+import org.apache.qpid.server.security.access.config.RuleSet;
+
+/**
+ * These tests check that the ACL file parsing works correctly.
+ *
+ * For each message that can be returned in a {@link ConfigurationException}, an ACL file is created that should trigger this
+ * particular message.
+ */
+public class AccessControlTest extends TestCase
+{
+ public void writeACLConfig(String...aclData) throws Exception
+ {
+ File acl = File.createTempFile(getClass().getName() + getName(), "acl");
+ acl.deleteOnExit();
+
+ // Write ACL file
+ PrintWriter aclWriter = new PrintWriter(new FileWriter(acl));
+ for (String line : aclData)
+ {
+ aclWriter.println(line);
+ }
+ aclWriter.close();
+
+ // Load ruleset
+ ConfigurationFile configFile = new PlainConfiguration(acl);
+ RuleSet ruleSet = configFile.load();
+ }
+
+ public void testMissingACLConfig() throws Exception
+ {
+ try
+ {
+ // Load ruleset
+ ConfigurationFile configFile = new PlainConfiguration(new File("doesnotexist"));
+ RuleSet ruleSet = configFile.load();
+
+ fail("fail");
+ }
+ catch (ConfigurationException ce)
+ {
+ assertEquals(String.format(PlainConfiguration.CONFIG_NOT_FOUND_MSG, "doesnotexist"), ce.getMessage());
+ assertTrue(ce.getCause() instanceof FileNotFoundException);
+ assertEquals("doesnotexist (No such file or directory)", ce.getCause().getMessage());
+ }
+ }
+
+ public void testACLFileSyntaxContinuation() throws Exception
+ {
+ try
+ {
+ writeACLConfig("ACL ALLOW ALL \\ ALL");
+ fail("fail");
+ }
+ catch (ConfigurationException ce)
+ {
+ assertEquals(String.format(PlainConfiguration.PREMATURE_CONTINUATION_MSG, 1), ce.getMessage());
+ }
+ }
+
+ public void testACLFileSyntaxTokens() throws Exception
+ {
+ try
+ {
+ writeACLConfig("ACL unparsed ALL ALL");
+ fail("fail");
+ }
+ catch (ConfigurationException ce)
+ {
+ assertEquals(String.format(PlainConfiguration.PARSE_TOKEN_FAILED_MSG, 1), ce.getMessage());
+ assertTrue(ce.getCause() instanceof IllegalArgumentException);
+ assertEquals("Not a valid permission: unparsed", ce.getCause().getMessage());
+ }
+ }
+
+ public void testACLFileSyntaxNotEnoughGroup() throws Exception
+ {
+ try
+ {
+ writeACLConfig("GROUP blah");
+ fail("fail");
+ }
+ catch (ConfigurationException ce)
+ {
+ assertEquals(String.format(PlainConfiguration.NOT_ENOUGH_GROUP_MSG, 1), ce.getMessage());
+ }
+ }
+
+ public void testACLFileSyntaxNotEnoughACL() throws Exception
+ {
+ try
+ {
+ writeACLConfig("ACL ALLOW");
+ fail("fail");
+ }
+ catch (ConfigurationException ce)
+ {
+ assertEquals(String.format(PlainConfiguration.NOT_ENOUGH_ACL_MSG, 1), ce.getMessage());
+ }
+ }
+
+ public void testACLFileSyntaxNotEnoughConfig() throws Exception
+ {
+ try
+ {
+ writeACLConfig("CONFIG");
+ fail("fail");
+ }
+ catch (ConfigurationException ce)
+ {
+ assertEquals(String.format(PlainConfiguration.NOT_ENOUGH_TOKENS_MSG, 1), ce.getMessage());
+ }
+ }
+
+ public void testACLFileSyntaxNotEnough() throws Exception
+ {
+ try
+ {
+ writeACLConfig("INVALID");
+ fail("fail");
+ }
+ catch (ConfigurationException ce)
+ {
+ assertEquals(String.format(PlainConfiguration.NOT_ENOUGH_TOKENS_MSG, 1), ce.getMessage());
+ }
+ }
+
+ public void testACLFileSyntaxPropertyKeyOnly() throws Exception
+ {
+ try
+ {
+ writeACLConfig("ACL ALLOW adk CREATE QUEUE name");
+ fail("fail");
+ }
+ catch (ConfigurationException ce)
+ {
+ assertEquals(String.format(PlainConfiguration.PROPERTY_KEY_ONLY_MSG, 1), ce.getMessage());
+ }
+ }
+
+ public void testACLFileSyntaxPropertyNoEquals() throws Exception
+ {
+ try
+ {
+ writeACLConfig("ACL ALLOW adk CREATE QUEUE name test");
+ fail("fail");
+ }
+ catch (ConfigurationException ce)
+ {
+ assertEquals(String.format(PlainConfiguration.PROPERTY_NO_EQUALS_MSG, 1), ce.getMessage());
+ }
+ }
+
+ public void testACLFileSyntaxPropertyNoValue() throws Exception
+ {
+ try
+ {
+ writeACLConfig("ACL ALLOW adk CREATE QUEUE name =");
+ fail("fail");
+ }
+ catch (ConfigurationException ce)
+ {
+ assertEquals(String.format(PlainConfiguration.PROPERTY_NO_VALUE_MSG, 1), ce.getMessage());
+ }
+ }
+}
diff --git a/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/RuleSetTest.java b/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/RuleSetTest.java
new file mode 100644
index 0000000000..aad7290557
--- /dev/null
+++ b/qpid/java/broker-plugins/access-control/src/test/java/org/apache/qpid/server/security/access/plugins/RuleSetTest.java
@@ -0,0 +1,289 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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.security.access.plugins;
+
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.security.Result;
+import org.apache.qpid.server.security.access.ObjectProperties;
+import org.apache.qpid.server.security.access.ObjectType;
+import org.apache.qpid.server.security.access.Operation;
+import org.apache.qpid.server.security.access.Permission;
+import org.apache.qpid.server.security.access.config.RuleSet;
+import org.apache.qpid.test.utils.QpidTestCase;
+
+/**
+ * This test checks that the {@link RuleSet} object which forms the core of the access control plugin performs correctly.
+ *
+ * The ruleset is configured directly rather than using an external file by adding rules individually, calling the
+ * {@link RuleSet#grant(Integer, String, Permission, Operation, ObjectType, ObjectProperties)} method. Then, the
+ * access control mechanism is validated by checking whether operations would be authorised by calling the
+ * {@link RuleSet#check(String, Operation, ObjectType, ObjectProperties)} method.
+ */
+public class RuleSetTest extends QpidTestCase
+{
+ private RuleSet _ruleSet;
+
+ // Common things that are passed to frame constructors
+ private AMQShortString _queueName = new AMQShortString(this.getClass().getName() + "queue");
+ private AMQShortString _exchangeName = new AMQShortString("amq.direct");
+ private AMQShortString _exchangeType = new AMQShortString("direct");
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+
+ _ruleSet = new RuleSet();
+ _ruleSet.configure(RuleSet.TRANSITIVE, Boolean.FALSE);
+ }
+
+ @Override
+ public void tearDown() throws Exception
+ {
+ _ruleSet.clear();
+ super.tearDown();
+ }
+
+ public void assertDenyGrantAllow(String identity, Operation operation, ObjectType objectType)
+ {
+ assertDenyGrantAllow(identity, operation, objectType, ObjectProperties.EMPTY);
+ }
+
+ public void assertDenyGrantAllow(String identity, Operation operation, ObjectType objectType, ObjectProperties properties)
+ {
+ assertEquals(Result.DENIED, _ruleSet.check(identity, operation, objectType, properties));
+ _ruleSet.grant(0, identity, Permission.ALLOW, operation, objectType, properties);
+ assertEquals(1, _ruleSet.getRuleCount());
+ assertEquals(Result.ALLOWED, _ruleSet.check(identity, operation, objectType, properties));
+ }
+
+ public void testEmptyRuleSet()
+ {
+ assertNotNull(_ruleSet);
+ assertEquals(_ruleSet.getRuleCount(), 0);
+ assertEquals(_ruleSet.getDefault(), _ruleSet.check("user", Operation.ACCESS, ObjectType.VIRTUALHOST, ObjectProperties.EMPTY));
+ }
+
+ public void testVirtualHostAccess() throws Exception
+ {
+ assertDenyGrantAllow("user", Operation.ACCESS, ObjectType.VIRTUALHOST);
+ }
+
+ public void testQueueCreateNamed() throws Exception
+ {
+ assertDenyGrantAllow("user", Operation.CREATE, ObjectType.QUEUE, new ObjectProperties(_queueName));
+ }
+
+ public void testQueueCreatenamedNullRoutingKey()
+ {
+ ObjectProperties properties = new ObjectProperties(_queueName);
+ properties.put(ObjectProperties.Property.ROUTING_KEY, (String) null);
+
+ assertDenyGrantAllow("user", Operation.CREATE, ObjectType.QUEUE, properties);
+ }
+
+ public void testExchangeCreate()
+ {
+ ObjectProperties properties = new ObjectProperties(_exchangeName);
+ properties.put(ObjectProperties.Property.TYPE, _exchangeType.asString());
+
+ assertDenyGrantAllow("user", Operation.CREATE, ObjectType.EXCHANGE, properties);
+ }
+
+ public void testConsume()
+ {
+ assertDenyGrantAllow("user", Operation.CONSUME, ObjectType.QUEUE);
+ }
+
+ public void testPublish()
+ {
+ assertDenyGrantAllow("user", Operation.PUBLISH, ObjectType.EXCHANGE);
+ }
+
+ /**
+ * If the consume permission for temporary queues is for an unnamed queue then it should
+ * be global for any temporary queue but not for any non-temporary queue
+ */
+ public void testTemporaryUnnamedQueueConsume()
+ {
+ ObjectProperties temporary = new ObjectProperties();
+ temporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE);
+
+ ObjectProperties normal = new ObjectProperties();
+ normal.put(ObjectProperties.Property.AUTO_DELETE, Boolean.FALSE);
+
+ assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CONSUME, ObjectType.QUEUE, temporary));
+ _ruleSet.grant(0, "user", Permission.ALLOW, Operation.CONSUME, ObjectType.QUEUE, temporary);
+ assertEquals(1, _ruleSet.getRuleCount());
+ assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CONSUME, ObjectType.QUEUE, temporary));
+
+ // defer to global if exists, otherwise default answer - this is handled by the security manager
+ assertEquals(Result.DEFER, _ruleSet.check("user", Operation.CONSUME, ObjectType.QUEUE, normal));
+ }
+
+ /**
+ * Test that temporary queue permissions before queue perms in the ACL config work correctly
+ */
+ public void testTemporaryQueueFirstConsume()
+ {
+ ObjectProperties temporary = new ObjectProperties(_queueName);
+ temporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE);
+
+ ObjectProperties normal = new ObjectProperties(_queueName);
+ normal.put(ObjectProperties.Property.AUTO_DELETE, Boolean.FALSE);
+
+ assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CONSUME, ObjectType.QUEUE, temporary));
+
+ // should not matter if the temporary permission is processed first or last
+ _ruleSet.grant(1, "user", Permission.ALLOW, Operation.CONSUME, ObjectType.QUEUE, normal);
+ _ruleSet.grant(2, "user", Permission.ALLOW, Operation.CONSUME, ObjectType.QUEUE, temporary);
+ assertEquals(2, _ruleSet.getRuleCount());
+
+ assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CONSUME, ObjectType.QUEUE, normal));
+ assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CONSUME, ObjectType.QUEUE, temporary));
+ }
+
+ /**
+ * Test that temporary queue permissions after queue perms in the ACL config work correctly
+ */
+ public void testTemporaryQueueLastConsume()
+ {
+ ObjectProperties temporary = new ObjectProperties(_queueName);
+ temporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE);
+
+ ObjectProperties normal = new ObjectProperties(_queueName);
+ normal.put(ObjectProperties.Property.AUTO_DELETE, Boolean.FALSE);
+
+ assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CONSUME, ObjectType.QUEUE, temporary));
+
+ // should not matter if the temporary permission is processed first or last
+ _ruleSet.grant(1, "user", Permission.ALLOW, Operation.CONSUME, ObjectType.QUEUE, temporary);
+ _ruleSet.grant(2, "user", Permission.ALLOW, Operation.CONSUME, ObjectType.QUEUE, normal);
+ assertEquals(2, _ruleSet.getRuleCount());
+
+ assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CONSUME, ObjectType.QUEUE, normal));
+ assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CONSUME, ObjectType.QUEUE, temporary));
+ }
+
+ /*
+ * Test different rules for temporary queues.
+ */
+
+ /**
+ * The more generic rule first is used, so both requests are allowed.
+ */
+ public void testFirstNamedSecondTemporaryQueueDenied()
+ {
+ ObjectProperties named = new ObjectProperties(_queueName);
+ ObjectProperties namedTemporary = new ObjectProperties(_queueName);
+ namedTemporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE);
+
+ assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, named));
+ assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedTemporary));
+
+ _ruleSet.grant(1, "user", Permission.ALLOW, Operation.CREATE, ObjectType.QUEUE, named);
+ _ruleSet.grant(2, "user", Permission.DENY, Operation.CREATE, ObjectType.QUEUE, namedTemporary);
+ assertEquals(2, _ruleSet.getRuleCount());
+
+ assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, named));
+ assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedTemporary));
+ }
+
+ /**
+ * The more specific rule is first, so those requests are denied.
+ */
+ public void testFirstTemporarySecondNamedQueueDenied()
+ {
+ ObjectProperties named = new ObjectProperties(_queueName);
+ ObjectProperties namedTemporary = new ObjectProperties(_queueName);
+ namedTemporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE);
+
+ assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, named));
+ assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedTemporary));
+
+ _ruleSet.grant(1, "user", Permission.DENY, Operation.CREATE, ObjectType.QUEUE, namedTemporary);
+ _ruleSet.grant(2, "user", Permission.ALLOW, Operation.CREATE, ObjectType.QUEUE, named);
+ assertEquals(2, _ruleSet.getRuleCount());
+
+ assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, named));
+ assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedTemporary));
+ }
+
+ /**
+ * The more specific rules are first, so those requests are denied.
+ */
+ public void testFirstTemporarySecondDurableThirdNamedQueueDenied()
+ {
+ ObjectProperties named = new ObjectProperties(_queueName);
+ ObjectProperties namedTemporary = new ObjectProperties(_queueName);
+ namedTemporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE);
+ ObjectProperties namedDurable = new ObjectProperties(_queueName);
+ namedDurable.put(ObjectProperties.Property.DURABLE, Boolean.TRUE);
+
+ assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, named));
+ assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedTemporary));
+ assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedDurable));
+
+ _ruleSet.grant(1, "user", Permission.DENY, Operation.CREATE, ObjectType.QUEUE, namedTemporary);
+ _ruleSet.grant(2, "user", Permission.DENY, Operation.CREATE, ObjectType.QUEUE, namedDurable);
+ _ruleSet.grant(3, "user", Permission.ALLOW, Operation.CREATE, ObjectType.QUEUE, named);
+ assertEquals(3, _ruleSet.getRuleCount());
+
+ assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, named));
+ assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedTemporary));
+ assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedDurable));
+ }
+
+ public void testNamedTemporaryQueueAllowed()
+ {
+ ObjectProperties named = new ObjectProperties(_queueName);
+ ObjectProperties namedTemporary = new ObjectProperties(_queueName);
+ namedTemporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE);
+
+ assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, named));
+ assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedTemporary));
+
+ _ruleSet.grant(1, "user", Permission.ALLOW, Operation.CREATE, ObjectType.QUEUE, namedTemporary);
+ _ruleSet.grant(2, "user", Permission.ALLOW, Operation.CREATE, ObjectType.QUEUE, named);
+ assertEquals(2, _ruleSet.getRuleCount());
+
+ assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, named));
+ assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedTemporary));
+ }
+
+ public void testNamedTemporaryQueueDeniedAllowed()
+ {
+ ObjectProperties named = new ObjectProperties(_queueName);
+ ObjectProperties namedTemporary = new ObjectProperties(_queueName);
+ namedTemporary.put(ObjectProperties.Property.AUTO_DELETE, Boolean.TRUE);
+
+ assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, named));
+ assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedTemporary));
+
+ _ruleSet.grant(1, "user", Permission.ALLOW, Operation.CREATE, ObjectType.QUEUE, namedTemporary);
+ _ruleSet.grant(2, "user", Permission.DENY, Operation.CREATE, ObjectType.QUEUE, named);
+ assertEquals(2, _ruleSet.getRuleCount());
+
+ assertEquals(Result.DENIED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, named));
+ assertEquals(Result.ALLOWED, _ruleSet.check("user", Operation.CREATE, ObjectType.QUEUE, namedTemporary));
+ }
+}
diff --git a/qpid/java/broker-plugins/experimental/info/MANIFEST.MF b/qpid/java/broker-plugins/experimental/info/MANIFEST.MF
new file mode 100644
index 0000000000..f213104d8d
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/info/MANIFEST.MF
@@ -0,0 +1,16 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: QpidPlugin
+Bundle-SymbolicName: qpid_info_plugin;singleton:=true
+Bundle-License: http://www.apache.org/licenses/LICENSE-2.0.txt
+Bundle-Version: 1.0.0
+Bundle-Activator: org.apache.qpid.info.Activator
+Import-Package: org.apache.qpid.server.configuration,
+ org.osgi.framework,
+ org.apache.qpid.common,
+ org.apache.qpid.server.registry
+Bundle-RequiredExecutionEnvironment: JavaSE-1.6
+Bundle-ClassPath: .
+Bundle-ActivationPolicy: lazy
+Export-Package: org.apache.qpid.info;uses:="org.osgi.framework"
+
diff --git a/qpid/java/broker-plugins/experimental/info/build.properties b/qpid/java/broker-plugins/experimental/info/build.properties
new file mode 100644
index 0000000000..bdbbe1c2af
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/info/build.properties
@@ -0,0 +1,31 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ plugin.xml,\
+ lib/eventTrackerClient-2.7.0.jar,\
+ lib/commons-logging-1.0.4.jar
+src.includes = src/,\
+ plugin.xml,\
+ lib/,\
+ build.properties,\
+ bin/,\
+ META-INF/
diff --git a/qpid/java/broker-plugins/experimental/info/build.xml b/qpid/java/broker-plugins/experimental/info/build.xml
new file mode 100644
index 0000000000..c5881aa839
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/info/build.xml
@@ -0,0 +1,32 @@
+<!--
+ -
+ - Licensed to the Apache Software Foundation (ASF) under one
+nn - or more contributor license agreements. See the NOTICE file
+ -n distributed with this work for additional information
+ - regarding copyright ownership. The ASF licenses this file
+ - to you under the Apache License, Version 2.0 (the
+ - "License"); you may not use this file except in compliance
+ - with the License. You may obtain a copy of the License at
+ -
+ - http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing,
+ - software distributed under the License is distributed on an
+ - "AS IS" BASIS, WITHOUT 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="AMQ Broker Info Plugin" default="build">
+
+ <property name="module.depends" value="common broker broker-plugins"/>
+ <property name="module.test.depends" value="test broker/test management/common client systests common/test"/>
+ <property name="module.manifest" value="MANIFEST.MF"/>
+ <property name="module.plugin" value="true"/>
+
+ <import file="../../../module.xml"/>
+
+ <target name="bundle" depends="bundle-tasks"/>
+
+</project>
diff --git a/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/Activator.java b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/Activator.java
new file mode 100644
index 0000000000..c7d3fd38ff
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/Activator.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.info;
+
+import org.apache.qpid.info.util.HttpPoster;
+import org.apache.qpid.info.util.IniFileReader;
+import org.apache.qpid.info.util.SoapClient;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+/** The Activator class for the OSGI info service */
+public class Activator implements BundleActivator
+{
+
+ private final List<String> _soapPropList = Arrays.asList("soap.hostname",
+ "soap.port", "soap.path", "soap.action", "soap.envelope");
+
+ private final List<String> _httpPropList = Arrays.asList("http.url",
+ "http.envelope");
+
+ InfoServiceImpl _service = null;
+
+ BundleContext _ctx = null;
+
+ /**
+ * Start bundle method
+ *
+ * @param ctx the bundle context
+ */
+ public void start(BundleContext ctx) throws Exception
+ {
+ if (null != ctx)
+ {
+ _ctx = ctx;
+ _service = new InfoServiceImpl();
+ ctx.registerService(InfoService.class.getName(), _service, null);
+ sendInfo("STARTUP");
+ }
+ }
+
+ /**
+ * Stop the bundle method
+ *
+ * @param ctx the bundle context
+ */
+ public void stop(BundleContext ctx) throws Exception
+ {
+ sendInfo("SHUTDOWN");
+ }
+
+ /**
+ * Sends the information message
+ *
+ * @param action label that identifies if we are starting up or shutting down
+ */
+ private void sendInfo(String action)
+ {
+ if ((null == _ctx) && (null == _service))
+ {
+ // invalid state
+ return;
+ }
+
+ IniFileReader ifr = new IniFileReader();
+ try
+ {
+ String QPID_HOME = System.getProperty("QPID_HOME");
+ String cfgFilePath = QPID_HOME + File.separator + "etc"
+ + File.separator + "qpidinfo.ini";
+ ifr.load(cfgFilePath);
+ }
+ catch (Throwable ex)
+ {
+ // drop everything to be silent
+ return;
+ }
+
+ // Only send Messages if we have some sections.
+ if (ifr.getSections().size() != 0)
+ {
+ Info<? extends Map<String, ?>> info = _service.invoke(action);
+ String protocol = ifr.getSections().get("").getProperty("protocol");
+ sendMessages(protocol, ifr, info);
+ }
+ }
+
+ /**
+ * Sends all the messages configured in the properties file
+ *
+ * @param protocol indicates what protocol to be used: http and soap implemented
+ * for now
+ * @param ifr an instance of IniFileReader class
+ * @param info an instance of an Info object, encapsulating the information
+ * we want to send
+ */
+ private void sendMessages(String protocol, IniFileReader ifr,
+ Info<? extends Map<String, ?>> info)
+ {
+ if (null != protocol)
+ {
+ // Set the global properties first (as they are the defaults)
+ Properties defaultProps = ifr.getSections().get("");
+ if (protocol.toLowerCase().startsWith("http"))
+ {
+ for (String section : ifr.getSections().keySet())
+ {
+ // Skip the defaults
+ if (section.equals(""))
+ {
+ continue;
+ }
+ Properties props = new Properties();
+ props.putAll(defaultProps);
+ props.putAll(ifr.getSections().get(section));
+ if (isValid(protocol, props))
+ {
+ new HttpPoster(props, info.toXML()).run();
+ }
+ }
+
+ }
+ else if (protocol.toLowerCase().startsWith("soap"))
+ {
+ for (String section : ifr.getSections().keySet())
+ {
+ Properties props = new Properties();
+ props.putAll(defaultProps);
+ props.putAll(ifr.getSections().get(section));
+ if (isValid(protocol, props))
+ {
+ new SoapClient(info.toMap(), props).sendSOAPMessage();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks if the properties for a specified protocol are valid
+ *
+ * @param protocol String representing the protocol
+ * @param props The properties associate with the specified protocol
+ * @return boolean
+ */
+ private boolean isValid(String protocol, Properties props)
+ {
+ if (null == protocol)
+ {
+ return false;
+ }
+ String value = "";
+ if (protocol.toLowerCase().startsWith("http"))
+ {
+ for (String prop : _httpPropList)
+ {
+ if (null == props.get(prop))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ if (protocol.toLowerCase().startsWith("soap"))
+ {
+ for (String prop : _soapPropList)
+ {
+ value = props.getProperty(prop);
+ if (null == value)
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+} // end class
+
diff --git a/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/AppInfo.java b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/AppInfo.java
new file mode 100644
index 0000000000..a5d267282b
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/AppInfo.java
@@ -0,0 +1,94 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.info;
+
+import org.apache.qpid.common.QpidProperties;
+import org.apache.qpid.server.configuration.ServerConfiguration;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.TreeMap;
+
+/** AppInfo class is gathering application specific information */
+public class AppInfo
+{
+
+ private static final List<String> appProps = Arrays.asList("QPID_HOME",
+ "QPID_WORK");
+
+ private static Map<String, String> appInfoMap = new TreeMap<String, String>();
+
+ /**
+ * getInfo method retrieves a key-value map for specific application properties
+ *
+ * @return Map<String,String>
+ */
+ public static Map<String, String> getInfo()
+ {
+
+ // Gather the selected app props
+ Properties sysprops = System.getProperties();
+ String propName;
+ for (Iterator<Entry<Object, Object>> it = sysprops.entrySet()
+ .iterator(); it.hasNext();)
+ {
+ Entry<Object, Object> en = it.next();
+ propName = en.getKey().toString();
+ if (appProps.indexOf(propName) >= 0)
+ {
+ appInfoMap.put(propName, en.getValue().toString());
+ }
+ }
+
+ ServerConfiguration sc;
+ try
+ {
+ sc = ApplicationRegistry.getInstance().getConfiguration();
+ if (null != sc)
+ {
+ appInfoMap.put("jmxport", sc.getJMXManagementPort() + "");
+ appInfoMap.put("port", sc.getPorts().toString());
+ appInfoMap.put("version", QpidProperties.getReleaseVersion());
+ appInfoMap.put("vhosts", "standalone");
+ appInfoMap.put("JMXPrincipalDatabase", sc
+ .getJMXPrincipalDatabase());
+ appInfoMap.put("KeystorePath", sc.getKeystorePath());
+ appInfoMap.put("PluginDirectory", sc.getPluginDirectory());
+ appInfoMap.put("CertType", sc.getCertType());
+ appInfoMap.put("QpidWork", sc.getQpidWork());
+ appInfoMap.put("Bind", sc.getBind());
+ }
+ }
+ catch (Exception e)
+ {
+ // drop everything to be silent
+ }
+ return appInfoMap;
+
+ }
+
+}
diff --git a/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/Info.java b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/Info.java
new file mode 100644
index 0000000000..2fb9382526
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/Info.java
@@ -0,0 +1,143 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ *
+ * @author sorin
+ *
+ * Info object
+ */
+
+package org.apache.qpid.info;
+
+import org.apache.qpid.info.util.XMLWriter;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * The Info class encapsulates all the information we are collecting
+ * and it is able to render it in different data representations
+ */
+public class Info<T extends Map<String, ?>>
+{
+ private T _info;
+
+ /**
+ * Constructor.
+ *
+ * @param info instantiates the object with a Map<String,?>
+ */
+ public Info(T info)
+ {
+ _info = info;
+ }
+
+ @Override
+ public String toString()
+ {
+ String result = "";
+ for (Iterator<String> it = _info.keySet().iterator(); it.hasNext();)
+ {
+ String str = it.next();
+ result += str + "=" + _info.get(str).toString() + "\n";
+ }
+ return result;
+ }
+
+ /**
+ * Renders Info map to a property object
+ *
+ * @return A Properties object representing the Info map
+ */
+ public Properties toProps()
+ {
+ Properties props = new Properties();
+ if (null == _info)
+ {
+ return null;
+ }
+ for (Iterator<String> it = _info.keySet().iterator(); it.hasNext();)
+ {
+ String key = it.next();
+ props.put(key, _info.get(key));
+ }
+ return props;
+ }
+
+ /**
+ * Renders Info map to a StringBuffer
+ *
+ * @return A StringBuffer object representing the Info map
+ */
+ public StringBuffer toStringBuffer()
+ {
+ StringBuffer sb = new StringBuffer();
+ for (Iterator<String> it = _info.keySet().iterator(); it.hasNext();)
+ {
+ String str = it.next();
+ sb.append(str + "=" + _info.get(str).toString() + "\n");
+ }
+ return sb;
+ }
+
+ /**
+ * Renders Info map to a StringBuffer containing an XML string
+ *
+ * @return A StringBuffer object containing an XML representation of the Info map
+ */
+ public StringBuffer toXML()
+ {
+ XMLWriter xw = new XMLWriter(new StringBuffer());
+ xw.writeXMLHeader();
+ Map<String, String> attr = new HashMap<String, String>();
+ xw.writeOpenTag("qpidinfo", attr);
+ String key;
+ for (Iterator<String> it = _info.keySet().iterator(); it.hasNext();)
+ {
+ attr.clear();
+ key = it.next();
+ xw.writeTag(key, attr, _info.get(key).toString());
+ }
+ xw.writeCloseTag("qpidinfo");
+ return xw.getXML();
+ }
+
+ /**
+ * Renders Info map to a HashMap
+ *
+ * @return A HashMap object representing the Info map
+ */
+ public HashMap<String, String> toMap()
+ {
+ String key;
+ HashMap<String, String> infoMap = new HashMap<String, String>();
+ for (Iterator<String> it = _info.keySet().iterator(); it.hasNext();)
+ {
+ key = it.next();
+ infoMap.put(key, _info.get(key).toString());
+ }
+ return infoMap;
+ }
+
+}
diff --git a/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/InfoService.java b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/InfoService.java
new file mode 100644
index 0000000000..2804dfb1b4
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/InfoService.java
@@ -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.
+ *
+ */
+
+ /**
+ * Interface exposing the service methods
+ */
+ package org.apache.qpid.info;
+
+ public interface InfoService
+ {
+ public Info<?> invoke(String action);
+ }
diff --git a/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/InfoServiceImpl.java b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/InfoServiceImpl.java
new file mode 100644
index 0000000000..5522f2701e
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/InfoServiceImpl.java
@@ -0,0 +1,66 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ *
+ * @author sorin
+ *
+ * Implementation for Info service
+ */
+
+package org.apache.qpid.info;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+
+public class InfoServiceImpl implements InfoService
+{
+
+ SortedMap<String, String> infoMap = new TreeMap<String, String>();
+
+ /**
+ * invoke method collects all the information from System and Application
+ * and encapsulates them in an Info object
+ * @return An instance of an Info object
+ */
+ public Info<? extends Map<String,?>> invoke(String action)
+ {
+ // Record the action (STARTUP/SHUTDOWN)
+ infoMap.put("action",action);
+
+ // Record the current time stamp
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ");
+ infoMap.put("time", sdf.format(Calendar.getInstance().getTime()));
+
+ // Add the system specific properties
+ infoMap.putAll(SystemInfo.getInfo());
+
+ // Add the application specific properties
+ infoMap.putAll(AppInfo.getInfo());
+
+ return new Info<SortedMap<String, String>>(infoMap);
+ }
+
+}
diff --git a/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/SystemInfo.java b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/SystemInfo.java
new file mode 100644
index 0000000000..8bd94fe14d
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/SystemInfo.java
@@ -0,0 +1,91 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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.info;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+import java.util.Map.Entry;
+
+/**
+ * Collector for system specific information
+ */
+public class SystemInfo
+{
+
+ private static Map<String, String> sysInfoMap = new TreeMap<String, String>();
+
+ private static final List<String> sysProps = Arrays.asList(
+ "java.class.path", "java.home", "java.vm.name", "java.vm.vendor",
+ "java.vm.version", "java.class.version", "java.runtime.version",
+ "os.arch", "os.name", "os.version", "sun.arch.data.model",
+ "user.home", "user.dir", "user.name", "user.timezone");
+
+ /**
+ * getInfo collects all the properties specified in sysprops list
+ * @return A Map<String,String>
+ */
+ public static Map<String, String> getInfo()
+ {
+
+ // Get the hostname
+ try
+ {
+ InetAddress addr = InetAddress.getLocalHost();
+ String hostname = addr.getHostName();
+ sysInfoMap.put("hostname", hostname);
+ sysInfoMap.put("ip", addr.getHostAddress());
+ }
+ catch (UnknownHostException e)
+ {
+ // drop everything to be silent
+ }
+ // Get the runtime info
+ sysInfoMap.put("CPUCores", Runtime.getRuntime().availableProcessors()
+ + "");
+ sysInfoMap.put("Maximum_Memory", Runtime.getRuntime().maxMemory() + "");
+ sysInfoMap.put("Free_Memory", Runtime.getRuntime().freeMemory() + "");
+
+ // Gather the selected system props
+ Properties sysprops = System.getProperties();
+ String propName;
+ for (Iterator<Entry<Object, Object>> it = sysprops.entrySet()
+ .iterator(); it.hasNext();)
+ {
+ Entry<Object, Object> en = it.next();
+ propName = en.getKey().toString();
+ if (sysProps.indexOf(propName) >= 0)
+ {
+ sysInfoMap.put(propName, en.getValue().toString());
+ }
+ }
+
+ return sysInfoMap;
+
+ }
+
+}
diff --git a/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/HttpPoster.java b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/HttpPoster.java
new file mode 100644
index 0000000000..d27980be05
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/HttpPoster.java
@@ -0,0 +1,130 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.info.util;
+
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.InetAddress;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.UnknownHostException;
+
+/**
+ *
+ * An simple Http post class for qpid info service
+ */
+public class HttpPoster implements Runnable
+{
+ private final String _url;
+
+ private final Hashtable<String, String> _header;
+
+ private final List<String> _response = new ArrayList<String>();
+
+ private final StringBuffer _buf;
+
+ /**
+ * Constructor
+ *
+ * @param props Properties containing the URL
+ * @param buf Buffer containing the message to be posted
+ */
+ public HttpPoster(Properties props, StringBuffer buf)
+ {
+ _buf = buf;
+ if (null != props)
+ {
+ _url = props.getProperty("http.url");
+ _header = new Hashtable<String, String>();
+ try
+ {
+ String hostname = InetAddress.getLocalHost().getHostName();
+ _header.put("hostname", hostname);
+ }
+ catch (UnknownHostException e)
+ {
+ // Silently ignoring the error ;)
+ }
+ }
+ else
+ {
+ _url = null;
+ _header = null;
+ }
+ }
+
+ /** Posts the message from the _buf StringBuffer to the http server */
+ public void run()
+ {
+ if (null == _url)
+ {
+ return;
+ }
+ String line;
+ URL urlDest;
+ URLConnection urlConn;
+ try
+ {
+ urlDest = new URL(_url);
+ urlConn = urlDest.openConnection();
+ urlConn.setDoOutput(true);
+ urlConn.setUseCaches(false);
+ for (Iterator<String> it = _header.keySet().iterator(); it.hasNext();)
+ {
+ String prop = it.next();
+ urlConn.setRequestProperty(prop, _header.get(prop));
+ }
+ OutputStreamWriter wr =
+ new OutputStreamWriter(urlConn.getOutputStream());
+ wr.write(_buf.toString());
+ wr.flush();
+ // Get the response
+ BufferedReader rd = new BufferedReader(new InputStreamReader(
+ urlConn.getInputStream()));
+ while ((line = rd.readLine()) != null)
+ {
+ _response.add(line);
+ }
+ }
+ catch (Exception ex)
+ {
+ // Silently ignoring the error ;)
+ }
+ }
+
+ /**
+ * Retrieves the response from the http server
+ *
+ * @return List<String> response received from the http server
+ */
+ public List<String> get_response()
+ {
+ return _response;
+ }
+
+}
diff --git a/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/IniFileReader.java b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/IniFileReader.java
new file mode 100644
index 0000000000..60a025d322
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/IniFileReader.java
@@ -0,0 +1,193 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.info.util;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * This class is simple implementation of an ini file reader. It expects a
+ * file with the following structure:
+ *
+ * ; global values, can be overwritten in sections
+ * key1=value1
+ * key2=value2
+ *
+ * [Section1]
+ * key1=value1_new ; overwriting the global key1
+ * key3=value3
+ * key4=value4
+ *
+ * [Section2]
+ * key5=value5
+ * key6=value6
+ * key7=value7
+ *
+ * Note: Commentaries are preceded by ; or # and are supported throughout
+ * A commentary line at the end of section is interpreted as
+ * a section end marker
+ *
+ * A structure <String,Properties> (section name, associated properties)
+ * is generated as a result of processing the ini file.
+ */
+public class IniFileReader
+{
+ private final Map<String, Properties> _sections;
+
+ private final String COMMENT_SEMICOLON = ";";
+
+ private final String COMMENT_HASH = "#";
+
+ enum State
+ {
+ IN_SECTION, OFF_SECTION, GLOBAL
+ }
+
+ /*
+ * IniFileReader constructor
+ */
+
+ public IniFileReader()
+ {
+ _sections = new HashMap<String, Properties>();
+ }
+
+ /**
+ * Cleans up the after comments or the empty spaces/tabs surrounding the given string
+ *
+ * @param str The String to be cleaned
+ *
+ * @return String Cleanup Version
+ */
+ private String cleanUp(String str)
+ {
+ if (str.contains(COMMENT_SEMICOLON))
+ {
+ str = str.substring(0, str.indexOf(COMMENT_SEMICOLON));
+ }
+ if (str.contains(COMMENT_HASH))
+ {
+ str = str.substring(0, str.indexOf(COMMENT_HASH));
+ }
+ return str.trim();
+ }
+
+ /**
+ * Loads and parses the ini file with the full path specified in the argument
+ *
+ * @param fileName Full path to the ini file
+ *
+ * @throws IllegalArgumentException If the file cannot be processed
+ */
+ public void load(String fileName) throws IllegalArgumentException
+ {
+ if (!new File(fileName).isFile())
+ {
+ throw new IllegalArgumentException("File: " + fileName + " does not exist or cannot be read.");
+ }
+ State state = State.GLOBAL;
+ String line;
+ Properties sectionProps = new Properties();
+ String sectionName = "";
+ try
+ {
+ BufferedReader in = new BufferedReader(new FileReader(fileName));
+ while ((line = in.readLine()) != null)
+ {
+ String str = cleanUp(line);
+
+ // Did we get a section header?
+ if (str.startsWith("["))
+ {
+ if (!str.endsWith("]"))
+ {
+ // Index of 1 to skip '['
+ throw new IllegalArgumentException(str.substring(1)
+ + " is not closed");
+ }
+
+ // We encountered a new section header
+ if (state != State.IN_SECTION)
+ {
+ _sections.put(sectionName, sectionProps);
+ sectionProps = new Properties();
+ sectionName = str.replace("[", "").replace("]", "")
+ .trim();
+ state = State.IN_SECTION;
+ }
+ }
+
+ // Any other line tested separately, ignore if out of a section
+ // and add if in section
+ if (str.length() == 0)
+ {
+ // We encountered a commented or an empty line, both cases
+ // mean we are off the section
+ if (state == State.IN_SECTION)
+ {
+ _sections.put(sectionName, sectionProps);
+ state = State.OFF_SECTION;
+ }
+ }
+ else
+ {
+ // proper line, add it to the props
+ if (state != State.OFF_SECTION)
+ {
+ if (str.contains("="))
+ {
+ int ix = str.indexOf("=");
+ sectionProps.put(str.substring(0, ix).trim(), str
+ .substring(ix + 1).trim());
+ }
+ }
+ }
+ }
+ in.close();
+ }
+ catch (IOException e)
+ {
+ _sections.clear();
+ return;
+ }
+ if (state != State.OFF_SECTION)
+ {
+ _sections.put(sectionName, sectionProps);
+ }
+ }
+
+ /**
+ * Getter for the Sections Map
+ *
+ * @return Map<String,Properties> The parsed content of the ini file in this structure
+ */
+ public Map<String, Properties> getSections()
+ {
+ return _sections;
+ }
+
+}
diff --git a/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/SoapClient.java b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/SoapClient.java
new file mode 100644
index 0000000000..0f66085fc3
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/SoapClient.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.
+ *
+ */
+/**
+ *
+ * @author sorin
+ *
+ * An simple SOAP client for qpid info service
+ */
+package org.apache.qpid.info.util;
+
+import java.io.BufferedWriter;
+import java.io.OutputStreamWriter;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.util.HashMap;
+import java.util.Properties;
+
+public class SoapClient
+{
+
+ private final StringBuffer _xmlData;
+
+ private final Properties _destprops;
+
+ private final String _hostname;
+
+ private final int _port;
+
+ private final String _urlpath;
+
+ private final String _soapenvelope;
+
+ private final String _soapaction;
+
+ private final StringBuffer _soapMessage = new StringBuffer();
+
+
+ public SoapClient(HashMap<String, String> map, Properties destprops)
+ {
+ _destprops = destprops;
+ _hostname = (String) _destprops.get("soap.hostname");
+ _port = Integer.parseInt((String) _destprops.get("soap.port"));
+ _urlpath = (String) destprops.get("soap.path");
+ _soapenvelope = (String) destprops.get("soap.envelope");
+ _soapaction = (String) destprops.get("soap.action");
+ _xmlData = new StringBuffer(_soapenvelope);
+ replaceVariables(map);
+ }
+
+ public StringBuffer getXMLData()
+ {
+ return _xmlData;
+ }
+
+ public StringBuffer getSoapMessage() {
+ return _soapMessage;
+ }
+
+ public String getSoapEnvelope() {
+ return _soapenvelope;
+ }
+
+ /**
+ * Clears and sets new XML data
+ * @param sb the new data to set
+ */
+ public void setXMLData(StringBuffer sb)
+ {
+ _xmlData.delete(0, _xmlData.length());
+ _xmlData.append(sb);
+ }
+
+
+ public void replaceVariables(HashMap<String, String> vars)
+ {
+ int ix = 0;
+ for (String var : vars.keySet())
+ {
+ while ((ix = _xmlData.indexOf("@" + var.toUpperCase())) >= 0)
+ {
+ _xmlData.replace(ix, ix + 1 + var.length(), vars.get(var));
+ }
+ }
+ }
+
+ public void replaceVariables(Properties varProps)
+ {
+ if (varProps == null)
+ {
+ return;
+ }
+ int ix = 0;
+ for (Object var : varProps.keySet())
+ {
+ while ((ix = _xmlData.indexOf("@" + var)) >= 0)
+ {
+ _xmlData.replace(ix, ix + 1 + var.toString().length(), varProps
+ .get(var).toString());
+ }
+ }
+ }
+
+
+ public void sendSOAPMessage()
+ {
+
+ try
+ {
+ InetAddress addr = InetAddress.getByName(_hostname);
+ Socket sock = new Socket(addr, _port);
+ StringBuffer sb = new StringBuffer();
+ sb.append("POST " + _urlpath + " HTTP/1.1\r\n");
+ sb.append("Host: " + _hostname + ":" + _port + "\r\n");
+ sb.append("Content-Length: " + _xmlData.length() + "\r\n");
+ sb.append("Content-Type: text/xml; charset=\"utf-8\"\r\n");
+ sb.append("SOAPAction: \"urn:"+ _soapaction +"\"\r\n");
+ sb.append("User-Agent: Axis2\r\n");
+ sb.append("\r\n");
+ // Send header
+ BufferedWriter wr = new BufferedWriter(new OutputStreamWriter(sock
+ .getOutputStream(), "UTF-8"));
+ synchronized(_soapMessage) {
+ _soapMessage.setLength(0);
+ _soapMessage.append(sb);
+ _soapMessage.append(_xmlData);
+ }
+ // Send data
+ wr.write(_soapMessage.toString());
+ wr.flush();
+ wr.close();
+
+ } catch (Exception ex)
+ {
+ // Drop any exception
+ }
+ }
+}
diff --git a/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/XMLWriter.java b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/XMLWriter.java
new file mode 100644
index 0000000000..a266edae00
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/info/src/main/java/org/apache/qpid/info/util/XMLWriter.java
@@ -0,0 +1,100 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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.info.util;
+
+import java.util.Map;
+
+/**
+ *
+ * Naive and rudimentary XML writer
+ * It has methods to write the header, a tag with attributes
+ * and values. It escapes the XML special characters
+ */
+public class XMLWriter
+{
+
+ private final StringBuffer _sb;
+
+ private final String INDENT = " ";
+
+ public XMLWriter(StringBuffer sb)
+ {
+ _sb = sb;
+ }
+
+ public StringBuffer getXML()
+ {
+ return _sb;
+ }
+
+ public void writeXMLHeader()
+ {
+ _sb.append("<?xml version=\"1.0\"?>\n");
+ }
+
+ public void writeTag(String tagName, Map<String, String> attributes,
+ String value)
+ {
+ writeOpenTag(tagName, attributes);
+ writeValue(value);
+ writeCloseTag(tagName);
+ }
+
+ public void writeOpenTag(String tagName, Map<String, String> attributes)
+ {
+ _sb.append("<").append(tagName);
+ if (null == attributes)
+ {
+ _sb.append(">\n");
+ return;
+ }
+ for (String key : attributes.keySet())
+ {
+ _sb.append(" ").append(key + "=\"" + attributes.get(key) + "\"");
+ }
+ _sb.append(">\n");
+
+ }
+
+ private void writeValue(String val)
+ {
+ _sb.append(INDENT).append(escapeXML(val) + "\n");
+ }
+
+ public void writeCloseTag(String tagName)
+ {
+ _sb.append("</" + tagName + ">\n");
+ }
+
+ private String escapeXML(String xmlStr)
+ {
+ if (null == xmlStr)
+ return null;
+ xmlStr = xmlStr.replaceAll("&", "&amp;");
+ xmlStr = xmlStr.replace("<", "&lt;");
+ xmlStr = xmlStr.replace(">", "&gt;");
+ xmlStr = xmlStr.replace("\"", "&quot;");
+ xmlStr = xmlStr.replace("'", "&apos;");
+ return xmlStr;
+ }
+
+}
diff --git a/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/systest/InfoPluginTest.java b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/systest/InfoPluginTest.java
new file mode 100644
index 0000000000..156c9eb138
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/systest/InfoPluginTest.java
@@ -0,0 +1,276 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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.info.systest;
+
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class InfoPluginTest extends QpidBrokerTestCase
+{
+ private String QPID_HOME = null;
+
+ private ServerSocket _server = null;
+
+ private int _port;
+
+ private static final String CR = System.getProperty("line.separator");
+
+ private static final String FS = File.separator;
+
+ private final String _cfgRelPath = "etc" + FS + "qpidinfo.ini";
+
+ private File _tmpCfgFile;
+
+ private final String _soapEnvelopeHead = "<?xml version=\"1.0\"?><soap:Envelope xmlns:soap=\"http://www.w3.org/2001/12/soap-envelope\" soap:encodingStyle=\"http://www.w3.org/2001/12/soap-encoding\">";
+
+ private final String _soapEnvelopeTail = "</soap:Envelope>";
+
+ private String _soapMessage1 = "@ACTION" + "-" + "@VERSION";
+
+ private String _soapMessage2 = "@VERSION" + "-" + "@ACTION";
+
+ private CountDownLatch _latch = new CountDownLatch(2);
+
+ final List<List<String>> _recv = new ArrayList<List<String>>();
+
+ Thread _socketAcceptor;
+
+ public void setUp() throws Exception
+ {
+ QPID_HOME = System.getProperty("QPID_HOME");
+ if (QPID_HOME != null)
+ {
+ System.out.println("QPID_HOME=" + QPID_HOME);
+ }
+ else
+ {
+ fail("QPID_HOME not set");
+ }
+
+ startSoapServer();
+ // Must start the server first to identify a free port.
+ createConfigFile();
+ }
+
+ public void tearDown() throws Exception
+ {
+ System.out.println("*** Stopping socket server...");
+ _socketAcceptor.join(2000);
+
+ System.out.println("*** Deleting the config file...");
+ if (_tmpCfgFile.isFile())
+ {
+ _tmpCfgFile.delete();
+ }
+ super.tearDown();
+ }
+
+ private void createConfigFile()
+ {
+ try
+ {
+ _tmpCfgFile = new File(QPID_HOME + FS + _cfgRelPath);
+ _tmpCfgFile.deleteOnExit();
+ if (_tmpCfgFile.isFile())
+ {
+ _tmpCfgFile.delete();
+ }
+ assertTrue("Unable to create file.", _tmpCfgFile.createNewFile());
+ assertTrue(_tmpCfgFile.isFile());
+ FileWriter fwriter = new FileWriter(_tmpCfgFile);
+ BufferedWriter writer = new BufferedWriter(fwriter);
+ writer.write("protocol=soap");
+ writer.write(CR);
+ writer.write("soap.hostname=localhost");
+ writer.write(CR);
+ writer.write("soap.port=" + _port);
+ writer.write(CR);
+ writer.write(CR);
+ writer.write("[MSG1]");
+ writer.write(CR);
+ writer.write("soap.path=/info1");
+ writer.write(CR);
+ writer.write("soap.action=submitinfo1");
+ writer.write(CR);
+ writer.write("soap.envelope=" + _soapEnvelopeHead + _soapMessage1
+ + _soapEnvelopeTail);
+ writer.write(CR);
+ writer.write(CR);
+ writer.write("[MSG2]");
+ writer.write(CR);
+ writer.write("soap.path=/info2");
+ writer.write(CR);
+ writer.write("soap.action=submitinfo2");
+ writer.write(CR);
+ writer.write("soap.envelope=" + _soapEnvelopeHead + _soapMessage2
+ + _soapEnvelopeTail);
+ writer.write(CR);
+ writer.write(CR);
+ writer.close();
+ assertTrue("Config file size is zero", _tmpCfgFile.length() > 0);
+ }
+ catch (IOException e)
+ {
+ fail("Unable to create the qpidinfo.properties due to: "
+ + e.getMessage());
+ }
+ }
+
+ private void startSoapServer() throws Exception
+ {
+ try
+ {
+ _server = new ServerSocket(0);
+ _port = _server.getLocalPort();
+ assertTrue("Server not yet bound.", _port != -1);
+
+ assertNotNull("SocketServer is null", _server);
+ }
+ catch (Exception ex)
+ {
+ fail("Unable to start the socket server due to: " + ex.getMessage());
+ }
+
+ _socketAcceptor = new Thread()
+ {
+ public void run()
+ {
+ while (true)
+ {
+ try
+ {
+ Socket socket = _server.accept();
+ new ConnectionHandler(socket);
+ }
+ catch (IOException e)
+ {
+ fail("Error opening the socket in accept mode");
+ }
+ }
+ }
+ };
+ _socketAcceptor.start();
+ System.out.println("*** Socket server started...");
+ }
+
+ class ConnectionHandler implements Runnable
+ {
+ private Socket _socket;
+
+ public ConnectionHandler(Socket socket)
+ {
+ _socket = socket;
+ Thread t = new Thread(this);
+ t.start();
+ }
+
+ public void run()
+ {
+ System.out.println("*** Connection handler running...");
+ List<String> buf = new ArrayList<String>();
+ String line;
+ try
+ {
+ BufferedReader br = new BufferedReader(new InputStreamReader(
+ _socket.getInputStream()));
+ assertNotNull(br);
+ while ((line = br.readLine()) != null)
+ {
+ buf.add(line);
+ }
+ br.close();
+ System.out.println("*** Received buffer: " + buf);
+ System.out.println("*** Latch countdown");
+ _latch.countDown();
+ synchronized (_recv)
+ {
+ _recv.add(buf);
+ }
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace();
+ fail("Exception while reading from the socket");
+ }
+
+ }
+
+ }
+
+ public void testInfoPlugin() throws Exception
+ {
+ //Start the broker
+ super.setUp();
+ if (!_latch.await(10, TimeUnit.SECONDS))
+ {
+ fail("Timeout awaiting for the latch, upon startup");
+ }
+
+ validateResponses("STARTUP");
+
+ _recv.clear();
+ _latch = new CountDownLatch(2);
+
+ stopBroker();
+
+ if (!_latch.await(10, TimeUnit.SECONDS))
+ {
+ fail("Timeout awaiting for the latch, upon shutdown");
+ }
+
+ validateResponses("SHUTDOWN");
+
+ }
+
+ /**
+ * Check the responses from the server to ensure they contain the required messages.
+ * @param action String to match for the SHUTDOWN or STARTUP action.
+ */
+ private void validateResponses(String action)
+ {
+ assertTrue("Received less than 2 messages", _recv.size() > 1);
+
+ // Message 1
+ assertTrue("Message does not contain Host: localhost:" + _port + "\n" + _recv.get(0), _recv.get(0).contains("Host: localhost:" + _port));
+ assertTrue("Message does not contain: User-Agent: Axis2 " + "\n" + _recv.get(0), _recv.get(0).contains("User-Agent: Axis2"));
+ assertTrue("Message does not contain: SOAPAction: \"urn:submitinfo\"" + "\n" + _recv.get(0).get(4), _recv.get(0).get(4).startsWith("SOAPAction: \"urn:submitinfo"));
+ assertTrue("Message does not contain '" + action + "' in the soap envelope" + "\n" + _recv.get(0).get(7), _recv.get(0).get(7).contains(action));
+
+ // Message 2
+ assertTrue("Message does not contain Host: localhost:" + _port + "\n" + _recv.get(1), _recv.get(1).contains("Host: localhost:" + _port));
+ assertTrue("Message does not contain: User-Agent: Axis2 " + "\n" + _recv.get(1), _recv.get(1).contains("User-Agent: Axis2"));
+ assertTrue("Message does not contain: SOAPAction: \"urn:submitinfo\"" + "\n" + _recv.get(1).get(4), _recv.get(1).get(4).startsWith("SOAPAction: \"urn:submitinfo"));
+ assertTrue("Message does not contain '" + action + "' in the soap envelope" + "\n" + _recv.get(1).get(7), _recv.get(1).get(7).contains(action));
+ }
+
+}
diff --git a/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/HttpPosterTest.java b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/HttpPosterTest.java
new file mode 100644
index 0000000000..4f76fea8ef
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/HttpPosterTest.java
@@ -0,0 +1,107 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.info.test;
+
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.qpid.info.util.HttpPoster;
+import org.mortbay.jetty.testing.ServletTester;
+
+import junit.framework.TestCase;
+
+/*
+ * This test verifies that the plugin posts correctly to a webserver
+ * We use an embedded jetty container to mimic the webserver
+ */
+public class HttpPosterTest extends TestCase
+{
+
+ private ServletTester tester;
+
+ private String baseURL;
+
+ private final String contextPath = "/info";
+
+ /*
+ * This method generates a dummy HttpPoster with a dummy body containing a
+ * single line. The url we are posting to can be controlled by the parameter
+ * url
+ *
+ * @param url
+ */
+ private HttpPoster getHttpPoster(String url)
+ {
+ StringBuffer sb = new StringBuffer("test=TEST");
+ Properties props = new Properties();
+ props.put("http.url", url);
+ return new HttpPoster(props, sb);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#setUp()
+ */
+ protected void setUp() throws Exception
+ {
+ tester = new ServletTester();
+ tester.setContextPath("/");
+ tester.addServlet(InfoServlet.class, contextPath);
+ baseURL = tester.createSocketConnector(true);
+ tester.start();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see junit.framework.TestCase#tearDown()
+ */
+ protected void tearDown() throws Exception
+ {
+ super.tearDown();
+ tester.stop();
+ }
+
+ /*
+ * This test is posting a string to an embedded Jetty Servlet and captures
+ * the response message. If the servlet receives the message ok, it will
+ * print Ok. A failure test is following where we post to a non-existent URL
+ */
+ public void testHttpPoster() throws Exception
+ {
+ // Test HttpPoster posts correctly to the servlet
+ HttpPoster hp = getHttpPoster(baseURL + contextPath);
+ assertNotNull(hp);
+ hp.run();
+ List<String> response = hp.get_response();
+ assertTrue(response.size() > 0);
+ assertEquals("OK <br>", response.get(0).toString());
+
+ // Failure Test
+ hp = getHttpPoster("http://localhost/nonexistent");
+ hp.run();
+ response = hp.get_response();
+ assertTrue(response.size() == 0);
+
+ }
+
+}
diff --git a/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/InfoServiceImplTest.java b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/InfoServiceImplTest.java
new file mode 100644
index 0000000000..9f359582a5
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/InfoServiceImplTest.java
@@ -0,0 +1,63 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.info.test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.qpid.info.Info;
+import org.apache.qpid.info.InfoServiceImpl;
+
+import junit.framework.TestCase;
+
+/*
+ * This test verifies the invoke() method for the info service making sure that the parameters are returned
+ */
+public class InfoServiceImplTest extends TestCase
+{
+
+ InfoServiceImpl _isi = null;
+
+ @SuppressWarnings("unchecked")
+ public void testInvoke()
+ {
+ _isi = new InfoServiceImpl();
+ assertNotNull(_isi);
+ Info<? extends Map<String, String>> info = (Info<? extends Map<String, String>>) _isi
+ .invoke("START");
+ assertNotNull(info);
+ Properties props = info.toProps();
+ assertNotNull(props);
+ List<String> infoProps = Arrays.asList("java.class.path",
+ "java.vm.name", "java.class.version", "os.arch", "os.name",
+ "os.version", "sun.arch.data.model", "user.dir", "user.name",
+ "user.timezone");
+ for (String tag : infoProps)
+ {
+ assertNotNull("Info.toProps() does not have the property: " + tag,
+ props.getProperty(tag));
+ }
+ }
+
+}
diff --git a/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/InfoServlet.java b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/InfoServlet.java
new file mode 100644
index 0000000000..6b12a2d80c
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/InfoServlet.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.info.test;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.GenericServlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/*
+ * This is a servlet used by the embedded Jetty to be able to receive http post
+ * from the info plugin
+ */
+
+public class InfoServlet extends GenericServlet
+{
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void service(ServletRequest request, ServletResponse response)
+ throws ServletException, IOException
+ {
+ String line;
+ BufferedReader in = request.getReader();
+ while ((line = in.readLine()) != null)
+ {
+ System.out.println(line);
+ }
+ response.setContentType("text/html");
+ PrintWriter out = response.getWriter();
+ out.println("OK <br>\n");
+ System.out.println("ServletResponse: OK");
+ }
+
+} \ No newline at end of file
diff --git a/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/InfoTest.java b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/InfoTest.java
new file mode 100644
index 0000000000..bb4965ef1e
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/InfoTest.java
@@ -0,0 +1,112 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.info.test;
+
+import java.util.HashMap;
+import java.util.Properties;
+import junit.framework.TestCase;
+import org.apache.qpid.info.Info;
+
+/*
+ * This test verifies the toString(), toProps(), toXML() and toStringBuffer() methods of the Info object
+ *
+ */
+public class InfoTest extends TestCase
+{
+ private HashMap<String, String> _infoPayLoad = null;
+
+ private Info<HashMap<String, String>> _info = null;
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ _infoPayLoad = new HashMap<String, String>();
+ _infoPayLoad.put("test", "Test");
+ _info = new Info<HashMap<String, String>>(_infoPayLoad);
+ }
+
+ /*
+ * Test the conversion toString() of the Info object
+ */
+ public void testToString()
+ {
+ assertNotNull("toString() returned null", _info.toString());
+ assertEquals("toString() did not return the proper string",
+ "test=Test\n", _info.toString());
+ }
+
+ /*
+ * Test the conversion toProps() of the Info object
+ */
+ public void testToProps()
+ {
+ Properties props = new Properties();
+ props.put("test", "Test");
+ assertNotNull("toProperties() returned null", _info.toProps());
+ assertEquals("toProperties not returned the proper object", props, _info
+ .toProps());
+ }
+
+ /*
+ * Test the conversion toStringBuffer() of the Info object
+ */
+ public void testToStringBuffer()
+ {
+ StringBuffer sb = new StringBuffer("test=Test\n");
+ assertNotNull(_info.toStringBuffer());
+ assertEquals(sb.toString(), _info.toStringBuffer().toString());
+ }
+
+ /*
+ * Test conversion toXML() of the info object
+ */
+ public void testToXML()
+ {
+ String INDENT = " ";
+ StringBuffer sb = new StringBuffer();
+ sb.append("<?xml version=\"1.0\"?>\n");
+ sb.append("<qpidinfo>\n");
+ sb.append("<test>\n");
+ sb.append(INDENT + "Test\n");
+ sb.append("</test>\n");
+ sb.append("</qpidinfo>\n");
+ assertEquals("toString() does not return the proper string", _info
+ .toXML().toString(), sb.toString());
+ }
+
+ /*
+ * Test the conversion toMap() of the Info object
+ */
+ public void testToMap()
+ {
+ HashMap<String, String> thm = _info.toMap();
+ assertFalse("toMap() returned empty map", thm.isEmpty());
+ assertEquals("testToMap did not returned 1", 1, thm.size());
+ assertTrue("toMap() returned a map not containing expected key: test",
+ thm.containsKey("test"));
+ assertTrue(
+ "toMap() returned a map not containing the value for key test: Test",
+ thm.containsValue("Test"));
+
+ }
+
+}
diff --git a/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/IniFileReaderTest.java b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/IniFileReaderTest.java
new file mode 100644
index 0000000000..77ecaa2176
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/IniFileReaderTest.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.info.test;
+
+import junit.framework.TestCase;
+import org.apache.qpid.info.util.IniFileReader;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Test the Loading of the ini file reader by first writing
+ * out a correct ini file.
+ */
+public class IniFileReaderTest extends TestCase
+{
+
+ public void testLoad()
+ {
+ IniFileReader ifr = new IniFileReader();
+ File iniFile = null;
+ try
+ {
+ iniFile = File.createTempFile("temp", "ini");
+ iniFile.deleteOnExit();
+ BufferedWriter writer = new BufferedWriter(new FileWriter(iniFile));
+ writer.write("# Global Comment1\n");
+ writer.write("globalprop1=globalval1\n");
+ writer.write("globalprop2=globalval2\n");
+ writer.write("\n");
+ writer.write("[Section1] # Comment on Section\n");
+ writer.write("key1=val1 # Comment on Value\n");
+ writer.write("key2=val2\n");
+ writer.write("\n");
+ writer.write("#Section2 Comment\n");
+ writer.write("[Section2]\n");
+ writer.write("key3=val3\n");
+ writer.write("key4=val4\n");
+ writer.write("key5=val5\n");
+ writer.write("\n");
+ writer.write("[Section3]\n");
+ writer.write("key6=val6\n");
+ writer.write("key7=val7\n");
+ writer.write("\n");
+ writer.close();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ fail("Unable to create temporary File");
+ }
+ ifr.load(iniFile.getAbsolutePath());
+ Map<String, Properties> sections = ifr.getSections();
+ assertNotNull("Sections not null", sections);
+ assertEquals("Have 4 sections", sections.keySet().size(), 4);
+ assertTrue("Get globalprop1", sections.get("").getProperty("globalprop1").equals("globalval1"));
+ assertTrue("Get globalprop2", sections.get("").getProperty("globalprop2").equals("globalval2"));
+ assertNotNull("Section1 not null", sections.get("Section1"));
+ assertEquals("Section1 has 2 properties", sections.get("Section1").size(), 2);
+ assertTrue("Section1 key1 has val1", sections.get("Section1").getProperty("key1").equals("val1"));
+ assertTrue("Section1 key2 has val2", sections.get("Section1").getProperty("key2").equals("val2"));
+ assertEquals("Section2 has 3 properties", sections.get("Section2").size(), 3);
+ assertTrue("Section2 key3 has val3", sections.get("Section2").getProperty("key3").equals("val3"));
+ assertTrue("Section2 key4 has val4", sections.get("Section2").getProperty("key4").equals("val4"));
+ assertTrue("Section2 key5 has val5", sections.get("Section2").getProperty("key5").equals("val5"));
+ assertEquals("Section3 has 2 properties", sections.get("Section3").size(), 2);
+ assertTrue("Section3 key6 has val6", sections.get("Section3").getProperty("key6").equals("val6"));
+ assertTrue("Section3 key7 has val7", sections.get("Section3").getProperty("key7").equals("val7"));
+ }
+
+ /**
+ * Test to ensure that the loading of a file with an unclosed section header
+ * fails to parse.
+ *
+ * Section needs to be fully enclosed in square brackets '[<name>]'
+ */
+ public void testIncompleteSection1Load()
+ {
+ IniFileReader ifr = new IniFileReader();
+ File iniFile = null;
+ try
+ {
+ iniFile = File.createTempFile(getName(), "ini");
+ iniFile.deleteOnExit();
+ BufferedWriter writer = new BufferedWriter(new FileWriter(iniFile));
+ writer.write("# Global Comment1\n");
+ writer.write("globalprop1=globalval1\n");
+ writer.write("globalprop2=globalval2\n");
+ writer.write("\n");
+ writer.write("[Section1\n"); // Note '[Section1' not complete
+ writer.write("key1=val1\n");
+ writer.write("key2=val2\n");
+ writer.write("\n");
+ writer.close();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ fail("Unable to create temporary File");
+ }
+ try
+ {
+ ifr.load(iniFile.getAbsolutePath());
+ fail("File should fail to parse");
+ }
+ catch (IllegalArgumentException iae)
+ {
+ assertEquals("Incorrect Exception", "Section1 is not closed", iae.getMessage());
+ }
+
+ }
+
+}
diff --git a/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/SoapClientTest.java b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/SoapClientTest.java
new file mode 100644
index 0000000000..a3d993a39f
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/SoapClientTest.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.info.test;
+
+import junit.framework.TestCase;
+import org.apache.qpid.info.util.SoapClient;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Properties;
+
+public class SoapClientTest extends TestCase
+{
+
+ private int _port;
+
+ private final String _hostName = "localhost";
+
+ private final String _urlPath = "/testSoap";
+
+ private ServerSocket _server = null;
+
+ /*
+ * Generate a soap client from a custom URL, hostname, port and soap context
+ * path to be derived
+ */
+ private SoapClient getSoapClient()
+ {
+ Properties destprops = new Properties();
+ destprops.setProperty("soap.hostname", _hostName);
+ destprops.setProperty("soap.port", _port + "");
+ destprops.setProperty("soap.urlpath", _urlPath);
+ destprops.setProperty("soap.envelope", "<ip>@IP</ip>");
+ destprops.setProperty("soap.action", "send");
+ HashMap<String, String> soapmap = new HashMap<String, String>();
+ soapmap.put("IP", "127.0.0.1");
+ return new SoapClient(soapmap, destprops);
+ }
+
+ /*
+ * A connection handler class that verifies the correct message is received
+ *
+ */
+ class ConnectionHandler implements Runnable
+ {
+ private Socket socket;
+
+ public ConnectionHandler(Socket socket)
+ {
+ this.socket = socket;
+ Thread t = new Thread(this);
+ t.start();
+ }
+
+ public void run()
+ {
+ String line;
+ final List<String> response = new ArrayList<String>();
+ try
+ {
+ BufferedReader br = new BufferedReader(new InputStreamReader(
+ socket.getInputStream()));
+ assertNotNull(br);
+ while ((line = br.readLine()) != null)
+ {
+ response.add(line);
+ }
+ br.close();
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace();
+ fail("Exception while reading from the socket");
+ }
+ assertTrue(response.contains("<ip>127.0.0.1</ip>"));
+ assertTrue(response.contains("SOAPAction: \"urn:send\""));
+ assertTrue(response
+ .contains("Content-Type: text/xml; charset=\"utf-8\""));
+ assertTrue(response.contains("Host: localhost" + _port));
+ assertTrue(response.contains("User-Agent: Axis2"));
+ }
+
+ }
+
+ /*
+ * Test that the SOAP client sends the expected data to the socket We mock a
+ * simple SOAP envelope: <ip>127.0.0.1</ip>
+ */
+ public void testSoapClient() throws Exception
+ {
+ //
+ try
+ {
+ _server = new ServerSocket(0);
+ _port = _server.getLocalPort();
+ assertTrue("Server is not yet bound to a port", _port != -1);
+ assertNotNull(_server);
+ }
+ catch (Exception ex)
+ {
+ ex.printStackTrace();
+ fail("Unable to start the socket server");
+ }
+
+ Thread _socketAcceptor = new Thread()
+ {
+ public void run()
+ {
+ try
+ {
+ Socket socket = _server.accept();
+ new ConnectionHandler(socket);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ };
+ _socketAcceptor.start();
+ // Sleep for 1 second to allow the ServerSocket readiness
+ Thread.sleep(1000);
+ SoapClient sc = getSoapClient();
+ assertNotNull(sc);
+ sc.sendSOAPMessage();
+
+ _socketAcceptor.join(2000);
+
+ assertFalse("Socket Acceptor not stopped.", _socketAcceptor.isAlive());
+ }
+
+ /**
+ * Test SoapClient correctly clears previously set values
+ */
+ public void testSoapClientXMLData()
+ {
+ SoapClient sc = getSoapClient();
+
+ StringBuffer initial = new StringBuffer("Initial Value");
+
+ sc.setXMLData(initial);
+
+ assertEquals("getXMLData is not set with initial value",
+ initial.toString(), sc.getXMLData().toString());
+
+
+ StringBuffer sb = new StringBuffer("<?xml version=\"1.0\"?><ip=@IP><port=@PORT>");
+ sc.setXMLData(sb);
+ assertEquals(sc.getXMLData().length(), sb.length());
+ assertEquals("getXMLData does not return the same StringBuffer set by setXMLData",
+ sb.toString(), sc.getXMLData().toString());
+ }
+
+ /**
+ * Test that variable replacement is performed on the soap.envelope.
+ * Create dummy soap message and validate that the variable have been replaced.
+ */
+ public void testReplaceVariablesMap()
+ {
+ Properties props = new Properties();
+ // Add dummy values as required to create a soap message
+ props.setProperty("soap.hostname", _hostName);
+ props.setProperty("soap.port", "0");
+ props.setProperty("soap.urlpath", _urlPath);
+ props.setProperty("soap.action", "send");
+
+ /// The envelope is what we care about
+ props.setProperty("soap.envelope", "<addr>@IP:@PORT</addr>");
+ HashMap<String, String> soapmap = new HashMap<String, String>();
+
+ /// Variables that should be replaced.
+ final String ip = "127.0.0.1";
+ soapmap.put("IP", ip);
+ final String port = "8080";
+ soapmap.put("PORT", port);
+
+ SoapClient sc = new SoapClient(soapmap, props);
+ assertNotNull("SoapClient is null", sc);
+
+ assertTrue("Replace variables did not work as expected", ("<addr>" + ip + ":" + port + "</addr>").equals(sc.getXMLData().toString()));
+ }
+
+}
diff --git a/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/SystemInfoTest.java b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/SystemInfoTest.java
new file mode 100644
index 0000000000..6cb8e3a90a
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/SystemInfoTest.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.info.test;
+
+import junit.framework.TestCase;
+import org.apache.qpid.info.SystemInfo;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/** Test the SystemInfo component */
+public class SystemInfoTest extends TestCase
+{
+
+ /**
+ * Ensure the list of required properties are returned by the
+ * SystemInfo.getInfo call
+ */
+ public void testGetInfo()
+ {
+ Map<String, String> sysInfoMap = SystemInfo.getInfo();
+ assertNotNull("SystemInfo.getInfo() returned null", sysInfoMap);
+ List<String> sysInfoProps = Arrays.asList(
+ "java.class.path",
+ "java.vm.name", "java.class.version", "os.arch", "os.name",
+ "os.version", "sun.arch.data.model", "user.dir", "user.name",
+ "user.timezone", "hostname", "ip", "CPUCores", "Maximum_Memory",
+ "Free_Memory");
+
+ for (String tag : sysInfoProps)
+ {
+ assertNotNull("Map does not contain the tag: " + tag, sysInfoMap.get(tag));
+ }
+ }
+
+} \ No newline at end of file
diff --git a/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/XMLWriterTest.java b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/XMLWriterTest.java
new file mode 100644
index 0000000000..f352226361
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/info/src/test/java/org/apache/qpid/info/test/XMLWriterTest.java
@@ -0,0 +1,132 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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.info.test;
+
+import junit.framework.TestCase;
+import org.apache.qpid.info.util.XMLWriter;
+
+import java.util.HashMap;
+
+/*
+ * This test verifies the XML writer custom class operations
+ */
+
+public class XMLWriterTest extends TestCase
+{
+
+ private XMLWriter xw = null;
+
+ /** Test constructor arg is returned via getXML() */
+ public void testXMLWriter()
+ {
+ StringBuffer input = new StringBuffer("Test");
+ xw = new XMLWriter(input);
+ assertNotNull("XMLWriter could not instantiate", xw);
+ assertEquals("XMLWriter.getXML() failed", input, xw.getXML());
+ }
+
+ /** Test header generation */
+ public void testWriteXMLHeader()
+ {
+ xw = new XMLWriter(new StringBuffer());
+ assertNotNull(xw);
+ xw.writeXMLHeader();
+ assertEquals("XMLWriter.writeXMLHeader(...) failed", "<?xml version=\"1.0\"?>\n", xw.getXML().toString());
+ }
+
+ /** Test tag created and written correctly */
+ public void testWriteTag()
+ {
+ String INDENT = " ";
+ xw = new XMLWriter(new StringBuffer());
+ assertNotNull("XMLWriter could not instantiate", xw);
+ xw.writeTag("test", new HashMap<String, String>(), "TEST");
+ assertEquals("XMLWriter.writeTag(...) failed", "<test>\n" + INDENT + "TEST\n" + "</test>\n", xw.getXML()
+ .toString());
+ }
+
+ /** Test tag created and written correctly */
+ public void testWriteTagWithNullAttribute()
+ {
+ String INDENT = " ";
+ xw = new XMLWriter(new StringBuffer());
+ assertNotNull("XMLWriter could not instantiate", xw);
+ xw.writeTag("test", null, "TEST");
+ assertEquals("XMLWriter.writeTag(...) failed", "<test>\n" + INDENT + "TEST\n" + "</test>\n", xw.getXML()
+ .toString());
+ }
+
+ /** Test tag created and written correctly with attribute */
+ public void testWriteTagWithAttribute()
+ {
+ String INDENT = " ";
+ xw = new XMLWriter(new StringBuffer());
+ assertNotNull("XMLWriter could not instantiate", xw);
+ HashMap<String, String> attr = new HashMap<String, String>();
+ attr.put("id", "1");
+
+ xw.writeTag("test", attr, "TEST");
+ assertEquals("XMLWriter.writeTag(...) failed", "<test id=\"1\">\n" + INDENT + "TEST\n" + "</test>\n", xw.getXML()
+ .toString());
+ }
+
+ /** Test open tag with an empty attribute map. Just creates an open tag */
+ public void testWriteOpenTag()
+ {
+ xw = new XMLWriter(new StringBuffer());
+ assertNotNull(xw);
+ HashMap<String, String> attr = new HashMap<String, String>();
+ xw.writeOpenTag("test", attr);
+ assertEquals("XMLWriter.writeOpenTag(...) failed", "<test>\n", xw.getXML().toString());
+ }
+
+ /** Test open tag with a null attribute map. Just creates an open tag */
+ public void testNullAtrributeOnTag()
+ {
+ xw = new XMLWriter(new StringBuffer());
+ assertNotNull(xw);
+ xw.writeOpenTag("test", null);
+ assertEquals("XMLWriter.writeOpenTag(...) failed", "<test>\n", xw.getXML().toString());
+ }
+
+ /** Test that setting an attribute value on the tag is correctly outputted. */
+ public void testAtrributeOnTag()
+ {
+ xw = new XMLWriter(new StringBuffer());
+ assertNotNull(xw);
+ HashMap<String, String> attr = new HashMap<String, String>();
+
+ attr.put("id", "1");
+ xw.writeOpenTag("test1", attr);
+ assertEquals("XMLWriter.writeOpenTag(...) failed", "<test1 id=\"1\">\n", xw.getXML().toString());
+ }
+
+ /** Test Close Tag is correctly written */
+ public void testWriteCloseTag()
+ {
+ xw = new XMLWriter(new StringBuffer());
+ assertNotNull(xw);
+ xw.writeCloseTag("test");
+ assertEquals("</test>\n", xw.getXML().toString());
+ }
+
+}
diff --git a/qpid/java/broker-plugins/experimental/shutdown/MANIFEST.MF b/qpid/java/broker-plugins/experimental/shutdown/MANIFEST.MF
new file mode 100644
index 0000000000..49e90c6aad
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/shutdown/MANIFEST.MF
@@ -0,0 +1,15 @@
+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
+Bundle-RequiredExecutionEnvironment: J2SE-1.5
+Bundle-ActivationPolicy: lazy
+
diff --git a/qpid/java/broker-plugins/experimental/shutdown/build.xml b/qpid/java/broker-plugins/experimental/shutdown/build.xml
new file mode 100644
index 0000000000..ec4fce374e
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/shutdown/build.xml
@@ -0,0 +1,32 @@
+<!--
+ -
+ - Licensed to the Apache Software Foundation (ASF) under one
+ - or more contributor license agreements. See the NOTICE file
+ - distributed with this work for additional information
+ - regarding copyright ownership. The ASF licenses this file
+ - to you under the Apache License, Version 2.0 (the
+ - "License"); you may not use this file except in compliance
+ - with the License. You may obtain a copy of the License at
+ -
+ - http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing,
+ - software distributed under the License is distributed on an
+ - "AS IS" BASIS, WITHOUT 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="AMQ Broker Shutdown Plugin" default="build">
+
+ <property name="module.depends" value="common broker 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"/>
+
+ <target name="bundle" depends="bundle-tasks"/>
+
+</project>
diff --git a/qpid/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/Activator.java b/qpid/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/Activator.java
new file mode 100644
index 0000000000..ad5e7707b6
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/Activator.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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 java.lang.management.ManagementFactory;
+
+import javax.management.InstanceNotFoundException;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+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 static final String SHUTDOWN_MBEAN_NAME = "org.apache.qpid:type=ShutdownMBean";
+
+ /** @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext) */
+ public void start(BundleContext ctx) throws Exception {
+ Shutdown shutdown = new Shutdown();
+ if (ctx != null)
+ {
+ ctx.registerService(ShutdownMBean.class.getName(), shutdown, null);
+ }
+
+ // MBean registration
+ MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ ObjectName name = new ObjectName(SHUTDOWN_MBEAN_NAME);
+ mbs.registerMBean(shutdown, name);
+
+ _logger.info("Shutdown plugin MBean registered");
+ }
+
+ /** @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) */
+ public void stop(BundleContext ctx) throws Exception
+ {
+ // Unregister MBean
+ MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+ ObjectName name = new ObjectName(SHUTDOWN_MBEAN_NAME);
+ try
+ {
+ mbs.unregisterMBean(name);
+ }
+ catch (InstanceNotFoundException e)
+ {
+ //ignore
+ }
+
+ _logger.info("Shutdown plugin MBean unregistered");
+ }
+}
diff --git a/qpid/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/Shutdown.java b/qpid/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/Shutdown.java
new file mode 100644
index 0000000000..9a6f85fe9c
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/Shutdown.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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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 java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Implementation of the JMX broker shutdown plugin.
+ */
+public class Shutdown implements ShutdownMBean
+{
+ private static final Logger _logger = Logger.getLogger(Shutdown.class);
+
+ private static final String FORMAT = "yyyyy/MM/dd hh:mm:ss";
+ private static final int THREAD_COUNT = 1;
+ private static final ScheduledExecutorService EXECUTOR = new ScheduledThreadPoolExecutor(THREAD_COUNT);
+
+ private final Runnable _shutdown = new SystemExiter();
+
+ /** @see ShutdownMBean#shutdown() */
+ public void shutdown()
+ {
+ _logger.info("Shutting down at user's request");
+ shutdownBroker(0);
+ }
+
+ /** @see ShutdownMBean#shutdown(long) */
+ public void shutdown(long delay)
+ {
+ _logger.info("Scheduled broker shutdown after " + delay + "ms");
+ shutdownBroker(delay);
+ }
+
+ /** @see ShutdownMBean#shutdownAt(String) */
+ public void shutdownAt(String when)
+ {
+ Date date;
+ DateFormat df = new SimpleDateFormat(FORMAT);
+ try
+ {
+ date = df.parse(when);
+ }
+ catch (ParseException e)
+ {
+ _logger.error("Invalid date \"" + when + "\": expecting " + FORMAT, e);
+ return;
+ }
+ _logger.info("Scheduled broker shutdown at " + when);
+ long now = System.currentTimeMillis();
+ long time = date.getTime();
+ if (time > now)
+ {
+ shutdownBroker(time - now);
+ }
+ else
+ {
+ shutdownBroker(0);
+ }
+ }
+
+ /**
+ * Submits the {@link SystemExiter} job to shutdown the broker.
+ */
+ private void shutdownBroker(long delay)
+ {
+ EXECUTOR.schedule(_shutdown, delay, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Shutting down the system in another thread to avoid JMX exceptions being thrown.
+ */
+ class SystemExiter implements Runnable
+ {
+ public void run()
+ {
+ System.exit(0);
+ }
+ }
+}
diff --git a/qpid/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/ShutdownMBean.java b/qpid/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/ShutdownMBean.java
new file mode 100644
index 0000000000..6294f869e9
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/shutdown/src/main/java/org/apache/qpid/shutdown/ShutdownMBean.java
@@ -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.
+ *
+ */
+package org.apache.qpid.shutdown;
+
+/**
+ * Shutdown plugin JMX MBean interface.
+ *
+ * Shuts the Qpid broker down via JMX.
+ */
+public interface ShutdownMBean
+{
+ /**
+ * Broker will be shut down immediately.
+ */
+ public void shutdown();
+
+ /**
+ * Broker will be shutdown after the specified delay
+ *
+ * @param delay the number of ms to wait
+ */
+ public void shutdown(long delay);
+
+ /**
+ * Broker will be shutdown at the specified date and time.
+ *
+ * @param when the date and time to shutdown
+ */
+ public void shutdownAt(String when);
+}
diff --git a/qpid/java/broker-plugins/experimental/shutdown/src/main/java/shutdown.bnd b/qpid/java/broker-plugins/experimental/shutdown/src/main/java/shutdown.bnd
new file mode 100755
index 0000000000..f49578ba8c
--- /dev/null
+++ b/qpid/java/broker-plugins/experimental/shutdown/src/main/java/shutdown.bnd
@@ -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.
+#
+
+ver: 0.11.0
+
+Bundle-SymbolicName: qpid-shutdown-plugin
+Bundle-Version: ${ver}
+Export-Package: *;version=${ver}
+Bundle-RequiredExecutionEnvironment: J2SE-1.5
diff --git a/qpid/java/broker-plugins/extras/MANIFEST.MF b/qpid/java/broker-plugins/extras/MANIFEST.MF
new file mode 100644
index 0000000000..f4ef6e8178
--- /dev/null
+++ b/qpid/java/broker-plugins/extras/MANIFEST.MF
@@ -0,0 +1,21 @@
+Bundle-ManifestVersion: 2
+Bundle-Name: Qpid Broker-Plugins Extras
+Bundle-SymbolicName: broker-plugins-extras
+Bundle-Description: Extra exchange types plugin for Qpid.
+Bundle-License: http://www.apache.org/licenses/LICENSE-2.0.txt
+Bundle-DocURL: http://www.apache.org/
+Bundle-Activator: org.apache.qpid.extras.Activator
+Private-Package: org.apache.qpid.extras,
+ org.apache.qpid.extras.exchanges.diagnostic,
+ org.apache.qpid.extras.exchanges.example
+Import-Package: org.apache.qpid,
+ org.apache.qpid.framing,
+ org.apache.qpid.junit.extensions.util,
+ org.apache.qpid.protocol,
+ org.apache.qpid.server.exchange,
+ org.apache.qpid.server.management,
+ org.apache.qpid.server.queue,
+ org.apache.qpid.server.virtualhost,
+ javax.management;version=1.0.0,
+ javax.management.openmbean;version=1.0.0,
+ org.osgi.framework;version=1.3
diff --git a/qpid/java/broker-plugins/extras/build.xml b/qpid/java/broker-plugins/extras/build.xml
new file mode 100644
index 0000000000..7c1d0be49f
--- /dev/null
+++ b/qpid/java/broker-plugins/extras/build.xml
@@ -0,0 +1,31 @@
+<!--
+ -
+ - Licensed to the Apache Software Foundation (ASF) under one
+nn - or more contributor license agreements. See the NOTICE file
+ -n distributed with this work for additional information
+ - regarding copyright ownership. The ASF licenses this file
+ - to you under the Apache License, Version 2.0 (the
+ - "License"); you may not use this file except in compliance
+ - with the License. You may obtain a copy of the License at
+ -
+ - http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing,
+ - software distributed under the License is distributed on an
+ - "AS IS" BASIS, WITHOUT 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 Extras" default="build">
+ <property name="module.depends" value="common client management/common broker broker-plugins"/>
+ <property name="module.test.depends" value="test broker/test common/test"/>
+ <property name="module.manifest" value="MANIFEST.MF"/>
+ <property name="module.plugin" value="true"/>
+
+ <import file="../../module.xml"/>
+
+
+ <target name="bundle" depends="bundle-tasks" />
+</project>
diff --git a/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/Activator.java b/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/Activator.java
new file mode 100644
index 0000000000..ca6c05a435
--- /dev/null
+++ b/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/Activator.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.extras;
+
+import org.apache.qpid.extras.exchanges.diagnostic.DiagnosticExchangeType;
+import org.apache.qpid.extras.exchanges.example.TestExchangeType;
+import org.apache.qpid.server.exchange.ExchangeType;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+/**
+ *
+ * @author aidan
+ *
+ * Dummy class, used by PluginTest
+ */
+
+public class Activator implements BundleActivator
+{
+
+ public void start(BundleContext ctx) throws Exception
+ {
+ ctx.registerService(ExchangeType.class.getName(), new TestExchangeType(), null);
+ ctx.registerService(ExchangeType.class.getName(), new DiagnosticExchangeType(), null);
+ }
+
+ public void stop(BundleContext ctx) throws Exception
+ {
+ }
+}
diff --git a/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/diagnostic/DiagnosticExchange.java b/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/diagnostic/DiagnosticExchange.java
new file mode 100644
index 0000000000..5d2c0dd5b2
--- /dev/null
+++ b/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/diagnostic/DiagnosticExchange.java
@@ -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.
+ *
+ */
+package org.apache.qpid.extras.exchanges.diagnostic;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+import javax.management.JMException;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.TabularData;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.management.common.mbeans.annotations.MBeanConstructor;
+import org.apache.qpid.management.common.mbeans.annotations.MBeanDescription;
+import org.apache.qpid.server.binding.Binding;
+import org.apache.qpid.server.exchange.AbstractExchange;
+import org.apache.qpid.server.exchange.AbstractExchangeMBean;
+import org.apache.qpid.server.exchange.ExchangeType;
+import org.apache.qpid.server.message.InboundMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+/**
+ * This is a special diagnostic exchange type which doesn't actually do anything
+ * with messages. When it receives a message, it writes information about the
+ * current memory usage to the "memory" property of the message and places it on the
+ * diagnosticqueue for retrieval
+ */
+public class DiagnosticExchange extends AbstractExchange
+{
+ private static final Logger _logger = Logger.getLogger(DiagnosticExchange.class);
+
+ public static final AMQShortString DIAGNOSTIC_EXCHANGE_CLASS = new AMQShortString("x-diagnostic");
+ public static final AMQShortString DIAGNOSTIC_EXCHANGE_NAME = new AMQShortString("diagnostic");
+
+ /** The logger */
+ //private static final Logger _logger = Logger.getLogger(DiagnosticExchange.class);
+
+ /**
+ * MBean class implementing the management interfaces.
+ */
+ @MBeanDescription("Management Bean for Diagnostic Exchange")
+ private final class DiagnosticExchangeMBean extends AbstractExchangeMBean<DiagnosticExchange>
+ {
+ /**
+ * Usual constructor.
+ *
+ * @throws JMException
+ */
+ @MBeanConstructor("Creates an MBean for AMQ Diagnostic exchange")
+ public DiagnosticExchangeMBean() throws JMException
+ {
+ super(DiagnosticExchange.this);
+
+ init();
+ }
+
+ /**
+ * Returns nothing, there can be no tabular data for this...
+ *
+ * @throws OpenDataException
+ * @returns null
+ * TODO or can there? Could this actually return all the information in one easy to read table?
+ */
+ @Override
+ public TabularData bindings() throws OpenDataException
+ {
+ return null;
+ }
+
+ /**
+ * This exchange type doesn't support queues, so this method does
+ * nothing.
+ *
+ * @param queueName the queue you'll fail to create
+ * @param binding the binding you'll fail to create
+ * @throws JMException an exception that will never be thrown
+ */
+ @Override
+ public void createNewBinding(String queueName, String binding) throws JMException
+ {
+ // No Op
+ }
+
+ /**
+ * This exchange type doesn't support queues.
+ *
+ * @see #createNewBinding(String, String)
+ */
+ @Override
+ public void removeBinding(String queueName, String binding) throws JMException
+ {
+ // No Op
+ }
+ }
+
+
+ public static final ExchangeType<DiagnosticExchange> TYPE = new ExchangeType<DiagnosticExchange>()
+ {
+
+ public AMQShortString getName()
+ {
+ return DIAGNOSTIC_EXCHANGE_CLASS;
+ }
+
+ public Class<DiagnosticExchange> getExchangeClass()
+ {
+ return DiagnosticExchange.class;
+ }
+
+ public DiagnosticExchange newInstance(VirtualHost host,
+ AMQShortString name,
+ boolean durable,
+ int ticket,
+ boolean autoDelete) throws AMQException
+ {
+ DiagnosticExchange exch = new DiagnosticExchange();
+ exch.initialise(host,name,durable,ticket,autoDelete);
+ return exch;
+ }
+
+ public AMQShortString getDefaultExchangeName()
+ {
+ return DIAGNOSTIC_EXCHANGE_NAME ;
+ }
+ };
+
+ public DiagnosticExchange()
+ {
+ super(TYPE);
+ }
+
+ /**
+ * Creates a new MBean instance
+ *
+ * @return the newly created MBean
+ * @throws AMQException
+ * if something goes wrong
+ */
+ protected AbstractExchangeMBean createMBean() throws JMException
+ {
+ return new DiagnosticExchange.DiagnosticExchangeMBean();
+ }
+
+ public Logger getLogger()
+ {
+ return _logger;
+ }
+
+ public void registerQueue(String routingKey, AMQQueue queue, Map<String, Object> args) throws AMQException
+ {
+ // No op
+ }
+
+
+ public boolean isBound(AMQShortString routingKey, AMQQueue queue)
+ {
+ return false;
+ }
+
+ public boolean isBound(AMQShortString routingKey)
+ {
+ return false;
+ }
+
+ public boolean isBound(AMQQueue queue)
+ {
+ return false;
+ }
+
+ public boolean hasBindings()
+ {
+ return false;
+ }
+
+ public ArrayList<AMQQueue> doRoute(InboundMessage payload)
+ {
+ //TODO shouldn't modify messages... perhaps put a new message on the queue?
+ /*
+ Long value = new Long(SizeOf.getUsedMemory());
+ AMQShortString key = new AMQShortString("memory");
+ FieldTable headers = ((BasicContentHeaderProperties)payload.getMessageHeader().properties).getHeaders();
+ headers.put(key, value);
+ ((BasicContentHeaderProperties)payload.getMessageHeader().properties).setHeaders(headers);
+ */
+ AMQQueue q = getQueueRegistry().getQueue(new AMQShortString("diagnosticqueue"));
+ ArrayList<AMQQueue> queues = new ArrayList<AMQQueue>();
+ queues.add(q);
+ return queues;
+ }
+
+
+ public boolean isBound(AMQShortString routingKey, FieldTable arguments,
+ AMQQueue queue) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ protected void onBind(final Binding binding)
+ {
+ // No op
+ }
+
+ protected void onUnbind(final Binding binding)
+ {
+ // No op
+ }
+}
diff --git a/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/diagnostic/DiagnosticExchangeType.java b/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/diagnostic/DiagnosticExchangeType.java
new file mode 100644
index 0000000000..b4d0d1aa0d
--- /dev/null
+++ b/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/diagnostic/DiagnosticExchangeType.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.extras.exchanges.diagnostic;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.exchange.ExchangeType;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+/**
+ * Exchange type class for getting hold of the exchange.
+ */
+public final class DiagnosticExchangeType implements ExchangeType<DiagnosticExchange>
+{
+
+ public AMQShortString getName()
+ {
+ return DiagnosticExchange.DIAGNOSTIC_EXCHANGE_CLASS;
+ }
+
+ public Class<DiagnosticExchange> getExchangeClass()
+ {
+ return DiagnosticExchange.class;
+ }
+
+ public DiagnosticExchange newInstance(VirtualHost host, AMQShortString name, boolean durable, int ticket, boolean autoDelete)
+ throws AMQException
+ {
+ DiagnosticExchange exch = new DiagnosticExchange();
+ exch.initialise(host, name, durable, ticket, autoDelete);
+ return exch;
+ }
+
+ public AMQShortString getDefaultExchangeName()
+ {
+ return DiagnosticExchange.DIAGNOSTIC_EXCHANGE_NAME;
+ }
+}
diff --git a/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/example/TestExchange.java b/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/example/TestExchange.java
new file mode 100644
index 0000000000..def0b3f91a
--- /dev/null
+++ b/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/example/TestExchange.java
@@ -0,0 +1,257 @@
+package org.apache.qpid.extras.exchanges.example;
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.server.binding.Binding;
+import org.apache.qpid.server.configuration.ConfiguredObject;
+import org.apache.qpid.server.configuration.ExchangeConfigType;
+import org.apache.qpid.server.configuration.VirtualHostConfig;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.exchange.ExchangeReferrer;
+import org.apache.qpid.server.exchange.ExchangeType;
+import org.apache.qpid.server.message.InboundMessage;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.BaseQueue;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+public class TestExchange implements Exchange
+{
+
+ public void close() throws AMQException
+ {
+ }
+
+
+
+ public void addBindingListener(final BindingListener listener)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void removeBindingListener(final BindingListener listener)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public AMQShortString getNameShortString()
+ {
+ return null;
+ }
+
+ public AMQShortString getTypeShortString()
+ {
+ return null;
+ }
+
+ public boolean hasBindings()
+ {
+ return false;
+ }
+
+ public boolean isBound(String bindingKey, AMQQueue queue)
+ {
+ return false;
+ }
+
+ public boolean isBound(String bindingKey)
+ {
+ return false;
+ }
+
+ public void addCloseTask(final Task task)
+ {
+
+ }
+
+ public void removeCloseTask(final Task task)
+ {
+
+ }
+
+ public Exchange getAlternateExchange()
+ {
+ return null;
+ }
+
+ public Map<String, Object> getArguments()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getBindingCount()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getBindingCountHigh()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getMsgReceives()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getMsgRoutes()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getByteReceives()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getByteRoutes()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getCreateTime()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void setAlternateExchange(Exchange exchange)
+ {
+
+ }
+
+ public void removeReference(ExchangeReferrer exchange)
+ {
+
+ }
+
+ public void addReference(ExchangeReferrer exchange)
+ {
+
+ }
+
+ public boolean hasReferrers()
+ {
+ return false;
+ }
+
+ public void addBinding(final Binding binding)
+ {
+
+ }
+
+ public void removeBinding(final Binding binding)
+ {
+
+ }
+
+ public Collection<Binding> getBindings()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void initialise(VirtualHost host, AMQShortString name, boolean durable, boolean autoDelete)
+ throws AMQException
+ {
+ }
+
+ public VirtualHostConfig getVirtualHost()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public String getName()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public ExchangeType getType()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isAutoDelete()
+ {
+ return false;
+ }
+
+ public boolean isBound(AMQShortString routingKey, FieldTable arguments, AMQQueue queue)
+ {
+ return false;
+ }
+
+ public boolean isBound(AMQShortString routingKey, AMQQueue queue)
+ {
+ return false;
+ }
+
+ public boolean isBound(AMQShortString routingKey)
+ {
+ return false;
+ }
+
+ public boolean isBound(AMQQueue queue)
+ {
+ return false;
+ }
+
+ public UUID getId()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public ExchangeConfigType getConfigType()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public ConfiguredObject getParent()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isDurable()
+ {
+ return false;
+ }
+
+ public ArrayList<? extends BaseQueue> route(InboundMessage message)
+ {
+ return new ArrayList<AMQQueue>();
+ }
+
+ public int getTicket()
+ {
+ return 0;
+ }
+
+ public void initialise(VirtualHost arg0, AMQShortString arg1, boolean arg2, int arg3, boolean arg4)
+ throws AMQException
+ {
+ }
+}
diff --git a/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/example/TestExchangeType.java b/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/example/TestExchangeType.java
new file mode 100644
index 0000000000..db02ca13ea
--- /dev/null
+++ b/qpid/java/broker-plugins/extras/src/main/java/org/apache/qpid/extras/exchanges/example/TestExchangeType.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.extras.exchanges.example;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.exchange.ExchangeType;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+public class TestExchangeType implements ExchangeType
+{
+
+ public Class getExchangeClass()
+ {
+ return TestExchange.class;
+ }
+
+ public AMQShortString getName()
+ {
+ return null;
+ }
+
+ public Exchange newInstance(VirtualHost host, AMQShortString name, boolean durable,
+ int token, boolean autoDelete)
+ throws AMQException
+ {
+ TestExchange ex = new TestExchange();
+ ex.initialise(host, name, durable, token, autoDelete);
+ return ex;
+ }
+
+ public AMQShortString getDefaultExchangeName()
+ {
+ return new AMQShortString("test.exchange");
+ }
+
+}
diff --git a/qpid/java/broker-plugins/extras/src/test/java/org/apache/qpid/server/plugins/ExtrasTest.java b/qpid/java/broker-plugins/extras/src/test/java/org/apache/qpid/server/plugins/ExtrasTest.java
new file mode 100644
index 0000000000..57b6e19b5d
--- /dev/null
+++ b/qpid/java/broker-plugins/extras/src/test/java/org/apache/qpid/server/plugins/ExtrasTest.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.plugins;
+
+import junit.framework.TestCase;
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.qpid.server.configuration.ServerConfiguration;
+import org.apache.qpid.server.exchange.ExchangeType;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.registry.IApplicationRegistry;
+import org.apache.qpid.server.util.TestApplicationRegistry;
+
+import java.util.Map;
+
+public class ExtrasTest extends TestCase
+{
+ private static final String TEST_EXCHANGE_CLASS = "org.apache.qpid.extras.exchanges.example.TestExchangeType";
+
+ private static final String PLUGIN_DIRECTORY = System.getProperty("example.plugin.target");
+ private static final String CACHE_DIRECTORY = System.getProperty("example.cache.target");
+
+ IApplicationRegistry _registry;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ PropertiesConfiguration properties = new PropertiesConfiguration();
+ properties.addProperty("plugin-directory", PLUGIN_DIRECTORY);
+ properties.addProperty("cache-directory", CACHE_DIRECTORY);
+ ServerConfiguration config = new ServerConfiguration(properties);
+
+ // This Test requires an application Registry
+ ApplicationRegistry.initialise(new TestApplicationRegistry(config));
+ _registry = ApplicationRegistry.getInstance();
+ }
+
+ @Override
+ public void tearDown() throws Exception
+ {
+ ApplicationRegistry.remove();
+ }
+
+ public void testLoadExchanges() throws Exception
+ {
+ PluginManager manager = _registry.getPluginManager();
+ Map<String, ExchangeType<?>> exchanges = manager.getExchanges();
+ assertNotNull("No exchanges found in " + PLUGIN_DIRECTORY, exchanges);
+ assertEquals("Wrong number of exchanges found in " + PLUGIN_DIRECTORY, 2, exchanges.size());
+ assertNotNull("Wrong exchange found in " + PLUGIN_DIRECTORY, exchanges.get(TEST_EXCHANGE_CLASS));
+ }
+
+ public void testNoExchanges() throws Exception
+ {
+ PluginManager manager = new PluginManager("/path/to/nowhere", "/tmp");
+ Map<String, ExchangeType<?>> exchanges = manager.getExchanges();
+ assertTrue("Exchanges found", exchanges.isEmpty());
+ }
+}
diff --git a/qpid/java/broker-plugins/firewall/MANIFEST.MF b/qpid/java/broker-plugins/firewall/MANIFEST.MF
new file mode 100644
index 0000000000..6ceea119da
--- /dev/null
+++ b/qpid/java/broker-plugins/firewall/MANIFEST.MF
@@ -0,0 +1,36 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Qpid Broker-Plugins Firewall
+Bundle-SymbolicName: broker-plugins-firewall
+Bundle-Description: Firewall 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.security.access.plugins.FirewallActivator
+Bundle-RequiredExecutionEnvironment: JavaSE-1.6
+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,
+ org.apache.qpid.server.security.access,
+ org.apache.qpid.server.virtualhost,
+ org.apache.qpid.util,
+ 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,
+ 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.security.access.config
+Export-Package: org.apache.qpid.server.security.access.plugins;uses:="org.osgi.framework"
diff --git a/qpid/java/broker-plugins/firewall/build.xml b/qpid/java/broker-plugins/firewall/build.xml
new file mode 100644
index 0000000000..576435de7f
--- /dev/null
+++ b/qpid/java/broker-plugins/firewall/build.xml
@@ -0,0 +1,29 @@
+<!--
+ - Licensed to the Apache Software Foundation (ASF) under one
+ - or more contributor license agreements. See the NOTICE file
+ - distributed with this work for additional information
+ - regarding copyright ownership. The ASF licenses this file
+ - to you under the Apache License, Version 2.0 (the
+ - "License"); you may not use this file except in compliance
+ - with the License. You may obtain a copy of the License at
+ -
+ - http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing,
+ - software distributed under the License is distributed on an
+ - "AS IS" BASIS, WITHOUT 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 Firewall" default="build">
+ <property name="module.depends" value="common broker broker-plugins" />
+ <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="bundle" depends="bundle-tasks" />
+</project>
diff --git a/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/config/FirewallException.java b/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/config/FirewallException.java
new file mode 100644
index 0000000000..a9e3fdc242
--- /dev/null
+++ b/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/config/FirewallException.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.security.access.config;
+
+/**
+ * Firewall plugin exception.
+ */
+public class FirewallException extends Exception
+{
+ /** serialVersionUID */
+ private static final long serialVersionUID = 4526157149690917805L;
+
+ public FirewallException() {
+ super();
+ }
+
+ public FirewallException(String message) {
+ super(message);
+ }
+
+ public FirewallException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public FirewallException(Throwable cause) {
+ super(cause);
+ }
+} \ No newline at end of file
diff --git a/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/config/FirewallRule.java b/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/config/FirewallRule.java
new file mode 100644
index 0000000000..f257b58867
--- /dev/null
+++ b/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/config/FirewallRule.java
@@ -0,0 +1,137 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.access.config;
+
+import java.net.InetAddress;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.regex.Pattern;
+
+import org.apache.qpid.server.security.Result;
+import org.apache.qpid.util.NetMatcher;
+
+public class FirewallRule
+{
+ public static final String ALLOW = "ALLOW";
+ public static final String DENY = "DENY";
+
+ private static final long DNS_TIMEOUT = 30000;
+ private static final ExecutorService DNS_LOOKUP = Executors.newCachedThreadPool();
+
+ private Result _access;
+ private NetMatcher _network;
+ private Pattern[] _hostnamePatterns;
+
+ public FirewallRule(String access, List networks, List hostnames)
+ {
+ _access = (access.equalsIgnoreCase(ALLOW)) ? Result.ALLOWED : Result.DENIED;
+
+ if (networks != null && networks.size() > 0)
+ {
+ String[] networkStrings = objListToStringArray(networks);
+ _network = new NetMatcher(networkStrings);
+ }
+
+ if (hostnames != null && hostnames.size() > 0)
+ {
+ int i = 0;
+ _hostnamePatterns = new Pattern[hostnames.size()];
+ for (String hostname : objListToStringArray(hostnames))
+ {
+ _hostnamePatterns[i++] = Pattern.compile(hostname);
+ }
+ }
+ }
+
+ private String[] objListToStringArray(List objList)
+ {
+ String[] networkStrings = new String[objList.size()];
+ int i = 0;
+ for (Object network : objList)
+ {
+ networkStrings[i++] = (String) network;
+ }
+ return networkStrings;
+ }
+
+ public boolean match(InetAddress remote) throws FirewallException
+ {
+ if (_hostnamePatterns != null)
+ {
+ String hostname = getHostname(remote);
+ if (hostname == null)
+ {
+ throw new FirewallException("DNS lookup failed");
+ }
+ for (Pattern pattern : _hostnamePatterns)
+ {
+ if (pattern.matcher(hostname).matches())
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ else
+ {
+ return _network.matchInetNetwork(remote);
+ }
+ }
+
+ /**
+ * @param remote the InetAddress to look up
+ * @return the hostname, null if not found, takes longer than 30s to find or otherwise fails
+ */
+ private String getHostname(final InetAddress remote) throws FirewallException
+ {
+ FutureTask<String> lookup = new FutureTask<String>(new Callable<String>()
+ {
+ public String call()
+ {
+ return remote.getCanonicalHostName();
+ }
+ });
+ DNS_LOOKUP.execute(lookup);
+
+ try
+ {
+ return lookup.get(DNS_TIMEOUT, TimeUnit.MILLISECONDS);
+ }
+ catch (Exception e)
+ {
+ return null;
+ }
+ finally
+ {
+ lookup.cancel(true);
+ }
+ }
+
+ public Result getAccess()
+ {
+ return _access;
+ }
+} \ No newline at end of file
diff --git a/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/plugins/Firewall.java b/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/plugins/Firewall.java
new file mode 100644
index 0000000000..a6ea9d261e
--- /dev/null
+++ b/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/plugins/Firewall.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.security.access.plugins;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin;
+import org.apache.qpid.server.security.AbstractPlugin;
+import org.apache.qpid.server.security.Result;
+import org.apache.qpid.server.security.SecurityPluginFactory;
+import org.apache.qpid.server.security.access.ObjectProperties;
+import org.apache.qpid.server.security.access.ObjectType;
+import org.apache.qpid.server.security.access.Operation;
+import org.apache.qpid.server.security.access.config.FirewallException;
+import org.apache.qpid.server.security.access.config.FirewallRule;
+
+public class Firewall extends AbstractPlugin
+{
+ public static final SecurityPluginFactory<Firewall> FACTORY = new SecurityPluginFactory<Firewall>()
+ {
+ public Firewall newInstance(ConfigurationPlugin config) throws ConfigurationException
+ {
+ FirewallConfiguration configuration = config.getConfiguration(FirewallConfiguration.class.getName());
+
+ // If there is no configuration for this plugin then don't load it.
+ if (configuration == null)
+ {
+ return null;
+ }
+
+ Firewall plugin = new Firewall();
+ plugin.configure(configuration);
+ return plugin;
+ }
+
+ public Class<Firewall> getPluginClass()
+ {
+ return Firewall.class;
+ }
+
+ public String getPluginName()
+ {
+ return Firewall.class.getName();
+ }
+ };
+
+ private Result _default = Result.ABSTAIN;
+ private FirewallRule[] _rules;
+
+ public Result getDefault()
+ {
+ return _default;
+ }
+
+ public Result authorise(Operation operation, ObjectType objectType, ObjectProperties properties)
+ {
+ return Result.ABSTAIN; // We only deal with access requests
+ }
+
+ public Result access(ObjectType objectType, Object instance)
+ {
+ if (objectType != ObjectType.VIRTUALHOST)
+ {
+ return Result.ABSTAIN; // We are only interested in access to virtualhosts
+ }
+
+ if (!(instance instanceof InetSocketAddress))
+ {
+ return Result.ABSTAIN; // We need an internet address
+ }
+
+ InetAddress address = ((InetSocketAddress) instance).getAddress();
+
+ try
+ {
+ for (FirewallRule rule : _rules)
+ {
+ boolean match = rule.match(address);
+ if (match)
+ {
+ return rule.getAccess();
+ }
+ }
+ return getDefault();
+ }
+ catch (FirewallException fe)
+ {
+ return Result.DENIED;
+ }
+ }
+
+
+ public void configure(ConfigurationPlugin config)
+ {
+ super.configure(config);
+ FirewallConfiguration firewallConfiguration = (FirewallConfiguration) _config;
+
+ // Get default action
+ _default = firewallConfiguration.getDefaultAction();
+
+ Configuration finalConfig = firewallConfiguration.getConfiguration();
+
+ // all rules must have an access attribute
+ int numRules = finalConfig.getList("rule[@access]").size();
+ _rules = new FirewallRule[numRules];
+ for (int i = 0; i < numRules; i++)
+ {
+ FirewallRule rule = new FirewallRule(finalConfig.getString("rule(" + i + ")[@access]"),
+ finalConfig.getList("rule(" + i + ")[@network]"),
+ finalConfig.getList("rule(" + i + ")[@hostname]"));
+ _rules[i] = rule;
+ }
+
+ }
+}
diff --git a/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/plugins/FirewallActivator.java b/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/plugins/FirewallActivator.java
new file mode 100644
index 0000000000..c20bba8d2c
--- /dev/null
+++ b/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/plugins/FirewallActivator.java
@@ -0,0 +1,42 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.access.plugins;
+
+import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory;
+import org.apache.qpid.server.security.SecurityPluginActivator;
+import org.apache.qpid.server.security.SecurityPluginFactory;
+import org.osgi.framework.BundleActivator;
+
+/**
+ * The OSGi {@link BundleActivator} for {@link Firewall}.
+ */
+public class FirewallActivator extends SecurityPluginActivator
+{
+ public SecurityPluginFactory getFactory()
+ {
+ return Firewall.FACTORY;
+ }
+
+ public ConfigurationPluginFactory getConfigurationFactory()
+ {
+ return FirewallConfiguration.FACTORY;
+ }
+}
diff --git a/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/plugins/FirewallConfiguration.java b/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/plugins/FirewallConfiguration.java
new file mode 100644
index 0000000000..b10656d622
--- /dev/null
+++ b/qpid/java/broker-plugins/firewall/src/main/java/org/apache/qpid/server/security/access/plugins/FirewallConfiguration.java
@@ -0,0 +1,102 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.access.plugins;
+
+import java.util.Arrays;
+import java.util.List;
+
+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 org.apache.qpid.server.security.Result;
+import org.apache.qpid.server.security.access.config.FirewallRule;
+
+public class FirewallConfiguration extends ConfigurationPlugin
+{
+ CompositeConfiguration _finalConfig;
+
+ public static final ConfigurationPluginFactory FACTORY = new ConfigurationPluginFactory()
+ {
+ public ConfigurationPlugin newInstance(String path, Configuration config) throws ConfigurationException
+ {
+ ConfigurationPlugin instance = new FirewallConfiguration();
+ instance.setConfiguration(path, config);
+ return instance;
+ }
+
+ public List<String> getParentPaths()
+ {
+ return Arrays.asList("security.firewall", "virtualhosts.virtualhost.security.firewall");
+ }
+ };
+
+ public String[] getElementsProcessed()
+ {
+ return new String[] { "" };
+ }
+
+ public Configuration getConfiguration()
+ {
+ return _finalConfig;
+ }
+
+ public Result getDefaultAction()
+ {
+ String defaultAction = _configuration.getString("[@default-action]");
+ if (defaultAction == null)
+ {
+ return Result.ABSTAIN;
+ }
+ else if (defaultAction.equalsIgnoreCase(FirewallRule.ALLOW))
+ {
+ return Result.ALLOWED;
+ }
+ else
+ {
+ return Result.DENIED;
+ }
+ }
+
+
+
+ @Override
+ public void validateConfiguration() throws ConfigurationException
+ {
+ // Valid Configuration either has xml links to new files
+ _finalConfig = new CompositeConfiguration(_configuration);
+ List subFiles = _configuration.getList("xml[@fileName]");
+ for (Object subFile : subFiles)
+ {
+ _finalConfig.addConfiguration(new XMLConfiguration((String) subFile));
+ }
+
+ // all rules must have an access attribute or a default value
+ if (_finalConfig.getList("rule[@access]").size() == 0 &&
+ _configuration.getString("[@default-action]") == null)
+ {
+ throw new ConfigurationException("No rules or default-action found in firewall configuration.");
+ }
+ }
+
+}
diff --git a/qpid/java/broker-plugins/firewall/src/test/java/org/apache/qpid/server/security/access/FirewallConfigurationTest.java b/qpid/java/broker-plugins/firewall/src/test/java/org/apache/qpid/server/security/access/FirewallConfigurationTest.java
new file mode 100644
index 0000000000..ebede414f4
--- /dev/null
+++ b/qpid/java/broker-plugins/firewall/src/test/java/org/apache/qpid/server/security/access/FirewallConfigurationTest.java
@@ -0,0 +1,355 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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.security.access;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.net.InetSocketAddress;
+
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.registry.ConfigurationFileApplicationRegistry;
+import org.apache.qpid.server.util.InternalBrokerBaseCase;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.virtualhost.VirtualHostRegistry;
+
+public class FirewallConfigurationTest extends InternalBrokerBaseCase
+{
+ public void testFirewallConfiguration() throws Exception
+ {
+ // Write out config
+ File mainFile = File.createTempFile(getClass().getName(), null);
+ mainFile.deleteOnExit();
+ writeConfigFile(mainFile, false);
+
+ // Load config
+ ApplicationRegistry reg = new ConfigurationFileApplicationRegistry(mainFile);
+ try
+ {
+ ApplicationRegistry.initialise(reg, 1);
+
+ // Test config
+ assertFalse(reg.getSecurityManager().accessVirtualhost("test", new InetSocketAddress("127.0.0.1", 65535)));
+ assertTrue(reg.getSecurityManager().accessVirtualhost("test", new InetSocketAddress("127.1.2.3", 65535)));
+ }
+ finally
+ {
+ ApplicationRegistry.remove(1);
+ }
+ }
+
+ public void testCombinedConfigurationFirewall() throws Exception
+ {
+ // Write out config
+ File mainFile = File.createTempFile(getClass().getName(), null);
+ File fileA = File.createTempFile(getClass().getName(), null);
+ File fileB = File.createTempFile(getClass().getName(), null);
+
+ mainFile.deleteOnExit();
+ fileA.deleteOnExit();
+ fileB.deleteOnExit();
+
+ FileWriter out = new FileWriter(mainFile);
+ out.write("<configuration><system/>");
+ out.write("<xml fileName=\"" + fileA.getAbsolutePath() + "\"/>");
+ out.write("</configuration>");
+ out.close();
+
+ out = new FileWriter(fileA);
+ out.write("<broker>\n");
+ out.write("\t<plugin-directory>${QPID_HOME}/lib/plugins</plugin-directory>\n");
+ out.write("\t<cache-directory>${QPID_WORK}/cache</cache-directory>\n");
+ out.write("\t<management><enabled>false</enabled></management>\n");
+ out.write("\t<security>\n");
+ out.write("\t\t<principal-databases>\n");
+ out.write("\t\t\t<principal-database>\n");
+ out.write("\t\t\t\t<name>passwordfile</name>\n");
+ out.write("\t\t\t\t<class>org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase</class>\n");
+ out.write("\t\t\t\t<attributes>\n");
+ out.write("\t\t\t\t\t<attribute>\n");
+ out.write("\t\t\t\t\t\t<name>passwordFile</name>\n");
+ out.write("\t\t\t\t\t\t<value>/dev/null</value>\n");
+ out.write("\t\t\t\t\t</attribute>\n");
+ out.write("\t\t\t\t</attributes>\n");
+ out.write("\t\t\t</principal-database>\n");
+ out.write("\t\t</principal-databases>\n");
+ out.write("\t\t<jmx>\n");
+ out.write("\t\t\t<principal-database>passwordfile</principal-database>\n");
+ out.write("\t\t</jmx>\n");
+ out.write("\t\t<firewall>\n");
+ out.write("\t\t\t<xml fileName=\"" + fileB.getAbsolutePath() + "\"/>");
+ out.write("\t\t</firewall>\n");
+ out.write("\t</security>\n");
+ out.write("\t<virtualhosts>\n");
+ out.write("\t\t<virtualhost>\n");
+ out.write("\t\t\t<name>test</name>\n");
+ out.write("\t\t</virtualhost>\n");
+ out.write("\t</virtualhosts>\n");
+ out.write("</broker>\n");
+ out.close();
+
+ out = new FileWriter(fileB);
+ out.write("<firewall>\n");
+ out.write("\t<rule access=\"deny\" network=\"127.0.0.1\"/>");
+ out.write("</firewall>\n");
+ out.close();
+
+ // Load config
+ ApplicationRegistry reg = new ConfigurationFileApplicationRegistry(mainFile);
+ try
+ {
+ ApplicationRegistry.initialise(reg, 1);
+
+ // Test config
+ assertFalse(reg.getSecurityManager().accessVirtualhost("test", new InetSocketAddress("127.0.0.1", 65535)));
+ }
+ finally
+ {
+ ApplicationRegistry.remove(1);
+ }
+ }
+
+ public void testConfigurationFirewallReload() throws Exception
+ {
+ // Write out config
+ File mainFile = File.createTempFile(getClass().getName(), null);
+
+ mainFile.deleteOnExit();
+ writeConfigFile(mainFile, false);
+
+ // Load config
+ ApplicationRegistry reg = new ConfigurationFileApplicationRegistry(mainFile);
+ try
+ {
+ ApplicationRegistry.initialise(reg, 1);
+
+ // Test config
+ assertFalse(reg.getSecurityManager().accessVirtualhost("test", new InetSocketAddress("127.0.0.1", 65535)));
+
+ // Switch to deny the connection
+ writeConfigFile(mainFile, true);
+
+ reg.getConfiguration().reparseConfigFileSecuritySections();
+
+ assertTrue(reg.getSecurityManager().accessVirtualhost("test", new InetSocketAddress("127.0.0.1", 65535)));
+ }
+ finally
+ {
+ ApplicationRegistry.remove(1);
+ }
+ }
+
+ public void testCombinedConfigurationFirewallReload() throws Exception
+ {
+ // Write out config
+ File mainFile = File.createTempFile(getClass().getName(), null);
+ File fileA = File.createTempFile(getClass().getName(), null);
+ File fileB = File.createTempFile(getClass().getName(), null);
+
+ mainFile.deleteOnExit();
+ fileA.deleteOnExit();
+ fileB.deleteOnExit();
+
+ FileWriter out = new FileWriter(mainFile);
+ out.write("<configuration><system/>");
+ out.write("<xml fileName=\"" + fileA.getAbsolutePath() + "\"/>");
+ out.write("</configuration>");
+ out.close();
+
+ out = new FileWriter(fileA);
+ out.write("<broker>\n");
+ out.write("\t<plugin-directory>${QPID_HOME}/lib/plugins</plugin-directory>\n");
+ out.write("\t<management><enabled>false</enabled></management>\n");
+ out.write("\t<security>\n");
+ out.write("\t\t<principal-databases>\n");
+ out.write("\t\t\t<principal-database>\n");
+ out.write("\t\t\t\t<name>passwordfile</name>\n");
+ out.write("\t\t\t\t<class>org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase</class>\n");
+ out.write("\t\t\t\t<attributes>\n");
+ out.write("\t\t\t\t\t<attribute>\n");
+ out.write("\t\t\t\t\t\t<name>passwordFile</name>\n");
+ out.write("\t\t\t\t\t\t<value>/dev/null</value>\n");
+ out.write("\t\t\t\t\t</attribute>\n");
+ out.write("\t\t\t\t</attributes>\n");
+ out.write("\t\t\t</principal-database>\n");
+ out.write("\t\t</principal-databases>\n");
+ out.write("\t\t<jmx>\n");
+ out.write("\t\t\t<principal-database>passwordfile</principal-database>\n");
+ out.write("\t\t</jmx>\n");
+ out.write("\t\t<firewall>\n");
+ out.write("\t\t\t<xml fileName=\"" + fileB.getAbsolutePath() + "\"/>");
+ out.write("\t\t</firewall>\n");
+ out.write("\t</security>\n");
+ out.write("\t<virtualhosts>\n");
+ out.write("\t\t<virtualhost>\n");
+ out.write("\t\t\t<name>test</name>\n");
+ out.write("\t\t</virtualhost>\n");
+ out.write("\t</virtualhosts>\n");
+ out.write("</broker>\n");
+ out.close();
+
+ out = new FileWriter(fileB);
+ out.write("<firewall>\n");
+ out.write("\t<rule access=\"deny\" network=\"127.0.0.1\"/>");
+ out.write("</firewall>\n");
+ out.close();
+
+ // Load config
+ ApplicationRegistry reg = new ConfigurationFileApplicationRegistry(mainFile);
+ try
+ {
+ ApplicationRegistry.initialise(reg, 1);
+
+ // Test config
+ assertFalse(reg.getSecurityManager().accessVirtualhost("test", new InetSocketAddress("127.0.0.1", 65535)));
+
+ RandomAccessFile fileBRandom = new RandomAccessFile(fileB, "rw");
+ fileBRandom.setLength(0);
+ fileBRandom.seek(0);
+ fileBRandom.close();
+
+ out = new FileWriter(fileB);
+ out.write("<firewall>\n");
+ out.write("\t<rule access=\"allow\" network=\"127.0.0.1\"/>");
+ out.write("</firewall>\n");
+ out.close();
+
+ reg.getConfiguration().reparseConfigFileSecuritySections();
+
+ assertTrue(reg.getSecurityManager().accessVirtualhost("test", new InetSocketAddress("127.0.0.1", 65535)));
+
+ fileBRandom = new RandomAccessFile(fileB, "rw");
+ fileBRandom.setLength(0);
+ fileBRandom.seek(0);
+ fileBRandom.close();
+
+ out = new FileWriter(fileB);
+ out.write("<firewall>\n");
+ out.write("\t<rule access=\"deny\" network=\"127.0.0.1\"/>");
+ out.write("</firewall>\n");
+ out.close();
+
+ reg.getConfiguration().reparseConfigFileSecuritySections();
+
+ assertFalse(reg.getSecurityManager().accessVirtualhost("test", new InetSocketAddress("127.0.0.1", 65535)));
+ }
+ finally
+ {
+ ApplicationRegistry.remove(1);
+ }
+ }
+
+ private void writeFirewallVhostsFile(File vhostsFile, boolean allow) throws IOException
+ {
+ FileWriter out = new FileWriter(vhostsFile);
+ String ipAddr = "127.0.0.1"; // FIXME: get this from InetAddress.getLocalHost().getAddress() ?
+ out.write("<virtualhosts><virtualhost>");
+ out.write("<name>test</name>");
+ out.write("<test>");
+ out.write("<security><firewall>");
+ out.write("<rule access=\""+((allow) ? "allow" : "deny")+"\" network=\""+ipAddr +"\"/>");
+ out.write("</firewall></security>");
+ out.write("</test>");
+ out.write("</virtualhost></virtualhosts>");
+ out.close();
+ }
+
+ private void writeConfigFile(File mainFile, boolean allow) throws IOException {
+ writeConfigFile(mainFile, allow, true, null, "test");
+ }
+
+ /*
+ XMLConfiguration config = new XMLConfiguration(mainFile);
+ PluginManager pluginManager = new MockPluginManager("");
+ SecurityManager manager = new SecurityManager(config, pluginManager, Firewall.FACTORY);
+
+ */
+ private void writeConfigFile(File mainFile, boolean allow, boolean includeVhosts, File vhostsFile, String name) throws IOException {
+ FileWriter out = new FileWriter(mainFile);
+ out.write("<broker>\n");
+ out.write("\t<plugin-directory>${QPID_HOME}/lib/plugins</plugin-directory>\n");
+ out.write("\t<management><enabled>false</enabled></management>\n");
+ out.write("\t<security>\n");
+ out.write("\t\t<principal-databases>\n");
+ out.write("\t\t\t<principal-database>\n");
+ out.write("\t\t\t\t<name>passwordfile</name>\n");
+ out.write("\t\t\t\t<class>org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase</class>\n");
+ out.write("\t\t\t\t<attributes>\n");
+ out.write("\t\t\t\t\t<attribute>\n");
+ out.write("\t\t\t\t\t\t<name>passwordFile</name>\n");
+ out.write("\t\t\t\t\t\t<value>/dev/null</value>\n");
+ out.write("\t\t\t\t\t</attribute>\n");
+ out.write("\t\t\t\t</attributes>\n");
+ out.write("\t\t\t</principal-database>\n");
+ out.write("\t\t</principal-databases>\n");
+ out.write("\t\t<jmx>\n");
+ out.write("\t\t\t<principal-database>passwordfile</principal-database>\n");
+ out.write("\t\t</jmx>\n");
+ out.write("\t\t<firewall>\n");
+ out.write("\t\t\t<rule access=\""+ ((allow) ? "allow" : "deny") +"\" network=\"127.0.0.1\"/>");
+ out.write("\t\t</firewall>\n");
+ out.write("\t</security>\n");
+ if (includeVhosts)
+ {
+ out.write("\t<virtualhosts>\n");
+ out.write("\t\t<default>test</default>\n");
+ out.write("\t\t<virtualhost>\n");
+ out.write(String.format("\t\t\t<name>%s</name>\n", name));
+ out.write("\t\t</virtualhost>\n");
+ out.write("\t</virtualhosts>\n");
+ }
+ if (vhostsFile != null)
+ {
+ out.write("\t<virtualhosts>"+vhostsFile.getAbsolutePath()+"</virtualhosts>\n");
+ }
+ out.write("</broker>\n");
+ out.close();
+ }
+
+ /**
+ * Test that configuration loads correctly when virtual hosts are specified in an external
+ * configuration file only.
+ * <p>
+ * Test for QPID-2360
+ */
+ public void testExternalFirewallVirtualhostXMLFile() throws Exception
+ {
+ // Write out config
+ File mainFile = File.createTempFile(getClass().getName(), "config");
+ mainFile.deleteOnExit();
+ File vhostsFile = File.createTempFile(getClass().getName(), "vhosts");
+ vhostsFile.deleteOnExit();
+ writeConfigFile(mainFile, false, false, vhostsFile, null);
+ writeFirewallVhostsFile(vhostsFile, false);
+
+ // Load config
+ ApplicationRegistry reg = new ConfigurationFileApplicationRegistry(mainFile);
+ ApplicationRegistry.initialise(reg, 1);
+
+ // Test config
+ VirtualHostRegistry virtualHostRegistry = reg.getVirtualHostRegistry();
+ VirtualHost virtualHost = virtualHostRegistry.getVirtualHost("test");
+
+ assertEquals("Incorrect virtualhost count", 1, virtualHostRegistry.getVirtualHosts().size());
+ assertEquals("Incorrect virtualhost name", "test", virtualHost.getName());
+ }
+}
diff --git a/qpid/java/broker-plugins/firewall/src/test/java/org/apache/qpid/server/security/access/FirewallPluginTest.java b/qpid/java/broker-plugins/firewall/src/test/java/org/apache/qpid/server/security/access/FirewallPluginTest.java
new file mode 100644
index 0000000000..2b04962c89
--- /dev/null
+++ b/qpid/java/broker-plugins/firewall/src/test/java/org/apache/qpid/server/security/access/FirewallPluginTest.java
@@ -0,0 +1,282 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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.security.access;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.XMLConfiguration;
+import org.apache.qpid.server.security.Result;
+import org.apache.qpid.server.security.access.plugins.Firewall;
+import org.apache.qpid.server.security.access.plugins.FirewallConfiguration;
+import org.apache.qpid.server.util.InternalBrokerBaseCase;
+
+public class FirewallPluginTest extends InternalBrokerBaseCase
+{
+ public class RuleInfo
+ {
+ private String _access;
+ private String _network;
+ private String _hostname;
+
+ public void setAccess(String _access)
+ {
+ this._access = _access;
+ }
+
+ public String getAccess()
+ {
+ return _access;
+ }
+
+ public void setNetwork(String _network)
+ {
+ this._network = _network;
+ }
+
+ public String getNetwork()
+ {
+ return _network;
+ }
+
+ public void setHostname(String _hostname)
+ {
+ this._hostname = _hostname;
+ }
+
+ public String getHostname()
+ {
+ return _hostname;
+ }
+ }
+
+ // IP address
+ private SocketAddress _address;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+
+ _address = new InetSocketAddress("127.0.0.1", 65535);
+ }
+
+ private Firewall initialisePlugin(String defaultAction, RuleInfo[] rules) throws IOException, ConfigurationException
+ {
+ // Create sample config file
+ File confFile = File.createTempFile(getClass().getSimpleName()+"conffile", null);
+ confFile.deleteOnExit();
+ BufferedWriter buf = new BufferedWriter(new FileWriter(confFile));
+ buf.write("<firewall default-action=\""+defaultAction+"\">\n");
+ if (rules != null)
+ {
+ for (RuleInfo rule : rules)
+ {
+ buf.write("<rule");
+ buf.write(" access=\""+rule.getAccess()+"\"");
+ if (rule.getHostname() != null)
+ {
+ buf.write(" hostname=\""+rule.getHostname()+"\"");
+ }
+ if (rule.getNetwork() != null)
+ {
+ buf.write(" network=\""+rule.getNetwork()+"\"");
+ }
+ buf.write("/>\n");
+ }
+ }
+ buf.write("</firewall>");
+ buf.close();
+
+ // Configure plugin
+ FirewallConfiguration config = new FirewallConfiguration();
+ config.setConfiguration("", new XMLConfiguration(confFile));
+ Firewall plugin = new Firewall();
+ plugin.configure(config);
+ return plugin;
+ }
+
+ private Firewall initialisePlugin(String string) throws ConfigurationException, IOException
+ {
+ return initialisePlugin(string, null);
+ }
+
+ public void testDefaultAction() throws Exception
+ {
+ // Test simple deny
+ Firewall plugin = initialisePlugin("deny");
+ assertEquals(Result.DENIED, plugin.access(ObjectType.VIRTUALHOST, _address));
+
+ // Test simple allow
+ plugin = initialisePlugin("allow");
+ assertEquals(Result.ALLOWED, plugin.access(ObjectType.VIRTUALHOST, _address));
+ }
+
+
+ public void testSingleIPRule() throws Exception
+ {
+ RuleInfo rule = new RuleInfo();
+ rule.setAccess("allow");
+ rule.setNetwork("192.168.23.23");
+
+ Firewall plugin = initialisePlugin("deny", new RuleInfo[]{rule});
+
+ assertEquals(Result.DENIED, plugin.access(ObjectType.VIRTUALHOST, _address));
+
+ // Set IP so that we're connected from the right address
+ _address = new InetSocketAddress("192.168.23.23", 65535);
+ assertEquals(Result.ALLOWED, plugin.access(ObjectType.VIRTUALHOST, _address));
+ }
+
+ public void testSingleNetworkRule() throws Exception
+ {
+ RuleInfo rule = new RuleInfo();
+ rule.setAccess("allow");
+ rule.setNetwork("192.168.23.0/24");
+
+ Firewall plugin = initialisePlugin("deny", new RuleInfo[]{rule});
+
+ assertEquals(Result.DENIED, plugin.access(ObjectType.VIRTUALHOST, _address));
+
+ // Set IP so that we're connected from the right address
+ _address = new InetSocketAddress("192.168.23.23", 65535);
+ assertEquals(Result.ALLOWED, plugin.access(ObjectType.VIRTUALHOST, _address));
+ }
+
+ public void testSingleHostRule() throws Exception
+ {
+ RuleInfo rule = new RuleInfo();
+ rule.setAccess("allow");
+ rule.setHostname(new InetSocketAddress("127.0.0.1", 5672).getHostName());
+
+ Firewall plugin = initialisePlugin("deny", new RuleInfo[]{rule});
+
+ // Set IP so that we're connected from the right address
+ _address = new InetSocketAddress("127.0.0.1", 65535);
+ assertEquals(Result.ALLOWED, plugin.access(ObjectType.VIRTUALHOST, _address));
+ }
+
+ public void testSingleHostWilcardRule() throws Exception
+ {
+ RuleInfo rule = new RuleInfo();
+ rule.setAccess("allow");
+ String hostname = new InetSocketAddress("127.0.0.1", 0).getHostName();
+ rule.setHostname(".*"+hostname.subSequence(hostname.length() - 1, hostname.length())+"*");
+ Firewall plugin = initialisePlugin("deny", new RuleInfo[]{rule});
+
+ // Set IP so that we're connected from the right address
+ _address = new InetSocketAddress("127.0.0.1", 65535);
+ assertEquals(Result.ALLOWED, plugin.access(ObjectType.VIRTUALHOST, _address));
+ }
+
+ public void testSeveralFirstAllowsAccess() throws Exception
+ {
+ RuleInfo firstRule = new RuleInfo();
+ firstRule.setAccess("allow");
+ firstRule.setNetwork("192.168.23.23");
+
+ RuleInfo secondRule = new RuleInfo();
+ secondRule.setAccess("deny");
+ secondRule.setNetwork("192.168.42.42");
+
+ RuleInfo thirdRule = new RuleInfo();
+ thirdRule.setAccess("deny");
+ thirdRule.setHostname("localhost");
+
+ Firewall plugin = initialisePlugin("deny", new RuleInfo[]{firstRule, secondRule, thirdRule});
+
+ assertEquals(Result.DENIED, plugin.access(ObjectType.VIRTUALHOST, _address));
+
+ // Set IP so that we're connected from the right address
+ _address = new InetSocketAddress("192.168.23.23", 65535);
+ assertEquals(Result.ALLOWED, plugin.access(ObjectType.VIRTUALHOST, _address));
+ }
+
+ public void testSeveralLastAllowsAccess() throws Exception
+ {
+ RuleInfo firstRule = new RuleInfo();
+ firstRule.setAccess("deny");
+ firstRule.setHostname("localhost");
+
+ RuleInfo secondRule = new RuleInfo();
+ secondRule.setAccess("deny");
+ secondRule.setNetwork("192.168.42.42");
+
+ RuleInfo thirdRule = new RuleInfo();
+ thirdRule.setAccess("allow");
+ thirdRule.setNetwork("192.168.23.23");
+
+ Firewall plugin = initialisePlugin("deny", new RuleInfo[]{firstRule, secondRule, thirdRule});
+
+ assertEquals(Result.DENIED, plugin.access(ObjectType.VIRTUALHOST, _address));
+
+ // Set IP so that we're connected from the right address
+ _address = new InetSocketAddress("192.168.23.23", 65535);
+ assertEquals(Result.ALLOWED, plugin.access(ObjectType.VIRTUALHOST, _address));
+ }
+
+ public void testNetmask() throws Exception
+ {
+ RuleInfo firstRule = new RuleInfo();
+ firstRule.setAccess("allow");
+ firstRule.setNetwork("192.168.23.0/24");
+ Firewall plugin = initialisePlugin("deny", new RuleInfo[]{firstRule});
+
+ assertEquals(Result.DENIED, plugin.access(ObjectType.VIRTUALHOST, _address));
+
+ // Set IP so that we're connected from the right address
+ _address = new InetSocketAddress("192.168.23.23", 65535);
+ assertEquals(Result.ALLOWED, plugin.access(ObjectType.VIRTUALHOST, _address));
+ }
+
+ public void testCommaSeperatedNetmask() throws Exception
+ {
+ RuleInfo firstRule = new RuleInfo();
+ firstRule.setAccess("allow");
+ firstRule.setNetwork("10.1.1.1/8, 192.168.23.0/24");
+ Firewall plugin = initialisePlugin("deny", new RuleInfo[]{firstRule});
+
+ assertEquals(Result.DENIED, plugin.access(ObjectType.VIRTUALHOST, _address));
+
+ // Set IP so that we're connected from the right address
+ _address = new InetSocketAddress("192.168.23.23", 65535);
+ assertEquals(Result.ALLOWED, plugin.access(ObjectType.VIRTUALHOST, _address));
+ }
+
+ public void testCommaSeperatedHostnames() throws Exception
+ {
+ RuleInfo firstRule = new RuleInfo();
+ firstRule.setAccess("allow");
+ firstRule.setHostname("foo, bar, "+new InetSocketAddress("127.0.0.1", 5672).getHostName());
+ Firewall plugin = initialisePlugin("deny", new RuleInfo[]{firstRule});
+
+ // Set IP so that we're connected from the right address
+ _address = new InetSocketAddress("10.0.0.1", 65535);
+ assertEquals(Result.DENIED, plugin.access(ObjectType.VIRTUALHOST, _address));
+
+ // Set IP so that we're connected from the right address
+ _address = new InetSocketAddress("127.0.0.1", 65535);
+ assertEquals(Result.ALLOWED, plugin.access(ObjectType.VIRTUALHOST, _address));
+ }
+}
diff --git a/qpid/java/broker-plugins/simple-xml/MANIFEST.MF b/qpid/java/broker-plugins/simple-xml/MANIFEST.MF
new file mode 100644
index 0000000000..04fe7518df
--- /dev/null
+++ b/qpid/java/broker-plugins/simple-xml/MANIFEST.MF
@@ -0,0 +1,36 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Qpid Broker-Plugins Simple XML
+Bundle-SymbolicName: broker-plugins-simple-xml
+Bundle-Description: Simple XML ACL 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.security.access.plugins.SimpleXMLActivator
+Bundle-RequiredExecutionEnvironment: JavaSE-1.5
+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,
+ org.apache.qpid.server.security.access,
+ org.apache.qpid.server.virtualhost,
+ org.apache.qpid.util,
+ 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,
+ 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.security.access.config
+Export-Package: org.apache.qpid.server.security.access.plugins
diff --git a/qpid/java/broker-plugins/simple-xml/build.xml b/qpid/java/broker-plugins/simple-xml/build.xml
new file mode 100644
index 0000000000..d3cd451648
--- /dev/null
+++ b/qpid/java/broker-plugins/simple-xml/build.xml
@@ -0,0 +1,29 @@
+<!--
+ - Licensed to the Apache Software Foundation (ASF) under one
+ - or more contributor license agreements. See the NOTICE file
+ - distributed with this work for additional information
+ - regarding copyright ownership. The ASF licenses this file
+ - to you under the Apache License, Version 2.0 (the
+ - "License"); you may not use this file except in compliance
+ - with the License. You may obtain a copy of the License at
+ -
+ - http://www.apache.org/licenses/LICENSE-2.0
+ -
+ - Unless required by applicable law or agreed to in writing,
+ - software distributed under the License is distributed on an
+ - "AS IS" BASIS, WITHOUT 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 Simple XML" default="build">
+ <property name="module.depends" value="common broker broker-plugins" />
+ <property name="module.test.depends" value="test broker/test common/test management/common systests" />
+
+ <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/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/config/PrincipalPermissions.java b/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/config/PrincipalPermissions.java
new file mode 100755
index 0000000000..d9fc292f03
--- /dev/null
+++ b/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/config/PrincipalPermissions.java
@@ -0,0 +1,687 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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.security.access.config;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.security.Result;
+
+@SuppressWarnings("unchecked")
+public class PrincipalPermissions
+{
+ public enum Permission
+ {
+ CONSUME,
+ PUBLISH,
+ CREATEQUEUE,
+ CREATEEXCHANGE,
+ ACCESS,
+ BIND,
+ UNBIND,
+ DELETE,
+ PURGE
+ }
+
+ private static final Logger _logger = Logger.getLogger(PrincipalPermissions.class);
+
+ private static final Object CONSUME_QUEUES_KEY = new Object();
+ private static final Object CONSUME_TEMPORARY_KEY = new Object();
+ private static final Object CONSUME_OWN_QUEUES_ONLY_KEY = new Object();
+
+ private static final Object CREATE_QUEUES_KEY = new Object();
+ private static final Object CREATE_EXCHANGES_KEY = new Object();
+
+
+ private static final Object CREATE_QUEUE_TEMPORARY_KEY = new Object();
+ private static final Object CREATE_QUEUE_QUEUES_KEY = new Object();
+ private static final Object CREATE_QUEUE_EXCHANGES_KEY = new Object();
+
+ private static final Object CREATE_QUEUE_EXCHANGES_ROUTINGKEYS_KEY = new Object();
+
+ private static final int PUBLISH_EXCHANGES_KEY = 0;
+
+ private Map _permissions;
+ private boolean _fullVHostAccess = false;
+
+ private String _user;
+
+
+ public PrincipalPermissions(String user)
+ {
+ _user = user;
+ _permissions = new ConcurrentHashMap();
+ }
+
+ /**
+ *
+ * @param permission the type of permission to check
+ *
+ * @param parameters vararg depending on what permission was passed in
+ * ACCESS: none
+ * BIND: none
+ * CONSUME: AMQShortString queueName, Boolean temporary, Boolean ownQueueOnly
+ * CREATEQUEUE: Boolean temporary, AMQShortString queueName, AMQShortString exchangeName, AMQShortString routingKey
+ * CREATEEXCHANGE: AMQShortString exchangeName, AMQShortString Class
+ * DELETE: none
+ * PUBLISH: Exchange exchange, AMQShortString routingKey
+ * PURGE: none
+ * UNBIND: none
+ */
+ public void grant(Permission permission, Object... parameters)
+ {
+ switch (permission)
+ {
+ case ACCESS:// Parameters : None
+ grantAccess(permission);
+ break;
+ case CONSUME: // Parameters : AMQShortString queueName, Boolean Temporary, Boolean ownQueueOnly
+ grantConsume(permission, parameters);
+ break;
+ case CREATEQUEUE: // Parameters : Boolean temporary, AMQShortString queueName
+ // , AMQShortString exchangeName , AMQShortString routingKey
+ grantCreateQueue(permission, parameters);
+ break;
+ case CREATEEXCHANGE:
+ // Parameters AMQShortString exchangeName , AMQShortString Class
+ grantCreateExchange(permission, parameters);
+ break;
+ case PUBLISH: // Parameters : Exchange exchange, AMQShortString routingKey
+ grantPublish(permission, parameters);
+ break;
+ /* The other cases just fall through to no-op */
+ case DELETE:
+ case BIND: // All the details are currently included in the create setup.
+ case PURGE:
+ case UNBIND:
+ break;
+ }
+
+ }
+
+ private void grantAccess(Permission permission)
+ {
+ _fullVHostAccess = true;
+ }
+
+ private void grantPublish(Permission permission, Object... parameters) {
+ Map publishRights = (Map) _permissions.get(permission);
+
+ if (publishRights == null)
+ {
+ publishRights = new ConcurrentHashMap();
+ _permissions.put(permission, publishRights);
+ }
+
+ if (parameters == null || parameters.length == 0)
+ {
+ //If we have no parameters then allow publish to all destinations
+ // this is signified by having a null value for publish_exchanges
+ }
+ else
+ {
+ Map publish_exchanges = (Map) publishRights.get(PUBLISH_EXCHANGES_KEY);
+
+ if (publish_exchanges == null)
+ {
+ publish_exchanges = new ConcurrentHashMap();
+ publishRights.put(PUBLISH_EXCHANGES_KEY, publish_exchanges);
+ }
+
+
+ HashSet routingKeys = (HashSet) publish_exchanges.get(parameters[0]);
+
+ // Check to see if we have a routing key
+ if (parameters.length == 2)
+ {
+ if (routingKeys == null)
+ {
+ routingKeys = new HashSet<AMQShortString>();
+ }
+ //Add routing key to permitted publish destinations
+ routingKeys.add(parameters[1]);
+ }
+
+ // Add the updated routingkey list or null if all values allowed
+ publish_exchanges.put(parameters[0], routingKeys);
+ }
+ }
+
+ private void grantCreateExchange(Permission permission, Object... parameters) {
+ Map rights = (Map) _permissions.get(permission);
+ if (rights == null)
+ {
+ rights = new ConcurrentHashMap();
+ _permissions.put(permission, rights);
+ }
+
+ Map create_exchanges = (Map) rights.get(CREATE_EXCHANGES_KEY);
+ if (create_exchanges == null)
+ {
+ create_exchanges = new ConcurrentHashMap();
+ rights.put(CREATE_EXCHANGES_KEY, create_exchanges);
+ }
+
+ //Should perhaps error if parameters[0] is null;
+ AMQShortString name = parameters.length > 0 ? (AMQShortString) parameters[0] : null;
+ AMQShortString className = parameters.length > 1 ? (AMQShortString) parameters[1] : new AMQShortString("direct");
+
+ //Store the exchangeName / class mapping if the mapping is null
+ rights.put(name, className);
+ }
+
+ private void grantCreateQueue(Permission permission, Object... parameters)
+ {
+ Map createRights = (Map) _permissions.get(permission);
+
+ if (createRights == null)
+ {
+ createRights = new ConcurrentHashMap();
+ _permissions.put(permission, createRights);
+ }
+
+ //The existence of the empty map mean permission to all.
+ if (parameters.length == 0)
+ {
+ return;
+ }
+
+ // Get the queues map
+ Map create_queues = (Map) createRights.get(CREATE_QUEUES_KEY);
+
+ //Initialiase the queue permissions if not already done
+ if (create_queues == null)
+ {
+ create_queues = new ConcurrentHashMap();
+ //initialise temp queue permission to false and overwrite below if true
+ create_queues.put(CREATE_QUEUE_TEMPORARY_KEY, false);
+ createRights.put(CREATE_QUEUES_KEY, create_queues);
+ }
+
+ //Create empty list of queues
+ Map create_queues_queues = (Map) create_queues.get(CREATE_QUEUE_QUEUES_KEY);
+
+ if (create_queues_queues == null)
+ {
+ create_queues_queues = new ConcurrentHashMap();
+ create_queues.put(CREATE_QUEUE_QUEUES_KEY, create_queues_queues);
+ }
+
+ // If we are initialising and granting CREATE rights to all temporary queues, then that's all we do
+ Boolean temporary = false;
+ if (parameters.length == 1)
+ {
+ temporary = (Boolean) parameters[0];
+ create_queues.put(CREATE_QUEUE_TEMPORARY_KEY, temporary);
+ return;
+ }
+
+ //From here we can be permissioning a variety of things, with varying parameters
+ AMQShortString queueName = parameters.length > 1 ? (AMQShortString) parameters[1] : null;
+ AMQShortString exchangeName = parameters.length > 2 ? (AMQShortString) parameters[2] : null;
+ //Set the routingkey to the specified value or the queueName if present
+ AMQShortString routingKey = (parameters.length > 3 && null != parameters[3]) ? (AMQShortString) parameters[3] : queueName;
+ // if we have a queueName then we need to store any associated exchange / rk bindings
+ if (queueName != null)
+ {
+ Map queue = (Map) create_queues_queues.get(queueName);
+ if (queue == null)
+ {
+ queue = new ConcurrentHashMap();
+ create_queues_queues.put(queueName, queue);
+ }
+
+ if (exchangeName != null)
+ {
+ queue.put(exchangeName, routingKey);
+ }
+
+ //If no exchange is specified then the presence of the queueName in the map says any exchange is ok
+ }
+
+ // Store the exchange that we are being granted rights to. This will be used as part of binding
+
+ //Lookup the list of exchanges
+ Map create_queues_exchanges = (Map) create_queues.get(CREATE_QUEUE_EXCHANGES_KEY);
+
+ if (create_queues_exchanges == null)
+ {
+ create_queues_exchanges = new ConcurrentHashMap();
+ create_queues.put(CREATE_QUEUE_EXCHANGES_KEY, create_queues_exchanges);
+ }
+
+ //if we have an exchange
+ if (exchangeName != null)
+ {
+ //Retrieve the list of permitted exchanges.
+ Map exchanges = (Map) create_queues_exchanges.get(exchangeName);
+
+ if (exchanges == null)
+ {
+ exchanges = new ConcurrentHashMap();
+ create_queues_exchanges.put(exchangeName, exchanges);
+ }
+
+ //Store the binding details of queue/rk for this exchange.
+ if (queueName != null)
+ {
+ //Retrieve the list of permitted routingKeys.
+ Map rKeys = (Map) exchanges.get(exchangeName);
+
+ if (rKeys == null)
+ {
+ rKeys = new ConcurrentHashMap();
+ exchanges.put(CREATE_QUEUE_EXCHANGES_ROUTINGKEYS_KEY, rKeys);
+ }
+
+ rKeys.put(queueName, routingKey);
+ }
+ }
+ }
+
+ /**
+ * Grant consume permissions
+ */
+ private void grantConsume(Permission permission, Object... parameters)
+ {
+ Map consumeRights = (Map) _permissions.get(permission);
+
+ if (consumeRights == null)
+ {
+ consumeRights = new ConcurrentHashMap();
+ _permissions.put(permission, consumeRights);
+
+ //initialise own and temporary rights to false to be overwritten below if set
+ consumeRights.put(CONSUME_TEMPORARY_KEY, false);
+ consumeRights.put(CONSUME_OWN_QUEUES_ONLY_KEY, false);
+ }
+
+
+ //if we only have one param then we're permissioning temporary queues and topics
+ if (parameters.length == 1)
+ {
+ Boolean temporary = (Boolean) parameters[0];
+
+ if (temporary)
+ {
+ consumeRights.put(CONSUME_TEMPORARY_KEY, true);
+ }
+ }
+
+ //if we have 2 parameters - should be a contract for this, but for now we'll handle it as is
+ if (parameters.length == 2)
+ {
+ AMQShortString queueName = (AMQShortString) parameters[0];
+ Boolean ownQueueOnly = (Boolean) parameters[1];
+
+ if (ownQueueOnly)
+ {
+ consumeRights.put(CONSUME_OWN_QUEUES_ONLY_KEY, true);
+ }
+
+ LinkedList queues = (LinkedList) consumeRights.get(CONSUME_QUEUES_KEY);
+ if (queues == null)
+ {
+ queues = new LinkedList();
+ consumeRights.put(CONSUME_QUEUES_KEY, queues);
+ }
+
+ if (queueName != null)
+ {
+ queues.add(queueName);
+ }
+ }
+ }
+
+ /**
+ *
+ * @param permission the type of permission to check
+ *
+ * @param parameters vararg depending on what permission was passed in
+ * ACCESS: none
+ * BIND: QueueBindBody bindmethod, Exchange exchange, AMQQueue queue, AMQShortString routingKey
+ * CONSUME: AMQQueue queue
+ * CREATEQUEUE: Boolean autodelete, AMQShortString name
+ * CREATEEXCHANGE: AMQShortString exchangeName
+ * DELETE: none
+ * PUBLISH: Exchange exchange, AMQShortString routingKey
+ * PURGE: none
+ * UNBIND: none
+ */
+ public Result authorise(Permission permission, String... parameters)
+ {
+
+ switch (permission)
+ {
+ case ACCESS://No Parameters
+ return Result.ALLOWED; // The existence of this user-specific PP infers some level of access is authorised
+ case BIND: // Parameters : QueueBindMethod , exhangeName , queueName, routingKey
+ return authoriseBind(parameters);
+ case CREATEQUEUE:// Parameters : autoDelete, queueName
+ return authoriseCreateQueue(permission, parameters);
+ case CREATEEXCHANGE: //Parameters: exchangeName
+ return authoriseCreateExchange(permission, parameters);
+ case CONSUME: // Parameters : queueName, autoDelete, owner
+ return authoriseConsume(permission, parameters);
+ case PUBLISH: // Parameters : exchangeName, routingKey
+ return authorisePublish(permission, parameters);
+ /* Fall through */
+ case DELETE:
+ case PURGE:
+ case UNBIND:
+ default:
+ if(_fullVHostAccess)
+ {
+ //user has been granted full access to the vhost
+ return Result.ALLOWED;
+ }
+ else
+ {
+ //SimpleXML ACL does not implement these permissions and should abstain
+ return Result.ABSTAIN;
+ }
+ }
+
+ }
+
+ private Result authoriseConsume(Permission permission, String... parameters)
+ {
+ if(_fullVHostAccess)
+ {
+ //user has been granted full access to the vhost
+ return Result.ALLOWED;
+ }
+
+ if (parameters.length == 3)
+ {
+ AMQShortString queueName = new AMQShortString(parameters[0]);
+ Boolean autoDelete = Boolean.valueOf(parameters[1]);
+ AMQShortString owner = new AMQShortString(parameters[2]);
+ Map queuePermissions = (Map) _permissions.get(permission);
+
+ _logger.error("auth consume on " + StringUtils.join(parameters, ", "));
+
+ if (queuePermissions == null)
+ {
+ //we have a problem - we've never granted this type of permission .....
+ return Result.DENIED;
+ }
+
+ List queues = (List) queuePermissions.get(CONSUME_QUEUES_KEY);
+
+ Boolean temporaryQueues = (Boolean) queuePermissions.get(CONSUME_TEMPORARY_KEY);
+ Boolean ownQueuesOnly = (Boolean) queuePermissions.get(CONSUME_OWN_QUEUES_ONLY_KEY);
+
+
+ // If user is allowed to consume from temporary queues and this is a temp queue then allow it.
+ if (temporaryQueues && autoDelete)
+ {
+ // This will allow consumption from any temporary queue including ones not owned by this user.
+ // Of course the exclusivity will not be broken.
+ {
+
+ // if not limited to ownQueuesOnly then ok else check queue Owner.
+ return (!ownQueuesOnly || owner.equals(_user)) ? Result.ALLOWED : Result.DENIED;
+ }
+ }
+ //if this is a temporary queue and the user does not have permissions for temporary queues then deny
+ else if (!temporaryQueues && autoDelete)
+ {
+ return Result.DENIED;
+ }
+
+ // if queues are white listed then ensure it is ok
+ if (queues != null)
+ {
+ // if no queues are listed then ALL are ok othereise it must be specified.
+ if (ownQueuesOnly)
+ {
+ if (owner.equals(_user))
+ {
+ return (queues.size() == 0 || queues.contains(queueName)) ? Result.ALLOWED : Result.DENIED;
+ }
+ else
+ {
+ return Result.DENIED;
+ }
+ }
+
+ // If we are
+ return (queues.size() == 0 || queues.contains(queueName)) ? Result.ALLOWED : Result.DENIED;
+ }
+ }
+
+ // Can't authenticate without the right parameters
+ return Result.DENIED;
+ }
+
+ private Result authorisePublish(Permission permission, String... parameters)
+ {
+ if(_fullVHostAccess)
+ {
+ //user has been granted full access to the vhost
+ return Result.ALLOWED;
+ }
+
+ Map publishRights = (Map) _permissions.get(permission);
+
+ if (publishRights == null)
+ {
+ return Result.DENIED;
+ }
+
+ Map exchanges = (Map) publishRights.get(PUBLISH_EXCHANGES_KEY);
+
+ // Having no exchanges listed gives full publish rights to all exchanges
+ if (exchanges == null)
+ {
+ return Result.ALLOWED;
+ }
+ // Otherwise exchange must be listed in the white list
+
+ // If the map doesn't have the exchange then it isn't allowed
+ AMQShortString exchangeName = new AMQShortString(parameters[0]);
+ if (!exchanges.containsKey(exchangeName))
+ {
+ return Result.DENIED;
+ }
+ else
+ {
+ // Get valid routing keys
+ HashSet routingKeys = (HashSet) exchanges.get(exchangeName);
+
+ // Having no routingKeys in the map then all are allowed.
+ if (routingKeys == null)
+ {
+ return Result.ALLOWED;
+ }
+ else
+ {
+ // We have routingKeys so a match must be found to allowed binding
+ Iterator keys = routingKeys.iterator();
+
+ AMQShortString publishRKey = new AMQShortString(parameters[1]);
+
+ boolean matched = false;
+ while (keys.hasNext() && !matched)
+ {
+ AMQShortString rkey = (AMQShortString) keys.next();
+
+ if (rkey.endsWith("*"))
+ {
+ matched = publishRKey.startsWith(rkey.subSequence(0, rkey.length() - 1));
+ }
+ else
+ {
+ matched = publishRKey.equals(rkey);
+ }
+ }
+ return (matched) ? Result.ALLOWED : Result.DENIED;
+ }
+ }
+ }
+
+ private Result authoriseCreateExchange(Permission permission, String... parameters)
+ {
+ if(_fullVHostAccess)
+ {
+ //user has been granted full access to the vhost
+ return Result.ALLOWED;
+ }
+
+ Map rights = (Map) _permissions.get(permission);
+
+ AMQShortString exchangeName = new AMQShortString(parameters[0]);
+
+ // If the exchange list is doesn't exist then all is allowed else
+ // check the valid exchanges
+ if (rights == null || rights.containsKey(exchangeName))
+ {
+ return Result.ALLOWED;
+ }
+ else
+ {
+ return Result.DENIED;
+ }
+ }
+
+ private Result authoriseCreateQueue(Permission permission, String... parameters)
+ {
+ if(_fullVHostAccess)
+ {
+ //user has been granted full access to the vhost
+ return Result.ALLOWED;
+ }
+
+ Map createRights = (Map) _permissions.get(permission);
+
+ // If there are no create rights then deny request
+ if (createRights == null)
+ {
+ return Result.DENIED;
+ }
+
+ //Look up the Queue Creation Rights
+ Map create_queues = (Map) createRights.get(CREATE_QUEUES_KEY);
+
+ //Lookup the list of queues allowed to be created
+ Map create_queues_queues = (Map) create_queues.get(CREATE_QUEUE_QUEUES_KEY);
+
+
+ Boolean autoDelete = Boolean.valueOf(parameters[0]);
+ AMQShortString queueName = new AMQShortString(parameters[1]);
+
+ if (autoDelete)// we have a temporary queue
+ {
+ return ((Boolean) create_queues.get(CREATE_QUEUE_TEMPORARY_KEY)) ? Result.ALLOWED : Result.DENIED;
+ }
+ else
+ {
+ // If there is a white list then check
+ if (create_queues_queues == null || create_queues_queues.containsKey(queueName))
+ {
+ return Result.ALLOWED;
+ }
+ else
+ {
+ return Result.DENIED;
+ }
+
+ }
+ }
+
+ private Result authoriseBind(String... parameters)
+ {
+ if(_fullVHostAccess)
+ {
+ //user has been granted full access to the vhost
+ return Result.ALLOWED;
+ }
+
+ AMQShortString exchangeName = new AMQShortString(parameters[1]);
+ AMQShortString bind_queueName = new AMQShortString(parameters[2]);
+ AMQShortString routingKey = new AMQShortString(parameters[3]);
+
+ //Get all Create Rights for this user
+ Map bindCreateRights = (Map) _permissions.get(Permission.CREATEQUEUE);
+
+ //Lookup the list of queues
+ Map bind_create_queues_queues = (Map) bindCreateRights.get(CREATE_QUEUE_QUEUES_KEY);
+
+ // Check and see if we have a queue white list to check
+ if (bind_create_queues_queues != null)
+ {
+ //There a white list for queues
+ Map exchangeDetails = (Map) bind_create_queues_queues.get(bind_queueName);
+
+ if (exchangeDetails == null) //Then all queue can be bound to all exchanges.
+ {
+ return Result.ALLOWED;
+ }
+
+ // Check to see if we have a white list of routingkeys to check
+ Map rkeys = (Map) exchangeDetails.get(exchangeName);
+
+ // if keys is null then any rkey is allowed on this exchange
+ if (rkeys == null)
+ {
+ // There is no routingkey white list
+ return Result.ALLOWED;
+ }
+ else
+ {
+ // We have routingKeys so a match must be found to allowed binding
+ Iterator keys = rkeys.keySet().iterator();
+
+ boolean matched = false;
+ while (keys.hasNext() && !matched)
+ {
+ AMQShortString rkey = (AMQShortString) keys.next();
+ if (rkey.endsWith("*"))
+ {
+ matched = routingKey.startsWith(rkey.subSequence(0, rkey.length() - 1).toString());
+ }
+ else
+ {
+ matched = routingKey.equals(rkey);
+ }
+ }
+
+
+ return (matched) ? Result.ALLOWED : Result.DENIED;
+ }
+
+
+ }
+ else
+ {
+ //no white list so all allowed.
+ return Result.ALLOWED;
+ }
+ }
+}
diff --git a/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXML.java b/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXML.java
new file mode 100644
index 0000000000..ab43653122
--- /dev/null
+++ b/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXML.java
@@ -0,0 +1,427 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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.security.access.plugins;
+
+import static org.apache.qpid.server.security.access.ObjectProperties.Property.*;
+
+import java.security.Principal;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.log4j.Logger;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin;
+import org.apache.qpid.server.security.AbstractPlugin;
+import org.apache.qpid.server.security.Result;
+import org.apache.qpid.server.security.SecurityManager;
+import org.apache.qpid.server.security.SecurityPluginFactory;
+import org.apache.qpid.server.security.access.ObjectProperties;
+import org.apache.qpid.server.security.access.ObjectType;
+import org.apache.qpid.server.security.access.Operation;
+import org.apache.qpid.server.security.access.config.PrincipalPermissions;
+import org.apache.qpid.server.security.access.config.PrincipalPermissions.Permission;
+
+/**
+ * This uses the default
+ */
+public class SimpleXML extends AbstractPlugin
+{
+ public static final Logger _logger = Logger.getLogger(SimpleXML.class);
+
+ private Map<String, PrincipalPermissions> _users;
+
+ public static final SecurityPluginFactory<SimpleXML> FACTORY = new SecurityPluginFactory<SimpleXML>()
+ {
+ public SimpleXML newInstance(ConfigurationPlugin config) throws ConfigurationException
+ {
+ SimpleXMLConfiguration configuration = config.getConfiguration(SimpleXMLConfiguration.class.getName());
+
+ // If there is no configuration for this plugin then don't load it.
+ if (configuration == null)
+ {
+ return null;
+ }
+
+ SimpleXML plugin = new SimpleXML();
+ plugin.configure(configuration);
+ return plugin;
+ }
+
+ public String getPluginName()
+ {
+ return SimpleXML.class.getName();
+ }
+
+ public Class<SimpleXML> getPluginClass()
+ {
+ return SimpleXML.class;
+ }
+ };
+
+ public void configure(ConfigurationPlugin config)
+ {
+ super.configure(config);
+
+ SimpleXMLConfiguration configuration = (SimpleXMLConfiguration) _config;
+
+ _users = new ConcurrentHashMap<String, PrincipalPermissions>();
+
+ processConfig(configuration.getConfiguration());
+ }
+
+ private void processConfig(Configuration config)
+ {
+ processPublish(config);
+ processConsume(config);
+ processCreate(config);
+ processAccess(config);
+ }
+
+ /**
+ * @param config XML Configuration
+ */
+ private void processAccess(Configuration config)
+ {
+ Configuration accessConfig = config.subset("access");
+
+ if (accessConfig.isEmpty())
+ {
+ //there is no access configuration to process
+ return;
+ }
+
+ // Process users that have full access permission
+ String[] users = accessConfig.getStringArray("users.user");
+
+ for (String user : users)
+ {
+ grant(Permission.ACCESS, user);
+ }
+ }
+
+ /**
+ * Publish format takes Exchange + Routing Key Pairs
+ *
+ * @param config XML Configuration
+ */
+ private void processPublish(Configuration config)
+ {
+ Configuration publishConfig = config.subset("publish");
+
+ // Process users that have full publish permission
+ String[] users = publishConfig.getStringArray("users.user");
+
+ for (String user : users)
+ {
+ grant(Permission.PUBLISH, user);
+ }
+
+ // Process exchange limited users
+ int exchangeCount = 0;
+ Configuration exchangeConfig = publishConfig.subset("exchanges.exchange(" + exchangeCount + ")");
+
+ while (!exchangeConfig.isEmpty())
+ {
+ // Get Exchange Name
+ AMQShortString exchangeName = new AMQShortString(exchangeConfig.getString("name"));
+
+ // Get Routing Keys
+ int keyCount = 0;
+ Configuration routingkeyConfig = exchangeConfig.subset("routing_keys.routing_key(" + keyCount + ")");
+
+ while (!routingkeyConfig.isEmpty())
+ {
+ // Get RoutingKey Value
+ AMQShortString routingKeyValue = new AMQShortString(routingkeyConfig.getString("value"));
+
+ // Apply Exchange + RoutingKey permissions to Users
+ users = routingkeyConfig.getStringArray("users.user");
+ for (String user : users)
+ {
+ grant(Permission.PUBLISH, user, exchangeName, routingKeyValue);
+ }
+
+ // Apply permissions to Groups
+
+ // Check for more configs
+ keyCount++;
+ routingkeyConfig = exchangeConfig.subset("routing_keys.routing_key(" + keyCount + ")");
+ }
+
+ // Apply Exchange wide permissions to Users
+ users = exchangeConfig.getStringArray("exchange(" + exchangeCount + ").users.user");
+
+ for (String user : users)
+ {
+ grant(Permission.PUBLISH, user, exchangeName);
+ }
+
+ // Apply permissions to Groups
+ exchangeCount++;
+ exchangeConfig = publishConfig.subset("exchanges.exchange(" + exchangeCount + ")");
+ }
+ }
+
+ private void grant(Permission permission, String user, Object... parameters)
+ {
+ PrincipalPermissions permissions = _users.get(user);
+
+ if (permissions == null)
+ {
+ permissions = new PrincipalPermissions(user);
+ }
+
+ _users.put(user, permissions);
+ permissions.grant(permission, parameters);
+ }
+
+ /**
+ * @param config XML Configuration
+ */
+ private void processConsume(Configuration config)
+ {
+ boolean temporary = false;
+ Configuration tempConfig = null;
+ Configuration consumeConfig = config.subset("consume");
+
+ tempConfig = consumeConfig.subset("queues.temporary(0)");
+ if (tempConfig != null)
+ {
+ temporary = true;
+ }
+
+ //Permission all users who have rights to temp queues and topics
+ if (tempConfig != null && !tempConfig.isEmpty())
+ {
+ String[] tempUsers = tempConfig.getStringArray("users.user");
+ for (String user : tempUsers)
+ {
+ grant(Permission.CONSUME, user, temporary);
+ }
+ }
+
+ // Process queue limited users
+ int queueCount = 0;
+ Configuration queueConfig = consumeConfig.subset("queues.queue(" + queueCount + ")");
+
+ while (!queueConfig.isEmpty())
+ {
+ // Get queue Name
+ AMQShortString queueName = new AMQShortString(queueConfig.getString("name"));
+ // if there is no name then there may be a temporary element
+
+ boolean ownQueues = queueConfig.containsKey("own_queues");
+
+ // Process permissions for this queue
+ String[] users = queueConfig.getStringArray("users.user");
+ for (String user : users)
+ {
+ grant(Permission.CONSUME, user, queueName, ownQueues);
+ }
+
+ // See if we have another config
+ queueCount++;
+ queueConfig = consumeConfig.subset("queues.queue(" + queueCount + ")");
+ }
+
+ // Process users that have full consume permission
+ String[] users = consumeConfig.getStringArray("users.user");
+
+ for (String user : users)
+ {
+ //NOTE: this call does not appear to do anything inside the grant section for consume
+ grant(Permission.CONSUME, user);
+ }
+ }
+
+ /**
+ * @param config XML Configuration
+ */
+ private void processCreate(Configuration config)
+ {
+ boolean temporary = false;
+ Configuration tempConfig = null;
+
+ Configuration createConfig = config.subset("create");
+
+ tempConfig = createConfig.subset("queues.temporary(0)");
+ if (tempConfig != null)
+ {
+ temporary = true;
+ }
+
+ //Permission all users who have rights to temp queues and topics
+ if (tempConfig != null && !tempConfig.isEmpty())
+ {
+ String[] tempUsers = tempConfig.getStringArray("users.user");
+ for (String user : tempUsers)
+ {
+ grant(Permission.CREATEQUEUE, user, temporary);
+ }
+ }
+ // Process create permissions for queue creation
+ int queueCount = 0;
+ Configuration queueConfig = createConfig.subset("queues.queue(" + queueCount + ")");
+
+ while (!queueConfig.isEmpty())
+ {
+ // Get queue Name
+ AMQShortString queueName = new AMQShortString(queueConfig.getString("name"));
+
+ int exchangeCount = 0;
+ Configuration exchangeConfig = queueConfig.subset("exchanges.exchange(" + exchangeCount + ")");
+
+ while (!exchangeConfig.isEmpty())
+ {
+
+ AMQShortString exchange = new AMQShortString(exchangeConfig.getString("name"));
+ AMQShortString routingKey = new AMQShortString(exchangeConfig.getString("routing_key"));
+
+ // Process permissions for this queue
+ String[] users = exchangeConfig.getStringArray("users.user");
+ for (String user : users)
+ {
+ //This is broken as the user name is not stored
+ grant(Permission.CREATEEXCHANGE, user, exchange);
+
+ //This call could be cleaned up as temporary is now being set earlier (above)
+ grant(Permission.CREATEQUEUE, user, temporary, (queueName.equals("") ? null : queueName), (exchange
+ .equals("") ? null : exchange), (routingKey.equals("") ? null : routingKey));
+ }
+
+ // See if we have another config
+ exchangeCount++;
+ exchangeConfig = queueConfig.subset("exchanges.exchange(" + exchangeCount + ")");
+ }
+
+ // Process users that are not bound to an exchange
+ String[] users = queueConfig.getStringArray("users.user");
+
+ for (String user : users)
+ {
+ grant(Permission.CREATEQUEUE, user, temporary, queueName);
+ }
+
+ // See if we have another config
+ queueCount++;
+ queueConfig = createConfig.subset("queues.queue(" + queueCount + ")");
+ }
+
+ // Process create permissions for exchange creation
+ int exchangeCount = 0;
+ Configuration exchangeConfig = createConfig.subset("exchanges.exchange(" + exchangeCount + ")");
+
+ while (!exchangeConfig.isEmpty())
+ {
+ AMQShortString exchange = new AMQShortString(exchangeConfig.getString("name"));
+ AMQShortString clazz = new AMQShortString(exchangeConfig.getString("class"));
+
+ // Process permissions for this queue
+ String[] users = exchangeConfig.getStringArray("users.user");
+ for (String user : users)
+ {
+ //And this is broken too
+ grant(Permission.CREATEEXCHANGE, user, exchange, clazz);
+ }
+
+ // See if we have another config
+ exchangeCount++;
+ exchangeConfig = queueConfig.subset("exchanges.exchange(" + exchangeCount + ")");
+ }
+
+ // Process users that have full create permission
+ String[] users = createConfig.getStringArray("users.user");
+
+ for (String user : users)
+ {
+ grant(Permission.CREATEEXCHANGE, user);
+ grant(Permission.CREATEQUEUE, user);
+ }
+ }
+
+ public Result access(ObjectType objectType, Object instance)
+ {
+ Principal principal = SecurityManager.getThreadPrincipal();
+ if (principal == null)
+ {
+ return getDefault(); // Default if there is no user associated with the thread
+ }
+ PrincipalPermissions principalPermissions = _users.get(principal.getName());
+ if (principalPermissions == null)
+ {
+ return Result.DENIED;
+ }
+
+ // Authorise object access
+ if (objectType == ObjectType.VIRTUALHOST)
+ {
+ return principalPermissions.authorise(Permission.ACCESS);
+ }
+
+ // Default
+ return getDefault();
+ }
+
+ public Result authorise(Operation operation, ObjectType objectType, ObjectProperties properties)
+ {
+ Principal principal = SecurityManager.getThreadPrincipal();
+ if (principal == null)
+ {
+ return getDefault(); // Default if there is no user associated with the thread
+ }
+ PrincipalPermissions principalPermissions = _users.get(principal.getName());
+ if (principalPermissions == null)
+ {
+ return Result.DENIED;
+ }
+
+ // Authorise operation
+ switch (operation)
+ {
+ case CONSUME:
+ return principalPermissions.authorise(Permission.CONSUME, properties.get(NAME), properties.get(AUTO_DELETE), properties.get(OWNER));
+ case PUBLISH:
+ return principalPermissions.authorise(Permission.PUBLISH, properties.get(NAME), properties.get(ROUTING_KEY));
+ case CREATE:
+ if (objectType == ObjectType.EXCHANGE)
+ {
+ return principalPermissions.authorise(Permission.CREATEEXCHANGE, properties.get(NAME));
+ }
+ else if (objectType == ObjectType.QUEUE)
+ {
+ return principalPermissions.authorise(Permission.CREATEQUEUE, properties.get(AUTO_DELETE), properties.get(NAME));
+ }
+ case ACCESS:
+ return principalPermissions.authorise(Permission.ACCESS);
+ case BIND:
+ return principalPermissions.authorise(Permission.BIND, null, properties.get(NAME), properties.get(QUEUE_NAME), properties.get(ROUTING_KEY));
+ case UNBIND:
+ return principalPermissions.authorise(Permission.UNBIND);
+ case DELETE:
+ return principalPermissions.authorise(Permission.DELETE);
+ case PURGE:
+ return principalPermissions.authorise(Permission.PURGE);
+ }
+
+ // Default
+ return getDefault();
+ }
+}
diff --git a/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXMLActivator.java b/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXMLActivator.java
new file mode 100644
index 0000000000..c09a9da0d8
--- /dev/null
+++ b/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXMLActivator.java
@@ -0,0 +1,42 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.access.plugins;
+
+import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory;
+import org.apache.qpid.server.security.SecurityPluginActivator;
+import org.apache.qpid.server.security.SecurityPluginFactory;
+import org.osgi.framework.BundleActivator;
+
+/**
+ * The OSGi {@link BundleActivator} for {@link SimpleXML}.
+ */
+public class SimpleXMLActivator extends SecurityPluginActivator
+{
+ public SecurityPluginFactory getFactory()
+ {
+ return SimpleXML.FACTORY;
+ }
+
+ public ConfigurationPluginFactory getConfigurationFactory()
+ {
+ return SimpleXMLConfiguration.FACTORY;
+ }
+}
diff --git a/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXMLConfiguration.java b/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXMLConfiguration.java
new file mode 100644
index 0000000000..e95c21b590
--- /dev/null
+++ b/qpid/java/broker-plugins/simple-xml/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXMLConfiguration.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.security.access.plugins;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin;
+import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory;
+
+public class SimpleXMLConfiguration extends ConfigurationPlugin
+{
+ public static final ConfigurationPluginFactory FACTORY = new ConfigurationPluginFactory()
+ {
+ public ConfigurationPlugin newInstance(String path, Configuration config) throws ConfigurationException
+ {
+ ConfigurationPlugin instance = new SimpleXMLConfiguration();
+ instance.setConfiguration(path, config);
+ return instance;
+ }
+
+ public List<String> getParentPaths()
+ {
+ return Arrays.asList("security.access_control_list", "virtualhosts.virtualhost.security.access_control_list");
+ }
+ };
+
+ public String[] getElementsProcessed()
+ {
+ return new String[] { "" };
+ }
+
+ public Configuration getConfiguration()
+ {
+ return _configuration;
+ }
+}
diff --git a/qpid/java/broker-plugins/simple-xml/src/test/java/org/apache/qpid/server/security/access/PrincipalPermissionsTest.java b/qpid/java/broker-plugins/simple-xml/src/test/java/org/apache/qpid/server/security/access/PrincipalPermissionsTest.java
new file mode 100644
index 0000000000..65ab12a095
--- /dev/null
+++ b/qpid/java/broker-plugins/simple-xml/src/test/java/org/apache/qpid/server/security/access/PrincipalPermissionsTest.java
@@ -0,0 +1,209 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.security.access;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.security.Result;
+import org.apache.qpid.server.security.access.config.PrincipalPermissions;
+import org.apache.qpid.server.security.access.config.PrincipalPermissions.Permission;
+import org.apache.qpid.test.utils.QpidTestCase;
+
+public class PrincipalPermissionsTest extends QpidTestCase
+{
+ private String _user = "user";
+ private PrincipalPermissions _perms;
+
+ // Common things that are passed to frame constructors
+ private AMQShortString _queueName = new AMQShortString(this.getClass().getName() + "queue");
+ private AMQShortString _tempQueueName = new AMQShortString(this.getClass().getName() + "tempqueue");
+ private AMQShortString _exchangeName = new AMQShortString("amq.direct");
+ private AMQShortString _routingKey = new AMQShortString(this.getClass().getName() + "route");
+ private boolean _autoDelete = false;
+ private AMQShortString _exchangeType = new AMQShortString("direct");
+ private Boolean _temporary = false;
+ private Boolean _ownQueue = false;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+
+ _perms = new PrincipalPermissions(_user);
+ }
+
+
+ public void testPrincipalPermissions()
+ {
+ assertNotNull(_perms);
+ assertEquals(Result.ALLOWED, _perms.authorise(Permission.ACCESS, (String[]) null));
+ }
+
+ // FIXME: test has been disabled since the permissions assume that the user has tried to create
+ // the queue first. QPID-1597
+ public void disableTestBind() throws Exception
+ {
+ String[] args = new String[]{null, _exchangeName.asString(), _queueName.asString(), _routingKey.asString()};
+
+ assertEquals(Result.DENIED, _perms.authorise(Permission.BIND, args));
+ _perms.grant(Permission.BIND, (Object[]) null);
+ assertEquals(Result.ALLOWED, _perms.authorise(Permission.BIND, args));
+ }
+
+ public void testQueueCreate()
+ {
+ Object[] grantArgs = new Object[]{_temporary , _queueName, _exchangeName, _routingKey};
+ String[] authArgs = new String[]{Boolean.toString(_autoDelete), _queueName.asString()};
+
+ assertEquals(Result.DENIED, _perms.authorise(Permission.CREATEQUEUE, authArgs));
+ _perms.grant(Permission.CREATEQUEUE, grantArgs);
+ assertEquals(Result.ALLOWED, _perms.authorise(Permission.CREATEQUEUE, authArgs));
+ }
+
+ public void testQueueCreateWithNullRoutingKey()
+ {
+ Object[] grantArgs = new Object[]{_temporary , _queueName, _exchangeName, null};
+ String[] authArgs = new String[]{Boolean.toString(_autoDelete), _queueName.asString()};
+
+ assertEquals(Result.DENIED, _perms.authorise(Permission.CREATEQUEUE, authArgs));
+ _perms.grant(Permission.CREATEQUEUE, grantArgs);
+ assertEquals(Result.ALLOWED, _perms.authorise(Permission.CREATEQUEUE, authArgs));
+ }
+
+ // FIXME disabled, this fails due to grant putting the grant into the wrong map QPID-1598
+ public void disableTestExchangeCreate()
+ {
+ String[] authArgs = new String[]{_exchangeName.asString()};
+ Object[] grantArgs = new Object[]{_exchangeName, _exchangeType};
+
+ assertEquals(Result.DENIED, _perms.authorise(Permission.CREATEEXCHANGE, authArgs));
+ _perms.grant(Permission.CREATEEXCHANGE, grantArgs);
+ assertEquals(Result.ALLOWED, _perms.authorise(Permission.CREATEEXCHANGE, authArgs));
+ }
+
+ public void testConsume()
+ {
+ String[] authArgs = new String[]{_queueName.asString(), Boolean.toString(_autoDelete), _user};
+ Object[] grantArgs = new Object[]{_queueName, _ownQueue};
+
+ // FIXME: This throws a null pointer exception QPID-1599
+ // assertFalse(_perms.authorise(Permission.CONSUME, authArgs));
+ _perms.grant(Permission.CONSUME, grantArgs);
+ assertEquals(Result.ALLOWED, _perms.authorise(Permission.CONSUME, authArgs));
+ }
+
+ public void testPublish() throws AMQException
+ {
+ String[] authArgs = new String[]{_exchangeName.asString(), _routingKey.asString()};
+ Object[] grantArgs = new Object[]{_exchangeName, _routingKey};
+
+ assertEquals(Result.DENIED, _perms.authorise(Permission.PUBLISH, authArgs));
+ _perms.grant(Permission.PUBLISH, grantArgs);
+ assertEquals(Result.ALLOWED, _perms.authorise(Permission.PUBLISH, authArgs));
+ }
+
+ public void testVhostAccess()
+ {
+ //Tests that granting a user Virtualhost level access allows all authorisation requests
+ //where previously they would be denied
+
+ //QPID-2133 createExchange rights currently allow all exchange creation unless rights for creating some
+ //specific exchanges are granted. Grant a specific exchange creation to cause all others to be denied.
+ Object[] createArgsCreateExchange = new Object[]{new AMQShortString("madeup"), _exchangeType};
+ String[] authArgsCreateExchange = new String[]{_exchangeName.asString()};
+ assertEquals("Exchange creation was not allowed", Result.ALLOWED, _perms.authorise(Permission.CREATEEXCHANGE, authArgsCreateExchange));
+ _perms.grant(Permission.CREATEEXCHANGE, createArgsCreateExchange);
+
+ String[] authArgsPublish = new String[]{_exchangeName.asString(), _routingKey.asString()};
+ String[] authArgsConsume = new String[]{_queueName.asString(), Boolean.toString(_autoDelete), _user};
+ String[] authArgsCreateQueue = new String[]{Boolean.toString(_autoDelete), _queueName.asString()};
+// QueueBindBodyImpl bind = new QueueBindBodyImpl(_ticket, _queueName, _exchangeName, _routingKey, _nowait, _arguments);
+ String[] authArgsBind = new String[]{ null, _exchangeName.asString(), _queueName.asString(), _routingKey.asString()};
+
+ assertEquals("Exchange creation was not denied", Result.DENIED, _perms.authorise(Permission.CREATEEXCHANGE, authArgsCreateExchange));
+ assertEquals("Publish was not denied", Result.DENIED, _perms.authorise(Permission.PUBLISH, authArgsPublish));
+ assertEquals("Consume creation was not denied", Result.DENIED, _perms.authorise(Permission.CONSUME, authArgsConsume));
+ assertEquals("Queue creation was not denied", Result.DENIED, _perms.authorise(Permission.CREATEQUEUE, authArgsCreateQueue));
+ //BIND pre-grant authorise check disabled due to QPID-1597
+ //assertEquals("Binding creation was not denied", Result.DENIED, _perms.authorise(Permission.BIND, authArgsBind));
+
+ _perms.grant(Permission.ACCESS);
+
+ assertEquals("Exchange creation was not allowed", Result.ALLOWED, _perms.authorise(Permission.CREATEEXCHANGE, authArgsCreateExchange));
+ assertEquals("Publish was not allowed", Result.ALLOWED, _perms.authorise(Permission.PUBLISH, authArgsPublish));
+ assertEquals("Consume creation was not allowed", Result.ALLOWED, _perms.authorise(Permission.CONSUME, authArgsConsume));
+ assertEquals("Queue creation was not allowed", Result.ALLOWED, _perms.authorise(Permission.CREATEQUEUE, authArgsCreateQueue));
+ assertEquals("Binding creation was not allowed", Result.ALLOWED, _perms.authorise(Permission.BIND, authArgsBind));
+ }
+
+ /**
+ * If the consume permission for temporary queues is for an unnamed queue then is should
+ * be global for any temporary queue but not for any non-temporary queue
+ */
+ public void testTemporaryUnnamedQueueConsume()
+ {
+ String[] authNonTempQArgs = new String[]{_queueName.asString(), Boolean.toString(_autoDelete), _user};
+ String[] authTempQArgs = new String[]{_tempQueueName.asString(), Boolean.TRUE.toString(), _user};
+ Object[] grantArgs = new Object[]{true};
+
+ _perms.grant(Permission.CONSUME, grantArgs);
+
+ //Next line shows up bug - non temp queue should be denied
+ assertEquals(Result.DENIED, _perms.authorise(Permission.CONSUME, authNonTempQArgs));
+ assertEquals(Result.ALLOWED, _perms.authorise(Permission.CONSUME, authTempQArgs));
+ }
+
+ /**
+ * Test that temporary queue permissions before queue perms in the ACL config work correctly
+ */
+ public void testTemporaryQueueFirstConsume()
+ {
+ String[] authNonTempQArgs = new String[]{_queueName.asString(), Boolean.toString(_autoDelete), _user};
+ String[] authTempQArgs = new String[]{_tempQueueName.asString(), Boolean.TRUE.toString(), _user};
+ Object[] grantArgs = new Object[]{true};
+ Object[] grantNonTempQArgs = new Object[]{_queueName, _ownQueue};
+
+ //should not matter if the temporary permission is processed first or last
+ _perms.grant(Permission.CONSUME, grantNonTempQArgs);
+ _perms.grant(Permission.CONSUME, grantArgs);
+
+ assertEquals(Result.ALLOWED, _perms.authorise(Permission.CONSUME, authNonTempQArgs));
+ assertEquals(Result.ALLOWED, _perms.authorise(Permission.CONSUME, authTempQArgs));
+ }
+
+ /**
+ * Test that temporary queue permissions after queue perms in the ACL config work correctly
+ */
+ public void testTemporaryQueueLastConsume()
+ {
+ String[] authNonTempQArgs = new String[]{_queueName.asString(), Boolean.toString(_autoDelete), _user};
+ String[] authTempQArgs = new String[]{_tempQueueName.asString(), Boolean.TRUE.toString(), _user};
+ Object[] grantArgs = new Object[]{true};
+ Object[] grantNonTempQArgs = new Object[]{_queueName, _ownQueue};
+
+ //should not matter if the temporary permission is processed first or last
+ _perms.grant(Permission.CONSUME, grantArgs);
+ _perms.grant(Permission.CONSUME, grantNonTempQArgs);
+
+ assertEquals(Result.ALLOWED, _perms.authorise(Permission.CONSUME, authNonTempQArgs));
+ assertEquals(Result.ALLOWED, _perms.authorise(Permission.CONSUME, authTempQArgs));
+ }
+}