From ebeea15c2a8568952be8af2c4567ca5cf39898a8 Mon Sep 17 00:00:00 2001 From: Tomas Hofman Date: Tue, 19 Jan 2021 15:04:35 +0100 Subject: [PATCH] ARTEMIS-3074 Add ActiveMQServerControl#createBridge() method variant accepting a JSON string --- .../management/ActiveMQServerControl.java | 24 ++ .../core/config/TransformerConfiguration.java | 47 +++- .../core/config/BridgeConfiguration.java | 251 ++++++++++++++++++ .../impl/ActiveMQServerControlImpl.java | 23 ++ .../core/config/BridgeConfigurationTest.java | 194 ++++++++++++++ .../management/ActiveMQServerControlTest.java | 92 +++++++ .../ActiveMQServerControlUsingCoreTest.java | 5 + 7 files changed, 635 insertions(+), 1 deletion(-) create mode 100644 artemis-server/src/test/java/org/apache/activemq/artemis/core/config/BridgeConfigurationTest.java diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java index e981444939..7668b65734 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java @@ -1679,6 +1679,10 @@ public interface ActiveMQServerControl { @Attribute(desc = "Names of the bridges deployed on this server") String[] getBridgeNames(); + /** + * @deprecated Deprecated in favour of {@link #createBridge(String)} + */ + @Deprecated @Operation(desc = "Create a Bridge", impact = MBeanOperationInfo.ACTION) void createBridge(@Parameter(name = "name", desc = "Name of the bridge") String name, @Parameter(name = "queueName", desc = "Name of the source queue") String queueName, @@ -1699,6 +1703,10 @@ public interface ActiveMQServerControl { @Parameter(name = "user", desc = "User name") String user, @Parameter(name = "password", desc = "User password") String password) throws Exception; + /** + * @deprecated Deprecated in favour of {@link #createBridge(String)} + */ + @Deprecated @Operation(desc = "Create a Bridge", impact = MBeanOperationInfo.ACTION) void createBridge(@Parameter(name = "name", desc = "Name of the bridge") String name, @Parameter(name = "queueName", desc = "Name of the source queue") String queueName, @@ -1720,6 +1728,10 @@ public interface ActiveMQServerControl { @Parameter(name = "user", desc = "User name") String user, @Parameter(name = "password", desc = "User password") String password) throws Exception; + /** + * @deprecated Deprecated in favour of {@link #createBridge(String)} + */ + @Deprecated @Operation(desc = "Create a Bridge", impact = MBeanOperationInfo.ACTION) void createBridge(@Parameter(name = "name", desc = "Name of the bridge") String name, @Parameter(name = "queueName", desc = "Name of the source queue") String queueName, @@ -1741,6 +1753,10 @@ public interface ActiveMQServerControl { @Parameter(name = "user", desc = "User name") String user, @Parameter(name = "password", desc = "User password") String password) throws Exception; + /** + * @deprecated Deprecated in favour of {@link #createBridge(String)} + */ + @Deprecated @Operation(desc = "Create a Bridge", impact = MBeanOperationInfo.ACTION) void createBridge(@Parameter(name = "name", desc = "Name of the bridge") String name, @Parameter(name = "queueName", desc = "Name of the source queue") String queueName, @@ -1760,6 +1776,14 @@ public interface ActiveMQServerControl { @Parameter(name = "user", desc = "User name") String user, @Parameter(name = "password", desc = "User password") String password) throws Exception; + /** + * Create a bridge. + * + * @param bridgeConfiguration the configuration of the queue in JSON format + */ + @Operation(desc = "Create a bridge", impact = MBeanOperationInfo.ACTION) + void createBridge(@Parameter(name = "bridgeConfiguration", desc = "the configuration of the bridge in JSON format") String bridgeConfiguration) throws Exception; + @Operation(desc = "Destroy a bridge", impact = MBeanOperationInfo.ACTION) void destroyBridge(@Parameter(name = "name", desc = "Name of the bridge") String name) throws Exception; diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/TransformerConfiguration.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/TransformerConfiguration.java index 36c9960be9..1819679d3b 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/TransformerConfiguration.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/config/TransformerConfiguration.java @@ -16,7 +16,13 @@ */ package org.apache.activemq.artemis.core.config; +import org.apache.activemq.artemis.utils.JsonLoader; + +import javax.json.JsonObject; +import javax.json.JsonString; +import javax.json.JsonValue; import java.io.Serializable; +import java.io.StringReader; import java.util.HashMap; import java.util.Map; @@ -24,8 +30,10 @@ public final class TransformerConfiguration implements Serializable { private static final long serialVersionUID = -1057244274380572226L; - private final String className; + public static final String CLASS_NAME = "class-name"; + public static final String PROPERTIES = "properties"; + private final String className; private Map properties = new HashMap<>(); public TransformerConfiguration(String className) { @@ -40,6 +48,43 @@ public final class TransformerConfiguration implements Serializable { return properties; } + /** + * This method returns a {@code TransformerConfiguration} created from the JSON-formatted input {@code String}. + * The input should contain these entries: + * + *

