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