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 }