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