blob: a0a4bb9f43d6a0796e0e191991209e00c52c9180 [file] [log] [blame]
/*
* 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;
}