001: /*
002: * Copyright 1999-2004 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.naming.modules.fs;
018:
019: import java.io.File;
020: import java.io.FileOutputStream;
021: import java.io.IOException;
022: import java.io.InputStream;
023: import java.util.Arrays;
024: import java.util.Hashtable;
025: import java.util.Vector;
026:
027: import javax.naming.Name;
028: import javax.naming.NameAlreadyBoundException;
029: import javax.naming.NamingEnumeration;
030: import javax.naming.NamingException;
031: import javax.naming.directory.Attributes;
032: import javax.naming.directory.DirContext;
033:
034: import org.apache.naming.core.BaseDirContext;
035: import org.apache.naming.core.NamingContextEnumeration;
036: import org.apache.naming.core.NamingEntry;
037: import org.apache.tomcat.util.res.StringManager;
038:
039: /**
040: * DirContext for a filesystem directory.
041: *
042: * The 'bind' operation will accept an InputStream ( TODO: File, any
043: * resource with content )
044: * and create the file. ( TODO: what attributes can we support ? )
045: *
046: * The lookup operation will return a FileDirContext or a File.
047: *
048: * Supported attributes: (TODO: lastModified, size, ...)
049: *
050: * Note that JNDI allows memory-efficient style, without having one wrapper
051: * object for each real resource.
052: *
053: * @author Remy Maucherat
054: * @author Costin Manolache
055: */
056: public class FileDirContext extends BaseDirContext {
057:
058: private static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
059: .getLog(FileDirContext.class);
060:
061: // -------------------------------------------------------------- Constants
062:
063: protected StringManager sm = StringManager
064: .getManager("org.apache.naming.res");
065:
066: protected static final int BUFFER_SIZE = 2048;
067:
068: // ----------------------------------------------------------- Constructors
069:
070: /**
071: * Builds a file directory context using the given environment.
072: */
073: public FileDirContext() {
074: super ();
075: }
076:
077: /**
078: * Builds a file directory context using the given environment.
079: */
080: public FileDirContext(Hashtable env) {
081: super (env);
082: }
083:
084: // ----------------------------------------------------- Instance Variables
085:
086: /**
087: * The document base directory.
088: */
089: protected File base = null;
090:
091: /**
092: * Absolute normalized filename of the base.
093: */
094: protected String absoluteBase = null;
095:
096: /**
097: * Case sensitivity.
098: */
099: protected boolean caseSensitive = true;
100:
101: /**
102: * The document base path.
103: */
104: protected String docBase = null;
105:
106: // ------------------------------------------------------------- Properties
107:
108: /**
109: * Set the document root.
110: *
111: * @param docBase The new document root
112: *
113: * @exception IllegalArgumentException if the specified value is not
114: * supported by this implementation
115: * @exception IllegalArgumentException if this would create a
116: * malformed URL
117: */
118: public void setDocBase(String docBase) {
119:
120: // Validate the format of the proposed document root
121: if (docBase == null)
122: throw new IllegalArgumentException(sm
123: .getString("resources.null"));
124:
125: // Calculate a File object referencing this document base directory
126: base = new File(docBase);
127: try {
128: base = base.getCanonicalFile();
129: } catch (IOException e) {
130: // Ignore
131: }
132:
133: // Validate that the document base is an existing directory
134: if (!base.exists() || !base.isDirectory() || !base.canRead())
135: throw new IllegalArgumentException(sm.getString(
136: "fileResources.base", docBase));
137: this .absoluteBase = base.getAbsolutePath();
138:
139: // Change the document root property
140: this .docBase = docBase;
141:
142: }
143:
144: /**
145: * Return the document root for this component.
146: */
147: public String getDocBase() {
148: return (this .docBase);
149: }
150:
151: /**
152: * Set case sensitivity.
153: */
154: public void setCaseSensitive(boolean caseSensitive) {
155: this .caseSensitive = caseSensitive;
156: }
157:
158: /**
159: * Is case sensitive ?
160: */
161: public boolean isCaseSensitive() {
162: return caseSensitive;
163: }
164:
165: // --------------------------------------------------------- Public Methods
166:
167: /**
168: * Release any resources allocated for this directory context.
169: */
170: public void release() {
171: caseSensitive = true;
172: absoluteBase = null;
173: base = null;
174: super .release();
175: }
176:
177: public void setAttribute(String name, Object v) {
178: new Throwable().printStackTrace();
179: System.out.println(name + " " + v);
180: }
181:
182: // -------------------- BaseDirContext implementation --------------------
183:
184: /**
185: * Retrieves the named object. The result is a File relative to the docBase
186: * or a FileDirContext for directories.
187: *
188: * @param name the name of the object to look up
189: * @return the object bound to name
190: * @exception NamingException if a naming exception is encountered
191: */
192: public Object lookup(Name nameObj, boolean resolveLinkx)
193: throws NamingException {
194: if (log.isDebugEnabled())
195: log.debug("lookup " + nameObj);
196:
197: System.out.println("XXX " + nameObj.get(0));
198: if ("fs:".equals(nameObj.get(0).toString()))
199: nameObj = nameObj.getSuffix(1);
200:
201: String name = nameObj.toString(); // we need to convert anyway, for File constructor
202:
203: Object result = null;
204: File file = file(name);
205:
206: if (file == null)
207: throw new NamingException(sm.getString(
208: "resources.notFound", name));
209:
210: if (file.isDirectory()) {
211: FileDirContext tempContext = new FileDirContext(env);
212: tempContext.setDocBase(file.getPath());
213: result = tempContext;
214: } else {
215: // TODO: based on the name, return various 'styles' of
216: // content
217: // TODO: use lazy streams, cacheable
218: result = file; //new FileResource(file);
219: }
220:
221: return result;
222: }
223:
224: /**
225: * Unbinds the named object. Removes the terminal atomic name in name
226: * from the target context--that named by all but the terminal atomic
227: * part of name.
228: * <p>
229: * This method is idempotent. It succeeds even if the terminal atomic
230: * name is not bound in the target context, but throws
231: * NameNotFoundException if any of the intermediate contexts do not exist.
232: *
233: * @param name the name to bind; may not be empty
234: * @exception NameNotFoundException if an intermediate context does not
235: * exist
236: * @exception NamingException if a naming exception is encountered
237: */
238: public void unbind(Name nameObj) throws NamingException {
239: if ("fs:".equals(nameObj.get(0).toString()))
240: nameObj = nameObj.getSuffix(1);
241: String name = nameObj.toString();
242: if (log.isDebugEnabled())
243: log.debug("unbind " + name);
244: File file = file(name);
245:
246: if (file == null)
247: throw new NamingException(sm.getString(
248: "resources.notFound", name));
249:
250: if (!file.delete())
251: throw new NamingException(sm.getString(
252: "resources.unbindFailed", name));
253:
254: }
255:
256: /**
257: * Binds a new name to the object bound to an old name, and unbinds the
258: * old name. Both names are relative to this context. Any attributes
259: * associated with the old name become associated with the new name.
260: * Intermediate contexts of the old name are not changed.
261: *
262: * @param oldName the name of the existing binding; may not be empty
263: * @param newName the name of the new binding; may not be empty
264: * @exception NameAlreadyBoundException if newName is already bound
265: * @exception NamingException if a naming exception is encountered
266: */
267: public void rename(Name oldNameO, Name newNameO)
268: throws NamingException {
269: String oldName = oldNameO.toString();
270: String newName = newNameO.toString();
271: File file = file(oldName);
272:
273: if (file == null)
274: throw new NamingException(sm.getString(
275: "resources.notFound", oldName));
276:
277: File newFile = new File(base, newName);
278:
279: file.renameTo(newFile);
280: }
281:
282: /**
283: * Enumerates the names bound in the named context, along with the class
284: * names of objects bound to them. The contents of any subcontexts are
285: * not included.
286: * <p>
287: * If a binding is added to or removed from this context, its effect on
288: * an enumeration previously returned is undefined.
289: *
290: * @param name the name of the context to list
291: * @return an enumeration of the names and class names of the bindings in
292: * this context. Each element of the enumeration is of type NameClassPair.
293: * @exception NamingException if a naming exception is encountered
294: */
295: public NamingEnumeration list(Name nameN) throws NamingException {
296: String name = nameN.toString();
297: if (log.isDebugEnabled())
298: log.debug("list " + name);
299: File file = file(name);
300:
301: if (file == null)
302: throw new NamingException(sm.getString(
303: "resources.notFound", name));
304:
305: Vector entries = list(file);
306:
307: return new NamingContextEnumeration(entries.elements(), this ,
308: false);
309:
310: }
311:
312: /**
313: * Enumerates the names bound in the named context, along with the
314: * objects bound to them. The contents of any subcontexts are not
315: * included.
316: * <p>
317: * If a binding is added to or removed from this context, its effect on
318: * an enumeration previously returned is undefined.
319: *
320: * @param name the name of the context to list
321: * @return an enumeration of the bindings in this context.
322: * Each element of the enumeration is of type Binding.
323: * @exception NamingException if a naming exception is encountered
324: */
325: public NamingEnumeration listBindings(Name nameN)
326: throws NamingException {
327: String name = nameN.toString();
328: if (log.isDebugEnabled())
329: log.debug("listBindings " + name);
330:
331: File file = file(name);
332:
333: if (file == null)
334: throw new NamingException(sm.getString(
335: "resources.notFound", name));
336:
337: Vector entries = list(file);
338:
339: return new NamingContextEnumeration(entries.elements(), this ,
340: true);
341:
342: }
343:
344: /**
345: * Destroys the named context and removes it from the namespace. Any
346: * attributes associated with the name are also removed. Intermediate
347: * contexts are not destroyed.
348: * <p>
349: * This method is idempotent. It succeeds even if the terminal atomic
350: * name is not bound in the target context, but throws
351: * NameNotFoundException if any of the intermediate contexts do not exist.
352: *
353: * In a federated naming system, a context from one naming system may be
354: * bound to a name in another. One can subsequently look up and perform
355: * operations on the foreign context using a composite name. However, an
356: * attempt destroy the context using this composite name will fail with
357: * NotContextException, because the foreign context is not a "subcontext"
358: * of the context in which it is bound. Instead, use unbind() to remove
359: * the binding of the foreign context. Destroying the foreign context
360: * requires that the destroySubcontext() be performed on a context from
361: * the foreign context's "native" naming system.
362: *
363: * @param name the name of the context to be destroyed; may not be empty
364: * @exception NameNotFoundException if an intermediate context does not
365: * exist
366: * @exception NotContextException if the name is bound but does not name
367: * a context, or does not name a context of the appropriate type
368: */
369: public void destroySubcontext(Name name) throws NamingException {
370: unbind(name);
371: }
372:
373: /**
374: * Retrieves the full name of this context within its own namespace.
375: * <p>
376: * Many naming services have a notion of a "full name" for objects in
377: * their respective namespaces. For example, an LDAP entry has a
378: * distinguished name, and a DNS record has a fully qualified name. This
379: * method allows the client application to retrieve this name. The string
380: * returned by this method is not a JNDI composite name and should not be
381: * passed directly to context methods. In naming systems for which the
382: * notion of full name does not make sense,
383: * OperationNotSupportedException is thrown.
384: *
385: * @return this context's name in its own namespace; never null
386: * @exception OperationNotSupportedException if the naming system does
387: * not have the notion of a full name
388: * @exception NamingException if a naming exception is encountered
389: */
390: public String getNameInNamespace() throws NamingException {
391: return docBase;
392: }
393:
394: // ----------------------------------------------------- DirContext Methods
395:
396: /**
397: * Retrieves selected attributes associated with a named object.
398: * See the class description regarding attribute models, attribute type
399: * names, and operational attributes.
400: *
401: * @return the requested attributes; never null
402: * @param name the name of the object from which to retrieve attributes
403: * @param attrIds the identifiers of the attributes to retrieve. null
404: * indicates that all attributes should be retrieved; an empty array
405: * indicates that none should be retrieved
406: * @exception NamingException if a naming exception is encountered
407: */
408: public Attributes getAttributes(Name nameN, String[] attrIds)
409: throws NamingException {
410: String name = nameN.toString();
411: if (log.isDebugEnabled())
412: log.debug("getAttributes " + name);
413:
414: // Building attribute list
415: File file = file(name);
416:
417: if (file == null)
418: throw new NamingException(sm.getString(
419: "resources.notFound", name));
420:
421: return new FileAttributes(file);
422:
423: }
424:
425: /**
426: * Binds a name to an object, along with associated attributes. If attrs
427: * is null, the resulting binding will have the attributes associated
428: * with obj if obj is a DirContext, and no attributes otherwise. If attrs
429: * is non-null, the resulting binding will have attrs as its attributes;
430: * any attributes associated with obj are ignored.
431: *
432: * @param name the name to bind; may not be empty
433: * @param obj the object to bind; possibly null
434: * @param attrs the attributes to associate with the binding
435: * @exception NameAlreadyBoundException if name is already bound
436: * @exception InvalidAttributesException if some "mandatory" attributes
437: * of the binding are not supplied
438: * @exception NamingException if a naming exception is encountered
439: */
440: public void bind(Name nameN, Object obj, Attributes attrs)
441: throws NamingException {
442:
443: String name = nameN.toString();
444: // Note: No custom attributes allowed
445:
446: File file = new File(base, name);
447: if (file.exists())
448: throw new NameAlreadyBoundException(sm.getString(
449: "resources.alreadyBound", name));
450:
451: rebind(name, obj, attrs);
452: }
453:
454: /**
455: * Binds a name to an object, along with associated attributes,
456: * overwriting any existing binding. If attrs is null and obj is a
457: * DirContext, the attributes from obj are used. If attrs is null and obj
458: * is not a DirContext, any existing attributes associated with the object
459: * already bound in the directory remain unchanged. If attrs is non-null,
460: * any existing attributes associated with the object already bound in
461: * the directory are removed and attrs is associated with the named
462: * object. If obj is a DirContext and attrs is non-null, the attributes
463: * of obj are ignored.
464: *
465: * @param name the name to bind; may not be empty
466: * @param obj the object to bind; possibly null
467: * @param attrs the attributes to associate with the binding
468: * @exception InvalidAttributesException if some "mandatory" attributes
469: * of the binding are not supplied
470: * @exception NamingException if a naming exception is encountered
471: */
472: public void rebind(Name nameN, Object obj, Attributes attrs)
473: throws NamingException {
474: String name = nameN.toString();
475:
476: // Note: No custom attributes allowed
477: // Check obj type
478:
479: File file = new File(base, name);
480:
481: InputStream is = null;
482: // if (obj instanceof Resource) {
483: // try {
484: // is = ((Resource) obj).streamContent();
485: // } catch (IOException e) {
486: // }
487: // } else
488:
489: // TODO support File, byte[], String
490: if (obj instanceof InputStream) {
491: is = (InputStream) obj;
492: } else if (obj instanceof DirContext) {
493: if (file.exists()) {
494: if (!file.delete())
495: throw new NamingException(sm.getString(
496: "resources.bindFailed", name));
497: }
498: if (!file.mkdir())
499: throw new NamingException(sm.getString(
500: "resources.bindFailed", name));
501: }
502: if (is == null)
503: throw new NamingException(sm.getString(
504: "resources.bindFailed", name));
505:
506: // Open os
507:
508: try {
509: FileOutputStream os = null;
510: byte buffer[] = new byte[BUFFER_SIZE];
511: int len = -1;
512: try {
513: os = new FileOutputStream(file);
514: while (true) {
515: len = is.read(buffer);
516: if (len == -1)
517: break;
518: os.write(buffer, 0, len);
519: }
520: } finally {
521: if (os != null)
522: os.close();
523: is.close();
524: }
525: } catch (IOException e) {
526: throw new NamingException(sm.getString(
527: "resources.bindFailed", e));
528: }
529: }
530:
531: /**
532: * Creates and binds a new context, along with associated attributes.
533: * This method creates a new subcontext with the given name, binds it in
534: * the target context (that named by all but terminal atomic component of
535: * the name), and associates the supplied attributes with the newly
536: * created object. All intermediate and target contexts must already
537: * exist. If attrs is null, this method is equivalent to
538: * Context.createSubcontext().
539: *
540: * @param name the name of the context to create; may not be empty
541: * @param attrs the attributes to associate with the newly created context
542: * @return the newly created context
543: * @exception NameAlreadyBoundException if the name is already bound
544: * @exception InvalidAttributesException if attrs does not contain all
545: * the mandatory attributes required for creation
546: * @exception NamingException if a naming exception is encountered
547: */
548: public DirContext createSubcontext(Name nameN, Attributes attrs)
549: throws NamingException {
550: String name = nameN.toString();
551: File file = new File(base, name);
552: if (file.exists())
553: throw new NameAlreadyBoundException(sm.getString(
554: "resources.alreadyBound", name));
555: if (!file.mkdir())
556: throw new NamingException(sm.getString(
557: "resources.bindFailed", name));
558: return (DirContext) lookup(name);
559: }
560:
561: // ------------------------------------------------------ Protected Methods
562:
563: /**
564: * Return a context-relative path, beginning with a "/", that represents
565: * the canonical version of the specified path after ".." and "." elements
566: * are resolved out. If the specified path attempts to go outside the
567: * boundaries of the current context (i.e. too many ".." path elements
568: * are present), return <code>null</code> instead.
569: *
570: * @param path Path to be normalized
571: */
572: protected String normalize(String path) {
573:
574: String normalized = path;
575:
576: // Normalize the slashes and add leading slash if necessary
577: if (normalized.indexOf('\\') >= 0)
578: normalized = normalized.replace('\\', '/');
579: if (!normalized.startsWith("/"))
580: normalized = "/" + normalized;
581:
582: // Resolve occurrences of "//" in the normalized path
583: while (true) {
584: int index = normalized.indexOf("//");
585: if (index < 0)
586: break;
587: normalized = normalized.substring(0, index)
588: + normalized.substring(index + 1);
589: }
590:
591: // Resolve occurrences of "/./" in the normalized path
592: while (true) {
593: int index = normalized.indexOf("/./");
594: if (index < 0)
595: break;
596: normalized = normalized.substring(0, index)
597: + normalized.substring(index + 2);
598: }
599:
600: // Resolve occurrences of "/../" in the normalized path
601: while (true) {
602: int index = normalized.indexOf("/../");
603: if (index < 0)
604: break;
605: if (index == 0)
606: return (null); // Trying to go outside our context
607: int index2 = normalized.lastIndexOf('/', index - 1);
608: normalized = normalized.substring(0, index2)
609: + normalized.substring(index + 3);
610: }
611:
612: // Return the normalized path that we have completed
613: return (normalized);
614:
615: }
616:
617: /**
618: * Return a File object representing the specified normalized
619: * context-relative path if it exists and is readable. Otherwise,
620: * return <code>null</code>.
621: *
622: * @param name Normalized context-relative path (with leading '/')
623: */
624: protected File file(String name) {
625:
626: File file = new File(base, name);
627: if (file.exists() && file.canRead()) {
628:
629: // Check that this file belongs to our root path
630: String canPath = null;
631: try {
632: canPath = file.getCanonicalPath();
633: } catch (IOException e) {
634: }
635: if (canPath == null)
636: return null;
637:
638: if (!canPath.startsWith(absoluteBase)) {
639: return null;
640: }
641:
642: // Windows only check
643: if ((caseSensitive) && (File.separatorChar == '\\')) {
644: String fileAbsPath = file.getAbsolutePath();
645: if (fileAbsPath.endsWith("."))
646: fileAbsPath = fileAbsPath + "/";
647: String absPath = normalize(fileAbsPath);
648: if (canPath != null)
649: canPath = normalize(canPath);
650: if ((absoluteBase.length() < absPath.length())
651: && (absoluteBase.length() < canPath.length())) {
652: absPath = absPath
653: .substring(absoluteBase.length() + 1);
654: if ((canPath == null) || (absPath == null))
655: return null;
656: if (absPath.equals(""))
657: absPath = "/";
658: canPath = canPath
659: .substring(absoluteBase.length() + 1);
660: if (canPath.equals(""))
661: canPath = "/";
662: if (!canPath.equals(absPath))
663: return null;
664: }
665: }
666:
667: } else {
668: if (log.isDebugEnabled())
669: log.debug(file + " " + file.exists() + " "
670: + file.canRead());
671: return null;
672: }
673: return file;
674:
675: }
676:
677: /**
678: * List the resources which are members of a collection.
679: *
680: * @param file Collection
681: * @return Vector containg NamingEntry objects
682: */
683: protected Vector list(File file) {
684:
685: Vector entries = new Vector();
686: if (!file.isDirectory())
687: return entries;
688: String[] names = file.list();
689: Arrays.sort(names); // Sort alphabetically
690: if (names == null)
691: return entries;
692: NamingEntry entry = null;
693:
694: for (int i = 0; i < names.length; i++) {
695:
696: File currentFile = new File(file, names[i]);
697: Object object = null;
698: if (currentFile.isDirectory()) {
699: FileDirContext tempContext = new FileDirContext(env);
700: tempContext.setDocBase(file.getPath());
701: object = tempContext;
702: } else {
703: //object = new FileResource(currentFile);
704: object = currentFile;
705: }
706: entry = new NamingEntry(names[i], object, null,
707: NamingEntry.ENTRY);
708: entries.addElement(entry);
709:
710: }
711:
712: return entries;
713:
714: }
715:
716: }
|