001/* 002 * #%L 003 * Netarchivesuite - monitor 004 * %% 005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library, 006 * the National Library of France and the Austrian National Library. 007 * %% 008 * This program is free software: you can redistribute it and/or modify 009 * it under the terms of the GNU Lesser General Public License as 010 * published by the Free Software Foundation, either version 2.1 of the 011 * License, or (at your option) any later version. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Lesser Public License for more details. 017 * 018 * You should have received a copy of the GNU General Lesser Public 019 * License along with this program. If not, see 020 * <http://www.gnu.org/licenses/lgpl-2.1.html>. 021 * #L% 022 */ 023 024package dk.netarkivet.monitor.webinterface; 025 026import java.util.ArrayList; 027import java.util.Collections; 028import java.util.Date; 029import java.util.List; 030import java.util.Locale; 031import java.util.Set; 032 033import javax.management.InstanceNotFoundException; 034import javax.management.MBeanRegistrationException; 035import javax.management.MBeanServer; 036import javax.management.MBeanServerFactory; 037import javax.management.MalformedObjectNameException; 038import javax.management.ObjectName; 039import javax.management.RuntimeMBeanException; 040 041import org.slf4j.Logger; 042import org.slf4j.LoggerFactory; 043 044import dk.netarkivet.common.exceptions.ArgumentNotValid; 045import dk.netarkivet.common.utils.ExceptionUtils; 046import dk.netarkivet.common.utils.I18n; 047import dk.netarkivet.common.webinterface.HTMLUtils; 048import dk.netarkivet.monitor.jmx.HostForwarding; 049import dk.netarkivet.monitor.logging.SingleLogRecord; 050 051/** 052 * Implementation of StatusEntry, that receives its data from the MBeanServer (JMX). 053 */ 054public class JMXStatusEntry implements StatusEntry { 055 056 private static final Logger log = LoggerFactory.getLogger(JMXStatusEntry.class); 057 058 059 /** The ObjectName assigned to the MBean for this JMXStatusEntry. */ 060 private ObjectName mBeanName; 061 /** JMX Query to retrieve the logmessage associated with this Entry. */ 062 private static final String LOGGING_QUERY = "dk.netarkivet.common.logging:*"; 063 /** JMX Attribute containing the logmessage itself. */ 064 private static final String JMXLogMessageAttribute = "RecordString"; 065 /** MBeanserver used by this class. */ 066 private static final MBeanServer mBeanServer = MBeanServerFactory.createMBeanServer(); 067 068 /** Internationalisation object. */ 069 private static final I18n I18N = new I18n(dk.netarkivet.monitor.Constants.TRANSLATIONS_BUNDLE); 070 071 /** 072 * Constructor for the JMXStatusEntry. 073 * 074 * @param mBeanName The ObjectName to be assigned to the MBean representing this JMXStatusEntry. 075 */ 076 public JMXStatusEntry(ObjectName mBeanName) { 077 ArgumentNotValid.checkNotNull(mBeanName, "ObjectName mBeanName"); 078 this.mBeanName = mBeanName; 079 } 080 081 /** 082 * @return the location designated by the key {@link JMXSummaryUtils#JMXPhysLocationProperty} 083 */ 084 public String getPhysicalLocation() { 085 return mBeanName.getKeyProperty(JMXSummaryUtils.JMXPhysLocationProperty); 086 } 087 088 /** 089 * @return the hostname designated by the key {@link JMXSummaryUtils#JMXMachineNameProperty} 090 */ 091 public String getMachineName() { 092 return mBeanName.getKeyProperty(JMXSummaryUtils.JMXMachineNameProperty); 093 } 094 095 /** 096 * @return the http-port designated by the key {@link JMXSummaryUtils#JMXHttpportProperty} 097 */ 098 public String getHTTPPort() { 099 return mBeanName.getKeyProperty(JMXSummaryUtils.JMXHttpportProperty); 100 } 101 102 /** 103 * @return the application name designated by the key {@link JMXSummaryUtils#JMXApplicationNameProperty} 104 */ 105 public String getApplicationName() { 106 return mBeanName.getKeyProperty(JMXSummaryUtils.JMXApplicationNameProperty); 107 } 108 109 /** 110 * @return the application inst id designated by the key {@link JMXSummaryUtils#JMXApplicationInstIdProperty} 111 */ 112 public String getApplicationInstanceID() { 113 return mBeanName.getKeyProperty(JMXSummaryUtils.JMXApplicationInstIdProperty); 114 } 115 116 /** 117 * @return the harvest priority designated by the key {@link JMXSummaryUtils#JMXHarvestChannelProperty} 118 */ 119 public String getHarvestPriority() { 120 return mBeanName.getKeyProperty(JMXSummaryUtils.JMXHarvestChannelProperty); 121 } 122 123 /** 124 * @return the replica id designated by the key {@link JMXSummaryUtils#JMXArchiveReplicaNameProperty} 125 */ 126 public String getArchiveReplicaName() { 127 return mBeanName.getKeyProperty(JMXSummaryUtils.JMXArchiveReplicaNameProperty); 128 } 129 130 /** 131 * @return the index designated by the key {@link JMXSummaryUtils#JMXIndexProperty} 132 */ 133 public String getIndex() { 134 return mBeanName.getKeyProperty(JMXSummaryUtils.JMXIndexProperty); 135 } 136 137 /** 138 * Gets the log message from this status entry. This implementation actually talks to an MBeanServer to get the log 139 * message. Will return an explanation if remote host does not respond, throws exception or returns null. 140 * 141 * @param l the current Locale 142 * @return A log message. 143 * @throws ArgumentNotValid if the current Locale is null 144 */ 145 public String getLogMessage(Locale l) { 146 ArgumentNotValid.checkNotNull(l, "l"); 147 // Make sure mbeans are forwarded 148 HostForwarding.getInstance(SingleLogRecord.class, mBeanServer, LOGGING_QUERY); 149 try { 150 String logMessage = (String) mBeanServer.getAttribute(mBeanName, JMXLogMessageAttribute); 151 if (logMessage == null) { 152 return HTMLUtils.escapeHtmlValues(getLogDate() 153 + I18N.getString(l, "errormsg;remote.host.returned.null.log.record")); 154 } else { 155 return logMessage; 156 } 157 } catch (RuntimeMBeanException e) { 158 return HTMLUtils.escapeHtmlValues(getLogDate() 159 + I18N.getString(l, "errormsg;jmx.error.while.getting.log.record") + "\n" 160 + I18N.getString(l, "errormsg;probably.host.is.not.responding") + "\n" 161 + ExceptionUtils.getStackTrace(e)); 162 } catch (Exception e) { 163 return HTMLUtils.escapeHtmlValues(getLogDate() 164 + I18N.getString(l, "errormsg;remote.jmx.bean.generated.exception") + "\n" 165 + ExceptionUtils.getStackTrace(e)); 166 } 167 } 168 169 private String getLogDate() { 170 return "[" + new Date() + "] "; 171 } 172 173 /** 174 * Compares two entries according to first their location, then their machine name, then their ports, and then their 175 * application name, and then their index. 176 * 177 * @param o The object to compare with 178 * @return A negative number if this entry comes first, a positive if it comes second and 0 if they are equal. 179 */ 180 public int compareTo(StatusEntry o) { 181 int c; 182 183 if (getPhysicalLocation() != null && o.getPhysicalLocation() != null) { 184 c = getPhysicalLocation().compareTo(o.getPhysicalLocation()); 185 if (c != 0) { 186 return c; 187 } 188 } else if (getPhysicalLocation() == null) { 189 return -1; 190 } else { 191 return 1; 192 } 193 194 if (getMachineName() != null && o.getMachineName() != null) { 195 c = getMachineName().compareTo(o.getMachineName()); 196 if (c != 0) { 197 return c; 198 } 199 } else if (getMachineName() == null) { 200 return -1; 201 } else { 202 return 1; 203 } 204 205 if (getHTTPPort() != null && o.getHTTPPort() != null) { 206 c = getHTTPPort().compareTo(o.getHTTPPort()); 207 if (c != 0) { 208 return c; 209 } 210 } else if (getHTTPPort() == null) { 211 return -1; 212 } else { 213 return 1; 214 } 215 216 if (getApplicationName() != null && o.getApplicationName() != null) { 217 c = getApplicationName().compareTo(o.getApplicationName()); 218 if (c != 0) { 219 return c; 220 } 221 } else if (getApplicationName() == null) { 222 return -1; 223 } else { 224 return 1; 225 } 226 227 if (getApplicationInstanceID() != null && o.getApplicationInstanceID() != null) { 228 c = getApplicationInstanceID().compareTo(o.getApplicationInstanceID()); 229 if (c != 0) { 230 return c; 231 } 232 } else if (getApplicationInstanceID() == null) { 233 return -1; 234 } else { 235 return 1; 236 } 237 238 if (getHarvestPriority() != null && o.getHarvestPriority() != null) { 239 c = getHarvestPriority().compareTo(o.getHarvestPriority()); 240 if (c != 0) { 241 return c; 242 } 243 } else if (getHarvestPriority() == null) { 244 return -1; 245 } else { 246 return 1; 247 } 248 249 if (getArchiveReplicaName() != null && o.getArchiveReplicaName() != null) { 250 c = getArchiveReplicaName().compareTo(o.getArchiveReplicaName()); 251 if (c != 0) { 252 return c; 253 } 254 } else if (getArchiveReplicaName() == null) { 255 return -1; 256 } else { 257 return 1; 258 } 259 260 Integer i1; 261 Integer i2; 262 try { 263 i1 = Integer.valueOf(getIndex()); 264 } catch (NumberFormatException e) { 265 i1 = null; 266 } 267 try { 268 i2 = Integer.valueOf(o.getIndex()); 269 } catch (NumberFormatException e) { 270 i2 = null; 271 } 272 273 if (i1 != null && i2 != null) { 274 c = i1.compareTo(i2); 275 if (c != 0) { 276 return c; 277 } 278 } else if (i1 == null) { 279 return -1; 280 } else { 281 return 1; 282 } 283 284 return 0; 285 } 286 287 /** 288 * Query the JMX system for system status mbeans. 289 * 290 * @param query A JMX request, e.g. dk.netarkivet.logging:location=EAST,httpport=8080,* 291 * @return A list of status entries for the mbeans that match the query. 292 * @throws MalformedObjectNameException If the query has wrong format. 293 */ 294 public static List<StatusEntry> queryJMX(String query) throws MalformedObjectNameException { 295 ArgumentNotValid.checkNotNull(query, "query"); 296 297 List<StatusEntry> entries = new ArrayList<StatusEntry>(); 298 299 // Make sure mbeans are forwarded 300 HostForwarding.getInstance(SingleLogRecord.class, mBeanServer, LOGGING_QUERY); 301 // The "null" in this case is used to indicate no further filters on the 302 // query. 303 log.debug("Querying mbean server {} with {}.", mBeanServer.toString(), LOGGING_QUERY); 304 Set<ObjectName> resultSet = mBeanServer.queryNames(new ObjectName(query), null); 305 for (ObjectName objectName : resultSet) { 306 entries.add(new JMXStatusEntry(objectName)); 307 } 308 Collections.sort(entries); 309 log.debug("Query returned {} results.", entries.size()); 310 return entries; 311 } 312 313 /** 314 * Unregister an JMX MBean instance. 315 * 316 * @param query A JMX request, for picking the beans to unregister. 317 * @throws MalformedObjectNameException if query is malformed. 318 * @throws InstanceNotFoundException if the instanced unregistered doesn't exists. 319 * @throws MBeanRegistrationException if unregeterBean is thrown. 320 */ 321 public static void unregisterJMXInstance(String query) throws MalformedObjectNameException, 322 InstanceNotFoundException, MBeanRegistrationException { 323 ArgumentNotValid.checkNotNull(query, "query"); 324 Set<ObjectName> namesMatchingQuery = mBeanServer.queryNames(new ObjectName(query), null); 325 for (ObjectName name : namesMatchingQuery) { 326 mBeanServer.unregisterMBean(name); 327 } 328 } 329}