View Javadoc

1   package net.sf.snmpadaptor4j.core;
2   
3   import java.net.URL;
4   import java.util.HashMap;
5   import java.util.List;
6   import java.util.Map;
7   import java.util.Set;
8   import javax.management.MBeanAttributeInfo;
9   import javax.management.MBeanServer;
10  import javax.management.MBeanServerDelegate;
11  import javax.management.MBeanServerNotification;
12  import javax.management.Notification;
13  import javax.management.NotificationListener;
14  import javax.management.ObjectName;
15  import javax.management.relation.MBeanServerNotificationFilter;
16  import net.sf.snmpadaptor4j.SnmpAppContext;
17  import net.sf.snmpadaptor4j.core.mapping.MBeanAttributeMapping;
18  import net.sf.snmpadaptor4j.core.mapping.SnmpTrapMapping;
19  import net.sf.snmpadaptor4j.core.mapping.XmlMappingParser;
20  import org.apache.log4j.Logger;
21  
22  /**
23   * Object designed to respond to each registration or deregistration of MBeans.
24   * @author <a href="http://fr.linkedin.com/in/jpminetti/">Jean-Philippe MINETTI</a>
25   */
26  public class JmxListener
27  		implements NotificationListener {
28  
29  	/**
30  	 * Logger.
31  	 */
32  	protected final Logger logger = Logger.getLogger(JmxListener.class);
33  
34  	/**
35  	 * <b>M</b>anagement <b>I</b>nformation <b>B</b>ase (MIB) for access to JMX attributes.
36  	 */
37  	private final JmxSnmpMib jmxSnmpMib;
38  
39  	/**
40  	 * Manager of JMX notifications.
41  	 */
42  	private final JmxNotificationManager jmxNotificationManager;
43  
44  	/**
45  	 * Context of main application.
46  	 */
47  	private final SnmpAppContext mainAppContext;
48  
49  	/**
50  	 * Application context map.
51  	 */
52  	private final Map<ClassLoader, SnmpAppContext> appContextMap;
53  
54  	/**
55  	 * <code>TRUE</code> for handle only MBeans created by the same {@link ClassLoader} that the SNMP adapter. <code>FALSE</code> for handle all MBeans of the JVM.
56  	 */
57  	private final boolean classLoaderScope;
58  
59  	/**
60  	 * JMX agent.
61  	 */
62  	private MBeanServer jmxServer;
63  
64  	/**
65  	 * Constructor.
66  	 * @param jmxSnmpMib <b>M</b>anagement <b>I</b>nformation <b>B</b>ase (MIB) for access to JMX attributes (must not be <code>NULL</code>).
67  	 * @param jmxNotificationManager Manager of JMX notifications (must not be <code>NULL</code>).
68  	 * @param mainAppContext Context of main application (must not be <code>NULL</code>).
69  	 * @param appContextMap Application context map (must not be <code>NULL</code>).
70  	 * @param classLoaderScope <code>TRUE</code> for handle only MBeans created by the same {@link ClassLoader} that the SNMP adapter. <code>FALSE</code> for handle
71  	 *            all MBeans of the JVM.
72  	 */
73  	public JmxListener (final JmxSnmpMib jmxSnmpMib, final JmxNotificationManager jmxNotificationManager, final SnmpAppContext mainAppContext,
74  			final Map<ClassLoader, SnmpAppContext> appContextMap, final boolean classLoaderScope) {
75  		this(jmxSnmpMib, jmxNotificationManager, mainAppContext, appContextMap, classLoaderScope, null);
76  	}
77  
78  	/**
79  	 * Constructor (used for tests).
80  	 * @param jmxSnmpMib <b>M</b>anagement <b>I</b>nformation <b>B</b>ase (MIB) for access to JMX attributes (must not be <code>NULL</code>).
81  	 * @param jmxNotificationManager Manager of JMX notifications.
82  	 * @param mainAppContext Context of main application (must not be <code>NULL</code>).
83  	 * @param appContextMap Application context map (must not be <code>NULL</code>).
84  	 * @param classLoaderScope <code>TRUE</code> for handle only MBeans created by the same {@link ClassLoader} that the SNMP adapter. <code>FALSE</code> for handle
85  	 *            all MBeans of the JVM.
86  	 * @param jmxServer JMX agent.
87  	 */
88  	protected JmxListener (final JmxSnmpMib jmxSnmpMib, final JmxNotificationManager jmxNotificationManager, final SnmpAppContext mainAppContext,
89  			final Map<ClassLoader, SnmpAppContext> appContextMap, final boolean classLoaderScope, final MBeanServer jmxServer) {
90  		super();
91  		this.mainAppContext = mainAppContext;
92  		this.appContextMap = appContextMap;
93  		this.classLoaderScope = classLoaderScope;
94  		this.jmxSnmpMib = jmxSnmpMib;
95  		this.jmxNotificationManager = jmxNotificationManager;
96  		this.jmxServer = jmxServer;
97  	}
98  
99  	/**
100 	 * Returns the JMX agent.
101 	 * @return JMX agent.
102 	 */
103 	protected final MBeanServer getJmxServer () {
104 		return this.jmxServer;
105 	}
106 
107 	/**
108 	 * Opens the connection with the JMX agent.
109 	 * @param server JMX agent.
110 	 * @throws Exception Exception if an error occurred.
111 	 */
112 	public synchronized void open (final MBeanServer server) throws Exception {
113 		this.logger.trace("JMX opening...");
114 		if (this.jmxServer != null) {
115 			throw new Exception("Already connected to a JMX agent");
116 		}
117 
118 		// JMX notification registering
119 		final MBeanServerNotificationFilter filter = new MBeanServerNotificationFilter();
120 		filter.enableAllObjectNames();
121 		server.addNotificationListener(MBeanServerDelegate.DELEGATE_NAME, this, filter, server);
122 
123 		// Existing MBean parsing
124 		final Set<ObjectName> mBeanNameList = server.queryNames(null, null);
125 		for (final ObjectName mBeanName : mBeanNameList) {
126 			register(server, mBeanName);
127 		}
128 
129 		this.jmxServer = server;
130 		this.logger.trace("JMX opened");
131 	}
132 
133 	/**
134 	 * Closes the connection with the JMX agent.
135 	 * @throws Exception Exception if an error occurred.
136 	 */
137 	public synchronized void close () throws Exception {
138 		this.logger.trace("JMX closing...");
139 		if (this.jmxServer == null) {
140 			throw new Exception("Not connected to a JMX agent");
141 		}
142 
143 		// JMX notification deregistering
144 		this.jmxServer.removeNotificationListener(MBeanServerDelegate.DELEGATE_NAME, this);
145 
146 		// Cleaning
147 		this.jmxNotificationManager.unregisterAll(this.jmxServer);
148 		this.jmxSnmpMib.unregisterAllAttributes();
149 
150 		this.jmxServer = null;
151 		this.logger.trace("JMX closed");
152 	}
153 
154 	/*
155 	 * {@inheritDoc}
156 	 * @see javax.management.NotificationListener#handleNotification(javax.management.Notification, java.lang.Object)
157 	 */
158 	public final synchronized void handleNotification (final Notification notification, final Object handback) {
159 		if ((handback instanceof MBeanServer) && (this.jmxServer == (MBeanServer) handback)) {
160 			if (notification instanceof MBeanServerNotification) {
161 				final MBeanServerNotification serverNotification = (MBeanServerNotification) notification;
162 				if (MBeanServerNotification.REGISTRATION_NOTIFICATION.equals(serverNotification.getType())) {
163 					try {
164 						register((MBeanServer) handback, serverNotification.getMBeanName());
165 					}
166 					catch (final Throwable e) {
167 						this.logger.error(serverNotification.getMBeanName() + ": MBean not loaded in SNMP adapter", e);
168 					}
169 				}
170 				else if (MBeanServerNotification.UNREGISTRATION_NOTIFICATION.equals(serverNotification.getType())) {
171 					try {
172 						unregister((MBeanServer) handback, serverNotification.getMBeanName());
173 					}
174 					catch (final Throwable e) {
175 						this.logger.error(serverNotification.getMBeanName() + ": MBean not unloaded in SNMP adapter", e);
176 					}
177 				}
178 			}
179 		}
180 	}
181 
182 	/**
183 	 * Registers a MBean to SNMP <b>M</b>anagement <b>I</b>nformation <b>B</b>ase (MIB).
184 	 * @param server JMX agent.
185 	 * @param mBeanName MBean name.
186 	 * @throws Exception Exception if an error occurred.
187 	 */
188 	private void register (final MBeanServer server, final ObjectName mBeanName) throws Exception {
189 		if (server.isRegistered(mBeanName)) {
190 			final ClassLoader classLoader = server.getClassLoaderFor(mBeanName);
191 			if (classLoader != null) {
192 				if (!this.classLoaderScope || this.getClass().getClassLoader().equals(classLoader)) {
193 					final Class<?> mBeanClass = classLoader.loadClass(server.getObjectInstance(mBeanName).getClassName());
194 					final URL url = mBeanClass.getResource(mBeanClass.getSimpleName() + ".snmp.xml");
195 					if (url != null) {
196 						if (this.logger.isDebugEnabled()) {
197 							this.logger.debug("SNMP mapping found at " + url);
198 						}
199 						final XmlMappingParser parser = XmlMappingParser.newInstance(url);
200 
201 						// Base OID finding
202 						final SnmpAppContext ctx = findAppContext(classLoader);
203 						String baseOid = ctx.getMBeanOidMap().get(mBeanName);
204 						if (baseOid == null) {
205 							baseOid = parser.findBaseOid(mBeanName, ctx.getRootOidMap(), ctx.getDefaultRootOid(), this.mainAppContext.getDefaultRootOid());
206 						}
207 						if (baseOid == null) {
208 							if (this.logger.isDebugEnabled()) {
209 								this.logger.debug("None OID found for [" + mBeanName + "]");
210 							}
211 						}
212 						else {
213 
214 							// Mapping loading
215 							final Map<String, MBeanAttributeInfo> mBeanAttributeInfoMap = new HashMap<String, MBeanAttributeInfo>();
216 							for (final MBeanAttributeInfo mBeanAttributeInfo : server.getMBeanInfo(mBeanName).getAttributes()) {
217 								mBeanAttributeInfoMap.put(mBeanAttributeInfo.getName(), mBeanAttributeInfo);
218 							}
219 							final List<MBeanAttributeMapping> mBeanAttributeMappingList = parser.newMBeanAttributeMappingList(mBeanAttributeInfoMap, classLoader,
220 									baseOid);
221 							this.jmxSnmpMib.registerAttributes(server, mBeanName, mBeanAttributeMappingList);
222 							final Map<String, SnmpTrapMapping> trapMappingMap = parser.newSnmpTrapMappingMap(baseOid);
223 							this.jmxNotificationManager.register(server, mBeanName, trapMappingMap);
224 
225 						}
226 					}
227 					else if (this.logger.isDebugEnabled()) {
228 						this.logger.debug("SNMP mapping missing for [" + mBeanName + "]");
229 					}
230 				}
231 				else if (this.logger.isTraceEnabled()) {
232 					this.logger.trace("SNMP mapping ignored because classLoaderScope = TRUE for [" + mBeanName + "]");
233 				}
234 			}
235 			else if (this.logger.isTraceEnabled()) {
236 				this.logger.trace("SNMP mapping ignored because the MBean is not accessible for [" + mBeanName + "]");
237 			}
238 		}
239 	}
240 
241 	/**
242 	 * Finds the application context by its class loader.
243 	 * @param classLoader Class loader of application.
244 	 * @return Application context found.
245 	 */
246 	private SnmpAppContext findAppContext (final ClassLoader classLoader) {
247 		SnmpAppContext ctx = null;
248 		ClassLoader cl = classLoader;
249 		while ((ctx == null) && (cl != null)) {
250 			ctx = this.appContextMap.get(cl);
251 			cl = cl.getParent();
252 		}
253 		if (ctx == null) {
254 			ctx = this.mainAppContext;
255 		}
256 		return ctx;
257 	}
258 
259 	/**
260 	 * Unregisters a MBean of SNMP <b>M</b>anagement <b>I</b>nformation <b>B</b>ase (MIB).
261 	 * @param server JMX agent.
262 	 * @param mBeanName MBean name.
263 	 */
264 	private void unregister (final MBeanServer server, final ObjectName mBeanName) {
265 		this.jmxSnmpMib.unregisterAttributes(server, mBeanName);
266 		this.jmxNotificationManager.unregister(server, mBeanName);
267 	}
268 
269 	/*
270 	 * {@inheritDoc}
271 	 * @see java.lang.Object#toString()
272 	 */
273 	@Override
274 	public final String toString () {
275 		return "JmxListener[jmxSnmpMib=" + this.jmxSnmpMib + "; jmxNotificationManager=" + this.jmxNotificationManager + "; mainAppContext=" + this.mainAppContext
276 				+ "; appContextMap=" + this.appContextMap + "; classLoaderScope=" + this.classLoaderScope + "]";
277 	}
278 
279 }