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