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