001    /* NamingManager.java -- Creates contexts and objects
002       Copyright (C) 2000, 2001, 2002, 2003, 2004,
003       2006 Free Software Foundation, Inc.
004    
005    This file is part of GNU Classpath.
006    
007    GNU Classpath is free software; you can redistribute it and/or modify
008    it under the terms of the GNU General Public License as published by
009    the Free Software Foundation; either version 2, or (at your option)
010    any later version.
011    
012    GNU Classpath is distributed in the hope that it will be useful, but
013    WITHOUT ANY WARRANTY; without even the implied warranty of
014    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015    General Public License for more details.
016    
017    You should have received a copy of the GNU General Public License
018    along with GNU Classpath; see the file COPYING.  If not, write to the
019    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
020    02110-1301 USA.
021    
022    Linking this library statically or dynamically with other modules is
023    making a combined work based on this library.  Thus, the terms and
024    conditions of the GNU General Public License cover the whole
025    combination.
026    
027    As a special exception, the copyright holders of this library give you
028    permission to link this library with independent modules to produce an
029    executable, regardless of the license terms of these independent
030    modules, and to copy and distribute the resulting executable under
031    terms of your choice, provided that you also meet, for each linked
032    independent module, the terms and conditions of the license of that
033    module.  An independent module is a module which is not derived from
034    or based on this library.  If you modify this library, you may extend
035    this exception to your version of the library, but you are not
036    obligated to do so.  If you do not wish to do so, delete this
037    exception statement from your version. */
038    
039    
040    package javax.naming.spi;
041    
042    import gnu.classpath.VMStackWalker;
043    
044    import gnu.java.lang.CPStringBuilder;
045    
046    import java.util.Enumeration;
047    import java.util.Hashtable;
048    import java.util.StringTokenizer;
049    
050    import javax.naming.CannotProceedException;
051    import javax.naming.Context;
052    import javax.naming.Name;
053    import javax.naming.NamingException;
054    import javax.naming.NoInitialContextException;
055    import javax.naming.RefAddr;
056    import javax.naming.Reference;
057    import javax.naming.Referenceable;
058    import javax.naming.StringRefAddr;
059    
060    /**
061     * Contains methods for creating contexts and objects referred to by
062     * location information. The location is specified in the scope of the
063     * certain naming or directory service. This class only contais static
064     * methods and cannot be instantiated.
065     */
066    public class NamingManager
067    {
068      /**
069       * The environment property into which getContinuationContext() stores the
070       * value of the CannotProceedException parameter. The value of this field
071       * is <i>java.naming.spi.CannotProceedException<i>.
072       */
073      public static final String CPE = "java.naming.spi.CannotProceedException";
074    
075      private static InitialContextFactoryBuilder icfb;
076    
077      // Package private so DirectoryManager can access it.
078      static ObjectFactoryBuilder ofb;
079    
080      // This class cannot be instantiated.
081      NamingManager ()
082      {
083      }
084    
085      /**
086       * Checks if the initial context factory builder has been set.
087       * 
088       * @return true if the builder has been set
089       * 
090       * @see #setInitialContextFactoryBuilder(InitialContextFactoryBuilder)
091       */
092      public static boolean hasInitialContextFactoryBuilder ()
093      {
094        return icfb != null;
095      }
096      
097      /**
098       * Creates the initial context. If the initial object factory builder has
099       * been set with {@link #setObjectFactoryBuilder(ObjectFactoryBuilder)},
100       * the work is delegated to this builder. Otherwise, the method searches
101       * for the property Context.INITIAL_CONTEXT_FACTORY first in the passed
102       * table and then in the system properties. The value of this property is
103       * uses as a class name to install the context factory. The corresponding
104       * class must exist, be public and have the public parameterless constructor. 
105       * 
106       * @param environment the properties, used to create the context.
107       * 
108       * @return the created context
109       * 
110       * @throws NoInitialContextException if the initial builder is not set,
111       *           the property Context.INITIAL_CONTEXT_FACTORY is missing of the
112       *           class, named by this property, cannot be instantiated. 
113       * @throws NamingException if throws by the context factory
114       */
115      public static Context getInitialContext (Hashtable<?, ?> environment)
116        throws NamingException
117      {
118        InitialContextFactory icf = null;
119        
120        if (icfb != null)
121          icf = icfb.createInitialContextFactory(environment);
122        else
123          {  
124            String java_naming_factory_initial = null;
125            if (environment != null)
126              java_naming_factory_initial
127                = (String) environment.get (Context.INITIAL_CONTEXT_FACTORY);
128            if (java_naming_factory_initial == null)
129              java_naming_factory_initial =
130                System.getProperty (Context.INITIAL_CONTEXT_FACTORY);
131            if (java_naming_factory_initial == null)
132              throw new
133                NoInitialContextException ("Can't find property: "
134                                           + Context.INITIAL_CONTEXT_FACTORY);
135    
136            try
137              {
138                icf = (InitialContextFactory)Class.forName
139                    (java_naming_factory_initial, true,
140                     Thread.currentThread().getContextClassLoader())
141                    .newInstance ();
142              }
143            catch (Exception exception)
144              {
145                NoInitialContextException e
146                  = new NoInitialContextException
147                  ("Can't load InitialContextFactory class: "
148                   + java_naming_factory_initial);
149                e.setRootCause(exception);
150                throw e;
151              }
152          }
153    
154        return icf.getInitialContext (environment);
155      }
156    
157      /**
158       * <p>
159       * Creates the URL context for the given URL scheme id.
160       * </p>
161       * <p>
162       * The class name of the factory that creates the context has the naming
163       * pattern scheme-idURLContextFactory. For instance, the factory for the "ftp"
164       * sheme should be named "ftpURLContextFactory".
165       * </p>
166       * <p>
167       * The Context.URL_PKG_PREFIXES environment property contains the
168       * colon-separated list of the possible package prefixes. The package name is
169       * constructed concatenating the package prefix with the scheme id. This
170       * property is searched in the passed <i>environment</i> parameter and later
171       * in the system properties.
172       * </p>
173       * <p>
174       * If the factory class cannot be found in the specified packages, system will
175       * try to use the default internal factory for the given scheme.
176       * </p>
177       * <p>
178       * After the factory is instantiated, its method
179       * {@link ObjectFactory#getObjectInstance(Object, Name, Context, Hashtable)}
180       * is called to create and return the object instance.
181       * 
182       * @param refInfo passed to the factory
183       * @param name passed to the factory
184       * @param nameCtx passed to the factory
185       * @param scheme the url scheme that must be supported by the given context
186       * @param environment the properties for creating the factory and context (may
187       *          be null)
188       * @return the created context
189       * @throws NamingException if thrown by the factory when creating the context.
190       */
191      static Context getURLContext(Object refInfo, Name name, Context nameCtx,
192                                   String scheme, Hashtable<?,?> environment)
193          throws NamingException
194      {
195        // Doc specifies com.sun.jndi.url as the final destination, but we cannot
196        // put our classes into such namespace.
197        String defaultPrefix = "gnu.javax.naming.jndi.url";
198    
199        // The final default location, as specified in the documentation.
200        String finalPrefix = "com.sun.jndi.url";
201      
202        CPStringBuilder allPrefixes = new CPStringBuilder();
203    
204        String prefixes;
205          if (environment != null)
206            {
207            prefixes = (String) environment.get(Context.URL_PKG_PREFIXES);
208            if (prefixes != null)
209              allPrefixes.append(prefixes);
210            }
211      
212        prefixes = System.getProperty(Context.URL_PKG_PREFIXES);
213        if (prefixes != null)
214          {
215            if (allPrefixes.length() > 0)
216              allPrefixes.append(':');
217            allPrefixes.append(prefixes);
218          }
219    
220        if (allPrefixes.length() > 0)
221          allPrefixes.append(':');
222        allPrefixes.append(defaultPrefix);
223        allPrefixes.append(':');
224        allPrefixes.append(finalPrefix);
225    
226          scheme = scheme + "." + scheme + "URLContextFactory";
227      
228        StringTokenizer tokens = new StringTokenizer(allPrefixes.toString(), ":");
229        while (tokens.hasMoreTokens())
230            {
231            String aTry = tokens.nextToken();
232            try
233              {
234                String tryClass = aTry + "." + scheme;
235                Class factoryClass = forName(tryClass);
236                if (factoryClass != null)
237                  {
238                    Object obj;
239                    try
240                      {
241                        ObjectFactory factory = (ObjectFactory) factoryClass.newInstance();
242                        obj = factory.getObjectInstance(refInfo, name, nameCtx,
243                                                        environment);
244                        Context ctx = (Context) obj;
245                        if (ctx != null)
246                          return ctx;
247                      }
248                    catch (RuntimeException e)
249                      {
250                        // TODO Auto-generated catch block
251                        e.printStackTrace();
252                      }
253                  }
254              }
255            catch (ClassNotFoundException _1)
256              {
257                // Ignore it.
258              }
259            catch (ClassCastException _2)
260              {
261                // This means that the class we found was not an
262                // ObjectFactory or that the factory returned something
263                // which was not a Context.
264              }
265            catch (InstantiationException _3)
266              {
267                // If we couldn't instantiate the factory we might get
268                // this.
269              }
270            catch (IllegalAccessException _4)
271              {
272                // Another possibility when instantiating.
273              }
274            catch (NamingException _5)
275              {
276                throw _5;
277              }
278            catch (Exception _6)
279              {
280                // Anything from getObjectInstance.
281              }
282            }
283        
284        return null;
285      }
286    
287      /**
288       * Load the class with the given name. This method tries to use the context
289       * class loader first. If this fails, it searches for the suitable class
290       * loader in the caller stack trace. This method is a central point where all
291       * requests to find a class by name are delegated.
292       */
293      static Class forName(String className)
294      {
295        try
296          {
297            return Class.forName(className, true,
298                                 Thread.currentThread().getContextClassLoader());
299          }
300        catch (ClassNotFoundException nex)
301          {
302            /**
303             * Returns the first user defined class loader on the call stack, or
304             * null when no non-null class loader was found.
305             */
306            Class[] ctx = VMStackWalker.getClassContext();
307            for (int i = 0; i < ctx.length; i++)
308              {
309                // Since we live in a class loaded by the bootstrap
310                // class loader, getClassLoader is safe to call without
311                // needing to be wrapped in a privileged action.
312                ClassLoader cl = ctx[i].getClassLoader();
313                try
314                  {
315                    if (cl != null)
316                      return Class.forName(className, true, cl);
317                  }
318                catch (ClassNotFoundException nex2)
319                  {
320                    // Try next.
321                  }
322              }
323          }
324        return null;
325      }  
326      
327      
328      /**
329       * <p>
330       * Creates the URL context for the given URL scheme id.
331       * </p>
332       * <p>
333       * The class name of the factory that creates the context has the naming
334       * pattern scheme-idURLContextFactory. For instance, the factory for the
335       * "ftp" scheme should be named "ftpURLContextFactory".
336       * The Context.URL_PKG_PREFIXES environment property contains the
337       * colon-separated list of the possible package prefixes. The package name
338       * is constructed by concatenating the package prefix with the scheme id.
339       * </p>
340       * <p>
341       * If the factory class cannot be found in the specified packages, the
342       * system will try to use the default internal factory for the given scheme.
343       * </p>
344       * <p>
345       * After the factory is instantiated, its method
346       * {@link ObjectFactory#getObjectInstance(Object, Name, Context, Hashtable)}
347       * is called to create and return the object instance.
348       * 
349       * @param scheme the url scheme that must be supported by the given context
350       * @param environment the properties for creating the factory and context
351       *                    (may be null)
352       * @return the created context
353       * @throws NamingException if thrown by the factory when creating the
354       *                         context.
355       */
356      public static Context getURLContext (String scheme,
357                                           Hashtable<?, ?> environment) 
358           throws NamingException
359      {
360        return getURLContext (null, null, null, scheme, environment);
361      }
362    
363      /**
364       * Sets the initial object factory builder.
365       * 
366       * @param builder the builder to set
367       * 
368       * @throws SecurityException if the builder cannot be installed due
369       *           security restrictions.
370       * @throws NamingException if the builder cannot be installed due other 
371       *           reasons
372       * @throws IllegalStateException if setting the builder repeatedly
373       */
374      public static void setObjectFactoryBuilder (ObjectFactoryBuilder builder)
375        throws NamingException
376      {
377        SecurityManager sm = System.getSecurityManager ();
378        if (sm != null)
379          sm.checkSetFactory ();
380        // Once the builder is installed it cannot be replaced.
381        if (ofb != null)
382          throw new IllegalStateException ("object factory builder already installed");
383        if (builder != null)
384          ofb = builder;
385      }
386    
387      static StringTokenizer getPlusPath (String property, Hashtable env,
388                                          Context nameCtx)
389        throws NamingException
390      {
391        String path = (String) env.get (property);
392        if (nameCtx == null)
393          nameCtx = getInitialContext (env);
394        String path2 = (String) nameCtx.getEnvironment ().get (property);
395        if (path == null)
396          path = path2;
397        else if (path2 != null)
398          path += ":" + path2;
399        return new StringTokenizer (path != null ? path : "", ":");
400      }
401      
402      /**
403       * <p>Creates an object for the specified name context, environment and
404       * referencing context object.</p>
405       * <p>
406       * If the builder factory is set by 
407       * {@link #setObjectFactoryBuilder(ObjectFactoryBuilder)}, the call is
408       * delegated to that factory. Otherwise, the object is created using the
409       * following rules:
410       * <ul>
411       * <li>If the referencing object (refInfo) contains the factory class name,
412       *       the object is created by this factory. If the creation fails,
413       *       the parameter refInfo is returned as the method return value.</li>
414       * <li>If the referencing object has no factory class name, and the addresses
415       *       are StringRefAddrs having the address type "URL", the object is
416       *       created by the URL context factory. The used factory corresponds the
417       *       the naming schema of the each URL. If the attempt to create
418       *       the object this way is not successful, the subsequent rule is 
419       *       tried.</li>
420       * <li>  If the refInfo is not an instance of Reference or Referencable
421       *       (for example, null), the object is created by the factories,
422       *       specified in the Context.OBJECT_FACTORIES property of the 
423       *       environment and the provider resource file, associated with the
424       *       nameCtx. The value of this property is the colon separated list
425       *       of the possible factories. If none of the factories can be
426       *       loaded, the refInfo is returned.            
427       * </ul>
428       * </p>
429       * <p>The object factory must be public and have the public parameterless
430       * constructor.</p>
431       *  
432       * @param refInfo the referencing object, for which the new object must be
433       *          created (can be null). If not null, it is usually an instance of
434       *          the {@link Reference} or {@link Referenceable}.
435       * @param name the name of the object. The name is relative to
436       *          the nameCtx naming context. The value of this parameter can be
437       *          null if the name is not specified.
438       * @param nameCtx the naming context, in which scope the name of the new
439       *          object is specified. If this parameter is null, the name is
440       *          specified in the scope of the initial context.
441       * @param environment contains additional information for creating the object.
442       *          This paramter can be null if there is no need to provide any
443       *          additional information.
444       *        
445       * @return  the created object. If the creation fails, in some cases
446       *          the parameter refInfo may be returned.
447       * 
448       * @throws NamingException if the attempt to name the new object has failed
449       * @throws Exception if the object factory throws it. The object factory
450       *           only throws an exception if it does not want other factories
451       *           to be used to create the object.
452       */
453      public static Object getObjectInstance (Object refInfo,
454                                              Name name,
455                                              Context nameCtx,
456                                              Hashtable<?, ?> environment)
457        throws Exception
458      {
459        ObjectFactory factory = null;
460    
461        if (ofb != null)
462          factory = ofb.createObjectFactory (refInfo, environment);
463        else
464          {
465            // First see if we have a Reference or a Referenceable.  If so
466            // we do some special processing.
467            Object ref2 = refInfo;
468            if (refInfo instanceof Referenceable)
469              ref2 = ((Referenceable) refInfo).getReference ();
470            if (ref2 instanceof Reference)
471              {
472                Reference ref = (Reference) ref2;
473    
474                // If we have a factory class name then we use that.
475                String fClass = ref.getFactoryClassName ();
476                if (fClass != null)
477                  {
478                    // Exceptions here are passed to the caller.
479                    Class k = Class.forName (fClass,
480                                             true,
481                                             Thread.currentThread().getContextClassLoader());
482                    factory = (ObjectFactory) k.newInstance ();
483                  }
484                else
485                  {
486                    // There's no factory class name.  If the address is a
487                    // StringRefAddr with address type `URL', then we try
488                    // the URL's context factory.
489                    Enumeration e = ref.getAll ();
490                    while (e.hasMoreElements ())
491                      {
492                        RefAddr ra = (RefAddr) e.nextElement ();
493                        if (ra instanceof StringRefAddr
494                            && "URL".equals (ra.getType ()))
495                          {
496                            factory
497                              = (ObjectFactory) getURLContext (refInfo,
498                                                               name,
499                                                               nameCtx,
500                                                               (String) ra.getContent (),
501                                                               environment);
502                            Object obj = factory.getObjectInstance (refInfo,
503                                                                    name,
504                                                                    nameCtx,
505                                                                    environment);
506                            if (obj != null)
507                              return obj;
508                          }
509                      }
510    
511                    // Have to try the next step.
512                    factory = null;
513                  }
514              }
515    
516            // Now look at OBJECT_FACTORIES to find the factory.
517            if (factory == null)
518              {
519                StringTokenizer tokens = getPlusPath (Context.OBJECT_FACTORIES,
520                                                      environment, nameCtx);
521    
522                while (tokens.hasMoreTokens ())
523                  {
524                    String klassName = tokens.nextToken ();
525                    Class k = Class.forName (klassName,
526                                             true,
527                                             Thread.currentThread().getContextClassLoader());
528                    factory = (ObjectFactory) k.newInstance ();
529                    Object obj = factory.getObjectInstance (refInfo, name,
530                                                            nameCtx, environment);
531                    if (obj != null)
532                      return obj;
533                  }
534    
535                // Failure.
536                return refInfo;
537              }
538          }
539    
540        if (factory == null)
541          return refInfo;
542        Object obj = factory.getObjectInstance (refInfo, name,
543                                                nameCtx, environment);
544        return obj == null ? refInfo : obj;
545      }
546    
547      /**
548       * Sets the initial context factory builder.
549       * 
550       * @param builder the builder to set
551       * 
552       * @throws SecurityException if the builder cannot be installed due
553       *           security restrictions.
554       * @throws NamingException if the builder cannot be installed due other 
555       *           reasons
556       * @throws IllegalStateException if setting the builder repeatedly
557       * 
558       * @see #hasInitialContextFactoryBuilder()
559       */
560      public static void setInitialContextFactoryBuilder 
561        (InitialContextFactoryBuilder builder)
562        throws NamingException
563      {
564        SecurityManager sm = System.getSecurityManager ();
565        if (sm != null)
566          sm.checkSetFactory ();
567        // Once the builder is installed it cannot be replaced.
568        if (icfb != null)
569          throw new IllegalStateException ("ctx factory builder already installed");
570        if (builder != null)
571          icfb = builder;
572      }
573      
574      /**
575       * Creates a context in which the context operation must be continued.
576       * This method is used by operations on names that span multiple namespaces.
577       * 
578       * @param cpe the exception that triggered this continuation. This method
579       * obtains the environment ({@link CannotProceedException#getEnvironment()}
580       * and sets the environment property {@link #CPE} = cpe.
581       * 
582       * @return a non null context for continuing the operation
583       * 
584       * @throws NamingException if the naming problems have occured
585       */
586      public static Context getContinuationContext (CannotProceedException cpe)
587        throws NamingException
588      {
589        Hashtable env = cpe.getEnvironment ();
590        if (env != null)
591          env.put (CPE, cpe);
592    
593        // TODO: Check if this implementation matches the API specification
594        try
595          {
596            Object obj = getObjectInstance (cpe.getResolvedObj(),
597                                            cpe.getAltName (),
598                                            cpe.getAltNameCtx (), 
599                                            env);
600            if (obj != null)
601              return (Context) obj;
602          }
603        catch (Exception _)
604          {
605          }
606    
607        // fix stack trace for re-thrown exception (message confusing otherwise)
608        cpe.fillInStackTrace();
609    
610        throw cpe;
611      }
612      
613      /**
614       * Get the object state for binding.
615       * 
616       * @param obj the object, for that the binding state must be retrieved. Cannot
617       *          be null.
618       * @param name the name of this object, related to the nameCtx. Can be null if
619       *          not specified.
620       * @param nameCtx the naming context, to that the object name is related. Can
621       *          be null if the name is related to the initial default context.
622       * @param environment the properties for creating the object state. Can be
623       *          null if no properties are provided.
624       * @return the object state for binding, may be null if no changes are
625       *         returned by the factory
626       * @throws NamingException
627       */ 
628      public static Object getStateToBind (Object obj, Name name,
629                                           Context nameCtx, Hashtable<?, ?> environment)
630        throws NamingException
631      {
632        StringTokenizer tokens = getPlusPath (Context.STATE_FACTORIES,
633                                              environment, nameCtx);
634        while (tokens.hasMoreTokens ())
635          {
636            String klassName = tokens.nextToken ();
637            try
638              {
639                Class k = Class.forName (klassName,
640                                         true,
641                                         Thread.currentThread().getContextClassLoader());
642                StateFactory factory = (StateFactory) k.newInstance ();
643                Object o = factory.getStateToBind (obj, name, nameCtx,
644                                                   environment);
645                if (o != null)
646                  return o;
647              }
648            catch (ClassNotFoundException _1)
649              {
650                // Ignore it.
651              }
652            catch (ClassCastException _2)
653              {
654                // This means that the class we found was not an
655                // ObjectFactory or that the factory returned something
656                // which was not a Context.
657              }
658            catch (InstantiationException _3)
659              {
660                // If we couldn't instantiate the factory we might get
661                // this.
662              }
663            catch (IllegalAccessException _4)
664              {
665                // Another possibility when instantiating.
666              }
667          }
668    
669        return obj;
670      }
671    }