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.status.StatusLogger;
48  
49  /**
50   * Implementation of the {@code LoggerContextAdminMBean} interface.
51   */
52  public class LoggerContextAdmin extends NotificationBroadcasterSupport
53          implements LoggerContextAdminMBean, PropertyChangeListener {
54      private static final int PAGE = 4 * 1024;
55      private static final int TEXT_BUFFER = 64 * 1024;
56      private static final int BUFFER_SIZE = 2048;
57      private static final StatusLogger LOGGER = StatusLogger.getLogger();
58  
59      private final AtomicLong sequenceNo = new AtomicLong();
60      private final ObjectName objectName;
61      private final LoggerContext loggerContext;
62      private String customConfigText;
63  
64      /**
65       * Constructs a new {@code LoggerContextAdmin} with the {@code Executor} to
66       * be used for sending {@code Notification}s asynchronously to listeners.
67       *
68       * @param executor used to send notifications asynchronously
69       * @param loggerContext the instrumented object
70       */
71      public LoggerContextAdmin(LoggerContext loggerContext, Executor executor) {
72          super(executor, createNotificationInfo());
73          this.loggerContext = Assert.isNotNull(loggerContext, "loggerContext");
74          try {
75              String ctxName = Server.escape(loggerContext.getName());
76              String name = String.format(PATTERN, ctxName);
77              objectName = new ObjectName(name);
78          } catch (Exception e) {
79              throw new IllegalStateException(e);
80          }
81          loggerContext.addPropertyChangeListener(this);
82      }
83  
84      private static MBeanNotificationInfo createNotificationInfo() {
85          String[] notifTypes = new String[] {//
86                  NOTIF_TYPE_RECONFIGURED };
87          String name = Notification.class.getName();
88          String description = "Configuration reconfigured";
89          return new MBeanNotificationInfo(notifTypes, name, description);
90      }
91  
92      @Override
93      public String getStatus() {
94          return loggerContext.getStatus().toString();
95      }
96  
97      @Override
98      public String getName() {
99          return loggerContext.getName();
100     }
101 
102     private Configuration getConfig() {
103         return loggerContext.getConfiguration();
104     }
105 
106     @Override
107     public String getConfigLocationURI() {
108         if (loggerContext.getConfigLocation() != null) {
109             return String.valueOf(loggerContext.getConfigLocation());
110         }
111         if (getConfigName() != null) {
112             return String.valueOf(new File(getConfigName()).toURI());
113         }
114         return "";
115     }
116 
117     @Override
118     public void setConfigLocationURI(String configLocation)
119             throws URISyntaxException, IOException {
120         LOGGER.debug("---------");
121         LOGGER.debug("Remote request to reconfigure using location "
122                 + configLocation);
123         URI uri = new URI(configLocation);
124 
125         // validate the location first: invalid location will result in
126         // default configuration being configured, try to avoid that...
127         uri.toURL().openStream().close();
128 
129         loggerContext.setConfigLocation(uri);
130         LOGGER.debug("Completed remote request to reconfigure.");
131     }
132 
133     @Override
134     public void propertyChange(PropertyChangeEvent evt) {
135         if (!LoggerContext.PROPERTY_CONFIG.equals(evt.getPropertyName())) {
136             return;
137         }
138         // erase custom text if new configuration was read from a location
139         if (loggerContext.getConfiguration().getName() != null) {
140             customConfigText = null;
141         }
142         Notification notif = new Notification(NOTIF_TYPE_RECONFIGURED,
143                 getObjectName(), nextSeqNo(), now(), null);
144         sendNotification(notif);
145     }
146 
147     @Override
148     public String getConfigText() throws IOException {
149         return getConfigText(Charsets.UTF_8.name());
150     }
151 
152     @Override
153     public String getConfigText(String charsetName) throws IOException {
154         if (customConfigText != null) {
155             return customConfigText;
156         }
157         try {
158             Charset charset = Charset.forName(charsetName);
159             return readContents(new URI(getConfigLocationURI()), charset);
160         } catch (Exception ex) {
161             StringWriter sw = new StringWriter(BUFFER_SIZE);
162             ex.printStackTrace(new PrintWriter(sw));
163             return sw.toString();
164         }
165     }
166 
167     @Override
168     public void setConfigText(String configText, String charsetName) {
169         String old = customConfigText;
170         customConfigText = Assert.isNotNull(configText, "configText");
171         LOGGER.debug("---------");
172         LOGGER.debug("Remote request to reconfigure from config text.");
173 
174         try {
175             InputStream in = new ByteArrayInputStream(
176                     configText.getBytes(charsetName));
177             ConfigurationSource source = new ConfigurationSource(in);
178             Configuration updated = ConfigurationFactory.getInstance()
179                     .getConfiguration(source);
180             loggerContext.start(updated);
181             LOGGER.debug("Completed remote request to reconfigure from config text.");
182         } catch (Exception ex) {
183             customConfigText = old;
184             String msg = "Could not reconfigure from config text";
185             LOGGER.error(msg, ex);
186             throw new IllegalArgumentException(msg, ex);
187         }
188     }
189 
190     private String readContents(URI uri, Charset charset) throws IOException {
191         InputStream in = null;
192         try {
193             in = uri.toURL().openStream();
194             Reader reader = new InputStreamReader(in, charset);
195             StringBuilder result = new StringBuilder(TEXT_BUFFER);
196             char[] buff = new char[PAGE];
197             int count = -1;
198             while ((count = reader.read(buff)) >= 0) {
199                 result.append(buff, 0, count);
200             }
201             return result.toString();
202         } finally {
203             try {
204                 in.close();
205             } catch (Exception ignored) {
206                 // ignored
207             }
208         }
209     }
210 
211     @Override
212     public String getConfigName() {
213         return getConfig().getName();
214     }
215 
216     @Override
217     public String getConfigClassName() {
218         return getConfig().getClass().getName();
219     }
220 
221     @Override
222     public String getConfigFilter() {
223         return String.valueOf(getConfig().getFilter());
224     }
225 
226     @Override
227     public String getConfigMonitorClassName() {
228         return getConfig().getConfigurationMonitor().getClass().getName();
229     }
230 
231     @Override
232     public Map<String, String> getConfigProperties() {
233         return getConfig().getProperties();
234     }
235 
236     /**
237      * Returns the {@code ObjectName} of this mbean.
238      * 
239      * @return the {@code ObjectName}
240      * @see LoggerContextAdminMBean#PATTERN
241      */
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 }