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.Charsets;
048    import org.apache.logging.log4j.core.util.Closer;
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(Charsets.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    }