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