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.FileInputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.InputStreamReader;
27  import java.io.PrintWriter;
28  import java.io.Reader;
29  import java.io.StringWriter;
30  import java.net.URISyntaxException;
31  import java.net.URL;
32  import java.nio.charset.Charset;
33  import java.util.Map;
34  import java.util.concurrent.Executor;
35  import java.util.concurrent.atomic.AtomicLong;
36  
37  import javax.management.MBeanNotificationInfo;
38  import javax.management.Notification;
39  import javax.management.NotificationBroadcasterSupport;
40  import javax.management.ObjectName;
41  
42  import org.apache.logging.log4j.core.LoggerContext;
43  import org.apache.logging.log4j.core.config.Configuration;
44  import org.apache.logging.log4j.core.config.ConfigurationFactory;
45  import org.apache.logging.log4j.core.config.ConfigurationSource;
46  import org.apache.logging.log4j.core.util.Assert;
47  import org.apache.logging.log4j.core.util.Closer;
48  import org.apache.logging.log4j.core.util.Constants;
49  import org.apache.logging.log4j.status.StatusLogger;
50  import org.apache.logging.log4j.util.Strings;
51  
52  /**
53   * Implementation of the {@code LoggerContextAdminMBean} interface.
54   */
55  public class LoggerContextAdmin extends NotificationBroadcasterSupport implements LoggerContextAdminMBean,
56          PropertyChangeListener {
57      private static final int PAGE = 4 * 1024;
58      private static final int TEXT_BUFFER = 64 * 1024;
59      private static final int BUFFER_SIZE = 2048;
60      private static final StatusLogger LOGGER = StatusLogger.getLogger();
61  
62      private final AtomicLong sequenceNo = new AtomicLong();
63      private final ObjectName objectName;
64      private final LoggerContext loggerContext;
65  
66      /**
67       * Constructs a new {@code LoggerContextAdmin} with the {@code Executor} to be used for sending {@code Notification}
68       * 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.requireNonNull(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[] { NOTIF_TYPE_RECONFIGURED };
88          final String name = Notification.class.getName();
89          final String description = "Configuration reconfigured";
90          return new MBeanNotificationInfo(notifTypes, name, description);
91      }
92  
93      @Override
94      public String getStatus() {
95          return loggerContext.getState().toString();
96      }
97  
98      @Override
99      public String getName() {
100         return loggerContext.getName();
101     }
102 
103     private Configuration getConfig() {
104         return loggerContext.getConfiguration();
105     }
106 
107     @Override
108     public String getConfigLocationUri() {
109         if (loggerContext.getConfigLocation() != null) {
110             return String.valueOf(loggerContext.getConfigLocation());
111         }
112         if (getConfigName() != null) {
113             return String.valueOf(new File(getConfigName()).toURI());
114         }
115         return Strings.EMPTY;
116     }
117 
118     @Override
119     public void setConfigLocationUri(final String configLocation) throws URISyntaxException, IOException {
120         if (configLocation == null || configLocation.isEmpty()) {
121             throw new IllegalArgumentException("Missing configuration location");
122         }
123         LOGGER.debug("---------");
124         LOGGER.debug("Remote request to reconfigure using location " + configLocation);
125         final File configFile = new File(configLocation);
126         ConfigurationSource configSource = null;
127         if (configFile.exists()) {
128             LOGGER.debug("Opening config file {}", configFile.getAbsolutePath());
129             configSource = new ConfigurationSource(new FileInputStream(configFile), configFile);
130         } else {
131             final URL configURL = new URL(configLocation);
132             LOGGER.debug("Opening config URL {}", configURL);
133             configSource = new ConfigurationSource(configURL.openStream(), configURL);
134         }
135         final Configuration config = ConfigurationFactory.getInstance().getConfiguration(configSource);
136         loggerContext.start(config);
137         LOGGER.debug("Completed remote request to reconfigure.");
138     }
139 
140     @Override
141     public void propertyChange(final PropertyChangeEvent evt) {
142         if (!LoggerContext.PROPERTY_CONFIG.equals(evt.getPropertyName())) {
143             return;
144         }
145         final Notification notif = new Notification(NOTIF_TYPE_RECONFIGURED, getObjectName(), nextSeqNo(), now(), null);
146         sendNotification(notif);
147     }
148 
149     @Override
150     public String getConfigText() throws IOException {
151         return getConfigText(Constants.UTF_8.name());
152     }
153 
154     @Override
155     public String getConfigText(final String charsetName) throws IOException {
156         try {
157             final ConfigurationSource source = loggerContext.getConfiguration().getConfigurationSource();
158             final ConfigurationSource copy = source.resetInputStream();
159             final Charset charset = Charset.forName(charsetName);
160             return readContents(copy.getInputStream(), charset);
161         } catch (final Exception ex) {
162             final StringWriter sw = new StringWriter(BUFFER_SIZE);
163             ex.printStackTrace(new PrintWriter(sw));
164             return sw.toString();
165         }
166     }
167 
168     /**
169      * Returns the contents of the specified input stream as a String.
170      * @param in stream to read from
171      * @param charset MUST not be null
172      * @return stream contents
173      * @throws IOException if a problem occurred reading from the stream.
174      */
175     private String readContents(final InputStream in, final Charset charset) throws IOException {
176         Reader reader = null;
177         try {
178             reader = new InputStreamReader(in, charset);
179             final StringBuilder result = new StringBuilder(TEXT_BUFFER);
180             final char[] buff = new char[PAGE];
181             int count = -1;
182             while ((count = reader.read(buff)) >= 0) {
183                 result.append(buff, 0, count);
184             }
185             return result.toString();
186         } finally {
187             Closer.closeSilently(in);
188             Closer.closeSilently(reader);
189         }
190     }
191 
192     @Override
193     public void setConfigText(final String configText, final String charsetName) {
194         LOGGER.debug("---------");
195         LOGGER.debug("Remote request to reconfigure from config text.");
196 
197         try {
198             final InputStream in = new ByteArrayInputStream(configText.getBytes(charsetName));
199             final ConfigurationSource source = new ConfigurationSource(in);
200             final Configuration updated = ConfigurationFactory.getInstance().getConfiguration(source);
201             loggerContext.start(updated);
202             LOGGER.debug("Completed remote request to reconfigure from config text.");
203         } catch (final Exception ex) {
204             final String msg = "Could not reconfigure from config text";
205             LOGGER.error(msg, ex);
206             throw new IllegalArgumentException(msg, ex);
207         }
208     }
209 
210     @Override
211     public String getConfigName() {
212         return getConfig().getName();
213     }
214 
215     @Override
216     public String getConfigClassName() {
217         return getConfig().getClass().getName();
218     }
219 
220     @Override
221     public String getConfigFilter() {
222         return String.valueOf(getConfig().getFilter());
223     }
224 
225     @Override
226     public String getConfigMonitorClassName() {
227         return getConfig().getConfigurationMonitor().getClass().getName();
228     }
229 
230     @Override
231     public Map<String, String> getConfigProperties() {
232         return getConfig().getProperties();
233     }
234 
235     /**
236      * Returns the {@code ObjectName} of this mbean.
237      *
238      * @return the {@code ObjectName}
239      * @see LoggerContextAdminMBean#PATTERN
240      */
241     @Override
242     public ObjectName getObjectName() {
243         return objectName;
244     }
245 
246     private long nextSeqNo() {
247         return sequenceNo.getAndIncrement();
248     }
249 
250     private long now() {
251         return System.currentTimeMillis();
252     }
253 }