View Javadoc

1   /*
2   Copyright (C) 2000 - 2007 Grid Systems, S.A.
3   
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License, version 2, as
6   published by the Free Software Foundation.
7   
8   This program is distributed in the hope that it will be useful,
9   but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  GNU General Public License for more details.
12  
13  You should have received a copy of the GNU General Public License
14  along with this program; if not, write to the Free Software
15  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  */
17  
18  /*
19   * Project: KernelConfigurator
20   * Created on 05-oct-2004
21   *
22   * Copyright (c)2004 Grid Systems
23   *
24   * WARNING: SJM. The RedundantServerPlugin has a copy of this class to read the
25   * data of the ports of the server. That copy needs to be updated when this
26   * class suffers changes that do affect the serialization of the port list.
27   * WARNING2: SJM. The VirtualNityaPlugin has also a copy of this class for the
28   * same purposes.
29   */
30  package com.gridsystems.config.modules.tomcat;
31  
32  import java.io.BufferedReader;
33  import java.io.File;
34  import java.io.FileReader;
35  import java.io.FileWriter;
36  import java.io.IOException;
37  import java.io.PrintWriter;
38  import java.lang.reflect.Field;
39  import java.lang.reflect.Modifier;
40  import java.security.Security;
41  import java.text.ParseException;
42  import java.util.ArrayList;
43  import java.util.HashMap;
44  import java.util.Iterator;
45  import java.util.List;
46  import java.util.Set;
47  
48  /**
49   * Type description.
50   *
51   * @author <a href="mailto:rruiz@gridsystems.com">Rodrigo Ruiz Aguayo</a>
52   * @version 1.0
53   */
54  public class Connector implements Cloneable {
55  
56    /**
57     * HTTP protocol identifier.
58     */
59    public static final String HTTP = "http";
60  
61    /**
62     * HTTPS protocol identifier.
63     */
64    public static final String HTTPS = "https";
65  
66    /**
67     * Default accept count.
68     */
69    private static final int DEFAULT_ACCEPT_COUNT = 10;
70  
71    /**
72     * Default buffer size.
73     */
74    private static final int DEFAULT_BUFFER_SIZE = 2048;
75  
76    /**
77     * Default maximum number of processing threads.
78     */
79    private static final int DEFAULT_MAX_THREADS = 20;
80  
81    /**
82     * Default minimum number of processing threads.
83     */
84    private static final int DEFAULT_MIN_THREADS = 5;
85  
86    /**
87     * Default connection timeout.
88     */
89    private static final long DEFAULT_TIMEOUT = 60000;
90  
91    /**
92     * Algorithm used for keystores and keys.
93     */
94    private static final String ALGORITHM;
95  
96    /**
97     * Default value for the clientAuth attribute.
98     */
99    private static final String DEFAULT_CLIENT_AUTH = "false";
100 
101   static {
102     String[] preferred = {
103       "SunX509", "IbmX509"
104     };
105     // The first algorithm is the default if none is found
106     String alg = preferred[0];
107 
108     Set available = Security.getAlgorithms("TrustManagerFactory");
109     for (int i = 0; i < preferred.length; i++) {
110       for (Iterator it = available.iterator(); it.hasNext();) {
111         String s = it.next().toString();
112         if (s.equalsIgnoreCase(preferred[i])) {
113           alg = preferred[i];
114           break;
115         }
116       }
117     }
118     ALGORITHM = alg;
119   };
120 
121   /**
122    * Connector name, for screen display.
123    * <p>
124    * This field has no default value, so it MUST be declared at construction time.
125    */
126   private String name;
127 
128   /**
129    * Set to <code>true</code> if you want calls to request.getRemoteHost() to perform
130    * DNS lookups in order to return the actual host name of the remote client.
131    * <p>
132    * Set to <code>false</code> to skip the DNS lookup and return the IP address in
133    * String form instead (thereby improving performance).
134    * <p>
135    * By default, DNS lookups are disabled.
136    */
137   private boolean enableLookups = false;
138 
139   /**
140    * Flag controlling whether protocol use SSL.
141    */
142   private boolean secure = false;
143 
144   /**
145    * Protocol: HTTP,HTTPS, FTP, ...
146    */
147   private String protocol = null;
148 
149   /**
150    * The maximum queue length for incoming connection requests when all possible
151    * request processing threads are in use. Any requests received when the queue is
152    * full will be refused. The default value is 10.
153    */
154   private int acceptCount = DEFAULT_ACCEPT_COUNT;
155 
156   /**
157    * The IP address this connector will be bound to.
158    * <p>
159    * By default, the connector will bind to all host public addresses.
160    */
161   private String address = null;
162 
163   /**
164    * The size (in bytes) of the buffer to be provided for input streams created by
165    * this connector. By default, buffers of 2048 bytes will be provided.
166    */
167   private int bufferSize = DEFAULT_BUFFER_SIZE;
168 
169   /**
170    * The Connector may use <code>HTTP/1.1 GZIP</code> compression in an attempt to
171    * save server bandwidth. The acceptable values for the parameter are:
172    *
173    * <ul>
174    *   <li> <code>off</code> : disable compression,
175    *   <li> <code>on</code> : allow compression, which causes text data to be compressed,
176    *   <li> <code>force</code> : forces compression in all cases,
177    *   <li> a numerical integer value : which is equivalent to "on", but specifies the
178    *        minimum amount of data before the output is compressed.
179    * </ul>
180    *
181    * If the content-length is not known and compression is set to "on" or more aggressive,
182    * the output will also be compressed.
183    * <p>
184    * If not specified, this attribute is set to "off".
185    */
186   private String compression = "off";
187 
188   /**
189    * The number of milliseconds during which the sockets used by this Connector will
190    * linger when they are closed.
191    * <p>
192    * The default value is -1 (socket linger is disabled).
193    */
194   private int connectionLinger = -1;
195 
196   /**
197    * The number of milliseconds this Connector will wait, after accepting a connection,
198    * for the request URI line to be presented.
199    * <p>
200    * The default value is 60000 (i.e. 60 seconds).
201    */
202   private long connectionTimeout = DEFAULT_TIMEOUT;
203 
204   /**
205    * The maximum number of request processing threads to be created by this Connector,
206    * which therefore determines the maximum number of simultaneous requests that can be
207    * handled.
208    * <p>
209    * If not specified, this attribute is set to 20.
210    */
211   private int maxProcessors = DEFAULT_MAX_THREADS;
212 
213   /**
214    * The number of request processing threads that will be created when this Connector
215    * is first started. This attribute should be set to a value smaller than that set for
216    * maxProcessors.
217    * <p>
218    * The default value is 5.
219    */
220   private int minProcessors = DEFAULT_MIN_THREADS;
221 
222   /**
223    * Port number.
224    * <p>
225    * This field has no default value, so it MUST be declared at construction time.
226    */
227   private int port;
228 
229   /**
230    * Flag controlling whether a connector has been created by a user, or by a plugin.
231    */
232   private boolean userCreated = true;
233 
234   /**
235    * clientAuth attribute.
236    */
237   private String clientAuth = DEFAULT_CLIENT_AUTH;
238 
239   /**
240    * Hidden constructor.
241    */
242   private Connector() { }
243 
244   /**
245    * Creates a new instance.
246    *
247    * @param name  the connector display name
248    * @param port  the connector port
249    * @param protocol The protocol.
250    */
251   public Connector(String name, int port, String protocol) {
252     this.name = name;
253     this.port = port;
254     this.protocol = protocol;
255   }
256 
257   /**
258    * Gets the acceptCount value.
259    *
260    * @return The acceptCount
261    */
262   public int getAcceptCount() {
263     return this.acceptCount;
264   }
265 
266   /**
267    * Sets the acceptCount value.
268    *
269    * @param acceptCount The acceptCount to set
270    */
271   public void setAcceptCount(int acceptCount) {
272     this.acceptCount = acceptCount;
273   }
274 
275   /**
276    * Gets the address value.
277    *
278    * @return The address
279    */
280   public String getAddress() {
281     return this.address;
282   }
283 
284   /**
285    * Sets the address value.
286    *
287    * @param address The address to set
288    */
289   public void setAddress(String address) {
290     this.address = address;
291   }
292 
293   /**
294    * Gets the bufferSize value.
295    *
296    * @return The bufferSize
297    */
298   public int getBufferSize() {
299     return this.bufferSize;
300   }
301 
302   /**
303    * Sets the bufferSize value.
304    *
305    * @param bufferSize The bufferSize to set
306    */
307   public void setBufferSize(int bufferSize) {
308     this.bufferSize = bufferSize;
309   }
310 
311   /**
312    * Gets the clientAuth value.
313    *
314    * @return The attribute value
315    */
316   public String getClientAuth() {
317     return this.clientAuth;
318   }
319 
320   /**
321    * Sets the clientAuth value.
322    *
323    * @param clientAuth The attribute value. Valid values are (false, true, want)
324    */
325   public void setClientAuth(String clientAuth) {
326     this.clientAuth = (clientAuth == null) ? DEFAULT_CLIENT_AUTH : clientAuth;
327   }
328 
329   /**
330    * Gets the compression value.
331    *
332    * @return The compression
333    */
334   public String getCompression() {
335     return this.compression;
336   }
337 
338   /**
339    * Sets the compression value.
340    *
341    * @param compression The compression to set
342    */
343   public void setCompression(String compression) {
344     this.compression = compression;
345   }
346 
347   /**
348    * Gets the connectionLinger value.
349    *
350    * @return The connectionLinger
351    */
352   public int getConnectionLinger() {
353     return this.connectionLinger;
354   }
355 
356   /**
357    * Sets the connectionLinger value.
358    *
359    * @param connectionLinger The connectionLinger to set
360    */
361   public void setConnectionLinger(int connectionLinger) {
362     this.connectionLinger = connectionLinger;
363   }
364 
365   /**
366    * Gets the connectionTimeout value.
367    *
368    * @return The connectionTimeout
369    */
370   public long getConnectionTimeout() {
371     return this.connectionTimeout;
372   }
373 
374   /**
375    * Sets the connectionTimeout value.
376    *
377    * @param connectionTimeout The connectionTimeout to set
378    */
379   public void setConnectionTimeout(long connectionTimeout) {
380     this.connectionTimeout = connectionTimeout;
381   }
382 
383   /**
384    * Gets the enableLookups value.
385    *
386    * @return The enableLookups
387    */
388   public boolean isEnableLookups() {
389     return this.enableLookups;
390   }
391 
392   /**
393    * Sets the enableLookups value.
394    *
395    * @param enableLookups The enableLookups to set
396    */
397   public void setEnableLookups(boolean enableLookups) {
398     this.enableLookups = enableLookups;
399   }
400 
401   /**
402    * Gets the maxProcessors value.
403    *
404    * @return The maxProcessors
405    */
406   public int getMaxProcessors() {
407     return this.maxProcessors;
408   }
409 
410   /**
411    * Sets the maxProcessors value.
412    *
413    * @param maxProcessors The maxProcessors to set
414    */
415   public void setMaxProcessors(int maxProcessors) {
416     this.maxProcessors = maxProcessors;
417   }
418 
419   /**
420    * Gets the minProcessors value.
421    *
422    * @return The minProcessors
423    */
424   public int getMinProcessors() {
425     return this.minProcessors;
426   }
427 
428   /**
429    * Sets the minProcessors value.
430    *
431    * @param minProcessors The minProcessors to set
432    */
433   public void setMinProcessors(int minProcessors) {
434     this.minProcessors = minProcessors;
435   }
436 
437   /**
438    * Gets the name value.
439    *
440    * @return The name
441    */
442   public String getName() {
443     return this.name;
444   }
445 
446   /**
447    * Sets the name value.
448    *
449    * @param name The name to set
450    */
451   public void setName(String name) {
452     this.name = name;
453   }
454 
455   /**
456    * Gets the port value.
457    *
458    * @return The port
459    */
460   public int getPort() {
461     return this.port;
462   }
463 
464   /**
465    * Sets the port value.
466    *
467    * @param port The port to set
468    */
469   public void setPort(int port) {
470     this.port = port;
471   }
472 
473   /**
474    * Gets the userCreated value.
475    *
476    * @return The userCreated
477    */
478   public boolean isUserCreated() {
479     return this.userCreated;
480   }
481 
482   /**
483    * Sets the userCreated value.
484    *
485    * @param userCreated The userCreated to set
486    */
487   public void setUserCreated(boolean userCreated) {
488     this.userCreated = userCreated;
489   }
490 
491   /**
492    * Gets the name of the protocol to have returned by calls to request.getScheme().
493    * <p>
494    * It can be "http" or "https", depending on the <code>secure</code> flag value.
495    *
496    * @return The connector scheme.
497    */
498   public String getScheme() {
499     return getProtocol();
500   }
501 
502   /**
503    * {@inheritDoc}
504    *
505    * WARNING: SJM. The RedundantServerPlugin has a copy of this class to read
506    * the data of the ports of the server. That copy needs to be updated when
507    * this class suffers changes that do affect the serialization of the port
508    * list.
509    */
510   public String toString() {
511     getProtocol();
512     StringBuffer sb = new StringBuffer("Connector { ");
513     try {
514       Field[] fields = this.getClass().getDeclaredFields();
515       boolean first = true;
516       for (int i = 0; i < fields.length; i++) {
517         if (fields[i].getName().equals("secure")) {
518           continue;
519         }
520         if (!Modifier.isStatic(fields[i].getModifiers())) {
521           if (!first) {
522             sb.append("; ");
523           } else {
524             first = false;
525           }
526           sb.append(fields[i].getName()).append(":").append(fields[i].get(this));
527         }
528       }
529     } catch (Exception e) {
530       e.printStackTrace();
531     }
532     sb.append(" }");
533     return sb.toString();
534   }
535 
536   /**
537    * Type-safe version of clone().
538    *
539    * @return  a copy of this instance
540    */
541   public Connector copy() {
542     try {
543       return (Connector)clone();
544     } catch (Exception e) {
545       return null;
546     }
547   }
548   /**
549    * Gets an XML fragment for insertion into a Tomcat server.xml file.
550    *
551    * WARNING: SJM. The RedundantServerPlugin has a copy of this class to read
552    * the data of the ports of the server. That copy needs to be updated when
553    * this class suffers changes that do affect the serialization of the port
554    * list.
555    *
556    * @param keystore  the path to the keystore (for SSL connectors)
557    * @param password  the keystore password (for SSL connectors)
558    * @param truststore  the path to the truststore (optional, for SSL connectors)
559    * @param trustpass  the truststore password (optional, for SSL connectors)
560    * @return  a string with the XML representation of this object
561    */
562   public String toXML(String keystore, String password, String truststore,
563     String trustpass) {
564 
565     if (!HTTP.equals(getProtocol()) && !HTTPS.equals(getProtocol())) {
566       return "";
567     }
568     String nl = System.getProperty("line.separator");
569 
570     StringBuffer sb = new StringBuffer();
571     sb.append("<Connector");
572     sb.append(" acceptCount='").append(this.acceptCount);
573     sb.append("' bufferSize='").append(this.bufferSize);
574     if (this.address != null && this.address.length() > 0) {
575       sb.append("' address='").append(this.address);
576     }
577     if (this.compression != null && this.compression.length() > 0) {
578       sb.append("' compression='").append(this.compression);
579     }
580     sb.append("' connectionLinger='").append(this.connectionLinger);
581     sb.append("' connectionTimeout='").append(this.connectionTimeout);
582     sb.append("' enableLookups='").append(this.enableLookups);
583     sb.append("' maxProcessors='").append(this.maxProcessors);
584     sb.append("' minProcessors='").append(this.minProcessors);
585     sb.append("' port='").append(this.port);
586     sb.append("' scheme='").append(this.getProtocol());
587     sb.append("' secure='").append(HTTPS.equals(this.getProtocol()));
588     sb.append("' useURIValidationHack='false' debug='0");
589     sb.append("' disableUploadTimeout='false' tcpNoDelay='true");
590     if (HTTPS.equals(getProtocol())) {
591       sb.append("' sslProtocol='TLS");
592       sb.append("' clientAuth='").append(clientAuth);
593       sb.append("' algorithm='").append(ALGORITHM);
594       sb.append("' keystoreFile='").append(keystore);
595       sb.append("' keystorePass='").append(password);
596       if (truststore != null && trustpass != null) {
597         sb.append("' truststoreFile='").append(truststore);
598         sb.append("' truststorePass='").append(trustpass);
599       }
600     }
601     sb.append("' />").append(nl).append(nl);
602 
603     return sb.toString();
604   }
605 
606   /**
607    * Converts a list of connectors into an XML string.
608    *
609    * WARNING: SJM. The RedundantServerPlugin has a copy of this class to read
610    * the data of the ports of the server. That copy needs to be updated when
611    * this class suffers changes that do affect the serialization of the port
612    * list.
613    *
614    * @param conns     the list of connectors
615    * @param keystore  the keystore path
616    * @param password  the keystore password
617    * @param trustStore the trust store
618    * @param trustPaswd the trust store password
619    * @return  the xml for a Tomcat server.xml configuration file
620    */
621   public static String toXML(List conns, String keystore, String password,
622       String trustStore, String trustPaswd) {
623     // Merges all connectors with the same port
624     HashMap<String, Connector> map = new HashMap<String, Connector>();
625     for (Iterator it = conns.iterator(); it.hasNext();) {
626       Connector c = (Connector)it.next();
627       String addr = c.getAddress() + ":" + c.getPort();
628       Connector cc = (Connector)map.get(addr);
629       if (cc != null) {
630         c = merge(cc, c);
631       }
632       map.put(addr, c);
633     }
634 
635     StringBuffer sb = new StringBuffer();
636     for (Iterator it = map.values().iterator(); it.hasNext();) {
637       Connector c = (Connector)it.next();
638       sb.append(c.toXML(keystore, password, trustStore, trustPaswd));
639     }
640     return sb.toString();
641   }
642 
643   /**
644    * Merges two instances in a new one.
645    * <p>
646    * The created instance will have its attributes set to appropriate values for serving
647    * both Connectors.
648    *
649    * @param c1  The first connector
650    * @param c2  The second connector
651    * @return  A connector with merged attributes
652    */
653   public static Connector merge(Connector c1, Connector c2) {
654     Connector c = new Connector();
655     c.setName("Merged connector");
656     c.setPort(c1.getPort());
657     c.setAcceptCount(c1.getAcceptCount() + c2.getAcceptCount());
658     c.setAddress(c1.getAddress());
659     c.setBufferSize(Math.max(c1.getBufferSize(), c2.getBufferSize()));
660     c.setCompression(minCompression(c1.getCompression(), c2.getCompression()));
661     int maxLinger = Math.max(c1.getConnectionLinger(), c2.getConnectionLinger());
662     c.setConnectionLinger(maxLinger);
663     long maxTimeout = Math.max(c1.getConnectionTimeout(), c2.getConnectionTimeout());
664     c.setConnectionTimeout(maxTimeout);
665     c.setEnableLookups(c1.isEnableLookups() || c2.isEnableLookups());
666     c.setMaxProcessors(c1.getMaxProcessors() + c2.getMaxProcessors());
667     c.setMinProcessors(c1.getMinProcessors() + c2.getMinProcessors());
668     c.setProtocol(c1.getProtocol());
669     return c;
670   }
671 
672   /**
673    * Gets the minimum compression level of the specified ones.
674    *
675    * @param c1  the first compression level to compare
676    * @param c2  the second compression level to compare
677    * @return  the minimum level between c1 and c2, or "off", if c1 and c2 are not valid
678    */
679   private static String minCompression(String c1, String c2) {
680     if (c1 == c2) {
681       return c1;
682     } else if (c1 == null || c2 == null) {
683       return "off";
684     } else {
685       String combo = c1 + " " + c2;
686       String[] levels = { "off", "on", "force" };
687       for (int i = 0; i < levels.length; i++) {
688         if (combo.indexOf(levels[i]) != -1) {
689           return levels[i];
690         }
691       }
692       return "off";
693     }
694   }
695 
696   /**
697    * Factory method that creates a Connector instance by parsing the specified string.
698    *
699    * WARNING: SJM. The RedundantServerPlugin has a copy of this class to read
700    * the data of the ports of the server. That copy needs to be updated when
701    * this class suffers changes that do affect the serialization of the port
702    * list.
703    *
704    * @param s  the string to parse
705    * @return   a Connector instance
706    * @throws ParseException if a syntax error is detected
707    */
708   public static Connector fromString(String s) throws ParseException {
709     if (s.matches("^Connector \\{ .+ \\}$")) {
710       Connector c = new Connector();
711       // Strips leading and trailling characters
712       String ss = s.substring("Connector { ".length(), s.length() - 2);
713       String[] sfields = ss.split(";");
714       Class cl = Connector.class;
715       for (int i = 0; i < sfields.length; i++) {
716         String[] pair = sfields[i].split(":");
717         try {
718           Field f = cl.getDeclaredField(pair[0].trim());
719           setValue(c, f, pair[1].trim());
720         } catch (NoSuchFieldException e) {
721           throw new ParseException(sfields[i], 0);
722         }
723       }
724       return c;
725     } else {
726       throw new ParseException(s, 0);
727     }
728   }
729 
730   /**
731    * Reads a list of Connector instance from a text file, and returns it into an
732    * ArrayList instance.
733    *
734    * @param f  the file to read from
735    * @return   the list
736    * @throws IOException If a read error occurs
737    * @throws ParseException If a syntax error occurs
738    */
739   public static List<Connector> loadFromFile(File f) throws IOException, ParseException {
740     ArrayList<Connector> list = new ArrayList<Connector>();
741 
742     BufferedReader reader = null;
743     try {
744       reader = new BufferedReader(new FileReader(f));
745       String line = reader.readLine();
746       while (line != null) {
747         line = line.trim();
748         if (!line.startsWith("#")) {
749           try {
750             list.add(fromString(line));
751           } catch (Exception skipLine) { }
752         }
753         line = reader.readLine();
754       }
755     } finally {
756       try {
757         if (reader != null) {
758           reader.close();
759         }
760       } catch (Exception ignore) { }
761     }
762 
763     return list;
764   }
765 
766   /**
767    * Saves to a text file the list of connectors.
768    *
769    * @param f     the destination file
770    * @param list  the list of connectors
771    * @throws IOException If a write error occurs
772    * @throws ClassCastException If an element of the list is not a Connector instance
773    */
774   public static void saveToFile(File f, List list) throws IOException {
775     PrintWriter writer = null;
776     try {
777       writer = new PrintWriter(new FileWriter(f), false);
778 
779       if (list != null) {
780         for (Iterator it = list.iterator(); it.hasNext();) {
781           Connector c = (Connector)it.next();
782           writer.println(c.toString());
783         }
784       }
785     } finally {
786       try {
787         if (writer != null) {
788           writer.flush();
789           writer.close();
790         }
791       } catch (Exception ignore) { }
792     }
793   }
794 
795   /**
796    * Helper method for setting the value of an internal field through reflection.
797    *
798    * @param target   the instance whose field must be set
799    * @param f        the field
800    * @param value    the field value
801    */
802   private static void setValue(Object target, Field f, String value) {
803     try {
804       if (value.equals("null")) {
805         f.set(target, null);
806       } else {
807         Class type = f.getType();
808 
809         if (String.class.isAssignableFrom(type)) {
810           f.set(target, value);
811         } else if (Boolean.TYPE.isAssignableFrom(type)) {
812           f.setBoolean(target, "true".equals(value));
813         } else if (Integer.TYPE.isAssignableFrom(type)) {
814           f.setInt(target, Integer.parseInt(value));
815         } else if (Long.TYPE.isAssignableFrom(type)) {
816           f.setLong(target, Long.parseLong(value));
817         }
818       }
819     } catch (IllegalAccessException e) {
820       // This cannot happen, because only fields of this class are set
821     }
822   }
823 
824   /**
825    * @return Returns the protocol.
826    */
827   public String getProtocol() {
828     if (protocol == null) {
829       if (secure) {
830         protocol = Connector.HTTPS;
831       } else {
832         protocol = Connector.HTTP;
833       }
834     }
835     return protocol;
836   }
837 
838   /**
839    * @param protocol The protocol to set.
840    */
841   public void setProtocol(String protocol) {
842     this.protocol = protocol;
843     getProtocol();
844   }
845 
846   /**
847    * List of available protocols.
848    */
849   private static ArrayList<String> availableProtocols = new ArrayList<String>();
850 
851   static {
852     availableProtocols.add(HTTP);
853     availableProtocols.add(HTTPS);
854   }
855 
856   /**
857    * Add to list of available protocols a new protocol.
858    * @param  protocol New protocol to add.
859    */
860   public static void addNewProtocol(String protocol) {
861     availableProtocols.add(protocol.toLowerCase());
862   }
863 
864   /**
865    * Return list of available protocols.
866    * @return List of available protocols.
867    */
868   public static String[] getAvailableProtocols() {
869     return availableProtocols.toArray(new String[availableProtocols.size()]);
870   }
871 }