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 }