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 static org.junit.jupiter.api.Assertions.assertEquals;
14  import static org.junit.jupiter.api.Assertions.assertFalse;
15  import static org.junit.jupiter.api.Assertions.assertNull;
16  import static org.junit.jupiter.api.Assertions.assertSame;
17  import static org.junit.jupiter.api.Assertions.assertTrue;
18  import static org.mockito.Mockito.mock;
19  import static org.mockito.Mockito.verify;
20  import static org.mockito.Mockito.when;
21  
22  import java.lang.management.ManagementFactory;
23  import java.sql.SQLException;
24  import java.util.List;
25  import java.util.Set;
26  
27  import javax.management.MBeanServer;
28  import javax.management.ObjectName;
29  
30  import org.apache.catalina.Context;
31  import org.apache.catalina.core.StandardServer;
32  import org.junit.jupiter.api.BeforeEach;
33  import org.junit.jupiter.api.Test;
34  import org.mockito.MockedStatic;
35  import org.springframework.test.util.ReflectionTestUtils;
36  
37  import psiprobe.TomcatContainer;
38  import psiprobe.beans.accessors.DatasourceAccessor;
39  import psiprobe.model.ApplicationResource;
40  import psiprobe.model.DataSourceInfo;
41  
42  class ResourceResolverBeanTest {
43  
44    private TestableResourceResolverBean bean;
45  
46    @BeforeEach
47    void setUp() {
48      bean = new TestableResourceResolverBean();
49      ReflectionTestUtils.setField(bean, "datasourceMappers",
50          List.of(TestDatasourceAccessor.class.getName()));
51      TestDatasourceAccessor.infoToReturn = null;
52      TestDatasourceAccessor.resetToReturn = false;
53      TestDatasourceAccessor.lastResource = null;
54    }
55  
56    @Test
57    void getApplicationResourcesMapsJmxAttributesAndInvokesLookup() throws Exception {
58      MBeanServer server = mock(MBeanServer.class);
59      ObjectName objectName = new ObjectName(
60          "Catalina:type=Resource,resourcetype=Global,class=javax.sql.DataSource,name=jdbc/test");
61  
62      when(server.queryNames(new ObjectName("Catalina:type=Resource,resourcetype=Global,*"), null))
63          .thenReturn(Set.of(objectName));
64      when(server.getAttribute(objectName, "name")).thenReturn("jdbc/test");
65      when(server.getAttribute(objectName, "type")).thenReturn("javax.sql.DataSource");
66      when(server.getAttribute(objectName, "scope")).thenReturn("Shareable");
67      when(server.getAttribute(objectName, "auth")).thenReturn("Container");
68      when(server.getAttribute(objectName, "description")).thenReturn("Primary DS");
69  
70      bean.mbeanServer = server;
71      bean.lookupInfo = new DataSourceInfo();
72  
73      List<ApplicationResource> resources = bean.getApplicationResources();
74  
75      assertEquals(1, resources.size());
76      ApplicationResource resource = resources.get(0);
77      assertEquals("jdbc/test", resource.getName());
78      assertEquals("javax.sql.DataSource", resource.getType());
79      assertEquals("Shareable", resource.getScope());
80      assertEquals("Container", resource.getAuth());
81      assertEquals("Primary DS", resource.getDescription());
82      assertSame(bean.lookupInfo, resource.getDataSourceInfo());
83      assertEquals(List.of("jdbc/test:true:true"), bean.lookupCalls);
84    }
85  
86    @Test
87    void getApplicationResourcesForContextBindsLooksUpAndUnbinds() throws Exception {
88      ContainerWrapperBean containerWrapper = mock(ContainerWrapperBean.class);
89      TomcatContainer tomcatContainer = mock(TomcatContainer.class);
90      Context context = mock(Context.class);
91      ApplicationResource resource = new ApplicationResource();
92      resource.setName("jdbc/private");
93  
94      when(containerWrapper.getTomcatContainer()).thenReturn(tomcatContainer);
95      when(tomcatContainer.getAvailable(context)).thenReturn(true);
96      when(context.getName()).thenReturn("/app");
97  
98      bean.resourcesToAdd = List.of(resource);
99      List<ApplicationResource> resources = bean.getApplicationResources(context, containerWrapper);
100 
101     assertEquals(1, resources.size());
102     assertEquals(List.of("jdbc/private:true:false"), bean.lookupCalls);
103     verify(tomcatContainer).bindToContext(context);
104     verify(tomcatContainer).addContextResource(context, resources);
105     verify(tomcatContainer).addContextResourceLink(context, resources);
106     verify(tomcatContainer).unbindFromContext(context);
107   }
108 
109   @Test
110   void lookupResourceMarksResourceUnresolvedWhenContextIsNotBound() {
111     ResourceResolverBean realBean = new ResourceResolverBean();
112     ReflectionTestUtils.setField(realBean, "datasourceMappers",
113         List.of(TestDatasourceAccessor.class.getName()));
114     ApplicationResource resource = new ApplicationResource();
115     resource.setName("jdbc/test");
116     resource.setLookedUp(true);
117 
118     realBean.lookupResource(resource, false, false);
119 
120     assertFalse(resource.isLookedUp());
121   }
122 
123   @Test
124   void resetResourceUsesDatasourceAccessorForGlobalResource() throws Exception {
125     javax.naming.Context namingContext = mock(javax.naming.Context.class);
126     TestDatasourceAccessor.resetToReturn = true;
127     when(namingContext.lookup("jdbc/test")).thenReturn("resetResource");
128 
129     try (MockedStatic<ResourceResolverBean> mocked = org.mockito.Mockito
130         .mockStatic(ResourceResolverBean.class, org.mockito.Mockito.CALLS_REAL_METHODS)) {
131       mocked.when(ResourceResolverBean::getGlobalNamingContext).thenReturn(namingContext);
132       assertTrue(bean.resetResource(null, "jdbc/test", null));
133     }
134 
135     assertEquals("resetResource", TestDatasourceAccessor.lastResource);
136   }
137 
138   @Test
139   void getGlobalNamingContextReturnsContextFromManagedStandardServer() throws Exception {
140     MBeanServer server = mock(MBeanServer.class);
141     javax.naming.Context globalContext = mock(javax.naming.Context.class);
142     StandardServer standardServer = new StandardServer();
143     standardServer.setGlobalNamingContext(globalContext);
144     ObjectName objectName = new ObjectName("Catalina:type=Server");
145 
146     when(server.getDomains()).thenReturn(new String[] {"Catalina"});
147     when(server.getAttribute(objectName, "managedResource")).thenReturn(standardServer);
148 
149     try (MockedStatic<ManagementFactory> mocked =
150         org.mockito.Mockito.mockStatic(ManagementFactory.class)) {
151       mocked.when(ManagementFactory::getPlatformMBeanServer).thenReturn(server);
152       assertSame(globalContext, ResourceResolverBean.getGlobalNamingContext());
153     }
154   }
155 
156   @Test
157   void lookupDataSourceReturnsNullWhenResolvedObjectIsNotDataSource() throws Exception {
158     javax.naming.Context namingContext = mock(javax.naming.Context.class);
159     when(namingContext.lookup("jdbc/notds")).thenReturn("value");
160 
161     try (MockedStatic<ResourceResolverBean> mocked = org.mockito.Mockito
162         .mockStatic(ResourceResolverBean.class, org.mockito.Mockito.CALLS_REAL_METHODS)) {
163       mocked.when(ResourceResolverBean::getGlobalNamingContext).thenReturn(namingContext);
164       assertNull(bean.lookupDataSource(null, "jdbc/notds", null));
165     }
166   }
167 
168   public static class TestDatasourceAccessor implements DatasourceAccessor {
169 
170     private static DataSourceInfo infoToReturn;
171 
172     private static Object lastResource;
173 
174     private static boolean resetToReturn;
175 
176     @Override
177     public DataSourceInfo getInfo(Object resource) throws SQLException {
178       lastResource = resource;
179       return infoToReturn;
180     }
181 
182     @Override
183     public boolean reset(Object resource) throws SQLException {
184       lastResource = resource;
185       return resetToReturn;
186     }
187 
188     @Override
189     public boolean canMap(Object resource) {
190       return true;
191     }
192   }
193 
194   private static class TestableResourceResolverBean extends ResourceResolverBean {
195 
196     private MBeanServer mbeanServer;
197 
198     private DataSourceInfo lookupInfo;
199 
200     private List<ApplicationResource> resourcesToAdd = List.of();
201 
202     private final List<String> lookupCalls = new java.util.ArrayList<>();
203 
204     @Override
205     public MBeanServer getMBeanServer() {
206       return mbeanServer;
207     }
208 
209     @Override
210     public void lookupResource(ApplicationResource resource, boolean contextBound, boolean global) {
211       lookupCalls.add(resource.getName() + ":" + contextBound + ":" + global);
212       if (lookupInfo != null) {
213         resource.setLookedUp(true);
214         resource.setDataSourceInfo(lookupInfo);
215       }
216     }
217 
218     @Override
219     public synchronized List<ApplicationResource> getApplicationResources(Context context,
220         ContainerWrapperBean containerWrapper) throws javax.naming.NamingException {
221       return super.getApplicationResources(context, new ContainerWrapperBean() {
222         @Override
223         public TomcatContainer getTomcatContainer() {
224           return new DelegatingTomcatContainer(containerWrapper.getTomcatContainer(),
225               resourcesToAdd);
226         }
227       });
228     }
229   }
230 
231   private static class DelegatingTomcatContainer implements TomcatContainer {
232 
233     private final TomcatContainer delegate;
234 
235     private final List<ApplicationResource> resourcesToAdd;
236 
237     private DelegatingTomcatContainer(TomcatContainer delegate,
238         List<ApplicationResource> resourcesToAdd) {
239       this.delegate = delegate;
240       this.resourcesToAdd = resourcesToAdd;
241     }
242 
243     @Override
244     public void addContextResource(Context context, List<ApplicationResource> resourceList) {
245       resourceList.addAll(resourcesToAdd);
246       delegate.addContextResource(context, resourceList);
247     }
248 
249     @Override
250     public void addContextResourceLink(Context context, List<ApplicationResource> resourceList) {
251       delegate.addContextResourceLink(context, resourceList);
252     }
253 
254     @Override
255     public boolean getAvailable(Context context) {
256       return delegate.getAvailable(context);
257     }
258 
259     @Override
260     public void bindToContext(Context context) throws javax.naming.NamingException {
261       delegate.bindToContext(context);
262     }
263 
264     @Override
265     public void unbindFromContext(Context context) throws javax.naming.NamingException {
266       delegate.unbindFromContext(context);
267     }
268 
269     @Override
270     public Context findContext(String name) {
271       return delegate.findContext(name);
272     }
273 
274     @Override
275     public String formatContextName(String name) {
276       return delegate.formatContextName(name);
277     }
278 
279     @Override
280     public String formatContextFilename(String contextName) {
281       return delegate.formatContextFilename(contextName);
282     }
283 
284     @Override
285     public List<Context> findContexts() {
286       return delegate.findContexts();
287     }
288 
289     @Override
290     public List<org.apache.catalina.connector.Connector> findConnectors() {
291       return delegate.findConnectors();
292     }
293 
294     @Override
295     public void stop(String name) throws Exception {
296       delegate.stop(name);
297     }
298 
299     @Override
300     public void start(String name) throws Exception {
301       delegate.start(name);
302     }
303 
304     @Override
305     public void remove(String name) throws Exception {
306       delegate.remove(name);
307     }
308 
309     @Override
310     public void installWar(String name) throws Exception {
311       delegate.installWar(name);
312     }
313 
314     @Override
315     public java.io.File getAppBase() {
316       return delegate.getAppBase();
317     }
318 
319     @Override
320     public java.io.File getConfigFile(Context context) {
321       return delegate.getConfigFile(context);
322     }
323 
324     @Override
325     public String getConfigBase() {
326       return delegate.getConfigBase();
327     }
328 
329     @Override
330     public void setWrapper(org.apache.catalina.Wrapper wrapper) {
331       delegate.setWrapper(wrapper);
332     }
333 
334     @Override
335     public boolean canBoundTo(String binding) {
336       return delegate.canBoundTo(binding);
337     }
338 
339     @Override
340     public boolean installContext(String contextName) throws Exception {
341       return delegate.installContext(contextName);
342     }
343 
344     @Override
345     public void listContextJsps(Context context, psiprobe.model.jsp.Summary summary,
346         boolean compile) {
347       delegate.listContextJsps(context, summary, compile);
348     }
349 
350     @Override
351     public void recompileJsps(Context context, psiprobe.model.jsp.Summary summary,
352         List<String> names) {
353       delegate.recompileJsps(context, summary, names);
354     }
355 
356     @Override
357     public void discardWorkDir(Context context) {
358       delegate.discardWorkDir(context);
359     }
360 
361     @Override
362     public String getHostName() {
363       return delegate.getHostName();
364     }
365 
366     @Override
367     public String getName() {
368       return delegate.getName();
369     }
370 
371     @Override
372     public String getServletFileNameForJsp(Context context, String jspName) {
373       return delegate.getServletFileNameForJsp(context, jspName);
374     }
375 
376     @Override
377     public List<psiprobe.model.FilterMapping> getApplicationFilterMaps(Context context) {
378       return delegate.getApplicationFilterMaps(context);
379     }
380 
381     @Override
382     public List<psiprobe.model.FilterInfo> getApplicationFilters(Context context) {
383       return delegate.getApplicationFilters(context);
384     }
385 
386     @Override
387     public List<psiprobe.model.ApplicationParam> getApplicationInitParams(Context context) {
388       return delegate.getApplicationInitParams(context);
389     }
390 
391     @Override
392     public boolean resourceExists(String name, Context context) {
393       return delegate.resourceExists(name, context);
394     }
395 
396     @Override
397     public java.io.InputStream getResourceStream(String name, Context context)
398         throws java.io.IOException {
399       return delegate.getResourceStream(name, context);
400     }
401 
402     @Override
403     public Long[] getResourceAttributes(String name, Context context) {
404       return delegate.getResourceAttributes(name, context);
405     }
406   }
407 }