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.IOException; 024 import java.io.InputStream; 025 import java.io.InputStreamReader; 026 import java.io.PrintWriter; 027 import java.io.Reader; 028 import java.io.StringWriter; 029 import java.net.URI; 030 import java.net.URISyntaxException; 031 import java.util.Map; 032 import java.util.concurrent.Executor; 033 import java.util.concurrent.atomic.AtomicLong; 034 035 import javax.management.MBeanNotificationInfo; 036 import javax.management.Notification; 037 import javax.management.NotificationBroadcasterSupport; 038 import javax.management.ObjectName; 039 040 import org.apache.logging.log4j.core.LoggerContext; 041 import org.apache.logging.log4j.core.config.Configuration; 042 import org.apache.logging.log4j.core.config.ConfigurationFactory; 043 import org.apache.logging.log4j.core.config.ConfigurationFactory.ConfigurationSource; 044 import org.apache.logging.log4j.status.StatusLogger; 045 046 /** 047 * Implementation of the {@code LoggerContextAdminMBean} interface. 048 */ 049 public class LoggerContextAdmin extends NotificationBroadcasterSupport 050 implements LoggerContextAdminMBean, PropertyChangeListener { 051 private static final int PAGE = 4 * 1024; 052 private static final int TEXT_BUFFER = 64 * 1024; 053 private static final int BUFFER_SIZE = 2048; 054 private static final StatusLogger LOGGER = StatusLogger.getLogger(); 055 056 private final AtomicLong sequenceNo = new AtomicLong(); 057 private final ObjectName objectName; 058 private final LoggerContext loggerContext; 059 private String customConfigText; 060 061 /** 062 * Constructs a new {@code LoggerContextAdmin} with the {@code Executor} to 063 * be used for sending {@code Notification}s asynchronously to listeners. 064 * 065 * @param executor 066 */ 067 public LoggerContextAdmin(LoggerContext loggerContext, Executor executor) { 068 super(executor, createNotificationInfo()); 069 this.loggerContext = Assert.isNotNull(loggerContext, "loggerContext"); 070 try { 071 String ctxName = Server.escape(loggerContext.getName()); 072 String name = String.format(PATTERN, ctxName); 073 objectName = new ObjectName(name); 074 } catch (Exception e) { 075 throw new IllegalStateException(e); 076 } 077 loggerContext.addPropertyChangeListener(this); 078 } 079 080 private static MBeanNotificationInfo createNotificationInfo() { 081 String[] notifTypes = new String[] { NOTIF_TYPE_RECONFIGURED }; 082 String name = Notification.class.getName(); 083 String description = "Configuration reconfigured"; 084 return new MBeanNotificationInfo(notifTypes, name, description); 085 } 086 087 @Override 088 public String getStatus() { 089 return loggerContext.getStatus().toString(); 090 } 091 092 @Override 093 public String getName() { 094 return loggerContext.getName(); 095 } 096 097 private Configuration getConfig() { 098 return loggerContext.getConfiguration(); 099 } 100 101 @Override 102 public String getConfigLocationURI() { 103 if (loggerContext.getConfigLocation() != null) { 104 return String.valueOf(loggerContext.getConfigLocation()); 105 } 106 if (getConfigName() != null) { 107 return String.valueOf(new File(getConfigName()).toURI()); 108 } 109 return ""; 110 } 111 112 @Override 113 public void setConfigLocationURI(String configLocation) 114 throws URISyntaxException, IOException { 115 LOGGER.debug("---------"); 116 LOGGER.debug("Remote request to reconfigure using location " 117 + configLocation); 118 URI uri = new URI(configLocation); 119 120 // validate the location first: invalid location will result in 121 // default configuration being configured, try to avoid that... 122 uri.toURL().openStream().close(); 123 124 loggerContext.setConfigLocation(uri); 125 LOGGER.debug("Completed remote request to reconfigure."); 126 } 127 128 @Override 129 public void propertyChange(PropertyChangeEvent evt) { 130 if (!LoggerContext.PROPERTY_CONFIG.equals(evt.getPropertyName())) { 131 return; 132 } 133 // erase custom text if new configuration was read from a location 134 if (loggerContext.getConfiguration().getName() != null) { 135 customConfigText = null; 136 } 137 Notification notif = new Notification(NOTIF_TYPE_RECONFIGURED, 138 getObjectName(), nextSeqNo(), now(), null); 139 sendNotification(notif); 140 } 141 142 @Override 143 public String getConfigText() throws IOException { 144 if (customConfigText != null) { 145 return customConfigText; 146 } 147 try { 148 return readContents(new URI(getConfigLocationURI())); 149 } catch (Exception ex) { 150 StringWriter sw = new StringWriter(BUFFER_SIZE); 151 ex.printStackTrace(new PrintWriter(sw)); 152 return sw.toString(); 153 } 154 } 155 156 @Override 157 public void setConfigText(String configText, String charsetName) { 158 String old = customConfigText; 159 customConfigText = Assert.isNotNull(configText, "configText"); 160 LOGGER.debug("---------"); 161 LOGGER.debug("Remote request to reconfigure from config text."); 162 163 try { 164 InputStream in = new ByteArrayInputStream( 165 configText.getBytes(charsetName)); 166 ConfigurationSource source = new ConfigurationSource(in); 167 Configuration updated = ConfigurationFactory.getInstance() 168 .getConfiguration(source); 169 loggerContext.setConfiguration(updated); 170 LOGGER.debug("Completed remote request to reconfigure from config text."); 171 } catch (Exception ex) { 172 customConfigText = old; 173 String msg = "Could not reconfigure from config text"; 174 LOGGER.error(msg, ex); 175 throw new IllegalArgumentException(msg, ex); 176 } 177 } 178 179 private String readContents(URI uri) throws IOException { 180 InputStream in = null; 181 try { 182 in = uri.toURL().openStream(); 183 Reader reader = new InputStreamReader(in); 184 StringBuilder result = new StringBuilder(TEXT_BUFFER); 185 char[] buff = new char[PAGE]; 186 int count = -1; 187 while ((count = reader.read(buff)) >= 0) { 188 result.append(buff, 0, count); 189 } 190 return result.toString(); 191 } finally { 192 try { 193 in.close(); 194 } catch (Exception ignored) { 195 } 196 } 197 } 198 199 @Override 200 public String getConfigName() { 201 return getConfig().getName(); 202 } 203 204 @Override 205 public String getConfigClassName() { 206 return getConfig().getClass().getName(); 207 } 208 209 @Override 210 public String getConfigFilter() { 211 return String.valueOf(getConfig().getFilter()); 212 } 213 214 @Override 215 public String getConfigMonitorClassName() { 216 return getConfig().getConfigurationMonitor().getClass().getName(); 217 } 218 219 @Override 220 public Map<String, String> getConfigProperties() { 221 return getConfig().getProperties(); 222 } 223 224 /** @see LoggerContextAdminMBean#PATTERN */ 225 public ObjectName getObjectName() { 226 return objectName; 227 } 228 229 private long nextSeqNo() { 230 return sequenceNo.getAndIncrement(); 231 } 232 233 private long now() { 234 return System.currentTimeMillis(); 235 } 236 }