/* * 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.api.core; import javax.json.JsonObject; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import org.apache.activemq.artemis.core.client.ActiveMQClientMessageBundle; import org.apache.activemq.artemis.core.remoting.impl.TransportConfigurationUtil; import org.apache.activemq.artemis.utils.JsonLoader; import org.apache.activemq.artemis.utils.UUIDGenerator; /** * A TransportConfiguration is used by a client to specify connections to a server and its backup if * one exists. * <p> * Typically the constructors take the class name and parameters for needed to create the * connection. These will be different dependent on which connector is being used, i.e. Netty or * InVM etc. For example: * * <pre> * HashMap<String, Object> map = new HashMap<String, Object>(); * map.put("host", "localhost"); * map.put("port", 61616); * TransportConfiguration config = new TransportConfiguration(InVMConnectorFactory.class.getName(), map); * ClientSessionFactory sf = new ClientSessionFactoryImpl(config); * </pre> */ public class TransportConfiguration implements Serializable { private static final long serialVersionUID = -3994528421527392679L; private String name; private String factoryClassName; private Map<String, Object> params; private Map<String, Object> extraProps; private static final byte TYPE_BOOLEAN = 0; private static final byte TYPE_INT = 1; private static final byte TYPE_LONG = 2; private static final byte TYPE_STRING = 3; public JsonObject toJson() { return JsonLoader.createObjectBuilder().add("name", name).add("factoryClassName", factoryClassName).add("params", JsonUtil.toJsonObject(params)).add("extraProps", JsonUtil.toJsonObject(extraProps)).build(); } /** * Utility method for splitting a comma separated list of hosts * * @param commaSeparatedHosts the comma separated host string * @return the hosts */ public static String[] splitHosts(final String commaSeparatedHosts) { if (commaSeparatedHosts == null) { return new String[0]; } String[] hosts = commaSeparatedHosts.split(","); for (int i = 0; i < hosts.length; i++) { hosts[i] = hosts[i].trim(); } return hosts; } /** * Creates a default TransportConfiguration with no configured transport. */ public TransportConfiguration() { this.params = new HashMap<>(); } /** * Creates a TransportConfiguration with a specific name providing the class name of the {@link org.apache.activemq.artemis.spi.core.remoting.ConnectorFactory} * and any parameters needed. * * @param className The class name of the ConnectorFactory * @param params The parameters needed by the ConnectorFactory * @param name The name of this TransportConfiguration */ public TransportConfiguration(final String className, final Map<String, Object> params, final String name) { this(className, params, name, null); } /** * Creates a TransportConfiguration with a specific name providing the class name of the {@link org.apache.activemq.artemis.spi.core.remoting.ConnectorFactory} * and any parameters needed. * * @param className The class name of the ConnectorFactory * @param params The parameters needed by the ConnectorFactory * @param name The name of this TransportConfiguration * @param extraProps The extra properties that specific to protocols */ public TransportConfiguration(final String className, final Map<String, Object> params, final String name, final Map<String, Object> extraProps) { factoryClassName = className; if (params == null || params.isEmpty()) { this.params = TransportConfigurationUtil.getDefaults(className); } else { this.params = params; } this.name = name; this.extraProps = extraProps; } public TransportConfiguration newTransportConfig(String newName) { return new TransportConfiguration(factoryClassName, params, newName); } /** * Creates a TransportConfiguration providing the class name of the {@link org.apache.activemq.artemis.spi.core.remoting.ConnectorFactory} * and any parameters needed. * * @param className The class name of the ConnectorFactory * @param params The parameters needed by the ConnectorFactory */ public TransportConfiguration(final String className, final Map<String, Object> params) { this(className, params, UUIDGenerator.getInstance().generateStringUUID()); } /** * Creates a TransportConfiguration providing the class name of the {@link org.apache.activemq.artemis.spi.core.remoting.ConnectorFactory} * * @param className The class name of the ConnectorFactory */ public TransportConfiguration(final String className) { this(className, new HashMap<String, Object>(), UUIDGenerator.getInstance().generateStringUUID()); } /** * Returns the name of this TransportConfiguration. * * @return the name */ public String getName() { return name; } /** * Returns the class name of ConnectorFactory being used by this TransportConfiguration * * @return The factory's class name */ public String getFactoryClassName() { return factoryClassName; } /** * Returns any parameters set for this TransportConfiguration * * @return the parameters */ public Map<String, Object> getParams() { return params; } public Map<String, Object> getExtraParams() { return extraProps; } @Override public int hashCode() { int result = name != null ? name.hashCode() : 0; result = 31 * result + factoryClassName.hashCode(); result = 31 * result + (params != null ? params.hashCode() : 0); return result; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TransportConfiguration that = (TransportConfiguration) o; if (!isSameParams(that)) { return false; } if (name != null ? !name.equals(that.name) : that.name != null) return false; return true; } public boolean isSameParams(TransportConfiguration that) { if (!factoryClassName.equals(that.factoryClassName)) return false; if (params != null ? !params.equals(that.params) : that.params != null) return false; return true; } /** * There's a case on ClusterConnections that we need to find an equivalent Connector and we can't * use a Netty Cluster Connection on an InVM ClusterConnection (inVM used on tests) for that * reason I need to test if the two instances of the TransportConfiguration are equivalent while * a test a connector against an acceptor * * @param otherConfig * @return {@code true} if the factory class names are equivalents */ public boolean isEquivalent(TransportConfiguration otherConfig) { if (this.getFactoryClassName().equals(otherConfig.getFactoryClassName())) { return true; } else if (this.getFactoryClassName().contains("Netty") && otherConfig.getFactoryClassName().contains("Netty")) { return true; } else if (this.getFactoryClassName().contains("InVM") && otherConfig.getFactoryClassName().contains("InVM")) { return true; } else { return false; } } @Override public String toString() { StringBuilder str = new StringBuilder(TransportConfiguration.class.getSimpleName()); str.append("(name=" + name + ", "); str.append("factory=" + replaceWildcardChars(factoryClassName)); str.append(") "); str.append(toStringParameters(params, extraProps)); return str.toString(); } public static String toStringParameters(Map<String, Object> params, Map<String, Object> extraProps) { StringBuilder str = new StringBuilder(); if (params != null) { if (!params.isEmpty()) { str.append("?"); } boolean first = true; for (Map.Entry<String, Object> entry : params.entrySet()) { if (!first) { str.append("&"); } String key = entry.getKey(); // HORNETQ-1281 - don't log passwords String val; if (key.toLowerCase().contains("password")) { val = "****"; } else { val = entry.getValue() == null ? "null" : entry.getValue().toString(); } str.append(replaceWildcardChars(key)).append('=').append(replaceWildcardChars(val)); first = false; } if (extraProps != null) { for (Map.Entry<String, Object> entry : extraProps.entrySet()) { if (!first) { str.append("&"); } String key = entry.getKey(); String val = entry.getValue() == null ? "null" : entry.getValue().toString(); str.append(replaceWildcardChars(key)).append('=').append(replaceWildcardChars(val)); first = false; } } } return str.toString(); } private void encodeMap(final ActiveMQBuffer buffer, final Map<String, Object> map) { for (Map.Entry<String, Object> entry : map.entrySet()) { buffer.writeString(entry.getKey()); Object val = entry.getValue(); if (val instanceof Boolean) { buffer.writeByte(TransportConfiguration.TYPE_BOOLEAN); buffer.writeBoolean((Boolean) val); } else if (val instanceof Integer) { buffer.writeByte(TransportConfiguration.TYPE_INT); buffer.writeInt((Integer) val); } else if (val instanceof Long) { buffer.writeByte(TransportConfiguration.TYPE_LONG); buffer.writeLong((Long) val); } else if (val instanceof String) { buffer.writeByte(TransportConfiguration.TYPE_STRING); buffer.writeString((String) val); } else { throw ActiveMQClientMessageBundle.BUNDLE.invalidEncodeType(val); } } } /** * Encodes this TransportConfiguration into a buffer. * <p> * Note that this is only used internally ActiveMQ Artemis. * * @param buffer the buffer to encode into */ public void encode(final ActiveMQBuffer buffer) { buffer.writeString(name); buffer.writeString(factoryClassName); buffer.writeInt(params == null ? 0 : params.size()); if (params != null) { encodeMap(buffer, params); } if (extraProps != null) { encodeMap(buffer, extraProps); } } /** * Decodes this TransportConfiguration from a buffer. * <p> * Note this is only used internally by ActiveMQ * * @param buffer the buffer to decode from */ public void decode(final ActiveMQBuffer buffer) { name = buffer.readString(); factoryClassName = buffer.readString(); int num = buffer.readInt(); if (params == null) { if (num > 0) { params = new HashMap<>(); } } else { params.clear(); } for (int i = 0; i < num; i++) { String key = buffer.readString(); byte type = buffer.readByte(); Object val; switch (type) { case TYPE_BOOLEAN: { val = buffer.readBoolean(); break; } case TYPE_INT: { val = buffer.readInt(); break; } case TYPE_LONG: { val = buffer.readLong(); break; } case TYPE_STRING: { val = buffer.readString(); break; } default: { throw ActiveMQClientMessageBundle.BUNDLE.invalidType(type); } } params.put(key, val); } } private static String replaceWildcardChars(final String str) { return str.replace('.', '-'); } }