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