/* * 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; }