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>
GenericURLContext(Hashtable<?,?> env)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 
close()60     public void close() throws NamingException {
61         myEnv = null;
62     }
63 
getNameInNamespace()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       */
getRootURLContext(String url, Hashtable<?,?> env)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       */
getURLSuffix(String prefix, String url)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       */
getURLPrefix(String url)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      */
urlEquals(String url1, String url2)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      */
getContinuationContext(Name n)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 
lookup(String name)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 
lookup(Name name)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 
bind(String name, Object obj)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 
bind(Name name, Object obj)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 
rebind(String name, Object obj)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 
rebind(Name name, Object obj)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 
unbind(String name)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 
unbind(Name name)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 
rename(String oldName, String newName)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 
rename(Name name, Name newName)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 
list(String name)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 
list(Name name)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 
listBindings(String name)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 
listBindings(Name name)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 
destroySubcontext(String name)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 
destroySubcontext(Name name)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 
createSubcontext(String name)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 
createSubcontext(Name name)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 
lookupLink(String name)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 
lookupLink(Name name)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 
getNameParser(String name)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 
getNameParser(Name name)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 
composeName(String name, String prefix)477     public String composeName(String name, String prefix)
478         throws NamingException {
479             if (prefix.isEmpty()) {
480                 return name;
481             } else if (name.isEmpty()) {
482                 return prefix;
483             } else {
484                 return (prefix + "/" + name);
485             }
486     }
487 
composeName(Name name, Name prefix)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 
removeFromEnvironment(String propName)494     public Object removeFromEnvironment(String propName)
495         throws NamingException {
496             if (myEnv == null) {
497                 return null;
498             }
499             return myEnv.remove(propName);
500     }
501 
addToEnvironment(String propName, Object propVal)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()
getEnvironment()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 }
536