1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
19 package org.apache.naming.resources;
20
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Date;
29 import java.util.Hashtable;
30
31 import javax.naming.NameAlreadyBoundException;
32 import javax.naming.NamingEnumeration;
33 import javax.naming.NamingException;
34 import javax.naming.OperationNotSupportedException;
35 import javax.naming.directory.Attributes;
36 import javax.naming.directory.DirContext;
37 import javax.naming.directory.ModificationItem;
38 import javax.naming.directory.SearchControls;
39
40 import org.apache.naming.NamingContextBindingsEnumeration;
41 import org.apache.naming.NamingContextEnumeration;
42 import org.apache.naming.NamingEntry;
43
44 /**
45 * Filesystem Directory Context implementation helper class.
46 *
47 * @author Remy Maucherat
48 * @version $Revision: 607568 $ $Date: 2007-12-30 18:56:50 +0100 (dim., 30 déc. 2007) $
49 */
50
51 public class FileDirContext extends BaseDirContext {
52
53 private static org.apache.juli.logging.Log log=
54 org.apache.juli.logging.LogFactory.getLog( FileDirContext.class );
55
56 // -------------------------------------------------------------- Constants
57
58
59 /**
60 * The descriptive information string for this implementation.
61 */
62 protected static final int BUFFER_SIZE = 2048;
63
64
65 // ----------------------------------------------------------- Constructors
66
67
68 /**
69 * Builds a file directory context using the given environment.
70 */
71 public FileDirContext() {
72 super();
73 }
74
75
76 /**
77 * Builds a file directory context using the given environment.
78 */
79 public FileDirContext(Hashtable env) {
80 super(env);
81 }
82
83
84 // ----------------------------------------------------- Instance Variables
85
86
87 /**
88 * The document base directory.
89 */
90 protected File base = null;
91
92
93 /**
94 * Absolute normalized filename of the base.
95 */
96 protected String absoluteBase = null;
97
98
99 /**
100 * Case sensitivity.
101 */
102 protected boolean caseSensitive = true;
103
104
105 /**
106 * Allow linking.
107 */
108 protected boolean allowLinking = false;
109
110
111 // ------------------------------------------------------------- Properties
112
113
114 /**
115 * Set the document root.
116 *
117 * @param docBase The new document root
118 *
119 * @exception IllegalArgumentException if the specified value is not
120 * supported by this implementation
121 * @exception IllegalArgumentException if this would create a
122 * malformed URL
123 */
124 public void setDocBase(String docBase) {
125
126 // Validate the format of the proposed document root
127 if (docBase == null)
128 throw new IllegalArgumentException
129 (sm.getString("resources.null"));
130
131 // Calculate a File object referencing this document base directory
132 base = new File(docBase);
133 try {
134 base = base.getCanonicalFile();
135 } catch (IOException e) {
136 // Ignore
137 }
138
139 // Validate that the document base is an existing directory
140 if (!base.exists() || !base.isDirectory() || !base.canRead())
141 throw new IllegalArgumentException
142 (sm.getString("fileResources.base", docBase));
143 this.absoluteBase = base.getAbsolutePath();
144 super.setDocBase(docBase);
145
146 }
147
148
149 /**
150 * Set case sensitivity.
151 */
152 public void setCaseSensitive(boolean caseSensitive) {
153 this.caseSensitive = caseSensitive;
154 }
155
156
157 /**
158 * Is case sensitive ?
159 */
160 public boolean isCaseSensitive() {
161 return caseSensitive;
162 }
163
164
165 /**
166 * Set allow linking.
167 */
168 public void setAllowLinking(boolean allowLinking) {
169 this.allowLinking = allowLinking;
170 }
171
172
173 /**
174 * Is linking allowed.
175 */
176 public boolean getAllowLinking() {
177 return allowLinking;
178 }
179
180
181 // --------------------------------------------------------- Public Methods
182
183
184 /**
185 * Release any resources allocated for this directory context.
186 */
187 public void release() {
188 super.release();
189 }
190
191
192 // -------------------------------------------------------- Context Methods
193
194
195 /**
196 * Retrieves the named object.
197 *
198 * @param name the name of the object to look up
199 * @return the object bound to name
200 * @exception NamingException if a naming exception is encountered
201 */
202 public Object lookup(String name)
203 throws NamingException {
204 Object result = null;
205 File file = file(name);
206
207 if (file == null)
208 throw new NamingException
209 (sm.getString("resources.notFound", name));
210
211 if (file.isDirectory()) {
212 FileDirContext tempContext = new FileDirContext(env);
213 tempContext.setDocBase(file.getPath());
214 tempContext.setAllowLinking(getAllowLinking());
215 tempContext.setCaseSensitive(isCaseSensitive());
216 result = tempContext;
217 } else {
218 result = new FileResource(file);
219 }
220
221 return result;
222
223 }
224
225
226 /**
227 * Unbinds the named object. Removes the terminal atomic name in name
228 * from the target context--that named by all but the terminal atomic
229 * part of name.
230 * <p>
231 * This method is idempotent. It succeeds even if the terminal atomic
232 * name is not bound in the target context, but throws
233 * NameNotFoundException if any of the intermediate contexts do not exist.
234 *
235 * @param name the name to bind; may not be empty
236 * @exception NameNotFoundException if an intermediate context does not
237 * exist
238 * @exception NamingException if a naming exception is encountered
239 */
240 public void unbind(String name)
241 throws NamingException {
242
243 File file = file(name);
244
245 if (file == null)
246 throw new NamingException
247 (sm.getString("resources.notFound", name));
248
249 if (!file.delete())
250 throw new NamingException
251 (sm.getString("resources.unbindFailed", name));
252
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(String oldName, String newName)
268 throws NamingException {
269
270 File file = file(oldName);
271
272 if (file == null)
273 throw new NamingException
274 (sm.getString("resources.notFound", oldName));
275
276 File newFile = new File(base, newName);
277
278 file.renameTo(newFile);
279
280 }
281
282
283 /**
284 * Enumerates the names bound in the named context, along with the class
285 * names of objects bound to them. The contents of any subcontexts are
286 * not included.
287 * <p>
288 * If a binding is added to or removed from this context, its effect on
289 * an enumeration previously returned is undefined.
290 *
291 * @param name the name of the context to list
292 * @return an enumeration of the names and class names of the bindings in
293 * this context. Each element of the enumeration is of type NameClassPair.
294 * @exception NamingException if a naming exception is encountered
295 */
296 public NamingEnumeration list(String name)
297 throws NamingException {
298
299 File file = file(name);
300
301 if (file == null)
302 throw new NamingException
303 (sm.getString("resources.notFound", name));
304
305 return new NamingContextEnumeration(list(file).iterator());
306
307 }
308
309
310 /**
311 * Enumerates the names bound in the named context, along with the
312 * objects bound to them. The contents of any subcontexts are not
313 * included.
314 * <p>
315 * If a binding is added to or removed from this context, its effect on
316 * an enumeration previously returned is undefined.
317 *
318 * @param name the name of the context to list
319 * @return an enumeration of the bindings in this context.
320 * Each element of the enumeration is of type Binding.
321 * @exception NamingException if a naming exception is encountered
322 */
323 public NamingEnumeration listBindings(String name)
324 throws NamingException {
325
326 File file = file(name);
327
328 if (file == null)
329 throw new NamingException
330 (sm.getString("resources.notFound", name));
331
332 return new NamingContextBindingsEnumeration(list(file).iterator(),
333 this);
334
335 }
336
337
338 /**
339 * Destroys the named context and removes it from the namespace. Any
340 * attributes associated with the name are also removed. Intermediate
341 * contexts are not destroyed.
342 * <p>
343 * This method is idempotent. It succeeds even if the terminal atomic
344 * name is not bound in the target context, but throws
345 * NameNotFoundException if any of the intermediate contexts do not exist.
346 *
347 * In a federated naming system, a context from one naming system may be
348 * bound to a name in another. One can subsequently look up and perform
349 * operations on the foreign context using a composite name. However, an
350 * attempt destroy the context using this composite name will fail with
351 * NotContextException, because the foreign context is not a "subcontext"
352 * of the context in which it is bound. Instead, use unbind() to remove
353 * the binding of the foreign context. Destroying the foreign context
354 * requires that the destroySubcontext() be performed on a context from
355 * the foreign context's "native" naming system.
356 *
357 * @param name the name of the context to be destroyed; may not be empty
358 * @exception NameNotFoundException if an intermediate context does not
359 * exist
360 * @exception NotContextException if the name is bound but does not name
361 * a context, or does not name a context of the appropriate type
362 */
363 public void destroySubcontext(String name)
364 throws NamingException {
365 unbind(name);
366 }
367
368
369 /**
370 * Retrieves the named object, following links except for the terminal
371 * atomic component of the name. If the object bound to name is not a
372 * link, returns the object itself.
373 *
374 * @param name the name of the object to look up
375 * @return the object bound to name, not following the terminal link
376 * (if any).
377 * @exception NamingException if a naming exception is encountered
378 */
379 public Object lookupLink(String name)
380 throws NamingException {
381 // Note : Links are not supported
382 return lookup(name);
383 }
384
385
386 /**
387 * Retrieves the full name of this context within its own namespace.
388 * <p>
389 * Many naming services have a notion of a "full name" for objects in
390 * their respective namespaces. For example, an LDAP entry has a
391 * distinguished name, and a DNS record has a fully qualified name. This
392 * method allows the client application to retrieve this name. The string
393 * returned by this method is not a JNDI composite name and should not be
394 * passed directly to context methods. In naming systems for which the
395 * notion of full name does not make sense,
396 * OperationNotSupportedException is thrown.
397 *
398 * @return this context's name in its own namespace; never null
399 * @exception OperationNotSupportedException if the naming system does
400 * not have the notion of a full name
401 * @exception NamingException if a naming exception is encountered
402 */
403 public String getNameInNamespace()
404 throws NamingException {
405 return docBase;
406 }
407
408
409 // ----------------------------------------------------- DirContext Methods
410
411
412 /**
413 * Retrieves selected attributes associated with a named object.
414 * See the class description regarding attribute models, attribute type
415 * names, and operational attributes.
416 *
417 * @return the requested attributes; never null
418 * @param name the name of the object from which to retrieve attributes
419 * @param attrIds the identifiers of the attributes to retrieve. null
420 * indicates that all attributes should be retrieved; an empty array
421 * indicates that none should be retrieved
422 * @exception NamingException if a naming exception is encountered
423 */
424 public Attributes getAttributes(String name, String[] attrIds)
425 throws NamingException {
426
427 // Building attribute list
428 File file = file(name);
429
430 if (file == null)
431 throw new NamingException
432 (sm.getString("resources.notFound", name));
433
434 return new FileResourceAttributes(file);
435
436 }
437
438
439 /**
440 * Modifies the attributes associated with a named object. The order of
441 * the modifications is not specified. Where possible, the modifications
442 * are performed atomically.
443 *
444 * @param name the name of the object whose attributes will be updated
445 * @param mod_op the modification operation, one of: ADD_ATTRIBUTE,
446 * REPLACE_ATTRIBUTE, REMOVE_ATTRIBUTE
447 * @param attrs the attributes to be used for the modification; may not
448 * be null
449 * @exception AttributeModificationException if the modification cannot be
450 * completed successfully
451 * @exception NamingException if a naming exception is encountered
452 */
453 public void modifyAttributes(String name, int mod_op, Attributes attrs)
454 throws NamingException {
455
456 }
457
458
459 /**
460 * Modifies the attributes associated with a named object using an an
461 * ordered list of modifications. The modifications are performed in the
462 * order specified. Each modification specifies a modification operation
463 * code and an attribute on which to operate. Where possible, the
464 * modifications are performed atomically.
465 *
466 * @param name the name of the object whose attributes will be updated
467 * @param mods an ordered sequence of modifications to be performed; may
468 * not be null
469 * @exception AttributeModificationException if the modification cannot be
470 * completed successfully
471 * @exception NamingException if a naming exception is encountered
472 */
473 public void modifyAttributes(String name, ModificationItem[] mods)
474 throws NamingException {
475
476 }
477
478
479 /**
480 * Binds a name to an object, along with associated attributes. If attrs
481 * is null, the resulting binding will have the attributes associated
482 * with obj if obj is a DirContext, and no attributes otherwise. If attrs
483 * is non-null, the resulting binding will have attrs as its attributes;
484 * any attributes associated with obj are ignored.
485 *
486 * @param name the name to bind; may not be empty
487 * @param obj the object to bind; possibly null
488 * @param attrs the attributes to associate with the binding
489 * @exception NameAlreadyBoundException if name is already bound
490 * @exception InvalidAttributesException if some "mandatory" attributes
491 * of the binding are not supplied
492 * @exception NamingException if a naming exception is encountered
493 */
494 public void bind(String name, Object obj, Attributes attrs)
495 throws NamingException {
496
497 // Note: No custom attributes allowed
498
499 File file = new File(base, name);
500 if (file.exists())
501 throw new NameAlreadyBoundException
502 (sm.getString("resources.alreadyBound", name));
503
504 rebind(name, obj, attrs);
505
506 }
507
508
509 /**
510 * Binds a name to an object, along with associated attributes,
511 * overwriting any existing binding. If attrs is null and obj is a
512 * DirContext, the attributes from obj are used. If attrs is null and obj
513 * is not a DirContext, any existing attributes associated with the object
514 * already bound in the directory remain unchanged. If attrs is non-null,
515 * any existing attributes associated with the object already bound in
516 * the directory are removed and attrs is associated with the named
517 * object. If obj is a DirContext and attrs is non-null, the attributes
518 * of obj are ignored.
519 *
520 * @param name the name to bind; may not be empty
521 * @param obj the object to bind; possibly null
522 * @param attrs the attributes to associate with the binding
523 * @exception InvalidAttributesException if some "mandatory" attributes
524 * of the binding are not supplied
525 * @exception NamingException if a naming exception is encountered
526 */
527 public void rebind(String name, Object obj, Attributes attrs)
528 throws NamingException {
529
530 // Note: No custom attributes allowed
531 // Check obj type
532
533 File file = new File(base, name);
534
535 InputStream is = null;
536 if (obj instanceof Resource) {
537 try {
538 is = ((Resource) obj).streamContent();
539 } catch (IOException e) {
540 }
541 } else if (obj instanceof InputStream) {
542 is = (InputStream) obj;
543 } else if (obj instanceof DirContext) {
544 if (file.exists()) {
545 if (!file.delete())
546 throw new NamingException
547 (sm.getString("resources.bindFailed", name));
548 }
549 if (!file.mkdir())
550 throw new NamingException
551 (sm.getString("resources.bindFailed", name));
552 }
553 if (is == null)
554 throw new NamingException
555 (sm.getString("resources.bindFailed", name));
556
557 // Open os
558
559 try {
560 FileOutputStream os = null;
561 byte buffer[] = new byte[BUFFER_SIZE];
562 int len = -1;
563 try {
564 os = new FileOutputStream(file);
565 while (true) {
566 len = is.read(buffer);
567 if (len == -1)
568 break;
569 os.write(buffer, 0, len);
570 }
571 } finally {
572 if (os != null)
573 os.close();
574 is.close();
575 }
576 } catch (IOException e) {
577 throw new NamingException
578 (sm.getString("resources.bindFailed", e));
579 }
580
581 }
582
583
584 /**
585 * Creates and binds a new context, along with associated attributes.
586 * This method creates a new subcontext with the given name, binds it in
587 * the target context (that named by all but terminal atomic component of
588 * the name), and associates the supplied attributes with the newly
589 * created object. All intermediate and target contexts must already
590 * exist. If attrs is null, this method is equivalent to
591 * Context.createSubcontext().
592 *
593 * @param name the name of the context to create; may not be empty
594 * @param attrs the attributes to associate with the newly created context
595 * @return the newly created context
596 * @exception NameAlreadyBoundException if the name is already bound
597 * @exception InvalidAttributesException if attrs does not contain all
598 * the mandatory attributes required for creation
599 * @exception NamingException if a naming exception is encountered
600 */
601 public DirContext createSubcontext(String name, Attributes attrs)
602 throws NamingException {
603
604 File file = new File(base, name);
605 if (file.exists())
606 throw new NameAlreadyBoundException
607 (sm.getString("resources.alreadyBound", name));
608 if (!file.mkdir())
609 throw new NamingException
610 (sm.getString("resources.bindFailed", name));
611 return (DirContext) lookup(name);
612
613 }
614
615
616 /**
617 * Retrieves the schema associated with the named object. The schema
618 * describes rules regarding the structure of the namespace and the
619 * attributes stored within it. The schema specifies what types of
620 * objects can be added to the directory and where they can be added;
621 * what mandatory and optional attributes an object can have. The range
622 * of support for schemas is directory-specific.
623 *
624 * @param name the name of the object whose schema is to be retrieved
625 * @return the schema associated with the context; never null
626 * @exception OperationNotSupportedException if schema not supported
627 * @exception NamingException if a naming exception is encountered
628 */
629 public DirContext getSchema(String name)
630 throws NamingException {
631 throw new OperationNotSupportedException();
632 }
633
634
635 /**
636 * Retrieves a context containing the schema objects of the named
637 * object's class definitions.
638 *
639 * @param name the name of the object whose object class definition is to
640 * be retrieved
641 * @return the DirContext containing the named object's class
642 * definitions; never null
643 * @exception OperationNotSupportedException if schema not supported
644 * @exception NamingException if a naming exception is encountered
645 */
646 public DirContext getSchemaClassDefinition(String name)
647 throws NamingException {
648 throw new OperationNotSupportedException();
649 }
650
651
652 /**
653 * Searches in a single context for objects that contain a specified set
654 * of attributes, and retrieves selected attributes. The search is
655 * performed using the default SearchControls settings.
656 *
657 * @param name the name of the context to search
658 * @param matchingAttributes the attributes to search for. If empty or
659 * null, all objects in the target context are returned.
660 * @param attributesToReturn the attributes to return. null indicates
661 * that all attributes are to be returned; an empty array indicates that
662 * none are to be returned.
663 * @return a non-null enumeration of SearchResult objects. Each
664 * SearchResult contains the attributes identified by attributesToReturn
665 * and the name of the corresponding object, named relative to the
666 * context named by name.
667 * @exception NamingException if a naming exception is encountered
668 */
669 public NamingEnumeration search(String name, Attributes matchingAttributes,
670 String[] attributesToReturn)
671 throws NamingException {
672 return null;
673 }
674
675
676 /**
677 * Searches in a single context for objects that contain a specified set
678 * of attributes. This method returns all the attributes of such objects.
679 * It is equivalent to supplying null as the atributesToReturn parameter
680 * to the method search(Name, Attributes, String[]).
681 *
682 * @param name the name of the context to search
683 * @param matchingAttributes the attributes to search for. If empty or
684 * null, all objects in the target context are returned.
685 * @return a non-null enumeration of SearchResult objects. Each
686 * SearchResult contains the attributes identified by attributesToReturn
687 * and the name of the corresponding object, named relative to the
688 * context named by name.
689 * @exception NamingException if a naming exception is encountered
690 */
691 public NamingEnumeration search(String name, Attributes matchingAttributes)
692 throws NamingException {
693 return null;
694 }
695
696
697 /**
698 * Searches in the named context or object for entries that satisfy the
699 * given search filter. Performs the search as specified by the search
700 * controls.
701 *
702 * @param name the name of the context or object to search
703 * @param filter the filter expression to use for the search; may not be
704 * null
705 * @param cons the search controls that control the search. If null,
706 * the default search controls are used (equivalent to
707 * (new SearchControls())).
708 * @return an enumeration of SearchResults of the objects that satisfy
709 * the filter; never null
710 * @exception InvalidSearchFilterException if the search filter specified
711 * is not supported or understood by the underlying directory
712 * @exception InvalidSearchControlsException if the search controls
713 * contain invalid settings
714 * @exception NamingException if a naming exception is encountered
715 */
716 public NamingEnumeration search(String name, String filter,
717 SearchControls cons)
718 throws NamingException {
719 return null;
720 }
721
722
723 /**
724 * Searches in the named context or object for entries that satisfy the
725 * given search filter. Performs the search as specified by the search
726 * controls.
727 *
728 * @param name the name of the context or object to search
729 * @param filterExpr the filter expression to use for the search.
730 * The expression may contain variables of the form "{i}" where i is a
731 * nonnegative integer. May not be null.
732 * @param filterArgs the array of arguments to substitute for the
733 * variables in filterExpr. The value of filterArgs[i] will replace each
734 * occurrence of "{i}". If null, equivalent to an empty array.
735 * @param cons the search controls that control the search. If null, the
736 * default search controls are used (equivalent to (new SearchControls())).
737 * @return an enumeration of SearchResults of the objects that satisy the
738 * filter; never null
739 * @exception ArrayIndexOutOfBoundsException if filterExpr contains {i}
740 * expressions where i is outside the bounds of the array filterArgs
741 * @exception InvalidSearchControlsException if cons contains invalid
742 * settings
743 * @exception InvalidSearchFilterException if filterExpr with filterArgs
744 * represents an invalid search filter
745 * @exception NamingException if a naming exception is encountered
746 */
747 public NamingEnumeration search(String name, String filterExpr,
748 Object[] filterArgs, SearchControls cons)
749 throws NamingException {
750 return null;
751 }
752
753
754 // ------------------------------------------------------ Protected Methods
755
756
757 /**
758 * Return a context-relative path, beginning with a "/", that represents
759 * the canonical version of the specified path after ".." and "." elements
760 * are resolved out. If the specified path attempts to go outside the
761 * boundaries of the current context (i.e. too many ".." path elements
762 * are present), return <code>null</code> instead.
763 *
764 * @param path Path to be normalized
765 */
766 protected String normalize(String path) {
767
768 String normalized = path;
769
770 // Normalize the slashes and add leading slash if necessary
771 if (File.separatorChar == '\\' && normalized.indexOf('\\') >= 0)
772 normalized = normalized.replace('\\', '/');
773 if (!normalized.startsWith("/"))
774 normalized = "/" + normalized;
775
776 // Resolve occurrences of "//" in the normalized path
777 while (true) {
778 int index = normalized.indexOf("//");
779 if (index < 0)
780 break;
781 normalized = normalized.substring(0, index) +
782 normalized.substring(index + 1);
783 }
784
785 // Resolve occurrences of "/./" in the normalized path
786 while (true) {
787 int index = normalized.indexOf("/./");
788 if (index < 0)
789 break;
790 normalized = normalized.substring(0, index) +
791 normalized.substring(index + 2);
792 }
793
794 // Resolve occurrences of "/../" in the normalized path
795 while (true) {
796 int index = normalized.indexOf("/../");
797 if (index < 0)
798 break;
799 if (index == 0)
800 return (null); // Trying to go outside our context
801 int index2 = normalized.lastIndexOf('/', index - 1);
802 normalized = normalized.substring(0, index2) +
803 normalized.substring(index + 3);
804 }
805
806 // Return the normalized path that we have completed
807 return (normalized);
808
809 }
810
811
812 /**
813 * Return a File object representing the specified normalized
814 * context-relative path if it exists and is readable. Otherwise,
815 * return <code>null</code>.
816 *
817 * @param name Normalized context-relative path (with leading '/')
818 */
819 protected File file(String name) {
820
821 File file = new File(base, name);
822 if (file.exists() && file.canRead()) {
823
824 if (allowLinking)
825 return file;
826
827 // Check that this file belongs to our root path
828 String canPath = null;
829 try {
830 canPath = file.getCanonicalPath();
831 } catch (IOException e) {
832 }
833 if (canPath == null)
834 return null;
835
836 // Check to see if going outside of the web application root
837 if (!canPath.startsWith(absoluteBase)) {
838 return null;
839 }
840
841 // Case sensitivity check
842 if (caseSensitive) {
843 String fileAbsPath = file.getAbsolutePath();
844 if (fileAbsPath.endsWith("."))
845 fileAbsPath = fileAbsPath + "/";
846 String absPath = normalize(fileAbsPath);
847 if (canPath != null)
848 canPath = normalize(canPath);
849 if ((absoluteBase.length() < absPath.length())
850 && (absoluteBase.length() < canPath.length())) {
851 absPath = absPath.substring(absoluteBase.length() + 1);
852 if ((canPath == null) || (absPath == null))
853 return null;
854 if (absPath.equals(""))
855 absPath = "/";
856 canPath = canPath.substring(absoluteBase.length() + 1);
857 if (canPath.equals(""))
858 canPath = "/";
859 if (!canPath.equals(absPath))
860 return null;
861 }
862 }
863
864 } else {
865 return null;
866 }
867 return file;
868
869 }
870
871
872 /**
873 * List the resources which are members of a collection.
874 *
875 * @param file Collection
876 * @return Vector containg NamingEntry objects
877 */
878 protected ArrayList list(File file) {
879
880 ArrayList entries = new ArrayList();
881 if (!file.isDirectory())
882 return entries;
883 String[] names = file.list();
884 if (names==null) {
885 /* Some IO error occurred such as bad file permissions.
886 Prevent a NPE with Arrays.sort(names) */
887 log.warn(sm.getString("fileResources.listingNull",
888 file.getAbsolutePath()));
889 return entries;
890 }
891
892 Arrays.sort(names); // Sort alphabetically
893 if (names == null)
894 return entries;
895 NamingEntry entry = null;
896
897 for (int i = 0; i < names.length; i++) {
898
899 File currentFile = new File(file, names[i]);
900 Object object = null;
901 if (currentFile.isDirectory()) {
902 FileDirContext tempContext = new FileDirContext(env);
903 tempContext.setDocBase(file.getPath());
904 tempContext.setAllowLinking(getAllowLinking());
905 tempContext.setCaseSensitive(isCaseSensitive());
906 object = tempContext;
907 } else {
908 object = new FileResource(currentFile);
909 }
910 entry = new NamingEntry(names[i], object, NamingEntry.ENTRY);
911 entries.add(entry);
912
913 }
914
915 return entries;
916
917 }
918
919
920 // ----------------------------------------------- FileResource Inner Class
921
922
923 /**
924 * This specialized resource implementation avoids opening the IputStream
925 * to the file right away (which would put a lock on the file).
926 */
927 protected class FileResource extends Resource {
928
929
930 // -------------------------------------------------------- Constructor
931
932
933 public FileResource(File file) {
934 this.file = file;
935 }
936
937
938 // --------------------------------------------------- Member Variables
939
940
941 /**
942 * Associated file object.
943 */
944 protected File file;
945
946
947 /**
948 * File length.
949 */
950 protected long length = -1L;
951
952
953 // --------------------------------------------------- Resource Methods
954
955
956 /**
957 * Content accessor.
958 *
959 * @return InputStream
960 */
961 public InputStream streamContent()
962 throws IOException {
963 if (binaryContent == null) {
964 inputStream = new FileInputStream(file);
965 }
966 return super.streamContent();
967 }
968
969
970 }
971
972
973 // ------------------------------------- FileResourceAttributes Inner Class
974
975
976 /**
977 * This specialized resource attribute implementation does some lazy
978 * reading (to speed up simple checks, like checking the last modified
979 * date).
980 */
981 protected class FileResourceAttributes extends ResourceAttributes {
982
983
984 // -------------------------------------------------------- Constructor
985
986
987 public FileResourceAttributes(File file) {
988 this.file = file;
989 getCreation();
990 getLastModified();
991 }
992
993 // --------------------------------------------------- Member Variables
994
995
996 protected File file;
997
998
999 protected boolean accessed = false;
1000
1001
1002 protected String canonicalPath = null;
1003
1004
1005 // ----------------------------------------- ResourceAttributes Methods
1006
1007
1008 /**
1009 * Is collection.
1010 */
1011 public boolean isCollection() {
1012 if (!accessed) {
1013 collection = file.isDirectory();
1014 accessed = true;
1015 }
1016 return super.isCollection();
1017 }
1018
1019
1020 /**
1021 * Get content length.
1022 *
1023 * @return content length value
1024 */
1025 public long getContentLength() {
1026 if (contentLength != -1L)
1027 return contentLength;
1028 contentLength = file.length();
1029 return contentLength;
1030 }
1031
1032
1033 /**
1034 * Get creation time.
1035 *
1036 * @return creation time value
1037 */
1038 public long getCreation() {
1039 if (creation != -1L)
1040 return creation;
1041 creation = getLastModified();
1042 return creation;
1043 }
1044
1045
1046 /**
1047 * Get creation date.
1048 *
1049 * @return Creation date value
1050 */
1051 public Date getCreationDate() {
1052 if (creation == -1L) {
1053 creation = getCreation();
1054 }
1055 return super.getCreationDate();
1056 }
1057
1058
1059 /**
1060 * Get last modified time.
1061 *
1062 * @return lastModified time value
1063 */
1064 public long getLastModified() {
1065 if (lastModified != -1L)
1066 return lastModified;
1067 lastModified = file.lastModified();
1068 return lastModified;
1069 }
1070
1071
1072 /**
1073 * Get lastModified date.
1074 *
1075 * @return LastModified date value
1076 */
1077 public Date getLastModifiedDate() {
1078 if (lastModified == -1L) {
1079 lastModified = getLastModified();
1080 }
1081 return super.getLastModifiedDate();
1082 }
1083
1084
1085 /**
1086 * Get name.
1087 *
1088 * @return Name value
1089 */
1090 public String getName() {
1091 if (name == null)
1092 name = file.getName();
1093 return name;
1094 }
1095
1096
1097 /**
1098 * Get resource type.
1099 *
1100 * @return String resource type
1101 */
1102 public String getResourceType() {
1103 if (!accessed) {
1104 collection = file.isDirectory();
1105 accessed = true;
1106 }
1107 return super.getResourceType();
1108 }
1109
1110
1111 /**
1112 * Get canonical path.
1113 *
1114 * @return String the file's canonical path
1115 */
1116 public String getCanonicalPath() {
1117 if (canonicalPath == null) {
1118 try {
1119 canonicalPath = file.getCanonicalPath();
1120 } catch (IOException e) {
1121 // Ignore
1122 }
1123 }
1124 return canonicalPath;
1125 }
1126
1127
1128 }
1129
1130
1131 }
1132