Extension Point Development Guide

Semantic Turkey uses the enterprise-ready container Apache Karaf, which is based on the OSGi dynamic module systems for Java. These technologies allowed Semantic Turkey to support the dynamic plugging of extensions.

Component

A component is an object that can be identified within the system (by means of a string identifier), which can be scoped to a domain (system, project, user and project-user).


package it.uniroma2.art.semanticturkey.extension;

public interface IdentifiableComponent {

  /**
    * returns the identifier of this component
    * 
    * @return the identifier of this component
    */
  String getId();
  
}

package it.uniroma2.art.semanticturkey.extension;

import it.uniroma2.art.semanticturkey.resources.Scope;

public interface ScopedComponent {

  Scope getScope();
  
}

The notion of component has been specialized in:

Configuration Manager

A ConfigurationManger supports the storage and retrieval of identified configurations in a subset of the available domains (system, project, user and project-user).

package it.uniroma2.art.semanticturkey.config;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

import it.uniroma2.art.semanticturkey.extension.IdentifiableComponent;
import it.uniroma2.art.semanticturkey.project.Project;
import it.uniroma2.art.semanticturkey.properties.STPropertyAccessException;
import it.uniroma2.art.semanticturkey.properties.STPropertyUpdateException;
import it.uniroma2.art.semanticturkey.properties.WrongPropertiesException;
import it.uniroma2.art.semanticturkey.resources.Reference;
import it.uniroma2.art.semanticturkey.resources.Scope;
import it.uniroma2.art.semanticturkey.user.STUser;

public interface ConfigurationManager<CONFTYPE extends Configuration> extends IdentifiableComponent {

  default Collection<Scope> getConfigurationScopes() {
    // omitted default implementation
  }

  default Collection<Reference> getConfigurationReferences(Project project, STUser user) {
    // omitted default implementation
  }

  default CONFTYPE getConfiguration(Reference reference) throws IOException, ConfigurationNotFoundException,
      WrongPropertiesException, STPropertyAccessException {
        // omitted default implementation
      }

  default void storeConfiguration(Reference reference, CONFTYPE configuration)
      throws IOException, WrongPropertiesException, STPropertyUpdateException {
        // omitted default implementation
  }

  default void deleteConfiguration(Reference reference) throws ConfigurationNotFoundException {
    // omitted default implementation
  }

}

The formal parameter CONFTYPE is the super-class of the configuration classes supported by this configuration manager. A configuration class implements the interface Configuration, and defines a number of annotated instance-level fields that defined different configuration properties. An example of parameter can be found below:


@STProperty(description = "description of the configuration parameter", displayName = "Parameter display label")
@Required // unless marked with this annotation, a parameter is considered optional
public String parameter;

There are additional annotations for advances use cases, including: @HasRole to provide an hint (for the UI) on the role of a resource, or @Enumeration to enumerate the allowed values.

The system supports a wide range of types:

Furthermore, it is possible to use any class that provided a Jackson serializer/deserializer via annotations. However, in these cases, the user interface will likely support the visualization and editing of these properties via their JSON serialization (instead of friendly, type-specific widgets)

It is possible to annotate a property with @FallbackSetting to retrieve its default value from a setting, which is determined by a settings manager identifier, the scope, and the path (just a property name, or a sequence of property names to traverse a longer path). The relevant Web APIs return the default value for a property in a form distinguishable from its actual value (if any), so that user interfaces can render it differently and avoid sending it to the server as part of a configuration instance.


@STProperty(description = "API Base URL", displayName = "API Base URL")
@FallbackSetting(
    manager = "it.uniroma2.art.semanticturkey.settings.connections.showvoc.ConnectedShowVocSettingsManager",
    scope = Scope.SYSTEM,
    path = "apiBaseURL"
)
public String apiBaseURL;

One should not implement the interface ConfigurationManger directly, but instead implement one of its subinterfaces: SystemConfigurationManager (supporting system-wide configurations), ProjectConfigurationManager (supporting project-scoped configurations), UserConfigurationManager (supporting user-specific configurations) and PUConfigurationManager (supporting configurations scoped to a given pair <project, user>). Actually, one may implement one or more interfaces depending on the desired scopes for the storage and retrieval of configuration.

It is worth to notice that these ConfigurationManagers provide a number of default implementations of the defined operations, and the sole operation to be provided by the implementing class is getId() returning the identifier for the configuration manager. Another important observation is that the type parameter shall be the same for all implemented interfaces: in other words, the configurations stored on different scopes must be homogeneous.

There are some combinations of ConfigurationManagers that are generally useful to deserve dedicated interfaces. Starting from the observation that a component can be scoped to one of four domains (system, project, user and project-user), we defined four scoped configurable components that can store and retrieve configurations in the domain in which they are scoped and in the more generic ones.

Settings Manager

A SettingsManger supports the storage and retrieval of a single collection of settings in a subset of the available domains (system, project, user and project-user).

