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