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 */
017package org.apache.logging.log4j.jmx.gui;
018
019import java.io.IOException;
020import java.util.ArrayList;
021import java.util.List;
022import java.util.Set;
023
024import javax.management.JMException;
025import javax.management.JMX;
026import javax.management.MBeanServerConnection;
027import javax.management.MalformedObjectNameException;
028import javax.management.ObjectName;
029import javax.management.remote.JMXConnector;
030
031import org.apache.logging.log4j.core.helpers.Assert;
032import org.apache.logging.log4j.core.jmx.LoggerContextAdminMBean;
033import org.apache.logging.log4j.core.jmx.Server;
034import org.apache.logging.log4j.core.jmx.StatusLoggerAdminMBean;
035
036/**
037 * This class allows client-side code to perform operations on remote
038 * (server-side) MBeans via proxies.
039 */
040public 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.isNotNull(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(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        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(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(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(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(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 (MalformedObjectNameException ex) {
189            throw new IllegalStateException(name, ex);
190        }
191    }
192}