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    }