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 */ 017package org.apache.logging.log4j.core.jmx; 018 019import java.beans.PropertyChangeEvent; 020import java.beans.PropertyChangeListener; 021import java.io.ByteArrayInputStream; 022import java.io.File; 023import java.io.IOException; 024import java.io.InputStream; 025import java.io.InputStreamReader; 026import java.io.PrintWriter; 027import java.io.Reader; 028import java.io.StringWriter; 029import java.net.URI; 030import java.net.URISyntaxException; 031import java.nio.charset.Charset; 032import java.util.Map; 033import java.util.concurrent.Executor; 034import java.util.concurrent.atomic.AtomicLong; 035 036import javax.management.MBeanNotificationInfo; 037import javax.management.Notification; 038import javax.management.NotificationBroadcasterSupport; 039import javax.management.ObjectName; 040 041import org.apache.logging.log4j.core.LoggerContext; 042import org.apache.logging.log4j.core.config.Configuration; 043import org.apache.logging.log4j.core.config.ConfigurationFactory; 044import org.apache.logging.log4j.core.config.ConfigurationFactory.ConfigurationSource; 045import org.apache.logging.log4j.core.helpers.Assert; 046import org.apache.logging.log4j.core.helpers.Charsets; 047import org.apache.logging.log4j.core.helpers.Closer; 048import org.apache.logging.log4j.core.helpers.FileUtils; 049import org.apache.logging.log4j.status.StatusLogger; 050 051/** 052 * Implementation of the {@code LoggerContextAdminMBean} interface. 053 */ 054public class LoggerContextAdmin extends NotificationBroadcasterSupport 055 implements LoggerContextAdminMBean, PropertyChangeListener { 056 private static final int PAGE = 4 * 1024; 057 private static final int TEXT_BUFFER = 64 * 1024; 058 private static final int BUFFER_SIZE = 2048; 059 private static final StatusLogger LOGGER = StatusLogger.getLogger(); 060 061 private final AtomicLong sequenceNo = new AtomicLong(); 062 private final ObjectName objectName; 063 private final LoggerContext loggerContext; 064 private String customConfigText; 065 066 /** 067 * Constructs a new {@code LoggerContextAdmin} with the {@code Executor} to 068 * be used for sending {@code Notification}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.isNotNull(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[] {// 088 NOTIF_TYPE_RECONFIGURED }; 089 final String name = Notification.class.getName(); 090 final String description = "Configuration reconfigured"; 091 return new MBeanNotificationInfo(notifTypes, name, description); 092 } 093 094 @Override 095 public String getStatus() { 096 return loggerContext.getStatus().toString(); 097 } 098 099 @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}