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.util.ArrayList;
16  import java.util.Collection;
17  import java.util.List;
18  import java.util.Map;
19  import java.util.concurrent.locks.ReentrantLock;
20  
21  import javax.naming.NamingException;
22  
23  import org.apache.catalina.Context;
24  import org.apache.catalina.Wrapper;
25  import org.apache.catalina.util.ServerInfo;
26  import org.slf4j.Logger;
27  import org.slf4j.LoggerFactory;
28  import org.springframework.beans.factory.annotation.Value;
29  
30  import psiprobe.TomcatContainer;
31  import psiprobe.model.ApplicationResource;
32  
33  /**
34   * This class wires support for Tomcat "privileged" context functionality into Spring. If
35   * application context is privileged Tomcat would always call servlet.setWrapper method on each
36   * request. ContainerWrapperBean wires the passed wrapper to the relevant Tomcat container adapter
37   * class, which in turn helps the Probe to interpret the wrapper.
38   */
39  public class ContainerWrapperBean {
40  
41    /** The Constant logger. */
42    private static final Logger logger = LoggerFactory.getLogger(ContainerWrapperBean.class);
43  
44    /** The tomcat container. */
45    private volatile TomcatContainer tomcatContainer;
46  
47    /** The lock. */
48    private final ReentrantLock lock = new ReentrantLock();
49  
50    /** List of class names to adapt particular Tomcat implementation to TomcatContainer interface. */
51    @Inject
52    private List<String> adapterClasses;
53  
54    /** The resource resolver. */
55    private ResourceResolver resourceResolver;
56  
57    /** The force first adapter. */
58    private boolean forceFirstAdapter;
59  
60    /** The resource resolvers. */
61    @Inject
62    private Map<String, ResourceResolver> resourceResolvers;
63  
64    /**
65     * Checks if is force first adapter.
66     *
67     * @return true, if is force first adapter
68     */
69    public boolean isForceFirstAdapter() {
70      return forceFirstAdapter;
71    }
72  
73    /**
74     * Sets the force first adapter. Setting this property to true will override the server polling
75     * each adapter performs to test for compatibility. Instead, it will use the first one in the
76     * adapterClasses list.
77     *
78     * @param forceFirstAdapter the new force first adapter
79     */
80    // TODO We should make this configurable
81    @Value("false")
82    public void setForceFirstAdapter(boolean forceFirstAdapter) {
83      this.forceFirstAdapter = forceFirstAdapter;
84    }
85  
86    /**
87     * Sets the wrapper.
88     *
89     * @param wrapper the new wrapper
90     */
91    public void setWrapper(Wrapper wrapper) {
92      if (tomcatContainer == null) {
93  
94        lock.lock();
95        try {
96          if (tomcatContainer == null) {
97  
98            String serverInfo = ServerInfo.getServerInfo();
99            logger.info("Server info: {}", serverInfo);
100           for (String className : adapterClasses) {
101             try {
102               Object obj = Class.forName(className).getDeclaredConstructor().newInstance();
103               logger.debug("Testing container adapter: {}", className);
104               if (obj instanceof TomcatContainer container) {
105                 if (forceFirstAdapter || container.canBoundTo(serverInfo)) {
106                   logger.info("Using {}", className);
107                   tomcatContainer = (TomcatContainer) obj;
108                   tomcatContainer.setWrapper(wrapper);
109                   break;
110                 }
111                 logger.debug("Cannot bind {} to {}", className, serverInfo);
112               } else {
113                 logger.error("{} does not implement {}", className,
114                     TomcatContainer.class.getName());
115               }
116             } catch (Exception e) {
117               logger.debug("", e);
118               logger.info("Failed to load {}", className);
119             }
120           }
121 
122           if (tomcatContainer == null) {
123             logger.error("No suitable container adapter found!");
124           }
125         }
126       } finally {
127         lock.unlock();
128       }
129     }
130 
131     try {
132       if (tomcatContainer != null && wrapper == null) {
133         logger.info("Unregistering container adapter");
134         tomcatContainer.setWrapper(null);
135       }
136     } catch (Exception e) {
137       logger.error("Could not unregister container adapter", e);
138     }
139   }
140 
141   /**
142    * Gets the tomcat container.
143    *
144    * @return the tomcat container
145    */
146   public TomcatContainer getTomcatContainer() {
147     return tomcatContainer;
148   }
149 
150   /**
151    * Gets the resource resolver.
152    *
153    * @return the resource resolver
154    */
155   public ResourceResolver getResourceResolver() {
156     if (resourceResolver == null) {
157       if (System.getProperty("jboss.server.name") != null) {
158         resourceResolver = resourceResolvers.get("jboss");
159         logger.info("Using JBOSS resource resolver");
160       } else {
161         resourceResolver = resourceResolvers.get("default");
162         logger.info("Using DEFAULT resource resolver");
163       }
164     }
165     return resourceResolver;
166   }
167 
168   /**
169    * Gets the data sources.
170    *
171    * @return the data sources
172    *
173    * @throws NamingException the naming exception
174    */
175   public List<ApplicationResource> getDataSources() throws NamingException {
176     List<ApplicationResource> resources = new ArrayList<>(getPrivateDataSources());
177     resources.addAll(getGlobalDataSources());
178     return resources;
179   }
180 
181   /**
182    * Gets the private data sources.
183    *
184    * @return the private data sources
185    *
186    * @throws NamingException the naming exception
187    */
188   public List<ApplicationResource> getPrivateDataSources() throws NamingException {
189     List<ApplicationResource> resources = new ArrayList<>();
190     if (tomcatContainer != null && getResourceResolver().supportsPrivateResources()) {
191       for (Context app : getTomcatContainer().findContexts()) {
192         List<ApplicationResource> appResources =
193             getResourceResolver().getApplicationResources(app, this);
194         // add only those resources that have data source info
195         filterDataSources(appResources, resources);
196       }
197     }
198     return resources;
199   }
200 
201   /**
202    * Gets the global data sources.
203    *
204    * @return the global data sources
205    *
206    * @throws NamingException the naming exception
207    */
208   public List<ApplicationResource> getGlobalDataSources() throws NamingException {
209     List<ApplicationResource> resources = new ArrayList<>();
210     if (getResourceResolver().supportsGlobalResources()) {
211       List<ApplicationResource> globalResources = getResourceResolver().getApplicationResources();
212       // add only those resources that have data source info
213       filterDataSources(globalResources, resources);
214     }
215     return resources;
216   }
217 
218   /**
219    * Filter data sources.
220    *
221    * @param resources the resources
222    * @param dataSources the data sources
223    */
224   protected void filterDataSources(Iterable<ApplicationResource> resources,
225       Collection<ApplicationResource> dataSources) {
226 
227     for (ApplicationResource res : resources) {
228       if (res.getDataSourceInfo() != null) {
229         dataSources.add(res);
230       }
231     }
232   }
233 
234 }