1   // ========================================================================
2   // Copyright 1996-2005 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // Licensed under the Apache License, Version 2.0 (the "License");
5   // you may not use this file except in compliance with the License.
6   // You may obtain a copy of the License at 
7   // http://www.apache.org/licenses/LICENSE-2.0
8   // Unless required by applicable law or agreed to in writing, software
9   // distributed under the License is distributed on an "AS IS" BASIS,
10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  // See the License for the specific language governing permissions and
12  // limitations under the License.
13  // ========================================================================
14  package org.mortbay.resource;
15  
16  import java.io.File;
17  import java.io.FileInputStream;
18  import java.io.FileOutputStream;
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.io.OutputStream;
22  import java.net.MalformedURLException;
23  import java.net.URI;
24  import java.net.URISyntaxException;
25  import java.net.URL;
26  import java.net.URLConnection;
27  import java.security.Permission;
28  
29  import org.mortbay.log.Log;
30  import org.mortbay.util.URIUtil;
31  
32  
33  /* ------------------------------------------------------------ */
34  /** File Resource.
35   *
36   * Handle resources of implied or explicit file type.
37   * This class can check for aliasing in the filesystem (eg case
38   * insensitivity).  By default this is turned on, or it can be controlled with the
39   * "org.mortbay.util.FileResource.checkAliases" system parameter.
40   *
41   * @author Greg Wilkins (gregw)
42   */
43  public class FileResource extends URLResource
44  {
45      private static boolean __checkAliases;
46      static
47      {
48          __checkAliases=
49              "true".equalsIgnoreCase
50              (System.getProperty("org.mortbay.util.FileResource.checkAliases","true"));
51   
52         if (__checkAliases)
53              Log.debug("Checking Resource aliases");
54      }
55      
56      /* ------------------------------------------------------------ */
57      private File _file;
58      private transient URL _alias=null;
59      private transient boolean _aliasChecked=false;
60  
61      /* ------------------------------------------------------------------------------- */
62      /** setCheckAliases.
63       * @param checkAliases True of resource aliases are to be checked for (eg case insensitivity or 8.3 short names) and treated as not found.
64       */
65      public static void setCheckAliases(boolean checkAliases)
66      {
67          __checkAliases=checkAliases;
68      }
69  
70      /* ------------------------------------------------------------------------------- */
71      /** getCheckAliases.
72       * @return True of resource aliases are to be checked for (eg case insensitivity or 8.3 short names) and treated as not found.
73       */
74      public static boolean getCheckAliases()
75      {
76          return __checkAliases;
77      }
78      
79      /* -------------------------------------------------------- */
80      public FileResource(URL url)
81          throws IOException, URISyntaxException
82      {
83          super(url,null);
84  
85          try
86          {
87              // Try standard API to convert URL to file.
88              _file =new File(new URI(url.toString()));
89          }
90          catch (Exception e)
91          {
92              Log.ignore(e);
93              try
94              {
95                  // Assume that File.toURL produced unencoded chars. So try
96                  // encoding them.
97                  String file_url="file:"+URIUtil.encodePath(url.toString().substring(5));           
98                  URI uri = new URI(file_url);
99                  if (uri.getAuthority()==null) 
100                     _file = new File(uri);
101                 else
102                     _file = new File("//"+uri.getAuthority()+URIUtil.decodePath(url.getFile()));
103             }
104             catch (Exception e2)
105             {
106                 Log.ignore(e2);
107 
108                 // Still can't get the file.  Doh! try good old hack!
109                 checkConnection();
110                 Permission perm = _connection.getPermission();
111                 _file = new File(perm==null?url.getFile():perm.getName());
112             }
113         }
114         if (_file.isDirectory())
115         {
116             if (!_urlString.endsWith("/"))
117                 _urlString=_urlString+"/";
118         }
119         else
120         {
121             if (_urlString.endsWith("/"))
122                 _urlString=_urlString.substring(0,_urlString.length()-1);
123         }
124             
125     }
126     
127     /* -------------------------------------------------------- */
128     FileResource(URL url, URLConnection connection, File file)
129     {
130         super(url,connection);
131         _file=file;
132         if (_file.isDirectory() && !_urlString.endsWith("/"))
133             _urlString=_urlString+"/";
134     }
135     
136     /* -------------------------------------------------------- */
137     public Resource addPath(String path)
138         throws IOException,MalformedURLException
139     {
140         URLResource r=null;
141         String url=null;
142 
143         path = org.mortbay.util.URIUtil.canonicalPath(path);
144        
145         if ("/".equals(path))
146             return this;
147         else if (!isDirectory())
148         {
149             r=(FileResource)super.addPath(path);
150             url=r._urlString;
151         }
152         else
153         {
154             if (path==null)
155                 throw new MalformedURLException();   
156             
157             // treat all paths being added as relative
158             String rel=path;
159             if (path.startsWith("/"))
160                 rel = path.substring(1);
161             
162             url=URIUtil.addPaths(_urlString,URIUtil.encodePath(rel));
163             r=(URLResource)Resource.newResource(url);
164         }
165         
166         String encoded=URIUtil.encodePath(path);
167         int expected=r.toString().length()-encoded.length();
168         int index = r._urlString.lastIndexOf(encoded, expected);
169         
170         if (expected!=index && ((expected-1)!=index || path.endsWith("/") || !r.isDirectory()))
171         {
172             if (!(r instanceof BadResource))
173             {
174                 ((FileResource)r)._alias=new URL(url);
175                 ((FileResource)r)._aliasChecked=true;
176             }
177         }                             
178         return r;
179     }
180    
181     
182     /* ------------------------------------------------------------ */
183     public URL getAlias()
184     {
185         if (__checkAliases && !_aliasChecked)
186         {
187             try
188             {    
189                 String abs=_file.getAbsolutePath();
190                 String can=_file.getCanonicalPath();
191                 
192                 if (abs.length()!=can.length() || !abs.equals(can))
193                     _alias=new File(can).toURI().toURL();
194                 
195                 _aliasChecked=true;
196                 
197                 if (_alias!=null && Log.isDebugEnabled())
198                 {
199                     Log.debug("ALIAS abs="+abs);
200                     Log.debug("ALIAS can="+can);
201                 }
202             }
203             catch(Exception e)
204             {
205                 Log.warn(Log.EXCEPTION,e);
206                 return getURL();
207             }                
208         }
209         return _alias;
210     }
211     
212     /* -------------------------------------------------------- */
213     /**
214      * Returns true if the resource exists.
215      */
216     public boolean exists()
217     {
218         return _file.exists();
219     }
220         
221     /* -------------------------------------------------------- */
222     /**
223      * Returns the last modified time
224      */
225     public long lastModified()
226     {
227         return _file.lastModified();
228     }
229 
230     /* -------------------------------------------------------- */
231     /**
232      * Returns true if the respresenetd resource is a container/directory.
233      */
234     public boolean isDirectory()
235     {
236         return _file.isDirectory();
237     }
238 
239     /* --------------------------------------------------------- */
240     /**
241      * Return the length of the resource
242      */
243     public long length()
244     {
245         return _file.length();
246     }
247         
248 
249     /* --------------------------------------------------------- */
250     /**
251      * Returns the name of the resource
252      */
253     public String getName()
254     {
255         return _file.getAbsolutePath();
256     }
257         
258     /* ------------------------------------------------------------ */
259     /**
260      * Returns an File representing the given resource or NULL if this
261      * is not possible.
262      */
263     public File getFile()
264     {
265         return _file;
266     }
267         
268     /* --------------------------------------------------------- */
269     /**
270      * Returns an input stream to the resource
271      */
272     public InputStream getInputStream() throws IOException
273     {
274         return new FileInputStream(_file);
275     }
276         
277     /* --------------------------------------------------------- */
278     /**
279      * Returns an output stream to the resource
280      */
281     public OutputStream getOutputStream()
282         throws java.io.IOException, SecurityException
283     {
284         return new FileOutputStream(_file);
285     }
286         
287     /* --------------------------------------------------------- */
288     /**
289      * Deletes the given resource
290      */
291     public boolean delete()
292         throws SecurityException
293     {
294         return _file.delete();
295     }
296 
297     /* --------------------------------------------------------- */
298     /**
299      * Rename the given resource
300      */
301     public boolean renameTo( Resource dest)
302         throws SecurityException
303     {
304         if( dest instanceof FileResource)
305             return _file.renameTo( ((FileResource)dest)._file);
306         else
307             return false;
308     }
309 
310     /* --------------------------------------------------------- */
311     /**
312      * Returns a list of resources contained in the given resource
313      */
314     public String[] list()
315     {
316         String[] list =_file.list();
317         if (list==null)
318             return null;
319         for (int i=list.length;i-->0;)
320         {
321             if (new File(_file,list[i]).isDirectory() &&
322                 !list[i].endsWith("/"))
323                 list[i]+="/";
324         }
325         return list;
326     }
327          
328     /* ------------------------------------------------------------ */
329     /** Encode according to this resource type.
330      * File URIs are encoded.
331      * @param uri URI to encode.
332      * @return The uri unchanged.
333      */
334     public String encode(String uri)
335     {
336         return uri;
337     }
338     
339     /* ------------------------------------------------------------ */
340     /** 
341      * @param o
342      * @return <code>true</code> of the object <code>o</code> is a {@link FileResource} pointing to the same file as this resource. 
343      */
344     public boolean equals( Object o)
345     {
346         if (this == o)
347             return true;
348 
349         if (null == o || ! (o instanceof FileResource))
350             return false;
351 
352         FileResource f=(FileResource)o;
353         return f._file == _file || (null != _file && _file.equals(f._file));
354     }
355 
356     /* ------------------------------------------------------------ */
357     /**
358      * @return the hashcode.
359      */
360     public int hashCode()
361     {
362        return null == _file ? super.hashCode() : _file.hashCode();
363     }
364 }