View Javadoc
1   /**
2    * This file Copyright (c) 2003-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.logging;
35  
36  import info.magnolia.cms.core.FileSystemHelper;
37  import info.magnolia.init.MagnoliaConfigurationProperties;
38  import info.magnolia.objectfactory.Components;
39  
40  import java.io.ByteArrayInputStream;
41  import java.io.IOException;
42  import java.io.InputStream;
43  import java.util.Collections;
44  import java.util.Locale;
45  import java.util.Map;
46  import java.util.Properties;
47  
48  import javax.xml.parsers.DocumentBuilder;
49  import javax.xml.parsers.DocumentBuilderFactory;
50  import javax.xml.parsers.ParserConfigurationException;
51  
52  import org.apache.commons.io.IOUtils;
53  import org.apache.commons.lang3.StringUtils;
54  import org.apache.log4j.LogManager;
55  import org.apache.log4j.PropertyConfigurator;
56  import org.apache.log4j.xml.DOMConfigurator;
57  import org.w3c.dom.Document;
58  import org.xml.sax.EntityResolver;
59  import org.xml.sax.InputSource;
60  import org.xml.sax.SAXException;
61  
62  /**
63   * <p>
64   * Log4j initializer. Loads the file specified using the <code>log4j.config</code> init parameter and optionally set a
65   * system property containing the magnolia web application root directory with the name specified by the
66   * <code>magnolia.root.sysproperty</code> init parameter.
67   * </p>
68   * <p>
69   * If <code>magnolia.root.sysproperty</code> is empty no system variable will be set; if <code>log4j.config</code>
70   * is empty no log4j initialization will be performed.
71   * </p>
72   * <p>
73   * You can easily specify relative paths for log4j configuration files using the magnolia root system property, for
74   * example using <code>${magnolia.root}logs/magnolia-debug.log</code>
75   * </p>
76   * <p>
77   * Note: if you drop multiple magnolia wars in a container which doesn't isolate system properties (e.g. tomcat) you
78   * could need to change the name of the <code>magnolia.root.sysproperty</code> variable in web.xml and in log4j
79   * configuration files.
80   * </p>
81   * <p>
82   * <em>Some ideas and snippets borrowed from the more complex Spring implementation http://www.springframework.org</em>;
83   * </p>
84   */
85  public class Log4jConfigurer {
86  
87      /**
88       * Init parameter specifying the location of the Log4J config file.
89       */
90      public static final String LOG4J_CONFIG = "log4j.config";
91  
92      /**
93       * Initialize Log4J, including setting the web app root system property.
94       * @deprecated since 5.5.2, use {@link #initLogging(MagnoliaConfigurationProperties, FileSystemHelper)} instead.
95       */
96      @Deprecated
97      public static void initLogging() {
98          initLogging(Components.getComponent(MagnoliaConfigurationProperties.class), Components.getComponent(FileSystemHelper.class));
99      }
100 
101     /**
102      * Initialize Log4J, including setting the web app root system property.
103      */
104     public static void initLogging(MagnoliaConfigurationProperties configurationProperties, FileSystemHelper fileSystemHelper) {
105 
106         // can't use log4j yet
107         log("Initializing Log4J");
108 
109         String log4jFileName = configurationProperties.getProperty(LOG4J_CONFIG);
110         if (StringUtils.isNotEmpty(log4jFileName)) {
111             boolean isXml = log4jFileName.toLowerCase(Locale.ROOT).endsWith(".xml");
112 
113             log("Initializing Log4J from [" + log4jFileName + "]");
114 
115             final String config;
116             try {
117                 config = fileSystemHelper.getTokenizedConfigFile(log4jFileName);
118             } catch (IOException e) {
119                 log("Unable to initialize Log4J from [" + log4jFileName + "], got a IOException " + e.getMessage());
120                 return;
121             }
122 
123             // classpath?
124             if (isXml) {
125                 try {
126                     final Map dtds = Collections.singletonMap("log4j.dtd", "/org/apache/log4j/xml/log4j.dtd");
127                     final Document document = string2DOM(config, dtds);
128                     DOMConfigurator.configure(document.getDocumentElement());
129                 } catch (Exception e) {
130                     log("Unable to initialize Log4J from [" + log4jFileName + "], got an Exception during reading the xml file : " + e.getMessage());
131                 }
132             } else {
133                 try {
134                     final Properties properties = new Properties();
135                     properties.load(IOUtils.toInputStream(config));
136                     PropertyConfigurator.configure(properties);
137                 } catch (IOException e) {
138                     log("Unable to initialize Log4J from [" + log4jFileName + "], got an Exception during reading the properties file : " + e.getMessage());
139                 }
140             }
141 
142         }
143     }
144 
145     /**
146      * Shuts down Log4J.
147      */
148     public static void shutdownLogging() {
149         log("Shutting down Log4J");
150         LogManager.shutdown();
151     }
152 
153     /**
154      * Handy System.out method to use when logging isn't configured yet.
155      *
156      * @param message log message
157      */
158     private static void log(String message) {
159         System.out.println(message);
160     }
161 
162 
163     /**
164      * Uses a map to find dtds in the resources.
165      */
166     private static Document string2DOM(String xml, final Map<String, String> dtds) throws ParserConfigurationException, SAXException, IOException {
167         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
168         dbf.setValidating(false);
169         // when dtds are not specified, prevent from loading external entities
170         if (dtds.size() == 0) {
171             dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
172             dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
173             dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
174             dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
175             dbf.setNamespaceAware(true);
176         }
177         DocumentBuilder builder = dbf.newDocumentBuilder();
178         builder.setEntityResolver(new MapDTDEntityResolver(dtds));
179         return builder.parse(IOUtils.toInputStream(xml));
180     }
181 
182     /**
183      * EntityResolver using a Map to resources.
184      */
185     private static final class MapDTDEntityResolver implements EntityResolver {
186 
187         private final Map<String, String> dtds;
188 
189         private MapDTDEntityResolver(Map<String, String> dtds) {
190             this.dtds = dtds;
191         }
192 
193         @Override
194         public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
195             String key = StringUtils.substringAfterLast(systemId, "/");
196             if (dtds.containsKey(key)) {
197                 Class<? extends MapDTDEntityResolver> clazz = getClass();
198                 InputStream in = clazz.getResourceAsStream(dtds.get(key));
199                 if (in == null) {
200                     log("Could not find [" + systemId + "]. Used [" + clazz.getClassLoader() + "] class loader in the search, parsed without DTD.");
201                     return new InputSource(new ByteArrayInputStream(new byte[]{}));
202                 }
203                 return new InputSource(in);
204             }
205             return new InputSource(new ByteArrayInputStream(new byte[]{}));
206         }
207     }
208 
209 }