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