+ * + * @param jsonString json string + * @return the {@code TransformerConfiguration} created from the JSON-formatted input {@code String} + */ + public static TransformerConfiguration fromJSON(String jsonString) { + JsonObject json = JsonLoader.readObject(new StringReader(jsonString)); + + // name is the only required value + if (!json.containsKey(CLASS_NAME)) { + return null; + } + + TransformerConfiguration result = new TransformerConfiguration(json.getString(CLASS_NAME)); + + if (json.containsKey(PROPERTIES)) { + HashMap properties = new HashMap<>(); + for (Map.Entry propEntry: json.getJsonObject(PROPERTIES).entrySet()) { + if (propEntry.getValue().getValueType() == JsonValue.ValueType.STRING) { + properties.put(propEntry.getKey(), ((JsonString) propEntry.getValue()).getString()); + } else { + properties.put(propEntry.getKey(), propEntry.getValue().toString()); + } + } + result.setProperties(properties); + } + + return result; + } + /** * @param properties the properties to set */ diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/BridgeConfiguration.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/BridgeConfiguration.java index d7b388d6b8..6eb6146aff 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/BridgeConfiguration.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/BridgeConfiguration.java @@ -16,17 +16,51 @@ */ package org.apache.activemq.artemis.core.config; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.json.JsonString; +import javax.json.JsonValue; import java.io.Serializable; +import java.io.StringReader; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration; import org.apache.activemq.artemis.api.core.client.ActiveMQClient; import org.apache.activemq.artemis.core.server.ComponentConfigurationRoutingType; +import org.apache.activemq.artemis.utils.JsonLoader; public final class BridgeConfiguration implements Serializable { private static final long serialVersionUID = -1057244274380572226L; + public static String NAME = "name"; + public static String QUEUE_NAME = "queue-name"; + public static String FORWARDING_ADDRESS = "forwarding-address"; + public static String FILTER_STRING = "filter-string"; + public static String STATIC_CONNECTORS = "static-connectors"; + public static String DISCOVERY_GROUP_NAME = "discovery-group-name"; + public static String HA = "ha"; + public static String TRANSFORMER_CONFIGURATION = "transformer-configuration"; + public static String RETRY_INTERVAL = "retry-interval"; + public static String RETRY_INTERVAL_MULTIPLIER = "retry-interval-multiplier"; + public static String INITIAL_CONNECT_ATTEMPTS = "initial-connect-attempts"; + public static String RECONNECT_ATTEMPTS = "reconnect-attempts"; + public static String RECONNECT_ATTEMPTS_ON_SAME_NODE = "reconnect-attempts-on-same-node"; + public static String USE_DUPLICATE_DETECTION = "use-duplicate-detection"; + public static String CONFIRMATION_WINDOW_SIZE = "confirmation-window-size"; + public static String PRODUCER_WINDOW_SIZE = "producer-window-size"; + public static String CLIENT_FAILURE_CHECK_PERIOD = "client-failure-check-period"; + public static String USER = "user"; + public static String PASSWORD = "password"; + public static String CONNECTION_TTL = "connection-ttl"; + public static String MAX_RETRY_INTERVAL = "max-retry-interval"; + public static String MIN_LARGE_MESSAGE_SIZE = "min-large-message-size"; + public static String CALL_TIMEOUT = "call-timeout"; + public static String ROUTING_TYPE = "routing-type"; + private String name = null; private String queueName = null; @@ -81,6 +115,110 @@ public final class BridgeConfiguration implements Serializable { public BridgeConfiguration() { } + public BridgeConfiguration(String name) { + setName(name); + } + + /** + * Set the value of a parameter based on its "key" {@code String}. Valid key names and corresponding {@code static} + * {@code final} are: + *

    + *
  • name: {@link #NAME} + *
  • queue-name: {@link #QUEUE_NAME} + *
  • forwarding-address: {@link #FORWARDING_ADDRESS} + *
  • filter-string: {@link #FILTER_STRING} + *
  • static-connectors: {@link #STATIC_CONNECTORS} + *
  • discovery-group-name: {@link #DISCOVERY_GROUP_NAME} + *
  • ha: {@link #HA} + *
  • transformer-configuration: {@link #TRANSFORMER_CONFIGURATION} + *
  • retry-interval: {@link #RETRY_INTERVAL} + *
  • RETRY-interval-multiplier: {@link #RETRY_INTERVAL_MULTIPLIER} + *
  • initial-connect-attempts: {@link #INITIAL_CONNECT_ATTEMPTS} + *
  • reconnect-attempts: {@link #RECONNECT_ATTEMPTS} + *
  • reconnect-attempts-on-same-node: {@link #RECONNECT_ATTEMPTS_ON_SAME_NODE} + *
  • use-duplicate-detection: {@link #USE_DUPLICATE_DETECTION} + *
  • confirmation-window-size: {@link #CONFIRMATION_WINDOW_SIZE} + *
  • producer-window-size: {@link #PRODUCER_WINDOW_SIZE} + *
  • client-failure-check-period: {@link #CLIENT_FAILURE_CHECK_PERIOD} + *
  • user: {@link #USER} + *
  • password: {@link #PASSWORD} + *
  • connection-ttl: {@link #CONNECTION_TTL} + *
  • max-retry-interval: {@link #MAX_RETRY_INTERVAL} + *
  • min-large-message-size: {@link #MIN_LARGE_MESSAGE_SIZE} + *
  • call-timeout: {@link #CALL_TIMEOUT} + *
  • routing-type: {@link #ROUTING_TYPE} + *

+ * The {@code String}-based values will be converted to the proper value types based on the underlying property. For + * example, if you pass the value "TRUE" for the key "auto-created" the {@code String} "TRUE" will be converted to + * the {@code Boolean} {@code true}. + * + * @param key the key to set to the value + * @param value the value to set for the key + * @return this {@code BridgeConfiguration} + */ + public BridgeConfiguration set(String key, String value) { + if (key != null) { + if (key.equals(NAME)) { + setName(value); + } else if (key.equals(QUEUE_NAME)) { + setQueueName(value); + } else if (key.equals(FORWARDING_ADDRESS)) { + setForwardingAddress(value); + } else if (key.equals(FILTER_STRING)) { + setFilterString(value); + } else if (key.equals(STATIC_CONNECTORS)) { + // convert JSON array to string list + List stringList = JsonLoader.readArray(new StringReader(value)).stream() + .map(v -> ((JsonString) v).getString()) + .collect(Collectors.toList()); + setStaticConnectors(stringList); + } else if (key.equals(DISCOVERY_GROUP_NAME)) { + setDiscoveryGroupName(value); + } else if (key.equals(HA)) { + setHA(Boolean.parseBoolean(value)); + } else if (key.equals(TRANSFORMER_CONFIGURATION)) { + // create a transformer instance from a JSON string + TransformerConfiguration transformerConfiguration = TransformerConfiguration.fromJSON(value); + if (transformerConfiguration != null) { + setTransformerConfiguration(transformerConfiguration); + } + } else if (key.equals(RETRY_INTERVAL)) { + setRetryInterval(Long.parseLong(value)); + } else if (key.equals(RETRY_INTERVAL_MULTIPLIER)) { + setRetryIntervalMultiplier(Double.parseDouble(value)); + } else if (key.equals(INITIAL_CONNECT_ATTEMPTS)) { + setInitialConnectAttempts(Integer.parseInt(value)); + } else if (key.equals(RECONNECT_ATTEMPTS)) { + setReconnectAttempts(Integer.parseInt(value)); + } else if (key.equals(RECONNECT_ATTEMPTS_ON_SAME_NODE)) { + setReconnectAttemptsOnSameNode(Integer.parseInt(value)); + } else if (key.equals(USE_DUPLICATE_DETECTION)) { + setUseDuplicateDetection(Boolean.parseBoolean(value)); + } else if (key.equals(CONFIRMATION_WINDOW_SIZE)) { + setConfirmationWindowSize(Integer.parseInt(value)); + } else if (key.equals(PRODUCER_WINDOW_SIZE)) { + setProducerWindowSize(Integer.parseInt(value)); + } else if (key.equals(CLIENT_FAILURE_CHECK_PERIOD)) { + setClientFailureCheckPeriod(Long.parseLong(value)); + } else if (key.equals(USER)) { + setUser(value); + } else if (key.equals(PASSWORD)) { + setPassword(value); + } else if (key.equals(CONNECTION_TTL)) { + setConnectionTTL(Long.parseLong(value)); + } else if (key.equals(MAX_RETRY_INTERVAL)) { + setMaxRetryInterval(Long.parseLong(value)); + } else if (key.equals(MIN_LARGE_MESSAGE_SIZE)) { + setMinLargeMessageSize(Integer.parseInt(value)); + } else if (key.equals(CALL_TIMEOUT)) { + setCallTimeout(Long.parseLong(value)); + } else if (key.equals(ROUTING_TYPE)) { + setRoutingType(ComponentConfigurationRoutingType.valueOf(value)); + } + } + return this; + } + public String getName() { return name; } @@ -360,6 +498,119 @@ public final class BridgeConfiguration implements Serializable { return this; } + /** + * This method returns a JSON-formatted {@code String} representation of this {@code BridgeConfiguration}. It is a + * simple collection of key/value pairs. The keys used are referenced in {@link #set(String, String)}. + * + * @return a JSON-formatted {@code String} representation of this {@code BridgeConfiguration} + */ + public String toJSON() { + JsonObjectBuilder builder = JsonLoader.createObjectBuilder(); + + // string fields which default to null (only serialize if value is not null) + + if (getName() != null) { + builder.add(NAME, getName()); + } + if (getQueueName() != null) { + builder.add(QUEUE_NAME, getQueueName()); + } + if (getForwardingAddress() != null) { + builder.add(FORWARDING_ADDRESS, getForwardingAddress()); + } + if (getFilterString() != null) { + builder.add(FILTER_STRING, getFilterString()); + } + if (getDiscoveryGroupName() != null) { + builder.add(DISCOVERY_GROUP_NAME, getDiscoveryGroupName()); + } + + // string fields which default to non-null values (always serialize) + + addNullable(builder, USER, getUser()); + addNullable(builder, PASSWORD, getPassword()); + + // primitive data type fields (always serialize) + + builder.add(HA, isHA()); + builder.add(RETRY_INTERVAL, getRetryInterval()); + builder.add(RETRY_INTERVAL_MULTIPLIER, getRetryIntervalMultiplier()); + builder.add(INITIAL_CONNECT_ATTEMPTS, getInitialConnectAttempts()); + builder.add(RECONNECT_ATTEMPTS, getReconnectAttempts()); + builder.add(RECONNECT_ATTEMPTS_ON_SAME_NODE, getReconnectAttemptsOnSameNode()); + builder.add(USE_DUPLICATE_DETECTION, isUseDuplicateDetection()); + builder.add(CONFIRMATION_WINDOW_SIZE, getConfirmationWindowSize()); + builder.add(PRODUCER_WINDOW_SIZE, getProducerWindowSize()); + builder.add(CLIENT_FAILURE_CHECK_PERIOD, getClientFailureCheckPeriod()); + builder.add(CONNECTION_TTL, getConnectionTTL()); + builder.add(MAX_RETRY_INTERVAL, getMaxRetryInterval()); + builder.add(MIN_LARGE_MESSAGE_SIZE, getMinLargeMessageSize()); + builder.add(CALL_TIMEOUT, getCallTimeout()); + + // complex fields (only serialize if value is not null) + + if (getRoutingType() != null) { + builder.add(ROUTING_TYPE, getRoutingType().name()); + } + + final List staticConnectors = getStaticConnectors(); + if (staticConnectors != null) { + JsonArrayBuilder arrayBuilder = JsonLoader.createArrayBuilder(); + staticConnectors.forEach(arrayBuilder::add); + builder.add(STATIC_CONNECTORS, arrayBuilder); + } + + TransformerConfiguration tc = getTransformerConfiguration(); + if (tc != null) { + JsonObjectBuilder tcBuilder = JsonLoader.createObjectBuilder().add(TransformerConfiguration.CLASS_NAME, tc.getClassName()); + if (tc.getProperties() != null && tc.getProperties().size() > 0) { + JsonObjectBuilder propBuilder = JsonLoader.createObjectBuilder(); + tc.getProperties().forEach(propBuilder::add); + tcBuilder.add(TransformerConfiguration.PROPERTIES, propBuilder); + } + builder.add(TRANSFORMER_CONFIGURATION, tcBuilder); + } + + return builder.build().toString(); + } + + private static void addNullable(JsonObjectBuilder builder, String key, String value) { + if (value == null) { + builder.addNull(key); + } else { + builder.add(key, value); + } + } + + /** + * This method returns a {@code BridgeConfiguration} created from the JSON-formatted input {@code String}. The input + * should be a simple object of key/value pairs. Valid keys are referenced in {@link #set(String, String)}. + * + * @param jsonString json string + * @return the {@code BridgeConfiguration} created from the JSON-formatted input {@code String} + */ + public static BridgeConfiguration fromJSON(String jsonString) { + JsonObject json = JsonLoader.readObject(new StringReader(jsonString)); + + // name is the only required value + if (!json.containsKey(NAME)) { + return null; + } + BridgeConfiguration result = new BridgeConfiguration(json.getString(NAME)); + + for (Map.Entry entry : json.entrySet()) { + if (entry.getValue().getValueType() == JsonValue.ValueType.STRING) { + result.set(entry.getKey(), ((JsonString) entry.getValue()).getString()); + } else if (entry.getValue().getValueType() == JsonValue.ValueType.NULL) { + result.set(entry.getKey(), null); + } else { + result.set(entry.getKey(), entry.getValue().toString()); + } + } + + return result; + } + @Override public int hashCode() { final int prime = 31; diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java index 8306a6dea8..b1783a634d 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java @@ -3822,6 +3822,29 @@ public class ActiveMQServerControlImpl extends AbstractControl implements Active } } + @Override + public void createBridge(String bridgeConfigurationAsJson) throws Exception { + if (AuditLogger.isEnabled()) { + AuditLogger.createBridge(this.server, bridgeConfigurationAsJson); + } + checkStarted(); + + clearIO(); + + try { + // when the BridgeConfiguration is passed through createBridge all of its defaults get set which we return to the caller + BridgeConfiguration bridgeConfiguration = BridgeConfiguration.fromJSON(bridgeConfigurationAsJson); + if (bridgeConfiguration == null) { + throw ActiveMQMessageBundle.BUNDLE.failedToParseJson(bridgeConfigurationAsJson); + } + server.deployBridge(bridgeConfiguration); + } catch (ActiveMQException e) { + throw new IllegalStateException(e.getMessage()); + } finally { + blockOnIO(); + } + } + @Override public String listBrokerConnections() { if (AuditLogger.isEnabled()) { diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/BridgeConfigurationTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/BridgeConfigurationTest.java new file mode 100644 index 0000000000..17a8050781 --- /dev/null +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/BridgeConfigurationTest.java @@ -0,0 +1,194 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.activemq.artemis.core.config; + +import org.apache.activemq.artemis.core.server.ComponentConfigurationRoutingType; +import org.apache.activemq.artemis.utils.JsonLoader; +import org.junit.Assert; +import org.junit.Test; + +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.json.JsonValue; +import javax.json.spi.JsonProvider; +import java.io.StringReader; + +import static org.hamcrest.Matchers.containsInAnyOrder; + +public class BridgeConfigurationTest { + + @Test + public void testFromJSON() { + String jsonString = createFullJsonObject().toString(); + + BridgeConfiguration bridgeConfiguration = BridgeConfiguration.fromJSON(jsonString); + + Assert.assertNotNull(bridgeConfiguration); + Assert.assertEquals("name", bridgeConfiguration.getName()); + Assert.assertEquals("queue-name", bridgeConfiguration.getQueueName()); + Assert.assertEquals("forwarding-address", bridgeConfiguration.getForwardingAddress()); + Assert.assertEquals("filter-string", bridgeConfiguration.getFilterString()); + Assert.assertArrayEquals(new String[]{"connector1", "connector2"}, + bridgeConfiguration.getStaticConnectors().toArray()); + Assert.assertEquals("dg", bridgeConfiguration.getDiscoveryGroupName()); + Assert.assertTrue(bridgeConfiguration.isHA()); + Assert.assertEquals("ClassName", bridgeConfiguration.getTransformerConfiguration().getClassName()); + Assert.assertThat(bridgeConfiguration.getTransformerConfiguration().getProperties().keySet(), containsInAnyOrder("prop1", "prop2")); + Assert.assertEquals("val1", bridgeConfiguration.getTransformerConfiguration().getProperties().get("prop1")); + Assert.assertEquals("val2", bridgeConfiguration.getTransformerConfiguration().getProperties().get("prop2")); + Assert.assertEquals(1, bridgeConfiguration.getRetryInterval()); + Assert.assertEquals(2.0, bridgeConfiguration.getRetryIntervalMultiplier(), 0); + Assert.assertEquals(3, bridgeConfiguration.getInitialConnectAttempts()); + Assert.assertEquals(4, bridgeConfiguration.getReconnectAttempts()); + Assert.assertEquals(5, bridgeConfiguration.getReconnectAttemptsOnSameNode()); + Assert.assertTrue(bridgeConfiguration.isUseDuplicateDetection()); + Assert.assertEquals(6, bridgeConfiguration.getConfirmationWindowSize()); + Assert.assertEquals(7, bridgeConfiguration.getProducerWindowSize()); + Assert.assertEquals(8, bridgeConfiguration.getClientFailureCheckPeriod()); + Assert.assertEquals("user", bridgeConfiguration.getUser()); + Assert.assertEquals("password", bridgeConfiguration.getPassword()); + Assert.assertEquals(9, bridgeConfiguration.getConnectionTTL()); + Assert.assertEquals(10, bridgeConfiguration.getMaxRetryInterval()); + Assert.assertEquals(11, bridgeConfiguration.getMinLargeMessageSize()); + Assert.assertEquals(12, bridgeConfiguration.getCallTimeout()); + Assert.assertEquals(ComponentConfigurationRoutingType.MULTICAST, bridgeConfiguration.getRoutingType()); + } + + @Test + public void testToJSON() { + // create bc instance from a JSON object, all attributes are set + JsonObject jsonObject = createFullJsonObject(); + BridgeConfiguration bridgeConfiguration = BridgeConfiguration.fromJSON(jsonObject.toString()); + Assert.assertNotNull(bridgeConfiguration); + + // serialize it back to JSON + String serializedBridgeConfiguration = bridgeConfiguration.toJSON(); + JsonObject serializedBridgeConfigurationJsonObject = JsonLoader.readObject(new StringReader(serializedBridgeConfiguration)); + + // verify that the original JSON object is identical to the one serialized via the toJSON() method + Assert.assertEquals(jsonObject, serializedBridgeConfigurationJsonObject); + } + + @Test + public void testDefaultsToJson() { + // create and serialize BridgeConfiguration instance without modifying any fields + BridgeConfiguration bridgeConfiguration = new BridgeConfiguration(); + String jsonString = bridgeConfiguration.toJSON(); + JsonObject jsonObject = JsonLoader.readObject(new StringReader(jsonString)); + + // the serialized JSON string should contain default values of primitive type fields + Assert.assertEquals("2000", jsonObject.get(BridgeConfiguration.RETRY_INTERVAL).toString()); + Assert.assertEquals("1.0", jsonObject.get(BridgeConfiguration.RETRY_INTERVAL_MULTIPLIER).toString()); + Assert.assertEquals("-1", jsonObject.get(BridgeConfiguration.INITIAL_CONNECT_ATTEMPTS).toString()); + Assert.assertEquals("-1", jsonObject.get(BridgeConfiguration.RECONNECT_ATTEMPTS).toString()); + Assert.assertEquals("10", jsonObject.get(BridgeConfiguration.RECONNECT_ATTEMPTS_ON_SAME_NODE).toString()); + Assert.assertEquals("true", jsonObject.get(BridgeConfiguration.USE_DUPLICATE_DETECTION).toString()); + Assert.assertEquals("-1", jsonObject.get(BridgeConfiguration.CONFIRMATION_WINDOW_SIZE).toString()); + Assert.assertEquals("-1", jsonObject.get(BridgeConfiguration.PRODUCER_WINDOW_SIZE).toString()); + Assert.assertEquals("30000", jsonObject.get(BridgeConfiguration.CLIENT_FAILURE_CHECK_PERIOD).toString()); + Assert.assertEquals("2000", jsonObject.get(BridgeConfiguration.MAX_RETRY_INTERVAL).toString()); + Assert.assertEquals("102400", jsonObject.get(BridgeConfiguration.MIN_LARGE_MESSAGE_SIZE).toString()); + Assert.assertEquals("30000", jsonObject.get(BridgeConfiguration.CALL_TIMEOUT).toString()); + + // also should contain default non-null values of string fields + Assert.assertEquals("\"ACTIVEMQ.CLUSTER.ADMIN.USER\"", jsonObject.get(BridgeConfiguration.USER).toString()); + Assert.assertEquals("\"CHANGE ME!!\"", jsonObject.get(BridgeConfiguration.PASSWORD).toString()); + } + + @Test + public void testDefaultsFromJson() { + // create BridgeConfiguration instance from empty JSON string + final String jsonString = "{\"name\": \"name\"}"; // name field is required + BridgeConfiguration deserializedConfiguration = BridgeConfiguration.fromJSON(jsonString); + Assert.assertNotNull(deserializedConfiguration); + + // the deserialized object should return the same default values as a newly instantiated object + Assert.assertEquals(deserializedConfiguration, new BridgeConfiguration("name")); + } + + @Test + public void testNullableFieldsFromJson() { + // set string fields which default value is not null to null + JsonObjectBuilder builder = JsonLoader.createObjectBuilder(); + builder.add(BridgeConfiguration.NAME, "name"); // required field + builder.addNull(BridgeConfiguration.USER); + builder.addNull(BridgeConfiguration.PASSWORD); + + BridgeConfiguration configuration = BridgeConfiguration.fromJSON(builder.build().toString()); + + // in deserialized object the fields should still remain null + Assert.assertNotNull(configuration); + Assert.assertEquals("name", configuration.getName()); + Assert.assertNull(configuration.getUser()); + Assert.assertNull(configuration.getPassword()); + } + + @Test + public void testNullableFieldsToJson() { + // set string fields which default value is not null to null + BridgeConfiguration bridgeConfiguration = new BridgeConfiguration("name"); + bridgeConfiguration.setUser(null); + bridgeConfiguration.setPassword(null); + + String jsonString = bridgeConfiguration.toJSON(); + JsonObject jsonObject = JsonLoader.readObject(new StringReader(jsonString)); + + // after serialization the fields value should remain null + Assert.assertEquals(JsonValue.ValueType.NULL, jsonObject.get(BridgeConfiguration.USER).getValueType()); + Assert.assertEquals(JsonValue.ValueType.NULL, jsonObject.get(BridgeConfiguration.PASSWORD).getValueType()); + } + + private static JsonObject createFullJsonObject() { + JsonObjectBuilder objectBuilder = JsonLoader.createObjectBuilder(); + + objectBuilder.add(BridgeConfiguration.NAME, "name"); + objectBuilder.add(BridgeConfiguration.QUEUE_NAME, "queue-name"); + objectBuilder.add(BridgeConfiguration.FORWARDING_ADDRESS, "forwarding-address"); + objectBuilder.add(BridgeConfiguration.FILTER_STRING, "filter-string"); + objectBuilder.add(BridgeConfiguration.STATIC_CONNECTORS, + JsonProvider.provider().createArrayBuilder() + .add("connector1") + .add("connector2")); + objectBuilder.add(BridgeConfiguration.DISCOVERY_GROUP_NAME, "dg"); + objectBuilder.add(BridgeConfiguration.HA, true); + objectBuilder.add(BridgeConfiguration.TRANSFORMER_CONFIGURATION, + JsonLoader.createObjectBuilder() + .add("class-name", "ClassName") + .add("properties", + JsonLoader.createObjectBuilder() + .add("prop1", "val1") + .add("prop2", "val2"))); + objectBuilder.add(BridgeConfiguration.RETRY_INTERVAL, 1); + objectBuilder.add(BridgeConfiguration.RETRY_INTERVAL_MULTIPLIER, 2.0); + objectBuilder.add(BridgeConfiguration.INITIAL_CONNECT_ATTEMPTS, 3); + objectBuilder.add(BridgeConfiguration.RECONNECT_ATTEMPTS, 4); + objectBuilder.add(BridgeConfiguration.RECONNECT_ATTEMPTS_ON_SAME_NODE, 5); + objectBuilder.add(BridgeConfiguration.USE_DUPLICATE_DETECTION, true); + objectBuilder.add(BridgeConfiguration.CONFIRMATION_WINDOW_SIZE, 6); + objectBuilder.add(BridgeConfiguration.PRODUCER_WINDOW_SIZE, 7); + objectBuilder.add(BridgeConfiguration.CLIENT_FAILURE_CHECK_PERIOD, 8); + objectBuilder.add(BridgeConfiguration.USER, "user"); + objectBuilder.add(BridgeConfiguration.PASSWORD, "password"); + objectBuilder.add(BridgeConfiguration.CONNECTION_TTL, 9); + objectBuilder.add(BridgeConfiguration.MAX_RETRY_INTERVAL, 10); + objectBuilder.add(BridgeConfiguration.MIN_LARGE_MESSAGE_SIZE, 11); + objectBuilder.add(BridgeConfiguration.CALL_TIMEOUT, 12); + objectBuilder.add(BridgeConfiguration.ROUTING_TYPE, "MULTICAST"); + + return objectBuilder.build(); + } +} diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlTest.java index 7ae3606634..1a887979ce 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlTest.java @@ -28,6 +28,7 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -60,6 +61,7 @@ import org.apache.activemq.artemis.api.jms.ActiveMQJMSClient; import org.apache.activemq.artemis.api.jms.JMSFactoryType; import org.apache.activemq.artemis.core.client.impl.ClientSessionFactoryImpl; import org.apache.activemq.artemis.core.client.impl.ClientSessionImpl; +import org.apache.activemq.artemis.core.config.BridgeConfiguration; import org.apache.activemq.artemis.core.config.ClusterConnectionConfiguration; import org.apache.activemq.artemis.core.config.Configuration; import org.apache.activemq.artemis.core.config.impl.SecurityConfiguration; @@ -1878,6 +1880,96 @@ public class ActiveMQServerControlTest extends ManagementTestBase { locator.close(); } + @Test + public void testCreateAndDestroyBridgeFromJson() throws Exception { + String name = RandomUtil.randomString(); + String sourceAddress = RandomUtil.randomString(); + String sourceQueue = RandomUtil.randomString(); + String targetAddress = RandomUtil.randomString(); + String targetQueue = RandomUtil.randomString(); + + ActiveMQServerControl serverControl = createManagementControl(); + + checkNoResource(ObjectNameBuilder.DEFAULT.getBridgeObjectName(name)); + assertEquals(0, serverControl.getBridgeNames().length); + + ServerLocator locator = createInVMNonHALocator(); + ClientSessionFactory csf = createSessionFactory(locator); + ClientSession session = csf.createSession(); + + if (legacyCreateQueue) { + session.createQueue(sourceAddress, RoutingType.ANYCAST, sourceQueue); + session.createQueue(targetAddress, RoutingType.ANYCAST, targetQueue); + } else { + session.createQueue(new QueueConfiguration(sourceQueue).setAddress(sourceAddress).setRoutingType(RoutingType.ANYCAST).setDurable(false)); + session.createQueue(new QueueConfiguration(targetQueue).setAddress(targetAddress).setRoutingType(RoutingType.ANYCAST).setDurable(false)); + } + + BridgeConfiguration bridgeConfiguration = new BridgeConfiguration(name) + .setQueueName(sourceQueue) + .setForwardingAddress(targetAddress) + .setUseDuplicateDetection(false) + .setConfirmationWindowSize(1) + .setProducerWindowSize(-1) + .setStaticConnectors(Collections.singletonList(connectorConfig.getName())) + .setHA(false) + .setUser(null) + .setPassword(null); + serverControl.createBridge(bridgeConfiguration.toJSON()); + + checkResource(ObjectNameBuilder.DEFAULT.getBridgeObjectName(name)); + String[] bridgeNames = serverControl.getBridgeNames(); + assertEquals(1, bridgeNames.length); + assertEquals(name, bridgeNames[0]); + + BridgeControl bridgeControl = ManagementControlHelper.createBridgeControl(name, mbeanServer); + assertEquals(name, bridgeControl.getName()); + assertTrue(bridgeControl.isStarted()); + + // check that a message sent to the sourceAddress is put in the tagetQueue + ClientProducer producer = session.createProducer(sourceAddress); + ClientMessage message = session.createMessage(false); + String text = RandomUtil.randomString(); + message.putStringProperty("prop", text); + producer.send(message); + + session.start(); + + ClientConsumer targetConsumer = session.createConsumer(targetQueue); + message = targetConsumer.receive(5000); + assertNotNull(message); + assertEquals(text, message.getStringProperty("prop")); + + ClientConsumer sourceConsumer = session.createConsumer(sourceQueue); + assertNull(sourceConsumer.receiveImmediate()); + + serverControl.destroyBridge(name); + + checkNoResource(ObjectNameBuilder.DEFAULT.getBridgeObjectName(name)); + assertEquals(0, serverControl.getBridgeNames().length); + + // check that a message is no longer diverted + message = session.createMessage(false); + String text2 = RandomUtil.randomString(); + message.putStringProperty("prop", text2); + producer.send(message); + + assertNull(targetConsumer.receiveImmediate()); + message = sourceConsumer.receive(5000); + assertNotNull(message); + assertEquals(text2, message.getStringProperty("prop")); + + sourceConsumer.close(); + targetConsumer.close(); + + session.deleteQueue(sourceQueue); + session.deleteQueue(targetQueue); + + session.close(); + + locator.close(); + } + @Test public void testListPreparedTransactionDetails() throws Exception { SimpleString atestq = new SimpleString("BasicXaTestq"); diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlUsingCoreTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlUsingCoreTest.java index 882e5caf40..c7719550bb 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlUsingCoreTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlUsingCoreTest.java @@ -1556,6 +1556,11 @@ public class ActiveMQServerControlUsingCoreTest extends ActiveMQServerControlTes proxy.invokeOperation("createBridge", name, queueName, forwardingAddress, filterString, transformerClassName, retryInterval, retryIntervalMultiplier, initialConnectAttempts, reconnectAttempts, useDuplicateDetection, confirmationWindowSize, clientFailureCheckPeriod, connectorNames, useDiscovery, ha, user, password); } + @Override + public void createBridge(String bridgeConfiguration) throws Exception { + proxy.invokeOperation("createBridge", bridgeConfiguration); + } + @Override public String listProducersInfoAsJSON() throws Exception { return (String) proxy.invokeOperation("listProducersInfoAsJSON");