001/* NamingManager.java -- Creates contexts and objects
002   Copyright (C) 2000, 2001, 2002, 2003, 2004,
003   2006 Free Software Foundation, Inc.
004
005This file is part of GNU Classpath.
006
007GNU Classpath is free software; you can redistribute it and/or modify
008it under the terms of the GNU General Public License as published by
009the Free Software Foundation; either version 2, or (at your option)
010any later version.
011
012GNU Classpath is distributed in the hope that it will be useful, but
013WITHOUT ANY WARRANTY; without even the implied warranty of
014MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015General Public License for more details.
016
017You should have received a copy of the GNU General Public License
018along with GNU Classpath; see the file COPYING.  If not, write to the
019Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02002110-1301 USA.
021
022Linking this library statically or dynamically with other modules is
023making a combined work based on this library.  Thus, the terms and
024conditions of the GNU General Public License cover the whole
025combination.
026
027As a special exception, the copyright holders of this library give you
028permission to link this library with independent modules to produce an
029executable, regardless of the license terms of these independent
030modules, and to copy and distribute the resulting executable under
031terms of your choice, provided that you also meet, for each linked
032independent module, the terms and conditions of the license of that
033module.  An independent module is a module which is not derived from
034or based on this library.  If you modify this library, you may extend
035this exception to your version of the library, but you are not
036obligated to do so.  If you do not wish to do so, delete this
037exception statement from your version. */
038
039
040package javax.naming.spi;
041
042import gnu.classpath.VMStackWalker;
043
044import gnu.java.lang.CPStringBuilder;
045
046import java.util.Enumeration;
047import java.util.Hashtable;
048import java.util.StringTokenizer;
049
050import javax.naming.CannotProceedException;
051import javax.naming.Context;
052import javax.naming.Name;
053import javax.naming.NamingException;
054import javax.naming.NoInitialContextException;
055import javax.naming.RefAddr;
056import javax.naming.Reference;
057import javax.naming.Referenceable;
058import 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 */
066public 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}