Changeset 4180


Ignore:
Timestamp:
Mar 17, 2008, 2:40:46 PM (15 years ago)
Author:
Nicklas Nordborg
Message:

References #436: Create extension system for the web application

Started with the automatic installation / resource extraction servlet.

Location:
branches/extensions
Files:
11 added
1 deleted
9 edited
1 moved

Legend:

Unmodified
Added
Removed
  • branches/extensions/config/dist/log4j.properties

    r4093 r4180  
    5757#log4j.logger.net.sf.basedb.core.signal=debug
    5858
     59### Extensions API
     60#log4j.logger.net.sf.basedb.clients.web.extensions=debug
     61
    5962
    6063# -----------------
  • branches/extensions/src/clients/web/net/sf/basedb/clients/web/extensions/ExtensionsDirectory.java

    r4170 r4180  
    2424package net.sf.basedb.clients.web.extensions;
    2525
    26 
    27 import net.sf.basedb.util.extensions.Extension;
     26import java.io.File;
     27import java.io.FileFilter;
     28import java.util.HashMap;
     29import java.util.Iterator;
     30import java.util.Map;
     31import java.util.regex.Pattern;
     32
     33import net.sf.basedb.util.RegexpFileFilter;
    2834import net.sf.basedb.util.extensions.ExtensionsInvoker;
    2935import net.sf.basedb.util.extensions.Registry;
     36import net.sf.basedb.util.extensions.xml.PathConverter;
     37import net.sf.basedb.util.extensions.xml.VariableConverter;
     38import net.sf.basedb.util.extensions.xml.XmlLoader;
    3039
    3140/**
     41  Represents a directory with extensions. This class can automatically
     42  detect changes and install, update or uninstall extensions when files
     43  are added, changed or removed from the specified directory.
     44  Resources, such as images, JSP files, etc. are automatically extracted
     45  from JAR files and placed in the resources directory. This directory is
     46  accessible from the web server.
    3247
    3348  @author nicklas
    3449  @version 2.7
    3550  @base.modified $Date$
    36  */
    37 public class WebExtensions
     51*/
     52public class ExtensionsDirectory
    3853{
    3954
    40   public static Registry REGISTRY = new Registry();
    41  
    42   public static Settings SETTINGS;
    43  
    44   public static String ROOT = "/extension";
    45  
    46   public static ExtensionsInvoker<?> useExtensions(JspContext context, String...extensionPoints)
    47   {
    48     return REGISTRY.useExtensions(context, SETTINGS, extensionPoints);
    49   }
    50  
    51   public static String getRoot(Extension extension)
    52   {
    53     return ROOT + "/extensions/" + extension.getId();
    54   }
     55  private static final org.apache.log4j.Logger log =
     56    org.apache.log4j.LogManager.getLogger("net.sf.basedb.clients.web.extensions.WebExtensions");
     57 
     58  private final Registry registry;
     59  private final File extensionsDir;
     60  private final File resourcesDir;
     61  private final String resourcesUrl;
     62  private final String rootUrl;
     63 
     64  private final Settings settings;
     65
     66  private final Map<File, ExtensionsFile> installedExtensions;
     67  private final VariableConverter variableConverter;
     68  private final PathConverter pathConverter;
     69 
     70 
     71  public ExtensionsDirectory(Registry registry, File extensionsDir, File resourcesDir,
     72    String rootUrl, String resourcesUrl)
     73  {
     74    this.extensionsDir = extensionsDir;
     75    this.resourcesDir = resourcesDir;
     76    this.resourcesUrl = resourcesUrl;
     77    this.rootUrl = rootUrl;
     78   
     79    this.registry = registry;
     80    this.settings = new Settings(new File(extensionsDir, "settings.xml"));
     81   
     82    this.installedExtensions = new HashMap<File, ExtensionsFile>();
     83    this.variableConverter = new VariableConverter();
     84    this.variableConverter.setVariable("ROOT", rootUrl);
     85    this.variableConverter.setVariable("HOME", null);
     86    this.pathConverter = new PathConverter(rootUrl, null);
     87  }
     88 
     89  public Settings getSettings()
     90  {
     91    return settings;
     92  }
     93 
     94  public synchronized void installOrUpdateExtensions(boolean forceUpdate)
     95  {
     96    int numDeleted = unregisterDeleted();
     97    int numNew = scanForNew();
     98    loadDefinitions(forceUpdate);
     99    extractResources(forceUpdate);
     100    registerExtensions();
     101  }
     102 
     103 
     104  public ExtensionsInvoker<?> useExtensions(JspContext context, String...extensionPoints)
     105  {
     106    return registry.useExtensions(context, settings, extensionPoints);
     107  }
     108
    55109 
    56110  /**
    57     Convert a path to an absolute path seen from the web
    58     servers perspective. Here are the rules:
    59     <ol>
    60     <li>If <code>path</code> starts with '/', the path is relative to
    61       the BASE application root directory, which is added to
    62       the beginning of the path, for example,
    63       <code>/images/copy.gif --&gt; /base/images/copy.gif</code>
    64 
    65     <li>If <code>path</code> stars with '~', the path is relative to
    66       the extensions home directory. The home directory of an extension
    67       is normally located in the <code>/extensions</code> directory
    68       with a name based on the JAR file the extension is located in.
    69       For example,
    70       <code>~/images/tmev.png --&gt; /base/extensions/net.sf.basedb.mev/images/tmev.png</code>.
    71 
    72     <li>Otherwise, the <code>path</code> is returned unmodified.
    73     </ol>
    74 
    75     @param path The path to convert to an absolute path
    76     @param extension The extension that is requesting the path
    77       conversion
    78     @return The absolute path
     111    Unregister extensions/extension points that no longer
     112    exists.
    79113  */
    80   public static String makeAbsolutePath(String path, Extension extension)
    81   {
    82     if (path != null)
    83     {
    84       if (path.startsWith("/"))
    85       {
    86         path = ROOT + path;
    87       }
    88       else if (path.startsWith("~"))
    89       {
    90         path = getRoot(extension) + path.substring(1);
    91       }
    92     }
    93     return path;
    94   }
    95  
     114  private int unregisterDeleted()
     115  {
     116    log.info("Checking for deleted extensions in directory: " +
     117      extensionsDir.getAbsolutePath());
     118    // Unregister extensions that have been deleted
     119    int numDeleted = 0;
     120    Iterator<ExtensionsFile> it = installedExtensions.values().iterator();
     121    while (it.hasNext())
     122    {
     123      ExtensionsFile extFile = it.next();
     124      boolean exists = extFile.exists();
     125      log.debug("File '" + extFile.getName() + "' " + (exists ? "exists" : "has been deleted"));
     126      if (!exists)
     127      {
     128        log.info("Unregistering extensions from " + extFile.getName());
     129        //extFile.unregisterAll();
     130        File homeDir = new File(resourcesDir, extFile.getName());
     131        extFile.removeResources(homeDir);
     132        it.remove();
     133        numDeleted++;
     134      }
     135    }
     136    log.info(numDeleted + " extensions has been deleted from directory: " +
     137      extensionsDir.getAbsolutePath());
     138    return numDeleted;
     139  }
     140 
     141  /**
     142    Scan for new extensions. This method checks in the
     143    extensions directory for *.xml and *.jar files.
     144  */
     145  private int scanForNew()
     146  {
     147    log.info("Scanning for new extensions in directory: " +
     148      extensionsDir.getAbsolutePath());
     149    // Search for new .xml or .jar files; don't search subdirectories
     150    FileFilter filter = new RegexpFileFilter(".*\\.(xml|jar)", "");
     151    int numNew = 0;
     152    for (File file : extensionsDir.listFiles(filter))
     153    {
     154      if (file.getName().equals("settings.xml")) continue;
     155     
     156      // Ignore files that we already know about
     157      if (!installedExtensions.containsKey(file))
     158      {
     159        log.debug("Possible match: " + file.getName());
     160        ExtensionsFile extFile =
     161          new ExtensionsFile(file, createXmlLoader());
     162        if (extFile.isValid())
     163        {
     164          // This is a new valid extensions file
     165          log.info("Found new extensions in file: " + file.getName());
     166          installedExtensions.put(file, extFile);
     167          numNew++;
     168        }
     169      }
     170      else
     171      {
     172        log.debug("File is already installed: " + file.getName());
     173      }
     174    }
     175    log.info(numNew + " new extensions found in directory: " +
     176      extensionsDir.getAbsolutePath());
     177    return numNew;
     178  }
     179 
     180  /**
     181    Load the extension definitions from XML files.
     182  */
     183  private void loadDefinitions(boolean forceUpdate)
     184  {
     185    Iterator<ExtensionsFile> it = installedExtensions.values().iterator();
     186    while (it.hasNext())
     187    {
     188      ExtensionsFile extFile = it.next();
     189      // Only load new or updated file, unless forced
     190      if (forceUpdate || extFile.isModified())
     191      {
     192        // Set home directory for resources on value converters
     193        String homePath = resourcesUrl + "/" + extFile.getName();
     194        variableConverter.setVariable("HOME", homePath);
     195        pathConverter.setHome(homePath);
     196        extFile.loadExtensions();
     197      }
     198    }
     199  }
     200 
     201 
     202  private void extractResources(boolean forceUpdate)
     203  {
     204    // Extract resources for new and modified extensions
     205    Pattern resourcePattern = Pattern.compile("resources/(.*)");
     206    for (ExtensionsFile extFile : installedExtensions.values())
     207    {
     208      if (forceUpdate || extFile.isModified())
     209      {
     210        File homeDir = new File(resourcesDir, extFile.getName());
     211        extFile.extractResources(homeDir, resourcePattern, "$1", forceUpdate);
     212      }
     213    }
     214  }
     215 
     216  private void registerExtensions()
     217  {
     218    for (ExtensionsFile extFile : installedExtensions.values())
     219    {
     220      if (extFile.isModified())
     221      {
     222        extFile.registerExtensionPoints(registry);
     223      }
     224    }
     225   
     226    for (ExtensionsFile extFile : installedExtensions.values())
     227    {
     228      extFile.registerExtensions(registry);
     229      extFile.resetModified();
     230    }
     231  }
     232 
     233
     234  private XmlLoader createXmlLoader()
     235  {
     236    XmlLoader loader = new XmlLoader();
     237    loader.addValueConverter(variableConverter);
     238    loader.addValueConverter(pathConverter);
     239    return loader;
     240  }
     241   
    96242}
  • branches/extensions/src/clients/web/net/sf/basedb/clients/web/extensions/Settings.java

    r4170 r4180  
    6464{
    6565
     66  private final File file;
    6667  private boolean hasChanged;
    6768  private Presets presets;
     
    7071  private Preset disabledExtensionPoints;
    7172 
    72   private String filename;
    73  
    74   public Settings(String filename)
    75     throws IOException
     73  public Settings(File file)
    7674  {
    77     this.filename = filename;
     75    this.file = file;
    7876    this.presets = new Presets();
    79     File file = new File(filename);
    8077    if (file.exists())
    8178    {
    82       InputStream in = new BufferedInputStream(new FileInputStream(file));
    83       presets.loadFrom(in, filename);
    84       in.close();
     79      try
     80      {
     81        InputStream in = new BufferedInputStream(new FileInputStream(file));
     82        presets.loadFrom(in, file.getAbsolutePath());
     83        in.close();
     84      }
     85      catch (IOException ex)
     86      {
     87        throw new RuntimeException(ex);
     88      }
    8589    }
    8690    this.settings = presets.getDefault();
     
    134138    Save the settings to disk. If the settings has not been
    135139    changed the file is not written.
    136    
    137     @throws IOException
    138140  */
    139141  public synchronized void save()
    140     throws IOException
    141142  {
    142143    if (!hasChanged) return;
    143     OutputStream out = new FileOutputStream(filename);
    144     presets.writeTo(out);
    145     out.close();
     144    try
     145    {
     146      OutputStream out = new FileOutputStream(file);
     147      presets.writeTo(out);
     148      out.close();
     149    }
     150    catch (IOException ex)
     151    {
     152      throw new RuntimeException(ex);
     153    }
    146154    hasChanged = true;
    147155  }
  • branches/extensions/src/clients/web/net/sf/basedb/clients/web/extensions/toolbar/ButtonAction.java

    r4168 r4180  
    7676    line up with the other icons used by the BASE.
    7777    The reference will be path converted as described by
    78     {@link WebExtensions#makeAbsolutePath(String, net.sf.basedb.util.extensions.Extension)}
     78    {@link ExtensionsDirectory#makeAbsolutePath(String, net.sf.basedb.util.extensions.Extension)}
    7979   
    8080    @return A reference to an image, or null if no image should
  • branches/extensions/src/core/net/sf/basedb/util/extensions/Registry.java

    r4170 r4180  
    5151  @version 2.7
    5252  @base.modified $Date$
    53   @see net.sf.basedb.clients.web.extensions.WebExtensions
     53  @see net.sf.basedb.clients.web.extensions.ExtensionsDirectory
    5454*/
    5555public class Registry
  • branches/extensions/src/core/net/sf/basedb/util/extensions/xml/XmlLoader.java

    r4170 r4180  
    199199 
    200200  /**
     201    Unregister all loaded extension points from a registry.
     202    @param registry The registry
     203  */
     204  public void unregisterExtensionPoints(Registry registry)
     205  {
     206    for (ExtensionPoint<Action> ep : extensionPoints)
     207    {
     208      registry.unregisterExtensionPoint(ep.getId());
     209    }
     210  }
     211 
     212  /**
    201213    Register all loaded extensions with a registry.
    202214    @param registry The registry
     
    207219    {
    208220      registry.registerExtension(ext);
     221    }
     222  }
     223 
     224  /**
     225    Unregister all loaded extensions from a registry.
     226    @param registry The registry
     227  */
     228  public void unregisterExtensions(Registry registry)
     229  {
     230    for (Extension<Action> ext : extensions)
     231    {
     232      registry.unregisterExtension(ext.getId());
    209233    }
    210234  }
  • branches/extensions/www/admin/extensions/details.jsp

    r4168 r4180  
    3737  import="net.sf.basedb.util.extensions.ExtensionPoint"
    3838  import="net.sf.basedb.util.extensions.Extension"
    39   import="net.sf.basedb.clients.web.extensions.WebExtensions"
     39  import="net.sf.basedb.clients.web.servlet.ExtensionsServlet"
    4040  import="net.sf.basedb.clients.web.extensions.Settings"
    4141  import="net.sf.basedb.core.plugin.About"
     
    5757  final Client currentClient = Client.getById(dc, sc.getClientId());
    5858  final boolean writePermission = currentClient.hasPermission(Permission.WRITE);
    59   final Settings settings = WebExtensions.SETTINGS;
     59  final Settings settings = ExtensionsServlet.getSettings();
    6060 
    6161  Extension<?> ext = null;
     
    6464  if (extensionId != null)
    6565  {
    66     ext = WebExtensions.REGISTRY.getExtension(extensionId);
     66    ext = ExtensionsServlet.getRegistry().getExtension(extensionId);
    6767    if (ext == null)
    6868    {
     
    7474  if (extensionPointId != null)
    7575  {
    76     ep = WebExtensions.REGISTRY.getExtensionPoint(extensionPointId);
     76    ep = ExtensionsServlet.getRegistry().getExtensionPoint(extensionPointId);
    7777    if (ep == null)
    7878    {
  • branches/extensions/www/admin/extensions/index.jsp

    r4168 r4180  
    1 <%@page import="net.sf.basedb.clients.web.extensions.WebExtensions"%>
    21<%-- $Id$
    32  ------------------------------------------------------------------
     
    3635  import="net.sf.basedb.util.extensions.ExtensionPoint"
    3736  import="net.sf.basedb.util.extensions.Extension"
    38   import="net.sf.basedb.clients.web.extensions.WebExtensions"
     37  import="net.sf.basedb.clients.web.servlet.ExtensionsServlet"
    3938%>
    4039<%@ taglib prefix="base" uri="/WEB-INF/base.tld" %>
     
    5958    String extensionId = request.getParameter("extensionId");
    6059    boolean enable = Values.getBoolean(request.getParameter("enable"));
    61     WebExtensions.SETTINGS.enableExtension(extensionId, enable);
    62     WebExtensions.SETTINGS.save();
     60    ExtensionsServlet.getSettings().enableExtension(extensionId, enable);
     61    ExtensionsServlet.getSettings().save();
    6362    redirect = "details.jsp?ID=" + ID + "&extensionId=" + extensionId;
    6463  }
     
    6766    String extensionPointId = request.getParameter("extensionPointId");
    6867    boolean enable = Values.getBoolean(request.getParameter("enable"));
    69     WebExtensions.SETTINGS.enableExtensionPoint(extensionPointId, enable);
    70     WebExtensions.SETTINGS.save();
     68    ExtensionsServlet.getSettings().enableExtensionPoint(extensionPointId, enable);
     69    ExtensionsServlet.getSettings().save();
    7170    redirect = "details.jsp?ID=" + ID + "&extensionPointId=" + extensionPointId;
    7271  }
  • branches/extensions/www/admin/extensions/tree.jsp

    r4168 r4180  
    3434  import="net.sf.basedb.util.extensions.ExtensionPoint"
    3535  import="net.sf.basedb.util.extensions.Extension"
    36   import="net.sf.basedb.clients.web.extensions.WebExtensions"
     36  import="net.sf.basedb.clients.web.servlet.ExtensionsServlet"
    3737  import="net.sf.basedb.clients.web.extensions.Settings"
    3838  import="net.sf.basedb.core.plugin.About"
     
    7373    <%
    7474    Iterator<ExtensionPoint<?>> extensionPoints =
    75       WebExtensions.REGISTRY.getExtensionPoints();
    76     Settings settings = WebExtensions.SETTINGS;
     75      ExtensionsServlet.getRegistry().getExtensionPoints();
     76    Settings settings = ExtensionsServlet.getSettings();
    7777    while (extensionPoints.hasNext())
    7878    {
     
    8686      <%
    8787      Iterator<Extension<?>> extensions =
    88         WebExtensions.REGISTRY.getExtensions(epId);
     88        ExtensionsServlet.getRegistry().getExtensions(epId);
    8989      while (extensions.hasNext())
    9090      {
  • branches/extensions/www/include/menu.jsp

    r4168 r4180  
    5757  import="net.sf.basedb.clients.web.util.HTML"
    5858  import="net.sf.basedb.util.Values"
    59   import="net.sf.basedb.clients.web.extensions.WebExtensions"
     59  import="net.sf.basedb.clients.web.servlet.ExtensionsServlet"
    6060  import="net.sf.basedb.clients.web.extensions.JspContext"
    6161  import="net.sf.basedb.clients.web.extensions.menu.MenuItemAction"
     
    954954    JspContext context = new JspContext(sc);
    955955    ExtensionsInvoker<MenuItemAction> invoker =
    956       (ExtensionsInvoker<MenuItemAction>)WebExtensions.useExtensions(context,
     956      (ExtensionsInvoker<MenuItemAction>)ExtensionsServlet.useExtensions(context,
    957957        "net.sf.basedb.clients.web.menu.extensions");
    958958    ActionIterator<MenuItemAction> items = invoker.iterate();
     
    979979            style="<%=item.getStyle()%>"
    980980            title="<%=item.getTitle()%>"
    981             icon="<%=WebExtensions.makeAbsolutePath(item.getIcon(), extension)%>"
     981            icon="<%=item.getIcon()%>"
    982982            tooltip="<%=item.getTooltip()%>"
    983983            enabled="<%=item.isEnabled()%>"
Note: See TracChangeset for help on using the changeset viewer.