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.beans;
12  
13  import jakarta.inject.Inject;
14  
15  import java.lang.management.ManagementFactory;
16  import java.util.ArrayList;
17  import java.util.List;
18  import java.util.Set;
19  
20  import javax.management.AttributeNotFoundException;
21  import javax.management.InstanceNotFoundException;
22  import javax.management.MBeanException;
23  import javax.management.MBeanServer;
24  import javax.management.MalformedObjectNameException;
25  import javax.management.ObjectName;
26  import javax.management.ReflectionException;
27  import javax.naming.InitialContext;
28  import javax.naming.NamingException;
29  import javax.sql.DataSource;
30  
31  import org.apache.catalina.Context;
32  import org.apache.catalina.Server;
33  import org.apache.catalina.core.StandardServer;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  
37  import psiprobe.beans.accessors.DatasourceAccessor;
38  import psiprobe.model.ApplicationResource;
39  import psiprobe.model.DataSourceInfo;
40  
41  /**
42   * The Class ResourceResolverBean.
43   */
44  public class ResourceResolverBean implements ResourceResolver {
45  
46    /** The Constant logger. */
47    private static final Logger logger = LoggerFactory.getLogger(ResourceResolverBean.class);
48  
49    /**
50     * The default resource prefix for JNDI objects in the global scope: <code>java:</code>.
51     */
52    public static final String DEFAULT_GLOBAL_RESOURCE_PREFIX = "";
53  
54    /**
55     * The default resource prefix for objects in a private application scope:
56     * <code>java:comp/env/</code>.
57     */
58    public static final String DEFAULT_RESOURCE_PREFIX =
59        DEFAULT_GLOBAL_RESOURCE_PREFIX + "java:comp/env/";
60  
61    /** The datasource mappers. */
62    @Inject
63    private List<String> datasourceMappers;
64  
65    @Override
66    public List<ApplicationResource> getApplicationResources() throws NamingException {
67      logger.debug("Reading GLOBAL resources");
68      List<ApplicationResource> resources = new ArrayList<>();
69  
70      MBeanServer server = getMBeanServer();
71      if (server != null) {
72        try {
73          Set<ObjectName> dsNames =
74              server.queryNames(new ObjectName("Catalina:type=Resource,resourcetype=Global,*"), null);
75          for (ObjectName objectName : dsNames) {
76            ApplicationResource resource = new ApplicationResource();
77  
78            logger.debug("reading resource: {}", objectName);
79            resource.setName(getStringAttribute(server, objectName, "name"));
80            resource.setType(getStringAttribute(server, objectName, "type"));
81            resource.setScope(getStringAttribute(server, objectName, "scope"));
82            resource.setAuth(getStringAttribute(server, objectName, "auth"));
83            resource.setDescription(getStringAttribute(server, objectName, "description"));
84  
85            lookupResource(resource, true, true);
86  
87            resources.add(resource);
88          }
89        } catch (Exception e) {
90          logger.error("There was an error querying JMX server:", e);
91        }
92      }
93      return resources;
94    }
95  
96    @Override
97    public synchronized List<ApplicationResource> getApplicationResources(Context context,
98        ContainerWrapperBean containerWrapper) throws NamingException {
99  
100     List<ApplicationResource> resourceList = new ArrayList<>();
101 
102     boolean contextAvailable = containerWrapper.getTomcatContainer().getAvailable(context);
103     if (contextAvailable) {
104 
105       logger.debug("Reading CONTEXT {}", context.getName());
106 
107       boolean contextBound = false;
108 
109       try {
110         containerWrapper.getTomcatContainer().bindToContext(context);
111         contextBound = true;
112       } catch (NamingException e) {
113         logger.error("Cannot bind to context. useNaming=false ?");
114         logger.debug("", e);
115       }
116 
117       try {
118         containerWrapper.getTomcatContainer().addContextResource(context, resourceList);
119 
120         containerWrapper.getTomcatContainer().addContextResourceLink(context, resourceList);
121 
122         for (ApplicationResource resourceList1 : resourceList) {
123           lookupResource(resourceList1, contextBound, false);
124         }
125 
126       } finally {
127         if (contextBound) {
128           containerWrapper.getTomcatContainer().unbindFromContext(context);
129         }
130       }
131     }
132 
133     return resourceList;
134   }
135 
136   /**
137    * Lookup resource.
138    *
139    * @param resource the resource
140    * @param contextBound the context bound
141    * @param global the global
142    */
143   public void lookupResource(ApplicationResource resource, boolean contextBound, boolean global) {
144     DataSourceInfo dataSourceInfo = null;
145     if (contextBound) {
146       try {
147         javax.naming.Context ctx = !global ? new InitialContext() : getGlobalNamingContext();
148         if (ctx == null) {
149           logger.error(
150               "Unable to find context. This may indicate invalid setup. Check global resources versus requested resources");
151           resource.setLookedUp(false);
152           return;
153         }
154         String jndiName = resolveJndiName(resource.getName(), global);
155         logger.debug("reading resource jndi name: {}", jndiName);
156         Object obj = ctx.lookup(jndiName);
157         resource.setLookedUp(true);
158         for (String accessorString : datasourceMappers) {
159           logger.debug("Looking up datasource adapter: {}", accessorString);
160           DatasourceAccessor accessor = Class.forName(accessorString)
161               .asSubclass(DatasourceAccessor.class).getDeclaredConstructor().newInstance();
162           dataSourceInfo = accessor.getInfo(obj);
163           if (dataSourceInfo != null) {
164             break;
165           }
166         }
167       } catch (Exception e) {
168         resource.setLookedUp(false);
169         dataSourceInfo = null;
170         logger.error("Failed to lookup: '{}'", resource.getName(), e);
171       }
172     } else {
173       resource.setLookedUp(false);
174     }
175 
176     if (resource.isLookedUp() && dataSourceInfo != null) {
177       resource.setDataSourceInfo(dataSourceInfo);
178     }
179   }
180 
181   @Override
182   public synchronized boolean resetResource(final Context context, String resourceName,
183       ContainerWrapperBean containerWrapper) throws NamingException {
184 
185     if (context != null) {
186       containerWrapper.getTomcatContainer().bindToContext(context);
187     }
188     try {
189       javax.naming.Context ctx = context != null ? new InitialContext() : getGlobalNamingContext();
190       String jndiName = resolveJndiName(resourceName, context == null);
191       try {
192         for (String accessorString : datasourceMappers) {
193           logger.debug("Resetting datasource adapter: {}", accessorString);
194           DatasourceAccessor accessor = Class.forName(accessorString)
195               .asSubclass(DatasourceAccessor.class).getDeclaredConstructor().newInstance();
196           if (ctx == null) {
197             return false;
198           }
199           Object obj = ctx.lookup(jndiName);
200           if (accessor.reset(obj)) {
201             return true;
202           }
203         }
204         return false;
205       } catch (Exception e) {
206         logger.trace("", e);
207         return false;
208       }
209     } finally {
210       if (context != null) {
211         containerWrapper.getTomcatContainer().unbindFromContext(context);
212       }
213     }
214   }
215 
216   @Override
217   public synchronized DataSource lookupDataSource(final Context context, String resourceName,
218       ContainerWrapperBean containerWrapper) throws NamingException {
219 
220     if (context != null) {
221       containerWrapper.getTomcatContainer().bindToContext(context);
222     }
223     try {
224       javax.naming.Context ctx = context != null ? new InitialContext() : getGlobalNamingContext();
225       String jndiName = resolveJndiName(resourceName, context == null);
226       if (ctx == null) {
227         return null;
228       }
229       Object obj = ctx.lookup(jndiName);
230 
231       if (obj instanceof DataSource dataSource) {
232         return dataSource;
233       }
234       return null;
235     } finally {
236       if (context != null) {
237         containerWrapper.getTomcatContainer().unbindFromContext(context);
238       }
239     }
240   }
241 
242   @Override
243   public boolean supportsPrivateResources() {
244     return true;
245   }
246 
247   @Override
248   public boolean supportsGlobalResources() {
249     return true;
250   }
251 
252   @Override
253   public boolean supportsDataSourceLookup() {
254     return true;
255   }
256 
257   @Override
258   public MBeanServer getMBeanServer() {
259     return ManagementFactory.getPlatformMBeanServer();
260   }
261 
262   /**
263    * Resolves a JNDI resource name by prepending the scope-appropriate prefix.
264    *
265    * @param name the JNDI name of the resource
266    * @param global whether to use the global prefix
267    *
268    * @return the JNDI resource name with the prefix appended
269    *
270    * @see #DEFAULT_GLOBAL_RESOURCE_PREFIX
271    * @see #DEFAULT_RESOURCE_PREFIX
272    */
273   protected static String resolveJndiName(String name, boolean global) {
274     return (global ? DEFAULT_GLOBAL_RESOURCE_PREFIX : DEFAULT_RESOURCE_PREFIX) + name;
275   }
276 
277   /**
278    * Gets the string attribute.
279    *
280    * @param server the server
281    * @param objectName the object name
282    * @param attributeName the attribute name
283    *
284    * @return the string attribute
285    */
286   private String getStringAttribute(MBeanServer server, ObjectName objectName,
287       String attributeName) {
288 
289     try {
290       return (String) server.getAttribute(objectName, attributeName);
291     } catch (Exception e) {
292       logger.error("Error getting attribute '{}' from '{}'", attributeName, objectName, e);
293       return null;
294     }
295   }
296 
297   /**
298    * Returns the Server's global naming context.
299    *
300    * @return the global JNDI context
301    */
302   public static javax.naming.Context getGlobalNamingContext() {
303 
304     javax.naming.Context globalContext = null;
305     MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
306     if (mbeanServer != null) {
307       for (String domain : mbeanServer.getDomains()) {
308 
309         ObjectName name;
310         try {
311           name = new ObjectName(domain + ":type=Server");
312         } catch (MalformedObjectNameException e) {
313           logger.error("", e);
314           return null;
315         }
316 
317         Server server = null;
318         try {
319           server = (Server) mbeanServer.getAttribute(name, "managedResource");
320         } catch (AttributeNotFoundException | InstanceNotFoundException | MBeanException
321             | ReflectionException e) {
322           logger.trace("JMX objectName {} does not contain any managedResource", name, e);
323         }
324 
325         // Get Global Naming Context
326         if (server instanceof StandardServer standardServer) {
327           globalContext = standardServer.getGlobalNamingContext();
328           break;
329         }
330       }
331     }
332 
333     return globalContext;
334   }
335 }