1 /*
   2  * Copyright (c) 1999, 2011, 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.toolkit.url;
  27 
  28 import javax.naming.*;
  29 import javax.naming.spi.ResolveResult;
  30 import javax.naming.spi.NamingManager;
  31 
  32 import java.util.Hashtable;
  33 import java.net.MalformedURLException;
  34 
  35 /**
  36  * This abstract class is a generic URL context that accepts as the
  37  * name argument either a string URL or a Name whose first component
  38  * is a URL. It resolves the URL to a target context and then continues
  39  * the operation using the remaining name in the target context as if
  40  * the first component names a junction.
  41  *
  42  * A subclass must define getRootURLContext()
  43  * to process the URL into head/tail pieces. If it wants to control how
  44  * URL strings are parsed and compared for the rename() operation, then
  45  * it should override getNonRootURLSuffixes() and urlEquals().
  46  *
  47  * @author Scott Seligman
  48  * @author Rosanna Lee
  49  */
  50 abstract public class GenericURLContext implements Context {
  51     protected Hashtable<String, Object> myEnv = null;
  52 
  53     @SuppressWarnings("unchecked") // Expect Hashtable<String, Object>
  54     public GenericURLContext(Hashtable<?,?> env) {
  55         // context that is not tied to any specific URL
  56         myEnv = (Hashtable<String, Object>)env;  // copied on write
  57     }
  58 
  59     public void close() throws NamingException {
  60         myEnv = null;
  61     }
  62 
  63     public String getNameInNamespace() throws NamingException {
  64         return ""; // %%% check this out: A URL context's name is ""
  65     }
  66 
  67     /**
  68       * Resolves 'name' into a target context with remaining name.
  69       * For example, with a JNDI URL "jndi://dnsname/rest_name",
  70       * this method resolves "jndi://dnsname/" to a target context,
  71       * and returns the target context with "rest_name".
  72       * The definition of "root URL" and how much of the URL to
  73       * consume is implementation specific.
  74       * If rename() is supported for a particular URL scheme,
  75       * getRootURLContext(), getURLPrefix(), and getURLSuffix()
  76       * must be in sync wrt how URLs are parsed and returned.
  77       */
  78     abstract protected ResolveResult getRootURLContext(String url,
  79         Hashtable<?,?> env) throws NamingException;
  80 
  81     /**
  82       * Returns the suffix of the url. The result should be identical to
  83       * that of calling getRootURLContext().getRemainingName(), but
  84       * without the overhead of doing anything with the prefix like
  85       * creating a context.
  86       *<p>
  87       * This method returns a Name instead of a String because to give
  88       * the provider an opportunity to return a Name (for example,
  89       * for weakly separated naming systems like COS naming).
  90       *<p>
  91       * The default implementation uses skips 'prefix', calls
  92       * UrlUtil.decode() on it, and returns the result as a single component
  93       * CompositeName.
  94       * Subclass should override if this is not appropriate.
  95       * This method is used only by rename().
  96       * If rename() is supported for a particular URL scheme,
  97       * getRootURLContext(), getURLPrefix(), and getURLSuffix()
  98       * must be in sync wrt how URLs are parsed and returned.
  99       *<p>
 100       * For many URL schemes, this method is very similar to URL.getFile(),
 101       * except getFile() will return a leading slash in the
 102       * 2nd, 3rd, and 4th cases. For schemes like "ldap" and "iiop",
 103       * the leading slash must be skipped before the name is an acceptable
 104       * format for operation by the Context methods. For schemes that treat the
 105       * leading slash as significant (such as "file"),
 106       * the subclass must override getURLSuffix() to get the correct behavior.
 107       * Remember, the behavior must match getRootURLContext().
 108       *
 109       * URL                                     Suffix
 110       * foo://host:port                         <empty string>
 111       * foo://host:port/rest/of/name            rest/of/name
 112       * foo:///rest/of/name                     rest/of/name
 113       * foo:/rest/of/name                       rest/of/name
 114       * foo:rest/of/name                        rest/of/name
 115       */
 116     protected Name getURLSuffix(String prefix, String url) throws NamingException {
 117         String suffix = url.substring(prefix.length());
 118         if (suffix.length() == 0) {
 119             return new CompositeName();
 120         }
 121 
 122         if (suffix.charAt(0) == '/') {
 123             suffix = suffix.substring(1); // skip leading slash
 124         }
 125 
 126         try {
 127             return new CompositeName().add(UrlUtil.decode(suffix));
 128         } catch (MalformedURLException e) {
 129             throw new InvalidNameException(e.getMessage());
 130         }
 131     }
 132 
 133     /**
 134       * Finds the prefix of a URL.
 135       * Default implementation looks for slashes and then extracts
 136       * prefixes using String.substring().
 137       * Subclass should override if this is not appropriate.
 138       * This method is used only by rename().
 139       * If rename() is supported for a particular URL scheme,
 140       * getRootURLContext(), getURLPrefix(), and getURLSuffix()
 141       * must be in sync wrt how URLs are parsed and returned.
 142       *<p>
 143       * URL                                     Prefix
 144       * foo://host:port                         foo://host:port
 145       * foo://host:port/rest/of/name            foo://host:port
 146       * foo:///rest/of/name                     foo://
 147       * foo:/rest/of/name                       foo:
 148       * foo:rest/of/name                        foo:
 149       */
 150     protected String getURLPrefix(String url) throws NamingException {
 151         int start = url.indexOf(":");
 152 
 153         if (start < 0) {
 154             throw new OperationNotSupportedException("Invalid URL: " + url);
 155         }
 156         ++start; // skip ':'
 157 
 158         if (url.startsWith("//", start)) {
 159             start += 2;  // skip double slash
 160 
 161             // find last slash
 162             int posn = url.indexOf("/", start);
 163             if (posn >= 0) {
 164                 start = posn;
 165             } else {
 166                 start = url.length();  // rest of URL
 167             }
 168         }
 169 
 170         // else 0 or 1 iniitial slashes; start is unchanged
 171         return url.substring(0, start);
 172     }
 173 
 174     /**
 175      * Determines whether two URLs are the same.
 176      * Default implementation uses String.equals().
 177      * Subclass should override if this is not appropriate.
 178      * This method is used by rename().
 179      */
 180     protected boolean urlEquals(String url1, String url2) {
 181         return url1.equals(url2);
 182     }
 183 
 184     /**
 185      * Gets the context in which to continue the operation. This method
 186      * is called when this context is asked to process a multicomponent
 187      * Name in which the first component is a URL.
 188      * Treat the first component like a junction: resolve it and then use
 189      * NamingManager.getContinuationContext() to get the target context in
 190      * which to operate on the remainder of the name (n.getSuffix(1)).
 191      */
 192     protected Context getContinuationContext(Name n) throws NamingException {
 193         Object obj = lookup(n.get(0));
 194         CannotProceedException cpe = new CannotProceedException();
 195         cpe.setResolvedObj(obj);
 196         cpe.setEnvironment(myEnv);
 197         return NamingManager.getContinuationContext(cpe);
 198     }
 199 
 200     public Object lookup(String name) throws NamingException {
 201         ResolveResult res = getRootURLContext(name, myEnv);
 202         Context ctx = (Context)res.getResolvedObj();
 203         try {
 204             return ctx.lookup(res.getRemainingName());
 205         } finally {
 206             ctx.close();
 207         }
 208     }
 209 
 210     public Object lookup(Name name) throws NamingException {
 211         if (name.size() == 1) {
 212             return lookup(name.get(0));
 213         } else {
 214             Context ctx = getContinuationContext(name);
 215             try {
 216                 return ctx.lookup(name.getSuffix(1));
 217             } finally {
 218                 ctx.close();
 219             }
 220         }
 221     }
 222 
 223     public void bind(String name, Object obj) throws NamingException {
 224         ResolveResult res = getRootURLContext(name, myEnv);
 225         Context ctx = (Context)res.getResolvedObj();
 226         try {
 227             ctx.bind(res.getRemainingName(), obj);
 228         } finally {
 229             ctx.close();
 230         }
 231     }
 232 
 233     public void bind(Name name, Object obj) throws NamingException {
 234         if (name.size() == 1) {
 235             bind(name.get(0), obj);
 236         } else {
 237             Context ctx = getContinuationContext(name);
 238             try {
 239                 ctx.bind(name.getSuffix(1), obj);
 240             } finally {
 241                 ctx.close();
 242             }
 243         }
 244     }
 245 
 246     public void rebind(String name, Object obj) throws NamingException {
 247         ResolveResult res = getRootURLContext(name, myEnv);
 248         Context ctx = (Context)res.getResolvedObj();
 249         try {
 250             ctx.rebind(res.getRemainingName(), obj);
 251         } finally {
 252             ctx.close();
 253         }
 254     }
 255 
 256     public void rebind(Name name, Object obj) throws NamingException {
 257         if (name.size() == 1) {
 258             rebind(name.get(0), obj);
 259         } else {
 260             Context ctx = getContinuationContext(name);
 261             try {
 262                 ctx.rebind(name.getSuffix(1), obj);
 263             } finally {
 264                 ctx.close();
 265             }
 266         }
 267     }
 268 
 269     public void unbind(String name) throws NamingException {
 270         ResolveResult res = getRootURLContext(name, myEnv);
 271         Context ctx = (Context)res.getResolvedObj();
 272         try {
 273             ctx.unbind(res.getRemainingName());
 274         } finally {
 275             ctx.close();
 276         }
 277     }
 278 
 279     public void unbind(Name name) throws NamingException {
 280         if (name.size() == 1) {
 281             unbind(name.get(0));
 282         } else {
 283             Context ctx = getContinuationContext(name);
 284             try {
 285                 ctx.unbind(name.getSuffix(1));
 286             } finally {
 287                 ctx.close();
 288             }
 289         }
 290     }
 291 
 292     public void rename(String oldName, String newName) throws NamingException {
 293         String oldPrefix = getURLPrefix(oldName);
 294         String newPrefix = getURLPrefix(newName);
 295         if (!urlEquals(oldPrefix, newPrefix)) {
 296             throw new OperationNotSupportedException(
 297                 "Renaming using different URL prefixes not supported : " +
 298                 oldName + " " + newName);
 299         }
 300 
 301         ResolveResult res = getRootURLContext(oldName, myEnv);
 302         Context ctx = (Context)res.getResolvedObj();
 303         try {
 304             ctx.rename(res.getRemainingName(), getURLSuffix(newPrefix, newName));
 305         } finally {
 306             ctx.close();
 307         }
 308     }
 309 
 310     public void rename(Name name, Name newName) throws NamingException {
 311         if (name.size() == 1) {
 312             if (newName.size() != 1) {
 313                 throw new OperationNotSupportedException(
 314             "Renaming to a Name with more components not supported: " + newName);
 315             }
 316             rename(name.get(0), newName.get(0));
 317         } else {
 318             // > 1 component with 1st one being URL
 319             // URLs must be identical; cannot deal with diff URLs
 320             if (!urlEquals(name.get(0), newName.get(0))) {
 321                 throw new OperationNotSupportedException(
 322                     "Renaming using different URLs as first components not supported: " +
 323                     name + " " + newName);
 324             }
 325 
 326             Context ctx = getContinuationContext(name);
 327             try {
 328                 ctx.rename(name.getSuffix(1), newName.getSuffix(1));
 329             } finally {
 330                 ctx.close();
 331             }
 332         }
 333     }
 334 
 335     public NamingEnumeration<NameClassPair> list(String name)   throws NamingException {
 336         ResolveResult res = getRootURLContext(name, myEnv);
 337         Context ctx = (Context)res.getResolvedObj();
 338         try {
 339             return ctx.list(res.getRemainingName());
 340         } finally {
 341             ctx.close();
 342         }
 343     }
 344 
 345     public NamingEnumeration<NameClassPair> list(Name name) throws NamingException {
 346         if (name.size() == 1) {
 347             return list(name.get(0));
 348         } else {
 349             Context ctx = getContinuationContext(name);
 350             try {
 351                 return ctx.list(name.getSuffix(1));
 352             } finally {
 353                 ctx.close();
 354             }
 355         }
 356     }
 357 
 358     public NamingEnumeration<Binding> listBindings(String name)
 359         throws NamingException {
 360         ResolveResult res = getRootURLContext(name, myEnv);
 361         Context ctx = (Context)res.getResolvedObj();
 362         try {
 363             return ctx.listBindings(res.getRemainingName());
 364         } finally {
 365             ctx.close();
 366         }
 367     }
 368 
 369     public NamingEnumeration<Binding> listBindings(Name name) throws NamingException {
 370         if (name.size() == 1) {
 371             return listBindings(name.get(0));
 372         } else {
 373             Context ctx = getContinuationContext(name);
 374             try {
 375                 return ctx.listBindings(name.getSuffix(1));
 376             } finally {
 377                 ctx.close();
 378             }
 379         }
 380     }
 381 
 382     public void destroySubcontext(String name) throws NamingException {
 383         ResolveResult res = getRootURLContext(name, myEnv);
 384         Context ctx = (Context)res.getResolvedObj();
 385         try {
 386             ctx.destroySubcontext(res.getRemainingName());
 387         } finally {
 388             ctx.close();
 389         }
 390     }
 391 
 392     public void destroySubcontext(Name name) throws NamingException {
 393         if (name.size() == 1) {
 394             destroySubcontext(name.get(0));
 395         } else {
 396             Context ctx = getContinuationContext(name);
 397             try {
 398                 ctx.destroySubcontext(name.getSuffix(1));
 399             } finally {
 400                 ctx.close();
 401             }
 402         }
 403     }
 404 
 405     public Context createSubcontext(String name) throws NamingException {
 406         ResolveResult res = getRootURLContext(name, myEnv);
 407         Context ctx = (Context)res.getResolvedObj();
 408         try {
 409             return ctx.createSubcontext(res.getRemainingName());
 410         } finally {
 411             ctx.close();
 412         }
 413     }
 414 
 415     public Context createSubcontext(Name name) throws NamingException {
 416         if (name.size() == 1) {
 417             return createSubcontext(name.get(0));
 418         } else {
 419             Context ctx = getContinuationContext(name);
 420             try {
 421                 return ctx.createSubcontext(name.getSuffix(1));
 422             } finally {
 423                 ctx.close();
 424             }
 425         }
 426     }
 427 
 428     public Object lookupLink(String name) throws NamingException {
 429         ResolveResult res = getRootURLContext(name, myEnv);
 430         Context ctx = (Context)res.getResolvedObj();
 431         try {
 432             return ctx.lookupLink(res.getRemainingName());
 433         } finally {
 434             ctx.close();
 435         }
 436     }
 437 
 438     public Object lookupLink(Name name) throws NamingException {
 439         if (name.size() == 1) {
 440             return lookupLink(name.get(0));
 441         } else {
 442             Context ctx = getContinuationContext(name);
 443             try {
 444                 return ctx.lookupLink(name.getSuffix(1));
 445             } finally {
 446                 ctx.close();
 447             }
 448         }
 449     }
 450 
 451     public NameParser getNameParser(String name) throws NamingException {
 452         ResolveResult res = getRootURLContext(name, myEnv);
 453         Context ctx = (Context)res.getResolvedObj();
 454         try {
 455             return ctx.getNameParser(res.getRemainingName());
 456         } finally {
 457             ctx.close();
 458         }
 459     }
 460 
 461     public NameParser getNameParser(Name name) throws NamingException {
 462         if (name.size() == 1) {
 463             return getNameParser(name.get(0));
 464         } else {
 465             Context ctx = getContinuationContext(name);
 466             try {
 467                 return ctx.getNameParser(name.getSuffix(1));
 468             } finally {
 469                 ctx.close();
 470             }
 471         }
 472     }
 473 
 474     public String composeName(String name, String prefix)
 475         throws NamingException {
 476             if (prefix.equals("")) {
 477                 return name;
 478             } else if (name.equals("")) {
 479                 return prefix;
 480             } else {
 481                 return (prefix + "/" + name);
 482             }
 483     }
 484 
 485     public Name composeName(Name name, Name prefix) throws NamingException {
 486         Name result = (Name)prefix.clone();
 487         result.addAll(name);
 488         return result;
 489     }
 490 
 491     @SuppressWarnings("unchecked") // clone()
 492     public Object removeFromEnvironment(String propName)
 493         throws NamingException {
 494             if (myEnv == null) {
 495                 return null;
 496             }
 497             myEnv = (Hashtable<String, Object>)myEnv.clone();
 498             return myEnv.remove(propName);
 499     }
 500 
 501     @SuppressWarnings("unchecked") // clone()
 502     public Object addToEnvironment(String propName, Object propVal)
 503         throws NamingException {
 504             myEnv = (myEnv == null)
 505                     ? new Hashtable<String, Object>(11, 0.75f)
 506                     : (Hashtable<String, Object>)myEnv.clone();
 507             return myEnv.put(propName, propVal);
 508     }
 509 
 510     @SuppressWarnings("unchecked") // clone()
 511     public Hashtable<String, Object> getEnvironment() throws NamingException {
 512         if (myEnv == null) {
 513             return new Hashtable<>(5, 0.75f);
 514         } else {
 515             return (Hashtable<String, Object>)myEnv.clone();
 516         }
 517     }
 518 
 519 /*
 520 // To test, declare getURLPrefix and getURLSuffix static.
 521 
 522     public static void main(String[] args) throws Exception {
 523         String[] tests = {"file://host:port",
 524                           "file:///rest/of/name",
 525                           "file://host:port/rest/of/name",
 526                           "file:/rest/of/name",
 527                           "file:rest/of/name"};
 528         for (int i = 0; i < tests.length; i++) {
 529             String pre = getURLPrefix(tests[i]);
 530             System.out.println(pre);
 531             System.out.println(getURLSuffix(pre, tests[i]));
 532         }
 533     }
 534 */
 535 }