View Javadoc
1   /**
2    * This file Copyright (c) 2017-2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.about.app;
35  
36  import info.magnolia.cms.beans.config.ServerConfiguration;
37  import info.magnolia.cms.pddescriptor.ProductDescriptorExtractor;
38  import info.magnolia.context.WebContext;
39  import info.magnolia.init.MagnoliaConfigurationProperties;
40  
41  import java.io.File;
42  import java.sql.Connection;
43  import java.sql.DatabaseMetaData;
44  import java.sql.DriverManager;
45  import java.sql.PreparedStatement;
46  import java.sql.ResultSet;
47  import java.sql.SQLException;
48  
49  import javax.inject.Inject;
50  import javax.inject.Provider;
51  import javax.jcr.Repository;
52  import javax.jcr.RepositoryException;
53  import javax.naming.Context;
54  import javax.naming.InitialContext;
55  import javax.naming.NamingException;
56  import javax.sql.DataSource;
57  import javax.xml.parsers.DocumentBuilder;
58  import javax.xml.parsers.DocumentBuilderFactory;
59  import javax.xml.parsers.SAXParserFactory;
60  import javax.xml.xpath.XPath;
61  import javax.xml.xpath.XPathConstants;
62  import javax.xml.xpath.XPathFactory;
63  
64  import org.apache.commons.lang3.StringUtils;
65  import org.apache.jackrabbit.commons.JcrUtils;
66  import org.slf4j.Logger;
67  import org.slf4j.LoggerFactory;
68  import org.w3c.dom.Document;
69  import org.xml.sax.Attributes;
70  import org.xml.sax.SAXException;
71  import org.xml.sax.helpers.DefaultHandler;
72  
73  /**
74   * Component for obtaining information about current installation and environment.
75   */
76  public class InstanceConfigurationProvider {
77  
78      private static final Logger log = LoggerFactory.getLogger(InstanceConfigurationProvider.class);
79  
80      private final Provider<WebContext> contextProvider;
81      private final ProductDescriptorExtractor productDescriptorExtractor;
82      private final MagnoliaConfigurationProperties magnoliaProperties;
83      private final ServerConfiguration serverConfiguration;
84  
85      @Inject
86      public InstanceConfigurationProvider(final Provider<WebContext> contextProvider,
87              final ServerConfiguration serverConfiguration, final MagnoliaConfigurationProperties magnoliaProperties,
88              final ProductDescriptorExtractor productDescriptorExtractor) {
89          this.contextProvider = contextProvider;
90          this.productDescriptorExtractor = productDescriptorExtractor;
91          this.serverConfiguration = serverConfiguration;
92          this.magnoliaProperties = magnoliaProperties;
93      }
94  
95      protected String[] getConnectionString() {
96          File config = null;
97          // Assuming, the path to the repository-config.-file is configured
98          // relative, starting with WEB-INF.
99          // Otherwise, assuming it's an absolute path for this config. (See JIRA
100         // MGNLUI-3163)
101         String configuredPath = magnoliaProperties.getProperty("magnolia.repositories.jackrabbit.config");
102         if (configuredPath != null) {
103             if (configuredPath.startsWith("WEB-INF")) {
104                 config = new File(magnoliaProperties.getProperty("magnolia.app.rootdir") + "/" + configuredPath);
105             } else {
106                 config = new File(configuredPath);
107             }
108         }
109         // No special handling here if the config (file) is null or not
110         // existing.
111         // If the path is wrong or not set, Magnolia won't start up properly and
112         // it won't be possible to launch the About-app.
113 
114         final String[] connectionString = new String[] { "", "", "" };
115         try {
116             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
117             factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
118 
119             DocumentBuilder builder = factory.newDocumentBuilder();
120             Document doc = builder.parse(config);
121             XPathFactory xPathfactory = XPathFactory.newInstance();
122             XPath xpath = xPathfactory.newXPath();
123             String url = (String) xpath.compile("/Repository/Workspace/PersistenceManager/param[@name='url']/@value")
124                     .evaluate(doc, XPathConstants.STRING);
125             if (StringUtils.isNotBlank(url)) {
126                 connectionString[0] = url;
127             } else {
128                 connectionString[0] = (String) xpath.compile(
129                         "/Repository/DataSources/DataSource/param[@name='url']/@value").evaluate(doc,
130                         XPathConstants.STRING);
131                 connectionString[1] = (String) xpath.compile(
132                         "/Repository/DataSources/DataSource/param[@name='user']/@value").evaluate(doc,
133                         XPathConstants.STRING);
134                 connectionString[2] = (String) xpath.compile(
135                         "/Repository/DataSources/DataSource/param[@name='password']/@value").evaluate(doc,
136                         XPathConstants.STRING);
137             }
138         } catch (Exception e) {
139             log.debug("Failed to obtain DB connection info with {}", e.getMessage(), e);
140         }
141         return connectionString;
142     }
143 
144     protected String getRepositoryName() {
145         String repoConfigPath = magnoliaProperties.getProperty("magnolia.repositories.config");
146         File config = new File(magnoliaProperties.getProperty("magnolia.app.rootdir") + "/" + repoConfigPath);
147         final String[] repoName = new String[1];
148         try {
149             SAXParserFactory.newInstance().newSAXParser().parse(config, new DefaultHandler() {
150                 private boolean inRepo;
151 
152                 @Override
153                 public void startElement(String uri, String localName, String qName, Attributes attributes)
154                         throws SAXException {
155                     super.startElement(uri, localName, qName, attributes);
156                     if ("RepositoryMapping".equals(qName)) {
157                         inRepo = true;
158                     }
159                     if (inRepo && "Map".equals(qName)) {
160                         if ("config".equals(attributes.getValue("name"))) {
161                             repoName[0] = attributes.getValue("repositoryName");
162                         }
163                     }
164                 }
165 
166                 @Override
167                 public void endElement(String uri, String localName, String qName) throws SAXException {
168                     super.endElement(uri, localName, qName);
169                     if ("RepositoryMapping".equals(localName)) {
170                         inRepo = false;
171                     }
172                 }
173             });
174             return repoName[0];
175         } catch (Exception e) {
176             log.debug("Failed to obtain repository configuration info with {}", e.getMessage(), e);
177         }
178         return null;
179     }
180 
181     protected String getRepositoryHome() {
182         return magnoliaProperties.getProperty("magnolia.repositories.home");
183     }
184 
185     private Connection getConnection() throws SQLException, NamingException {
186         Connection connection = null;
187         String connectionString[] = getConnectionString();
188         String repoHome = getRepositoryHome();
189         String repoName = getRepositoryName();
190         connectionString[0] = StringUtils.replace(connectionString[0], "${wsp.home}", repoHome + "/" + repoName
191                 + "/workspaces/default");
192         if (connectionString[0].startsWith("jdbc:")) {
193             // JDBC url
194             connection = DriverManager.getConnection(connectionString[0], connectionString[1], connectionString[2]);
195         } else if (connectionString[0].startsWith("java:")) {
196             // JNDI url
197             Context initialContext = new InitialContext();
198             DataSource datasource = (DataSource) initialContext.lookup(connectionString[0]);
199             if (datasource != null) {
200                 connection = datasource.getConnection();
201             } else {
202                 log.debug("Failed to lookup datasource.");
203             }
204         }
205         return connection;
206     }
207 
208     private String getMySQLEngineInfo(Connection connection, String[] connectionString) {
209         PreparedStatement statement = null;
210         ResultSet resultSet = null;
211         try {
212             statement = connection.prepareStatement("SHOW TABLE STATUS FROM `"
213                     + StringUtils.substringAfterLast(connectionString[0], "/") + "`;");
214             resultSet = statement.executeQuery();
215             if (resultSet.next()) {
216                 String engine = resultSet.getString("Engine");
217                 return " (" + engine + ")";
218             }
219         } catch (SQLException e) {
220             // can't get extra info, oh well
221         } finally {
222             try {
223                 if (resultSet != null) {
224                     resultSet.close();
225                 }
226                 if (statement != null) {
227                     statement.close();
228                 }
229             } catch (SQLException ignored) {
230             }
231         }
232         return null;
233     }
234 
235     public String getDatabase() {
236         String dbInfo = null;
237         Connection connection = null;
238         try {
239             connection = getConnection();
240             if (connection != null) {
241                 DatabaseMetaData meta = connection.getMetaData();
242                 dbInfo = meta.getDatabaseProductName() + " " + meta.getDatabaseProductVersion();
243                 if (dbInfo.toLowerCase().contains("mysql")) {
244                     String engine = getMySQLEngineInfo(connection, getConnectionString());
245                     if (engine != null) {
246                         dbInfo += engine;
247                     }
248                 }
249             }
250         } catch (NamingException e) {
251             log.debug("Failed obtain DB connection through JNDI with {}", e.getMessage(), e);
252         } catch (SQLException e) {
253             log.debug("Failed to read DB and driver info from connection with {}", e.getMessage(), e);
254         } catch (IllegalArgumentException e) {
255             log.debug("Failed to understand DB connection URL with {}", e.getMessage(), e);
256         } finally {
257             if (connection != null) {
258                 try {
259                     connection.close();
260                 } catch (SQLException e) {
261                     // ignore, nothing we can do
262                 }
263             }
264         }
265         return dbInfo;
266     }
267 
268     public String getDatabaseDriver() {
269         String dbDriverInfo = null;
270         Connection connection = null;
271         try {
272             connection = getConnection();
273             if (connection != null) {
274                 DatabaseMetaData meta = connection.getMetaData();
275                 dbDriverInfo = meta.getDriverName() + " " + meta.getDriverVersion();
276             }
277         } catch (NamingException e) {
278             log.debug("Failed obtain DB connection through JNDI with {}", e.getMessage(), e);
279         } catch (SQLException e) {
280             log.debug("Failed to read DB and driver info from connection with {}", e.getMessage(), e);
281         } catch (IllegalArgumentException e) {
282             log.debug("Failed to understand DB connection URL with {}", e.getMessage(), e);
283         } finally {
284             if (connection != null) {
285                 try {
286                     connection.close();
287                 } catch (SQLException e) {
288                     // ignore, nothing we can do
289                 }
290             }
291         }
292         return dbDriverInfo;
293     }
294 
295     public String getDatabaseVersion() {
296         return null;
297     }
298 
299     public String getJcrName() {
300         try {
301             Repository repo = JcrUtils.getRepository();
302             return repo.getDescriptor("jcr.repository.name");
303         } catch (RepositoryException e) {
304             log.debug("JCR repository information is not available", e);
305         }
306         return null;
307     }
308 
309     public String getJcrVersion() {
310         try {
311             Repository repo = JcrUtils.getRepository();
312             return repo.getDescriptor("jcr.repository.version");
313         } catch (RepositoryException e) {
314             log.debug("JCR repository information is not available", e);
315         }
316         return null;
317     }
318 
319     public String getMagnoliaVersion() {
320         return productDescriptorExtractor.get(ProductDescriptorExtractor.VERSION_NUMBER);
321     }
322 
323     public boolean isAdmin() {
324         return serverConfiguration.isAdmin();
325     }
326 
327     public String getOSName() {
328         return magnoliaProperties.getProperty("os.name");
329     }
330 
331     public String getOSVersion() {
332         return magnoliaProperties.getProperty("os.version");
333     }
334 
335     public String getOSArch() {
336         return magnoliaProperties.getProperty("os.arch");
337     }
338 
339     public String getJavaVendor() {
340         return magnoliaProperties.getProperty("java.vendor");
341     }
342 
343     public String getJavaVersion() {
344         return magnoliaProperties.getProperty("java.version");
345     }
346 
347     public String getJavaRuntimeVersion() {
348         return magnoliaProperties.getProperty("java.runtime.version");
349     }
350 
351     public String getApplicationServer() {
352         return contextProvider.get().getServletContext().getServerInfo();
353     }
354 
355     public String getEditionName() {
356         return productDescriptorExtractor.get(ProductDescriptorExtractor.EDITION);
357     }
358 }