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.jmx.gui;
018    
019    import java.io.IOException;
020    import java.util.ArrayList;
021    import java.util.List;
022    import java.util.Set;
023    
024    import javax.management.JMException;
025    import javax.management.JMX;
026    import javax.management.MBeanServerConnection;
027    import javax.management.MalformedObjectNameException;
028    import javax.management.ObjectName;
029    import javax.management.remote.JMXConnector;
030    
031    import org.apache.logging.log4j.core.jmx.LoggerContextAdminMBean;
032    import org.apache.logging.log4j.core.jmx.Server;
033    import org.apache.logging.log4j.core.jmx.StatusLoggerAdminMBean;
034    import org.apache.logging.log4j.core.util.Assert;
035    import org.apache.logging.log4j.core.util.Closer;
036    
037    /**
038     * This class allows client-side code to perform operations on remote
039     * (server-side) MBeans via proxies.
040     */
041    public class Client {
042        private JMXConnector connector;
043        private final MBeanServerConnection connection;
044    
045        /**
046         * Constructs a new {@code Client} object and creates proxies for all known
047         * remote MBeans.
048         *
049         * @param connector used to create the MBean server connection through which
050         *            to communicate with the remote mbeans
051         * @throws MalformedObjectNameException if a problem occurred identifying
052         *             one of the remote mbeans
053         * @throws IOException if the connection failed
054         */
055        public Client(final JMXConnector connector) throws MalformedObjectNameException, IOException {
056            this.connector = Assert.requireNonNull(connector, "JMXConnector");
057            this.connector.connect();
058            this.connection = connector.getMBeanServerConnection();
059            init();
060        }
061    
062        /**
063         * Constructs a new {@code Client} object and creates proxies for all known
064         * remote MBeans.
065         *
066         * @param mBeanServerConnection the MBean server connection through which to
067         *            communicate with the remote mbeans
068         * @throws MalformedObjectNameException if a problem occurred identifying
069         *             one of the remote mbeans
070         * @throws IOException if the connection failed
071         */
072        public Client(final MBeanServerConnection mBeanServerConnection) throws MalformedObjectNameException, IOException {
073            this.connection = mBeanServerConnection;
074            init();
075        }
076    
077        private void init() throws MalformedObjectNameException, IOException {
078        }
079    
080        private Set<ObjectName> find(final String pattern) throws JMException, IOException {
081            final ObjectName search = new ObjectName(String.format(pattern, "*"));
082            final Set<ObjectName> result = connection.queryNames(search, null);
083            return result;
084        }
085    
086        /**
087         * Returns a list of proxies that allow operations to be performed on the
088         * remote {@code LoggerContextAdminMBean}s.
089         *
090         * @return a list of proxies to the remote {@code LoggerContextAdminMBean}s
091         * @throws IOException If an I/O error occurred
092         * @throws JMException If a management error occurred
093         */
094        public List<LoggerContextAdminMBean> getLoggerContextAdmins() throws JMException, IOException {
095            final List<LoggerContextAdminMBean> result = new ArrayList<LoggerContextAdminMBean>();
096            final Set<ObjectName> contextNames = find(LoggerContextAdminMBean.PATTERN);
097            for (final ObjectName contextName : contextNames) {
098                result.add(getLoggerContextAdmin(contextName));
099            }
100            return result;
101        }
102    
103        public LoggerContextAdminMBean getLoggerContextAdmin(final ObjectName name) {
104            final LoggerContextAdminMBean ctx = JMX.newMBeanProxy(connection, //
105                    name, //
106                    LoggerContextAdminMBean.class, false);
107            return ctx;
108        }
109    
110        /**
111         * Closes the client connection to its server. Any ongoing or new requests
112         * to the MBeanServerConnection will fail.
113         */
114        public void close() {
115            Closer.closeSilently(connector);
116        }
117    
118        /**
119         * Returns the MBean server connection through which to communicate with the
120         * remote mbeans.
121         *
122         * @return the MBean server connection
123         */
124        public MBeanServerConnection getConnection() {
125            return connection;
126        }
127    
128        /**
129         * Returns the {@code StatusLoggerAdminMBean} associated with the specified
130         * context name, or {@code null}.
131         *
132         * @param contextName search key
133         * @return StatusLoggerAdminMBean or null
134         * @throws MalformedObjectNameException If an object name is malformed
135         * @throws IOException If an I/O error occurred
136         */
137        public StatusLoggerAdminMBean getStatusLoggerAdmin(final String contextName)
138                throws MalformedObjectNameException, IOException {
139            final String pattern = StatusLoggerAdminMBean.PATTERN;
140            final String mbean = String.format(pattern, Server.escape(contextName));
141            final ObjectName search = new ObjectName(mbean);
142            final Set<ObjectName> result = connection.queryNames(search, null);
143            if (result.size() == 0) {
144                return null;
145            }
146            if (result.size() > 1) {
147                System.err.println("WARN: multiple status loggers found for " + contextName + ": " + result);
148            }
149            final StatusLoggerAdminMBean proxy = JMX.newMBeanProxy(connection, //
150                    result.iterator().next(), //
151                    StatusLoggerAdminMBean.class, true); // notificationBroadcaster
152            return proxy;
153        }
154    
155        /**
156         * Returns {@code true} if the specified {@code ObjectName} is for a
157         * {@code LoggerContextAdminMBean}, {@code false} otherwise.
158         *
159         * @param mbeanName the {@code ObjectName} to check.
160         * @return {@code true} if the specified {@code ObjectName} is for a
161         *         {@code LoggerContextAdminMBean}, {@code false} otherwise
162         */
163        public boolean isLoggerContext(final ObjectName mbeanName) {
164            return Server.DOMAIN.equals(mbeanName.getDomain()) //
165                    && mbeanName.getKeyPropertyList().containsKey("type") //
166                    && mbeanName.getKeyPropertyList().size() == 1;
167        }
168    
169        /**
170         * Returns the {@code ObjectName} of the {@code StatusLoggerAdminMBean}
171         * associated with the specified {@code LoggerContextAdminMBean}.
172         *
173         * @param loggerContextObjName the {@code ObjectName} of a
174         *            {@code LoggerContextAdminMBean}
175         * @return {@code ObjectName} of the {@code StatusLoggerAdminMBean}
176         */
177        public ObjectName getStatusLoggerObjectName(final ObjectName loggerContextObjName) {
178            if (!isLoggerContext(loggerContextObjName)) {
179                throw new IllegalArgumentException("Not a LoggerContext: " + loggerContextObjName);
180            }
181            final String cxtName = loggerContextObjName.getKeyProperty("type");
182            final String name = String.format(StatusLoggerAdminMBean.PATTERN, cxtName);
183            try {
184                return new ObjectName(name);
185            } catch (final MalformedObjectNameException ex) {
186                throw new IllegalStateException(name, ex);
187            }
188        }
189    }