View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.jmx;
18  
19  import java.beans.PropertyChangeEvent;
20  import java.beans.PropertyChangeListener;
21  import java.io.ByteArrayInputStream;
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.InputStreamReader;
26  import java.io.PrintWriter;
27  import java.io.Reader;
28  import java.io.StringWriter;
29  import java.net.URI;
30  import java.net.URISyntaxException;
31  import java.nio.charset.Charset;
32  import java.util.Map;
33  import java.util.concurrent.Executor;
34  import java.util.concurrent.atomic.AtomicLong;
35  
36  import javax.management.MBeanNotificationInfo;
37  import javax.management.Notification;
38  import javax.management.NotificationBroadcasterSupport;
39  import javax.management.ObjectName;
40  
41  import org.apache.logging.log4j.core.LoggerContext;
42  import org.apache.logging.log4j.core.config.Configuration;
43  import org.apache.logging.log4j.core.config.ConfigurationFactory;
44  import org.apache.logging.log4j.core.config.ConfigurationFactory.ConfigurationSource;
45  import org.apache.logging.log4j.core.helpers.Assert;
46  import org.apache.logging.log4j.core.helpers.Charsets;
47  import org.apache.logging.log4j.core.helpers.Closer;
48  import org.apache.logging.log4j.core.helpers.FileUtils;
49  import org.apache.logging.log4j.status.StatusLogger;
50  
51  /**
52   * Implementation of the {@code LoggerContextAdminMBean} interface.
53   */
54  public class LoggerContextAdmin extends NotificationBroadcasterSupport
55          implements LoggerContextAdminMBean, PropertyChangeListener {
56      private static final int PAGE = 4 * 1024;
57      private static final int TEXT_BUFFER = 64 * 1024;
58      private static final int BUFFER_SIZE = 2048;
59      private static final StatusLogger LOGGER = StatusLogger.getLogger();
60  
61      private final AtomicLong sequenceNo = new AtomicLong();
62      private final ObjectName objectName;
63      private final LoggerContext loggerContext;
64      private String customConfigText;
65  
66      /**
67       * Constructs a new {@code LoggerContextAdmin} with the {@code Executor} to
68       * be used for sending {@code Notification}s asynchronously to listeners.
69       *
70       * @param executor used to send notifications asynchronously
71       * @param loggerContext the instrumented object
72       */
73      public LoggerContextAdmin(final LoggerContext loggerContext, final Executor executor) {
74          super(executor, createNotificationInfo());
75          this.loggerContext = Assert.isNotNull(loggerContext, "loggerContext");
76          try {
77              final String ctxName = Server.escape(loggerContext.getName());
78              final String name = String.format(PATTERN, ctxName);
79              objectName = new ObjectName(name);
80          } catch (final Exception e) {
81              throw new IllegalStateException(e);
82          }
83          loggerContext.addPropertyChangeListener(this);
84      }
85  
86      private static MBeanNotificationInfo createNotificationInfo() {
87          final String[] notifTypes = new String[] {//
88                  NOTIF_TYPE_RECONFIGURED };
89          final String name = Notification.class.getName();
90          final String description = "Configuration reconfigured";
91          return new MBeanNotificationInfo(notifTypes, name, description);
92      }
93  
94      @Override
95      public String getStatus() {
96          return loggerContext.getStatus().toString();
97      }
98  
99      @Override
100     public String getName() {
101         return loggerContext.getName();
102     }
103 
104     private Configuration getConfig() {
105         return loggerContext.getConfiguration();
106     }
107 
108     @Override
109     public String getConfigLocationURI() {
110         if (loggerContext.getConfigLocation() != null) {
111             return String.valueOf(loggerContext.getConfigLocation());
112         }
113         if (getConfigName() != null) {
114             return String.valueOf(new File(getConfigName()).toURI());
115         }
116         return "";
117     }
118 
119     @Override
120     public void setConfigLocationURI(final String configLocation)
121             throws URISyntaxException, IOException {
122         LOGGER.debug("---------");
123         LOGGER.debug("Remote request to reconfigure using location "
124                 + configLocation);
125         final URI uri = FileUtils.getCorrectedFilePathUri(configLocation);
126 
127         // validate the location first: invalid location will result in
128         // default configuration being configured, try to avoid that...
129         uri.toURL().openStream().close();
130 
131         loggerContext.setConfigLocation(uri);
132         LOGGER.debug("Completed remote request to reconfigure.");
133     }
134 
135     @Override
136     public void propertyChange(final PropertyChangeEvent evt) {
137         if (!LoggerContext.PROPERTY_CONFIG.equals(evt.getPropertyName())) {
138             return;
139         }
140         // erase custom text if new configuration was read from a location
141         if (loggerContext.getConfiguration().getName() != null) {
142             customConfigText = null;
143         }
144         final Notification notif = new Notification(NOTIF_TYPE_RECONFIGURED,
145                 getObjectName(), nextSeqNo(), now(), null);
146         sendNotification(notif);
147     }
148 
149     @Override
150     public String getConfigText() throws IOException {
151         return getConfigText(Charsets.UTF_8.name());
152     }
153 
154     @Override
155     public String getConfigText(final String charsetName) throws IOException {
156         if (customConfigText != null) {
157             return customConfigText;
158         }
159         try {
160             final Charset charset = Charset.forName(charsetName);
161             return readContents(FileUtils.getCorrectedFilePathUri(getConfigLocationURI()), charset);
162         } catch (final Exception ex) {
163             final StringWriter sw = new StringWriter(BUFFER_SIZE);
164             ex.printStackTrace(new PrintWriter(sw));
165             return sw.toString();
166         }
167     }
168 
169     @Override
170     public void setConfigText(final String configText, final String charsetName) {
171         final String old = customConfigText;
172         customConfigText = Assert.isNotNull(configText, "configText");
173         LOGGER.debug("---------");
174         LOGGER.debug("Remote request to reconfigure from config text.");
175 
176         try {
177             final InputStream in = new ByteArrayInputStream(
178                     configText.getBytes(charsetName));
179             final ConfigurationSource source = new ConfigurationSource(in);
180             final Configuration updated = ConfigurationFactory.getInstance()
181                     .getConfiguration(source);
182             loggerContext.start(updated);
183             LOGGER.debug("Completed remote request to reconfigure from config text.");
184         } catch (final Exception ex) {
185             customConfigText = old;
186             final String msg = "Could not reconfigure from config text";
187             LOGGER.error(msg, ex);
188             throw new IllegalArgumentException(msg, ex);
189         }
190     }
191 
192     /**
193      * 
194      * @param uri
195      * @param charset MUST not be null
196      * @return
197      * @throws IOException
198      */
199     private String readContents(final URI uri, final Charset charset) throws IOException {
200         InputStream in = null;
201         Reader reader = null;
202         try {
203             in = uri.toURL().openStream();
204             reader = new InputStreamReader(in, charset);
205             final StringBuilder result = new StringBuilder(TEXT_BUFFER);
206             final char[] buff = new char[PAGE];
207             int count = -1;
208             while ((count = reader.read(buff)) >= 0) {
209                 result.append(buff, 0, count);
210             }
211             return result.toString();
212         } finally {
213             Closer.closeSilent(in);
214             Closer.closeSilent(reader);
215         }
216     }
217 
218     @Override
219     public String getConfigName() {
220         return getConfig().getName();
221     }
222 
223     @Override
224     public String getConfigClassName() {
225         return getConfig().getClass().getName();
226     }
227 
228     @Override
229     public String getConfigFilter() {
230         return String.valueOf(getConfig().getFilter());
231     }
232 
233     @Override
234     public String getConfigMonitorClassName() {
235         return getConfig().getConfigurationMonitor().getClass().getName();
236     }
237 
238     @Override
239     public Map<String, String> getConfigProperties() {
240         return getConfig().getProperties();
241     }
242 
243     /**
244      * Returns the {@code ObjectName} of this mbean.
245      *
246      * @return the {@code ObjectName}
247      * @see LoggerContextAdminMBean#PATTERN
248      */
249     @Override
250     public ObjectName getObjectName() {
251         return objectName;
252     }
253 
254     private long nextSeqNo() {
255         return sequenceNo.getAndIncrement();
256     }
257 
258     private long now() {
259         return System.currentTimeMillis();
260     }
261 }