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 com.sun.jmx.examples.scandir.config.ResultLogConfig;
     45 import com.sun.jmx.examples.scandir.config.XmlConfigUtils;
     46 import com.sun.jmx.examples.scandir.config.ResultRecord;
     47 import java.io.File;
     48 import java.io.FileOutputStream;
     49 import java.io.IOException;
     50 import java.io.OutputStream;
     51 import java.util.Collections;
     52 import java.util.LinkedList;
     53 import java.util.List;
     54 import java.util.logging.Logger;
     55 import javax.management.MBeanNotificationInfo;
     56 import javax.management.MBeanRegistration;
     57 import javax.management.MBeanServer;
     58 import javax.management.Notification;
     59 import javax.management.NotificationBroadcasterSupport;
     60 import javax.management.ObjectName;
     61 import javax.xml.bind.JAXBException;
     62 
     63 /**
     64  * The <code>ResultLogManager</code> is in charge of managing result logs.
     65  * {@link DirectoryScanner DirectoryScanners} can be configured to log a
     66  * {@link ResultRecord} whenever they take action upon a file that
     67  * matches their set of matching criteria.
     68  * The <code>ResultLogManagerMXBean</code> is responsible for storing these
     69  * results in its result logs.
     70  * <p>The <code>ResultLogManagerMXBean</code> can be configured to log
     71  * these records to a flat file, or into a log held in memory, or both.
     72  * Both logs (file and memory) can be configured with a maximum capacity.
     73  * <br>When the maximum capacity of the memory log is reached - its first
     74  * entry (i.e. its eldest entry) is removed to make place for the latest.
     75  * <br>When the maximum capacity of the file log is reached, the file is
     76  * renamed by appending a tilde '~' to its name and a new result log is created.
     77  *
     78  *
     79  * @author Sun Microsystems, 2006 - All rights reserved.
     80  */
     81 public class ResultLogManager extends NotificationBroadcasterSupport
     82         implements ResultLogManagerMXBean, MBeanRegistration {
     83 
     84     /**
     85      * The default singleton name of the {@link ResultLogManagerMXBean}.
     86      **/
     87     public static final ObjectName RESULT_LOG_MANAGER_NAME =
     88             ScanManager.makeSingletonName(ResultLogManagerMXBean.class);
     89 
     90     /**
     91      * A logger for this class.
     92      **/
     93     private static final Logger LOG =
     94             Logger.getLogger(ResultLogManager.class.getName());
     95 
     96     // The memory log
     97     //
     98     private final List<ResultRecord> memoryLog;
     99 
    100     // Whether the memory log capacity was reached. In that case every
    101     // new entry triggers the deletion of the eldest one.
    102     //
    103     private volatile boolean memCapacityReached = false;
    104 
    105     // The maximum number of record that the memory log can
    106     // contain.
    107     //
    108     private volatile int memCapacity;
    109 
    110     // The maximum number of record that the ResultLogManager can
    111     // log in the log file before creating a new file.
    112     //
    113     private volatile long fileCapacity;
    114 
    115     // The current log file.
    116     //
    117     private volatile File logFile;
    118 
    119     // The OutputStream of the current log file.
    120     //
    121     private volatile OutputStream logStream = null;
    122 
    123     // number of record that this object has logged in the log file
    124     // since the log file was created. Creating a new file or clearing
    125     // the log file reset this value to '0'
    126     //
    127     private volatile long logCount = 0;
    128 
    129     // The ResultLogManager config - modified whenever
    130     // ScanManager.applyConfiguration is called.
    131     //
    132     private volatile ResultLogConfig config;
    133 
    134     /**
    135      * Create a new ResultLogManagerMXBean. This constructor is package
    136      * protected: only the {@link ScanManager} can create a
    137      * <code>ResultLogManager</code>.
    138      **/
    139     ResultLogManager() {
    140         // Instantiate the memory log - override the add() method so that
    141         // it removes the head of the list when the maximum capacity is
    142         // reached. Note that add() is the only method we will be calling,
    143         // otherwise we would have to override all the other flavors
    144         // of adding methods. Note also that this implies that the memoryLog
    145         // will *always* remain encapsulated in this object and is *never*
    146         // handed over (otherwise we wouldn't be able to ensure that
    147         // add() is the only method ever called to add a record).
    148         //
    149         memoryLog =
    150                 Collections.synchronizedList(new LinkedList<ResultRecord>() {
    151             public synchronized boolean add(ResultRecord e) {
    152                 final int max = getMemoryLogCapacity();
    153                 while (max > 0 && size() >= max) {
    154                     memCapacityReached = true;
    155                     removeFirst();
    156                 }
    157                 return super.add(e);
    158             }
    159         });
    160 
    161         // default memory capacity
    162         memCapacity = 2048;
    163 
    164         // default file capacity: 0 means infinite ;-)
    165         fileCapacity = 0;
    166 
    167         // by default logging to file is disabled.
    168         logFile = null;
    169 
    170         // Until the ScanManager apply a new configuration, we're going to
    171         // work with a default ResultLogConfig object.
    172         config = new ResultLogConfig();
    173         config.setMemoryMaxRecords(memCapacity);
    174         config.setLogFileName(getLogFileName(false));
    175         config.setLogFileMaxRecords(fileCapacity);
    176     }
    177 
    178 
    179     /**
    180      * Allows the MBean to perform any operations it needs before being
    181      * registered in the MBean server.
    182      * <p>If the name of the MBean is not
    183      * specified, the MBean can provide a name for its registration. If
    184      * any exception is raised, the MBean will not be registered in the
    185      * MBean server.</p>
    186      * <p>The {@code ResultLogManager} uses this method to supply its own
    187      * default singleton ObjectName (if <var>name</var> parameter is null).
    188      * @param server The MBean server in which the MBean will be registered.
    189      * @param name The object name of the MBean. This name is null if the
    190      * name parameter to one of the createMBean or registerMBean methods in
    191      * the MBeanServer interface is null. In that case, this method must
    192      * return a non-null ObjectName for the new MBean.
    193      * @return The name under which the MBean is to be registered. This value
    194      * must not be null. If the name parameter is not null, it will usually
    195      * but not necessarily be the returned value.
    196      * @throws Exception This exception will be caught by the MBean server and
    197      * re-thrown as an MBeanRegistrationException.
    198      */
    199     public ObjectName preRegister(MBeanServer server, ObjectName name)
    200     throws Exception {
    201         if (name == null)
    202             name = RESULT_LOG_MANAGER_NAME;
    203         objectName = name;
    204         mbeanServer = server;
    205         return name;
    206     }
    207 
    208     /**
    209      * Allows the MBean to perform any operations needed after having
    210      * been registered in the MBean server or after the registration has
    211      * failed.
    212      * <p>This implementation does nothing.</p>
    213      * @param registrationDone Indicates whether or not the MBean has been
    214      * successfully registered in the MBean server. The value false means
    215      * that the registration has failed.
    216      */
    217     public void postRegister(Boolean registrationDone) {
    218         // Don't need to do anything here.
    219     }
    220 
    221     /**
    222      * Allows the MBean to perform any operations it needs before being
    223      * unregistered by the MBean server.
    224      * <p>This implementation does nothing.</p>
    225      * @throws Exception This exception will be caught by the MBean server and
    226      * re-thrown as an MBeanRegistrationException.
    227      */
    228     public void preDeregister() throws Exception {
    229         // Don't need to do anything here.
    230     }
    231 
    232     /**
    233      * Allows the MBean to perform any operations needed after having been
    234      * unregistered in the MBean server.
    235      * <p>Closes the log file stream, if it is still open.</p>
    236      */
    237     public void postDeregister() {
    238         try {
    239             if (logStream != null) {
    240                 synchronized(this)  {
    241                     logStream.flush();
    242                     logStream.close();
    243                     logFile = null;
    244                     logStream = null;
    245                 }
    246             }
    247         } catch (Exception x) {
    248             LOG.finest("Failed to close log properly: "+x);
    249         }
    250     }
    251 
    252     /**
    253      * Create a new empty log file from the given basename, renaming
    254      * previously existing file by appending '~' to its name.
    255      **/
    256     private File createNewLogFile(String basename) throws IOException {
    257         return XmlConfigUtils.createNewXmlFile(basename);
    258     }
    259 
    260     /**
    261      * Check whether a new log file should be created.
    262      * If a new file needs to be created, creates it, renaming
    263      * previously existing file by appending '~' to its name.
    264      * Also reset the log count and file capacity.
    265      * Sends a notification indicating that the log file was changed.
    266      * Returns the new log stream;
    267      * Creation of a new file can be forced by passing force=true.
    268      **/
    269     private OutputStream checkLogFile(String basename, long maxRecords,
    270                                       boolean force)
    271     throws IOException {
    272         final OutputStream newStream;
    273         synchronized(this) {
    274             if ((force==false) && (logCount < maxRecords))
    275                 return logStream;
    276             final OutputStream oldStream = logStream;
    277 
    278             // First close the stream. On some platforms you cannot rename
    279             // a file that has open streams...
    280             //
    281             if (oldStream != null) {
    282                 oldStream.flush();
    283                 oldStream.close();
    284             }
    285             final File newFile = (basename==null)?null:createNewLogFile(basename);
    286 
    287             newStream = (newFile==null)?null:new FileOutputStream(newFile,true);
    288             logStream = newStream;
    289             logFile = newFile;
    290             fileCapacity = maxRecords;
    291             logCount = 0;
    292         }
    293         sendNotification(new Notification(LOG_FILE_CHANGED,objectName,
    294                 getNextSeqNumber(),
    295                 basename));
    296         return newStream;
    297     }
    298 
    299     // see ResultLogManagerMXBean
    300     public void log(ResultRecord record)
    301     throws IOException {
    302         if (memCapacity > 0) logToMemory(record);
    303         if (logFile != null) logToFile(record);
    304     }
    305 
    306     // see ResultLogManagerMXBean
    307     public ResultRecord[] getMemoryLog() {
    308         return memoryLog.toArray(new ResultRecord[0]);
    309     }
    310 
    311     // see ResultLogManagerMXBean
    312     public int getMemoryLogCapacity() {
    313         return memCapacity;
    314     }
    315 
    316     // see ResultLogManagerMXBean
    317     public void setMemoryLogCapacity(int maxRecords)  {
    318         synchronized(this) {
    319             memCapacity = maxRecords;
    320             if (memoryLog.size() < memCapacity)
    321                 memCapacityReached = false;
    322             config.setMemoryMaxRecords(maxRecords);
    323         }
    324     }
    325 
    326     // see ResultLogManagerMXBean
    327     public void setLogFileCapacity(long maxRecords)
    328     throws IOException {
    329         synchronized (this) {
    330             fileCapacity = maxRecords;
    331             config.setLogFileMaxRecords(maxRecords);
    332         }
    333         checkLogFile(getLogFileName(),fileCapacity,false);
    334     }
    335 
    336     // see ResultLogManagerMXBean
    337     public long getLogFileCapacity()  {
    338         return fileCapacity;
    339     }
    340 
    341     // see ResultLogManagerMXBean
    342     public long getLoggedCount() {
    343         return logCount;
    344     }
    345 
    346     // see ResultLogManagerMXBean
    347     public void newLogFile(String logname, long maxRecord)
    348     throws IOException {
    349         checkLogFile(logname,maxRecord,true);
    350         config.setLogFileName(getLogFileName(false));
    351         config.setLogFileMaxRecords(getLogFileCapacity());
    352     }
    353 
    354     // see ResultLogManagerMXBean
    355     public String getLogFileName() {
    356         return getLogFileName(true);
    357     }
    358 
    359     // see ResultLogManagerMXBean
    360     public void clearLogs() throws IOException {
    361         clearMemoryLog();
    362         clearLogFile();
    363     }
    364 
    365     // Clear the memory log, sends a notification indicating that
    366     // the memory log was cleared.
    367     //
    368     private void clearMemoryLog()throws IOException {
    369         synchronized(this) {
    370             memoryLog.clear();
    371             memCapacityReached = false;
    372         }
    373         sendNotification(new Notification(MEMORY_LOG_CLEARED,
    374                 objectName,
    375                 getNextSeqNumber(),"memory log cleared"));
    376     }
    377 
    378     // Clears the log file.
    379     //
    380     private void clearLogFile() throws IOException {
    381         // simply force the creation of a new log file.
    382         checkLogFile(getLogFileName(),fileCapacity,true);
    383     }
    384 
    385     // Log a record to the memory log. Send a notification if the
    386     // maximum capacity of the memory log is reached.
    387     //
    388     private void logToMemory(ResultRecord record) {
    389 
    390         final boolean before = memCapacityReached;
    391         final boolean after;
    392         synchronized(this) {
    393             memoryLog.add(record);
    394             after = memCapacityReached;
    395         }
    396         if (before==false && after==true)
    397             sendNotification(new Notification(MEMORY_LOG_MAX_CAPACITY,
    398                     objectName,
    399                     getNextSeqNumber(),"memory log capacity reached"));
    400     }
    401 
    402 
    403     // Log a record to the memory log. Send a notification if the
    404     // maximum capacity of the memory log is reached.
    405     //
    406     private void logToFile(ResultRecord record) throws IOException {
    407         final String basename;
    408         final long   maxRecords;
    409         synchronized (this) {
    410             if (logFile == null) return;
    411             basename = getLogFileName(false);
    412             maxRecords = fileCapacity;
    413         }
    414 
    415         // Get the stream into which we should log.
    416         final OutputStream stream =
    417                 checkLogFile(basename,maxRecords,false);
    418 
    419         // logging to file now disabled - too bad.
    420         if (stream == null) return;
    421 
    422         synchronized (this) {
    423             try {
    424                 XmlConfigUtils.write(record,stream,true);
    425                 stream.flush();
    426                 // don't increment logCount if we were not logging in logStream.
    427                 if (stream == logStream) logCount++;
    428             } catch (JAXBException x) {
    429                 final IllegalArgumentException iae =
    430                         new IllegalArgumentException("bad record",x);
    431                 LOG.finest("Failed to log record: "+x);
    432                 throw iae;
    433             }
    434         }
    435     }
    436 
    437     /**
    438      * The notification type which indicates that the log file was switched:
    439      * <i>com.sun.jmx.examples.scandir.log.file.switched</i>.
    440      * The message contains the name of the new file (or null if log to file
    441      * is now disabled).
    442      **/
    443     public final static String LOG_FILE_CHANGED =
    444             "com.sun.jmx.examples.scandir.log.file.switched";
    445 
    446     /**
    447      * The notification type which indicates that the memory log capacity has
    448      * been reached:
    449      * <i>com.sun.jmx.examples.scandir.log.memory.full</i>.
    450      **/
    451     public final static String MEMORY_LOG_MAX_CAPACITY =
    452             "com.sun.jmx.examples.scandir.log.memory.full";
    453 
    454     /**
    455      * The notification type which indicates that the memory log was
    456      * cleared:
    457      * <i>com.sun.jmx.examples.scandir.log.memory.cleared</i>.
    458      **/
    459     public final static String MEMORY_LOG_CLEARED =
    460             "com.sun.jmx.examples.scandir.log.memory.cleared";
    461 
    462     /**
    463      * This MBean emits three kind of notifications:
    464      * <pre>
    465      *    <i>com.sun.jmx.examples.scandir.log.file.switched</i>
    466      *    <i>com.sun.jmx.examples.scandir.log.memory.full</i>
    467      *    <i>com.sun.jmx.examples.scandir.log.memory.cleared</i>
    468      * </pre>
    469      **/
    470     public MBeanNotificationInfo[] getNotificationInfo() {
    471         return new MBeanNotificationInfo[] {
    472             new MBeanNotificationInfo(new String[] {
    473                 LOG_FILE_CHANGED},
    474                     Notification.class.getName(),
    475                     "Emitted when the log file is switched")
    476                     ,
    477             new MBeanNotificationInfo(new String[] {
    478                 MEMORY_LOG_MAX_CAPACITY},
    479                     Notification.class.getName(),
    480                     "Emitted when the memory log capacity is reached")
    481                     ,
    482             new MBeanNotificationInfo(new String[] {
    483                 MEMORY_LOG_CLEARED},
    484                     Notification.class.getName(),
    485                     "Emitted when the memory log is cleared")
    486         };
    487     }
    488 
    489     // Return the name of the log file, or null if logging to file is
    490     // disabled.
    491     private String getLogFileName(boolean absolute) {
    492         synchronized (this) {
    493             if (logFile == null) return null;
    494             if (absolute) return logFile.getAbsolutePath();
    495             return logFile.getPath();
    496         }
    497     }
    498 
    499     // This method is be called by the ScanManagerMXBean when a configuration
    500     // is applied.
    501     //
    502     void setConfig(ResultLogConfig logConfigBean) throws IOException {
    503         if (logConfigBean == null)
    504             throw new IllegalArgumentException("logConfigBean is null");
    505         synchronized (this) {
    506             config = logConfigBean;
    507             setMemoryLogCapacity(config.getMemoryMaxRecords());
    508         }
    509         final String filename = config.getLogFileName();
    510         final String logname  = getLogFileName(false);
    511         if ((filename != null && !filename.equals(logname))
    512         || (filename == null && logname != null)) {
    513             newLogFile(config.getLogFileName(),
    514                     config.getLogFileMaxRecords());
    515         } else {
    516             setLogFileCapacity(config.getLogFileMaxRecords());
    517         }
    518     }
    519 
    520     // This method is called by the ScanManagerMXBean when
    521     // applyCurrentResultLogConfig() is called.
    522     //
    523     ResultLogConfig getConfig() {
    524         return config;
    525     }
    526 
    527 
    528     // Set by preRegister().
    529     private MBeanServer mbeanServer;
    530     private ObjectName objectName;
    531 
    532 
    533 
    534 }
    535