1
2
3
4
5
6
7
8
9
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 }