View Javadoc
1   /**
2    * This file Copyright (c) 2013-2017 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 static info.magnolia.about.app.AboutView.*;
37  
38  import info.magnolia.cms.beans.config.ServerConfiguration;
39  import info.magnolia.cms.pddescriptor.ProductDescriptorExtractor;
40  import info.magnolia.context.MgnlContext;
41  import info.magnolia.i18nsystem.SimpleTranslator;
42  import info.magnolia.init.MagnoliaConfigurationProperties;
43  import info.magnolia.objectfactory.Components;
44  
45  import java.io.File;
46  import java.sql.Connection;
47  import java.sql.DatabaseMetaData;
48  import java.sql.DriverManager;
49  import java.sql.PreparedStatement;
50  import java.sql.ResultSet;
51  import java.sql.SQLException;
52  
53  import javax.inject.Inject;
54  import javax.jcr.Repository;
55  import javax.jcr.RepositoryException;
56  import javax.naming.Context;
57  import javax.naming.InitialContext;
58  import javax.naming.NamingException;
59  import javax.sql.DataSource;
60  import javax.xml.parsers.DocumentBuilder;
61  import javax.xml.parsers.DocumentBuilderFactory;
62  import javax.xml.parsers.SAXParserFactory;
63  import javax.xml.xpath.XPath;
64  import javax.xml.xpath.XPathConstants;
65  import javax.xml.xpath.XPathFactory;
66  
67  import org.apache.commons.lang3.StringUtils;
68  import org.apache.jackrabbit.commons.JcrUtils;
69  import org.slf4j.Logger;
70  import org.slf4j.LoggerFactory;
71  import org.w3c.dom.Document;
72  import org.xml.sax.Attributes;
73  import org.xml.sax.SAXException;
74  import org.xml.sax.helpers.DefaultHandler;
75  
76  import com.vaadin.data.Item;
77  import com.vaadin.data.util.ObjectProperty;
78  import com.vaadin.data.util.PropertysetItem;
79  
80  /**
81   * The AboutPresenter.
82   */
83  public class AboutPresenter {
84  
85      private static final Logger log = LoggerFactory.getLogger(AboutPresenter.class);
86  
87      static final String COMMUNITY_EDITION_I18N_KEY = "about.app.main.communityEdition";
88      static final String INSTANCE_AUTHOR_I18N_KEY = "about.app.main.instance.author";
89      static final String INSTANCE_PUBLIC_I18N_KEY = "about.app.main.instance.public";
90      static final String UNKNOWN_PROPERTY_I18N_KEY = "about.app.main.unknown";
91  
92      protected final SimpleTranslator i18n;
93      private final String unknownProperty;
94  
95      private final AboutView view;
96      private final ProductDescriptorExtractor productDescriptorExtractor;
97      private final ServerConfiguration serverConfiguration;
98      private final MagnoliaConfigurationProperties magnoliaProperties;
99  
100     /**
101      * @deprecated since 5.5, get view data through {@link #getInstallationInfo()}
102      */
103     @Deprecated
104     protected Item viewData = new PropertysetItem();
105 
106     @Inject
107     public AboutPresenter(AboutView view, ServerConfiguration serverConfiguration, MagnoliaConfigurationProperties magnoliaProperties, SimpleTranslator i18n, ProductDescriptorExtractor productDescriptorExtractor) {
108         this.view = view;
109         this.productDescriptorExtractor = productDescriptorExtractor;
110         this.serverConfiguration = serverConfiguration;
111         this.magnoliaProperties = magnoliaProperties;
112         this.i18n = i18n;
113         this.unknownProperty = i18n.translate(UNKNOWN_PROPERTY_I18N_KEY);
114     }
115 
116     /**
117      * @deprecated Since 5.4.3, use {@link #AboutPresenter(AboutView, ServerConfiguration, MagnoliaConfigurationProperties, SimpleTranslator, ProductDescriptorExtractor)}
118      */
119     @Deprecated
120     public AboutPresenter(AboutView view, ServerConfiguration serverConfiguration, MagnoliaConfigurationProperties magnoliaProperties, SimpleTranslator i18n) {
121         this(view, serverConfiguration, magnoliaProperties, i18n, Components.getComponent(ProductDescriptorExtractor.class));
122     }
123 
124     public AboutView start() {
125         Item viewData = getInstallationInfo();
126         // preserve compatibility
127         if (!this.viewData.getItemPropertyIds().isEmpty()) {
128             this.viewData.getItemPropertyIds()
129                     .forEach(propertyId -> viewData.addItemProperty(propertyId, AboutPresenter.this.viewData.getItemProperty(propertyId)));
130         }
131         view.setDataSource(viewData);
132         return view;
133     }
134 
135     protected Item getInstallationInfo() {
136         PropertysetItem viewData = new PropertysetItem();
137         // Magnolia information
138         String mgnlEdition = getEditionName();
139         String mgnlVersion = productDescriptorExtractor.get(ProductDescriptorExtractor.VERSION_NUMBER);
140         String authorInstance = serverConfiguration.isAdmin() ?
141                 i18n.translate(INSTANCE_AUTHOR_I18N_KEY) :
142                 i18n.translate(INSTANCE_PUBLIC_I18N_KEY);
143 
144         // System information
145         String osInfo = String.format("%s %s (%s)",
146                 magnoliaProperties.getProperty("os.name"),
147                 magnoliaProperties.getProperty("os.version"),
148                 magnoliaProperties.getProperty("os.arch"));
149         String javaInfo = String.format("%s (build %s)",
150                 magnoliaProperties.getProperty("java.version"),
151                 magnoliaProperties.getProperty("java.runtime.version"));
152         String serverInfo = MgnlContext.getWebContext().getServletContext().getServerInfo();
153 
154         String dbInfo = null;
155         String dbDriverInfo = null;
156         Connection connection = null;
157         try {
158             String connectionString[] = getConnectionString();
159 
160             String repoHome = magnoliaProperties.getProperty("magnolia.repositories.home");
161             String repoName = getRepoName();
162             connectionString[0] = StringUtils.replace(connectionString[0], "${wsp.home}", repoHome + "/" + repoName + "/workspaces/default");
163             if (connectionString[0].startsWith("jdbc:")) {
164                 // JDBC url
165                 connection = DriverManager.getConnection(connectionString[0], connectionString[1], connectionString[2]);
166             } else if (connectionString[0].startsWith("java:")) {
167                 // JNDI url
168                 Context initialContext = new InitialContext();
169                 DataSource datasource = (DataSource) initialContext.lookup(connectionString[0]);
170                 if (datasource != null) {
171                     connection = datasource.getConnection();
172                 } else {
173                     log.debug("Failed to lookup datasource.");
174                 }
175             }
176             if (connection != null) {
177                 DatabaseMetaData meta = connection.getMetaData();
178                 dbInfo = meta.getDatabaseProductName() + " " + meta.getDatabaseProductVersion();
179                 if (dbInfo.toLowerCase().contains("mysql")) {
180                     String engine = getMySQLEngineInfo(connection, connectionString);
181                     if (engine != null) {
182                         dbInfo += engine;
183                     }
184                 }
185                 dbDriverInfo = meta.getDriverName() + " " + meta.getDriverVersion();
186             } else {
187                 dbInfo = i18n.translate(UNKNOWN_PROPERTY_I18N_KEY);
188             }
189         } catch (NamingException e) {
190             log.debug("Failed obtain DB connection through JNDI with {}", e.getMessage(), e);
191         } catch (SQLException e) {
192             log.debug("Failed to read DB and driver info from connection with {}", e.getMessage(), e);
193         } catch (IllegalArgumentException e) {
194             log.debug("Failed to understand DB connection URL with {}", e.getMessage(), e);
195         } finally {
196             if (connection != null) {
197                 try {
198                     connection.close();
199                 } catch (SQLException e) {
200                     // ignore, nothing we can do
201                 }
202             }
203         }
204 
205         String jcrInfo;
206         try {
207             Repository repo = JcrUtils.getRepository();
208             jcrInfo = String.format("%s %s",
209                     repo.getDescriptor("jcr.repository.name"),
210                     repo.getDescriptor("jcr.repository.version"));
211         } catch (RepositoryException e) {
212             log.debug("JCR repository information is not available", e);
213             jcrInfo = "-";
214         }
215 
216         // Prepare information for the view
217         addItemProperty(viewData, MAGNOLIA_EDITION_KEY, mgnlEdition);
218         addItemProperty(viewData, MAGNOLIA_VERSION_KEY, mgnlVersion);
219         addItemProperty(viewData, MAGNOLIA_INSTANCE_KEY, authorInstance);
220         addItemProperty(viewData, OS_INFO_KEY, osInfo);
221         addItemProperty(viewData, JAVA_INFO_KEY, javaInfo);
222         addItemProperty(viewData, SERVER_INFO_KEY, serverInfo);
223         addItemProperty(viewData, JCR_INFO_KEY, jcrInfo);
224         addItemProperty(viewData, DB_INFO_KEY, dbInfo);
225         addItemProperty(viewData, DB_DRIVER_INFO_KEY, dbDriverInfo);
226 
227         return viewData;
228     }
229 
230     /**
231      * Adds a property to the given Item data-source; blank values will be translated as 'unknown'.
232      */
233     protected void addItemProperty(Item viewData, String key, String value) {
234         viewData.addItemProperty(key, new ObjectProperty<>(StringUtils.defaultIfBlank(value, unknownProperty)));
235     }
236 
237     /**
238      * @deprecated since 5.5, use {@link #addItemProperty(Item, String, String)} instead
239      */
240     @Deprecated
241     protected void addViewProperty(String key, String value) {
242         viewData.addItemProperty(key, new ObjectProperty<>(StringUtils.defaultIfBlank(value, unknownProperty)));
243     }
244 
245     protected AboutView getView() {
246         return view;
247     }
248 
249     /**
250      * Returns the name of the edition.
251      */
252     protected String getEditionName() {
253         // Hard code this in CE edition - value will be correctly populated for EE in EnterpriseAboutPresenter
254         return i18n.translate(COMMUNITY_EDITION_I18N_KEY);
255     }
256 
257     private String getMySQLEngineInfo(Connection connection, String[] connectionString) {
258         PreparedStatement statement = null;
259         ResultSet resultSet = null;
260         try {
261             statement = connection.prepareStatement("SHOW TABLE STATUS FROM `" + StringUtils.substringAfterLast(connectionString[0], "/") + "`;");
262             resultSet = statement.executeQuery();
263             if (resultSet.next()) {
264                 String engine = resultSet.getString("Engine");
265                 return " (" + engine + ")";
266             }
267         } catch (SQLException e) {
268             // can't get extra info, oh well
269         } finally {
270             try {
271                 if (resultSet != null) {
272                     resultSet.close();
273                 }
274                 if (statement != null) {
275                     statement.close();
276                 }
277             } catch (SQLException ignored) {
278             }
279         }
280         return null;
281     }
282 
283     String[] getConnectionString() {
284         File config = null;
285         // Assuming, the path to the repository-config.-file is configured relative, starting with WEB-INF.
286         // Otherwise, assuming it's an absolute path for this config. (See JIRA MGNLUI-3163)
287         String configuredPath = magnoliaProperties.getProperty("magnolia.repositories.jackrabbit.config");
288         if (configuredPath != null) {
289             if (configuredPath.startsWith("WEB-INF")) {
290                 config = new File(magnoliaProperties.getProperty("magnolia.app.rootdir") + "/" + configuredPath);
291             } else {
292                 config = new File(configuredPath);
293             }
294         }
295         // No special handling here if the config (file) is null or not existing.
296         // If the path is wrong or not set, Magnolia won't start up properly and it won't be possible to launch the About-app.
297 
298         final String[] connectionString = new String[]{"", "", ""};
299         try {
300             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
301             DocumentBuilder builder = factory.newDocumentBuilder();
302             Document doc = builder.parse(config);
303             XPathFactory xPathfactory = XPathFactory.newInstance();
304             XPath xpath = xPathfactory.newXPath();
305             String url = (String) xpath.compile("/Repository/Workspace/PersistenceManager/param[@name='url']/@value").evaluate(doc, XPathConstants.STRING);
306             if (StringUtils.isNotBlank(url)) {
307                 connectionString[0] = url;
308             } else {
309                 connectionString[0] = (String) xpath.compile("/Repository/DataSources/DataSource/param[@name='url']/@value").evaluate(doc, XPathConstants.STRING);
310                 connectionString[1] = (String) xpath.compile("/Repository/DataSources/DataSource/param[@name='user']/@value").evaluate(doc, XPathConstants.STRING);
311                 connectionString[2] = (String) xpath.compile("/Repository/DataSources/DataSource/param[@name='password']/@value").evaluate(doc, XPathConstants.STRING);
312             }
313         } catch (Exception e) {
314             log.debug("Failed to obtain DB connection info with {}", e.getMessage(), e);
315         }
316         return connectionString;
317     }
318 
319     String getRepoName() {
320         String repoConfigPath = magnoliaProperties.getProperty("magnolia.repositories.config");
321         File config = new File(magnoliaProperties.getProperty("magnolia.app.rootdir") + "/" + repoConfigPath);
322         final String[] repoName = new String[1];
323         try {
324             SAXParserFactory.newInstance().newSAXParser().parse(config, new DefaultHandler() {
325                 private boolean inRepo;
326 
327                 @Override
328                 public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
329                     super.startElement(uri, localName, qName, attributes);
330                     if ("RepositoryMapping".equals(qName)) {
331                         inRepo = true;
332                     }
333                     if (inRepo && "Map".equals(qName)) {
334                         if ("config".equals(attributes.getValue("name"))) {
335                             repoName[0] = attributes.getValue("repositoryName");
336                         }
337                     }
338                 }
339 
340                 @Override
341                 public void endElement(String uri, String localName, String qName) throws SAXException {
342                     super.endElement(uri, localName, qName);
343                     if ("RepositoryMapping".equals(localName)) {
344                         inRepo = false;
345                     }
346                 }
347             });
348             return repoName[0];
349         } catch (Exception e) {
350             log.debug("Failed to obtain repository configuration info with {}", e.getMessage(), e);
351         }
352         return null;
353     }
354 }