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