    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    }