diff options
author | Alex Rudyy <orudyy@apache.org> | 2013-03-20 17:07:58 +0000 |
---|---|---|
committer | Alex Rudyy <orudyy@apache.org> | 2013-03-20 17:07:58 +0000 |
commit | ca84abd2a227eb09d483c66b8a3dcf7211484ee7 (patch) | |
tree | bcf165fde7045da2e3d94ddfc17a82c891a6b779 | |
parent | f0858c211bcf8c9666ba2a0bbbf6d28b8a90d885 (diff) | |
download | qpid-python-ca84abd2a227eb09d483c66b8a3dcf7211484ee7.tar.gz |
QPID-4661: Add UI into java broker web management console to edit broker attributes
git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk@1458956 13f79535-47bb-0310-9956-ffa450edef68
10 files changed, 831 insertions, 43 deletions
diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java index 8b74eb1dce..4f6f122876 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/RestServlet.java @@ -341,6 +341,26 @@ public class RestServlet extends AbstractServlet } } + if (names.isEmpty()) + { + if (_hierarchy.length == 0) + { + try + { + doUpdate(getBroker(), providedObject); + response.setStatus(HttpServletResponse.SC_OK); + } + catch(RuntimeException e) + { + setResponseStatus(response, e); + } + return; + } + else + { + throw new ServletException("Cannot identify request target object"); + } + } providedObject.put("name", names.get(names.size()-1)); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js index b20169c94d..6f3049ec0d 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/common/util.js @@ -18,8 +18,22 @@ * under the License. * */ -define(["dojo/_base/xhr"], - function (xhr) { + +define(["dojo/_base/xhr", + "dojo/_base/event", + "dojo/_base/json", + "dojo/_base/lang", + "dijit/Dialog", + "dijit/form/Form", + "dijit/form/Button", + "dijit/form/RadioButton", + "dijit/form/CheckBox", + "dojox/layout/TableContainer", + "dojox/validate/us", + "dojox/validate/web", + "dojo/domReady!" + ], + function (xhr, event, json, lang) { var util = {}; if (Array.isArray) { util.isArray = function (object) { @@ -122,5 +136,112 @@ define(["dojo/_base/xhr"], return (type === "PlainPasswordFile" || type === "Base64MD5PasswordFile"); }; + util.showSetAttributesDialog = function(attributeWidgetFactories, data, putURL, dialogTitle) + { + var layout = new dojox.layout.TableContainer({ + cols: 1, + "labelWidth": "300", + showLabels: true, + orientation: "horiz", + customClass: "formLabel" + }); + var submitButton = new dijit.form.Button({label: "Submit", type: "submit"}); + var form = new dijit.form.Form(); + form.domNode.appendChild(layout.domNode); + form.domNode.appendChild(submitButton.domNode); + var widgets = {}; + var requiredFor ={}; + for(var i in attributeWidgetFactories) + { + var attributeWidgetFactory = attributeWidgetFactories[i]; + var widget = attributeWidgetFactory.createWidget(data); + var name = attributeWidgetFactory.name ? attributeWidgetFactory.name : widget.name; + widgets[name] = widget; + widget.initialValue = widget.value; + layout.addChild(widget); + if (attributeWidgetFactory.hasOwnProperty("requiredFor")) + { + requiredFor[attributeWidgetFactory.requiredFor] = widget; + } + } + + // add onchange handler to set required property for dependent widget + for(var widgetName in requiredFor) + { + var dependent = requiredFor[widgetName]; + var widget = widgets[widgetName]; + if (widget.value) + { + dependent.set("required", true); + } + if (widget) + { + widget.dependent = dependent; + widget.on("change", function(newValue){ + this.dependent.set("required", newValue != ""); + }); + } + } + var setAttributesDialog = new dijit.Dialog({ + title: dialogTitle, + content: form, + style: "width: 600px" + }); + form.on("submit", function(e) + { + event.stop(e); + try + { + if(form.validate()) + { + var values = {}; + for(var i in widgets) + { + var widget = widgets[i]; + var value = widget.value; + var propName = widget.name; + if ((widget instanceof dijit.form.CheckBox || widget instanceof dijit.form.RadioButton)) + { + values[ propName ] = widget.checked; + } + else if (value != "" || (widget.initialValue && value != widget.initialValue)) + { + values[ propName ] = value ? value: null; + } + } + + var that = this; + xhr.put({url: putURL, sync: true, handleAs: "json", + headers: { "Content-Type": "application/json"}, + putData: json.toJson(values), + load: function(x) {that.success = true; }, + error: function(error) {that.success = false; that.failureReason = error;}}); + if(this.success === true) + { + setAttributesDialog.destroy(); + } + else + { + alert("Error:" + this.failureReason); + } + return false; + } + else + { + alert('Form contains invalid data. Please correct first'); + return false; + } + } + catch(e) + { + alert("Unexpected exception:" + e.message); + return false; + } + }); + form.connectChildren(true); + setAttributesDialog.show(); + + }; + return util; });
\ No newline at end of file diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js index 98d442bf14..e479deddbc 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/Broker.js @@ -35,6 +35,11 @@ define(["dojo/_base/xhr", "dojox/grid/enhanced/plugins/IndirectSelection", "dijit/layout/AccordionContainer", "dijit/layout/AccordionPane", + "dijit/form/FilteringSelect", + "dijit/form/NumberSpinner", + "dijit/form/ValidationTextBox", + "dijit/form/CheckBox", + "dojo/store/Memory", "dojo/domReady!"], function (xhr, parser, query, connect, properties, updater, util, UpdatableStore, EnhancedGrid, registry, addAuthenticationProvider, addVirtualHost, addPort) { @@ -45,7 +50,301 @@ define(["dojo/_base/xhr", if(parent) { this.modelObj.parent = {}; this.modelObj.parent[ parent.type] = parent; - } + } + this.attributeWidgetFactories = [{ + name: "name", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + required: true, + value: brokerData.name, + disabled: true, + label: "Name*:", + name: "name"}) + } + }, { + name: "defaultAuthenticationProvider", + createWidget: function(brokerData) { + var providers = brokerData.authenticationproviders; + var data = []; + if (providers) { + for (var i=0; i< providers.length; i++) { + data.push({id: providers[i].name, name: providers[i].name}); + } + } + var providersStore = new dojo.store.Memory({ data: data }); + return new dijit.form.FilteringSelect({ + required: true, + store: providersStore, + value: brokerData.defaultAuthenticationProvider, + label: "Default Authentication Provider*:", + name: "defaultAuthenticationProvider"}) + } + }, { + name: "defaultVirtualHost", + createWidget: function(brokerData) { + var hosts = brokerData.virtualhosts; + var data = []; + if (hosts) { + for (var i=0; i< hosts.length; i++) { + data.push({id: hosts[i].name, name: hosts[i].name}); + } + } + var hostsStore = new dojo.store.Memory({ data: data }); + return new dijit.form.FilteringSelect({ + required: true, store: hostsStore, + value: brokerData.defaultVirtualHost, + label: "Default Virtual Host*:", + name: "defaultVirtualHost"}) + } + }, { + name: "aclFile", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + required: false, + value: brokerData.aclFile, + label: "ACL file location:", + name: "aclFile"}) + } + }, { + name: "groupFile", + createWidget: function(brokerData) + { + return new dijit.form.ValidationTextBox({ + required: false, + value: brokerData.groupFile, + label: "Group file location:", + name: "groupFile"}); + } + }, { + name: "keyStorePath", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + required: false, + value: brokerData.keyStorePath, + label: "Path to keystore:", + name: "keyStorePath"}); + } + }, { + name: "keyStoreCertAlias", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + required: false, + value: brokerData.keyStoreCertAlias, + label: "Keystore certificate alias:", + name: "keyStoreCertAlias"}); + } + }, { + name: "keyStorePassword", + requiredFor: "keyStorePath", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + required: false, + label: "Keystore password:", + invalidMessage: "Missed keystore password", + name: "keyStorePassword"}); + } + }, { + name: "trustStorePath", + createWidget: function(brokerData) + { + return new dijit.form.ValidationTextBox({ + required: false, + value: brokerData.trustStorePath, + label: "Path to truststore:", + name: "trustStorePath"}); + } + }, { + name: "trustStorePassword", + requiredFor: "trustStorePath", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + required: false, + label: "Truststore password:", + invalidMessage: "Missed trustore password", + name: "trustStorePassword"}); + } + }, { + name: "peerStorePath", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + required: false, + value: brokerData.peerStorePath, + label: "Path to peerstore:", + name: "peerStorePath"}); + } + }, { + name: "peerStorePassword", + requiredFor: "peerStorePath", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + required: false, + label: "Peerstore password:", + invalidMessage: "Missed peerstore password", + name: "peerStorePassword"}); + } + }, { + name: "alertThresholdQueueDepth", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + trim: "true", + regexp: "[0-9]+", + invalidMessage: "Invalid value", + required: false, + value: brokerData.alertThresholdQueueDepth, + placeholder: "Count of messages", + label: "Queue depth alert threshold:", + name: "alertThresholdQueueDepth" + }); + } + }, { + name: "alertThresholdMessageAge", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + trim: "true", + regexp: "[0-9]+", + invalidMessage: "Invalid value", + required: false, + value: brokerData.alertThresholdMessageAge, + placeholder: "Time in ms", + label: "Queue message age alert threshold:", + name: "alertThresholdMessageAge" + }); + } + }, { + name: "alertThresholdMessageSize", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + trim: "true", + regexp: "[0-9]+", + invalidMessage: "Invalid value", + required: false, + value: brokerData.alertThresholdMessageSize, + placeholder: "Size in bytes", + label: "Queue message size alert threshold:", + name: "alertThresholdMessageSize" + }); + } + }, { + name: "alertRepeatGap", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + trim: "true", + regexp: "[0-9]+", + invalidMessage: "Invalid value", + required: false, + value: brokerData.alertThresholdMessageSize, + value: brokerData.alertRepeatGap, + placeholder: "Time in ms", + label: "Queue alert repeat gap:", + name: "alertRepeatGap" + }); + } + }, { + name: "maximumDeliveryAttempts", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + trim: "true", + regexp: "[0-9]+", + invalidMessage: "Invalid value", + required: false, + value: brokerData.maximumDeliveryAttempts, + placeholder: "Count of messages", + label: "Queue maximum delivery retries:", + name: "maximumDeliveryAttempts" + }); + } + }, { + name: "deadLetterQueueEnabled", + createWidget: function(brokerData) { + return new dijit.form.CheckBox({ + required: false, + checked: brokerData.deadLetterQueueEnabled, + value: "true", + label: "Dead letter queue enabled:", + name: "deadLetterQueueEnabled", + }); + } + }, { + name: "queueFlowControlSizeBytes", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + trim: "true", + regexp: "[0-9]+", + invalidMessage: "Invalid value", + required: false, + value: brokerData.queueFlowControlSizeBytes, + placeholder: "Size in bytes", + label: "Queue flow capacity:", + name: "queueFlowControlSizeBytes", + }); + } + }, { + name: "queueFlowResumeSizeBytes", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + trim: "true", + regexp: "[0-9]+", + invalidMessage: "Invalid value", + required: false, + value: brokerData.queueFlowResumeSizeBytes, + placeholder: "Size in bytes", + label: "Queue flow resume capacity:", + name: "queueFlowResumeSizeBytes", + }); + } + }, { + name: "sessionCountLimit", + createWidget: function(brokerData) + { + return new dijit.form.NumberSpinner({ + invalidMessage: "Invalid value", + required: false, + value: brokerData.sessionCountLimit, + smallDelta: 1, + constraints: {min:1,max:65535,places:0, pattern: "#####"}, + label: "Connection session limit:", + name: "sessionCountLimit" + }); + } + }, { + name: "heartBeatDelay", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + trim: "true", + regexp: "[0-9]+", + invalidMessage: "Invalid value", + required: false, + value: brokerData.heartBeatDelay, + placeholder: "Time in ms", + label: "Heart beat delay:", + name: "heartBeatDelay" + }); + } + }, { + name: "statisticsReportingPeriod", + createWidget: function(brokerData) { + return new dijit.form.ValidationTextBox({ + trim: "true", + regexp: "[0-9]+", + invalidMessage: "Invalid value", + required: false, + value: brokerData.statisticsReportingPeriod, + placeholder: "Time in ms", + label: "Statistics reporting period:", + name: "statisticsReportingPeriod" + }); + } + }, { + name: "statisticsReportingResetEnabled", + createWidget: function(brokerData) + { + return new dijit.form.CheckBox({ + required: false, checked: brokerData.statisticsReportingResetEnabled, value: "true", + label: "Statistics reporting period enabled:", + name: "statisticsReportingResetEnabled" + }); + } + } ]; } Broker.prototype.getTitle = function() @@ -62,7 +361,7 @@ define(["dojo/_base/xhr", contentPane.containerNode.innerHTML = data; parser.parse(contentPane.containerNode); - that.brokerUpdater = new BrokerUpdater(contentPane.containerNode, that.modelObj, that.controller); + that.brokerUpdater = new BrokerUpdater(contentPane.containerNode, that.modelObj, that.controller, that.attributeWidgetFactories); updater.add( that.brokerUpdater ); @@ -109,6 +408,18 @@ define(["dojo/_base/xhr", "Are you sure you want to delete port"); } ); + + var editButton = query(".editBroker", contentPane.containerNode)[0]; + connect.connect(registry.byNode(editButton), "onClick", + function(evt){ + util.showSetAttributesDialog( + that.attributeWidgetFactories, + that.brokerUpdater.brokerData, + "rest/broker", + "Set broker attributes"); + } + ); + }}); }; @@ -116,16 +427,11 @@ define(["dojo/_base/xhr", updater.remove( this.brokerUpdater ); }; - function BrokerUpdater(node, brokerObj, controller) + function BrokerUpdater(node, brokerObj, controller, attributes) { this.controller = controller; - this.name = query(".broker-name", node)[0]; - /*this.state = dom.byId("state"); - this.durable = dom.byId("durable"); - this.lifetimePolicy = dom.byId("lifetimePolicy"); - */ this.query = "rest/broker"; - + this.attributes = attributes; var that = this; xhr.get({url: this.query, sync: properties.useSyncGet, handleAs: "json"}) @@ -259,11 +565,28 @@ define(["dojo/_base/xhr", BrokerUpdater.prototype.updateHeader = function() { - this.name.innerHTML = this.brokerData[ "name" ]; - /* this.state.innerHTML = this.brokerData[ "state" ]; - this.durable.innerHTML = this.brokerData[ "durable" ]; - this.lifetimePolicy.innerHTML = this.brokerData[ "lifetimePolicy" ]; -*/ + var brokerData = this.brokerData; + for(var i in this.attributes) + { + var propertyName = this.attributes[i].name; + var element = dojo.byId("brokerAttribute." + propertyName); + if (element) + { + if (brokerData.hasOwnProperty(propertyName)) + { + var container = dojo.byId("brokerAttribute." + propertyName + ".container"); + if (container) + { + container.style.display = "block"; + } + element.innerHTML = brokerData [propertyName]; + } + else + { + element.innerHTML = ""; + } + } + } }; BrokerUpdater.prototype.update = function() @@ -295,7 +618,5 @@ define(["dojo/_base/xhr", }; - - return Broker; }); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/showBroker.html b/qpid/java/broker-plugins/management-http/src/main/java/resources/showBroker.html index 60c514e262..8faae08e1e 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/showBroker.html +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/showBroker.html @@ -19,14 +19,103 @@ - --> <div class="broker"> - <span>Name:</span><span class="broker-name" style="position:absolute; left:6em"></span> - <br/> -<!-- <span>State:</span><span class="broker-state" style="position:absolute; left:6em"></span> - <br/> - <span>Durable:</span><span class="broker-durable" style="position:absolute; left:6em"></span> - <br/> - <span>Lifespan:</span><span class="broker-lifetimePolicy" style="position:absolute; left:6em" ></span> - <br/> --> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Broker Attributes', open: false"> + <div id="brokerAttributes" style="clear:both"> + <div id="brokerAttribute.name.container" style="display: none; clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Broker Name:</div> + <div id="brokerAttribute.name" style="float:left;"></div> + </div> + <div id="brokerAttribute.defaultAuthenticationProvider.container" style="display: none; clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Default authentication provider:</div> + <div id="brokerAttribute.defaultAuthenticationProvider" style="float:left;"></div> + </div> + <div id="brokerAttribute.defaultVirtualHost.container" style="display: none; clear:both; clear:both;"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Default virtual host:</div> + <div id="brokerAttribute.defaultVirtualHost" style="float:left;"></div> + </div> + <div id="brokerAttribute.aclFile.container" style="display: none; clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">ACL file location:</div> + <div id="brokerAttribute.aclFile" style="float:left;"></div> + </div> + <div id="brokerAttribute.groupFile.container" style="display: none; clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Group file location:</div> + <div id="brokerAttribute.groupFile" style="float:left;"></div> + </div> + <div id="brokerAttribute.keyStorePath.container" style="display: none; clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Path to keystore:</div> + <div id="brokerAttribute.keyStorePath" style="float:left;"></div><br/> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Keystore alias:</div> + <div id="brokerAttribute.keyStoreCertAlias" style="float:left;"></div> + </div> + <div id="brokerAttribute.trustStorePath.container" style="display: none; clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Path to truststore:</div> + <div id="brokerAttribute.trustStorePath" style="float:left;"></div> + </div> + <div id="brokerAttribute.peerStorePath.container" style="display: none; clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Path to peerstore:</div> + <div id="brokerAttribute.peerStorePath" style="float:left;"></div> + </div> + <div id="brokerAttribute.statisticsReportingPeriod.container" style="display: none; clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Statistics reporting period:</div> + <div id="brokerAttribute.statisticsReportingPeriod" style="float:left;"></div> + </div> + <div id="brokerAttribute.statisticsReportingResetEnabled.container" style="display: none; clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Statistics reporting period enabled:</div> + <div id="brokerAttribute.statisticsReportingResetEnabled" style="float:left;"></div> + </div> + <div style="clear:both"></div> + <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Queue Attributes', open: true"> + <div id="brokerAttribute.alertThresholdQueueDepth.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Queue depth alert threshold:</div> + <div id="brokerAttribute.alertThresholdQueueDepth" style="float:left;"></div> + </div> + <div id="brokerAttribute.alertThresholdMessageAge.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Queue message age alert threshold:</div> + <div id="brokerAttribute.alertThresholdMessageAge" style="float:left;"></div> ms + </div> + <div id="brokerAttribute.alertThresholdMessageSize.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Queue message size alert threshold:</div> + <div id="brokerAttribute.alertThresholdMessageSize" style="float:left;"></div> + </div> + <div id="brokerAttribute.alertRepeatGap.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Queue alert alert repeat gap:</div> + <div id="brokerAttribute.alertRepeatGap" style="float:left;"></div> ms + </div> + <div id="brokerAttribute.maximumDeliveryAttempts.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Queue maximum delivery retries:</div> + <div id="brokerAttribute.maximumDeliveryAttempts" style="float:left;"></div> + </div> + <div id="brokerAttribute.deadLetterQueueEnabled.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Dead letter queue enabled:</div> + <div id="brokerAttribute.deadLetterQueueEnabled" style="float:left;"></div> + </div> + <div id="brokerAttribute.queueFlowControlSizeBytes.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Queue flow capacity:</div> + <div id="brokerAttribute.queueFlowControlSizeBytes" style="float:left;"></div> + </div> + <div id="brokerAttribute.queueFlowResumeSizeBytes.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Queue flow resume capacity:</div> + <div id="brokerAttribute.queueFlowResumeSizeBytes" style="float:left;"></div> + </div> + <div style="clear:both"></div> + </div> + <br/> + <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Connection attributes', open: true"> + <div id="brokerAttribute.sessionCountLimit.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Connection session limit:</div> + <div id="brokerAttribute.sessionCountLimit" style="float:left;"></div> + </div> + <div id="brokerAttribute.heartBeatDelay.container" style="clear:both"> + <div class="formLabel-labelCell" style="float:left; width: 250px;">Heart beat delay:</div> + <div id="brokerAttribute.heartBeatDelay" style="float:left;"></div> ms + </div> + <div style="clear:both"></div> + </div> + </div> + <br/> + <button data-dojo-type="dijit.form.Button" class="editBroker">Edit</button> + </div> <br/> <div data-dojo-type="dijit.TitlePane" data-dojo-props="title: 'Virtual Hosts'"> <div class="broker-virtualhosts"></div> diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java index 16c6cb7e5e..1323655f3c 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java @@ -198,7 +198,7 @@ public abstract class AuthenticationProviderAdapter<T extends AuthenticationMana @Override public <C extends ConfiguredObject> Collection<C> getChildren(Class<C> clazz) { - return null; + return Collections.emptySet(); } @Override diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java index 1492982708..1b894b1232 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java @@ -24,6 +24,7 @@ import java.lang.reflect.Type; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.security.AccessControlException; +import java.security.KeyStoreException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -32,6 +33,7 @@ import java.util.Map; import java.util.UUID; import javax.net.ssl.KeyManagerFactory; +import java.security.cert.Certificate; import org.apache.log4j.Logger; import org.apache.qpid.common.QpidProperties; @@ -66,6 +68,7 @@ import org.apache.qpid.server.stats.StatisticsGatherer; import org.apache.qpid.server.store.MessageStoreCreator; import org.apache.qpid.server.util.MapValueConverter; import org.apache.qpid.server.virtualhost.VirtualHostRegistry; +import org.apache.qpid.transport.network.security.ssl.SSLUtil; public class BrokerAdapter extends AbstractAdapter implements Broker, ConfigurationChangeListener { @@ -146,7 +149,10 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat put(Broker.NAME, DEFAULT_NAME); }}); - + private String[] POSITIVE_NUMERIC_ATTRIBUTES = { ALERT_THRESHOLD_MESSAGE_AGE, ALERT_THRESHOLD_MESSAGE_COUNT, + ALERT_THRESHOLD_QUEUE_DEPTH, ALERT_THRESHOLD_MESSAGE_SIZE, ALERT_REPEAT_GAP, FLOW_CONTROL_SIZE_BYTES, + FLOW_CONTROL_RESUME_SIZE_BYTES, MAXIMUM_DELIVERY_ATTEMPTS, HOUSEKEEPING_CHECK_PERIOD, SESSION_COUNT_LIMIT, + HEART_BEAT_DELAY, STATISTICS_REPORTING_PERIOD }; private final StatisticsGatherer _statisticsGatherer; @@ -674,17 +680,13 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat { return _authenticationProviderFactory.getSupportedAuthenticationProviders(); } - else if (DEFAULT_AUTHENTICATION_PROVIDER.equals(name)) - { - return _defaultAuthenticationProvider == null ? null : _defaultAuthenticationProvider.getName(); - } - else if (KEY_STORE_PASSWORD.equals(name)) + else if (KEY_STORE_PASSWORD.equals(name) || TRUST_STORE_PASSWORD.equals(name) || PEER_STORE_PASSWORD.equals(name)) { - return DUMMY_PASSWORD_MASK; - } - else if (TRUST_STORE_PASSWORD.equals(name)) - { - return DUMMY_PASSWORD_MASK; + if (getActualAttributes().get(name) != null) + { + return DUMMY_PASSWORD_MASK; + } + return null; } return super.getAttribute(name); } @@ -990,6 +992,116 @@ public class BrokerAdapter extends AbstractAdapter implements Broker, Configurat @Override protected void changeAttributes(Map<String, Object> attributes) { - super.changeAttributes(MapValueConverter.convert(attributes, ATTRIBUTE_TYPES)); + //TODO: Add ACL check + //TODO: Add management mode check + Map<String, Object> convertedAttributes = MapValueConverter.convert(attributes, ATTRIBUTE_TYPES); + validateAttributes(convertedAttributes); + super.changeAttributes(convertedAttributes); + } + + private void validateAttributes(Map<String, Object> convertedAttributes) + { + String aclFile = (String) convertedAttributes.get(ACL_FILE); + if (aclFile != null) + { + // create a security manager to validate the ACL specified in file + new SecurityManager(aclFile); + } + String groupFile = (String) convertedAttributes.get(GROUP_FILE); + if (groupFile != null) + { + // create a group manager to validate the groups specified in file + new FileGroupManager(groupFile); + } + validateKeyStoreAttributes(convertedAttributes, "key store", KEY_STORE_PATH, KEY_STORE_PASSWORD, KEY_STORE_CERT_ALIAS); + validateKeyStoreAttributes(convertedAttributes, "trust store", TRUST_STORE_PATH, TRUST_STORE_PASSWORD, null); + validateKeyStoreAttributes(convertedAttributes, "peer store", PEER_STORE_PATH, PEER_STORE_PASSWORD, null); + String defaultAuthenticationProvider = (String) convertedAttributes.get(DEFAULT_AUTHENTICATION_PROVIDER); + if (defaultAuthenticationProvider != null) + { + AuthenticationProvider provider = getAuthenticationProviderByName(defaultAuthenticationProvider); + if (provider == null) + { + throw new IllegalConfigurationException("Authentication provider with name " + defaultAuthenticationProvider + + " canot be set as a default as it does not exist"); + } + } + String defaultVirtualHost = (String) convertedAttributes.get(DEFAULT_VIRTUAL_HOST); + if (defaultVirtualHost != null) + { + VirtualHost foundHost = findVirtualHostByName(defaultVirtualHost); + if (foundHost == null) + { + throw new IllegalConfigurationException("Virtual host with name " + defaultVirtualHost + + " cannot be set as a default as it does not exist"); + } + } + Long queueFlowControlSize = (Long) convertedAttributes.get(FLOW_CONTROL_SIZE_BYTES); + if (queueFlowControlSize != null && queueFlowControlSize > 0) + { + Long queueFlowControlResumeSize = (Long) convertedAttributes.get(FLOW_CONTROL_RESUME_SIZE_BYTES); + if (queueFlowControlResumeSize == null) + { + throw new IllegalConfigurationException("Flow control resume size attribute is not specified with flow control size attribute"); + } + if (queueFlowControlResumeSize >= queueFlowControlSize) + { + throw new IllegalConfigurationException("Flow control resume size should be less then flow control size"); + } + } + for (String attributeName : POSITIVE_NUMERIC_ATTRIBUTES) + { + Number value = (Number) convertedAttributes.get(attributeName); + if (value != null && value.longValue() < 0) + { + throw new IllegalConfigurationException("Only positive integer value can be specified for the attribute " + + attributeName); + } + } + } + + private void validateKeyStoreAttributes(Map<String, Object> convertedAttributes, String type, String pathAttribute, + String passwordAttribute, String aliasAttribute) + { + String keyStoreFile = (String) convertedAttributes.get(pathAttribute); + if (keyStoreFile != null) + { + String password = (String) convertedAttributes.get(passwordAttribute); + if (password == null) + { + password = (String) getActualAttributes().get(passwordAttribute); + } + java.security.KeyStore keyStore = null; + try + { + keyStore = SSLUtil.getInitializedKeyStore(keyStoreFile, password, java.security.KeyStore.getDefaultType()); + } + catch (Exception e) + { + throw new IllegalConfigurationException("Cannot instantiate " + type + " at " + keyStoreFile, e); + } + if (aliasAttribute != null) + { + String alias = (String) convertedAttributes.get(aliasAttribute); + if (alias != null) + { + Certificate cert = null; + try + { + cert = keyStore.getCertificate(alias); + } + catch (KeyStoreException e) + { + // key store should be initialized above + throw new RuntimeException("Key store has not been initialized", e); + } + if (cert == null) + { + throw new IllegalConfigurationException("Cannot find a certificate with alias " + alias + "in " + type + + " : " + keyStoreFile); + } + } + } + } } } diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/AuthenticationProviderRestTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/AuthenticationProviderRestTest.java index 078a1bb483..4ba2069dfd 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/AuthenticationProviderRestTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/AuthenticationProviderRestTest.java @@ -41,14 +41,21 @@ public class AuthenticationProviderRestTest extends QpidRestTestCase { List<Map<String, Object>> providerDetails = getRestTestHelper().getJsonAsList("/rest/authenticationprovider"); assertNotNull("Providers details cannot be null", providerDetails); - assertEquals("Unexpected number of providers", 1, providerDetails.size()); + assertEquals("Unexpected number of providers", 2, providerDetails.size()); for (Map<String, Object> provider : providerDetails) { - assertProvider(true, PlainPasswordFileAuthenticationManagerFactory.PROVIDER_TYPE, provider); + boolean managesPrincipals = true; + String type = PlainPasswordFileAuthenticationManagerFactory.PROVIDER_TYPE; + if (ANONYMOUS_AUTHENTICATION_PROVIDER.equals(provider.get(AuthenticationProvider.NAME))) + { + type = AnonymousAuthenticationManagerFactory.PROVIDER_TYPE; + managesPrincipals = false; + } + assertProvider(managesPrincipals, type , provider); Map<String, Object> data = getRestTestHelper().getJsonAsSingletonList("/rest/authenticationprovider/" + provider.get(AuthenticationProvider.NAME)); assertNotNull("Cannot load data for " + provider.get(AuthenticationProvider.NAME), data); - assertProvider(true, PlainPasswordFileAuthenticationManagerFactory.PROVIDER_TYPE, data); + assertProvider(managesPrincipals, type, data); } } diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/BrokerRestTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/BrokerRestTest.java index 63691e9915..fe4115b4c0 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/BrokerRestTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/BrokerRestTest.java @@ -20,8 +20,10 @@ */ package org.apache.qpid.systest.rest; +import java.io.File; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -32,7 +34,10 @@ import org.apache.qpid.server.model.LifetimePolicy; import org.apache.qpid.server.model.Port; import org.apache.qpid.server.model.State; import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.model.adapter.BrokerAdapter; +import org.apache.qpid.test.utils.QpidTestCase; import org.apache.qpid.test.utils.TestBrokerConfiguration; +import org.apache.qpid.test.utils.TestSSLConstants; public class BrokerRestTest extends QpidRestTestCase { @@ -86,6 +91,107 @@ public class BrokerRestTest extends QpidRestTestCase new HashSet<String>(port2Protocols)); } + public void testPutToUpdateWithValidAttributeValues() throws Exception + { + Map<String, Object> brokerAttributes = getValidBrokerAttributes(); + + int response = getRestTestHelper().submitRequest("/rest/broker", "PUT", brokerAttributes); + assertEquals("Unexpected update response", 200, response); + + restartBroker(); + Map<String, Object> brokerDetails = getRestTestHelper().getJsonAsSingletonList("/rest/broker"); + assertBrokerAttributes(brokerAttributes, brokerDetails); + } + + public void testPutToUpdateWithInvalidAttributeValues() throws Exception + { + Map<String, Object> invalidAttributes = new HashMap<String, Object>(); + invalidAttributes.put(Broker.DEFAULT_AUTHENTICATION_PROVIDER, "non-existing-provider"); + invalidAttributes.put(Broker.DEFAULT_VIRTUAL_HOST, "non-existing-host"); + invalidAttributes.put(Broker.ALERT_THRESHOLD_MESSAGE_AGE, -1000); + invalidAttributes.put(Broker.ALERT_THRESHOLD_MESSAGE_COUNT, -2000); + invalidAttributes.put(Broker.ALERT_THRESHOLD_QUEUE_DEPTH, -3000); + invalidAttributes.put(Broker.ALERT_THRESHOLD_MESSAGE_SIZE, -4000); + invalidAttributes.put(Broker.ALERT_REPEAT_GAP, -5000); + invalidAttributes.put(Broker.FLOW_CONTROL_SIZE_BYTES, -7000); + invalidAttributes.put(Broker.FLOW_CONTROL_RESUME_SIZE_BYTES, -16000); + invalidAttributes.put(Broker.MAXIMUM_DELIVERY_ATTEMPTS, -8); + invalidAttributes.put(Broker.HOUSEKEEPING_CHECK_PERIOD, -90000); + invalidAttributes.put(Broker.SESSION_COUNT_LIMIT, -10); + invalidAttributes.put(Broker.HEART_BEAT_DELAY, -11000); + invalidAttributes.put(Broker.STATISTICS_REPORTING_PERIOD, -12000); + invalidAttributes.put(Broker.ACL_FILE, QpidTestCase.QPID_HOME + File.separator + "etc" + File.separator + "non-existing-acl.acl"); + invalidAttributes.put(Broker.KEY_STORE_PATH, QpidTestCase.QPID_HOME + File.separator + "etc" + File.separator + "non-existing-keystore.jks"); + invalidAttributes.put(Broker.KEY_STORE_PASSWORD, "password1"); + invalidAttributes.put(Broker.KEY_STORE_CERT_ALIAS, "java-broker1"); + invalidAttributes.put(Broker.TRUST_STORE_PATH, QpidTestCase.QPID_HOME + File.separator + "etc" + File.separator + "non-existing-truststore.jks"); + invalidAttributes.put(Broker.TRUST_STORE_PASSWORD, "password2"); + invalidAttributes.put(Broker.PEER_STORE_PATH, QpidTestCase.QPID_HOME + File.separator + "etc" + File.separator + "non-existing-peerstore.jks"); + invalidAttributes.put(Broker.PEER_STORE_PASSWORD, "password3"); + invalidAttributes.put(Broker.GROUP_FILE, QpidTestCase.QPID_HOME + File.separator + "etc" + File.separator + "groups-non-existing"); + + for (Map.Entry<String, Object> entry : invalidAttributes.entrySet()) + { + Map<String, Object> brokerAttributes = getValidBrokerAttributes(); + brokerAttributes.put(entry.getKey(), entry.getValue()); + int response = getRestTestHelper().submitRequest("/rest/broker", "PUT", brokerAttributes); + assertEquals("Unexpected update response for invalid attribute " + entry.getKey() + "=" + entry.getValue(), 409, response); + } + + // a special case when FLOW_CONTROL_RESUME_SIZE_BYTES > FLOW_CONTROL_SIZE_BYTES + Map<String, Object> brokerAttributes = getValidBrokerAttributes(); + brokerAttributes.put(Broker.FLOW_CONTROL_SIZE_BYTES, 1000); + brokerAttributes.put(Broker.FLOW_CONTROL_RESUME_SIZE_BYTES, 2000); + int response = getRestTestHelper().submitRequest("/rest/broker", "PUT", brokerAttributes); + assertEquals("Unexpected update response for flow resume size > flow size", 409, response); + } + + private Map<String, Object> getValidBrokerAttributes() + { + Map<String, Object> brokerAttributes = new HashMap<String, Object>(); + brokerAttributes.put(Broker.DEFAULT_AUTHENTICATION_PROVIDER, ANONYMOUS_AUTHENTICATION_PROVIDER); + brokerAttributes.put(Broker.DEFAULT_VIRTUAL_HOST, TEST3_VIRTUALHOST); + brokerAttributes.put(Broker.ALERT_THRESHOLD_MESSAGE_AGE, 1000); + brokerAttributes.put(Broker.ALERT_THRESHOLD_MESSAGE_COUNT, 2000); + brokerAttributes.put(Broker.ALERT_THRESHOLD_QUEUE_DEPTH, 3000); + brokerAttributes.put(Broker.ALERT_THRESHOLD_MESSAGE_SIZE, 4000); + brokerAttributes.put(Broker.ALERT_REPEAT_GAP, 5000); + brokerAttributes.put(Broker.FLOW_CONTROL_SIZE_BYTES, 7000); + brokerAttributes.put(Broker.FLOW_CONTROL_RESUME_SIZE_BYTES, 6000); + brokerAttributes.put(Broker.MAXIMUM_DELIVERY_ATTEMPTS, 8); + brokerAttributes.put(Broker.DEAD_LETTER_QUEUE_ENABLED, true); + brokerAttributes.put(Broker.HOUSEKEEPING_CHECK_PERIOD, 90000); + brokerAttributes.put(Broker.SESSION_COUNT_LIMIT, 10); + brokerAttributes.put(Broker.HEART_BEAT_DELAY, 11000); + brokerAttributes.put(Broker.STATISTICS_REPORTING_PERIOD, 12000); + brokerAttributes.put(Broker.STATISTICS_REPORTING_RESET_ENABLED, true); + brokerAttributes.put(Broker.ACL_FILE, QpidTestCase.QPID_HOME + File.separator + "etc" + File.separator + "broker_example.acl"); + brokerAttributes.put(Broker.KEY_STORE_PATH, TestSSLConstants.BROKER_KEYSTORE); + brokerAttributes.put(Broker.KEY_STORE_PASSWORD, TestSSLConstants.BROKER_KEYSTORE_PASSWORD); + brokerAttributes.put(Broker.KEY_STORE_CERT_ALIAS, "java-broker"); + brokerAttributes.put(Broker.TRUST_STORE_PATH, TestSSLConstants.TRUSTSTORE); + brokerAttributes.put(Broker.TRUST_STORE_PASSWORD, TestSSLConstants.TRUSTSTORE_PASSWORD); + brokerAttributes.put(Broker.PEER_STORE_PATH, TestSSLConstants.TRUSTSTORE); + brokerAttributes.put(Broker.PEER_STORE_PASSWORD, TestSSLConstants.TRUSTSTORE_PASSWORD); + brokerAttributes.put(Broker.GROUP_FILE, QpidTestCase.QPID_HOME + File.separator + "etc" + File.separator + "groups"); + return brokerAttributes; + } + + private void assertBrokerAttributes(Map<String, Object> expectedAttributes, Map<String, Object> actualAttributes) + { + for (Map.Entry<String, Object> entry : expectedAttributes.entrySet()) + { + String attributeName = entry.getKey(); + Object attributeValue = entry.getValue(); + if (attributeName.equals(Broker.KEY_STORE_PASSWORD) || attributeName.equals(Broker.TRUST_STORE_PASSWORD) || attributeName.equals(Broker.PEER_STORE_PASSWORD)) + { + attributeValue = "********"; + } + Object currentValue = actualAttributes.get(attributeName); + assertEquals("Unexpected attribute " + attributeName + " value:", attributeValue, currentValue); + } + } + protected void assertBrokerAttributes(Map<String, Object> brokerDetails) { Asserts.assertAttributesPresent(brokerDetails, Broker.AVAILABLE_ATTRIBUTES, diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/QpidRestTestCase.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/QpidRestTestCase.java index e2b73aa2b5..30d5b195f1 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/QpidRestTestCase.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/QpidRestTestCase.java @@ -21,14 +21,21 @@ package org.apache.qpid.systest.rest; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.model.AuthenticationProvider; import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.plugin.AuthenticationManagerFactory; +import org.apache.qpid.server.security.auth.manager.AnonymousAuthenticationManagerFactory; +import org.apache.qpid.server.security.auth.manager.ExternalAuthenticationManagerFactory; import org.apache.qpid.test.utils.TestBrokerConfiguration; import org.apache.qpid.test.utils.QpidBrokerTestCase; public class QpidRestTestCase extends QpidBrokerTestCase { + public static final String ANONYMOUS_AUTHENTICATION_PROVIDER = "testAnonymous"; public static final String TEST1_VIRTUALHOST = "test"; public static final String TEST2_VIRTUALHOST = "test2"; public static final String TEST3_VIRTUALHOST = "test3"; @@ -77,6 +84,11 @@ public class QpidRestTestCase extends QpidBrokerTestCase config.setObjectAttribute(TestBrokerConfiguration.ENTRY_NAME_HTTP_PORT, Port.PORT, _restTestHelper.getHttpPort()); config.removeObjectConfiguration(TestBrokerConfiguration.ENTRY_NAME_JMX_PORT); config.removeObjectConfiguration(TestBrokerConfiguration.ENTRY_NAME_RMI_PORT); + + Map<String, Object> anonymousProviderAttributes = new HashMap<String, Object>(); + anonymousProviderAttributes.put(AuthenticationProvider.TYPE, AnonymousAuthenticationManagerFactory.PROVIDER_TYPE); + anonymousProviderAttributes.put(AuthenticationProvider.NAME, ANONYMOUS_AUTHENTICATION_PROVIDER); + config.addAuthenticationProviderConfiguration(anonymousProviderAttributes); } public RestTestHelper getRestTestHelper() diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/StructureRestTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/StructureRestTest.java index f5e326f90b..5593ad0b42 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/StructureRestTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/StructureRestTest.java @@ -45,7 +45,7 @@ public class StructureRestTest extends QpidRestTestCase @SuppressWarnings("unchecked") List<Map<String, Object>> providers = (List<Map<String, Object>>) structure.get("authenticationproviders"); - assertEquals("Unexpected number of authentication providers", 1, providers.size()); + assertEquals("Unexpected number of authentication providers", 2, providers.size()); for (String hostName : EXPECTED_VIRTUALHOSTS) { |