Application Extensions

 

Author: Eike Stepper

An application extension is a supported SPI for application-level server startup work. An OSGi bundle contributes an appExtension element to org.eclipse.emf.cdo.server.appExtensions; the server instantiates the declared class, so its no-argument construction must not depend on a repository already existing. Choose a callback interface for the context and lifecycle you need, install repository-specific listeners or handlers in that callback, and remove those exact registrations on stop. An application extension orchestrates application behavior; it is not itself a repository handler, a repository listener, or a managed-container factory.

1 Choosing an Extension Variant
2 Lifecycle Discipline
3 Repository-aware Extension Example

1  Choosing an Extension Variant

The interfaces extend the base IAppExtension lifecycle. The packaged server constructs one contributed instance at startup, then calls IAppExtension.start(File) and later IAppExtension.stop(). The base form receives the application XML file, but no repository array; look repositories up through a supported container context only when appropriate.

IAppExtension2 adds IAppExtension2.startDynamic(Reader) for each dynamically configured repository. The repository-configuration manager creates a separate extension instance for each dynamic start, passes that repository's XML, and calls that instance's stop() when the repository deactivates. A dynamic extension must therefore keep per-instance cleanup state and must not assume its base start(File) was called for that dynamic instance.

IAppExtension3 receives the initially configured repositories and config file in its repository-aware start callback, with a matching repository-array stop callback instead of the base callback pair. It is the clearest choice for installing one listener/handler per configured repository. The array describes initial startup; later dynamic repositories have their own configuration lifecycle and do not appear in it. IAppExtension4 supplies a numeric priority: smaller values start first within their phase and stop in reverse order; extensions without it use the default priority. IAppExtension5 supplies a logging name and selects the early phase through IAppExtension5.startBeforeRepositories(). Early callbacks run before the repository configurator and must not use repository state. These are orthogonal choices; an extension can implement more than one variant.

2  Lifecycle Discipline

Startup and stop callbacks run synchronously on the server application lifecycle path. Keep them bounded. Retain only the repository/listener/handler pairs needed for teardown. If setup fails after installing some listeners, remove the partial set in the start method's failure path: the packaged application logs an extension exception and continues, but does not call stop() as rollback for a failed start. On normal stop, remove those registrations before the owning repositories deactivate. Repository listener callbacks may run on the event delivery thread, so queue expensive work elsewhere. The contribution's predecessor attribute suppresses a competing extension implementation; it is not an ordering mechanism. Do not depend on internal repository classes merely because the packaged application uses them.

3  Repository-aware Extension Example

This repository-aware extension notifies an application service when its session state changes. It stores every listener that it adds, unwinds partial setup if installation fails, and removes exactly those listeners on shutdown.

CreateSessionObserverExtension.java      
return new IAppExtension3()
{
  private final Map<IRepository, IListener> listeners = new HashMap<>();

  @Override
  public void start(File configFile)
  {
  }

  @Override
  public void start(IRepository[] repositories, File configFile)
  {
    try
    {
      for (IRepository repository : repositories)
      {
        IListener listener = event -> sessionStateChanged.accept(repository);
        repository.getSessionManager().addListener(listener);
        listeners.put(repository, listener);
      }
    }
    catch (RuntimeException | Error ex)
    {
      removeListeners();
      throw ex;
    }
  }

  private void removeListeners()
  {
    for (Map.Entry<IRepository, IListener> entry : listeners.entrySet())
    {
      entry.getKey().getSessionManager().removeListener(entry.getValue());
    }

    listeners.clear();
  }

  @Override
  public void stop(IRepository[] repositories)
  {
    removeListeners();
  }

  @Override
  public void stop()
  {
    removeListeners();
  }
};

Contribute the extension from its bundle's plugin.xml with

plugin.xml      
<plugin>
  
<extension point="org.eclipse.emf.cdo.server.appExtensions">
    
<appExtension class="com.example.server.SessionObserverExtension"/>
  
</extension>
</plugin>

.