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}