1
2
3
4
5
6
7
8
9
10
11 package psiprobe;
12
13 import java.io.File;
14 import java.io.IOException;
15 import java.lang.management.ManagementFactory;
16 import java.net.URI;
17 import java.net.URISyntaxException;
18 import java.net.URL;
19 import java.net.URLClassLoader;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Map.Entry;
28 import java.util.Set;
29
30 import javax.management.MBeanServer;
31 import javax.management.MalformedObjectNameException;
32 import javax.management.ObjectName;
33 import javax.naming.NamingException;
34 import javax.servlet.ServletConfig;
35 import javax.servlet.ServletContext;
36
37 import org.apache.catalina.Container;
38 import org.apache.catalina.Context;
39 import org.apache.catalina.Engine;
40 import org.apache.catalina.Host;
41 import org.apache.catalina.Service;
42 import org.apache.catalina.Valve;
43 import org.apache.catalina.Wrapper;
44 import org.apache.catalina.connector.Connector;
45 import org.apache.catalina.core.StandardContext;
46 import org.apache.jasper.EmbeddedServletOptions;
47 import org.apache.jasper.JspCompilationContext;
48 import org.apache.jasper.Options;
49 import org.apache.jasper.compiler.Compiler;
50 import org.apache.jasper.compiler.JspRuntimeContext;
51 import org.apache.naming.ContextBindings;
52 import org.apache.naming.factory.ResourceLinkFactory;
53 import org.apache.tomcat.util.descriptor.web.ContextResourceLink;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56 import org.springframework.util.ClassUtils;
57
58 import psiprobe.beans.ResourceResolverBean;
59 import psiprobe.model.FilterMapping;
60 import psiprobe.model.jsp.Item;
61 import psiprobe.model.jsp.Summary;
62
63
64
65
66
67 public abstract class AbstractTomcatContainer implements TomcatContainer {
68
69
70 protected final Logger logger = LoggerFactory.getLogger(getClass());
71
72
73 private static final String NO_JSP_SERVLET = "Context '{}' does not have 'JSP' servlet";
74
75
76 protected Host host;
77
78
79 protected Connector[] connectors;
80
81
82 protected ObjectName objectNameDeployer;
83
84
85 protected MBeanServer mbeanServer;
86
87
88 public enum FilterMapType {
89
90
91 URL,
92
93
94 SERVLET_NAME
95 }
96
97 @Override
98 public void setWrapper(Wrapper wrapper) {
99 Valve valve = createValve();
100 if (wrapper != null) {
101 host = (Host) wrapper.getParent().getParent();
102 Engine engine = (Engine) host.getParent();
103 Service service = engine.getService();
104 connectors = service.findConnectors();
105 try {
106 objectNameDeployer =
107 new ObjectName(host.getParent().getName() + ":type=Deployer,host=" + host.getName());
108 } catch (MalformedObjectNameException e) {
109 logger.trace("", e);
110 }
111 host.getPipeline().addValve(valve);
112 mbeanServer = ManagementFactory.getPlatformMBeanServer();
113 } else if (host != null) {
114 host.getPipeline().removeValve(valve);
115 }
116 }
117
118 @Override
119 public File getAppBase() {
120 File base = new File(host.getAppBase());
121 if (!base.isAbsolute()) {
122 base = new File(System.getProperty("catalina.base"), host.getAppBase());
123 }
124 return base;
125 }
126
127 @Override
128 public String getConfigBase() {
129 File configBase = new File(System.getProperty("catalina.base"), "conf");
130 Container baseHost = null;
131 Container thisContainer = host;
132 while (thisContainer != null) {
133 if (thisContainer instanceof Host) {
134 baseHost = thisContainer;
135 }
136 thisContainer = thisContainer.getParent();
137 }
138 if (baseHost != null) {
139 configBase = new File(configBase, baseHost.getName());
140 }
141 return configBase.getAbsolutePath();
142 }
143
144 @Override
145 public String getHostName() {
146 return host.getName();
147 }
148
149 @Override
150 public String getName() {
151 return host.getParent().getName();
152 }
153
154 @Override
155 public List<Context> findContexts() {
156 List<Context> results = new ArrayList<>();
157 for (Container child : host.findChildren()) {
158 if (child instanceof Context) {
159 results.add((Context) child);
160 }
161 }
162 return results;
163 }
164
165 @Override
166 public List<Connector> findConnectors() {
167 return Collections.unmodifiableList(Arrays.asList(connectors));
168 }
169
170 @Override
171 public boolean installContext(String contextName) throws Exception {
172 contextName = formatContextName(contextName);
173 installContextInternal(contextName);
174 return findContext(contextName) != null;
175 }
176
177 @Override
178 public void stop(String name) throws Exception {
179 Context ctx = findContext(name);
180 if (ctx != null) {
181 ctx.stop();
182 }
183 }
184
185 @Override
186 public void start(String name) throws Exception {
187 Context ctx = findContext(name);
188 if (ctx != null) {
189 ctx.start();
190 }
191 }
192
193 @Override
194 public void remove(String name) throws Exception {
195 name = formatContextName(name);
196 Context ctx = findContext(name);
197
198 if (ctx != null) {
199
200 try {
201 stop(name);
202 } catch (Exception e) {
203 logger.info("Stopping '{}' threw this exception:", name, e);
204 }
205
206 File appDir;
207 File docBase = new File(ctx.getDocBase());
208
209 if (!docBase.isAbsolute()) {
210 appDir = new File(getAppBase(), ctx.getDocBase());
211 } else {
212 appDir = docBase;
213 }
214
215 logger.debug("Deleting '{}'", appDir.getAbsolutePath());
216 Utils.delete(appDir);
217
218 String warFilename = formatContextFilename(name);
219 File warFile = new File(getAppBase(), warFilename + ".war");
220 logger.debug("Deleting '{}'", warFile.getAbsolutePath());
221 Utils.delete(warFile);
222
223 File configFile = getConfigFile(ctx);
224 if (configFile != null) {
225 logger.debug("Deleting '{}'", configFile.getAbsolutePath());
226 Utils.delete(configFile);
227 }
228
229 removeInternal(name);
230 }
231 }
232
233
234
235
236
237
238
239
240 private void removeInternal(String name) throws Exception {
241 checkChanges(name);
242 }
243
244 @Override
245 public void installWar(String name) throws Exception {
246 checkChanges(name);
247 }
248
249
250
251
252
253
254
255
256 private void installContextInternal(String name) throws Exception {
257 checkChanges(name);
258 }
259
260 @Override
261 public Context findContext(String name) {
262 String safeName = formatContextName(name);
263 if (safeName == null) {
264 return null;
265 }
266 Context result = findContextInternal(safeName);
267 if (result == null && safeName.isEmpty()) {
268 result = findContextInternal("/");
269 }
270 return result;
271 }
272
273 @Override
274 public String formatContextName(String name) {
275 if (name == null) {
276 return null;
277 }
278 String result = name.trim();
279 if (!result.startsWith("/")) {
280 result = "/" + result;
281 }
282 if ("/".equals(result) || "/ROOT".equals(result)) {
283 result = "";
284 }
285
286 if (result.startsWith("/ROOT##")) {
287 result = result.substring(5);
288 }
289
290 if (result.startsWith("/##")) {
291 result = result.substring(1);
292 }
293 return result;
294 }
295
296 @Override
297 public String formatContextFilename(String contextName) {
298 if (contextName == null) {
299 return null;
300 }
301 if (contextName.isEmpty()) {
302 return "ROOT";
303 }
304 if (contextName.startsWith("/")) {
305 return contextName.substring(1);
306 }
307 return contextName;
308 }
309
310 @Override
311 public void discardWorkDir(Context context) {
312 if (context instanceof StandardContext) {
313 StandardContext standardContext = (StandardContext) context;
314 String path = standardContext.getWorkPath();
315 logger.info("Discarding '{}'", path);
316 Utils.delete(new File(path, "org"));
317 } else {
318 logger.error("context '{}' is not an instance of '{}', expected StandardContext",
319 context.getName(), context.getClass().getName());
320 }
321 }
322
323 @Override
324 public String getServletFileNameForJsp(Context context, String jspName) {
325 String servletName = null;
326
327 ServletConfig servletConfig = (ServletConfig) context.findChild("jsp");
328 if (servletConfig != null) {
329 ServletContext sctx = context.getServletContext();
330 Options opt = new EmbeddedServletOptions(servletConfig, sctx);
331 JspRuntimeContext jrctx = new JspRuntimeContext(sctx, opt);
332 JspCompilationContext jcctx = createJspCompilationContext(jspName, opt, sctx, jrctx, null);
333 servletName = jcctx.getServletJavaFileName();
334 } else {
335 logger.error(NO_JSP_SERVLET, context.getName());
336 }
337 return servletName;
338 }
339
340 @Override
341 public void recompileJsps(Context context, Summary summary, List<String> names) {
342 ServletConfig servletConfig = (ServletConfig) context.findChild("jsp");
343 if (servletConfig != null) {
344 if (summary != null) {
345 synchronized (servletConfig) {
346 ServletContext sctx = context.getServletContext();
347 Options opt = new EmbeddedServletOptions(servletConfig, sctx);
348
349 JspRuntimeContext jrctx = new JspRuntimeContext(sctx, opt);
350
351
352
353
354 try (URLClassLoader classLoader =
355 new URLClassLoader(new URL[0], context.getLoader().getClassLoader())) {
356 for (String name : names) {
357 long time = System.currentTimeMillis();
358 JspCompilationContext jcctx =
359 createJspCompilationContext(name, opt, sctx, jrctx, classLoader);
360 ClassLoader prevCl = ClassUtils.overrideThreadContextClassLoader(classLoader);
361 try {
362 Item item = summary.getItems().get(name);
363 if (item != null) {
364 try {
365 Compiler compiler = jcctx.createCompiler();
366 compiler.compile();
367 item.setState(Item.STATE_READY);
368 item.setException(null);
369 logger.info("Compiled '{}': OK", name);
370 } catch (Exception e) {
371 item.setState(Item.STATE_FAILED);
372 item.setException(e);
373 logger.error("Compiled '{}': FAILED", name, e);
374 }
375 item.setCompileTime(System.currentTimeMillis() - time);
376 } else {
377 logger.error("{} is not on the summary list, ignored", name);
378 }
379 } finally {
380 ClassUtils.overrideThreadContextClassLoader(prevCl);
381 }
382 }
383 } catch (IOException e) {
384 this.logger.error("", e);
385 } finally {
386 jrctx.destroy();
387 }
388 }
389 } else {
390 logger.error("summary is null for '{}', request ignored", context.getName());
391 }
392 } else {
393 logger.error(NO_JSP_SERVLET, context.getName());
394 }
395 }
396
397 @Override
398 public void listContextJsps(Context context, Summary summary, boolean compile) {
399 ServletConfig servletConfig = (ServletConfig) context.findChild("jsp");
400 if (servletConfig != null) {
401 synchronized (servletConfig) {
402 ServletContext sctx = context.getServletContext();
403 Options opt = new EmbeddedServletOptions(servletConfig, sctx);
404
405 JspRuntimeContext jrctx = new JspRuntimeContext(sctx, opt);
406 try {
407 if (summary.getItems() == null) {
408 summary.setItems(new HashMap<>());
409 }
410
411
412
413
414 for (Item item : summary.getItems().values()) {
415 item.setMissing(true);
416 }
417
418
419
420
421
422 try (URLClassLoader urlcl =
423 new URLClassLoader(new URL[0], context.getLoader().getClassLoader())) {
424
425 compileItem("/", opt, context, jrctx, summary, urlcl, 0, compile);
426 } catch (IOException e) {
427 this.logger.error("", e);
428 }
429 } finally {
430 jrctx.destroy();
431 }
432 }
433
434
435
436
437 Map<String, Item> hashMap = new HashMap<>();
438 for (Entry<String, Item> entry : summary.getItems().entrySet()) {
439 if (!entry.getValue().isMissing()) {
440 hashMap.put(entry.getKey(), entry.getValue());
441 }
442 }
443
444 summary.setItems(hashMap);
445 } else {
446 logger.error(NO_JSP_SERVLET, context.getName());
447 }
448 }
449
450 @Override
451 public boolean getAvailable(Context context) {
452 return context.getState().isAvailable();
453 }
454
455 @Override
456 public File getConfigFile(Context context) {
457 URL configUrl = context.getConfigFile();
458 if (configUrl != null) {
459 try {
460 URI configUri = configUrl.toURI();
461 if ("file".equals(configUri.getScheme())) {
462 return new File(configUri.getPath());
463 }
464 } catch (URISyntaxException ex) {
465 logger.error("Could not convert URL to URI: '{}'", configUrl, ex);
466 }
467 }
468 return null;
469 }
470
471 @Override
472 public void bindToContext(Context context) throws NamingException {
473 changeContextBinding(context, true);
474 }
475
476 @Override
477 public void unbindFromContext(Context context) throws NamingException {
478 changeContextBinding(context, false);
479 }
480
481
482
483
484
485
486 protected void registerGlobalResourceAccess(ContextResourceLink resourceLink) {
487 ResourceLinkFactory.registerGlobalResourceAccess(ResourceResolverBean.getGlobalNamingContext(),
488 resourceLink.getName(), resourceLink.getGlobal());
489 }
490
491
492
493
494
495
496
497
498
499 private void changeContextBinding(Context context, boolean bind) throws NamingException {
500 Object token = getNamingToken(context);
501 ClassLoader loader = Thread.currentThread().getContextClassLoader();
502 if (bind) {
503 ContextBindings.bindClassLoader(context, token, loader);
504 } else {
505 ContextBindings.unbindClassLoader(context, token, loader);
506 }
507 }
508
509
510
511
512
513
514
515
516
517
518
519
520
521 protected void compileItem(String jspName, Options opt, Context ctx, JspRuntimeContext jrctx,
522 Summary summary, URLClassLoader classLoader, int level, boolean compile) {
523 ServletContext sctx = ctx.getServletContext();
524 Set<String> paths = sctx.getResourcePaths(jspName);
525
526 if (paths != null) {
527 for (String name : paths) {
528 boolean isJsp =
529 name.endsWith(".jsp") || name.endsWith(".jspx") || opt.getJspConfig().isJspPage(name);
530
531 if (isJsp) {
532 JspCompilationContext jcctx =
533 createJspCompilationContext(name, opt, sctx, jrctx, classLoader);
534 ClassLoader prevCl = ClassUtils.overrideThreadContextClassLoader(classLoader);
535 try {
536 Item item = summary.getItems().get(name);
537
538 if (item == null) {
539 item = new Item();
540 item.setName(name);
541 }
542
543 item.setLevel(level);
544 item.setCompileTime(-1);
545
546 Long[] objects = this.getResourceAttributes(name, ctx);
547 item.setSize(objects[0]);
548 item.setLastModified(objects[1]);
549
550 long time = System.currentTimeMillis();
551 try {
552 Compiler compiler = jcctx.createCompiler();
553 if (compile) {
554 compiler.compile();
555 item.setState(Item.STATE_READY);
556 item.setException(null);
557 } else if (!compiler.isOutDated()) {
558 item.setState(Item.STATE_READY);
559 item.setException(null);
560 } else if (item.getState() != Item.STATE_FAILED) {
561 item.setState(Item.STATE_OOD);
562 item.setException(null);
563 }
564 logger.info("Compiled '{}': OK", name);
565 } catch (Exception e) {
566 item.setState(Item.STATE_FAILED);
567 item.setException(e);
568 logger.info("Compiled '{}': FAILED", name, e);
569 }
570 if (compile) {
571 item.setCompileTime(System.currentTimeMillis() - time);
572 }
573 item.setMissing(false);
574 summary.putItem(name, item);
575 } finally {
576 ClassUtils.overrideThreadContextClassLoader(prevCl);
577 }
578 } else {
579 compileItem(name, opt, ctx, jrctx, summary, classLoader, level + 1, compile);
580 }
581 }
582 } else {
583 logger.debug("getResourcePaths() is null for '{}'. Empty dir? Or Tomcat bug?", jspName);
584 }
585 }
586
587
588
589
590
591
592
593
594 protected Context findContextInternal(String name) {
595 return (Context) host.findChild(name);
596 }
597
598
599
600
601
602
603
604
605 protected void checkChanges(String name) throws Exception {
606 Boolean result = (Boolean) mbeanServer.invoke(objectNameDeployer, "isServiced",
607 new String[] {name}, new String[] {String.class.getName()});
608 if (!result.booleanValue()) {
609 mbeanServer.invoke(objectNameDeployer, "addServiced", new String[] {name},
610 new String[] {String.class.getName()});
611 try {
612 mbeanServer.invoke(objectNameDeployer, "check", new String[] {name},
613 new String[] {String.class.getName()});
614 } finally {
615 mbeanServer.invoke(objectNameDeployer, "removeServiced", new String[] {name},
616 new String[] {String.class.getName()});
617 }
618 }
619 }
620
621
622
623
624
625
626
627
628 protected abstract Object getNamingToken(Context context);
629
630
631
632
633
634
635
636
637
638
639
640
641 protected abstract JspCompilationContext createJspCompilationContext(String name, Options opt,
642 ServletContext sctx, JspRuntimeContext jrctx, ClassLoader classLoader);
643
644
645
646
647
648
649 protected abstract Valve createValve();
650
651
652
653
654
655
656
657
658
659
660
661 protected void addFilterMapping(String filterName, String dispatcherMap, String filterClass,
662 String[] types, Collection<FilterMapping> results, FilterMapType filterMapType) {
663 for (String type : types) {
664 FilterMapping fm = new FilterMapping();
665 if (filterMapType == FilterMapType.URL) {
666 fm.setUrl(type);
667 } else {
668 fm.setServletName(type);
669 }
670 fm.setFilterName(filterName);
671 fm.setDispatcherMap(dispatcherMap);
672 fm.setFilterClass(filterClass);
673 results.add(fm);
674 }
675 }
676
677 }