| /* |
| * Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * - Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * - Neither the name of Oracle nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS |
| * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
| * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
| * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /* |
| * This source code is provided to illustrate the usage of a given feature |
| * or technique and has been deliberately simplified. Additional steps |
| * required for a production-quality application, such as security checks, |
| * input validation and proper error handling, might not be present in |
| * this sample code. |
| */ |
| |
| |
| package com.sun.jmx.examples.scandir; |
| |
| import static com.sun.jmx.examples.scandir.ScanManager.getNextSeqNumber; |
| import com.sun.jmx.examples.scandir.config.ResultLogConfig; |
| import com.sun.jmx.examples.scandir.config.XmlConfigUtils; |
| import com.sun.jmx.examples.scandir.config.ResultRecord; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.Collections; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.logging.Logger; |
| import javax.management.MBeanNotificationInfo; |
| import javax.management.MBeanRegistration; |
| import javax.management.MBeanServer; |
| import javax.management.Notification; |
| import javax.management.NotificationBroadcasterSupport; |
| import javax.management.ObjectName; |
| import javax.xml.bind.JAXBException; |
| |
| /** |
| * The <code>ResultLogManager</code> is in charge of managing result logs. |
| * {@link DirectoryScanner DirectoryScanners} can be configured to log a |
| * {@link ResultRecord} whenever they take action upon a file that |
| * matches their set of matching criteria. |
| * The <code>ResultLogManagerMXBean</code> is responsible for storing these |
| * results in its result logs. |
| * <p>The <code>ResultLogManagerMXBean</code> can be configured to log |
| * these records to a flat file, or into a log held in memory, or both. |
| * Both logs (file and memory) can be configured with a maximum capacity. |
| * <br>When the maximum capacity of the memory log is reached - its first |
| * entry (i.e. its eldest entry) is removed to make place for the latest. |
| * <br>When the maximum capacity of the file log is reached, the file is |
| * renamed by appending a tilde '~' to its name and a new result log is created. |
| * |
| * |
| * @author Sun Microsystems, 2006 - All rights reserved. |
| */ |
| public class ResultLogManager extends NotificationBroadcasterSupport |
| implements ResultLogManagerMXBean, MBeanRegistration { |
| |
| /** |
| * The default singleton name of the {@link ResultLogManagerMXBean}. |
| **/ |
| public static final ObjectName RESULT_LOG_MANAGER_NAME = |
| ScanManager.makeSingletonName(ResultLogManagerMXBean.class); |
| |
| /** |
| * A logger for this class. |
| **/ |
| private static final Logger LOG = |
| Logger.getLogger(ResultLogManager.class.getName()); |
| |
| // The memory log |
| // |
| private final List<ResultRecord> memoryLog; |
| |
| // Whether the memory log capacity was reached. In that case every |
| // new entry triggers the deletion of the eldest one. |
| // |
| private volatile boolean memCapacityReached = false; |
| |
| // The maximum number of record that the memory log can |
| // contain. |
| // |
| private volatile int memCapacity; |
| |
| // The maximum number of record that the ResultLogManager can |
| // log in the log file before creating a new file. |
| // |
| private volatile long fileCapacity; |
| |
| // The current log file. |
| // |
| private volatile File logFile; |
| |
| // The OutputStream of the current log file. |
| // |
| private volatile OutputStream logStream = null; |
| |
| // number of record that this object has logged in the log file |
| // since the log file was created. Creating a new file or clearing |
| // the log file reset this value to '0' |
| // |
| private volatile long logCount = 0; |
| |
| // The ResultLogManager config - modified whenever |
| // ScanManager.applyConfiguration is called. |
| // |
| private volatile ResultLogConfig config; |
| |
| /** |
| * Create a new ResultLogManagerMXBean. This constructor is package |
| * protected: only the {@link ScanManager} can create a |
| * <code>ResultLogManager</code>. |
| **/ |
| ResultLogManager() { |
| // Instantiate the memory log - override the add() method so that |
| // it removes the head of the list when the maximum capacity is |
| // reached. Note that add() is the only method we will be calling, |
| // otherwise we would have to override all the other flavors |
| // of adding methods. Note also that this implies that the memoryLog |
| // will *always* remain encapsulated in this object and is *never* |
| // handed over (otherwise we wouldn't be able to ensure that |
| // add() is the only method ever called to add a record). |
| // |
| memoryLog = |
| Collections.synchronizedList(new LinkedList<ResultRecord>() { |
| public synchronized boolean add(ResultRecord e) { |
| final int max = getMemoryLogCapacity(); |
| while (max > 0 && size() >= max) { |
| memCapacityReached = true; |
| removeFirst(); |
| } |
| return super.add(e); |
| } |
| }); |
| |
| // default memory capacity |
| memCapacity = 2048; |
| |
| // default file capacity: 0 means infinite ;-) |
| fileCapacity = 0; |
| |
| // by default logging to file is disabled. |
| logFile = null; |
| |
| // Until the ScanManager apply a new configuration, we're going to |
| // work with a default ResultLogConfig object. |
| config = new ResultLogConfig(); |
| config.setMemoryMaxRecords(memCapacity); |
| config.setLogFileName(getLogFileName(false)); |
| config.setLogFileMaxRecords(fileCapacity); |
| } |
| |
| |
| /** |
| * Allows the MBean to perform any operations it needs before being |
| * registered in the MBean server. |
| * <p>If the name of the MBean is not |
| * specified, the MBean can provide a name for its registration. If |
| * any exception is raised, the MBean will not be registered in the |
| * MBean server.</p> |
| * <p>The {@code ResultLogManager} uses this method to supply its own |
| * default singleton ObjectName (if <var>name</var> parameter is null). |
| * @param server The MBean server in which the MBean will be registered. |
| * @param name The object name of the MBean. This name is null if the |
| * name parameter to one of the createMBean or registerMBean methods in |
| * the MBeanServer interface is null. In that case, this method must |
| * return a non-null ObjectName for the new MBean. |
| * @return The name under which the MBean is to be registered. This value |
| * must not be null. If the name parameter is not null, it will usually |
| * but not necessarily be the returned value. |
| * @throws Exception This exception will be caught by the MBean server and |
| * re-thrown as an MBeanRegistrationException. |
| */ |
| public ObjectName preRegister(MBeanServer server, ObjectName name) |
| throws Exception { |
| if (name == null) |
| name = RESULT_LOG_MANAGER_NAME; |
| objectName = name; |
| mbeanServer = server; |
| return name; |
| } |
| |
| /** |
| * Allows the MBean to perform any operations needed after having |
| * been registered in the MBean server or after the registration has |
| * failed. |
| * <p>This implementation does nothing.</p> |
| * @param registrationDone Indicates whether or not the MBean has been |
| * successfully registered in the MBean server. The value false means |
| * that the registration has failed. |
| */ |
| public void postRegister(Boolean registrationDone) { |
| // Don't need to do anything here. |
| } |
| |
| /** |
| * Allows the MBean to perform any operations it needs before being |
| * unregistered by the MBean server. |
| * <p>This implementation does nothing.</p> |
| * @throws Exception This exception will be caught by the MBean server and |
| * re-thrown as an MBeanRegistrationException. |
| */ |
| public void preDeregister() throws Exception { |
| // Don't need to do anything here. |
| } |
| |
| /** |
| * Allows the MBean to perform any operations needed after having been |
| * unregistered in the MBean server. |
| * <p>Closes the log file stream, if it is still open.</p> |
| */ |
| public void postDeregister() { |
| try { |
| if (logStream != null) { |
| synchronized(this) { |
| logStream.flush(); |
| logStream.close(); |
| logFile = null; |
| logStream = null; |
| } |
| } |
| } catch (Exception x) { |
| LOG.finest("Failed to close log properly: "+x); |
| } |
| } |
| |
| /** |
| * Create a new empty log file from the given basename, renaming |
| * previously existing file by appending '~' to its name. |
| **/ |
| private File createNewLogFile(String basename) throws IOException { |
| return XmlConfigUtils.createNewXmlFile(basename); |
| } |
| |
| /** |
| * Check whether a new log file should be created. |
| * If a new file needs to be created, creates it, renaming |
| * previously existing file by appending '~' to its name. |
| * Also reset the log count and file capacity. |
| * Sends a notification indicating that the log file was changed. |
| * Returns the new log stream; |
| * Creation of a new file can be forced by passing force=true. |
| **/ |
| private OutputStream checkLogFile(String basename, long maxRecords, |
| boolean force) |
| throws IOException { |
| final OutputStream newStream; |
| synchronized(this) { |
| if ((force==false) && (logCount < maxRecords)) |
| return logStream; |
| final OutputStream oldStream = logStream; |
| |
| // First close the stream. On some platforms you cannot rename |
| // a file that has open streams... |
| // |
| if (oldStream != null) { |
| oldStream.flush(); |
| oldStream.close(); |
| } |
| final File newFile = (basename==null)?null:createNewLogFile(basename); |
| |
| newStream = (newFile==null)?null:new FileOutputStream(newFile,true); |
| logStream = newStream; |
| logFile = newFile; |
| fileCapacity = maxRecords; |
| logCount = 0; |
| } |
| sendNotification(new Notification(LOG_FILE_CHANGED,objectName, |
| getNextSeqNumber(), |
| basename)); |
| return newStream; |
| } |
| |
| // see ResultLogManagerMXBean |
| public void log(ResultRecord record) |
| throws IOException { |
| if (memCapacity > 0) logToMemory(record); |
| if (logFile != null) logToFile(record); |
| } |
| |
| // see ResultLogManagerMXBean |
| public ResultRecord[] getMemoryLog() { |
| return memoryLog.toArray(new ResultRecord[0]); |
| } |
| |
| // see ResultLogManagerMXBean |
| public int getMemoryLogCapacity() { |
| return memCapacity; |
| } |
| |
| // see ResultLogManagerMXBean |
| public void setMemoryLogCapacity(int maxRecords) { |
| synchronized(this) { |
| memCapacity = maxRecords; |
| if (memoryLog.size() < memCapacity) |
| memCapacityReached = false; |
| config.setMemoryMaxRecords(maxRecords); |
| } |
| } |
| |
| // see ResultLogManagerMXBean |
| public void setLogFileCapacity(long maxRecords) |
| throws IOException { |
| synchronized (this) { |
| fileCapacity = maxRecords; |
| config.setLogFileMaxRecords(maxRecords); |
| } |
| checkLogFile(getLogFileName(),fileCapacity,false); |
| } |
| |
| // see ResultLogManagerMXBean |
| public long getLogFileCapacity() { |
| return fileCapacity; |
| } |
| |
| // see ResultLogManagerMXBean |
| public long getLoggedCount() { |
| return logCount; |
| } |
| |
| // see ResultLogManagerMXBean |
| public void newLogFile(String logname, long maxRecord) |
| throws IOException { |
| checkLogFile(logname,maxRecord,true); |
| config.setLogFileName(getLogFileName(false)); |
| config.setLogFileMaxRecords(getLogFileCapacity()); |
| } |
| |
| // see ResultLogManagerMXBean |
| public String getLogFileName() { |
| return getLogFileName(true); |
| } |
| |
| // see ResultLogManagerMXBean |
| public void clearLogs() throws IOException { |
| clearMemoryLog(); |
| clearLogFile(); |
| } |
| |
| // Clear the memory log, sends a notification indicating that |
| // the memory log was cleared. |
| // |
| private void clearMemoryLog()throws IOException { |
| synchronized(this) { |
| memoryLog.clear(); |
| memCapacityReached = false; |
| } |
| sendNotification(new Notification(MEMORY_LOG_CLEARED, |
| objectName, |
| getNextSeqNumber(),"memory log cleared")); |
| } |
| |
| // Clears the log file. |
| // |
| private void clearLogFile() throws IOException { |
| // simply force the creation of a new log file. |
| checkLogFile(getLogFileName(),fileCapacity,true); |
| } |
| |
| // Log a record to the memory log. Send a notification if the |
| // maximum capacity of the memory log is reached. |
| // |
| private void logToMemory(ResultRecord record) { |
| |
| final boolean before = memCapacityReached; |
| final boolean after; |
| synchronized(this) { |
| memoryLog.add(record); |
| after = memCapacityReached; |
| } |
| if (before==false && after==true) |
| sendNotification(new Notification(MEMORY_LOG_MAX_CAPACITY, |
| objectName, |
| getNextSeqNumber(),"memory log capacity reached")); |
| } |
| |
| |
| // Log a record to the memory log. Send a notification if the |
| // maximum capacity of the memory log is reached. |
| // |
| private void logToFile(ResultRecord record) throws IOException { |
| final String basename; |
| final long maxRecords; |
| synchronized (this) { |
| if (logFile == null) return; |
| basename = getLogFileName(false); |
| maxRecords = fileCapacity; |
| } |
| |
| // Get the stream into which we should log. |
| final OutputStream stream = |
| checkLogFile(basename,maxRecords,false); |
| |
| // logging to file now disabled - too bad. |
| if (stream == null) return; |
| |
| synchronized (this) { |
| try { |
| XmlConfigUtils.write(record,stream,true); |
| stream.flush(); |
| // don't increment logCount if we were not logging in logStream. |
| if (stream == logStream) logCount++; |
| } catch (JAXBException x) { |
| final IllegalArgumentException iae = |
| new IllegalArgumentException("bad record",x); |
| LOG.finest("Failed to log record: "+x); |
| throw iae; |
| } |
| } |
| } |
| |
| /** |
| * The notification type which indicates that the log file was switched: |
| * <i>com.sun.jmx.examples.scandir.log.file.switched</i>. |
| * The message contains the name of the new file (or null if log to file |
| * is now disabled). |
| **/ |
| public final static String LOG_FILE_CHANGED = |
| "com.sun.jmx.examples.scandir.log.file.switched"; |
| |
| /** |
| * The notification type which indicates that the memory log capacity has |
| * been reached: |
| * <i>com.sun.jmx.examples.scandir.log.memory.full</i>. |
| **/ |
| public final static String MEMORY_LOG_MAX_CAPACITY = |
| "com.sun.jmx.examples.scandir.log.memory.full"; |
| |
| /** |
| * The notification type which indicates that the memory log was |
| * cleared: |
| * <i>com.sun.jmx.examples.scandir.log.memory.cleared</i>. |
| **/ |
| public final static String MEMORY_LOG_CLEARED = |
| "com.sun.jmx.examples.scandir.log.memory.cleared"; |
| |
| /** |
| * This MBean emits three kind of notifications: |
| * <pre> |
| * <i>com.sun.jmx.examples.scandir.log.file.switched</i> |
| * <i>com.sun.jmx.examples.scandir.log.memory.full</i> |
| * <i>com.sun.jmx.examples.scandir.log.memory.cleared</i> |
| * </pre> |
| **/ |
| public MBeanNotificationInfo[] getNotificationInfo() { |
| return new MBeanNotificationInfo[] { |
| new MBeanNotificationInfo(new String[] { |
| LOG_FILE_CHANGED}, |
| Notification.class.getName(), |
| "Emitted when the log file is switched") |
| , |
| new MBeanNotificationInfo(new String[] { |
| MEMORY_LOG_MAX_CAPACITY}, |
| Notification.class.getName(), |
| "Emitted when the memory log capacity is reached") |
| , |
| new MBeanNotificationInfo(new String[] { |
| MEMORY_LOG_CLEARED}, |
| Notification.class.getName(), |
| "Emitted when the memory log is cleared") |
| }; |
| } |
| |
| // Return the name of the log file, or null if logging to file is |
| // disabled. |
| private String getLogFileName(boolean absolute) { |
| synchronized (this) { |
| if (logFile == null) return null; |
| if (absolute) return logFile.getAbsolutePath(); |
| return logFile.getPath(); |
| } |
| } |
| |
| // This method is be called by the ScanManagerMXBean when a configuration |
| // is applied. |
| // |
| void setConfig(ResultLogConfig logConfigBean) throws IOException { |
| if (logConfigBean == null) |
| throw new IllegalArgumentException("logConfigBean is null"); |
| synchronized (this) { |
| config = logConfigBean; |
| setMemoryLogCapacity(config.getMemoryMaxRecords()); |
| } |
| final String filename = config.getLogFileName(); |
| final String logname = getLogFileName(false); |
| if ((filename != null && !filename.equals(logname)) |
| || (filename == null && logname != null)) { |
| newLogFile(config.getLogFileName(), |
| config.getLogFileMaxRecords()); |
| } else { |
| setLogFileCapacity(config.getLogFileMaxRecords()); |
| } |
| } |
| |
| // This method is called by the ScanManagerMXBean when |
| // applyCurrentResultLogConfig() is called. |
| // |
| ResultLogConfig getConfig() { |
| return config; |
| } |
| |
| |
| // Set by preRegister(). |
| private MBeanServer mbeanServer; |
| private ObjectName objectName; |
| |
| |
| |
| } |