blob: 7ecd8a1ffdec867c0ef2b542ba2305cb1a7c7750 [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 static com.sun.jmx.examples.scandir.ScanDirConfigMXBean.SaveState.*;
import com.sun.jmx.examples.scandir.config.XmlConfigUtils;
import com.sun.jmx.examples.scandir.config.DirectoryScannerConfig;
import com.sun.jmx.examples.scandir.config.FileMatch;
import com.sun.jmx.examples.scandir.config.ScanManagerConfig;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.*;
import javax.xml.bind.JAXBException;
/**
* <p>The <code>ScanDirConfig</code> MBean is in charge of the
* <i>scandir</i> application configuration.
* </p>
* <p>The <code>ScanDirConfig</code> MBean is able to
* load and save the <i>scandir</i> application configuration to and from an
* XML file.
* </p>
* <p>
* It will let you also interactively modify that configuration, which you
* can later save to the file, by calling {@link #save}, or discard, by
* reloading the file without saving - see {@link #load}.
* </p>
* <p>
* There can be as many <code>ScanDirConfigMXBean</code> registered
* in the MBeanServer as you like, but only one of them will be identified as
* the current configuration of the {@link ScanManagerMXBean}.
* You can switch to another configuration by calling {@link
* ScanManagerMXBean#setConfigurationMBean
* ScanManagerMXBean.setConfigurationMBean}.
* </p>
* <p>
* Once the current configuration has been loaded (by calling {@link #load})
* or modified (by calling one of {@link #addDirectoryScanner
* addDirectoryScanner}, {@link #removeDirectoryScanner removeDirectoryScanner}
* or {@link #setConfiguration setConfiguration}) it can be pushed
* to the {@link ScanManagerMXBean} by calling {@link
* ScanManagerMXBean#applyConfiguration
* ScanManagerMXBean.applyConfiguration(true)} -
* <code>true</code> means that we apply the configuration from memory,
* without first reloading the file.
* </p>
* <p>
* The <code>ScanDirConfig</code> uses the XML annotated Java Beans defined
* in the {@link com.sun.jmx.examples.scandir.config} package.
* </p>
* <p>
* <u>Note:</u> The <code>ScanDirConfig</code> should probably use
* {@code java.nio.channels.FileLock} and lock its configuration file so that
* two <code>ScanDirConfig</code> object do not share the same file, but it
* doesn't. Feel free to improve the application in that way.
* </p>
* @author Sun Microsystems, 2006 - All rights reserved.
*/
public class ScanDirConfig extends NotificationBroadcasterSupport
implements ScanDirConfigMXBean, MBeanRegistration {
/**
* A logger for this class.
**/
private static final Logger LOG =
Logger.getLogger(ScanDirConfig.class.getName());
// We will emit a notification when the save state of this object
// chenges. We use directly the base notification class, with a
// notification type that indicates the new state at which the
// object has arrived.
//
// All these notification types will have the same prefix, which is
// 'com.sun.jmx.examples.scandir.config'.
//
private final static String NOTIFICATION_PREFIX =
ScanManagerConfig.class.getPackage().getName();
/**
* The <i>com.sun.jmx.examples.scandir.config.saved</i> notification
* indicates that the configuration data was saved.
**/
public final static String NOTIFICATION_SAVED =
NOTIFICATION_PREFIX+".saved";
/**
* The <i>com.sun.jmx.examples.scandir.config.loaded</i> notification
* indicates that the configuration data was loaded.
**/
public final static String NOTIFICATION_LOADED =
NOTIFICATION_PREFIX+".loaded";
/**
* The <i>com.sun.jmx.examples.scandir.config.modified</i> notification
* indicates that the configuration data was modified.
**/
public final static String NOTIFICATION_MODIFIED =
NOTIFICATION_PREFIX+".modified";
// The array of MBeanNotificationInfo that will be exposed in the
// ScanDirConfigMXBean MBeanInfo.
// We will pass this array to the NotificationBroadcasterSupport
// constructor.
//
private static MBeanNotificationInfo[] NOTIFICATION_INFO = {
new MBeanNotificationInfo(
new String[] {NOTIFICATION_SAVED},
Notification.class.getName(),
"Emitted when the configuration is saved"),
new MBeanNotificationInfo(
new String[] {NOTIFICATION_LOADED},
Notification.class.getName(),
"Emitted when the configuration is loaded"),
new MBeanNotificationInfo(
new String[] {NOTIFICATION_MODIFIED},
Notification.class.getName(),
"Emitted when the configuration is modified"),
};
// The ScanDirConfigMXBean configuration data.
private volatile ScanManagerConfig config;
// The name of the configuration file
private String filename = null;
// The name of this configuration. This is usually both equal to
// config.getName() and objectName.getKeyProperty(name).
private volatile String configname = null;
// This object save state. CREATED is the initial state.
//
private volatile SaveState status = CREATED;
/**
* Creates a new {@link ScanDirConfigMXBean}.
* <p>{@code ScanDirConfigMXBean} can be created by the {@link
* ScanManagerMXBean}, or directly by a remote client, using
* {@code createMBean} or {@code registerMBean}.
* </p>
* <p>{@code ScanDirConfigMXBean} created by the {@link
* ScanManagerMXBean} will be unregistered by the
* {@code ScanManagerMXBean}. {@code ScanDirConfigMXBean} created
* directly by a remote client will not be unregistered by the
* {@code ScanManagerMXBean} - this will remain to the responsibility of
* the code/client that created them.
* </p>
* <p>This object is created empty, you should call load() if you want it
* to load its data from the configuration file.
* </p>
* @param filename The configuration file used by this MBean.
* Can be null (in which case load() and save() will fail).
* Can point to a file that does not exists yet (in which case
* load() will fail if called before save(), and save() will
* attempt to create that file). Can point to an existing file,
* in which case load() will load that file and save() will save
* to that file.
*
**/
public ScanDirConfig(String filename) {
this(filename,null);
}
/**
* Create a new ScanDirConfig MBean with an initial configuration.
* @param filename The name of the configuration file.
* @param initialConfig an initial configuration.
**/
public ScanDirConfig(String filename, ScanManagerConfig initialConfig) {
super(NOTIFICATION_INFO);
this.filename = filename;
this.config = initialConfig;
}
// see ScanDirConfigMXBean
public void load() throws IOException {
if (filename == null)
throw new UnsupportedOperationException("load");
synchronized(this) {
config = new XmlConfigUtils(filename).readFromFile();
if (configname != null) config = config.copy(configname);
else configname = config.getName();
status=LOADED;
}
sendNotification(NOTIFICATION_LOADED);
}
// see ScanDirConfigMXBean
public void save() throws IOException {
if (filename == null)
throw new UnsupportedOperationException("load");
synchronized (this) {
new XmlConfigUtils(filename).writeToFile(config);
status = SAVED;
}
sendNotification(NOTIFICATION_SAVED);
}
// see ScanDirConfigMXBean
public ScanManagerConfig getConfiguration() {
synchronized (this) {
return XmlConfigUtils.xmlClone(config);
}
}
// sends a notification indicating the new save state.
private void sendNotification(String type) {
final Object source = (objectName==null)?this:objectName;
final Notification n = new Notification(type,source,
getNextSeqNumber(),
"The configuration is "+
type.substring(type.lastIndexOf('.')+1));
sendNotification(n);
}
/**
* Allows the MBean to perform any operations it needs before being
* registered in the MBean server. 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.
* @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 will
* try to guess its MBean name by examining its configuration data.
* If its configuration data is null (nothing was provided in the
* constructor) or doesn't contain a name, this method returns {@code null},
* and registration will fail.
* <p>
* Otherwise, if {@code name} wasn't {@code null} or if a default name could
* be constructed, the name of the configuration will be set to
* the value of the ObjectName's {@code name=} key, and the configuration
* data will always be renamed to reflect this change.
* </p>
*
* @return The name under which the MBean is to be registered.
* @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) {
if (config == null) return null;
if (config.getName() == null) return null;
name = ScanManager.
makeMBeanName(ScanDirConfigMXBean.class,config.getName());
}
objectName = name;
mbeanServer = server;
synchronized (this) {
configname = name.getKeyProperty("name");
if (config == null) config = new ScanManagerConfig(configname);
else config = config.copy(configname);
}
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) {
// Nothing to do 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 {
// Nothing to do here.
}
/**
* Allows the MBean to perform any operations needed after having been
* unregistered in the MBean server.
* <p>This implementation does nothing</p>
*/
public void postDeregister() {
// Nothing to do here.
}
// see ScanDirConfigMXBean
public String getConfigFilename() {
return filename;
}
// see ScanDirConfigMXBean
public void setConfiguration(ScanManagerConfig config) {
synchronized (this) {
if (config == null) {
this.config = null;
return;
}
if (configname == null)
configname = config.getName();
this.config = config.copy(configname);
status = MODIFIED;
}
sendNotification(NOTIFICATION_MODIFIED);
}
// see ScanDirConfigMXBean
public DirectoryScannerConfig
addDirectoryScanner(String name, String dir, String filePattern,
long sizeExceedsMaxBytes, long sinceLastModified) {
final DirectoryScannerConfig scanner =
new DirectoryScannerConfig(name);
scanner.setRootDirectory(dir);
if (filePattern!=null||sizeExceedsMaxBytes>0||sinceLastModified>0) {
final FileMatch filter = new FileMatch();
filter.setFilePattern(filePattern);
filter.setSizeExceedsMaxBytes(sizeExceedsMaxBytes);
if (sinceLastModified > 0)
filter.setLastModifiedBefore(new Date(new Date().getTime()
-sinceLastModified));
scanner.addIncludeFiles(filter);
}
synchronized (this) {
config.putScan(scanner);
status = MODIFIED;
}
LOG.fine("config: "+config);
sendNotification(NOTIFICATION_MODIFIED);
return scanner;
}
// see ScanDirConfigMXBean
public DirectoryScannerConfig removeDirectoryScanner(String name)
throws IOException, InstanceNotFoundException {
final DirectoryScannerConfig scanner;
synchronized (this) {
scanner = config.removeScan(name);
if (scanner == null)
throw new IllegalArgumentException(name+": scanner not found");
status = MODIFIED;
}
sendNotification(NOTIFICATION_MODIFIED);
return scanner;
}
// see ScanDirConfigMXBean
public SaveState getSaveState() {
return status;
}
// These methods are used by ScanManager to guess a configuration name from
// a configuration filename.
//
static String DEFAULT = "DEFAULT";
private static String getBasename(String name) {
final int dot = name.indexOf('.');
if (dot<0) return name;
if (dot==0) return getBasename(name.substring(1));
return name.substring(0,dot);
}
static String guessConfigName(String configFileName,String defaultFile) {
try {
if (configFileName == null) return DEFAULT;
final File f = new File(configFileName);
if (f.canRead()) {
final String confname = XmlConfigUtils.read(f).getName();
if (confname != null && confname.length()>0) return confname;
}
final File f2 = new File(defaultFile);
if (f.equals(f2)) return DEFAULT;
final String guess = getBasename(f.getName());
if (guess == null) return DEFAULT;
if (guess.length()==0) return DEFAULT;
return guess;
} catch (Exception x) {
return DEFAULT;
}
}
// Set by preRegister()
private volatile MBeanServer mbeanServer;
private volatile ObjectName objectName;
}