1
17 package org.apache.catalina.webresources;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.net.MalformedURLException;
23 import java.net.URISyntaxException;
24 import java.net.URL;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.HashSet;
28 import java.util.LinkedHashSet;
29 import java.util.List;
30 import java.util.Locale;
31 import java.util.Set;
32 import java.util.concurrent.ConcurrentHashMap;
33
34 import javax.management.ObjectName;
35
36 import org.apache.catalina.Context;
37 import org.apache.catalina.Host;
38 import org.apache.catalina.LifecycleException;
39 import org.apache.catalina.LifecycleState;
40 import org.apache.catalina.TrackedWebResource;
41 import org.apache.catalina.WebResource;
42 import org.apache.catalina.WebResourceRoot;
43 import org.apache.catalina.WebResourceSet;
44 import org.apache.catalina.util.LifecycleMBeanBase;
45 import org.apache.juli.logging.Log;
46 import org.apache.juli.logging.LogFactory;
47 import org.apache.tomcat.util.buf.UriUtil;
48 import org.apache.tomcat.util.compat.JreCompat;
49 import org.apache.tomcat.util.http.RequestUtil;
50 import org.apache.tomcat.util.res.StringManager;
51
52
64 public class StandardRoot extends LifecycleMBeanBase implements WebResourceRoot {
65
66 private static final Log log = LogFactory.getLog(StandardRoot.class);
67 protected static final StringManager sm = StringManager.getManager(StandardRoot.class);
68
69 private Context context;
70 private boolean allowLinking = false;
71 private final List<WebResourceSet> preResources = new ArrayList<>();
72 private WebResourceSet main;
73 private final List<WebResourceSet> classResources = new ArrayList<>();
74 private final List<WebResourceSet> jarResources = new ArrayList<>();
75 private final List<WebResourceSet> postResources = new ArrayList<>();
76
77 private final Cache cache = new Cache(this);
78 private boolean cachingAllowed = true;
79 private ObjectName cacheJmxName = null;
80
81 private boolean trackLockedFiles = false;
82 private final Set<TrackedWebResource> trackedResources =
83 Collections.newSetFromMap(new ConcurrentHashMap<TrackedWebResource,Boolean>());
84
85
86 private final List<WebResourceSet> mainResources = new ArrayList<>();
87 private final List<List<WebResourceSet>> allResources =
88 new ArrayList<>();
89 {
90 allResources.add(preResources);
91 allResources.add(mainResources);
92 allResources.add(classResources);
93 allResources.add(jarResources);
94 allResources.add(postResources);
95 }
96
97
98
104 public StandardRoot() {
105
106 }
107
108 public StandardRoot(Context context) {
109 this.context = context;
110 }
111
112 @Override
113 public String[] list(String path) {
114 return list(path, true);
115 }
116
117 private String[] list(String path, boolean validate) {
118 if (validate) {
119 path = validate(path);
120 }
121
122
123
124
125
126 HashSet<String> result = new LinkedHashSet<>();
127 for (List<WebResourceSet> list : allResources) {
128 for (WebResourceSet webResourceSet : list) {
129 if (!webResourceSet.getClassLoaderOnly()) {
130 String[] entries = webResourceSet.list(path);
131 for (String entry : entries) {
132 result.add(entry);
133 }
134 }
135 }
136 }
137 return result.toArray(new String[result.size()]);
138 }
139
140
141 @Override
142 public Set<String> listWebAppPaths(String path) {
143 path = validate(path);
144
145
146 Set<String> result = new HashSet<>();
147 for (List<WebResourceSet> list : allResources) {
148 for (WebResourceSet webResourceSet : list) {
149 if (!webResourceSet.getClassLoaderOnly()) {
150 result.addAll(webResourceSet.listWebAppPaths(path));
151 }
152 }
153 }
154 if (result.size() == 0) {
155 return null;
156 }
157 return result;
158 }
159
160 @Override
161 public boolean mkdir(String path) {
162 path = validate(path);
163
164 if (preResourceExists(path)) {
165 return false;
166 }
167
168 boolean mkdirResult = main.mkdir(path);
169
170 if (mkdirResult && isCachingAllowed()) {
171
172 cache.removeCacheEntry(path);
173 }
174 return mkdirResult;
175 }
176
177 @Override
178 public boolean write(String path, InputStream is, boolean overwrite) {
179 path = validate(path);
180
181 if (!overwrite && preResourceExists(path)) {
182 return false;
183 }
184
185 boolean writeResult = main.write(path, is, overwrite);
186
187 if (writeResult && isCachingAllowed()) {
188
189 cache.removeCacheEntry(path);
190 }
191
192 return writeResult;
193 }
194
195 private boolean preResourceExists(String path) {
196 for (WebResourceSet webResourceSet : preResources) {
197 WebResource webResource = webResourceSet.getResource(path);
198 if (webResource.exists()) {
199 return true;
200 }
201 }
202 return false;
203 }
204
205 @Override
206 public WebResource getResource(String path) {
207 return getResource(path, true, false);
208 }
209
210 protected WebResource getResource(String path, boolean validate,
211 boolean useClassLoaderResources) {
212 if (validate) {
213 path = validate(path);
214 }
215
216 if (isCachingAllowed()) {
217 return cache.getResource(path, useClassLoaderResources);
218 } else {
219 return getResourceInternal(path, useClassLoaderResources);
220 }
221 }
222
223
224 @Override
225 public WebResource getClassLoaderResource(String path) {
226 return getResource("/WEB-INF/classes" + path, true, true);
227 }
228
229
230 @Override
231 public WebResource[] getClassLoaderResources(String path) {
232 return getResources("/WEB-INF/classes" + path, true);
233 }
234
235
236
244 private String validate(String path) {
245 if (!getState().isAvailable()) {
246 throw new IllegalStateException(
247 sm.getString("standardRoot.checkStateNotStarted"));
248 }
249
250 if (path == null || path.length() == 0 || !path.startsWith("/")) {
251 throw new IllegalArgumentException(
252 sm.getString("standardRoot.invalidPath", path));
253 }
254
255 String result;
256 if (File.separatorChar == '\\') {
257
258
259 result = RequestUtil.normalize(path, true);
260 } else {
261
262
263 result = RequestUtil.normalize(path, false);
264 }
265 if (result == null || result.length() == 0 || !result.startsWith("/")) {
266 throw new IllegalArgumentException(
267 sm.getString("standardRoot.invalidPathNormal", path, result));
268 }
269
270 return result;
271 }
272
273 protected final WebResource getResourceInternal(String path,
274 boolean useClassLoaderResources) {
275 WebResource result = null;
276 WebResource virtual = null;
277 WebResource mainEmpty = null;
278 for (List<WebResourceSet> list : allResources) {
279 for (WebResourceSet webResourceSet : list) {
280 if (!useClassLoaderResources && !webResourceSet.getClassLoaderOnly() ||
281 useClassLoaderResources && !webResourceSet.getStaticOnly()) {
282 result = webResourceSet.getResource(path);
283 if (result.exists()) {
284 return result;
285 }
286 if (virtual == null) {
287 if (result.isVirtual()) {
288 virtual = result;
289 } else if (main.equals(webResourceSet)) {
290 mainEmpty = result;
291 }
292 }
293 }
294 }
295 }
296
297
298 if (virtual != null) {
299 return virtual;
300 }
301
302
303 return mainEmpty;
304 }
305
306 @Override
307 public WebResource[] getResources(String path) {
308 return getResources(path, false);
309 }
310
311 private WebResource[] getResources(String path,
312 boolean useClassLoaderResources) {
313 path = validate(path);
314
315 if (isCachingAllowed()) {
316 return cache.getResources(path, useClassLoaderResources);
317 } else {
318 return getResourcesInternal(path, useClassLoaderResources);
319 }
320 }
321
322 protected WebResource[] getResourcesInternal(String path,
323 boolean useClassLoaderResources) {
324 List<WebResource> result = new ArrayList<>();
325 for (List<WebResourceSet> list : allResources) {
326 for (WebResourceSet webResourceSet : list) {
327 if (useClassLoaderResources || !webResourceSet.getClassLoaderOnly()) {
328 WebResource webResource = webResourceSet.getResource(path);
329 if (webResource.exists()) {
330 result.add(webResource);
331 }
332 }
333 }
334 }
335
336 if (result.size() == 0) {
337 result.add(main.getResource(path));
338 }
339
340 return result.toArray(new WebResource[result.size()]);
341 }
342
343 @Override
344 public WebResource[] listResources(String path) {
345 return listResources(path, true);
346 }
347
348 protected WebResource[] listResources(String path, boolean validate) {
349 if (validate) {
350 path = validate(path);
351 }
352
353 String[] resources = list(path, false);
354 WebResource[] result = new WebResource[resources.length];
355 for (int i = 0; i < resources.length; i++) {
356 if (path.charAt(path.length() - 1) == '/') {
357 result[i] = getResource(path + resources[i], false, false);
358 } else {
359 result[i] = getResource(path + '/' + resources[i], false, false);
360 }
361 }
362 return result;
363 }
364
365
366
367
368 @Override
369 public void createWebResourceSet(ResourceSetType type, String webAppMount,
370 URL url, String internalPath) {
371 BaseLocation baseLocation = new BaseLocation(url);
372 createWebResourceSet(type, webAppMount, baseLocation.getBasePath(),
373 baseLocation.getArchivePath(), internalPath);
374 }
375
376 @Override
377 public void createWebResourceSet(ResourceSetType type, String webAppMount,
378 String base, String archivePath, String internalPath) {
379 List<WebResourceSet> resourceList;
380 WebResourceSet resourceSet;
381
382 switch (type) {
383 case PRE:
384 resourceList = preResources;
385 break;
386 case CLASSES_JAR:
387 resourceList = classResources;
388 break;
389 case RESOURCE_JAR:
390 resourceList = jarResources;
391 break;
392 case POST:
393 resourceList = postResources;
394 break;
395 default:
396 throw new IllegalArgumentException(
397 sm.getString("standardRoot.createUnknownType", type));
398 }
399
400
401
402 File file = new File(base);
403
404 if (file.isFile()) {
405 if (archivePath != null) {
406
407 resourceSet = new JarWarResourceSet(this, webAppMount, base,
408 archivePath, internalPath);
409 } else if (file.getName().toLowerCase(Locale.ENGLISH).endsWith(".jar")) {
410 resourceSet = new JarResourceSet(this, webAppMount, base,
411 internalPath);
412 } else {
413 resourceSet = new FileResourceSet(this, webAppMount, base,
414 internalPath);
415 }
416 } else if (file.isDirectory()) {
417 resourceSet =
418 new DirResourceSet(this, webAppMount, base, internalPath);
419 } else {
420 throw new IllegalArgumentException(
421 sm.getString("standardRoot.createInvalidFile", file));
422 }
423
424 if (type.equals(ResourceSetType.CLASSES_JAR)) {
425 resourceSet.setClassLoaderOnly(true);
426 } else if (type.equals(ResourceSetType.RESOURCE_JAR)) {
427 resourceSet.setStaticOnly(true);
428 }
429
430 resourceList.add(resourceSet);
431 }
432
433 @Override
434 public void addPreResources(WebResourceSet webResourceSet) {
435 webResourceSet.setRoot(this);
436 preResources.add(webResourceSet);
437 }
438
439 @Override
440 public WebResourceSet[] getPreResources() {
441 return preResources.toArray(new WebResourceSet[preResources.size()]);
442 }
443
444 @Override
445 public void addJarResources(WebResourceSet webResourceSet) {
446 webResourceSet.setRoot(this);
447 jarResources.add(webResourceSet);
448 }
449
450 @Override
451 public WebResourceSet[] getJarResources() {
452 return jarResources.toArray(new WebResourceSet[jarResources.size()]);
453 }
454
455 @Override
456 public void addPostResources(WebResourceSet webResourceSet) {
457 webResourceSet.setRoot(this);
458 postResources.add(webResourceSet);
459 }
460
461 @Override
462 public WebResourceSet[] getPostResources() {
463 return postResources.toArray(new WebResourceSet[postResources.size()]);
464 }
465
466 protected WebResourceSet[] getClassResources() {
467 return classResources.toArray(new WebResourceSet[classResources.size()]);
468 }
469
470 protected void addClassResources(WebResourceSet webResourceSet) {
471 webResourceSet.setRoot(this);
472 classResources.add(webResourceSet);
473 }
474
475 @Override
476 public void setAllowLinking(boolean allowLinking) {
477 if (this.allowLinking != allowLinking && cachingAllowed) {
478
479 cache.clear();
480 }
481 this.allowLinking = allowLinking;
482 }
483
484 @Override
485 public boolean getAllowLinking() {
486 return allowLinking;
487 }
488
489 @Override
490 public void setCachingAllowed(boolean cachingAllowed) {
491 this.cachingAllowed = cachingAllowed;
492 if (!cachingAllowed) {
493 cache.clear();
494 }
495 }
496
497 @Override
498 public boolean isCachingAllowed() {
499 return cachingAllowed;
500 }
501
502 @Override
503 public long getCacheTtl() {
504 return cache.getTtl();
505 }
506
507 @Override
508 public void setCacheTtl(long cacheTtl) {
509 cache.setTtl(cacheTtl);
510 }
511
512 @Override
513 public long getCacheMaxSize() {
514 return cache.getMaxSize();
515 }
516
517 @Override
518 public void setCacheMaxSize(long cacheMaxSize) {
519 cache.setMaxSize(cacheMaxSize);
520 }
521
522 @Override
523 public void setCacheObjectMaxSize(int cacheObjectMaxSize) {
524 cache.setObjectMaxSize(cacheObjectMaxSize);
525
526
527 if (getState().isAvailable()) {
528 cache.enforceObjectMaxSizeLimit();
529 }
530 }
531
532 @Override
533 public int getCacheObjectMaxSize() {
534 return cache.getObjectMaxSize();
535 }
536
537 @Override
538 public void setTrackLockedFiles(boolean trackLockedFiles) {
539 this.trackLockedFiles = trackLockedFiles;
540 if (!trackLockedFiles) {
541 trackedResources.clear();
542 }
543 }
544
545 @Override
546 public boolean getTrackLockedFiles() {
547 return trackLockedFiles;
548 }
549
550 public List<String> getTrackedResources() {
551 List<String> result = new ArrayList<>(trackedResources.size());
552 for (TrackedWebResource resource : trackedResources) {
553 result.add(resource.toString());
554 }
555 return result;
556 }
557
558 @Override
559 public Context getContext() {
560 return context;
561 }
562
563 @Override
564 public void setContext(Context context) {
565 this.context = context;
566 }
567
568
582 protected void processWebInfLib() throws LifecycleException {
583 WebResource[] possibleJars = listResources("/WEB-INF/lib", false);
584
585 for (WebResource possibleJar : possibleJars) {
586 if (possibleJar.isFile() && possibleJar.getName().endsWith(".jar")) {
587 createWebResourceSet(ResourceSetType.CLASSES_JAR,
588 "/WEB-INF/classes", possibleJar.getURL(), "/");
589 }
590 }
591 }
592
593
597 protected final void setMainResources(WebResourceSet main) {
598 this.main = main;
599 mainResources.clear();
600 mainResources.add(main);
601 }
602
603
604 @Override
605 public void backgroundProcess() {
606 cache.backgroundProcess();
607 gc();
608 }
609
610
611
612 @Override
613 public void gc() {
614 for (List<WebResourceSet> list : allResources) {
615 for (WebResourceSet webResourceSet : list) {
616 webResourceSet.gc();
617 }
618 }
619 }
620
621 @Override
622 public void registerTrackedResource(TrackedWebResource trackedResource) {
623 trackedResources.add(trackedResource);
624 }
625
626
627 @Override
628 public void deregisterTrackedResource(TrackedWebResource trackedResource) {
629 trackedResources.remove(trackedResource);
630 }
631
632
633 @Override
634 public List<URL> getBaseUrls() {
635 List<URL> result = new ArrayList<>();
636 for (List<WebResourceSet> list : allResources) {
637 for (WebResourceSet webResourceSet : list) {
638 if (!webResourceSet.getClassLoaderOnly()) {
639 URL url = webResourceSet.getBaseUrl();
640 if (url != null) {
641 result.add(url);
642 }
643 }
644 }
645 }
646 return result;
647 }
648
649
650
651
656 protected boolean isPackedWarFile() {
657 return main instanceof WarResourceSet && preResources.isEmpty() && postResources.isEmpty();
658 }
659
660
661
662 @Override
663 protected String getDomainInternal() {
664 return context.getDomain();
665 }
666
667 @Override
668 protected String getObjectNameKeyProperties() {
669 StringBuilder keyProperties = new StringBuilder("type=WebResourceRoot");
670 keyProperties.append(context.getMBeanKeyProperties());
671
672 return keyProperties.toString();
673 }
674
675
676
677 @Override
678 protected void initInternal() throws LifecycleException {
679 super.initInternal();
680
681 cacheJmxName = register(cache, getObjectNameKeyProperties() + ",name=Cache");
682
683 registerURLStreamHandlerFactory();
684
685 if (context == null) {
686 throw new IllegalStateException(
687 sm.getString("standardRoot.noContext"));
688 }
689
690 for (List<WebResourceSet> list : allResources) {
691 for (WebResourceSet webResourceSet : list) {
692 webResourceSet.init();
693 }
694 }
695 }
696
697 protected void registerURLStreamHandlerFactory() {
698 if (!JreCompat.isGraalAvailable()) {
699
700
701 TomcatURLStreamHandlerFactory.register();
702 }
703 }
704
705 @Override
706 protected void startInternal() throws LifecycleException {
707 mainResources.clear();
708
709 main = createMainResourceSet();
710
711 mainResources.add(main);
712
713 for (List<WebResourceSet> list : allResources) {
714
715 if (list != classResources) {
716 for (WebResourceSet webResourceSet : list) {
717 webResourceSet.start();
718 }
719 }
720 }
721
722
723
724 processWebInfLib();
725
726 for (WebResourceSet classResource : classResources) {
727 classResource.start();
728 }
729
730 cache.enforceObjectMaxSizeLimit();
731
732 setState(LifecycleState.STARTING);
733 }
734
735 protected WebResourceSet createMainResourceSet() {
736 String docBase = context.getDocBase();
737
738 WebResourceSet mainResourceSet;
739 if (docBase == null) {
740 mainResourceSet = new EmptyResourceSet(this);
741 } else {
742 File f = new File(docBase);
743 if (!f.isAbsolute()) {
744 f = new File(((Host)context.getParent()).getAppBaseFile(), f.getPath());
745 }
746 if (f.isDirectory()) {
747 mainResourceSet = new DirResourceSet(this, "/", f.getAbsolutePath(), "/");
748 } else if(f.isFile() && docBase.endsWith(".war")) {
749 mainResourceSet = new WarResourceSet(this, "/", f.getAbsolutePath());
750 } else {
751 throw new IllegalArgumentException(
752 sm.getString("standardRoot.startInvalidMain",
753 f.getAbsolutePath()));
754 }
755 }
756
757 return mainResourceSet;
758 }
759
760 @Override
761 protected void stopInternal() throws LifecycleException {
762 for (List<WebResourceSet> list : allResources) {
763 for (WebResourceSet webResourceSet : list) {
764 webResourceSet.stop();
765 }
766 }
767
768 if (main != null) {
769 main.destroy();
770 }
771 mainResources.clear();
772
773 for (WebResourceSet webResourceSet : jarResources) {
774 webResourceSet.destroy();
775 }
776 jarResources.clear();
777
778 for (WebResourceSet webResourceSet : classResources) {
779 webResourceSet.destroy();
780 }
781 classResources.clear();
782
783 for (TrackedWebResource trackedResource : trackedResources) {
784 log.error(sm.getString("standardRoot.lockedFile",
785 context.getName(),
786 trackedResource.getName()),
787 trackedResource.getCreatedBy());
788 try {
789 trackedResource.close();
790 } catch (IOException e) {
791
792 }
793 }
794 cache.clear();
795
796 setState(LifecycleState.STOPPING);
797 }
798
799 @Override
800 protected void destroyInternal() throws LifecycleException {
801 for (List<WebResourceSet> list : allResources) {
802 for (WebResourceSet webResourceSet : list) {
803 webResourceSet.destroy();
804 }
805 }
806
807 unregister(cacheJmxName);
808
809 super.destroyInternal();
810 }
811
812
813
814 static class BaseLocation {
815
816 private final String basePath;
817 private final String archivePath;
818
819 BaseLocation(URL url) {
820 File f = null;
821
822 if ("jar".equals(url.getProtocol()) || "war".equals(url.getProtocol())) {
823 String jarUrl = url.toString();
824 int endOfFileUrl = -1;
825 if ("jar".equals(url.getProtocol())) {
826 endOfFileUrl = jarUrl.indexOf("!/");
827 } else {
828 endOfFileUrl = jarUrl.indexOf(UriUtil.getWarSeparator());
829 }
830 String fileUrl = jarUrl.substring(4, endOfFileUrl);
831 try {
832 f = new File(new URL(fileUrl).toURI());
833 } catch (MalformedURLException | URISyntaxException e) {
834 throw new IllegalArgumentException(e);
835 }
836 int startOfArchivePath = endOfFileUrl + 2;
837 if (jarUrl.length() > startOfArchivePath) {
838 archivePath = jarUrl.substring(startOfArchivePath);
839 } else {
840 archivePath = null;
841 }
842 } else if ("file".equals(url.getProtocol())){
843 try {
844 f = new File(url.toURI());
845 } catch (URISyntaxException e) {
846 throw new IllegalArgumentException(e);
847 }
848 archivePath = null;
849 } else {
850 throw new IllegalArgumentException(sm.getString(
851 "standardRoot.unsupportedProtocol", url.getProtocol()));
852 }
853
854 basePath = f.getAbsolutePath();
855 }
856
857
858 String getBasePath() {
859 return basePath;
860 }
861
862
863 String getArchivePath() {
864 return archivePath;
865 }
866 }
867 }
868