Changeset 5603


Ignore:
Timestamp:
Apr 8, 2011, 2:50:27 PM (10 years ago)
Author:
Nicklas Nordborg
Message:

References #1592: Unified installation procedure for plug-ins, extensions and more...
References #1593: Extension system for the core API

Added support for adding and removing extensions at runtime. There are still some minor issues to fix since it seems difficult to get the various processors to do exactly what is needed. There are also some issues when an updated version of an extension is installed over an old one.

Added a lot more statistics to both the manager and processors. It is now possible to get almost as much information as before.

Location:
trunk
Files:
9 added
6 edited
2 moved

Legend:

Unmodified
Added
Removed
  • trunk/src/clients/web/net/sf/basedb/clients/web/extensions/ExtensionsControl.java

    r5602 r5603  
    2525import java.util.Collections;
    2626import java.util.EnumSet;
    27 import java.util.Iterator;
    2827import java.util.List;
    2928import java.util.Set;
     
    5453import net.sf.basedb.util.extensions.Registry;
    5554import net.sf.basedb.util.extensions.events.EventType;
    56 import net.sf.basedb.util.extensions.manager.ExtensionsFile.WriteableExtensionsFile;
     55import net.sf.basedb.util.extensions.manager.ExtensionsFile;
    5756import net.sf.basedb.util.extensions.manager.ExtensionsManager;
    58 import net.sf.basedb.util.extensions.manager.ExtractResourcesProcessor;
    5957import net.sf.basedb.util.extensions.manager.ObjectKey;
    6058import net.sf.basedb.util.extensions.manager.ProcessResults;
    61 import net.sf.basedb.util.extensions.manager.RegisterExtensionsProcessor;
    62 import net.sf.basedb.util.extensions.xml.ExtensionPointFilter;
    63 import net.sf.basedb.util.extensions.xml.PathConverter;
    64 import net.sf.basedb.util.extensions.xml.VariableConverter;
    65 import net.sf.basedb.util.extensions.xml.XmlLoader;
     59import net.sf.basedb.util.extensions.manager.filter.DeletedFilter;
     60import net.sf.basedb.util.extensions.manager.filter.ValidAndNewOrModifiedFilter;
     61import net.sf.basedb.util.extensions.manager.processor.DeleteResourcesProcessor;
     62import net.sf.basedb.util.extensions.manager.processor.ExtractResourcesProcessor;
     63import net.sf.basedb.util.extensions.manager.processor.MarkAsProcessedProcessor;
     64import net.sf.basedb.util.extensions.manager.processor.UnregisterExtensionsProcessor;
    6665
    6766/**
     
    166165   
    167166    // Install extensions
    168     XmlLoader loader = new XmlLoader();
     167    //XmlLoader loader = new XmlLoader();
    169168    lastScanResults = new ProcessResults(false, false);
    170169   
    171170    // Filter that only load web extension points
    172     loader.setFilter(new ExtensionPointFilter("net\\.sf\\.basedb\\.clients\\.web\\..*"));
    173 
     171    //loader.setFilter(new ExtensionPointFilter("net\\.sf\\.basedb\\.clients\\.web\\..*"));
     172
     173    /*
    174174    final VariableConverter variableConverter = new VariableConverter();
    175175    variableConverter.setVariable("ROOT", servletContext.getContextPath());
     
    180180    loader.addValueConverter(variableConverter);
    181181    loader.addValueConverter(pathConverter);
    182 
     182    */
     183
     184    /*
    183185    manager.processFiles(new RegisterExtensionsProcessor(loader, false, lastScanResults)
    184186    {
     
    196198        super.processFile(manager, xtFile);
    197199      }
    198      
    199200    });
    200     manager.processFiles(new ExtractResourcesProcessor(new java.io.File(servletContext.getRealPath(RESOURCES_URL)), false, Pattern.compile("resources/(.*)"), "$1", lastScanResults));
     201    */
     202//    manager.processFiles(new ExtractResourcesProcessor(new java.io.File(servletContext.getRealPath(RESOURCES_URL)), false, Pattern.compile("resources/(.*)"), "$1", lastScanResults));
    201203    //lastScanResults = new ScanResults(true, false);
    202204    /*
     
    204206      servletContext, false, false);
    205207    */
     208   
     209    scan(true, false);
    206210
    207211    initialised = true;
     
    229233  }
    230234 
    231   /*
    232   private static synchronized void setLastScanResults(ScanResults scanResults)
    233   {
    234     lastScanResults = scanResults;
    235   }
    236   */
     235 
     236  private static synchronized ProcessResults scan(boolean initialScan, boolean forceUpdate)
     237  {
     238    String rootPath = servletContext.getContextPath();
     239    File resourceDir = new File(servletContext.getRealPath(RESOURCES_URL));
     240
     241    // Collect the results from this scan
     242    ProcessResults results = new ProcessResults(!initialScan, forceUpdate);
     243   
     244    // File statistics
     245    int numNewFiles = 0;
     246    int numModifiedFiles = 0;
     247    int numDeletedFiles = 0;
     248   
     249    // Extensions+extension points statistics
     250    int numUnregistered = 0;
     251    int numRegistered = 0;
     252   
     253    if (!initialScan)
     254    {
     255      // 1. Process files that has been deleted
     256      DeletedFilter deleted = new DeletedFilter();
     257     
     258      // 1a. Remove resources extracted from JAR files
     259      DeleteResourcesProcessor deleteResources = new DeleteResourcesProcessor(resourceDir, results);
     260      manager.processFiles(deleteResources, deleted);
     261     
     262      // 1b. Unregister all extensions from deleted files
     263      UnregisterExtensionsProcessor unregister = new UnregisterExtensionsProcessor(results);
     264      manager.processFiles(unregister, deleted);
     265      numUnregistered = unregister.getNumUnregistered();
     266     
     267      // 2. Update the manager with new and modified files
     268      manager.scanForNewAndUpdated();
     269      numNewFiles = manager.getNumNew();
     270      numModifiedFiles = manager.getNumModified();
     271      numDeletedFiles = manager.getNumDeleted();
     272    }
     273   
     274    if (initialScan || numNewFiles > 0 || numModifiedFiles > 0 || forceUpdate)
     275    {
     276      // Process files that are valid and new/updated (unless forceUpdate=true)
     277      ValidAndNewOrModifiedFilter valid = new ValidAndNewOrModifiedFilter(forceUpdate);
     278     
     279      // 3. Load extension definitions
     280      WebClientRegisterExtensionsProcessor registerExtensions =
     281        new WebClientRegisterExtensionsProcessor(rootPath, rootPath + RESOURCES_URL, rootPath + SERVLET_URL, results);
     282      manager.processFiles(registerExtensions, valid);
     283
     284      // 4. Extract web resources
     285      ExtractResourcesProcessor extractResources = new ExtractResourcesProcessor(resourceDir, forceUpdate, Pattern.compile("resources/(.*)"), "$1", results);
     286      manager.processFiles(extractResources, valid);
     287
     288      // 5. Servlets
     289     
     290      // 6. Actual registration of all extensions
     291      registerExtensions.finalizeRegistration(manager);
     292      numRegistered = registerExtensions.getNumRegistered();
     293    }
     294   
     295    // Reset the 'last modifed' status of all files and generated summary per file
     296    manager.processFiles(new SetFileStatusProcessor(results));
     297    results.setEnded();
     298   
     299    // Generate summary message, etc.
     300    StringBuilder summary = new StringBuilder();
     301    if (numDeletedFiles > 0)
     302    {
     303      summary.append(numDeletedFiles).append(" deleted extension file(s) containing ");
     304      summary.append(numUnregistered).append(" extension(s)\n");
     305    }
     306    summary.append(numNewFiles).append(" new extension file(s)\n");
     307    summary.append(numModifiedFiles).append(" updated extension file(s)\n");
     308    summary.append(numRegistered).append(" extension(s) registered\n");
     309    if (results.getNumErrorFiles() > 0)
     310    {
     311      summary.append(results.getNumErrorFiles()).append(" file(s) with errors");
     312    }
     313    results.setSummary(summary.toString());
     314
     315    lastScanResults = results;
     316    return results;
     317  }
     318 
    237319 
    238320
     
    429511      have WRITE permission
    430512  */
    431   public ScanResults installAndUpdateExtensions(boolean forceUpdate)
    432   {
    433     /*
     513  public ProcessResults installAndUpdateExtensions(boolean forceUpdate)
     514  {
    434515    checkPermission(Permission.WRITE, "extensions");
    435     ScanResults results =
    436       extensionsDir.installAndUpdateExtensions(registry, servletContext, true, forceUpdate);
    437     setLastScanResults(results);
    438     return results;
    439     */
    440     return new ScanResults(true, false);
     516    return scan(false, forceUpdate);
    441517  }
    442518 
     
    606682    extensions.
    607683  */
    608   public List<net.sf.basedb.util.extensions.manager.ExtensionsFile> getFiles()
     684  public List<ExtensionsFile> getFiles()
    609685  {
    610686    return manager.getFiles();
     
    617693      the given file doesn't exists or isn't an extensions file
    618694  */
    619   public net.sf.basedb.util.extensions.manager.ExtensionsFile getFile(String fileuri)
     695  public ExtensionsFile getFile(String fileuri)
    620696  {
    621697    return manager.getFileByURI(fileuri);
     
    629705      or null if no extension with the given ID is found
    630706  */
    631   public net.sf.basedb.util.extensions.manager.ExtensionsFile getFileByObjectKey(ObjectKey key)
     707  public ExtensionsFile getFileByObjectKey(ObjectKey key)
    632708  {
    633709    return manager.getFileByObjectKey(key);
     
    644720  {
    645721    checkPermission(Permission.WRITE, "File[" + filename + "]");
     722    /*
    646723    ExtensionsFile file = null; //getFile(filename);
    647724    if (file == null)
     
    669746      if (enable) registry.handleEvent(AFTER_ENABLE, ep, ext);
    670747    }
     748    */
    671749  }
    672750 
  • trunk/src/core/net/sf/basedb/core/Application.java

    r5602 r5603  
    3838import net.sf.basedb.util.extensions.manager.ExtensionsManager;
    3939import net.sf.basedb.util.extensions.manager.ProcessResults;
    40 import net.sf.basedb.util.extensions.manager.RegisterExtensionsProcessor;
     40import net.sf.basedb.util.extensions.manager.processor.MarkAsProcessedProcessor;
     41import net.sf.basedb.util.extensions.manager.processor.RegisterExtensionsProcessor;
    4142import net.sf.basedb.util.extensions.xml.ExtensionPointFilter;
    4243import net.sf.basedb.util.extensions.xml.XmlLoader;
     
    548549        // Filter that only load core extension points
    549550        loader.setFilter(new ExtensionPointFilter("net\\.sf\\.basedb\\.core\\..*"));
    550         xtManager.processFiles(new RegisterExtensionsProcessor(loader, false, results));
    551        
     551        xtManager.processFiles(new RegisterExtensionsProcessor(loader, results));
     552        //xtManager.processFiles(new MarkAsProcessedProcessor());
    552553       
    553554        // Initialise log manager factory
  • trunk/src/core/net/sf/basedb/util/extensions/manager/ExtensionsFile.java

    r5602 r5603  
    3434import java.util.Map;
    3535import java.util.concurrent.TimeUnit;
    36 import java.util.concurrent.locks.Lock;
    37 import java.util.concurrent.locks.ReadWriteLock;
    3836import java.util.concurrent.locks.ReentrantReadWriteLock;
    3937import java.util.zip.ZipEntry;
     
    7270  private final boolean isJar;
    7371  private final String name;
    74   private final ReadWriteLock rwLock;
     72  private final ReentrantReadWriteLock rwLock;
    7573  private final Map<ObjectKey, Object> allObjects;
    7674  private final Map<ObjectKey, Object> objectMetadata;
    7775 
     76  private boolean isNew;
     77  private boolean wasModified;
     78  private long lastModified;
     79  private long lastLength;
    7880  private JarClassLoader jarLoader;
    79   private long lastModified;
    80   private long lastSize;
    8181  private About about;
    8282  private boolean hasError;
     
    128128    this.name = name;
    129129    this.isJar = isJar;
     130    this.isNew = true;
     131    this.wasModified = true;
    130132    this.rwLock = new ReentrantReadWriteLock();
    131133    this.allObjects = new LinkedHashMap<ObjectKey, Object>();
     
    207209 
    208210  /**
    209     Check if the file really exists on the disk. This method always
    210     return false for extension files defined by an URI.
     211    Check if the file exists. For files that are only represented by
     212    an URI this method always return true. For other files the call
     213    is forwarded to {@link File#exists()} and {@link File#isFile()}.
    211214    @return See {@link File#exists()}
    212215  */
    213216  public boolean exists()
    214217  {
    215     return file != null && file.exists() && file.isFile();
    216   }
    217 
    218   /**
    219     Check if the underlying file has been modified since it was last processed.
    220     We consider the file to be modified if the timestamp ({@link File#lastModified()})
    221     or size ({@link File#length()}) has changed, or if the underlying class loader
    222     has detected a change in any files it depends on (see {@link JarClassLoader#hasChanged(boolean)}).
    223     @return TRUE if the file has been modified, FALSE otherwise or if the extension is defined by an URI
    224     @see #resetModified()
    225   */
    226   public boolean isModified()
    227   {
    228     return file != null && (lastModified != file.lastModified() ||
    229       lastSize != file.length() ||
    230       (jarLoader != null && jarLoader.hasChanged(true)));
    231   }
    232  
    233   /**
    234     Resets the remembered timestamp and size attributes of the file.
    235     @see #isModified()
    236   */
    237   public void resetModified()
    238   {
    239     if (file == null) return;
    240     lastModified = file.lastModified();
    241     lastSize = file.length();
    242   }
    243 
     218    return file == null || (file.exists() && file.isFile());
     219  }
     220
     221  /**
     222    If the file is a new file not previously processed by the
     223    extension system. Thew 'new' status is changed when
     224    {@link WriteableExtensionsFile#markAsProcessed()} is called which
     225    usually happens when all extensions has been registered.
     226    @return TRUE if the file is a new file, FALSE otherwise
     227  */
     228  public boolean isNew()
     229  {
     230    return isNew;
     231  }
     232
     233  /**
     234    Check if the file was modified when the last call to
     235    {@link #checkModified()} was made. It is recommended
     236    that processor implementation use this method instead
     237    to avoid inconsistent behaviour if the file happens to
     238    be modified while a processor is running.
     239   
     240    @return TRUE if the file was modified, FALSE otherwise
     241  */
     242  public boolean wasModified()
     243  {
     244    return wasModified;
     245  }
     246 
     247  /**
     248    Check if the underlying file has been modified since it was
     249    last processed. Files that are new are always considered to be
     250    modified. Files that are only represented as an URI are considered
     251    to be unmodifiable. For other files, we check the
     252    {@link File#lastModified()} and {@link File#length()} of the
     253    underlying file and compare that to the last known timestamp and size.
     254    Files that are JAR files we also check with the class loader if any of
     255    the libraries it uses have changed.
     256    <p>
     257    The result of this call is remembered until this method is called
     258    again or until the file is marked as processed.
     259   
     260    @see WriteableExtensionsFile#markAsProcessed()
     261    @see #wasModified()
     262  */
     263  public boolean checkModified()
     264  {
     265    wasModified = false; // Assume not modified as a starting point
     266    if (isNew)
     267    {
     268      // New files are always modified
     269      wasModified = true;
     270    }
     271    else if (file != null && (lastModified != file.lastModified() || lastLength != file.length()))
     272    {
     273      // The XML or JAR file has changed
     274      wasModified = true;
     275    }
     276    else if (isJar && jarLoader != null && jarLoader.hasChanged(true))
     277    {
     278      // JAR files have changed if the class loader detect changes
     279      wasModified = true;
     280    }
     281    return wasModified;
     282  }
     283 
     284  /**
     285    Is the file a valid extensions file according to the last
     286    performed validation.
     287   
     288    This means that the file must be either:
     289    <ul>
     290    <li>An extensions definition XML file
     291    <li>A JAR file with an extensions definition XML file at
     292      <code>META-INF/extensions.xml</code>.
     293    </ul>
     294   
     295    The result of the validation is remembered as long as the
     296    file is not modified. If {@link #checkModified()} returns
     297    true, the return value of this method may be out of sync.
     298    Re-validation of modified files are usually performed
     299    by the extensions manager by calling
     300    {@link ExtensionsManager#scanForNewAndUpdated()}
     301   
     302    <p>
     303    NOTE! The validation will only verify that the XML file
     304    follows the rules defined by the schema definition. It will
     305    not check that actual classes, etc. exists and can be created.
     306    This usually happens later in the registration process, and
     307    can be checked by {@link #hasError()}.
     308   
     309    @return TRUE if the file is valid, FALSE otherwise
     310    @see #getValidationError()
     311    @see #checkModified()
     312    @see #hasError()
     313  */
     314  public boolean isValid()
     315  {
     316    return isValid;
     317  }
     318
     319  /**
     320    Get more information about the error that caused the validation
     321    to fail.
     322    @return An exception or null if the validation succeeded
     323    @see #isValid()
     324  */
     325  public Throwable getValidationError()
     326  {
     327    return validationError;
     328  }
     329 
     330  /**
     331    Get information about the extensions in this file.
     332    @return An About object or null if the file is not a valid
     333      extensions file
     334  */
     335  public About getAbout()
     336  {
     337    return about;
     338  }
     339
     340  /**
     341    Get an input stream for reading the XML file containing the extension
     342    definitions.
     343    @return An input stream
     344    @throws IOException
     345  */
    244346  public InputStream getXmlStream()
    245347    throws IOException
     
    274376    return in;
    275377  }
    276  
    277   /**
    278     Checks if the file is a valid extensions definition file. This means that
    279     the file must be either:
    280     <ul>
    281     <li>An extensions definition XML file
    282     <li>A JAR file with an extensions definition XML file at
    283       <code>META-INF/extensions.xml</code>.
    284     </ul>
    285    
    286     The result of the validation is remembered as long as the
    287     file is not modified. If {@link #isModified()} returns
    288     true, a call to this method will result in a re-validation.
    289    
    290     @return TRUE if the file is valid, FALSE otherwise
    291     @see #getValidationError()
    292     @see #isModified()
    293   */
    294   public boolean isValid()
    295   {
    296     if (!hasValidated || isModified())
    297     {
    298       try
    299       {
    300         validate();
    301       }
    302       catch (Exception ex)
    303       {}
    304     }
    305     return isValid;
    306   }
    307 
    308   /**
    309     Get more information about the error that caused the validation
    310     to fail.
    311     @return An exception or null if the validation succeeded
    312   */
    313   public Throwable getValidationError()
    314   {
    315     return validationError;
    316   }
    317 
    318   public About getAbout()
    319   {
    320     return about;
    321   }
     378
     379  /**
     380    Validate the XML file with the extension definitions.
     381  */
     382  synchronized void validate()
     383  {
     384    if (hasValidated) return;
     385   
     386    log.info("Validating extensions in file: " + this);
     387    hasValidated = false;
     388    isValid = false;
     389    validationError = null;
     390
     391    InputStream in = null;
     392    try
     393    {
     394      in = getXmlStream();
     395      about = new XmlLoader().validateXmlFile(in, getName());
     396    }
     397    catch (RuntimeException ex)
     398    {
     399      log.error("Error validating extensions in file: " + this, ex);
     400      validationError = ex;
     401      throw ex;
     402    }
     403    catch (Throwable t)
     404    {
     405      log.error("Error validating extensions in file: " + this, t);
     406      validationError = t;
     407      throw new RuntimeException(t);
     408    }
     409    finally
     410    {
     411      FileUtil.close(in);
     412      hasValidated = true;
     413    }
     414    log.info("Successfully validated extensions in file: " + this);
     415    isValid = true;
     416  }
     417
    322418 
    323419  /**
     
    347443  {
    348444    List<Object> copy = new ArrayList<Object>();
    349     boolean lock = readLock();
    350445    try
    351446    {
    352       if (lock)
     447      if (readLock())
    353448      {
    354449        copy.addAll(allObjects.values());
     
    357452    finally
    358453    {
    359       if (lock) rwLock.readLock().unlock();
     454      rwLock.readLock().unlock();
    360455    }
    361456    return copy;
     
    376471  {
    377472    List<T> matching = new ArrayList<T>();
    378     Lock lock = null;
    379473    try
    380474    {
     
    389483    finally
    390484    {
    391       if (lock != null) lock.unlock();
     485      rwLock.readLock().unlock();
    392486    }
    393487    return matching;
     
    426520  }
    427521 
    428   private synchronized void validate()
    429   {
    430     if (hasValidated && !isModified()) return;
    431     log.info("Validating extensions in file: " + getName());
    432     hasValidated = false;
    433     isValid = false;
    434     validationError = null;
    435 
    436     InputStream in = null;
    437     try
    438     {
    439       in = getXmlStream();
    440       about = new XmlLoader().validateXmlFile(in, getName());
    441     }
    442     catch (RuntimeException ex)
    443     {
    444       log.error("Error validating extensions in file: " + getName(), ex);
    445       validationError = ex;
    446       throw ex;
    447     }
    448     catch (Throwable t)
    449     {
    450       log.error("Error validating extensions in file: " + getName(), t);
    451       validationError = t;
    452       throw new RuntimeException(t);
    453     }
    454     finally
    455     {
    456       FileUtil.close(in);
    457       hasValidated = true;
    458     }
    459     log.info("Extensions in file are valid: " + getName());
    460     isValid = true;
    461   }
    462522
    463523
     
    540600    public void close()
    541601    {
     602      System.out.println("close(): " + isClosed);
    542603      if (isClosed) return;
    543604      isClosed = true;
     
    547608    public boolean open()
    548609    {
     610      System.out.println("open(): " + isClosed);
    549611      if (isClosed)
    550612      {
    551613        isClosed = !xtFile.writeLock();
    552614      }
     615      System.out.println("open2(): " + isClosed);
    553616      return !isClosed;
    554617    }
     
    563626      {
    564627        throw new IllegalStateException("The file has been closed: " + xtFile);
     628      }
     629    }
     630   
     631    /**
     632      Mark the file as fully processed by the extensions manager.
     633    */
     634    public void markAsProcessed()
     635    {
     636      xtFile.isNew = false;
     637      xtFile.wasModified = false;
     638      if (xtFile.file != null)
     639      {
     640        xtFile.lastModified = xtFile.file.lastModified();
     641        xtFile.lastLength = xtFile.file.length();
    565642      }
    566643    }
  • trunk/src/core/net/sf/basedb/util/extensions/manager/ExtensionsManager.java

    r5602 r5603  
    2626import java.net.URI;
    2727import java.util.ArrayList;
     28import java.util.Collection;
    2829import java.util.HashMap;
    2930import java.util.HashSet;
     31import java.util.Iterator;
    3032import java.util.List;
    3133import java.util.Map;
     
    3638import net.sf.basedb.util.extensions.Registry;
    3739import net.sf.basedb.util.extensions.manager.ExtensionsFile.WriteableExtensionsFile;
     40import net.sf.basedb.util.filter.Filter;
    3841
    3942/**
     
    5558
    5659  // Manually added ignore files
    57   private final Set<File> staticIgnore;
    58   // Automatically ignored files (due to validation errors, etc.) + statically ignored files
    59   private final Set<ExtensionsFile> ignore;
     60  private final Set<File> ignore;
     61  // Directories to scan for new/deleted/modified extensions
     62  private final Set<File> directories;
    6063  // Valid extension files
    6164  private final Map<URI, ExtensionsFile> xtFiles;
    62  
    6365  // Map objects to the file they are defined in. The key is a unique object id.
    6466  private final Map<ObjectKey, ExtensionsFile> installedObjects;
    6567 
     68  private int numNew;
     69  private int numModified;
     70  private int numUnmodified;
     71  private int numDeleted;
     72 
    6673  /**
    6774    Create a new extensions manager and use the given registry
     
    7380  {
    7481    this.registry = registry;
    75     this.staticIgnore = new HashSet<File>();
    76     this.ignore = new HashSet<ExtensionsFile>();
     82    this.ignore = new HashSet<File>();
     83    this.directories = new HashSet<File>();
    7784    this.xtFiles = new TreeMap<URI, ExtensionsFile>();
    7885    this.installedObjects = new HashMap<ObjectKey, ExtensionsFile>();
     
    98105  {
    99106    if (file == null) return;
    100     ignore.add(new ExtensionsFile(this, file));
    101     staticIgnore.add(file);
     107    ignore.add(file);
    102108  }
    103109 
     
    110116  {
    111117    if (file == null) return;
    112     ignore.remove(new ExtensionsFile(this, file));
    113     staticIgnore.remove(file);
    114   }
    115 
    116  
    117   public void addDirectory(File dir)
     118    ignore.remove(file);
     119  }
     120
     121  /**
     122    Add a directory to this manager. The directory is automatically
     123    scanned for files with .xml or .jar extensions which are
     124    added as extension files. This method call is ignored if the
     125    path is not pointing to an existing directory.
     126   
     127    @param dir An existing directory
     128    @return The number of files in the directory that was
     129      not previously known to the manager
     130  */
     131  public int addDirectory(File dir)
    118132  {
    119133    if (dir == null) throw new NullPointerException("dir");
    120     if (!dir.isDirectory())
    121     {
    122       log.warn("File '" + dir + "' is not a directory. Ignoring.");
    123       return;
    124     }
    125    
    126     // Search for .xml or .jar files; don't search subdirectories
    127     FileFilter filter = new RegexpFileFilter(".*\\.(xml|jar)", "");
    128     for (File file : dir.listFiles(filter))
    129     {
    130       addFile(file);
    131     }
    132 
     134    directories.add(dir);
     135    int numNew = scanForNewFiles(dir);
     136    return numNew;
    133137  }
    134138 
     
    140144   
    141145    @param file An extensions file (null is not allowed)
    142   */
    143   public void addFile(File file)
     146    @return The extension file object representing the added file
     147  */
     148  public ExtensionsFile addFile(File file)
    144149  {
    145150    if (file == null) throw new NullPointerException("file");
    146     addExtensionsFile(new ExtensionsFile(this, file));
     151    ExtensionsFile xtFile = new ExtensionsFile(this, file);
     152    addExtensionsFile(xtFile);
     153    return xtFile;
    147154  }
    148155 
     
    158165   
    159166    @param uri An URI pointing to an extensions file (null is not allowed)
    160   */
    161   public void addURI(URI uri)
     167    @return The extension file object representing the added uri
     168  */
     169  public ExtensionsFile addURI(URI uri)
    162170  {
    163171    if (uri == null) throw new NullPointerException("uri");
    164     addExtensionsFile(new ExtensionsFile(this, uri));
    165   }
    166 
     172    ExtensionsFile xtFile = new ExtensionsFile(this, uri);
     173    addExtensionsFile(xtFile);
     174    return xtFile;
     175  }
     176
     177  /*
     178  public void removeFile(ExtensionsFile xtFile)
     179  {
     180    xtFiles.remove(xtFile.getURI());
     181    //ignore.remove(xtFile);
     182    Iterator<ExtensionsFile> it = installedObjects.values().iterator();
     183    while (it.hasNext())
     184    {
     185      if (it.next().equals(xtFile)) it.remove();
     186    }
     187  }
     188  */
     189 
    167190  private void addExtensionsFile(ExtensionsFile xtFile)
    168191  {
    169     if (ignore.contains(xtFile))
    170     {
    171       log.info("File '" + xtFile + "' is ignored.");
    172       return;
    173     }
    174    
    175     if (xtFile.isValid())
    176     {
    177       xtFiles.put(xtFile.getURI(), xtFile);
     192    xtFile.validate();
     193    xtFiles.put(xtFile.getURI(), xtFile);
     194    log.info("Added file: " + xtFile);
     195  }
     196 
     197  /**
     198    Scan the managed directories for new/updated and deleted files.
     199    Files that have been deleted are simply removed from the manager.
     200    Any actions that are needed when a file has been deleted must be
     201    done before calling this method. Eg. call
     202    {@link #processFiles(ExtensionsFileProcessor)} with a processor
     203    implementation that checks {@link ExtensionsFile#exists()}.
     204
     205    @return The number of new + modified files that was found
     206  */
     207  public int scanForNewAndUpdated()
     208  {
     209    numNew = 0;
     210    numModified = 0;
     211    numDeleted = 0;
     212    numUnmodified = 0;
     213   
     214    // 1. Check all known files -- remove deleted and count modified
     215    Iterator<ExtensionsFile> it = xtFiles.values().iterator();
     216    while (it.hasNext())
     217    {
     218      ExtensionsFile xtFile = it.next();
     219      if (!xtFile.exists())
     220      {
     221        log.info("File '" + xtFile + "' has been deleted.");
     222        it.remove();
     223        numDeleted++;
     224      }
     225      else if (xtFile.checkModified())
     226      {
     227        xtFile.validate();
     228        numModified++;
     229      }
     230      else
     231      {
     232        numUnmodified++;
     233      }
     234    }
     235   
     236    // Scan the managed directories for new files
     237    for (File dir : directories)
     238    {
     239      numNew += scanForNewFiles(dir);
     240    }
     241    return numModified + numNew;
     242  }
     243 
     244  /**
     245    Get the number of new files that was found in the last
     246    call to {@link #scanForNewAndUpdated()}.
     247  */
     248  public int getNumNew()
     249  {
     250    return numNew;
     251  }
     252
     253  /**
     254    Get the number of modified files that was found in the last
     255    call to {@link #scanForNewAndUpdated()}.
     256  */
     257  public int getNumModified()
     258  {
     259    return numModified;
     260  }
     261
     262  /**
     263    Get the number of unmodified files that was found in the last
     264    call to {@link #scanForNewAndUpdated()}.
     265  */
     266  public int getNumUnmodified()
     267  {
     268    return numUnmodified;
     269  }
     270 
     271  /**
     272    Get the number of deleted files that was found in the last
     273    call to {@link #scanForNewAndUpdated()}.
     274  */
     275  public int getNumDeleted()
     276  {
     277    return numDeleted;
     278  }
     279 
     280  /**
     281    Scan the given directory and add files that are new.
     282    @return The number of new files in the given directory
     283  */
     284  private int scanForNewFiles(File dir)
     285  {
     286    int numNew = 0;
     287   
     288    if (!dir.isDirectory())
     289    {
     290      log.warn("File '" + dir + "' is not a directory. Ignoring.");
     291      return numNew;
     292    }
     293
     294    log.info("Scanning directory: " + dir);
     295    FileFilter filter = new RegexpFileFilter(".*\\.(xml|jar)", "");
     296    File[] files = dir.listFiles(filter);
     297   
     298    if (files != null && files.length > 0)
     299    {
     300      log.info("Found " + files.length + " file(s)");
     301      for (File file : files)
     302      {
     303        if (ignore.contains(file))
     304        {
     305          log.info("File '" + file + "' is ignored.");
     306          continue; // with the next file
     307        }
     308       
     309        URI uri = file.toURI();
     310        ExtensionsFile xtFile = xtFiles.get(uri);
     311        if (xtFile != null)
     312        {
     313          log.info("File '" + xtFile + "' is a known file.");
     314        }
     315        else
     316        {
     317          xtFile = new ExtensionsFile(this, file);
     318          addExtensionsFile(xtFile);
     319          numNew++;
     320        }
     321      }
    178322    }
    179323    else
    180324    {
    181       ignore.add(xtFile);
    182     }
     325      log.info("Found no files");
     326    }
     327   
     328    return numNew;
    183329  }
    184330 
     
    235381 
    236382  /**
     383    Unregister an object.
     384    @param key The object key
     385  */
     386  void unregisterObject(ObjectKey key)
     387  {
     388    installedObjects.remove(key);
     389  }
     390 
     391  public void processFiles(ExtensionsFileProcessor processor)
     392  {
     393    processFiles(processor, null);
     394  }
     395 
     396  /**
    237397    Process all known extension files with the given processor.
     398   
    238399    @param processor A processor implementation, null is not allowed
    239   */
    240   public void processFiles(ExtensionsFileProcessor processor)
     400    @param filter An optional filter implementation that can be used to
     401      limit which files are sent to the processor
     402  */
     403  public void processFiles(ExtensionsFileProcessor processor, Filter<ExtensionsFile> filter)
    241404  {
    242405    if (processor == null) throw new NullPointerException("processor");
    243     List<WriteableExtensionsFile> writeableFiles = new ArrayList<WriteableExtensionsFile>(xtFiles.size());
     406
     407    // 1. Filter files with the given filter
     408    Collection<ExtensionsFile> filtered = null;
     409    if (filter != null)
     410    {
     411      filtered = new ArrayList<ExtensionsFile>(xtFiles.size());
     412      log.debug("Filtering files with filter: " + filter);
     413      for (ExtensionsFile xtFile : xtFiles.values())
     414      {
     415        if (filter.evaluate(xtFile))
     416        {
     417          filtered.add(xtFile);
     418        }
     419        else
     420        {
     421          log.debug("File blocked by filter: " + xtFile);
     422        }
     423      }
     424    }
     425    else
     426    {
     427      filtered = xtFiles.values();
     428    }
     429   
     430    // 2. Process the remaining files
     431    int numFiles = filtered.size();
     432    List<WriteableExtensionsFile> writeableFiles = new ArrayList<WriteableExtensionsFile>(numFiles);
    244433    try
    245434    {
    246       log.debug("Processing " + xtFiles.size() + " file(s) with processor: " + processor);
    247       processor.begin(this, xtFiles.size());
    248       for (ExtensionsFile xtFile : xtFiles.values())
     435      log.debug("Processing " + numFiles + " file(s) with processor: " + processor);
     436      processor.begin(this, numFiles);
     437      for (ExtensionsFile xtFile : filtered)
    249438      {
    250439        log.debug("Processing file: " + xtFile);
  • trunk/src/core/net/sf/basedb/util/extensions/manager/ProcessResults.java

    r5602 r5603  
    100100 
    101101  /**
     102    Set the end time of the scan.
     103  */
     104  public void setEnded()
     105  {
     106    this.endTime = System.currentTimeMillis();
     107  }
     108
     109 
     110  /**
    102111    Get a summary of the scan results as a textual description.
    103112  */
     
    151160  }
    152161
     162  /**
     163    Sets a short overall status message for an entire file. Example:
     164    "Installed", "Failed", etc.
     165    @param xtFile The file to set the status on
     166    @param status The status message
     167    @see #addMessage(ExtensionsFile, String)
     168    @see #addErrorMessage(ExtensionsFile, String)
     169  */
     170  public void setStatus(ExtensionsFile xtFile, String status)
     171  {
     172    FileResults m = fileResults.get(xtFile);
     173    if (m == null)
     174    {
     175      m = new FileResults(xtFile);
     176      fileResults.put(xtFile, m);
     177    }
     178    m.status = status;
     179  }
     180 
    153181  /**
    154182    Adds a detailed message indicating a successful operation
  • trunk/src/core/net/sf/basedb/util/extensions/manager/processor/ExtractResourcesProcessor.java

    r5602 r5603  
    2020  along with BASE. If not, see <http://www.gnu.org/licenses/>.
    2121*/
    22 package net.sf.basedb.util.extensions.manager;
     22package net.sf.basedb.util.extensions.manager.processor;
    2323
    2424import java.io.File;
     
    3333
    3434import net.sf.basedb.util.FileUtil;
     35import net.sf.basedb.util.extensions.manager.ExtensionsFile;
     36import net.sf.basedb.util.extensions.manager.ExtensionsFileProcessor;
     37import net.sf.basedb.util.extensions.manager.ExtensionsManager;
     38import net.sf.basedb.util.extensions.manager.ProcessResults;
    3539import net.sf.basedb.util.extensions.manager.ExtensionsFile.WriteableExtensionsFile;
     40import net.sf.basedb.util.extensions.manager.filter.ValidAndNewOrModifiedFilter;
    3641
    3742/**
     
    4752  The filter is also used to re-write target file names if they
    4853  need to be different from the file names in the JAR file.
     54  <p>
     55 
     56  Note! It is recommended that this processor is paired with a
     57  {@link ValidAndNewOrModifiedFilter} since it is usually no
     58  point in extracting resources from invalid extensions.
    4959
    5060  @author Nicklas
     
    6575  private final ProcessResults results;
    6676 
     77  private int numFiles;
     78  private int numError;
     79  private int numTotalExtracted;
     80
    6781  /**
    6882    Create a new processor.
     
    104118  @Override
    105119  public void begin(ExtensionsManager manager, int numFiles)
    106   {}
    107 
    108   @Override
    109   public void processFile(ExtensionsManager manager, WriteableExtensionsFile writeableFile)
    110   {
    111     ExtensionsFile xtFile = writeableFile.getExtensionsFile();
     120  {
     121    this.numFiles = 0;
     122    this.numError = 0;
     123    this.numTotalExtracted = 0;
     124  }
     125
     126  @Override
     127  public void processFile(ExtensionsManager manager, WriteableExtensionsFile wFile)
     128  {
     129    ExtensionsFile xtFile = wFile.getExtensionsFile();
    112130    if (!xtFile.isJar())
    113131    {
    114       log.info("File is not a JAR file (skipping extraction): " + xtFile);
     132      log.info("File is not a JAR file (skipping): " + xtFile);
    115133      return;
    116134    }
    117    
    118     // Do not extract from files with an error
    119     if (xtFile.hasError())
    120     {
    121       log.info("File has errors (skipping extraction): " + xtFile);
    122       return;
    123     }
    124    
    125     // Skip the file not modified (unless forced to extract)
    126     if (!forceOverwrite && !xtFile.isModified())
    127     {
    128       log.info("File is unmodified (skipping extraction): " + xtFile);
    129       return;
    130     }
    131    
     135
    132136    File jar = xtFile.getFile();
    133137    File targetDir = new File(mainDir, jar.getName());
     
    137141    log.debug("filter=" + filter + "; replacement=" + replacement);
    138142
     143    ZipInputStream zipStream = null;
    139144    int numExtracted = 0;
    140     ZipInputStream zipStream = null;
    141145    try
    142146    {
     
    219223        }
    220224      }
     225      numFiles++;
     226      numTotalExtracted += numExtracted;
     227      if (numExtracted > 0 && results != null)
     228      {
     229        results.addMessage(xtFile,  numExtracted + " resource(s) extracted successfully.");
     230      }
    221231      log.info("Extracted " + numExtracted + " resources from '" + jar.getName() + "' to " + targetDir);
    222       results.addMessage(xtFile,  numExtracted + " resources extracted successfully.");
    223232    }
    224233    catch (Exception ex)
    225234    {
    226       String msg = "Failed to extract resources from '" + jar.getName() + "' to " + targetDir;
    227       log.error(msg, ex);
    228       writeableFile.setError(true);
    229       results.addErrorMessage(xtFile, msg);
     235      wFile.setError(true);
     236      numError++;
     237      if (results != null)
     238      {
     239        results.addErrorMessage(xtFile, "Failed to extract resources: " + ex.getMessage());
     240      }
     241      log.error("Failed to extract resources to directory: " + targetDir, ex);
    230242    }
    231243    finally
     
    243255  {}
    244256  // ---------------------------------------------------
     257 
     258  /**
     259    Get the number of files that was successfully processed.
     260  */
     261  public int getNumFiles()
     262  {
     263    return numFiles;
     264  }
     265 
     266  /**
     267    Get the number of files that was had an error.
     268  */
     269  public int getNumError()
     270  {
     271    return numError;
     272  }
     273 
     274  /**
     275    Get the total number of resources that was extracts.
     276  */
     277  public int getNumExtracted()
     278  {
     279    return numTotalExtracted;
     280  }
     281
     282 
    245283}
  • trunk/src/core/net/sf/basedb/util/extensions/manager/processor/RegisterExtensionsProcessor.java

    r5602 r5603  
    2020  along with BASE. If not, see <http://www.gnu.org/licenses/>.
    2121*/
    22 package net.sf.basedb.util.extensions.manager;
     22package net.sf.basedb.util.extensions.manager.processor;
    2323
    2424import java.io.InputStream;
     
    3131import net.sf.basedb.util.extensions.ExtensionPoint;
    3232import net.sf.basedb.util.extensions.Registry;
     33import net.sf.basedb.util.extensions.manager.ExtensionKey;
     34import net.sf.basedb.util.extensions.manager.ExtensionPointKey;
     35import net.sf.basedb.util.extensions.manager.ExtensionsFile;
     36import net.sf.basedb.util.extensions.manager.ExtensionsFileProcessor;
     37import net.sf.basedb.util.extensions.manager.ExtensionsManager;
     38import net.sf.basedb.util.extensions.manager.FactoryParametersKey;
     39import net.sf.basedb.util.extensions.manager.ProcessResults;
    3340import net.sf.basedb.util.extensions.manager.ExtensionsFile.WriteableExtensionsFile;
     41import net.sf.basedb.util.extensions.manager.filter.ValidAndNewOrModifiedFilter;
    3442import net.sf.basedb.util.extensions.xml.XmlLoader;
    3543
     
    4553  {@link ExtensionsManager#processFiles(ExtensionsFileProcessor)}.
    4654  The second step is either automatically executed when all files
    47   have been processed or executed by by calling {@link #registerExtensions(ExtensionsManager)}.
     55  have been processed or executed by by calling
     56  {@link #finalizeRegistration(ExtensionsManager)}.
     57  <p>
     58  Note! It is recommended that this processor is paired with a
     59  {@link ValidAndNewOrModifiedFilter} since it is usually no
     60  point in trying to register invalid extensions.
     61
    4862
    4963  @author Nicklas
     
    6175  private final XmlLoader loader;
    6276  private final ProcessResults results;
    63   private final boolean delayRegistration;
     77  private boolean delayRegistration;
    6478  private List<FileData> allData;
    65  
     79
     80  private int numFiles;
     81  private int numError;
     82  private int numRegistered;
     83
    6684  /**
    6785    Create a new processor.
    6886   
    6987    @param loader The XML loader to use when parsing the metadata
    70     @param delayRegistration If TRUE, the actual registriation of the extensions
    71       are delayed until {@link #registerExtensions(ExtensionsManager)} is called
    72   */
    73   public RegisterExtensionsProcessor(XmlLoader loader, boolean delayRegistration, ProcessResults results)
     88  */
     89  public RegisterExtensionsProcessor(XmlLoader loader, ProcessResults results)
    7490  {
    7591    this.loader = loader;
    76     this.delayRegistration = delayRegistration;
    7792    this.results = results;
    7893  }
     
    86101  {
    87102    this.allData = new ArrayList<FileData>(numFiles);
     103    this.numFiles = 0;
     104    this.numError = 0;
     105    this.numRegistered = 0;
    88106  }
    89107
    90108  @Override
    91   public void processFile(ExtensionsManager manager, WriteableExtensionsFile writeableFile)
     109  public void processFile(ExtensionsManager manager, WriteableExtensionsFile wFile)
    92110  {
    93111    InputStream in = null;
    94     ExtensionsFile xtFile = writeableFile.getExtensionsFile();
     112    ExtensionsFile xtFile = wFile.getExtensionsFile();
     113   
    95114    try
    96115    {
    97       log.info("Loading extensions from file: " + xtFile.getName());
     116      log.info("Loading extensions from file: " + xtFile);
    98117      in = xtFile.getXmlStream();
    99118      loader.loadXmlFile(in, xtFile.getName(), xtFile.getClassLoader(), true);
    100119      FileData data = new FileData();
    101       data.writeableFile = writeableFile;
     120      data.writeableFile = wFile;
    102121      data.extensionPoints = new ArrayList<ExtensionPoint>(loader.getExtensionPoints());
    103122      data.extensions = new ArrayList<Extension>(loader.getExtensions());
     123      numFiles++;
    104124      log.info("Loaded " + data.extensionPoints.size() + "/" + data.extensions.size() +
    105           " extensions from file: " + xtFile.getName());
     125          " extensions from file: " + xtFile);
    106126      allData.add(data);
    107127    }
    108128    catch (Exception ex)
    109129    {
    110       writeableFile.setError(true);
    111       String msg = "Failed to load extensions from '" + xtFile.getName() + "'";
    112       results.addErrorMessage(xtFile, msg + ":" + ex.getMessage());
    113       log.error(msg, ex);
     130      wFile.setError(true);
     131      numError++;
     132      if (results != null)
     133      {
     134        results.addErrorMessage(xtFile, "Failed to load extensions:" + ex.getMessage());
     135      }
     136      log.error("Failed to load extensions from file: " + xtFile, ex);
    114137    }
    115138    finally
     
    122145  public void done(ExtensionsManager manager)
    123146  {
    124     if (!delayRegistration) registerExtensions(manager);
     147    if (!delayRegistration) finalizeRegistration(manager);
    125148  }
    126149 
     
    133156  // ------------------------------------------
    134157
    135  
    136   public void registerExtensions(ExtensionsManager manager)
     158  /**
     159    Set a flag to indicate if the actual registration of the found
     160    extensions and extension points should be delayed or not.
     161    The default is to not delay registration, which means that
     162    it happend when the {@link #done(ExtensionsManager)} method is
     163    called by the manager. If this flag is set the
     164    {@link #finalizeRegistration(ExtensionsManager)}
     165    method must be explicitely called.
     166  */
     167  public void setDelayRegistration(boolean delayRegistration)
     168  {
     169    this.delayRegistration = delayRegistration;
     170  }
     171 
     172  /**
     173    Is the actual registration delayed or not?
     174  */
     175  public boolean isRegistrationDelayed()
     176  {
     177    return delayRegistration;
     178  }
     179 
     180  /**
     181    Get the loader the processor is using for parsing xml files with
     182    extension definitions.
     183  */
     184  public XmlLoader getXmlLoader()
     185  {
     186    return loader;
     187  }
     188 
     189  public ProcessResults getProcessResults()
     190  {
     191    return results;
     192  }
     193 
     194  public void finalizeRegistration(ExtensionsManager manager)
    137195  {
    138196    if (allData == null) return;
     
    158216        wFile.open();
    159217        int num = registerExtensionPoints(data, registry);
    160         if (num > 0)
     218        if (num > 0 && results != null)
    161219        {
    162           results.addMessage(xtFile, num + " extension points registered.");
     220          results.addMessage(xtFile, num + " extension point(s) registered.");
    163221        }
    164         log.info("Registered " + num + " extension points from file: " + xtFile);
     222        log.info("Registered " + num + " extension point(s) from file: " + xtFile);
     223        numRegistered += num;
    165224      }
    166225      catch (Throwable ex)
    167226      {
    168227        wFile.setError(true);
    169         results.addErrorMessage(xtFile,
    170           "Could not register extension points: " + ex.getMessage());
     228        if (results != null)
     229        {
     230          results.addErrorMessage(xtFile,
     231            "Could not register extension points: " + ex.getMessage());
     232        }
    171233        log.error("Could not register extension points from file: " + xtFile, ex);
    172234      }
     
    195257        wFile.open();
    196258        int num = registerExtensions(data, registry);
    197         if (num > 0)
     259        if (num > 0 && results != null)
    198260        {
    199           results.addMessage(xtFile, num + " extensions registered.");
     261          results.addMessage(xtFile, num + " extension(s) registered.");
    200262        }
    201         log.info("Registered " + num + " extension points from file: " + xtFile);
     263        log.info("Registered " + num + " extension(s) from file: " + xtFile);
     264        numRegistered += num;
    202265      }
    203266      catch (Throwable ex)
    204267      {
    205268        wFile.setError(true);
    206         results.addErrorMessage(xtFile,
    207           "Could not register extension points: " + ex.getMessage());
     269        if (results != null)
     270        {
     271          results.addErrorMessage(xtFile,
     272            "Could not register extension points: " + ex.getMessage());
     273        }
    208274        log.error("Could not register extension points from file: " + xtFile, ex);
    209275      }
     
    218284  }
    219285
     286  /**
     287    Get the number of files that was successfully processed.
     288  */
     289  public int getNumFiles()
     290  {
     291    return numFiles;
     292  }
     293 
     294  /**
     295    Get the number of files that was had an error.
     296  */
     297  public int getNumError()
     298  {
     299    return numError;
     300  }
     301 
     302  /**
     303    Get the number of extensions + extension points that
     304    was registered.
     305  */
     306  public int getNumRegistered()
     307  {
     308    return numRegistered;
     309  }
     310
    220311 
    221312  private int registerExtensionPoints(FileData data, Registry registry)
     
    224315    for (ExtensionPoint<Action> ep : data.extensionPoints)
    225316    {
    226       if (!registry.extensionPointIsRegistered(ep.getId()))
     317      //if (!registry.extensionPointIsRegistered(ep.getId()))
    227318      {
    228319        ExtensionPointKey key = new ExtensionPointKey(ep);
     
    242333    for (Extension<Action> ext : data.extensions)
    243334    {
    244       if (!registry.extensionIsRegistered(ext.getId()))
     335      //if (!registry.extensionIsRegistered(ext.getId()))
    245336      {
    246337        ExtensionKey key = new ExtensionKey(ext);
  • trunk/www/admin/extensions/details.jsp

    r5602 r5603  
    317317            <a href="javascript:showFile('<%=HTML.javaScriptEncode(extFile.getName())%>')"
    318318            ><%=extFile.getName()%></a>
    319             (<%=extFile.isModified() ? "Modified" : "Up to date" %>;
     319            (<%=extFile.checkModified() ? "Modified" : "Up to date" %>;
    320320            <%=extFile.hasError() ? "Error" : "Ok" %>)
    321321          </td>
     
    405405              <a href="javascript:showFile('<%=HTML.javaScriptEncode(epFile.getName())%>')"
    406406              ><%=epFile.getName()%></a>
    407               (<%=epFile.isModified() ? "Modified" : "Up to date" %>;
     407              (<%=epFile.checkModified() ? "Modified" : "Up to date" %>;
    408408              <%=epFile.hasError() ? "Error" : "Ok" %>)
    409409              <%
     
    451451        <tr>
    452452          <td class="prompt">Up to date</td>
    453           <td><%=file.isModified() ? "No" : "Yes"%></td>
     453          <td><%=file.checkModified() ? "No" : "Yes"%></td>
    454454        </tr>
    455455        <tr>
Note: See TracChangeset for help on using the changeset viewer.