package it.uniroma2.art.semanticturkey.extension.settings;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import it.uniroma2.art.semanticturkey.extension.IdentifiableComponent;
import it.uniroma2.art.semanticturkey.project.Project;
import it.uniroma2.art.semanticturkey.properties.STPropertyAccessException;
import it.uniroma2.art.semanticturkey.properties.STPropertyUpdateException;
import it.uniroma2.art.semanticturkey.resources.Scope;
import it.uniroma2.art.semanticturkey.user.STUser;


public interface SettingsManager extends IdentifiableComponent {

  default Collection<Scope> getSettingsScopes() {
    // default implementation omitted
  }

  default Settings getSettings(Project project, STUser user, Scope scope) throws STPropertyAccessException {
    // default implementation omitted
  }

  default void storeSettings(Project project, STUser user, Scope scope, Settings settings)
      throws STPropertyUpdateException {
    // default implementation omitted
  }
} 

One should not implement the interface SettingsManager directly, but instead implement one of its subinterfaces: SystemSettingsManager (supporting system-wide settings), ProjectSettingsManager (supporting project-scoped settings), UserSettingsManager (supporting user-specific settings) and PUSettingsManager (supporting settings scoped to a given pair <project, user>). Actually, one may implement one or more interfaces depending on the desired scopes for the storage and retrieval of settings.

The scope-specific settings managers have a type parameter that represent the settings class, and ,differently from ConfigurationManagers, it is possible to use different classes for different scopes. These settings classes shall implement the interface Settings; otherwise, they follow the same rules and constraints applied to configuration classes.

Extension Point

An ExtensionPoint defines an extensible capability of the system, and it can be scoped to one of the four available domains (system, project, user and project-user).

package it.uniroma2.art.semanticturkey.extension;

public interface ExtensionPoint extends IdentifiableComponent, ScopedComponent {

  @Override
  default String getId() {
    return getInterface().getName();
  }
  
  Class<?> getInterface();
  
  
}    

A class implementing this interface shall only defined the operation getInterface() returning the Java interface describing the syntactic contract for the extension point. The returned interface usually extends the interface Extension (i.e. classes implementing a given extension point are extensions).

Extension Factory

An ExtensionFactory is an identifiable component that create instances of an extension type implementing one or more extension points. The binding between an extension type and the extension point is determined by the fact the extension type implements the interface associated with the extension point.


package it.uniroma2.art.semanticturkey.extension;

import it.uniroma2.art.semanticturkey.utilities.ReflectionUtilities;

/**
  * An ExtensionFactory provides instances of a given {@link Extension}. The metadata provided by the factory
  * actually refers to Extension
  *
  * @param <EXTTYPE>
  */
public interface ExtensionFactory<EXTTYPE extends Extension> extends IdentifiableComponent {

  @Override
  default String getId() {
    return getExtensionType().getName();
  }

  default Class<EXTTYPE> getExtensionType() {
    return ReflectionUtilities.getInterfaceArgumentTypeAsClass(this.getClass(), ExtensionFactory.class,
        0);
  }

  /**
    * returns a short name for the extension
    * 
    * @return a short name for the extension
    */
  String getName();

  /**
    * returns a description of the extension
    * 
    * @return a description of the extension
    */
  String getDescription();
} 

In fact, one should not implement this interface, but one of its two subinterfaces:


package it.uniroma2.art.semanticturkey.extension;

/**
  *
  * @param <EXTTYPE>
  */
public interface NonConfigurableExtensionFactoryEXTTYPE extends Extension> extends ExtensionFactory<EXTTYPE>  {
  
  /**
    * Instantiates an extension
    * 
    * @param conf
    * @return
    */
  EXTTYPE createInstance();
  
  
}

package it.uniroma2.art.semanticturkey.extension;

import java.util.Collection;

import it.uniroma2.art.semanticturkey.config.Configuration;
import it.uniroma2.art.semanticturkey.config.ConfigurationManager;

/**
  *
  * @param <EXTTYPE>
  * @param <CONFIGTYPE>
  */
public interface ConfigurableExtensionFactory<EXTTYPE extends Extension, CONFIGTYPE extends Configuration> extends ExtensionFactory<EXTTYPE>, ConfigurationManager<CONFIGTYPE> {

  /**
    * Instantiates an extension based on the given configuration object.
    * 
    * @param conf
    * @return
    */
  EXTTYPE createInstance(CONFIGTYPE conf);
  
  
  /**
    * Returns allowed configurations for this factory.
    * 
    * @return
    */
  Collection<CONFIGTYPE> getConfigurations();
  
}

A ConfigurableExtensionFactory is also a ConfigurationManager: the supported configuration scopes are determined by implementing the appropriate specializations of ConfigurationManager. Otherwise, it is possible to implement an XYZScopedConfigurationManager.

Making components available to the system

A component is made available to system by registering it to the OSGI Service Registry under each of the interfaces of interest (described above). Inside an context descriptor for Spring DM, it is possible to publish a bean under all the implemented interfaces.


<osgi:service auto-export="interfaces">
    <bean
      class="org.example.ComponentClass" />
</osgi:service>        

Relevant Web API

There are a number of service classes relevant to the components above that can be accessed via the Web API.