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}