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 */
013package org.hornetq.api.core;
014
015import java.io.Serializable;
016import java.util.HashMap;
017import java.util.Map;
018
019import 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 */
037public 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   @Override
208   public String toString()
209   {
210      StringBuilder str = new StringBuilder(replaceWildcardChars(factoryClassName));
211
212      if (params != null)
213      {
214         if (!params.isEmpty())
215         {
216            str.append("?");
217         }
218
219         boolean first = true;
220         for (Map.Entry<String, Object> entry : params.entrySet())
221         {
222            if (!first)
223            {
224               str.append("&");
225            }
226            String encodedKey = replaceWildcardChars(entry.getKey());
227
228            String val = entry.getValue().toString();
229            String encodedVal = replaceWildcardChars(val);
230
231            str.append(encodedKey).append('=').append(encodedVal);
232
233            first = false;
234         }
235      }
236
237      return str.toString();
238   }
239
240   /**
241    * Encodes this TransportConfiguration into a buffer.
242    * <p/>
243    * Note that this is only used internally HornetQ.
244    *
245    * @param buffer the buffer to encode into
246    */
247   public void encode(final HornetQBuffer buffer)
248   {
249      buffer.writeString(name);
250      buffer.writeString(factoryClassName);
251
252      buffer.writeInt(params == null ? 0 : params.size());
253
254      if (params != null)
255      {
256         for (Map.Entry<String, Object> entry : params.entrySet())
257         {
258            buffer.writeString(entry.getKey());
259
260            Object val = entry.getValue();
261
262            if (val instanceof Boolean)
263            {
264               buffer.writeByte(TransportConfiguration.TYPE_BOOLEAN);
265               buffer.writeBoolean((Boolean) val);
266            }
267            else if (val instanceof Integer)
268            {
269               buffer.writeByte(TransportConfiguration.TYPE_INT);
270               buffer.writeInt((Integer) val);
271            }
272            else if (val instanceof Long)
273            {
274               buffer.writeByte(TransportConfiguration.TYPE_LONG);
275               buffer.writeLong((Long) val);
276            }
277            else if (val instanceof String)
278            {
279               buffer.writeByte(TransportConfiguration.TYPE_STRING);
280               buffer.writeString((String) val);
281            }
282            else
283            {
284               throw new IllegalArgumentException("Invalid type " + val);
285            }
286         }
287      }
288   }
289
290   /**
291    * Decodes this TransportConfiguration from a buffer.
292    * <p/>
293    * Note this is only used internally by HornetQ
294    *
295    * @param buffer the buffer to decode from
296    */
297   public void decode(final HornetQBuffer buffer)
298   {
299      name = buffer.readString();
300      factoryClassName = buffer.readString();
301
302      int num = buffer.readInt();
303
304      if (params == null)
305      {
306         if (num > 0)
307         {
308            params = new HashMap<String, Object>();
309         }
310      }
311      else
312      {
313         params.clear();
314      }
315
316      for (int i = 0; i < num; i++)
317      {
318         String key = buffer.readString();
319
320         byte type = buffer.readByte();
321
322         Object val;
323
324         switch (type)
325         {
326            case TYPE_BOOLEAN:
327            {
328               val = buffer.readBoolean();
329
330               break;
331            }
332            case TYPE_INT:
333            {
334               val = buffer.readInt();
335
336               break;
337            }
338            case TYPE_LONG:
339            {
340               val = buffer.readLong();
341
342               break;
343            }
344            case TYPE_STRING:
345            {
346               val = buffer.readString();
347
348               break;
349            }
350            default:
351            {
352               throw new IllegalArgumentException("Invalid type " + type);
353            }
354         }
355
356         params.put(key, val);
357      }
358   }
359
360   private String replaceWildcardChars(final String str)
361   {
362      return str.replace('.', '-');
363   }
364}