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