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.Objects; 023import java.util.Set; 024 025import javax.management.JMException; 026import javax.management.JMX; 027import javax.management.MBeanServerConnection; 028import javax.management.MalformedObjectNameException; 029import javax.management.ObjectName; 030import javax.management.remote.JMXConnector; 031 032import org.apache.logging.log4j.core.jmx.LoggerContextAdminMBean; 033import org.apache.logging.log4j.core.jmx.Server; 034import org.apache.logging.log4j.core.jmx.StatusLoggerAdminMBean; 035import 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 */ 041public 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 = Objects.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<>(); 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.isEmpty()) { 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}