View Javadoc

1   /**
2    * Copyright (C) 2005-2009 Alfresco Software Limited.
3    *
4    * This file is part of the Spring Surf Extension project.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *  http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.springframework.extensions.webscripts;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.InputStreamReader;
24  import java.io.Reader;
25  import java.io.UnsupportedEncodingException;
26  import java.net.JarURLConnection;
27  import java.net.URL;
28  import java.net.URLConnection;
29  import java.util.ArrayList;
30  import java.util.Enumeration;
31  import java.util.HashSet;
32  import java.util.Iterator;
33  import java.util.List;
34  import java.util.Set;
35  import java.util.jar.JarEntry;
36  import java.util.jar.JarFile;
37  
38  import javax.servlet.ServletContext;
39  
40  import org.apache.commons.logging.Log;
41  import org.apache.commons.logging.LogFactory;
42  import org.springframework.context.ApplicationContext;
43  import org.springframework.context.ApplicationContextAware;
44  import org.springframework.core.io.Resource;
45  import org.springframework.extensions.surf.exception.WebScriptsPlatformException;
46  import org.springframework.util.AntPathMatcher;
47  import org.springframework.util.PathMatcher;
48  import org.springframework.web.context.ServletContextAware;
49  
50  import freemarker.cache.TemplateLoader;
51  
52  
53  /**
54   * Read only store implementation that can be mounted against a
55   * JAR file.  Works against the contents of the JAR file beginning
56   * at a specified path.
57   * 
58   * This is used to prepackage assets into JAR files for ease of
59   * build and distribution into external web applications
60   * (via Maven).
61   * 
62   * As with any Store, the interface methods deal principally with
63   * document paths.  Document paths are assumed to be relative to
64   * the root path.
65   * 
66   * In this case, all of the JAR file entries under the root path 
67   * are considered to be Store contents.  The paths to these jar 
68   * entries are determined by combining the root path with the 
69   * document path.
70   * 
71   * An example of a relative path:
72   * 
73   *    org/alfresco/web/example/template.ftl
74   *    
75   * An example of an absolute path:
76   * 
77   *    META-INF/org/alfresco/web/example/template.ftl
78   *    
79   * Where the root path is "META-INF/"
80   *
81   * @author muzquiano
82   */
83  public class JarStore extends AbstractStore implements Store, ServletContextAware, ApplicationContextAware
84  {
85      public static final String JAR_PROTOCOL = "jar";
86  
87      private static final Log logger = LogFactory.getLog(JarStore.class);
88  
89      protected boolean mustExist = false;
90      protected boolean exists = false;
91      
92      protected String rootPath = null;    
93      protected String jarPath = null;
94      protected String jarAutoDiscoveryClassPath = null;
95      protected List<String> documents = null;
96      
97      protected ServletContext servletContext = null;
98      
99      protected ApplicationContext applicationContext = null;
100     
101     private Set<String> allowedResourcePaths = null;
102     
103     /**
104      * Sets the allowed resource paths.
105      * 
106      * @param allowedResourcePaths the new allowed resource paths
107      */
108     public void setAllowedResourcePaths(List<String> allowedResourcePaths)
109     {
110         this.allowedResourcePaths = new HashSet<String>(allowedResourcePaths);
111     }
112     
113     /* (non-Javadoc)
114      * @see org.springframework.web.context.ServletContextAware#setServletContext(javax.servlet.ServletContext)
115      */
116     public void setServletContext(ServletContext servletContext)
117     {
118         this.servletContext = servletContext;
119     }
120     
121     /**
122      * Sets the application context.
123      * 
124      * @param applicationContext the new application context
125      */
126     public void setApplicationContext(ApplicationContext applicationContext)
127     {
128         this.applicationContext = applicationContext;
129     }
130     
131     /**
132      * Gets the application context.
133      * 
134      * @return the application context
135      */
136     public ApplicationContext getApplicationContext()
137     {
138         return applicationContext;
139     }
140     
141     /**
142      * Sets the jar auto discovery path.
143      * 
144      * @param jarAutoDiscoveryPath the new jar auto discovery path
145      */
146     public void setJarAutoDiscoveryClassPath(String jarAutoDiscoveryClassPath)
147     {
148         this.jarAutoDiscoveryClassPath = jarAutoDiscoveryClassPath;
149     }
150     
151     /**
152      * Gets the jar auto discovery class path.
153      * 
154      * @return the jar auto discovery class path
155      */
156     public String getJarAutoDiscoveryClassPath()
157     {
158         return this.jarAutoDiscoveryClassPath;
159     }
160     
161     /**
162      * Sets the jar path.
163      * 
164      * @param jarPath the new jar path
165      */
166     public void setJarPath(String jarPath)
167     {
168         this.jarPath = jarPath;
169     }
170     
171     /**
172      * Gets the jar path.
173      * 
174      * @return the jar path
175      */
176     public String getJarPath()
177     {
178         return this.jarPath;
179     }
180     
181     /**
182      * Sets the root path
183      * 
184      * @param rootPath the new root path
185      */
186     public void setRootPath(String rootPath) 
187     {
188         this.rootPath = rootPath;
189     }
190     
191     /**
192      * Gets the root path.
193      * 
194      * @return the root path
195      */
196     public String getRootPath()
197     {
198         return this.rootPath;
199     }
200     
201     /**
202      * To relative path.
203      * 
204      * @param fullPath the full path
205      * 
206      * @return the string
207      */
208     protected String toRelativePath(String fullPath)
209     {
210         String relativePath = fullPath;
211         
212         if (getRootPath() != null)
213         {
214             relativePath = relativePath.substring(getRootPath().length());
215         }
216         
217         if (relativePath.startsWith("/"))
218         {
219             relativePath = relativePath.substring(1);
220         }
221         
222         return relativePath;
223     }
224     
225     /**
226      * To absolute path.
227      * 
228      * @param relativePath the relative path
229      * 
230      * @return the string
231      */
232     protected String toAbsolutePath(String relativePath)
233     {
234         String fullPath = relativePath;
235         
236         if (getRootPath() != null)
237         {
238             fullPath = getRootPath() + "/" + fullPath;            
239             fullPath = fullPath.replace("//", "/");
240         }
241         
242         return fullPath;
243     }
244     
245     /**
246      * Checks if the given relative path is allowed.
247      * Validates against the allowedResourcePaths.
248      * 
249      * @param relativePath the relative path to the file
250      * 
251      * @return true, if is allowed
252      */
253     private boolean isRelativePathAllowed(String relativePath) 
254     {
255         PathMatcher pathMatcher = new AntPathMatcher();
256         
257         Iterator allowedResourcePathsIt = allowedResourcePaths.iterator();
258         while (allowedResourcePathsIt.hasNext()) 
259         {
260             String pattern = (String) allowedResourcePathsIt.next();
261             if (pathMatcher.match(pattern, relativePath)) 
262             {
263                 return true;
264             }
265         }
266         
267         return false;
268     }
269     
270 
271     /**
272      * Sets whether the class path must exist
273      *
274      * If it must exist, but it doesn't exist, an exception is thrown
275      * on initialisation of the store
276      *
277      * @param mustExist
278      */
279     public void setMustExist(boolean mustExist)
280     {
281         this.mustExist = mustExist;
282     }
283         
284     /**
285      * Gets the jar file.
286      * 
287      * @return the jar file
288      */
289     protected JarFile getJarFile()
290     {
291         JarFile jar = null;
292 
293         String sourceString = getJarPath();
294         
295         try
296         {
297             // make sure the source string is valid
298             if (sourceString.startsWith(JAR_PROTOCOL) == false)
299             {
300                throw new IllegalArgumentException("sourceString must start with \"" + JAR_PROTOCOL + ":\"");
301             }
302             
303             URL url = new URL(sourceString);
304             URLConnection conn = url.openConnection();
305             if (conn instanceof JarURLConnection)
306             {
307                // open the jar file, if it does not contain the entry requested a FileNotFound exception
308                // is thrown and reported below
309                jar = ((JarURLConnection)conn).getJarFile();
310             }
311         }
312         catch(Throwable t)
313         {
314             logger.error("Unable to read jar file: " + sourceString, t); 
315         }
316         
317         return jar;
318     }
319     
320     /* (non-Javadoc)
321      * @see org.alfresco.web.scripts.Store#init()
322      */
323     public void init()
324     {
325         // set up root path
326         if (getRootPath() == null)
327         {
328             setRootPath("");
329         }
330         if ("/".equals(getRootPath()))
331         {
332             setRootPath("");
333         }
334         
335         // location of the jar file (with variable substitution)
336         String sourceString = getJarPath();
337         if (sourceString == null)
338         {
339             // support for auto-resolution of a jar file
340             if (getJarAutoDiscoveryClassPath() != null)
341             {
342                 Resource[] resources = null;
343                 try
344                 {
345                     resources = getApplicationContext().getResources("classpath*:" + getJarAutoDiscoveryClassPath() + "*");
346                     if (resources != null && resources.length > 0)
347                     {
348                         String autoDiscovered = resources[0].getURL().toExternalForm();
349                         if (autoDiscovered != null && autoDiscovered.startsWith(JAR_PROTOCOL))
350                         {
351                             int i = autoDiscovered.indexOf("!/");
352                             if (i > -1)
353                             {
354                                 autoDiscovered = autoDiscovered.substring(0, i+2);
355                             }
356                             sourceString = autoDiscovered;
357                         }
358                     }
359                 }
360                 catch (IOException ioe)
361                 {
362                     logger.error("Unable to find JAR file for auto discovery for classpath: " + getJarAutoDiscoveryClassPath());
363                 }
364             }
365         }
366         sourceString = sourceString.replace("${servletContext}", this.servletContext.getRealPath("/"));
367         sourceString = sourceString.replace("\\\\", "\\");
368         setJarPath(sourceString);
369         
370         // defaults for allowed resource paths
371         if (allowedResourcePaths == null)
372         {
373             allowedResourcePaths = new HashSet<String>();
374 
375             // allow all resources by default
376             allowedResourcePaths.add("**/*.*");
377         };        
378         
379         // make sure we have a valid jar file
380         JarFile jarFile = getJarFile();        
381         if (jarFile != null)
382         {
383             exists = true;
384         }
385         
386         // set up documents container
387         this.documents = new ArrayList<String>();
388         
389         // pull back all of the entries in the jar file
390         if (exists)
391         {
392             try
393             {
394                 URL url = new URL(sourceString);
395                 URLConnection conn = url.openConnection();
396                 if (conn instanceof JarURLConnection)
397                 {
398                    // open the jar file, if it does not contain the entry requested a FileNotFound exception
399                    // is thrown and reported below
400                    JarFile jar = ((JarURLConnection)conn).getJarFile();
401                    Enumeration<JarEntry> e = jar.entries();
402                    while (e.hasMoreElements())
403                    {
404                        JarEntry entry = (JarEntry) e.nextElement();
405                        String jarName = entry.getName();
406                        
407                        // check if this is a file and if it is under the root path
408                        if (!entry.isDirectory() && jarName.startsWith(getRootPath()))
409                        {
410                            // make sure the relative path is allowed
411                            String relativePath = toRelativePath(jarName);
412                            
413                            if (isRelativePathAllowed(relativePath))
414                            {
415                                documents.add(relativePath);
416                            }
417                        }
418                    }
419                 }
420             }
421             catch (Throwable t)
422             {
423                 logger.error("Error while reading contents of jar file: " + sourceString, t);
424                 exists = false;
425             }
426         }
427         
428         // ensure the store exists if it supposed to exist
429         if (mustExist && !exists)
430         {
431             throw new WebScriptsPlatformException("Jar Store must exist but could not be created: " + getBasePath());
432         }
433         
434         if (logger.isDebugEnabled())
435             logger.debug("JarStore initialized - loaded " + this.documents.size() + " documents");
436     }
437 
438     /* (non-Javadoc)
439      * @see org.alfresco.web.scripts.Store#exists()
440      */
441     public boolean exists()
442     {
443         return exists;
444     }
445 
446     /* (non-Javadoc)
447      * @see org.alfresco.web.scripts.Store#getBasePath()
448      */
449     public String getBasePath()
450     {
451         return jarPath;
452     }
453 
454     /* (non-Javadoc)
455      * @see org.alfresco.web.scripts.Store#isSecure()
456      */
457     public boolean isSecure()
458     {
459         return true;
460     }
461 
462     /* (non-Javadoc)
463      * @see org.alfresco.web.scripts.Store#getAllDocumentPaths()
464      */
465     public String[] getAllDocumentPaths()
466     {
467         return documents.toArray(new String[documents.size()]);
468     }
469 
470     /* (non-Javadoc)
471      * @see org.alfresco.web.scripts.Store#getDescriptionDocumentPaths()
472      */
473     public String[] getDescriptionDocumentPaths()
474     {
475         return getDocumentPaths("/", true, "*.desc.xml");
476     }
477 
478     /* (non-Javadoc)
479      * @see org.alfresco.web.scripts.Store#getScriptDocumentPaths(org.alfresco.web.scripts.WebScript)
480      */
481     public String[] getScriptDocumentPaths(WebScript script)
482     {
483         String scriptPaths = script.getDescription().getId() + ".*";
484         return getDocumentPaths("/", false, scriptPaths);
485     }
486     
487     /* (non-Javadoc)
488      * @see org.alfresco.web.scripts.Store#getDocumentPaths(java.lang.String, boolean, java.lang.String)
489      */
490     public String[] getDocumentPaths(String path, boolean includeSubPaths, String documentPattern)
491     {
492         String[] paths;
493         
494         if ((path == null) || (path.length() == 0))
495         {
496             path = "/";
497         }
498 
499         if (! path.startsWith("/"))
500         {
501             path = "/" + path;
502         }
503 
504         if (! path.endsWith("/"))
505         {
506             path = path + "/";
507         }
508 
509         if ((documentPattern == null) || (documentPattern.length() == 0))
510         {
511             documentPattern = "*";
512         }
513 
514         final StringBuilder pattern = new StringBuilder(128);
515         pattern.append(path)
516                .append((includeSubPaths ? "**/" : ""))
517                .append(documentPattern);
518 
519         try
520         {
521             List<String> documentPaths = getMatchingDocuments(pattern.toString());
522             paths = documentPaths.toArray(new String[documentPaths.size()]);
523         }
524         catch (IOException e)
525         {
526             // Note: Ignore: no documents found
527             paths = new String[0];
528         }
529 
530         return paths;
531     }
532 
533     /**
534      * Helper to return a list of resource document paths
535      * based on a search pattern.
536      * 
537      * @param pattern the pattern
538      * 
539      * @return the matching documents (relative to root path)
540      * 
541      * @throws IOException Signals that an I/O exception has occurred.
542      */    
543     private List<String> getMatchingDocuments(String pattern)
544         throws IOException
545     {
546         String[] validPaths = getDocumentPaths("/", pattern);
547         
548         List<String> list = new ArrayList<String>();
549         for (int i = 0; i < validPaths.length; i++)
550         {
551             String validPath = validPaths[i];
552             
553             // adjust path to make sure it is relative to base path
554             if (this.getRootPath() != null)
555             {
556                 if (validPath.startsWith(this.getRootPath()))
557                 {
558                     validPath = validPath.substring(this.getRootPath().length());
559                 }
560             }
561             
562             if (validPath.startsWith("/"))
563             {
564                 validPath = validPath.substring(1);
565             }
566             
567             list.add(validPath);
568         }
569         
570         return list;
571     }
572 
573     /* (non-Javadoc)
574      * @see org.alfresco.web.scripts.Store#lastModified(java.lang.String)
575      */
576     public long lastModified(String documentPath)
577         throws IOException
578     {
579         long lastModified = -1;
580         
581         String jarName = toAbsolutePath(getRootPath());
582         
583         JarFile jarFile = getJarFile();
584         if (jarFile != null)
585         {
586             JarEntry jarEntry = jarFile.getJarEntry(jarName);
587             if (jarEntry != null)
588             {
589                 lastModified = jarEntry.getTime();
590             }
591         }
592         
593         return lastModified;
594     }
595 
596     /* (non-Javadoc)
597      * @see org.alfresco.web.scripts.Store#hasDocument(java.lang.String)
598      */
599     public boolean hasDocument(String documentPath)
600     {
601         return documents.contains(documentPath);
602     }
603 
604     /* (non-Javadoc)
605      * @see org.alfresco.web.scripts.Store#getDescriptionDocument(java.lang.String)
606      */
607     public InputStream getDocument(String documentPath)
608         throws IOException
609     {
610         InputStream is = null;
611         
612         String jarName = toAbsolutePath(documentPath);
613         
614         JarFile jarFile = getJarFile();
615         if (jarFile != null)
616         {
617             JarEntry jarEntry = jarFile.getJarEntry(jarName);
618             if (jarEntry != null)
619             {
620                 is = jarFile.getInputStream(jarEntry);
621             }
622             
623         }
624         
625         return is;
626     }
627 
628     /* (non-Javadoc)
629      * @see org.alfresco.web.scripts.Store#createDocument(java.lang.String, java.lang.String)
630      */
631     public void createDocument(String documentPath, String content) throws IOException
632     {
633         throw new WebScriptsPlatformException("Method createDocument not supported against Jar Store");
634     }
635 
636     /* (non-Javadoc)
637      * @see org.alfresco.web.scripts.Store#updateDocument(java.lang.String, java.lang.String)
638      */
639     public void updateDocument(String documentPath, String content) throws IOException
640     {
641         throw new WebScriptsPlatformException("Method updateDocument not supported against Jar Store");
642     }
643 
644     /* (non-Javadoc)
645      * @see org.alfresco.web.scripts.Store#removeDocument(java.lang.String)
646      */
647     public boolean removeDocument(String documentPath)
648         throws IOException
649     {
650         throw new WebScriptsPlatformException("Method removeDocument not supported against Jar Store");
651     }
652 
653     /* (non-Javadoc)
654      * @see org.alfresco.web.scripts.Store#getTemplateLoader()
655      */
656     public TemplateLoader getTemplateLoader()
657     {
658         JarFile jarFile = getJarFile();
659         return new JarStoreTemplateLoader(jarFile);
660     }
661 
662     /* (non-Javadoc)
663      * @see org.alfresco.web.scripts.Store#getScriptLoader()
664      */
665     public ScriptLoader getScriptLoader()
666     {
667         JarFile jarFile = getJarFile();
668         return new JarScriptLoader(jarFile);
669     }
670     
671     /* (non-Javadoc)
672      * @see java.lang.Object#toString()
673      */
674     @Override
675     public String toString()
676     {
677         return getBasePath() + this.getRootPath();
678     }
679 
680 
681     /**
682      * Jar based script loader
683      *
684      * @author davidc
685      */
686     private class JarScriptLoader implements ScriptLoader
687     {
688         protected JarFile jarFile = null;
689         
690         public JarScriptLoader(JarFile jarFile)
691         {
692             this.jarFile = jarFile;
693         }
694 
695         /* (non-Javadoc)
696          * @see org.alfresco.web.scripts.ScriptLoader#getScriptLocation(java.lang.String)
697          */
698         public ScriptContent getScript(String documentPath)
699         {
700             ScriptContent scriptContent = null;
701             
702             String jarName = toAbsolutePath(documentPath);
703 
704             JarEntry jarEntry = jarFile.getJarEntry(jarName);
705             if (jarEntry != null)
706             {
707                 scriptContent = new ClassPathScriptContent(jarFile, documentPath);
708             }
709             
710             return scriptContent;
711         }
712     }
713 
714     /**
715      * Jar file script content
716      *
717      * @author muzquiano
718      */
719     private class ClassPathScriptContent implements ScriptContent
720     {
721         protected String documentPath;
722         protected JarFile jarFile;
723         
724         public ClassPathScriptContent(JarFile jarFile, String documentPath)
725         {
726             this.jarFile = jarFile;
727             this.documentPath = documentPath;
728         }
729 
730         /* (non-Javadoc)
731          * @see org.alfresco.service.cmr.repository.ScriptLocation#getInputStream()
732          */
733         public InputStream getInputStream()
734         {
735             InputStream is = null;
736             
737             String jarName = toAbsolutePath(documentPath);
738             
739             JarEntry jarEntry = jarFile.getJarEntry(jarName);
740             if (jarEntry != null)
741             {
742                 try
743                 {
744                     is = jarFile.getInputStream(jarEntry);
745                 }
746                 catch(IOException ioe)
747                 {
748                     logger.error("Error while getting input stream for path: " + documentPath + " in jar file: " + jarFile.getName());
749                 }
750             }
751 
752             return is;
753         }
754 
755         /* (non-Javadoc)
756          * @see org.alfresco.service.cmr.repository.ScriptLocation#getReader()
757          */
758         public Reader getReader()
759         {
760             try
761             {
762                 return new InputStreamReader(getInputStream(), "UTF-8");
763             }
764             catch (UnsupportedEncodingException e)
765             {
766                 throw new WebScriptsPlatformException("Unsupported Encoding", e);
767             }
768         }
769 
770         /* (non-Javadoc)
771          * @see org.alfresco.web.scripts.ScriptContent#getPath()
772          */
773         public String getPath()
774         {
775             return documentPath;
776         }
777 
778         /* (non-Javadoc)
779          * @see org.alfresco.web.scripts.ScriptContent#getPathDescription()
780          */
781         public String getPathDescription()
782         {
783             return "/" + documentPath + " (in jar store " + jarFile.getName() + ")";
784         }
785 
786         /* (non-Javadoc)
787          * @see org.alfresco.web.scripts.ScriptContent#isCachable()
788          */
789         public boolean isCachable()
790         {
791             return true;
792         }
793 
794         /* (non-Javadoc)
795          * @see org.alfresco.web.scripts.ScriptContent#isSecure()
796          */
797         public boolean isSecure()
798         {
799             return true;
800         }
801 
802         @Override
803         public String toString()
804         {
805             return getPathDescription();
806         }
807     }
808     
809     /**
810      * Jar store implementation of a Template Loader
811      */
812     private class JarStoreTemplateLoader implements TemplateLoader
813     {
814         private JarFile jarFile = null;
815         
816         public JarStoreTemplateLoader(JarFile jarFile)
817         {
818             this.jarFile = jarFile;
819         }
820         /**
821          * @see freemarker.cache.TemplateLoader#closeTemplateSource(java.lang.Object)
822          */
823         public void closeTemplateSource(Object templateSource)
824                 throws IOException
825         {
826             // nothing to do - we return a reader to fully retrieved in-memory
827             // data
828         }
829 
830         /**
831          * @see freemarker.cache.TemplateLoader#findTemplateSource(java.lang.String)
832          */
833         public Object findTemplateSource(String name) throws IOException
834         {
835             JarStoreTemplateSource source = null;
836             if (hasDocument(name))
837             {
838                 source = new JarStoreTemplateSource(jarFile, name);
839             }
840             return source;
841         }
842 
843         /**
844          * @see freemarker.cache.TemplateLoader#getLastModified(java.lang.Object)
845          */
846         public long getLastModified(Object templateSource)
847         {
848             return ((JarStoreTemplateSource) templateSource).lastModified();
849         }
850 
851         /**
852          * @see freemarker.cache.TemplateLoader#getReader(java.lang.Object,
853          *      java.lang.String)
854          */
855         public Reader getReader(Object templateSource, String encoding)
856                 throws IOException
857         {
858             return ((JarStoreTemplateSource) templateSource).getReader(encoding);
859         }
860     }
861 
862     /**
863      * Template Source - loads from a Jar Store.
864      */
865     private class JarStoreTemplateSource
866     {
867         private String templatePath;
868         private JarFile jarFile;
869 
870         private JarStoreTemplateSource(JarFile jarFile, String templatePath)
871         {
872             this.jarFile = jarFile;
873             this.templatePath = templatePath;
874         }
875 
876         private long lastModified()
877         {
878             long lastModified = -1;
879             
880             String jarName = toAbsolutePath(templatePath);
881             
882             JarEntry jarEntry = jarFile.getJarEntry(jarName);
883             if (jarEntry != null)
884             {
885                 lastModified = jarEntry.getTime();
886             }
887             
888             return lastModified;
889         }
890 
891         private Reader getReader(String encoding) throws IOException
892         {
893             Reader reader = null;
894             
895             String jarName = toAbsolutePath(templatePath);
896             
897             JarEntry jarEntry = jarFile.getJarEntry(jarName);
898             if (jarEntry != null)
899             {
900                 InputStream is = jarFile.getInputStream(jarEntry);
901                 reader = new InputStreamReader(is);
902             }
903             
904             return reader;
905         }
906     }
907     
908 }