diff options
| author | Robert Gemmell <robbie@apache.org> | 2009-07-17 13:54:59 +0000 |
|---|---|---|
| committer | Robert Gemmell <robbie@apache.org> | 2009-07-17 13:54:59 +0000 |
| commit | 3493a2769f8015dcf706b114273263d6b0ec0b8d (patch) | |
| tree | 816c9c4180f0c792e6c063f4ef5d20d044734d0e /java/management/eclipse-plugin/src | |
| parent | 10c869b18fbfcd9952675431cd54b1e32073aa0d (diff) | |
| download | qpid-python-3493a2769f8015dcf706b114273263d6b0ec0b8d.tar.gz | |
QPID-1946: support server management API checking in the console.
Use the API versioning to prevent the console working with future brokers using a major new management API.
Provide message upon connection if the server supports a minor new API the console doesnt, directing user to upgrade.
Add a test to check console supported API is kept in sync with server API for new releases.
git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk/qpid@795084 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'java/management/eclipse-plugin/src')
9 files changed, 496 insertions, 6 deletions
diff --git a/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/ApiVersion.java b/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/ApiVersion.java new file mode 100644 index 0000000000..a8eda3f3d1 --- /dev/null +++ b/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/ApiVersion.java @@ -0,0 +1,110 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.management.ui; + +public class ApiVersion +{ + private int major; + private int minor; + + public ApiVersion(int major, int minor) + { + this.major = major; + this.minor = minor; + } + + public int getMajor() + { + return major; + } + + public int getMinor() + { + return minor; + } + + public boolean greaterThanOrEqualTo(int major, int minor) + { + if((this.major == major) && (this.minor >= minor)) + { + return true; + } + else if (this.major > major) + { + return true; + } + + return false; + } + + public boolean lessThanOrEqualTo(int major, int minor) + { + if((this.major == major) && (this.minor <= minor)) + { + return true; + } + else if (this.major < major) + { + return true; + } + + return false; + } + + public boolean greaterThan(int major, int minor) + { + if(this.major > major) + { + return true; + } + else if ((this.major == major) && (this.minor > minor)) + { + return true; + } + + return false; + } + + public boolean lessThan(int major, int minor) + { + if(this.major < major) + { + return true; + } + else if ((this.major == major) && (this.minor < minor)) + { + return true; + } + + return false; + } + + public boolean equals(int major, int minor) + { + return (this.major == major) && (this.minor == minor); + } + + public String toString() + { + return new String("major=" + major + ",minor=" + minor); + } + +} diff --git a/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/ApplicationRegistry.java b/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/ApplicationRegistry.java index 2b35d6b0f1..2e87709a28 100644 --- a/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/ApplicationRegistry.java +++ b/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/ApplicationRegistry.java @@ -43,6 +43,10 @@ public abstract class ApplicationRegistry private static FontRegistry fontRegistry = new FontRegistry(); public static final boolean debug = Boolean.getBoolean("eclipse.consoleLog"); public static final long timeout = Long.parseLong(System.getProperty("timeout", "5000")); + + //max supported broker management interface supported by this release of the management console + public static final int SUPPORTED_QPID_JMX_API_MAJOR_VERSION = 1; + public static final int SUPPORTED_QPID_JMX_API_MINOR_VERSION = 3; static { diff --git a/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/ServerRegistry.java b/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/ServerRegistry.java index 313e143df5..cc44a19781 100644 --- a/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/ServerRegistry.java +++ b/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/ServerRegistry.java @@ -35,6 +35,9 @@ public abstract class ServerRegistry { private ManagedServer _managedServer = null; + // API version for the management interface on the broker + private ApiVersion _managementApiVersion = new ApiVersion(0,0); + // list of virtual hosts for this server private List<String> _virtualHosts = new ArrayList<String>(); // map of all Connection mbeans @@ -54,6 +57,16 @@ public abstract class ServerRegistry _managedServer = server; } + public void setManagementApiVersion(ApiVersion mgmtApiVersion) + { + _managementApiVersion = mgmtApiVersion; + } + + public ApiVersion getManagementApiVersion() + { + return _managementApiVersion; + } + public ManagedServer getManagedServer() { return _managedServer; diff --git a/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/actions/AbstractAction.java b/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/actions/AbstractAction.java index 474e31cd8f..12906d1ff4 100644 --- a/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/actions/AbstractAction.java +++ b/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/actions/AbstractAction.java @@ -32,6 +32,7 @@ import javax.net.ssl.SSLPeerUnverifiedException; import org.apache.qpid.management.ui.ApplicationRegistry; import org.apache.qpid.management.ui.ApplicationWorkbenchAdvisor; import org.apache.qpid.management.ui.Constants; +import org.apache.qpid.management.ui.exceptions.ManagementConsoleException; import org.apache.qpid.management.ui.jmx.MBeanUtility; import org.apache.qpid.management.ui.views.NavigationView; import org.eclipse.core.runtime.IStatus; @@ -168,6 +169,12 @@ public class AbstractAction displayErrorDialogue(msg, title); return; } + else if (ex instanceof ManagementConsoleException) + { + msg = ex.getMessage(); + displayErrorDialogue(msg, title); + return; + } else { //Unknown exception type/reason. diff --git a/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/jmx/MBeanUtility.java b/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/jmx/MBeanUtility.java index 479f68de03..bbb6686346 100644 --- a/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/jmx/MBeanUtility.java +++ b/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/jmx/MBeanUtility.java @@ -20,9 +20,9 @@ */ package org.apache.qpid.management.ui.jmx; +import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; -import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Set; @@ -37,10 +37,15 @@ import javax.management.MBeanInfo; import javax.management.MBeanNotificationInfo; import javax.management.MBeanOperationInfo; import javax.management.MBeanServerConnection; +import javax.management.MBeanServerInvocationHandler; +import javax.management.MalformedObjectNameException; import javax.management.ObjectInstance; import javax.management.ObjectName; import javax.management.ReflectionException; +import org.apache.qpid.management.common.mbeans.ServerInformation; +import org.apache.qpid.management.common.mbeans.UserManagement; +import org.apache.qpid.management.ui.ApiVersion; import org.apache.qpid.management.ui.ApplicationRegistry; import org.apache.qpid.management.ui.ManagedBean; import org.apache.qpid.management.ui.ManagedServer; @@ -440,6 +445,71 @@ public class MBeanUtility return mbeans; } + /** + * Classifies the management API version of the given server + * @return list of ManagedBeans + * @throws NullPointerException + * @throws ManagementConsoleException + * @throws MalformedObjectNameException + * @throws IOException + */ + public static void classifyManagementApiVersion(ManagedServer server, JMXServerRegistry serverRegistry) + throws ManagementConsoleException, MalformedObjectNameException, NullPointerException, IOException + { + MBeanServerConnection mbsc = serverRegistry.getServerConnection(); + + //Detect if the ServerInformation MBean is present, and use it to retrieve the API version. + ObjectName objName = new ObjectName(server.getDomain() + ":type="+ ServerInformation.TYPE + ",*"); + Set<ObjectName> objectInstances = mbsc.queryNames(objName, null); + + if(objectInstances.size() != 0) + { + for (Iterator<ObjectName> itr = objectInstances.iterator(); itr.hasNext();) + { + ObjectName instance = (ObjectName)itr.next(); + ServerInformation simb = (ServerInformation) + MBeanServerInvocationHandler.newProxyInstance(mbsc, + instance, ServerInformation.class, false); + + int major = simb.getManagementApiMajorVersion(); + int minor = simb.getManagementApiMinorVersion(); + + serverRegistry.setManagementApiVersion(new ApiVersion(major, minor)); + } + + return; + } + + //ServerInformation mbean was not present, so this is a older pre-v1.3 API server. + + //Detect the value of the 'version' key property on the UserManagement MBean ObjectName. + //If present, we have a v1.2 API server. If null, we have a v1.1 API server. + objName = new ObjectName(server.getDomain() + ":type="+ UserManagement.TYPE + ",*"); + objectInstances = mbsc.queryNames(objName, null); + + if(objectInstances.size() != 0) + { + for (Iterator<ObjectName> itr = objectInstances.iterator(); itr.hasNext();) + { + ObjectName instance = (ObjectName)itr.next(); + String version = instance.getKeyProperty("version"); + + if(version != null) + { + serverRegistry.setManagementApiVersion(new ApiVersion(1, 2)); + } + else + { + serverRegistry.setManagementApiVersion(new ApiVersion(1, 1)); + } + } + + return; + } + + throw new ManagementConsoleException("Unable to classify the server management API version"); + } + public static void printOutput(String statement) { if (ApplicationRegistry.debug) diff --git a/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/MBeanView.java b/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/MBeanView.java index 180be9d761..d92e8ef49d 100644 --- a/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/MBeanView.java +++ b/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/MBeanView.java @@ -23,9 +23,12 @@ package org.apache.qpid.management.ui.views; import javax.management.MBeanServerConnection; import static org.apache.qpid.management.ui.Constants.*; + +import org.apache.qpid.management.ui.ApiVersion; import org.apache.qpid.management.ui.ApplicationRegistry; import org.apache.qpid.management.ui.ManagedBean; import org.apache.qpid.management.ui.ManagedServer; +import org.apache.qpid.management.ui.ServerRegistry; import org.apache.qpid.management.ui.jmx.JMXManagedObject; import org.apache.qpid.management.ui.jmx.JMXServerRegistry; import org.apache.qpid.management.ui.jmx.MBeanUtility; @@ -95,8 +98,14 @@ public class MBeanView extends ViewPart } setServer(); + + if (MBEAN.equals(_selectedNode.getType())) + { + _mbean = (ManagedBean)_selectedNode.getManagedObject(); + } + + setFormTitle(); showRelevantTabView(); - setFormTitle(); } } @@ -154,7 +163,7 @@ public class MBeanView extends ViewPart { try { - if (_selectedNode == null || NODE_TYPE_SERVER.equals(_selectedNode.getType())) + if (_selectedNode == null) { return; } @@ -176,9 +185,33 @@ public class MBeanView extends ViewPart } else if (MBEAN.equals(mbeanType)) { - _mbean = (ManagedBean)_selectedNode.getManagedObject(); showMBean(_mbean); } + else if(NODE_TYPE_SERVER.equals(mbeanType)) + { + ServerRegistry serverReg = ApplicationRegistry.getServerRegistry(_server); + + //check the server is connected + if(serverReg != null) + { + //post a message if the server supports a newer API version. + ApiVersion serverAPI = serverReg.getManagementApiVersion(); + int supportedMajor = ApplicationRegistry.SUPPORTED_QPID_JMX_API_MAJOR_VERSION; + int supportedMinor = ApplicationRegistry.SUPPORTED_QPID_JMX_API_MINOR_VERSION; + + if(serverAPI.greaterThan(supportedMajor, supportedMinor)) + { + _form.setText("The server supports an updated management API and may offer " + + "functionality not available with this console. " + + "Please check for an updated console release."); + } + + } + } + else + { + return; + } _form.layout(true); _form.getBody().layout(true, true); diff --git a/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/NavigationView.java b/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/NavigationView.java index ef9a75d702..2f22a7164d 100644 --- a/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/NavigationView.java +++ b/java/management/eclipse-plugin/src/main/java/org/apache/qpid/management/ui/views/NavigationView.java @@ -33,11 +33,13 @@ import org.apache.qpid.management.common.mbeans.ConfigurationManagement; import org.apache.qpid.management.common.mbeans.LoggingManagement; import org.apache.qpid.management.common.mbeans.ServerInformation; import org.apache.qpid.management.common.mbeans.UserManagement; +import org.apache.qpid.management.ui.ApiVersion; import org.apache.qpid.management.ui.ApplicationRegistry; import org.apache.qpid.management.ui.ManagedBean; import org.apache.qpid.management.ui.ManagedServer; import org.apache.qpid.management.ui.ServerRegistry; import org.apache.qpid.management.ui.exceptions.InfoRequiredException; +import org.apache.qpid.management.ui.exceptions.ManagementConsoleException; import org.apache.qpid.management.ui.jmx.JMXServerRegistry; import org.apache.qpid.management.ui.jmx.MBeanUtility; import org.eclipse.jface.preference.PreferenceStore; @@ -234,7 +236,34 @@ public class NavigationView extends ViewPart private void createJMXServerConnection(ManagedServer server) throws Exception { // Currently Qpid Management Console only supports JMX MBeanServer - ServerRegistry serverRegistry = new JMXServerRegistry(server); + JMXServerRegistry serverRegistry = new JMXServerRegistry(server); + + try + { + //determine the management API version of the server just connected to + MBeanUtility.classifyManagementApiVersion(server, serverRegistry); + } + catch (Exception e) + { + //Exception classifying the server API, clean up the connection and rethrow + serverRegistry.closeServerConnection(); + throw e; + } + + //check that the console supports the API major version encountered, otherwise abort. + ApiVersion serverAPI = serverRegistry.getManagementApiVersion(); + + int serverMajor = serverAPI.getMajor(); + int supportedMajor = ApplicationRegistry.SUPPORTED_QPID_JMX_API_MAJOR_VERSION; + + if(serverMajor > supportedMajor) + { + serverRegistry.closeServerConnection(); + throw new ManagementConsoleException("The server management API version encountered is not supported" + + " by this console release. Please check for an updated console release."); + } + + //connection succeeded, add the ServerRegistry to the ApplicationRegistry ApplicationRegistry.addServer(server, serverRegistry); } @@ -294,6 +323,8 @@ public class NavigationView extends ViewPart expandInitialMBeanView(serverNode); + //(re)select the server node now that it is connected to force a selectionEvent + _treeViewer.setSelection(new StructuredSelection(serverNode)); _treeViewer.refresh(); // save server address in file @@ -777,7 +808,7 @@ public class NavigationView extends ViewPart managedServer.setUser(user); managedServer.setPassword(password); createJMXServerConnection(managedServer); - + // put the server in the managed server map _managedServerMap.put(managedServer, selectedNode); @@ -798,6 +829,8 @@ public class NavigationView extends ViewPart expandInitialMBeanView(selectedNode); + //(re)select the server node now that it is connected to force a selectionEvent + _treeViewer.setSelection(new StructuredSelection(selectedNode)); _treeViewer.refresh(); } diff --git a/java/management/eclipse-plugin/src/test/java/org/apache/qpid/management/ui/ApiVersionTest.java b/java/management/eclipse-plugin/src/test/java/org/apache/qpid/management/ui/ApiVersionTest.java new file mode 100644 index 0000000000..b4f6aea57b --- /dev/null +++ b/java/management/eclipse-plugin/src/test/java/org/apache/qpid/management/ui/ApiVersionTest.java @@ -0,0 +1,177 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.management.ui; + +import junit.framework.TestCase; + +public class ApiVersionTest extends TestCase +{ + + public void testGetMajor() + { + ApiVersion ver = new ApiVersion(1,3); + assertEquals(1, ver.getMajor()); + } + + public void testGetMinor() + { + ApiVersion ver = new ApiVersion(1,3); + assertEquals(3, ver.getMinor()); + } + + public void testGreaterThanOrEqualTo() + { + ApiVersion ver = new ApiVersion(1,3); + + //equal + assertTrue(ver.greaterThanOrEqualTo(1, 3)); + //same major, higher minor + assertFalse(ver.greaterThanOrEqualTo(1, 4)); + //same major, lower minor + assertTrue(ver.greaterThanOrEqualTo(1, 2)); + + //higher major, lower minor + assertFalse(ver.greaterThanOrEqualTo(2, 0)); + //higher major, same minor + assertFalse(ver.greaterThanOrEqualTo(2, 3)); + //higher major, higher minor + assertFalse(ver.greaterThanOrEqualTo(2, 4)); + + //lower major, higher minor + assertTrue(ver.greaterThanOrEqualTo(0, 9)); + //lower major, lower minor + assertTrue(ver.greaterThanOrEqualTo(0, 2)); + //lower major, same minor + assertTrue(ver.greaterThanOrEqualTo(0, 3)); + } + + public void testLessThanOrEqualTo() + { + ApiVersion ver = new ApiVersion(1,3); + + //equal + assertTrue(ver.lessThanOrEqualTo(1, 3)); + //same major, higher minor + assertTrue(ver.lessThanOrEqualTo(1, 4)); + //same major, lower minor + assertFalse(ver.lessThanOrEqualTo(1, 2)); + + //higher major, lower minor + assertTrue(ver.lessThanOrEqualTo(2, 0)); + //higher major, same minor + assertTrue(ver.lessThanOrEqualTo(2, 3)); + //higher major, higher minor + assertTrue(ver.lessThanOrEqualTo(2, 4)); + + //lower major, higher minor + assertFalse(ver.lessThanOrEqualTo(0, 9)); + //lower major, lower minor + assertFalse(ver.lessThanOrEqualTo(0, 2)); + //lower major, same minor + assertFalse(ver.lessThanOrEqualTo(0, 3)); + } + + public void testGreaterThan() + { + ApiVersion ver = new ApiVersion(1,3); + + //equal + assertFalse(ver.greaterThan(1, 3)); + //same major, higher minor + assertFalse(ver.greaterThan(1, 4)); + //same major, lower minor + assertTrue(ver.greaterThan(1, 2)); + + //higher major, lower minor + assertFalse(ver.greaterThan(2, 0)); + //higher major, same minor + assertFalse(ver.greaterThan(2, 3)); + //higher major, higher minor + assertFalse(ver.greaterThan(2, 4)); + + //lower major, higher minor + assertTrue(ver.greaterThan(0, 9)); + //lower major, lower minor + assertTrue(ver.greaterThan(0, 2)); + //lower major, same minor + assertTrue(ver.greaterThan(0, 3)); + } + + public void testLessThan() + { + ApiVersion ver = new ApiVersion(1,3); + + //equal + assertFalse(ver.lessThan(1, 3)); + //same major, higher minor + assertTrue(ver.lessThan(1, 4)); + //same major, lower minor + assertFalse(ver.lessThan(1, 2)); + + //higher major, lower minor + assertTrue(ver.lessThan(2, 0)); + //higher major, same minor + assertTrue(ver.lessThan(2, 3)); + //higher major, higher minor + assertTrue(ver.lessThan(2, 4)); + + //lower major, higher minor + assertFalse(ver.lessThan(0, 9)); + //lower major, lower minor + assertFalse(ver.lessThan(0, 2)); + //lower major, same minor + assertFalse(ver.lessThan(0, 3)); + } + + public void testEqualsIntInt() + { + ApiVersion ver = new ApiVersion(1,3); + + //equal + assertTrue(ver.equals(1, 3)); + //same major, higher minor + assertFalse(ver.equals(1, 4)); + //same major, lower minor + assertFalse(ver.equals(1, 2)); + + //higher major, lower minor + assertFalse(ver.equals(2, 0)); + //higher major, same minor + assertFalse(ver.equals(2, 3)); + //higher major, higher minor + assertFalse(ver.equals(2, 4)); + + //lower major, higher minor + assertFalse(ver.equals(0, 9)); + //lower major, lower minor + assertFalse(ver.equals(0, 2)); + //lower major, same minor + assertFalse(ver.equals(0, 3)); + } + + public void testToString() + { + ApiVersion ver = new ApiVersion(1,3); + + assertEquals("major=1,minor=3", ver.toString()); + } + +} diff --git a/java/management/eclipse-plugin/src/test/java/org/apache/qpid/management/ui/ApplicationRegistryTest.java b/java/management/eclipse-plugin/src/test/java/org/apache/qpid/management/ui/ApplicationRegistryTest.java new file mode 100644 index 0000000000..1a56ab69b6 --- /dev/null +++ b/java/management/eclipse-plugin/src/test/java/org/apache/qpid/management/ui/ApplicationRegistryTest.java @@ -0,0 +1,43 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.management.ui; + +import org.apache.qpid.management.common.mbeans.ServerInformation; + +import junit.framework.TestCase; + +public class ApplicationRegistryTest extends TestCase +{ + public void testSupportedManagementApiVersion() + { + //ensure that the console supported API version is kept in sync with the broker + + assertEquals("The management console does not support the same major version of management API as the broker. " + + "Make any required changes and update the supported value.", + ServerInformation.QPID_JMX_API_MAJOR_VERSION, + ApplicationRegistry.SUPPORTED_QPID_JMX_API_MAJOR_VERSION); + + assertEquals("The management console does not support the same minor version of management API as the broker. " + + "Make any required changes and update the supported value.", + ServerInformation.QPID_JMX_API_MINOR_VERSION, + ApplicationRegistry.SUPPORTED_QPID_JMX_API_MINOR_VERSION); + } +} |
