1 /*
   2  * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.jndi.rmi.registry;
  27 
  28 
  29 import java.util.Hashtable;
  30 import java.util.Properties;
  31 import java.rmi.*;
  32 import java.rmi.server.*;
  33 import java.rmi.registry.Registry;
  34 import java.rmi.registry.LocateRegistry;
  35 
  36 import javax.naming.*;
  37 import javax.naming.spi.NamingManager;
  38 
  39 
  40 /**
  41  * A RegistryContext is a context representing a remote RMI registry.
  42  *
  43  * @author Scott Seligman
  44  */
  45 
  46 
  47 public class RegistryContext implements Context, Referenceable {
  48 
  49     private Hashtable environment;
  50     private Registry registry;
  51     private String host;
  52     private int port;
  53     private static final NameParser nameParser = new AtomicNameParser();
  54     private static final String SOCKET_FACTORY = "com.sun.jndi.rmi.factory.socket";
  55 
  56     Reference reference = null; // ref used to create this context, if any
  57 
  58     // Environment property that, if set, indicates that a security
  59     // manager should be installed (if none is already in place).
  60     public static final String SECURITY_MGR =
  61             "java.naming.rmi.security.manager";
  62 
  63     /**
  64      * Returns a context for the registry at a given host and port.
  65      * If "host" is null, uses default host.
  66      * If "port" is non-positive, uses default port.
  67      * Cloning of "env" is handled by caller; see comments within
  68      * RegistryContextFactory.getObjectInstance(), for example.
  69      */
  70     public RegistryContext(String host, int port, Hashtable env)
  71             throws NamingException
  72     {
  73         environment = ((env == null) ? new Hashtable(5) : env);
  74         if (environment.get(SECURITY_MGR) != null) {
  75             installSecurityMgr();
  76         }
  77 
  78         // chop off '[' and ']' in an IPv6 literal address
  79         if ((host != null) && (host.charAt(0) == '[')) {
  80             host = host.substring(1, host.length() - 1);
  81         }
  82 
  83         RMIClientSocketFactory socketFactory =
  84                 (RMIClientSocketFactory) environment.get(SOCKET_FACTORY);
  85         registry = getRegistry(host, port, socketFactory);
  86         this.host = host;
  87         this.port = port;
  88     }
  89 
  90     /**
  91      * Returns a clone of a registry context.  The context's private state
  92      * is independent of the original's (so closing one context, for example,
  93      * won't close the other).
  94      */
  95     // %%% Alternatively, this could be done with a clone() method.
  96     RegistryContext(RegistryContext ctx) {
  97         environment = (Hashtable)ctx.environment.clone();
  98         registry = ctx.registry;
  99         host = ctx.host;
 100         port = ctx.port;
 101         reference = ctx.reference;
 102     }
 103 
 104     protected void finalize() {
 105         close();
 106     }
 107 
 108     public Object lookup(Name name) throws NamingException {
 109         if (name.isEmpty()) {
 110             return (new RegistryContext(this));
 111         }
 112         Remote obj;
 113         try {
 114             obj = registry.lookup(name.get(0));
 115         } catch (NotBoundException e) {
 116             throw (new NameNotFoundException(name.get(0)));
 117         } catch (RemoteException e) {
 118             throw (NamingException)wrapRemoteException(e).fillInStackTrace();
 119         }
 120         return (decodeObject(obj, name.getPrefix(1)));
 121     }
 122 
 123     public Object lookup(String name) throws NamingException {
 124         return lookup(new CompositeName(name));
 125     }
 126 
 127     /**
 128      * If the object to be bound is both Remote and Referenceable, binds the
 129      * object itself, not its Reference.
 130      */
 131     public void bind(Name name, Object obj) throws NamingException {
 132         if (name.isEmpty()) {
 133             throw (new InvalidNameException(
 134                     "RegistryContext: Cannot bind empty name"));
 135         }
 136         try {
 137             registry.bind(name.get(0), encodeObject(obj, name.getPrefix(1)));
 138         } catch (AlreadyBoundException e) {
 139             NamingException ne = new NameAlreadyBoundException(name.get(0));
 140             ne.setRootCause(e);
 141             throw ne;
 142         } catch (RemoteException e) {
 143             throw (NamingException)wrapRemoteException(e).fillInStackTrace();
 144         }
 145     }
 146 
 147     public void bind(String name, Object obj) throws NamingException {
 148         bind(new CompositeName(name), obj);
 149     }
 150 
 151     public void rebind(Name name, Object obj) throws NamingException {
 152         if (name.isEmpty()) {
 153             throw (new InvalidNameException(
 154                     "RegistryContext: Cannot rebind empty name"));
 155         }
 156         try {
 157             registry.rebind(name.get(0), encodeObject(obj, name.getPrefix(1)));
 158         } catch (RemoteException e) {
 159             throw (NamingException)wrapRemoteException(e).fillInStackTrace();
 160         }
 161     }
 162 
 163     public void rebind(String name, Object obj) throws NamingException {
 164         rebind(new CompositeName(name), obj);
 165     }
 166 
 167     public void unbind(Name name) throws NamingException {
 168         if (name.isEmpty()) {
 169             throw (new InvalidNameException(
 170                     "RegistryContext: Cannot unbind empty name"));
 171         }
 172         try {
 173             registry.unbind(name.get(0));
 174         } catch (NotBoundException e) {
 175             // method is idempotent
 176         } catch (RemoteException e) {
 177             throw (NamingException)wrapRemoteException(e).fillInStackTrace();
 178         }
 179     }
 180 
 181     public void unbind(String name) throws NamingException {
 182         unbind(new CompositeName(name));
 183     }
 184 
 185     /**
 186      * Rename is implemented by this sequence of operations:
 187      * lookup, bind, unbind.  The sequence is not performed atomically.
 188      */
 189     public void rename(Name oldName, Name newName) throws NamingException {
 190         bind(newName, lookup(oldName));
 191         unbind(oldName);
 192     }
 193 
 194     public void rename(String name, String newName) throws NamingException {
 195         rename(new CompositeName(name), new CompositeName(newName));
 196     }
 197 
 198     public NamingEnumeration list(Name name)    throws NamingException {
 199         if (!name.isEmpty()) {
 200             throw (new InvalidNameException(
 201                     "RegistryContext: can only list \"\""));
 202         }
 203         try {
 204             String[] names = registry.list();
 205             return (new NameClassPairEnumeration(names));
 206         } catch (RemoteException e) {
 207             throw (NamingException)wrapRemoteException(e).fillInStackTrace();
 208         }
 209     }
 210 
 211     public NamingEnumeration list(String name) throws NamingException {
 212         return list(new CompositeName(name));
 213     }
 214 
 215     public NamingEnumeration listBindings(Name name)
 216             throws NamingException
 217     {
 218         if (!name.isEmpty()) {
 219             throw (new InvalidNameException(
 220                     "RegistryContext: can only list \"\""));
 221         }
 222         try {
 223             String[] names = registry.list();
 224             return (new BindingEnumeration(this, names));
 225         } catch (RemoteException e) {
 226             throw (NamingException)wrapRemoteException(e).fillInStackTrace();
 227         }
 228     }
 229 
 230     public NamingEnumeration listBindings(String name) throws NamingException {
 231         return listBindings(new CompositeName(name));
 232     }
 233 
 234     public void destroySubcontext(Name name) throws NamingException {
 235         throw (new OperationNotSupportedException());
 236     }
 237 
 238     public void destroySubcontext(String name) throws NamingException {
 239         throw (new OperationNotSupportedException());
 240     }
 241 
 242     public Context createSubcontext(Name name) throws NamingException {
 243         throw (new OperationNotSupportedException());
 244     }
 245 
 246     public Context createSubcontext(String name) throws NamingException {
 247         throw (new OperationNotSupportedException());
 248     }
 249 
 250     public Object lookupLink(Name name) throws NamingException {
 251         return lookup(name);
 252     }
 253 
 254     public Object lookupLink(String name) throws NamingException {
 255         return lookup(name);
 256     }
 257 
 258     public NameParser getNameParser(Name name) throws NamingException {
 259         return nameParser;
 260     }
 261 
 262     public NameParser getNameParser(String name) throws NamingException {
 263         return nameParser;
 264     }
 265 
 266     public Name composeName(Name name, Name prefix) throws NamingException {
 267         Name result = (Name)prefix.clone();
 268         return result.addAll(name);
 269     }
 270 
 271     public String composeName(String name, String prefix)
 272             throws NamingException
 273     {
 274         return composeName(new CompositeName(name),
 275                            new CompositeName(prefix)).toString();
 276     }
 277 
 278     public Object removeFromEnvironment(String propName)
 279             throws NamingException
 280     {
 281         return environment.remove(propName);
 282     }
 283 
 284     public Object addToEnvironment(String propName, Object propVal)
 285             throws NamingException
 286     {
 287         if (propName.equals(SECURITY_MGR)) {
 288             installSecurityMgr();
 289         }
 290         return environment.put(propName, propVal);
 291     }
 292 
 293     public Hashtable getEnvironment() throws NamingException {
 294         return (Hashtable)environment.clone();
 295     }
 296 
 297     public void close() {
 298         environment = null;
 299         registry = null;
 300         // &&& If we were caching registry connections, we would probably
 301         // uncache this one now.
 302     }
 303 
 304     public String getNameInNamespace() {
 305         return ""; // Registry has an empty name
 306     }
 307 
 308     /**
 309      * Returns an RMI registry reference for this context.
 310      *<p>
 311      * If this context was created from a reference, that reference is
 312      * returned.  Otherwise, an exception is thrown if the registry's
 313      * host is "localhost" or the default (null).  Although this could
 314      * possibly make for a valid reference, it's far more likely to be
 315      * an easily made error.
 316      *
 317      * @see RegistryContextFactory
 318      */
 319     public Reference getReference() throws NamingException {
 320         if (reference != null) {
 321             return (Reference)reference.clone();  // %%% clone the addrs too?
 322         }
 323         if (host == null || host.equals("localhost")) {
 324             throw (new ConfigurationException(
 325                     "Cannot create a reference for an RMI registry whose " +
 326                     "host was unspecified or specified as \"localhost\""));
 327         }
 328         String url = "rmi://";
 329 
 330         // Enclose IPv6 literal address in '[' and ']'
 331         url = (host.indexOf(":") > -1) ? url + "[" + host + "]" :
 332                                          url + host;
 333         if (port > 0) {
 334             url += ":" + Integer.toString(port);
 335         }
 336         RefAddr addr = new StringRefAddr(RegistryContextFactory.ADDRESS_TYPE,
 337                                          url);
 338         return (new Reference(RegistryContext.class.getName(),
 339                               addr,
 340                               RegistryContextFactory.class.getName(),
 341                               null));
 342     }
 343 
 344 
 345     /**
 346      * Wrap a RemoteException inside a NamingException.
 347      */
 348     public static NamingException wrapRemoteException(RemoteException re) {
 349 
 350         NamingException ne;
 351 
 352         if (re instanceof ConnectException) {
 353             ne = new ServiceUnavailableException();
 354 
 355         } else if (re instanceof AccessException) {
 356             ne = new NoPermissionException();
 357 
 358         } else if (re instanceof StubNotFoundException ||
 359                    re instanceof UnknownHostException ||
 360                    re instanceof SocketSecurityException) {
 361             ne = new ConfigurationException();
 362 
 363         } else if (re instanceof ExportException ||
 364                    re instanceof ConnectIOException ||
 365                    re instanceof MarshalException ||
 366                    re instanceof UnmarshalException ||
 367                    re instanceof NoSuchObjectException) {
 368             ne = new CommunicationException();
 369 
 370         } else if (re instanceof ServerException &&
 371                    re.detail instanceof RemoteException) {
 372             ne = wrapRemoteException((RemoteException)re.detail);
 373 
 374         } else {
 375             ne = new NamingException();
 376         }
 377         ne.setRootCause(re);
 378         return ne;
 379     }
 380 
 381     /**
 382      * Returns the registry at a given host, port and socket factory.
 383      * If "host" is null, uses default host.
 384      * If "port" is non-positive, uses default port.
 385      * If "socketFactory" is null, uses the default socket.
 386      */
 387     private static Registry getRegistry(String host, int port,
 388                 RMIClientSocketFactory socketFactory)
 389             throws NamingException
 390     {
 391         // %%% We could cache registry connections here.  The transport layer
 392         // may already reuse connections.
 393         try {
 394             if (socketFactory == null) {
 395                 return LocateRegistry.getRegistry(host, port);
 396             } else {
 397                 return LocateRegistry.getRegistry(host, port, socketFactory);
 398             }
 399         } catch (RemoteException e) {
 400             throw (NamingException)wrapRemoteException(e).fillInStackTrace();
 401         }
 402     }
 403 
 404     /**
 405      * Attempts to install a security manager if none is currently in
 406      * place.
 407      */
 408     private static void installSecurityMgr() {
 409 
 410         try {
 411             System.setSecurityManager(new RMISecurityManager());
 412         } catch (Exception e) {
 413         }
 414     }
 415 
 416     /**
 417      * Encodes an object prior to binding it in the registry.  First,
 418      * NamingManager.getStateToBind() is invoked.  If the resulting
 419      * object is Remote, it is returned.  If it is a Reference or
 420      * Referenceable, the reference is wrapped in a Remote object.
 421      * Otherwise, an exception is thrown.
 422      *
 423      * @param name      The object's name relative to this context.
 424      */
 425     private Remote encodeObject(Object obj, Name name)
 426             throws NamingException, RemoteException
 427     {
 428         obj = NamingManager.getStateToBind(obj, name, this, environment);
 429 
 430         if (obj instanceof Remote) {
 431             return (Remote)obj;
 432         }
 433         if (obj instanceof Reference) {
 434             return (new ReferenceWrapper((Reference)obj));
 435         }
 436         if (obj instanceof Referenceable) {
 437             return (new ReferenceWrapper(((Referenceable)obj).getReference()));
 438         }
 439         throw (new IllegalArgumentException(
 440                 "RegistryContext: " +
 441                 "object to bind must be Remote, Reference, or Referenceable"));
 442     }
 443 
 444     /**
 445      * Decodes an object that has been retrieved from the registry.
 446      * First, if the object is a RemoteReference, the Reference is
 447      * unwrapped.  Then, NamingManager.getObjectInstance() is invoked.
 448      *
 449      * @param name      The object's name relative to this context.
 450      */
 451     private Object decodeObject(Remote r, Name name) throws NamingException {
 452         try {
 453             Object obj = (r instanceof RemoteReference)
 454                         ? ((RemoteReference)r).getReference()
 455                         : (Object)r;
 456             return NamingManager.getObjectInstance(obj, name, this,
 457                                                    environment);
 458         } catch (NamingException e) {
 459             throw e;
 460         } catch (RemoteException e) {
 461             throw (NamingException)
 462                 wrapRemoteException(e).fillInStackTrace();
 463         } catch (Exception e) {
 464             NamingException ne = new NamingException();
 465             ne.setRootCause(e);
 466             throw ne;
 467         }
 468     }
 469 
 470 }
 471 
 472 
 473 /**
 474  * A name parser for case-sensitive atomic names.
 475  */
 476 class AtomicNameParser implements NameParser {
 477     private static final Properties syntax = new Properties();
 478 
 479     public Name parse(String name) throws NamingException {
 480         return (new CompoundName(name, syntax));
 481     }
 482 }
 483 
 484 
 485 /**
 486  * An enumeration of name / class-name pairs.  Since we don't know anything
 487  * about the classes, each class name is returned as the generic
 488  * "java.lang.Object".
 489  */
 490 class NameClassPairEnumeration implements NamingEnumeration {
 491     private final String[] names;
 492     private int nextName;       // index into "names"
 493 
 494     NameClassPairEnumeration(String[] names) {
 495         this.names = names;
 496         nextName = 0;
 497     }
 498 
 499     public boolean hasMore() {
 500         return (nextName < names.length);
 501     }
 502 
 503     public Object next() throws NamingException {
 504         if (!hasMore()) {
 505             throw (new java.util.NoSuchElementException());
 506         }
 507         // Convert name to a one-element composite name, so embedded
 508         // meta-characters are properly escaped.
 509         String name = names[nextName++];
 510         Name cname = (new CompositeName()).add(name);
 511         NameClassPair ncp = new NameClassPair(cname.toString(),
 512                                             "java.lang.Object");
 513         ncp.setNameInNamespace(name);
 514         return ncp;
 515     }
 516 
 517     public boolean hasMoreElements() {
 518         return hasMore();
 519     }
 520 
 521     public Object nextElement() {
 522         try {
 523             return next();
 524         } catch (NamingException e) {   // should never happen
 525             throw (new java.util.NoSuchElementException(
 526                     "javax.naming.NamingException was thrown"));
 527         }
 528     }
 529 
 530     public void close() {
 531         nextName = names.length;
 532     }
 533 }
 534 
 535 
 536 /**
 537  * An enumeration of Bindings.
 538  *
 539  * The actual registry lookups are performed when next() is called.  It would
 540  * be nicer to defer this until the object (or its class name) is actually
 541  * requested.  The problem with that approach is that Binding.getObject()
 542  * cannot throw NamingException.
 543  */
 544 class BindingEnumeration implements NamingEnumeration {
 545     private RegistryContext ctx;
 546     private final String[] names;
 547     private int nextName;       // index into "names"
 548 
 549     BindingEnumeration(RegistryContext ctx, String[] names) {
 550         // Clone ctx in case someone closes it before we're through.
 551         this.ctx = new RegistryContext(ctx);
 552         this.names = names;
 553         nextName = 0;
 554     }
 555 
 556     protected void finalize() {
 557         ctx.close();
 558     }
 559 
 560     public boolean hasMore() {
 561         if (nextName >= names.length) {
 562             ctx.close();
 563         }
 564         return (nextName < names.length);
 565     }
 566 
 567     public Object next() throws NamingException {
 568         if (!hasMore()) {
 569             throw (new java.util.NoSuchElementException());
 570         }
 571         // Convert name to a one-element composite name, so embedded
 572         // meta-characters are properly escaped.
 573         String name = names[nextName++];
 574         Name cname = (new CompositeName()).add(name);
 575 
 576         Object obj = ctx.lookup(cname);
 577         String cnameStr = cname.toString();
 578         Binding binding = new Binding(cnameStr, obj);
 579         binding.setNameInNamespace(cnameStr);
 580         return binding;
 581     }
 582 
 583     public boolean hasMoreElements() {
 584         return hasMore();
 585     }
 586 
 587     public Object nextElement() {
 588         try {
 589             return next();
 590         } catch (NamingException e) {
 591             throw (new java.util.NoSuchElementException(
 592                     "javax.naming.NamingException was thrown"));
 593         }
 594     }
 595 
 596     public void close () {
 597         finalize();
 598     }
 599 }