001    /*
002     * Copyright 2009 Red Hat, Inc.
003     * Red Hat licenses this file to you under the Apache License, version
004     * 2.0 (the "License"); you may not use this file except in compliance
005     * with the License.  You may obtain a copy of the License at
006     *    http://www.apache.org/licenses/LICENSE-2.0
007     * Unless required by applicable law or agreed to in writing, software
008     * distributed under the License is distributed on an "AS IS" BASIS,
009     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
010     * implied.  See the License for the specific language governing
011     * permissions and limitations under the License.
012     */
013    package org.hornetq.api.core;
014    
015    import java.io.Serializable;
016    import java.util.HashMap;
017    import java.util.Map;
018    
019    import org.hornetq.utils.UUIDGenerator;
020    
021    /**
022     * A TransportConfiguration is used by a client to specify a connections to a server and its backup if one exists.<br><br>
023     * <p/>
024     * Typically the constructors take the class name and parameters for needed to create the connection. These will be
025     * different dependent on which connector is being used, i.e. Netty or InVM etc. For example:<br><br>
026     * <p/>
027     * <code>
028     * HashMap<String, Object> map = new HashMap<String, Object>();<br>
029     * map.put("host", "localhost");<br>
030     * map.put("port", 5445);<br>
031     * TransportConfiguration config = new TransportConfiguration(InVMConnectorFactory.class.getName(), map); <br>
032     * ClientSessionFactory sf = new ClientSessionFactoryImpl(config);  <br>
033     * </code><br><br>
034     *
035     * @author <a href="mailto:tim.fox@jboss.com">Tim Fox</a>
036     */
037    public class TransportConfiguration implements Serializable
038    {
039       private static final long serialVersionUID = -3994528421527392679L;
040    
041       private String name;
042    
043       private String factoryClassName;
044    
045       private Map<String, Object> params;
046    
047       private static final byte TYPE_BOOLEAN = 0;
048    
049       private static final byte TYPE_INT = 1;
050    
051       private static final byte TYPE_LONG = 2;
052    
053       private static final byte TYPE_STRING = 3;
054    
055       /**
056        * Utility method for splitting a comma separated list of hosts
057        *
058        * @param commaSeparatedHosts the comma separated host string
059        * @return the hosts
060        */
061       public static String[] splitHosts(final String commaSeparatedHosts)
062       {
063          if (commaSeparatedHosts == null)
064          {
065             return new String[0];
066          }
067          String[] hosts = commaSeparatedHosts.split(",");
068    
069          for (int i = 0; i < hosts.length; i++)
070          {
071             hosts[i] = hosts[i].trim();
072          }
073          return hosts;
074       }
075    
076       /**
077        * Creates a default TransportConfiguration with no configured transport.
078        */
079       public TransportConfiguration()
080       {
081       }
082    
083       /**
084        * Creates a TransportConfiguration with a specific name providing the class name of the {@link org.hornetq.spi.core.remoting.ConnectorFactory}
085        * and any parameters needed.
086        *
087        * @param className The class name of the ConnectorFactory
088        * @param params    The parameters needed by the ConnectorFactory
089        * @param name      The name of this TransportConfiguration
090        */
091       public TransportConfiguration(final String className, final Map<String, Object> params, final String name)
092       {
093          factoryClassName = className;
094    
095          this.params = params;
096    
097          this.name = name;
098       }
099    
100       /**
101        * Creates a TransportConfiguration providing the class name of the {@link org.hornetq.spi.core.remoting.ConnectorFactory}
102        * and any parameters needed.
103        *
104        * @param className The class name of the ConnectorFactory
105        * @param params    The parameters needed by the ConnectorFactory
106        */
107       public TransportConfiguration(final String className, final Map<String, Object> params)
108       {
109          this(className, params, UUIDGenerator.getInstance().generateStringUUID());
110       }
111    
112       /**
113        * Creates a TransportConfiguration providing the class name of the {@link org.hornetq.spi.core.remoting.ConnectorFactory}
114        *
115        * @param className The class name of the ConnectorFactory
116        */
117       public TransportConfiguration(final String className)
118       {
119          this(className, new HashMap<String, Object>(), UUIDGenerator.getInstance().generateStringUUID());
120       }
121    
122       /**
123        * Returns the name of this TransportConfiguration.
124        *
125        * @return the name
126        */
127       public String getName()
128       {
129          return name;
130       }
131    
132       /**
133        * Returns the class name of ConnectorFactory being used by this TransportConfiguration
134        *
135        * @return The factory's class name
136        */
137       public String getFactoryClassName()
138       {
139          return factoryClassName;
140       }
141    
142       /**
143        * Returns any parameters set for this TransportConfiguration
144        *
145        * @return the parameters
146        */
147       public Map<String, Object> getParams()
148       {
149          return params;
150       }
151    
152       @Override
153       public int hashCode()
154       {
155          return factoryClassName.hashCode();
156       }
157    
158       @Override
159       public boolean equals(final Object other)
160       {
161          if (other instanceof TransportConfiguration == false)
162          {
163             return false;
164          }
165    
166          TransportConfiguration kother = (TransportConfiguration) other;
167    
168          if (factoryClassName.equals(kother.factoryClassName))
169          {
170             if (params == null || params.isEmpty())
171             {
172                return kother.params == null || kother.params.isEmpty();
173             }
174             else
175             {
176                if (kother.params == null || kother.params.isEmpty())
177                {
178                   return false;
179                }
180                else if (params.size() == kother.params.size())
181                {
182                   for (Map.Entry<String, Object> entry : params.entrySet())
183                   {
184                      Object thisVal = entry.getValue();
185    
186                      Object otherVal = kother.params.get(entry.getKey());
187    
188                      if (otherVal == null || !otherVal.equals(thisVal))
189                      {
190                         return false;
191                      }
192                   }
193                   return true;
194                }
195                else
196                {
197                   return false;
198                }
199             }
200          }
201          else
202          {
203             return false;
204          }
205       }
206    
207       /**
208        * There's a case on ClusterConnections that we need to find an equivalent Connector
209        * and we can't use a Netty Cluster Connection on an InVM ClusterConnection (inVM used on tests)
210        * for that reason I need to test if the two instances of the TransportConfiguration are equivalent
211        * while a test a connector against an acceptor
212        * @param otherConfig
213        * @return
214        */
215       public boolean isEquivalent(TransportConfiguration otherConfig)
216       {
217          if (this.getFactoryClassName().equals(otherConfig.getFactoryClassName()))
218          {
219             return true;
220          }
221          else if (this.getFactoryClassName().contains("Netty") && otherConfig.getFactoryClassName().contains("Netty"))
222          {
223             return true;
224          }
225          else if (this.getFactoryClassName().contains("InVM") && otherConfig.getFactoryClassName().contains("InVM"))
226          {
227             return true;
228          }
229          else
230          {
231             return false;
232          }
233       }
234    
235       @Override
236       public String toString()
237       {
238          StringBuilder str = new StringBuilder(replaceWildcardChars(factoryClassName));
239    
240          if (params != null)
241          {
242             if (!params.isEmpty())
243             {
244                str.append("?");
245             }
246    
247             boolean first = true;
248             for (Map.Entry<String, Object> entry : params.entrySet())
249             {
250                if (!first)
251                {
252                   str.append("&");
253                }
254                String encodedKey = replaceWildcardChars(entry.getKey());
255    
256                String val = entry.getValue().toString();
257                String encodedVal = replaceWildcardChars(val);
258    
259                str.append(encodedKey).append('=').append(encodedVal);
260    
261                first = false;
262             }
263          }
264    
265          return str.toString();
266       }
267    
268       /**
269        * Encodes this TransportConfiguration into a buffer.
270        * <p/>
271        * Note that this is only used internally HornetQ.
272        *
273        * @param buffer the buffer to encode into
274        */
275       public void encode(final HornetQBuffer buffer)
276       {
277          buffer.writeString(name);
278          buffer.writeString(factoryClassName);
279    
280          buffer.writeInt(params == null ? 0 : params.size());
281    
282          if (params != null)
283          {
284             for (Map.Entry<String, Object> entry : params.entrySet())
285             {
286                buffer.writeString(entry.getKey());
287    
288                Object val = entry.getValue();
289    
290                if (val instanceof Boolean)
291                {
292                   buffer.writeByte(TransportConfiguration.TYPE_BOOLEAN);
293                   buffer.writeBoolean((Boolean) val);
294                }
295                else if (val instanceof Integer)
296                {
297                   buffer.writeByte(TransportConfiguration.TYPE_INT);
298                   buffer.writeInt((Integer) val);
299                }
300                else if (val instanceof Long)
301                {
302                   buffer.writeByte(TransportConfiguration.TYPE_LONG);
303                   buffer.writeLong((Long) val);
304                }
305                else if (val instanceof String)
306                {
307                   buffer.writeByte(TransportConfiguration.TYPE_STRING);
308                   buffer.writeString((String) val);
309                }
310                else
311                {
312                   throw new IllegalArgumentException("Invalid type " + val);
313                }
314             }
315          }
316       }
317    
318       /**
319        * Decodes this TransportConfiguration from a buffer.
320        * <p/>
321        * Note this is only used internally by HornetQ
322        *
323        * @param buffer the buffer to decode from
324        */
325       public void decode(final HornetQBuffer buffer)
326       {
327          name = buffer.readString();
328          factoryClassName = buffer.readString();
329    
330          int num = buffer.readInt();
331    
332          if (params == null)
333          {
334             if (num > 0)
335             {
336                params = new HashMap<String, Object>();
337             }
338          }
339          else
340          {
341             params.clear();
342          }
343    
344          for (int i = 0; i < num; i++)
345          {
346             String key = buffer.readString();
347    
348             byte type = buffer.readByte();
349    
350             Object val;
351    
352             switch (type)
353             {
354                case TYPE_BOOLEAN:
355                {
356                   val = buffer.readBoolean();
357    
358                   break;
359                }
360                case TYPE_INT:
361                {
362                   val = buffer.readInt();
363    
364                   break;
365                }
366                case TYPE_LONG:
367                {
368                   val = buffer.readLong();
369    
370                   break;
371                }
372                case TYPE_STRING:
373                {
374                   val = buffer.readString();
375    
376                   break;
377                }
378                default:
379                {
380                   throw new IllegalArgumentException("Invalid type " + type);
381                }
382             }
383    
384             params.put(key, val);
385          }
386       }
387    
388       private String replaceWildcardChars(final String str)
389       {
390          return str.replace('.', '-');
391       }
392    }