    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    }