View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *  http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package javax.mail;
21  
22  import java.io.BufferedReader;
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.InputStreamReader;
28  import java.io.PrintStream;
29  import java.lang.reflect.Constructor;
30  import java.lang.reflect.InvocationTargetException;
31  import java.net.InetAddress;
32  import java.net.URL;
33  import java.util.ArrayList;
34  import java.util.Enumeration;
35  import java.util.HashMap;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.Properties;
39  import java.util.StringTokenizer;
40  import java.util.WeakHashMap;
41  
42  
43  /**
44   * OK, so we have a final class in the API with a heck of a lot of implementation required...
45   * let's try and figure out what it is meant to do.
46   * <p/>
47   * It is supposed to collect together properties and defaults so that they can be
48   * shared by multiple applications on a desktop; with process isolation and no
49   * real concept of shared memory, this seems challenging. These properties and
50   * defaults rely on system properties, making management in a app server harder,
51   * and on resources loaded from "mail.jar" which may lead to skew between                    
52   * differnet independent implementations of this API.
53   *
54   * @version $Rev: 587072 $ $Date: 2007-10-22 08:38:45 -0400 (Mon, 22 Oct 2007) $
55   */
56  public final class Session {
57      private static final Class[] PARAM_TYPES = {Session.class, URLName.class};
58      private static final WeakHashMap addressMapsByClassLoader = new WeakHashMap();
59      private static Session DEFAULT_SESSION;
60  
61      private Map passwordAuthentications = new HashMap();
62  
63      private final Properties properties;
64      private final Authenticator authenticator;
65      private boolean debug;
66      private PrintStream debugOut = System.out;
67  
68      private static final WeakHashMap providersByClassLoader = new WeakHashMap();
69  
70      /**
71       * No public constrcutor allowed.
72       */
73      private Session(Properties properties, Authenticator authenticator) {
74          this.properties = properties;
75          this.authenticator = authenticator;
76          debug = Boolean.valueOf(properties.getProperty("mail.debug")).booleanValue();
77      }
78  
79      /**
80       * Create a new session initialized with the supplied properties which uses the supplied authenticator.
81       * Clients should ensure the properties listed in Appendix A of the JavaMail specification are
82       * set as the defaults are unlikey to work in most scenarios; particular attention should be given
83       * to:
84       * <ul>
85       * <li>mail.store.protocol</li>
86       * <li>mail.transport.protocol</li>
87       * <li>mail.host</li>
88       * <li>mail.user</li>
89       * <li>mail.from</li>
90       * </ul>
91       *
92       * @param properties    the session properties
93       * @param authenticator an authenticator for callbacks to the user
94       * @return a new session
95       */
96      public static Session getInstance(Properties properties, Authenticator authenticator) {
97          return new Session(new Properties(properties), authenticator);
98      }
99  
100     /**
101      * Create a new session initialized with the supplied properties with no authenticator.
102      *
103      * @param properties the session properties
104      * @return a new session
105      * @see #getInstance(java.util.Properties, Authenticator)
106      */
107     public static Session getInstance(Properties properties) {
108         return getInstance(properties, null);
109     }
110 
111     /**
112      * Get the "default" instance assuming no authenticator is required.
113      *
114      * @param properties the session properties
115      * @return if "default" session
116      * @throws SecurityException if the does not have permission to access the default session
117      */
118     public synchronized static Session getDefaultInstance(Properties properties) {
119         return getDefaultInstance(properties, null);
120     }
121 
122     /**
123      * Get the "default" session.
124      * If there is not current "default", a new Session is created and installed as the default.
125      *
126      * @param properties
127      * @param authenticator
128      * @return if "default" session
129      * @throws SecurityException if the does not have permission to access the default session
130      */
131     public synchronized static Session getDefaultInstance(Properties properties, Authenticator authenticator) {
132         if (DEFAULT_SESSION == null) {
133             DEFAULT_SESSION = getInstance(properties, authenticator);
134         } else {
135             if (authenticator != DEFAULT_SESSION.authenticator) {
136                 if (authenticator == null || DEFAULT_SESSION.authenticator == null || authenticator.getClass().getClassLoader() != DEFAULT_SESSION.authenticator.getClass().getClassLoader()) {
137                     throw new SecurityException();
138                 }
139             }
140             // todo we should check with the SecurityManager here as well
141         }
142         return DEFAULT_SESSION;
143     }
144 
145     /**
146      * Enable debugging for this session.
147      * Debugging can also be enabled by setting the "mail.debug" property to true when
148      * the session is being created.
149      *
150      * @param debug the debug setting
151      */
152     public void setDebug(boolean debug) {
153         this.debug = debug;
154     }
155 
156     /**
157      * Get the debug setting for this session.
158      *
159      * @return the debug setting
160      */
161     public boolean getDebug() {
162         return debug;
163     }
164 
165     /**
166      * Set the output stream where debug information should be sent.
167      * If set to null, System.out will be used.
168      *
169      * @param out the stream to write debug information to
170      */
171     public void setDebugOut(PrintStream out) {
172         debugOut = out == null ? System.out : out;
173     }
174 
175     /**
176      * Return the debug output stream.
177      *
178      * @return the debug output stream
179      */
180     public PrintStream getDebugOut() {
181         return debugOut;
182     }
183 
184     /**
185      * Return the list of providers available to this application.
186      * This method searches for providers that are defined in the javamail.providers
187      * and javamail.default.providers resources available through the current context
188      * classloader, or if that is not available, the classloader that loaded this class.
189      * <p/>
190      * As searching for providers is potentially expensive, this implementation maintains
191      * a WeakHashMap of providers indexed by ClassLoader.
192      *
193      * @return an array of providers
194      */
195     public Provider[] getProviders() {
196         ProviderInfo info = getProviderInfo();
197         return (Provider[]) info.all.toArray(new Provider[info.all.size()]);
198     }
199 
200     /**
201      * Return the provider for a specific protocol.
202      * This implementation initially looks in the Session properties for an property with the name
203      * "mail.<protocol>.class"; if found it attempts to create an instance of the class named in that
204      * property throwing a NoSuchProviderException if the class cannot be loaded.
205      * If this property is not found, it searches the providers returned by {@link #getProviders()}
206      * for a entry for the specified protocol.
207      *
208      * @param protocol the protocol to get a provider for
209      * @return a provider for that protocol
210      * @throws NoSuchProviderException
211      */
212     public Provider getProvider(String protocol) throws NoSuchProviderException {
213         ProviderInfo info = getProviderInfo();
214         Provider provider = null;
215         String providerName = properties.getProperty("mail." + protocol + ".class");
216         if (providerName != null) {
217             provider = (Provider) info.byClassName.get(providerName);
218             if (debug) {
219                 writeDebug("DEBUG: new provider loaded: " + provider.toString());
220             }
221         }
222 
223         // if not able to locate this by class name, just grab a registered protocol.
224         if (provider == null) {
225             provider = (Provider) info.byProtocol.get(protocol);
226         }
227 
228         if (provider == null) {
229             throw new NoSuchProviderException("Unable to locate provider for protocol: " + protocol);
230         }
231         if (debug) {
232             writeDebug("DEBUG: getProvider() returning provider " + provider.toString());
233         }
234         return provider;
235     }
236 
237     /**
238      * Make the supplied Provider the default for its protocol.
239      *
240      * @param provider the new default Provider
241      * @throws NoSuchProviderException
242      */
243     public void setProvider(Provider provider) throws NoSuchProviderException {
244         ProviderInfo info = getProviderInfo();
245         info.byProtocol.put(provider.getProtocol(), provider);
246     }
247 
248     /**
249      * Return a Store for the default protocol defined by the mail.store.protocol property.
250      *
251      * @return the store for the default protocol
252      * @throws NoSuchProviderException
253      */
254     public Store getStore() throws NoSuchProviderException {
255         String protocol = properties.getProperty("mail.store.protocol");
256         if (protocol == null) {
257             throw new NoSuchProviderException("mail.store.protocol property is not set");
258         }
259         return getStore(protocol);
260     }
261 
262     /**
263      * Return a Store for the specified protocol.
264      *
265      * @param protocol the protocol to get a Store for
266      * @return a Store
267      * @throws NoSuchProviderException if no provider is defined for the specified protocol
268      */
269     public Store getStore(String protocol) throws NoSuchProviderException {
270         Provider provider = getProvider(protocol);
271         return getStore(provider);
272     }
273 
274     /**
275      * Return a Store for the protocol specified in the given URL
276      *
277      * @param url the URL of the Store
278      * @return a Store
279      * @throws NoSuchProviderException if no provider is defined for the specified protocol
280      */
281     public Store getStore(URLName url) throws NoSuchProviderException {
282         return (Store) getService(getProvider(url.getProtocol()), url);
283     }
284 
285     /**
286      * Return the Store specified by the given provider.
287      *
288      * @param provider the provider to create from
289      * @return a Store
290      * @throws NoSuchProviderException if there was a problem creating the Store
291      */
292     public Store getStore(Provider provider) throws NoSuchProviderException {
293         if (Provider.Type.STORE != provider.getType()) {
294             throw new NoSuchProviderException("Not a Store Provider: " + provider);
295         }
296         return (Store) getService(provider, null);
297     }
298 
299     /**
300      * Return a closed folder for the supplied URLName, or null if it cannot be obtained.
301      * <p/>
302      * The scheme portion of the URL is used to locate the Provider and create the Store;
303      * the returned Store is then used to obtain the folder.
304      *
305      * @param name the location of the folder
306      * @return the requested folder, or null if it is unavailable
307      * @throws NoSuchProviderException if there is no provider
308      * @throws MessagingException      if there was a problem accessing the Store
309      */
310     public Folder getFolder(URLName name) throws MessagingException {
311         Store store = getStore(name);
312         return store.getFolder(name);
313     }
314 
315     /**
316      * Return a Transport for the default protocol specified by the
317      * <code>mail.transport.protocol</code> property.
318      *
319      * @return a Transport
320      * @throws NoSuchProviderException
321      */
322     public Transport getTransport() throws NoSuchProviderException {
323         String protocol = properties.getProperty("mail.transport.protocol");
324         if (protocol == null) {
325             throw new NoSuchProviderException("mail.transport.protocol property is not set");
326         }
327         return getTransport(protocol);
328     }
329 
330     /**
331      * Return a Transport for the specified protocol.
332      *
333      * @param protocol the protocol to use
334      * @return a Transport
335      * @throws NoSuchProviderException
336      */
337     public Transport getTransport(String protocol) throws NoSuchProviderException {
338         Provider provider = getProvider(protocol);
339         return getTransport(provider);
340     }
341 
342     /**
343      * Return a transport for the protocol specified in the URL.
344      *
345      * @param name the URL whose scheme specifies the protocol
346      * @return a Transport
347      * @throws NoSuchProviderException
348      */
349     public Transport getTransport(URLName name) throws NoSuchProviderException {
350         return (Transport) getService(getProvider(name.getProtocol()), name);
351     }
352 
353     /**
354      * Return a transport for the protocol associated with the type of this address.
355      *
356      * @param address the address we are trying to deliver to
357      * @return a Transport
358      * @throws NoSuchProviderException
359      */
360     public Transport getTransport(Address address) throws NoSuchProviderException {
361         String type = address.getType();
362         // load the address map from the resource files.
363         Map addressMap = getAddressMap();
364         String protocolName = (String)addressMap.get(type);
365         if (protocolName == null) {
366             throw new NoSuchProviderException("No provider for address type " + type);
367         }
368         return getTransport(protocolName);
369     }
370 
371     /**
372      * Return the Transport specified by a Provider
373      *
374      * @param provider the defining Provider
375      * @return a Transport
376      * @throws NoSuchProviderException
377      */
378     public Transport getTransport(Provider provider) throws NoSuchProviderException {
379         return (Transport) getService(provider, null);
380     }
381 
382     /**
383      * Set the password authentication associated with a URL.
384      *
385      * @param name          the url
386      * @param authenticator the authenticator
387      */
388     public void setPasswordAuthentication(URLName name, PasswordAuthentication authenticator) {
389         if (authenticator == null) {
390             passwordAuthentications.remove(name);
391         } else {
392             passwordAuthentications.put(name, authenticator);
393         }
394     }
395 
396     /**
397      * Get the password authentication associated with a URL
398      *
399      * @param name the URL
400      * @return any authenticator for that url, or null if none
401      */
402     public PasswordAuthentication getPasswordAuthentication(URLName name) {
403         return (PasswordAuthentication) passwordAuthentications.get(name);
404     }
405 
406     /**
407      * Call back to the application supplied authenticator to get the needed username add password.
408      *
409      * @param host            the host we are trying to connect to, may be null
410      * @param port            the port on that host
411      * @param protocol        the protocol trying to be used
412      * @param prompt          a String to show as part of the prompt, may be null
413      * @param defaultUserName the default username, may be null
414      * @return the authentication information collected by the authenticator; may be null
415      */
416     public PasswordAuthentication requestPasswordAuthentication(InetAddress host, int port, String protocol, String prompt, String defaultUserName) {
417         if (authenticator == null) {
418             return null;
419         }
420         return authenticator.authenticate(host, port, protocol, prompt, defaultUserName);
421     }
422 
423     /**
424      * Return the properties object for this Session; this is a live collection.
425      *
426      * @return the properties for the Session
427      */
428     public Properties getProperties() {
429         return properties;
430     }
431 
432     /**
433      * Return the specified property.
434      *
435      * @param property the property to get
436      * @return its value, or null if not present
437      */
438     public String getProperty(String property) {
439         return getProperties().getProperty(property);
440     }
441 
442 
443     /**
444      * Add a provider to the Session managed provider list.
445      *
446      * @param provider The new provider to add.
447      */
448     public synchronized void addProvider(Provider provider) {
449         ProviderInfo info = getProviderInfo();
450         info.addProvider(provider);
451     }
452 
453 
454 
455     /**
456      * Add a mapping between an address type and a protocol used
457      * to process that address type.
458      *
459      * @param addressType
460      *                 The address type identifier.
461      * @param protocol The protocol name mapping.
462      */
463     public void setProtocolForAddress(String addressType, String protocol) {
464         Map addressMap = getAddressMap();
465 
466         // no protocol specified is a removal
467         if (protocol == null) {
468             addressMap.remove(addressType);
469         }
470         else {
471             addressMap.put(addressType, protocol);
472         }
473     }
474 
475 
476     private Service getService(Provider provider, URLName name) throws NoSuchProviderException {
477         try {
478             if (name == null) {
479                 name = new URLName(provider.getProtocol(), null, -1, null, null, null); 
480            }
481             ClassLoader cl = getClassLoader();
482             Class clazz = cl.loadClass(provider.getClassName());
483             Constructor ctr = clazz.getConstructor(PARAM_TYPES);
484             return (Service) ctr.newInstance(new Object[]{this, name});
485         } catch (ClassNotFoundException e) {
486             throw (NoSuchProviderException) new NoSuchProviderException("Unable to load class for provider: " + provider).initCause(e);
487         } catch (NoSuchMethodException e) {
488             throw (NoSuchProviderException) new NoSuchProviderException("Provider class does not have a constructor(Session, URLName): " + provider).initCause(e);
489         } catch (InstantiationException e) {
490             throw (NoSuchProviderException) new NoSuchProviderException("Unable to instantiate provider class: " + provider).initCause(e);
491         } catch (IllegalAccessException e) {
492             throw (NoSuchProviderException) new NoSuchProviderException("Unable to instantiate provider class: " + provider).initCause(e);
493         } catch (InvocationTargetException e) {
494             throw (NoSuchProviderException) new NoSuchProviderException("Exception from constructor of provider class: " + provider).initCause(e.getCause());
495         }
496     }
497 
498     private ProviderInfo getProviderInfo() {
499         ClassLoader cl = getClassLoader();
500         ProviderInfo info = (ProviderInfo) providersByClassLoader.get(cl);
501         if (info == null) {
502             info = loadProviders(cl);
503         }
504         return info;
505     }
506 
507     private Map getAddressMap() {
508         ClassLoader cl = getClassLoader();
509         Map addressMap = (Map)addressMapsByClassLoader.get(cl);
510         if (addressMap == null) {
511             addressMap = loadAddressMap(cl);
512         }
513         return addressMap;
514     }
515 
516 
517     /**
518      * Resolve a class loader used to resolve context resources.  The
519      * class loader used is either a current thread context class
520      * loader (if set), the class loader used to load an authenticator
521      * we've been initialized with, or the class loader used to load
522      * this class instance (which may be a subclass of Session).
523      *
524      * @return The class loader used to load resources.
525      */
526     private ClassLoader getClassLoader() {
527         ClassLoader cl = Thread.currentThread().getContextClassLoader();
528         if (cl == null) {
529             if (authenticator != null) {
530                 cl = authenticator.getClass().getClassLoader();
531             }
532             else {
533                 cl = this.getClass().getClassLoader();
534             }
535         }
536         return cl;
537     }
538 
539     private ProviderInfo loadProviders(ClassLoader cl) {
540         // we create a merged map from reading all of the potential address map entries.  The locations
541         // searched are:
542         //   1.   java.home/lib/javamail.address.map
543         //   2. META-INF/javamail.address.map
544         //   3. META-INF/javamail.default.address.map
545         //
546         ProviderInfo info = new ProviderInfo();
547 
548         // make sure this is added to the global map.
549         providersByClassLoader.put(cl, info);
550 
551 
552         // NOTE:  Unlike the addressMap, we process these in the defined order.  The loading routine
553         // will not overwrite entries if they already exist in the map.
554 
555         try {
556             File file = new File(System.getProperty("java.home"), "lib/javamail.providers");
557             InputStream is = new FileInputStream(file);
558             try {
559                 loadProviders(info, is);
560                 if (debug) {
561                     writeDebug("Loaded lib/javamail.providers from " + file.toString());
562                 }
563             } finally{
564                 is.close();
565             }
566         } catch (SecurityException e) {
567             // ignore
568         } catch (IOException e) {
569             // ignore
570         }
571 
572         try {
573             Enumeration e = cl.getResources("META-INF/javamail.providers");
574             while (e.hasMoreElements()) {
575                 URL url = (URL) e.nextElement();
576                 if (debug) {
577                     writeDebug("Loading META-INF/javamail.providers from " + url.toString());
578                 }
579                 InputStream is = url.openStream();
580                 try {
581                     loadProviders(info, is);
582                 } finally{
583                     is.close();
584                 }
585             }
586         } catch (SecurityException e) {
587             // ignore
588         } catch (IOException e) {
589             // ignore
590         }
591 
592         try {
593             Enumeration e = cl.getResources("META-INF/javamail.default.providers");
594             while (e.hasMoreElements()) {
595                 URL url = (URL) e.nextElement();
596                 if (debug) {
597                     writeDebug("Loading javamail.default.providers from " + url.toString());
598                 }
599 
600                 InputStream is = url.openStream();
601                 try {
602                     loadProviders(info, is);
603                 } finally{
604                     is.close();
605                 }
606             }
607         } catch (SecurityException e) {
608             // ignore
609         } catch (IOException e) {
610             // ignore
611         }
612 
613         return info;
614     }
615 
616     private void loadProviders(ProviderInfo info, InputStream is) throws IOException {
617         BufferedReader reader = new BufferedReader(new InputStreamReader(is));
618         String line;
619         while ((line = reader.readLine()) != null) {
620             // Lines beginning with "#" are just comments.  
621             if (line.startsWith("#")) {
622                 continue; 
623             }
624             
625             StringTokenizer tok = new StringTokenizer(line, ";");
626             String protocol = null;
627             Provider.Type type = null;
628             String className = null;
629             String vendor = null;
630             String version = null;
631             while (tok.hasMoreTokens()) {
632                 String property = tok.nextToken();
633                 int index = property.indexOf('=');
634                 if (index == -1) {
635                     continue;
636                 }
637                 String key = property.substring(0, index).trim().toLowerCase();
638                 String value = property.substring(index+1).trim();
639                 if (protocol == null && "protocol".equals(key)) {
640                     protocol = value;
641                 } else if (type == null && "type".equals(key)) {
642                     if ("store".equals(value)) {
643                         type = Provider.Type.STORE;
644                     } else if ("transport".equals(value)) {
645                         type = Provider.Type.TRANSPORT;
646                     }
647                 } else if (className == null && "class".equals(key)) {
648                     className = value;
649                 } else if ("vendor".equals(key)) {
650                     vendor = value;
651                 } else if ("version".equals(key)) {
652                     version = value;
653                 }
654             }
655             if (protocol == null || type == null || className == null) {
656                 //todo should we log a warning?
657                 continue;
658             }
659 
660             if (debug) {
661                 writeDebug("DEBUG: loading new provider protocol=" + protocol + ", className=" + className + ", vendor=" + vendor + ", version=" + version);
662             }
663             Provider provider = new Provider(type, protocol, className, vendor, version);
664             // add to the info list.
665             info.addProvider(provider);
666         }
667     }
668 
669     /**
670      * Load up an address map associated with a using class loader
671      * instance.
672      *
673      * @param cl     The class loader used to resolve the address map.
674      *
675      * @return A map containing the entries associated with this classloader
676      *         instance.
677      */
678     private static Map loadAddressMap(ClassLoader cl) {
679         // we create a merged map from reading all of the potential address map entries.  The locations
680         // searched are:
681         //   1.   java.home/lib/javamail.address.map
682         //   2. META-INF/javamail.address.map
683         //   3. META-INF/javamail.default.address.map
684         //
685         // if all of the above searches fail, we just set up some "default" defaults.
686 
687         // the format of the address.map file is defined as a property file.  We can cheat and
688         // just use Properties.load() to read in the files.
689         Properties addressMap = new Properties();
690 
691         // add this to the tracking map.
692         addressMapsByClassLoader.put(cl, addressMap);
693 
694         // NOTE:  We are reading these resources in reverse order of what's cited above.  This allows
695         // user defined entries to overwrite default entries if there are similarly named items.
696 
697         try {
698             Enumeration e = cl.getResources("META-INF/javamail.default.address.map");
699             while (e.hasMoreElements()) {
700                 URL url = (URL) e.nextElement();
701                 InputStream is = url.openStream();
702                 try {
703                     // load as a property file
704                     addressMap.load(is);
705                 } finally{
706                     is.close();
707                 }
708             }
709         } catch (SecurityException e) {
710             // ignore
711         } catch (IOException e) {
712             // ignore
713         }
714 
715 
716         try {
717             Enumeration e = cl.getResources("META-INF/javamail.address.map");
718             while (e.hasMoreElements()) {
719                 URL url = (URL) e.nextElement();
720                 InputStream is = url.openStream();
721                 try {
722                     // load as a property file
723                     addressMap.load(is);
724                 } finally{
725                     is.close();
726                 }
727             }
728         } catch (SecurityException e) {
729             // ignore
730         } catch (IOException e) {
731             // ignore
732         }
733 
734 
735         try {
736             File file = new File(System.getProperty("java.home"), "lib/javamail.address.map");
737             InputStream is = new FileInputStream(file);
738             try {
739                 // load as a property file
740                 addressMap.load(is);
741             } finally{
742                 is.close();
743             }
744         } catch (SecurityException e) {
745             // ignore
746         } catch (IOException e) {
747             // ignore
748         }
749 
750         try {
751             Enumeration e = cl.getResources("META-INF/javamail.address.map");
752             while (e.hasMoreElements()) {
753                 URL url = (URL) e.nextElement();
754                 InputStream is = url.openStream();
755                 try {
756                     // load as a property file
757                     addressMap.load(is);
758                 } finally{
759                     is.close();
760                 }
761             }
762         } catch (SecurityException e) {
763             // ignore
764         } catch (IOException e) {
765             // ignore
766         }
767 
768 
769         // if unable to load anything, at least create the MimeMessage-smtp protocol mapping.
770         if (addressMap.isEmpty()) {
771             addressMap.put("rfc822", "smtp");
772         }
773 
774         return addressMap;
775     }
776 
777     /**
778      * Private convenience routine for debug output.
779      *
780      * @param msg    The message to write out to the debug stream.
781      */
782     private void writeDebug(String msg) {
783         debugOut.println(msg);
784     }
785 
786 
787     private static class ProviderInfo {
788         private final Map byClassName = new HashMap();
789         private final Map byProtocol = new HashMap();
790         private final List all = new ArrayList();
791 
792         public void addProvider(Provider provider) {
793             String className = provider.getClassName();
794 
795             if (!byClassName.containsKey(className)) {
796                 byClassName.put(className, provider);
797             }
798 
799             String protocol = provider.getProtocol();
800             if (!byProtocol.containsKey(protocol)) {
801                 byProtocol.put(protocol, provider);
802             }
803             all.add(provider);
804         }
805     }
806 }