View Javadoc
1   /*
2    * Licensed under the GPL License. You may not use this file except in compliance with the License.
3    * You may obtain a copy of the License at
4    *
5    *   https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
6    *
7    * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
8    * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
9    * PURPOSE.
10   */
11  package psiprobe.tools;
12  
13  import java.io.Serializable;
14  import java.lang.reflect.InvocationTargetException;
15  import java.util.ArrayList;
16  import java.util.Arrays;
17  import java.util.Collections;
18  import java.util.Date;
19  import java.util.HashSet;
20  import java.util.List;
21  import java.util.Locale;
22  import java.util.Set;
23  
24  import javax.naming.NamingException;
25  import javax.servlet.ServletContext;
26  import javax.servlet.http.HttpSession;
27  
28  import org.apache.catalina.Container;
29  import org.apache.catalina.Context;
30  import org.apache.catalina.Session;
31  import org.apache.catalina.Wrapper;
32  import org.apache.catalina.core.StandardWrapper;
33  import org.apache.commons.lang3.reflect.MethodUtils;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  import org.springframework.util.ClassUtils;
37  
38  import psiprobe.beans.ContainerWrapperBean;
39  import psiprobe.beans.ResourceResolver;
40  import psiprobe.model.Application;
41  import psiprobe.model.ApplicationParam;
42  import psiprobe.model.ApplicationResource;
43  import psiprobe.model.ApplicationSession;
44  import psiprobe.model.Attribute;
45  import psiprobe.model.FilterInfo;
46  import psiprobe.model.ServletInfo;
47  import psiprobe.model.ServletMapping;
48  
49  /**
50   * The Class ApplicationUtils.
51   */
52  public final class ApplicationUtils {
53  
54    /** The Constant logger. */
55    private static final Logger logger = LoggerFactory.getLogger(ApplicationUtils.class);
56  
57    /**
58     * Prevent Instantiation.
59     */
60    private ApplicationUtils() {
61      // Prevent Instantiation
62    }
63  
64    /**
65     * Gets the application.
66     *
67     * @param context the context
68     * @param containerWrapper the container wrapper
69     *
70     * @return the application
71     */
72    public static Application getApplication(Context context, ContainerWrapperBean containerWrapper) {
73      return getApplication(context, null, false, containerWrapper);
74    }
75  
76    /**
77     * Creates Application instance from Tomcat Context object. If ResourceResolver is passed the
78     * method will also collect additional information about the application such as session count,
79     * session attribute count, application attribute count, servlet count, servlet stats summary and
80     * datasource usage summary. Collecting additional information can be CPU intensive and time
81     * consuming so this should be avoided unless absolutely required. Some datasource implementations
82     * (c3p0) are known to be prone to internal deadlocks, so this method can also hang is datasource
83     * usage stats is to be collected.
84     *
85     * @param context the context from which to create the Application
86     * @param resourceResolver the resolver to use for resources associated with the given context
87     * @param calcSize flag which controls whether to calculate session size
88     * @param containerWrapper the wrapper for the context's root containing server
89     *
90     * @return Application object
91     */
92    public static Application getApplication(Context context, ResourceResolver resourceResolver,
93        boolean calcSize, ContainerWrapperBean containerWrapper) {
94  
95      // ContainerWrapperBean containerWrapper
96      logger.debug("Querying webapp: {}", context.getName());
97  
98      Application app = new Application();
99      app.setName(context.getName().length() > 0 ? context.getName() : "/");
100     app.setDocBase(context.getDocBase());
101     app.setDisplayName(context.getDisplayName());
102 
103     app.setAvailable(containerWrapper.getTomcatContainer().getAvailable(context));
104     app.setDistributable(context.getDistributable());
105     app.setSessionTimeout(context.getSessionTimeout());
106     app.setServletVersion(context.getServletContext().getMajorVersion() + "."
107         + context.getServletContext().getMinorVersion());
108 
109     if (resourceResolver != null) {
110       logger.debug("counting servlet attributes");
111 
112       app.setContextAttributeCount(
113           Collections.list(context.getServletContext().getAttributeNames()).size());
114 
115       if (app.isAvailable()) {
116         logger.debug("collecting session information");
117 
118         app.setSessionCount(context.getManager().findSessions().length);
119 
120         boolean serializable = true;
121         long sessionAttributeCount = 0;
122         long size = 0;
123 
124         for (Session session : context.getManager().findSessions()) {
125           ApplicationSession appSession = getApplicationSession(session, calcSize, false);
126           if (appSession != null) {
127             sessionAttributeCount += appSession.getObjectCount();
128             serializable = serializable && appSession.isSerializable();
129             size += appSession.getSize();
130           }
131         }
132         app.setSerializable(serializable);
133         app.setSessionAttributeCount(sessionAttributeCount);
134         app.setSize(size);
135       }
136 
137       logger.debug("aggregating servlet stats");
138 
139       collectApplicationServletStats(context, app);
140 
141       if (resourceResolver.supportsPrivateResources() && app.isAvailable()) {
142         int[] scores =
143             getApplicationDataSourceUsageScores(context, resourceResolver, containerWrapper);
144         app.setDataSourceBusyScore(scores[0]);
145         app.setDataSourceEstablishedScore(scores[1]);
146       }
147     }
148 
149     return app;
150   }
151 
152   /**
153    * Calculates Sum of requestCount, errorCount and processingTime for all servlets for the given
154    * application. It also works out minimum value of minTime and maximum value for maxTime for all
155    * servlets.
156    *
157    * @param context the context whose stats will be collected
158    * @param app the application in which to store the collected stats
159    */
160   public static void collectApplicationServletStats(Context context, Application app) {
161     int svltCount = 0;
162     int reqCount = 0;
163     int errCount = 0;
164     long procTime = 0;
165     long minTime = Long.MAX_VALUE;
166     long maxTime = 0;
167 
168     for (Container container : context.findChildren()) {
169       if (container instanceof StandardWrapper) {
170         StandardWrapper sw = (StandardWrapper) container;
171         svltCount++;
172         reqCount += sw.getRequestCount();
173         errCount += sw.getErrorCount();
174         procTime += sw.getProcessingTime();
175         if (sw.getRequestCount() > 0) {
176           minTime = Math.min(minTime, sw.getMinTime());
177         }
178         maxTime = Math.max(maxTime, sw.getMaxTime());
179       }
180     }
181     app.setServletCount(svltCount);
182     app.setRequestCount(reqCount);
183     app.setErrorCount(errCount);
184     app.setProcessingTime(procTime);
185     app.setMinTime(minTime == Long.MAX_VALUE ? 0 : minTime);
186     app.setMaxTime(maxTime);
187   }
188 
189   /**
190    * Gets the application data source usage scores.
191    *
192    * @param context the context
193    * @param resolver the resolver
194    * @param containerWrapper the container wrapper
195    *
196    * @return the application data source usage scores
197    */
198   public static int[] getApplicationDataSourceUsageScores(Context context,
199       ResourceResolver resolver, ContainerWrapperBean containerWrapper) {
200 
201     logger.debug("Calculating datasource usage score");
202 
203     int[] scores = {0, 0};
204     List<ApplicationResource> appResources;
205     try {
206       appResources = resolver.getApplicationResources(context, containerWrapper);
207     } catch (NamingException e) {
208       throw new RuntimeException(e);
209     }
210     for (ApplicationResource appResource : appResources) {
211       if (appResource.getDataSourceInfo() != null) {
212         scores[0] = Math.max(scores[0], appResource.getDataSourceInfo().getBusyScore());
213         scores[1] = Math.max(scores[1], appResource.getDataSourceInfo().getEstablishedScore());
214       }
215     }
216     return scores;
217   }
218 
219   /**
220    * Gets the application session.
221    *
222    * @param session the session
223    * @param calcSize the calc size
224    * @param addAttributes the add attributes
225    *
226    * @return the application session
227    */
228   public static ApplicationSession getApplicationSession(Session session, boolean calcSize,
229       boolean addAttributes) {
230 
231     ApplicationSession sbean = null;
232     if (session != null && session.isValid()) {
233       sbean = new ApplicationSession();
234 
235       sbean.setId(session.getId());
236       sbean.setCreationTime(new Date(session.getCreationTime()));
237       sbean.setLastAccessTime(new Date(session.getLastAccessedTime()));
238       sbean.setMaxIdleTime(session.getMaxInactiveInterval() * 1000);
239       sbean.setManagerType(session.getManager().getClass().getName());
240 
241       // Tomcat 8+ dropped support of getInfo off session. This patch allows it to continue working
242       // for tomcat 7.
243       try {
244         Object info = MethodUtils.invokeMethod(session, "getInfo");
245         sbean.setInfo(String.valueOf(info));
246       } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
247         sbean.setInfo(session.getClass().getSimpleName());
248         logger.trace("Cannot determine session info for tomcat 8+", e);
249       }
250 
251       boolean sessionSerializable = true;
252       int attributeCount = 0;
253       long size = 0;
254 
255       HttpSession httpSession = session.getSession();
256       Set<Object> processedObjects = new HashSet<>(1000);
257 
258       // Exclude references back to the session itself
259       processedObjects.add(httpSession);
260       try {
261         for (String name : Collections.list(httpSession.getAttributeNames())) {
262           Object obj = httpSession.getAttribute(name);
263           sessionSerializable = sessionSerializable && obj instanceof Serializable;
264 
265           long objSize = 0;
266           if (calcSize) {
267             try {
268               objSize += Instruments.sizeOf(name, processedObjects);
269               objSize += Instruments.sizeOf(obj, processedObjects);
270             } catch (Exception ex) {
271               logger.error("Cannot estimate size of attribute '{}'", name, ex);
272             }
273           }
274 
275           if (addAttributes) {
276             Attribute saBean = new Attribute();
277             saBean.setName(name);
278             saBean.setType(ClassUtils.getQualifiedName(obj.getClass()));
279             saBean.setValue(obj);
280             saBean.setSize(objSize);
281             saBean.setSerializable(obj instanceof Serializable);
282             sbean.addAttribute(saBean);
283           }
284           attributeCount++;
285           size += objSize;
286         }
287         String lastAccessedIp =
288             (String) httpSession.getAttribute(ApplicationSession.LAST_ACCESSED_BY_IP);
289         if (lastAccessedIp != null) {
290           sbean.setLastAccessedIp(lastAccessedIp);
291           sbean.setLastAccessedIpLocale(
292               (Locale) httpSession.getAttribute(ApplicationSession.LAST_ACCESSED_LOCALE));
293         }
294 
295       } catch (IllegalStateException e) {
296         logger.info("Session appears to be invalidated, ignore");
297         logger.trace("", e);
298       }
299 
300       sbean.setObjectCount(attributeCount);
301       sbean.setSize(size);
302       sbean.setSerializable(sessionSerializable);
303     }
304 
305     return sbean;
306   }
307 
308   /**
309    * Gets the application attributes.
310    *
311    * @param context the context
312    *
313    * @return the application attributes
314    */
315   public static List<Attribute> getApplicationAttributes(Context context) {
316     List<Attribute> attrs = new ArrayList<>();
317     ServletContext servletCtx = context.getServletContext();
318     for (String attrName : Collections.list(servletCtx.getAttributeNames())) {
319       Object attrValue = servletCtx.getAttribute(attrName);
320 
321       Attribute attr = new Attribute();
322       attr.setName(attrName);
323       attr.setValue(attrValue);
324       attr.setType(ClassUtils.getQualifiedName(attrValue.getClass()));
325       attrs.add(attr);
326     }
327     return attrs;
328   }
329 
330   /**
331    * Gets the application init params.
332    *
333    * @param context the context
334    * @param containerWrapper the container wrapper
335    *
336    * @return the application init params
337    */
338   public static List<ApplicationParam> getApplicationInitParams(Context context,
339       ContainerWrapperBean containerWrapper) {
340 
341     return containerWrapper.getTomcatContainer().getApplicationInitParams(context);
342   }
343 
344   /**
345    * Gets the application servlet.
346    *
347    * @param context the context
348    * @param servletName the servlet name
349    *
350    * @return the application servlet
351    */
352   public static ServletInfo getApplicationServlet(Context context, String servletName) {
353     Container container = context.findChild(servletName);
354 
355     if (container instanceof Wrapper) {
356       Wrapper wrapper = (Wrapper) container;
357       return getServletInfo(wrapper, context.getName());
358     }
359     return null;
360   }
361 
362   /**
363    * Gets the servlet info.
364    *
365    * @param wrapper the wrapper
366    * @param contextName the context name
367    *
368    * @return the servlet info
369    */
370   private static ServletInfo getServletInfo(Wrapper wrapper, String contextName) {
371     ServletInfo si = new ServletInfo();
372     si.setApplicationName(contextName.length() > 0 ? contextName : "/");
373     si.setServletName(wrapper.getName());
374     si.setServletClass(wrapper.getServletClass());
375     si.setAvailable(!wrapper.isUnavailable());
376     si.setLoadOnStartup(wrapper.getLoadOnStartup());
377     si.setRunAs(wrapper.getRunAs());
378     si.getMappings().addAll(Arrays.asList(wrapper.findMappings()));
379     if (wrapper instanceof StandardWrapper) {
380       StandardWrapper sw = (StandardWrapper) wrapper;
381       si.setAllocationCount(sw.getCountAllocated());
382       si.setErrorCount(sw.getErrorCount());
383       si.setLoadTime(sw.getLoadTime());
384       // XXX: Remove with tomcat 10.1
385       si.setMaxInstances(sw.getMaxInstances());
386       si.setMaxTime(sw.getMaxTime());
387       si.setMinTime(sw.getMinTime() == Long.MAX_VALUE ? 0 : sw.getMinTime());
388       si.setProcessingTime(sw.getProcessingTime());
389       si.setRequestCount(sw.getRequestCount());
390       // XXX: Remove with tomcat 10.1
391       si.setSingleThreaded(Boolean.TRUE.equals(sw.isSingleThreadModel()));
392     }
393     return si;
394   }
395 
396   /**
397    * Gets the application servlets.
398    *
399    * @param context the context
400    *
401    * @return the application servlets
402    */
403   public static List<ServletInfo> getApplicationServlets(Context context) {
404     Container[] cns = context.findChildren();
405     List<ServletInfo> servlets = new ArrayList<>(cns.length);
406     for (Container container : cns) {
407       if (container instanceof Wrapper) {
408         Wrapper wrapper = (Wrapper) container;
409         servlets.add(getServletInfo(wrapper, context.getName()));
410       }
411     }
412     return servlets;
413   }
414 
415   /**
416    * Gets the application servlet maps.
417    *
418    * @param context the context
419    *
420    * @return the application servlet maps
421    */
422   public static List<ServletMapping> getApplicationServletMaps(Context context) {
423     String[] sms = context.findServletMappings();
424     List<ServletMapping> servletMaps = new ArrayList<>(sms.length);
425     for (String servletMapping : sms) {
426       if (servletMapping != null) {
427         String sn = context.findServletMapping(servletMapping);
428         if (sn != null) {
429           ServletMapping sm = new ServletMapping();
430           sm.setApplicationName(context.getName().length() > 0 ? context.getName() : "/");
431           sm.setUrl(servletMapping);
432           sm.setServletName(sn);
433           Container container = context.findChild(sn);
434           if (container instanceof Wrapper) {
435             Wrapper wrapper = (Wrapper) container;
436             sm.setServletClass(wrapper.getServletClass());
437             sm.setAvailable(!wrapper.isUnavailable());
438           }
439           servletMaps.add(sm);
440         }
441       }
442     }
443     return servletMaps;
444   }
445 
446   /**
447    * Gets the application filters.
448    *
449    * @param context the context
450    * @param containerWrapper the container wrapper
451    *
452    * @return the application filters
453    */
454   public static List<FilterInfo> getApplicationFilters(Context context,
455       ContainerWrapperBean containerWrapper) {
456     return containerWrapper.getTomcatContainer().getApplicationFilters(context);
457   }
458 
459 }