Home | History | Annotate | Download | only in scandir
      1 /*
      2  * Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  *
      8  *   - Redistributions of source code must retain the above copyright
      9  *     notice, this list of conditions and the following disclaimer.
     10  *
     11  *   - Redistributions in binary form must reproduce the above copyright
     12  *     notice, this list of conditions and the following disclaimer in the
     13  *     documentation and/or other materials provided with the distribution.
     14  *
     15  *   - Neither the name of Oracle nor the names of its
     16  *     contributors may be used to endorse or promote products derived
     17  *     from this software without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
     20  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
     21  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     23  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     24  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     25  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     26  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     27  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     28  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     29  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 /*
     33  * This source code is provided to illustrate the usage of a given feature
     34  * or technique and has been deliberately simplified. Additional steps
     35  * required for a production-quality application, such as security checks,
     36  * input validation and proper error handling, might not be present in
     37  * this sample code.
     38  */
     39 
     40 
     41 package com.sun.jmx.examples.scandir;
     42 
     43 import static com.sun.jmx.examples.scandir.ScanManager.getNextSeqNumber;
     44 import static com.sun.jmx.examples.scandir.ScanDirConfigMXBean.SaveState.*;
     45 import com.sun.jmx.examples.scandir.config.XmlConfigUtils;
     46 import com.sun.jmx.examples.scandir.config.DirectoryScannerConfig;
     47 import com.sun.jmx.examples.scandir.config.FileMatch;
     48 import com.sun.jmx.examples.scandir.config.ScanManagerConfig;
     49 import java.io.File;
     50 import java.io.IOException;
     51 import java.io.InputStream;
     52 import java.io.OutputStream;
     53 import java.util.Date;
     54 import java.util.logging.Level;
     55 import java.util.logging.Logger;
     56 import javax.management.*;
     57 import javax.xml.bind.JAXBException;
     58 
     59 /**
     60  * <p>The <code>ScanDirConfig</code> MBean is in charge of the
     61  * <i>scandir</i> application configuration.
     62  * </p>
     63  * <p>The <code>ScanDirConfig</code> MBean is able to
     64  * load and save the <i>scandir</i> application configuration to and from an
     65  * XML file.
     66  * </p>
     67  * <p>
     68  * It will let you also interactively modify that configuration, which you
     69  * can later save to the file, by calling {@link #save}, or discard, by
     70  * reloading the file without saving - see {@link #load}.
     71  * </p>
     72  * <p>
     73  * There can be as many <code>ScanDirConfigMXBean</code> registered
     74  * in the MBeanServer as you like, but only one of them will be identified as
     75  * the current configuration of the {@link ScanManagerMXBean}.
     76  * You can switch to another configuration by calling {@link
     77  * ScanManagerMXBean#setConfigurationMBean
     78  * ScanManagerMXBean.setConfigurationMBean}.
     79  * </p>
     80  * <p>
     81  * Once the current configuration has been loaded (by calling {@link #load})
     82  * or modified (by calling one of {@link #addDirectoryScanner
     83  * addDirectoryScanner}, {@link #removeDirectoryScanner removeDirectoryScanner}
     84  * or {@link #setConfiguration setConfiguration}) it can be pushed
     85  * to the {@link ScanManagerMXBean} by calling {@link
     86  * ScanManagerMXBean#applyConfiguration
     87  * ScanManagerMXBean.applyConfiguration(true)} -
     88  * <code>true</code> means that we apply the configuration from memory,
     89  * without first reloading the file.
     90  * </p>
     91  * <p>
     92  * The <code>ScanDirConfig</code> uses the XML annotated Java Beans defined
     93  * in the {@link com.sun.jmx.examples.scandir.config} package.
     94  * </p>
     95  * <p>
     96  * <u>Note:</u> The <code>ScanDirConfig</code> should probably use
     97  * {@code java.nio.channels.FileLock} and lock its configuration file so that
     98  * two <code>ScanDirConfig</code> object do not share the same file, but it
     99  * doesn't. Feel free to improve the application in that way.
    100  * </p>
    101  * @author Sun Microsystems, 2006 - All rights reserved.
    102  */
    103 public class ScanDirConfig extends NotificationBroadcasterSupport
    104         implements ScanDirConfigMXBean, MBeanRegistration {
    105 
    106     /**
    107      * A logger for this class.
    108      **/
    109     private static final Logger LOG =
    110             Logger.getLogger(ScanDirConfig.class.getName());
    111 
    112     // We will emit a notification when the save state of this object
    113     // chenges. We use directly the base notification class, with a
    114     // notification type that indicates the new state at which the
    115     // object has arrived.
    116     //
    117     // All these notification types will have the same prefix, which is
    118     // 'com.sun.jmx.examples.scandir.config'.
    119     //
    120     private final static String NOTIFICATION_PREFIX =
    121             ScanManagerConfig.class.getPackage().getName();
    122 
    123     /**
    124      * The <i>com.sun.jmx.examples.scandir.config.saved</i> notification
    125      * indicates that the configuration data was saved.
    126      **/
    127     public final static String NOTIFICATION_SAVED =
    128             NOTIFICATION_PREFIX+".saved";
    129     /**
    130      * The <i>com.sun.jmx.examples.scandir.config.loaded</i> notification
    131      * indicates that the configuration data was loaded.
    132      **/
    133     public final static String NOTIFICATION_LOADED =
    134             NOTIFICATION_PREFIX+".loaded";
    135 
    136     /**
    137      * The <i>com.sun.jmx.examples.scandir.config.modified</i> notification
    138      * indicates that the configuration data was modified.
    139      **/
    140     public final static String NOTIFICATION_MODIFIED =
    141             NOTIFICATION_PREFIX+".modified";
    142 
    143     // The array of MBeanNotificationInfo that will be exposed in the
    144     // ScanDirConfigMXBean MBeanInfo.
    145     // We will pass this array to the NotificationBroadcasterSupport
    146     // constructor.
    147     //
    148     private static MBeanNotificationInfo[] NOTIFICATION_INFO = {
    149         new MBeanNotificationInfo(
    150                 new String[] {NOTIFICATION_SAVED},
    151                 Notification.class.getName(),
    152                 "Emitted when the configuration is saved"),
    153         new MBeanNotificationInfo(
    154                 new String[] {NOTIFICATION_LOADED},
    155                 Notification.class.getName(),
    156                 "Emitted when the configuration is loaded"),
    157         new MBeanNotificationInfo(
    158                 new String[] {NOTIFICATION_MODIFIED},
    159                 Notification.class.getName(),
    160                 "Emitted when the configuration is modified"),
    161     };
    162 
    163      // The ScanDirConfigMXBean configuration data.
    164     private volatile ScanManagerConfig config;
    165 
    166     // The name of the configuration file
    167     private String filename = null;
    168 
    169     // The name of this configuration. This is usually both equal to
    170     // config.getName() and objectName.getKeyProperty(name).
    171     private volatile String configname = null;
    172 
    173     // This object save state. CREATED is the initial state.
    174     //
    175     private volatile SaveState status = CREATED;
    176 
    177     /**
    178      * Creates a new {@link ScanDirConfigMXBean}.
    179      * <p>{@code ScanDirConfigMXBean} can be created by the {@link
    180      * ScanManagerMXBean}, or directly by a remote client, using
    181      * {@code createMBean} or {@code registerMBean}.
    182      * </p>
    183      * <p>{@code ScanDirConfigMXBean} created by the {@link
    184      * ScanManagerMXBean} will be unregistered by the
    185      * {@code ScanManagerMXBean}. {@code ScanDirConfigMXBean} created
    186      * directly by a remote client will not be unregistered by the
    187      * {@code ScanManagerMXBean} - this will remain to the responsibility of
    188      * the code/client that created them.
    189      * </p>
    190      * <p>This object is created empty, you should call load() if you want it
    191      *    to load its data from the configuration file.
    192      * </p>
    193      * @param  filename The configuration file used by this MBean.
    194      *         Can be null (in which case load() and save() will fail).
    195      *         Can point to a file that does not exists yet (in which case
    196      *         load() will fail if called before save(), and save() will
    197      *         attempt to create that file). Can point to an existing file,
    198      *         in which case load() will load that file and save() will save
    199      *         to that file.
    200      *
    201      **/
    202     public ScanDirConfig(String filename) {
    203         this(filename,null);
    204     }
    205 
    206     /**
    207      * Create a new ScanDirConfig MBean with an initial configuration.
    208      * @param filename The name of the configuration file.
    209      * @param initialConfig an initial configuration.
    210      **/
    211     public ScanDirConfig(String filename, ScanManagerConfig initialConfig) {
    212         super(NOTIFICATION_INFO);
    213         this.filename = filename;
    214         this.config = initialConfig;
    215     }
    216 
    217 
    218     // see ScanDirConfigMXBean
    219     public void load() throws IOException {
    220         if (filename == null)
    221             throw new UnsupportedOperationException("load");
    222 
    223         synchronized(this) {
    224             config = new XmlConfigUtils(filename).readFromFile();
    225             if (configname != null) config = config.copy(configname);
    226             else configname = config.getName();
    227 
    228             status=LOADED;
    229         }
    230         sendNotification(NOTIFICATION_LOADED);
    231     }
    232 
    233     // see ScanDirConfigMXBean
    234     public void save() throws IOException {
    235         if (filename == null)
    236             throw new UnsupportedOperationException("load");
    237         synchronized (this) {
    238             new XmlConfigUtils(filename).writeToFile(config);
    239             status = SAVED;
    240         }
    241         sendNotification(NOTIFICATION_SAVED);
    242     }
    243 
    244     // see ScanDirConfigMXBean
    245     public ScanManagerConfig getConfiguration() {
    246         synchronized (this) {
    247             return XmlConfigUtils.xmlClone(config);
    248         }
    249     }
    250 
    251 
    252     // sends a notification indicating the new save state.
    253     private void sendNotification(String type) {
    254         final Object source = (objectName==null)?this:objectName;
    255         final Notification n = new Notification(type,source,
    256                 getNextSeqNumber(),
    257                 "The configuration is "+
    258                 type.substring(type.lastIndexOf('.')+1));
    259         sendNotification(n);
    260     }
    261 
    262 
    263     /**
    264      * Allows the MBean to perform any operations it needs before being
    265      * registered in the MBean server. If the name of the MBean is not
    266      * specified, the MBean can provide a name for its registration. If
    267      * any exception is raised, the MBean will not be registered in the
    268      * MBean server.
    269      * @param server The MBean server in which the MBean will be registered.
    270      * @param name The object name of the MBean. This name is null if the
    271      * name parameter to one of the createMBean or registerMBean methods in
    272      * the MBeanServer interface is null. In that case, this method will
    273      * try to guess its MBean name by examining its configuration data.
    274      * If its configuration data is null (nothing was provided in the
    275      * constructor) or doesn't contain a name, this method returns {@code null},
    276      * and registration will fail.
    277      * <p>
    278      * Otherwise, if {@code name} wasn't {@code null} or if a default name could
    279      * be constructed, the name of the configuration will be set to
    280      * the value of the ObjectName's {@code name=} key, and the configuration
    281      * data will always be renamed to reflect this change.
    282      * </p>
    283      *
    284      * @return The name under which the MBean is to be registered.
    285      * @throws Exception This exception will be caught by the MBean server and
    286      * re-thrown as an MBeanRegistrationException.
    287      */
    288     public ObjectName preRegister(MBeanServer server, ObjectName name)
    289         throws Exception {
    290         if (name == null) {
    291             if (config == null) return null;
    292             if (config.getName() == null) return null;
    293             name = ScanManager.
    294                     makeMBeanName(ScanDirConfigMXBean.class,config.getName());
    295         }
    296         objectName = name;
    297         mbeanServer = server;
    298         synchronized (this) {
    299             configname = name.getKeyProperty("name");
    300             if (config == null) config = new ScanManagerConfig(configname);
    301             else config = config.copy(configname);
    302         }
    303         return name;
    304     }
    305 
    306     /**
    307      * Allows the MBean to perform any operations needed after having
    308      * been registered in the MBean server or after the registration has
    309      * failed.
    310      * <p>This implementation does nothing</p>
    311      * @param registrationDone Indicates whether or not the MBean has been
    312      * successfully registered in the MBean server. The value false means
    313      * that the registration has failed.
    314      */
    315     public void postRegister(Boolean registrationDone) {
    316         // Nothing to do here.
    317     }
    318 
    319     /**
    320      * Allows the MBean to perform any operations it needs before being
    321      * unregistered by the MBean server.
    322      * <p>This implementation does nothing</p>
    323      * @throws Exception This exception will be caught by the MBean server and
    324      * re-thrown as an MBeanRegistrationException.
    325      */
    326     public void preDeregister() throws Exception {
    327         // Nothing to do here.
    328     }
    329 
    330     /**
    331      * Allows the MBean to perform any operations needed after having been
    332      * unregistered in the MBean server.
    333      * <p>This implementation does nothing</p>
    334      */
    335     public void postDeregister() {
    336         // Nothing to do here.
    337     }
    338 
    339     // see ScanDirConfigMXBean
    340     public String getConfigFilename() {
    341         return filename;
    342     }
    343 
    344     // see ScanDirConfigMXBean
    345     public void setConfiguration(ScanManagerConfig config) {
    346         synchronized (this) {
    347             if (config == null) {
    348                 this.config = null;
    349                 return;
    350             }
    351 
    352             if (configname == null)
    353                 configname = config.getName();
    354 
    355             this.config = config.copy(configname);
    356             status = MODIFIED;
    357         }
    358         sendNotification(NOTIFICATION_MODIFIED);
    359     }
    360 
    361     // see ScanDirConfigMXBean
    362     public DirectoryScannerConfig
    363             addDirectoryScanner(String name, String dir, String filePattern,
    364                                 long sizeExceedsMaxBytes, long sinceLastModified) {
    365          final DirectoryScannerConfig scanner =
    366                  new DirectoryScannerConfig(name);
    367          scanner.setRootDirectory(dir);
    368          if (filePattern!=null||sizeExceedsMaxBytes>0||sinceLastModified>0) {
    369             final FileMatch filter = new FileMatch();
    370             filter.setFilePattern(filePattern);
    371             filter.setSizeExceedsMaxBytes(sizeExceedsMaxBytes);
    372             if (sinceLastModified > 0)
    373                 filter.setLastModifiedBefore(new Date(new Date().getTime()
    374                                                 -sinceLastModified));
    375             scanner.addIncludeFiles(filter);
    376          }
    377          synchronized (this) {
    378             config.putScan(scanner);
    379             status = MODIFIED;
    380          }
    381          LOG.fine("config: "+config);
    382          sendNotification(NOTIFICATION_MODIFIED);
    383          return scanner;
    384     }
    385 
    386     // see ScanDirConfigMXBean
    387     public DirectoryScannerConfig removeDirectoryScanner(String name)
    388         throws IOException, InstanceNotFoundException {
    389         final DirectoryScannerConfig scanner;
    390         synchronized (this) {
    391             scanner = config.removeScan(name);
    392             if (scanner == null)
    393                 throw new IllegalArgumentException(name+": scanner not found");
    394             status = MODIFIED;
    395         }
    396         sendNotification(NOTIFICATION_MODIFIED);
    397         return scanner;
    398     }
    399 
    400     // see ScanDirConfigMXBean
    401     public SaveState getSaveState() {
    402         return status;
    403     }
    404 
    405     // These methods are used by ScanManager to guess a configuration name from
    406     // a configuration filename.
    407     //
    408     static String DEFAULT = "DEFAULT";
    409 
    410     private static String getBasename(String name) {
    411         final int dot = name.indexOf('.');
    412         if (dot<0)  return name;
    413         if (dot==0) return getBasename(name.substring(1));
    414         return name.substring(0,dot);
    415     }
    416 
    417     static String guessConfigName(String configFileName,String defaultFile) {
    418         try {
    419             if (configFileName == null) return DEFAULT;
    420             final File f = new File(configFileName);
    421             if (f.canRead()) {
    422                 final String confname = XmlConfigUtils.read(f).getName();
    423                 if (confname != null && confname.length()>0) return confname;
    424             }
    425             final File f2 = new File(defaultFile);
    426             if (f.equals(f2)) return DEFAULT;
    427             final String guess = getBasename(f.getName());
    428             if (guess == null) return DEFAULT;
    429             if (guess.length()==0) return DEFAULT;
    430             return guess;
    431         } catch (Exception x) {
    432             return DEFAULT;
    433         }
    434     }
    435 
    436     // Set by preRegister()
    437     private volatile MBeanServer mbeanServer;
    438     private volatile ObjectName objectName;
    439 
    440 }
    441