001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017 package org.apache.logging.log4j.core.jmx;
018
019 import java.beans.PropertyChangeEvent;
020 import java.beans.PropertyChangeListener;
021 import java.io.ByteArrayInputStream;
022 import java.io.File;
023 import java.io.FileInputStream;
024 import java.io.IOException;
025 import java.io.InputStream;
026 import java.io.InputStreamReader;
027 import java.io.PrintWriter;
028 import java.io.Reader;
029 import java.io.StringWriter;
030 import java.net.URISyntaxException;
031 import java.net.URL;
032 import java.nio.charset.Charset;
033 import java.util.Map;
034 import java.util.concurrent.Executor;
035 import java.util.concurrent.atomic.AtomicLong;
036
037 import javax.management.MBeanNotificationInfo;
038 import javax.management.Notification;
039 import javax.management.NotificationBroadcasterSupport;
040 import javax.management.ObjectName;
041
042 import org.apache.logging.log4j.core.LoggerContext;
043 import org.apache.logging.log4j.core.config.Configuration;
044 import org.apache.logging.log4j.core.config.ConfigurationFactory;
045 import org.apache.logging.log4j.core.config.ConfigurationSource;
046 import org.apache.logging.log4j.core.util.Assert;
047 import org.apache.logging.log4j.core.util.Closer;
048 import org.apache.logging.log4j.core.util.Constants;
049 import org.apache.logging.log4j.status.StatusLogger;
050 import org.apache.logging.log4j.util.Strings;
051
052 /**
053 * Implementation of the {@code LoggerContextAdminMBean} interface.
054 */
055 public class LoggerContextAdmin extends NotificationBroadcasterSupport implements LoggerContextAdminMBean,
056 PropertyChangeListener {
057 private static final int PAGE = 4 * 1024;
058 private static final int TEXT_BUFFER = 64 * 1024;
059 private static final int BUFFER_SIZE = 2048;
060 private static final StatusLogger LOGGER = StatusLogger.getLogger();
061
062 private final AtomicLong sequenceNo = new AtomicLong();
063 private final ObjectName objectName;
064 private final LoggerContext loggerContext;
065
066 /**
067 * Constructs a new {@code LoggerContextAdmin} with the {@code Executor} to be used for sending {@code Notification}
068 * s asynchronously to listeners.
069 *
070 * @param executor used to send notifications asynchronously
071 * @param loggerContext the instrumented object
072 */
073 public LoggerContextAdmin(final LoggerContext loggerContext, final Executor executor) {
074 super(executor, createNotificationInfo());
075 this.loggerContext = Assert.requireNonNull(loggerContext, "loggerContext");
076 try {
077 final String ctxName = Server.escape(loggerContext.getName());
078 final String name = String.format(PATTERN, ctxName);
079 objectName = new ObjectName(name);
080 } catch (final Exception e) {
081 throw new IllegalStateException(e);
082 }
083 loggerContext.addPropertyChangeListener(this);
084 }
085
086 private static MBeanNotificationInfo createNotificationInfo() {
087 final String[] notifTypes = new String[] { NOTIF_TYPE_RECONFIGURED };
088 final String name = Notification.class.getName();
089 final String description = "Configuration reconfigured";
090 return new MBeanNotificationInfo(notifTypes, name, description);
091 }
092
093 @Override
094 public String getStatus() {
095 return loggerContext.getState().toString();
096 }
097
098 @Override
099 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 }