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 }