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       * URL                                     Suffix
 111       * foo://host:port                         <empty string>
 112       * foo://host:port/rest/of/name            rest/of/name
 113       * foo:///rest/of/name                     rest/of/name
 114       * foo:/rest/of/name                       rest/of/name
 115       * foo:rest/of/name                        rest/of/name
 116       */
 117     protected Name getURLSuffix(String prefix, String url) throws NamingException {
 118         String suffix = url.substring(prefix.length());
 119         if (suffix.length() == 0) {
 120             return new CompositeName();
 121         }
 122 
 123         if (suffix.charAt(0) == '/') {
 124             suffix = suffix.substring(1); // skip leading slash
 125         }
 126 
 127         try {
 128             return new CompositeName().add(UrlUtil.decode(suffix));
 129         } catch (MalformedURLException e) {
 130             throw new InvalidNameException(e.getMessage());
 131         }
 132     }
 133 
 134     /**
 135       * Finds the prefix of a URL.
 136       * Default implementation looks for slashes and then extracts
 137       * prefixes using String.substring().
 138       * Subclass should override if this is not appropriate.
 139       * This method is used only by rename().
 140       * If rename() is supported for a particular URL scheme,
 141       * getRootURLContext(), getURLPrefix(), and getURLSuffix()
 142       * must be in sync wrt how URLs are parsed and returned.
 143       *<p>
 144       * URL                                     Prefix
 145       * foo://host:port                         foo://host:port
 146       * foo://host:port/rest/of/name            foo://host:port
 147       * foo:///rest/of/name                     foo://
 148       * foo:/rest/of/name                       foo:
 149       * foo:rest/of/name                        foo:
 150       */
 151     protected String getURLPrefix(String url) throws NamingException {
 152         int start = url.indexOf(':');
 153 
 154         if (start < 0) {
 155             throw new OperationNotSupportedException("Invalid URL: " + url);
 156         }
 157         ++start; // skip ':'
 158 
 159         if (url.startsWith("//", start)) {
 160             start += 2;  // skip double slash
 161 
 162             // find last slash
 163             int posn = url.indexOf('/', start);
 164             if (posn >= 0) {
 165                 start = posn;
 166             } else {
 167                 start = url.length();  // rest of URL
 168             }
 169         }
 170 
 171         // else 0 or 1 initial slashes; start is unchanged
 172         return url.substring(0, start);
 173     }
 174 
 175     /**
 176      * Determines whether two URLs are the same.
 177      * Default implementation uses String.equals().
 178      * Subclass should override if this is not appropriate.
 179      * This method is used by rename().
 180      */
 181     protected boolean urlEquals(String url1, String url2) {
 182         return url1.equals(url2);
 183     }
 184 
 185     /**
 186      * Gets the context in which to continue the operation. This method
 187      * is called when this context is asked to process a multicomponent
 188      * Name in which the first component is a URL.
 189      * Treat the first component like a junction: resolve it and then use
 190      * NamingManager.getContinuationContext() to get the target context in
 191      * which to operate on the remainder of the name (n.getSuffix(1)).
 192      */
 193     protected Context getContinuationContext(Name n) throws NamingException {
 194         Object obj = lookup(n.get(0));
 195         CannotProceedException cpe = new CannotProceedException();
 196         cpe.setResolvedObj(obj);
 197         cpe.setEnvironment(myEnv);
 198         return NamingManager.getContinuationContext(cpe);
 199     }
 200 
 201     public Object lookup(String name) throws NamingException {
 202         ResolveResult res = getRootURLContext(name, myEnv);
 203         Context ctx = (Context)res.getResolvedObj();
 204         try {
 205             return ctx.lookup(res.getRemainingName());
 206         } finally {
 207             ctx.close();
 208         }
 209     }
 210 
 211     public Object lookup(Name name) throws NamingException {
 212         if (name.size() == 1) {
 213             return lookup(name.get(0));
 214         } else {
 215             Context ctx = getContinuationContext(name);
 216             try {
 217                 return ctx.lookup(name.getSuffix(1));
 218             } finally {
 219                 ctx.close();
 220             }
 221         }
 222     }
 223 
 224     public void bind(String name, Object obj) throws NamingException {
 225         ResolveResult res = getRootURLContext(name, myEnv);
 226         Context ctx = (Context)res.getResolvedObj();
 227         try {
 228             ctx.bind(res.getRemainingName(), obj);
 229         } finally {
 230             ctx.close();
 231         }
 232     }
 233 
 234     public void bind(Name name, Object obj) throws NamingException {
 235         if (name.size() == 1) {
 236             bind(name.get(0), obj);
 237         } else {
 238             Context ctx = getContinuationContext(name);
 239             try {
 240                 ctx.bind(name.getSuffix(1), obj);
 241             } finally {
 242                 ctx.close();
 243             }
 244         }
 245     }
 246 
 247     public void rebind(String name, Object obj) throws NamingException {
 248         ResolveResult res = getRootURLContext(name, myEnv);
 249         Context ctx = (Context)res.getResolvedObj();
 250         try {
 251             ctx.rebind(res.getRemainingName(), obj);
 252         } finally {
 253             ctx.close();
 254         }
 255     }
 256 
 257     public void rebind(Name name, Object obj) throws NamingException {
 258         if (name.size() == 1) {
 259             rebind(name.get(0), obj);
 260         } else {
 261             Context ctx = getContinuationContext(name);
 262             try {
 263                 ctx.rebind(name.getSuffix(1), obj);
 264             } finally {
 265                 ctx.close();
 266             }
 267         }
 268     }
 269 
 270     public void unbind(String name) throws NamingException {
 271         ResolveResult res = getRootURLContext(name, myEnv);
 272         Context ctx = (Context)res.getResolvedObj();
 273         try {
 274             ctx.unbind(res.getRemainingName());
 275         } finally {
 276             ctx.close();
 277         }
 278     }
 279 
 280     public void unbind(Name name) throws NamingException {
 281         if (name.size() == 1) {
 282             unbind(name.get(0));
 283         } else {
 284             Context ctx = getContinuationContext(name);
 285             try {
 286                 ctx.unbind(name.getSuffix(1));
 287             } finally {
 288                 ctx.close();
 289             }
 290         }
 291     }
 292 
 293     public void rename(String oldName, String newName) throws NamingException {
 294         String oldPrefix = getURLPrefix(oldName);
 295         String newPrefix = getURLPrefix(newName);
 296         if (!urlEquals(oldPrefix, newPrefix)) {
 297             throw new OperationNotSupportedException(
 298                 "Renaming using different URL prefixes not supported : " +
 299                 oldName + " " + newName);
 300         }
 301 
 302         ResolveResult res = getRootURLContext(oldName, myEnv);
 303         Context ctx = (Context)res.getResolvedObj();
 304         try {
 305             ctx.rename(res.getRemainingName(), getURLSuffix(newPrefix, newName));
 306         } finally {
 307             ctx.close();
 308         }
 309     }
 310 
 311     public void rename(Name name, Name newName) throws NamingException {
 312         if (name.size() == 1) {
 313             if (newName.size() != 1) {
 314                 throw new OperationNotSupportedException(
 315             "Renaming to a Name with more components not supported: " + newName);
 316             }
 317             rename(name.get(0), newName.get(0));
 318         } else {
 319             // > 1 component with 1st one being URL
 320             // URLs must be identical; cannot deal with diff URLs
 321             if (!urlEquals(name.get(0), newName.get(0))) {
 322                 throw new OperationNotSupportedException(
 323                     "Renaming using different URLs as first components not supported: " +
 324                     name + " " + newName);
 325             }
 326 
 327             Context ctx = getContinuationContext(name);
 328             try {
 329                 ctx.rename(name.getSuffix(1), newName.getSuffix(1));
 330             } finally {
 331                 ctx.close();
 332             }
 333         }
 334     }
 335 
 336     public NamingEnumeration<NameClassPair> list(String name)   throws NamingException {
 337         ResolveResult res = getRootURLContext(name, myEnv);
 338         Context ctx = (Context)res.getResolvedObj();
 339         try {
 340             return ctx.list(res.getRemainingName());
 341         } finally {
 342             ctx.close();
 343         }
 344     }
 345 
 346     public NamingEnumeration<NameClassPair> list(Name name) throws NamingException {
 347         if (name.size() == 1) {
 348             return list(name.get(0));
 349         } else {
 350             Context ctx = getContinuationContext(name);
 351             try {
 352                 return ctx.list(name.getSuffix(1));
 353             } finally {
 354                 ctx.close();
 355             }
 356         }
 357     }
 358 
 359     public NamingEnumeration<Binding> listBindings(String name)
 360         throws NamingException {
 361         ResolveResult res = getRootURLContext(name, myEnv);
 362         Context ctx = (Context)res.getResolvedObj();
 363         try {
 364             return ctx.listBindings(res.getRemainingName());
 365         } finally {
 366             ctx.close();
 367         }
 368     }
 369 
 370     public NamingEnumeration<Binding> listBindings(Name name) throws NamingException {
 371         if (name.size() == 1) {
 372             return listBindings(name.get(0));
 373         } else {
 374             Context ctx = getContinuationContext(name);
 375             try {
 376                 return ctx.listBindings(name.getSuffix(1));
 377             } finally {
 378                 ctx.close();
 379             }
 380         }
 381     }
 382 
 383     public void destroySubcontext(String name) throws NamingException {
 384         ResolveResult res = getRootURLContext(name, myEnv);
 385         Context ctx = (Context)res.getResolvedObj();
 386         try {
 387             ctx.destroySubcontext(res.getRemainingName());
 388         } finally {
 389             ctx.close();
 390         }
 391     }
 392 
 393     public void destroySubcontext(Name name) throws NamingException {
 394         if (name.size() == 1) {
 395             destroySubcontext(name.get(0));
 396         } else {
 397             Context ctx = getContinuationContext(name);
 398             try {
 399                 ctx.destroySubcontext(name.getSuffix(1));
 400             } finally {
 401                 ctx.close();
 402             }
 403         }
 404     }
 405 
 406     public Context createSubcontext(String name) throws NamingException {
 407         ResolveResult res = getRootURLContext(name, myEnv);
 408         Context ctx = (Context)res.getResolvedObj();
 409         try {
 410             return ctx.createSubcontext(res.getRemainingName());
 411         } finally {
 412             ctx.close();
 413         }
 414     }
 415 
 416     public Context createSubcontext(Name name) throws NamingException {
 417         if (name.size() == 1) {
 418             return createSubcontext(name.get(0));
 419         } else {
 420             Context ctx = getContinuationContext(name);
 421             try {
 422                 return ctx.createSubcontext(name.getSuffix(1));
 423             } finally {
 424                 ctx.close();
 425             }
 426         }
 427     }
 428 
 429     public Object lookupLink(String name) throws NamingException {
 430         ResolveResult res = getRootURLContext(name, myEnv);
 431         Context ctx = (Context)res.getResolvedObj();
 432         try {
 433             return ctx.lookupLink(res.getRemainingName());
 434         } finally {
 435             ctx.close();
 436         }
 437     }
 438 
 439     public Object lookupLink(Name name) throws NamingException {
 440         if (name.size() == 1) {
 441             return lookupLink(name.get(0));
 442         } else {
 443             Context ctx = getContinuationContext(name);
 444             try {
 445                 return ctx.lookupLink(name.getSuffix(1));
 446             } finally {
 447                 ctx.close();
 448             }
 449         }
 450     }
 451 
 452     public NameParser getNameParser(String name) throws NamingException {
 453         ResolveResult res = getRootURLContext(name, myEnv);
 454         Context ctx = (Context)res.getResolvedObj();
 455         try {
 456             return ctx.getNameParser(res.getRemainingName());
 457         } finally {
 458             ctx.close();
 459         }
 460     }
 461 
 462     public NameParser getNameParser(Name name) throws NamingException {
 463         if (name.size() == 1) {
 464             return getNameParser(name.get(0));
 465         } else {
 466             Context ctx = getContinuationContext(name);
 467             try {
 468                 return ctx.getNameParser(name.getSuffix(1));
 469             } finally {
 470                 ctx.close();
 471             }
 472         }
 473     }
 474 
 475     public String composeName(String name, String prefix)
 476         throws NamingException {
 477             if (prefix.equals("")) {
 478                 return name;
 479             } else if (name.equals("")) {
 480                 return prefix;
 481             } else {
 482                 return (prefix + "/" + name);
 483             }
 484     }
 485 
 486     public Name composeName(Name name, Name prefix) throws NamingException {
 487         Name result = (Name)prefix.clone();
 488         result.addAll(name);
 489         return result;
 490     }
 491 
 492     public Object removeFromEnvironment(String propName)
 493         throws NamingException {
 494             if (myEnv == null) {
 495                 return null;
 496             }
 497             return myEnv.remove(propName);
 498     }
 499 
 500     public Object addToEnvironment(String propName, Object propVal)
 501         throws NamingException {
 502             if (myEnv == null) {
 503                 myEnv = new Hashtable<String, Object>(11, 0.75f);
 504             }
 505             return myEnv.put(propName, propVal);
 506     }
 507 
 508     @SuppressWarnings("unchecked") // clone()
 509     public Hashtable<String, Object> getEnvironment() throws NamingException {
 510         if (myEnv == null) {
 511             return new Hashtable<>(5, 0.75f);
 512         } else {
 513             return (Hashtable<String, Object>)myEnv.clone();
 514         }
 515     }
 516 
 517 /*
 518 // To test, declare getURLPrefix and getURLSuffix static.
 519 
 520     public static void main(String[] args) throws Exception {
 521         String[] tests = {"file://host:port",
 522                           "file:///rest/of/name",
 523                           "file://host:port/rest/of/name",
 524                           "file:/rest/of/name",
 525                           "file:rest/of/name"};
 526         for (int i = 0; i < tests.length; i++) {
 527             String pre = getURLPrefix(tests[i]);
 528             System.out.println(pre);
 529             System.out.println(getURLSuffix(pre, tests[i]));
 530         }
 531     }
 532 */
 533 }