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