You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by cl...@apache.org on 2021/02/02 00:21:56 UTC

[activemq-artemis] branch master updated: ARTEMIS-3074 Add ActiveMQServerControl#createBridge() method variant accepting a JSON string

This is an automated email from the ASF dual-hosted git repository.

clebertsuconic pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/activemq-artemis.git


The following commit(s) were added to refs/heads/master by this push:
     new ebeea15  ARTEMIS-3074 Add ActiveMQServerControl#createBridge() method variant accepting a JSON string
     new 386aaa0  This closes #3412
ebeea15 is described below

commit ebeea15c2a8568952be8af2c4567ca5cf39898a8
Author: Tomas Hofman <th...@redhat.com>
AuthorDate: Tue Jan 19 15:04:35 2021 +0100

    ARTEMIS-3074 Add ActiveMQServerControl#createBridge() method variant accepting a JSON string
---
 .../api/core/management/ActiveMQServerControl.java |  24 ++
 .../core/config/TransformerConfiguration.java      |  47 +++-
 .../artemis/core/config/BridgeConfiguration.java   | 251 +++++++++++++++++++++
 .../management/impl/ActiveMQServerControlImpl.java |  23 ++
 .../core/config/BridgeConfigurationTest.java       | 194 ++++++++++++++++
 .../management/ActiveMQServerControlTest.java      |  92 ++++++++
 .../ActiveMQServerControlUsingCoreTest.java        |   5 +
 7 files changed, 635 insertions(+), 1 deletion(-)

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 e981444..7668b65 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 36c9960..1819679 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<String, String> properties = new HashMap<>();
 
    public TransformerConfiguration(String className) {
@@ -41,6 +49,43 @@ public final class TransformerConfiguration implements Serializable {
    }
 
    /**
+    * This method returns a {@code TransformerConfiguration} created from the JSON-formatted input {@code String}.
+    * The input should contain these entries:
+    *
+    * <p><ul>
+    * <li>class-name - a string value,
+    * <li>properties - an object containing string key-value pairs.
+    * </ul></p>
+    *
+    * @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<String, String> properties = new HashMap<>();
+         for (Map.Entry<String, JsonValue> 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
     */
    public TransformerConfiguration setProperties(final Map<String, String> properties) {
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 d7b388d..6eb6146 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:
+    * <p><ul>
+    * <li>name: {@link #NAME}
+    * <li>queue-name: {@link #QUEUE_NAME}
+    * <li>forwarding-address: {@link #FORWARDING_ADDRESS}
+    * <li>filter-string: {@link #FILTER_STRING}
+    * <li>static-connectors: {@link #STATIC_CONNECTORS}
+    * <li>discovery-group-name: {@link #DISCOVERY_GROUP_NAME}
+    * <li>ha: {@link #HA}
+    * <li>transformer-configuration: {@link #TRANSFORMER_CONFIGURATION}
+    * <li>retry-interval: {@link #RETRY_INTERVAL}
+    * <li>RETRY-interval-multiplier: {@link #RETRY_INTERVAL_MULTIPLIER}
+    * <li>initial-connect-attempts: {@link #INITIAL_CONNECT_ATTEMPTS}
+    * <li>reconnect-attempts: {@link #RECONNECT_ATTEMPTS}
+    * <li>reconnect-attempts-on-same-node: {@link #RECONNECT_ATTEMPTS_ON_SAME_NODE}
+    * <li>use-duplicate-detection: {@link #USE_DUPLICATE_DETECTION}
+    * <li>confirmation-window-size: {@link #CONFIRMATION_WINDOW_SIZE}
+    * <li>producer-window-size: {@link #PRODUCER_WINDOW_SIZE}
+    * <li>client-failure-check-period: {@link #CLIENT_FAILURE_CHECK_PERIOD}
+    * <li>user: {@link #USER}
+    * <li>password: {@link #PASSWORD}
+    * <li>connection-ttl: {@link #CONNECTION_TTL}
+    * <li>max-retry-interval: {@link #MAX_RETRY_INTERVAL}
+    * <li>min-large-message-size: {@link #MIN_LARGE_MESSAGE_SIZE}
+    * <li>call-timeout: {@link #CALL_TIMEOUT}
+    * <li>routing-type: {@link #ROUTING_TYPE}
+    * </ul><p>
+    * 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<String> 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<String> 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<String, JsonValue> 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 8306a6d..b1783a6 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
@@ -3823,6 +3823,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()) {
          AuditLogger.listBrokerConnections();
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 0000000..17a8050
--- /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 7ae3606..1a88797 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;
@@ -1879,6 +1881,96 @@ public class ActiveMQServerControlTest extends ManagementTestBase {
    }
 
    @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");
       Xid xid = newXID();
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 882e5ca..c771955 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
@@ -1557,6 +1557,11 @@ public class ActiveMQServerControlUsingCoreTest extends ActiveMQServerControlTes
          }
 
          @Override
+         public void createBridge(String bridgeConfiguration) throws Exception {
+            proxy.invokeOperation("createBridge", bridgeConfiguration);
+         }
+
+         @Override
          public String listProducersInfoAsJSON() throws Exception {
             return (String) proxy.invokeOperation("listProducersInfoAsJSON");
          }