1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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
105
106
107
108 public void setAllowedResourcePaths(List<String> allowedResourcePaths)
109 {
110 this.allowedResourcePaths = new HashSet<String>(allowedResourcePaths);
111 }
112
113
114
115
116 public void setServletContext(ServletContext servletContext)
117 {
118 this.servletContext = servletContext;
119 }
120
121
122
123
124
125
126 public void setApplicationContext(ApplicationContext applicationContext)
127 {
128 this.applicationContext = applicationContext;
129 }
130
131
132
133
134
135
136 public ApplicationContext getApplicationContext()
137 {
138 return applicationContext;
139 }
140
141
142
143
144
145
146 public void setJarAutoDiscoveryClassPath(String jarAutoDiscoveryClassPath)
147 {
148 this.jarAutoDiscoveryClassPath = jarAutoDiscoveryClassPath;
149 }
150
151
152
153
154
155
156 public String getJarAutoDiscoveryClassPath()
157 {
158 return this.jarAutoDiscoveryClassPath;
159 }
160
161
162
163
164
165
166 public void setJarPath(String jarPath)
167 {
168 this.jarPath = jarPath;
169 }
170
171
172
173
174
175
176 public String getJarPath()
177 {
178 return this.jarPath;
179 }
180
181
182
183
184
185
186 public void setRootPath(String rootPath)
187 {
188 this.rootPath = rootPath;
189 }
190
191
192
193
194
195
196 public String getRootPath()
197 {
198 return this.rootPath;
199 }
200
201
202
203
204
205
206
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
227
228
229
230
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
247
248
249
250
251
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
273
274
275
276
277
278
279 public void setMustExist(boolean mustExist)
280 {
281 this.mustExist = mustExist;
282 }
283
284
285
286
287
288
289 protected JarFile getJarFile()
290 {
291 JarFile jar = null;
292
293 String sourceString = getJarPath();
294
295 try
296 {
297
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
308
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
321
322
323 public void init()
324 {
325
326 if (getRootPath() == null)
327 {
328 setRootPath("");
329 }
330 if ("/".equals(getRootPath()))
331 {
332 setRootPath("");
333 }
334
335
336 String sourceString = getJarPath();
337 if (sourceString == null)
338 {
339
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
371 if (allowedResourcePaths == null)
372 {
373 allowedResourcePaths = new HashSet<String>();
374
375
376 allowedResourcePaths.add("**/*.*");
377 };
378
379
380 JarFile jarFile = getJarFile();
381 if (jarFile != null)
382 {
383 exists = true;
384 }
385
386
387 this.documents = new ArrayList<String>();
388
389
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
399
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
408 if (!entry.isDirectory() && jarName.startsWith(getRootPath()))
409 {
410
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
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
439
440
441 public boolean exists()
442 {
443 return exists;
444 }
445
446
447
448
449 public String getBasePath()
450 {
451 return jarPath;
452 }
453
454
455
456
457 public boolean isSecure()
458 {
459 return true;
460 }
461
462
463
464
465 public String[] getAllDocumentPaths()
466 {
467 return documents.toArray(new String[documents.size()]);
468 }
469
470
471
472
473 public String[] getDescriptionDocumentPaths()
474 {
475 return getDocumentPaths("/", true, "*.desc.xml");
476 }
477
478
479
480
481 public String[] getScriptDocumentPaths(WebScript script)
482 {
483 String scriptPaths = script.getDescription().getId() + ".*";
484 return getDocumentPaths("/", false, scriptPaths);
485 }
486
487
488
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
527 paths = new String[0];
528 }
529
530 return paths;
531 }
532
533
534
535
536
537
538
539
540
541
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
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
574
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
597
598
599 public boolean hasDocument(String documentPath)
600 {
601 return documents.contains(documentPath);
602 }
603
604
605
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
629
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
637
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
645
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
654
655
656 public TemplateLoader getTemplateLoader()
657 {
658 JarFile jarFile = getJarFile();
659 return new JarStoreTemplateLoader(jarFile);
660 }
661
662
663
664
665 public ScriptLoader getScriptLoader()
666 {
667 JarFile jarFile = getJarFile();
668 return new JarScriptLoader(jarFile);
669 }
670
671
672
673
674 @Override
675 public String toString()
676 {
677 return getBasePath() + this.getRootPath();
678 }
679
680
681
682
683
684
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
696
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
716
717
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
731
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
756
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
771
772
773 public String getPath()
774 {
775 return documentPath;
776 }
777
778
779
780
781 public String getPathDescription()
782 {
783 return "/" + documentPath + " (in jar store " + jarFile.getName() + ")";
784 }
785
786
787
788
789 public boolean isCachable()
790 {
791 return true;
792 }
793
794
795
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
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
822
823 public void closeTemplateSource(Object templateSource)
824 throws IOException
825 {
826
827
828 }
829
830
831
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
845
846 public long getLastModified(Object templateSource)
847 {
848 return ((JarStoreTemplateSource) templateSource).lastModified();
849 }
850
851
852
853
854
855 public Reader getReader(Object templateSource, String encoding)
856 throws IOException
857 {
858 return ((JarStoreTemplateSource) templateSource).getReader(encoding);
859 }
860 }
861
862
863
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 }