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) {
232         return (DataSource) obj;
233       }
234       return null;
235     } finally {
236       if (context != null) {
237         containerWrapper.getTomcatContainer().unbindFromContext(context);
238       }
239     }
240   }
241 
242   /**
243    * Gets the datasource mappers.
244    *
245    * @return the datasource mappers
246    */
247   public List<String> getDatasourceMappers() {
248     return datasourceMappers;
249   }
250 
251   /**
252    * Sets the datasource mappers.
253    *
254    * @param datasourceMappers the new datasource mappers
255    */
256   public void setDatasourceMappers(List<String> datasourceMappers) {
257     this.datasourceMappers = datasourceMappers;
258   }
259 
260   @Override
261   public boolean supportsPrivateResources() {
262     return true;
263   }
264 
265   @Override
266   public boolean supportsGlobalResources() {
267     return true;
268   }
269 
270   @Override
271   public boolean supportsDataSourceLookup() {
272     return true;
273   }
274 
275   @Override
276   public MBeanServer getMBeanServer() {
277     return ManagementFactory.getPlatformMBeanServer();
278   }
279 
280   /**
281    * Resolves a JNDI resource name by prepending the scope-appropriate prefix.
282    *
283    * @param name the JNDI name of the resource
284    * @param global whether to use the global prefix
285    *
286    * @return the JNDI resource name with the prefix appended
287    *
288    * @see #DEFAULT_GLOBAL_RESOURCE_PREFIX
289    * @see #DEFAULT_RESOURCE_PREFIX
290    */
291   protected static String resolveJndiName(String name, boolean global) {
292     return (global ? DEFAULT_GLOBAL_RESOURCE_PREFIX : DEFAULT_RESOURCE_PREFIX) + name;
293   }
294 
295   /**
296    * Gets the string attribute.
297    *
298    * @param server the server
299    * @param objectName the object name
300    * @param attributeName the attribute name
301    *
302    * @return the string attribute
303    */
304   private String getStringAttribute(MBeanServer server, ObjectName objectName,
305       String attributeName) {
306 
307     try {
308       return (String) server.getAttribute(objectName, attributeName);
309     } catch (Exception e) {
310       logger.error("Error getting attribute '{}' from '{}'", attributeName, objectName, e);
311       return null;
312     }
313   }
314 
315   /**
316    * Returns the Server's global naming context.
317    *
318    * @return the global JNDI context
319    */
320   public static javax.naming.Context getGlobalNamingContext() {
321 
322     javax.naming.Context globalContext = null;
323     MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
324     if (mbeanServer != null) {
325       for (String domain : mbeanServer.getDomains()) {
326 
327         ObjectName name;
328         try {
329           name = new ObjectName(domain + ":type=Server");
330         } catch (MalformedObjectNameException e) {
331           logger.error("", e);
332           return null;
333         }
334 
335         Server server = null;
336         try {
337           server = (Server) mbeanServer.getAttribute(name, "managedResource");
338         } catch (AttributeNotFoundException | InstanceNotFoundException | MBeanException
339             | ReflectionException e) {
340           logger.trace("JMX objectName {} does not contain any managedResource", name, e);
341         }
342 
343         // Get Global Naming Context
344         if (server instanceof StandardServer) {
345           globalContext = server.getGlobalNamingContext();
346           break;
347         }
348       }
349     }
350 
351     return globalContext;
352   }
353 }