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 dk.netarkivet.common.exceptions.ArgumentNotValid; 042import dk.netarkivet.common.utils.ExceptionUtils; 043import dk.netarkivet.common.utils.I18n; 044import dk.netarkivet.common.webinterface.HTMLUtils; 045import dk.netarkivet.monitor.jmx.HostForwarding; 046import dk.netarkivet.monitor.logging.SingleLogRecord; 047 048/** 049 * Implementation of StatusEntry, that receives its data from the MBeanServer (JMX). 050 */ 051public class JMXStatusEntry implements StatusEntry { 052 /** The ObjectName assigned to the MBean for this JMXStatusEntry. */ 053 private ObjectName mBeanName; 054 /** JMX Query to retrieve the logmessage associated with this Entry. */ 055 private static final String LOGGING_QUERY = "dk.netarkivet.common.logging:*"; 056 /** JMX Attribute containing the logmessage itself. */ 057 private static final String JMXLogMessageAttribute = "RecordString"; 058 /** MBeanserver used by this class. */ 059 private static final MBeanServer mBeanServer = MBeanServerFactory.createMBeanServer(); 060 061 /** Internationalisation object. */ 062 private static final I18n I18N = new I18n(dk.netarkivet.monitor.Constants.TRANSLATIONS_BUNDLE); 063 064 /** 065 * Constructor for the JMXStatusEntry. 066 * 067 * @param mBeanName The ObjectName to be assigned to the MBean representing this JMXStatusEntry. 068 */ 069 public JMXStatusEntry(ObjectName mBeanName) { 070 ArgumentNotValid.checkNotNull(mBeanName, "ObjectName mBeanName"); 071 this.mBeanName = mBeanName; 072 } 073 074 /** 075 * @return the location designated by the key {@link JMXSummaryUtils#JMXPhysLocationProperty} 076 */ 077 public String getPhysicalLocation() { 078 return mBeanName.getKeyProperty(JMXSummaryUtils.JMXPhysLocationProperty); 079 } 080 081 /** 082 * @return the hostname designated by the key {@link JMXSummaryUtils#JMXMachineNameProperty} 083 */ 084 public String getMachineName() { 085 return mBeanName.getKeyProperty(JMXSummaryUtils.JMXMachineNameProperty); 086 } 087 088 /** 089 * @return the http-port designated by the key {@link JMXSummaryUtils#JMXHttpportProperty} 090 */ 091 public String getHTTPPort() { 092 return mBeanName.getKeyProperty(JMXSummaryUtils.JMXHttpportProperty); 093 } 094 095 /** 096 * @return the application name designated by the key {@link JMXSummaryUtils#JMXApplicationNameProperty} 097 */ 098 public String getApplicationName() { 099 return mBeanName.getKeyProperty(JMXSummaryUtils.JMXApplicationNameProperty); 100 } 101 102 /** 103 * @return the application inst id designated by the key {@link JMXSummaryUtils#JMXApplicationInstIdProperty} 104 */ 105 public String getApplicationInstanceID() { 106 return mBeanName.getKeyProperty(JMXSummaryUtils.JMXApplicationInstIdProperty); 107 } 108 109 /** 110 * @return the harvest priority designated by the key {@link JMXSummaryUtils#JMXHarvestChannelProperty} 111 */ 112 public String getHarvestPriority() { 113 return mBeanName.getKeyProperty(JMXSummaryUtils.JMXHarvestChannelProperty); 114 } 115 116 /** 117 * @return the replica id designated by the key {@link JMXSummaryUtils#JMXArchiveReplicaNameProperty} 118 */ 119 public String getArchiveReplicaName() { 120 return mBeanName.getKeyProperty(JMXSummaryUtils.JMXArchiveReplicaNameProperty); 121 } 122 123 /** 124 * @return the index designated by the key {@link JMXSummaryUtils#JMXIndexProperty} 125 */ 126 public String getIndex() { 127 return mBeanName.getKeyProperty(JMXSummaryUtils.JMXIndexProperty); 128 } 129 130 /** 131 * Gets the log message from this status entry. This implementation actually talks to an MBeanServer to get the log 132 * message. Will return an explanation if remote host does not respond, throws exception or returns null. 133 * 134 * @param l the current Locale 135 * @return A log message. 136 * @throws ArgumentNotValid if the current Locale is null 137 */ 138 public String getLogMessage(Locale l) { 139 ArgumentNotValid.checkNotNull(l, "l"); 140 // Make sure mbeans are forwarded 141 HostForwarding.getInstance(SingleLogRecord.class, mBeanServer, LOGGING_QUERY); 142 try { 143 String logMessage = (String) mBeanServer.getAttribute(mBeanName, JMXLogMessageAttribute); 144 if (logMessage == null) { 145 return HTMLUtils.escapeHtmlValues(getLogDate() 146 + I18N.getString(l, "errormsg;remote.host.returned.null.log.record")); 147 } else { 148 return logMessage; 149 } 150 } catch (RuntimeMBeanException e) { 151 return HTMLUtils.escapeHtmlValues(getLogDate() 152 + I18N.getString(l, "errormsg;jmx.error.while.getting.log.record") + "\n" 153 + I18N.getString(l, "errormsg;probably.host.is.not.responding") + "\n" 154 + ExceptionUtils.getStackTrace(e)); 155 } catch (Exception e) { 156 return HTMLUtils.escapeHtmlValues(getLogDate() 157 + I18N.getString(l, "errormsg;remote.jmx.bean.generated.exception") + "\n" 158 + ExceptionUtils.getStackTrace(e)); 159 } 160 } 161 162 private String getLogDate() { 163 return "[" + new Date() + "] "; 164 } 165 166 /** 167 * Compares two entries according to first their location, then their machine name, then their ports, and then their 168 * application name, and then their index. 169 * 170 * @param o The object to compare with 171 * @return A negative number if this entry comes first, a positive if it comes second and 0 if they are equal. 172 */ 173 public int compareTo(StatusEntry o) { 174 int c; 175 176 if (getPhysicalLocation() != null && o.getPhysicalLocation() != null) { 177 c = getPhysicalLocation().compareTo(o.getPhysicalLocation()); 178 if (c != 0) { 179 return c; 180 } 181 } else if (getPhysicalLocation() == null) { 182 return -1; 183 } else { 184 return 1; 185 } 186 187 if (getMachineName() != null && o.getMachineName() != null) { 188 c = getMachineName().compareTo(o.getMachineName()); 189 if (c != 0) { 190 return c; 191 } 192 } else if (getMachineName() == null) { 193 return -1; 194 } else { 195 return 1; 196 } 197 198 if (getHTTPPort() != null && o.getHTTPPort() != null) { 199 c = getHTTPPort().compareTo(o.getHTTPPort()); 200 if (c != 0) { 201 return c; 202 } 203 } else if (getHTTPPort() == null) { 204 return -1; 205 } else { 206 return 1; 207 } 208 209 if (getApplicationName() != null && o.getApplicationName() != null) { 210 c = getApplicationName().compareTo(o.getApplicationName()); 211 if (c != 0) { 212 return c; 213 } 214 } else if (getApplicationName() == null) { 215 return -1; 216 } else { 217 return 1; 218 } 219 220 if (getApplicationInstanceID() != null && o.getApplicationInstanceID() != null) { 221 c = getApplicationInstanceID().compareTo(o.getApplicationInstanceID()); 222 if (c != 0) { 223 return c; 224 } 225 } else if (getApplicationInstanceID() == null) { 226 return -1; 227 } else { 228 return 1; 229 } 230 231 if (getHarvestPriority() != null && o.getHarvestPriority() != null) { 232 c = getHarvestPriority().compareTo(o.getHarvestPriority()); 233 if (c != 0) { 234 return c; 235 } 236 } else if (getHarvestPriority() == null) { 237 return -1; 238 } else { 239 return 1; 240 } 241 242 if (getArchiveReplicaName() != null && o.getArchiveReplicaName() != null) { 243 c = getArchiveReplicaName().compareTo(o.getArchiveReplicaName()); 244 if (c != 0) { 245 return c; 246 } 247 } else if (getArchiveReplicaName() == null) { 248 return -1; 249 } else { 250 return 1; 251 } 252 253 Integer i1; 254 Integer i2; 255 try { 256 i1 = Integer.valueOf(getIndex()); 257 } catch (NumberFormatException e) { 258 i1 = null; 259 } 260 try { 261 i2 = Integer.valueOf(o.getIndex()); 262 } catch (NumberFormatException e) { 263 i2 = null; 264 } 265 266 if (i1 != null && i2 != null) { 267 c = i1.compareTo(i2); 268 if (c != 0) { 269 return c; 270 } 271 } else if (i1 == null) { 272 return -1; 273 } else { 274 return 1; 275 } 276 277 return 0; 278 } 279 280 /** 281 * Query the JMX system for system status mbeans. 282 * 283 * @param query A JMX request, e.g. dk.netarkivet.logging:location=EAST,httpport=8080,* 284 * @return A list of status entries for the mbeans that match the query. 285 * @throws MalformedObjectNameException If the query has wrong format. 286 */ 287 public static List<StatusEntry> queryJMX(String query) throws MalformedObjectNameException { 288 ArgumentNotValid.checkNotNull(query, "query"); 289 290 List<StatusEntry> entries = new ArrayList<StatusEntry>(); 291 292 // Make sure mbeans are forwarded 293 HostForwarding.getInstance(SingleLogRecord.class, mBeanServer, LOGGING_QUERY); 294 // The "null" in this case is used to indicate no further filters on the 295 // query. 296 Set<ObjectName> resultSet = mBeanServer.queryNames(new ObjectName(query), null); 297 for (ObjectName objectName : resultSet) { 298 entries.add(new JMXStatusEntry(objectName)); 299 } 300 Collections.sort(entries); 301 return entries; 302 } 303 304 /** 305 * Unregister an JMX MBean instance. 306 * 307 * @param query A JMX request, for picking the beans to unregister. 308 * @throws MalformedObjectNameException if query is malformed. 309 * @throws InstanceNotFoundException if the instanced unregistered doesn't exists. 310 * @throws MBeanRegistrationException if unregeterBean is thrown. 311 */ 312 public static void unregisterJMXInstance(String query) throws MalformedObjectNameException, 313 InstanceNotFoundException, MBeanRegistrationException { 314 ArgumentNotValid.checkNotNull(query, "query"); 315 Set<ObjectName> namesMatchingQuery = mBeanServer.queryNames(new ObjectName(query), null); 316 for (ObjectName name : namesMatchingQuery) { 317 mBeanServer.unregisterMBean(name); 318 } 319 } 320}