In IRIS a plugin is a collection (one or more) of beans loaded into IRIS at startup. By convention the plugin beans should (but are not required to) implement the com.sri.iris.kernel.serviceloader.IPlugin interface. This interface defines 2 methods "startup" and "shutdown". Plugin lifecycle is controlled using these methods through Spring. Because lifecycles are controlled through Spring, implementing IPlugin is optional as long as the plugin developer provides lifecycle support using Spring.
Every IRIS plugin has the following attributes:
A unique name. If the Plugin SDK is used (see below), the name should be a simple name that could compile as a Java source token. For example, "myplugin" and "my_plugin" will work but "my-plugin" will not work.
A "plugin.xml" file which defines an ApplicationContext for the plugin. The ApplicationContext may define its own ClassLoader and parent ApplicationContext(s) if the plugin depends on other plugins.
The "plugin.xml" file will most likely refer to another XML file which contains the beans for the plugin.
Note that IRIS loads all plugins under the same ApplicationContext, so all "plugin.xml" files may refer to beans in other "plugin.xml" files. This is important to set up plugin dependencies properly. Also note that in CALO/Desktop plugins are also loaded into an ApplicationContext that contains the IRIS plugins ApplicationContext as the parent.
Note that any plugin which displays UI to the user is specified differently and uses a "plugin-client.xml" file. The differences are documented in later sections. This is due to a legacy code requirement that was created to facilitate the implementation of a mechanism that would support a user running IRIS in a client/server mode. The mechanism required each user to have 2 machines, one to run the user interface (client), and another to run non-ui (server) code. The mechanism has been deprecated, but the requirement remains
To facilitate writing a plugin both the iris/plugins-src and desktop/plugins-src (CALO development only) directories have an Ant build file named plugin-sdk.xml. Using the Plugin SDK a plugins directory structure can be easily created which contains:
A stub implementation for the plugin source.
Spring configuration files loaded by IRIS at runtime (ex: "plugin.xml").
Ant build files.
Unit test setup with a "test" target that bootstraps IRIS and loads just the plugin (and its dependencies). The bootstrap also redirects the IRIS kb into a temporary directory by redefining the IIrisAppDirLocator bean. A stub JUnit test case is also created for the plugin so test cases can be run.
To generate a plugin directory tree using the Plugin SDK, execute the following in iris/plugins-src (or desktop/plugins-src for CALO development) after building:
ant.bat -f plugin-sdk.xml generate-plugin -Dplugin.initFile=pluginInit-myplugin.xml
Where "pluginInit-myplugin.xml" is located in the same directory and looks like the following file:
<beans>
<bean id="com.sri.iris.tools.pluginsdk.PluginParams" class="com.sri.iris.tools.pluginsdk.PluginParams">
<property name="pluginName" value="myplugin" />
<property name="sourcePackage" value="com.sri.iris.plugins.myplugin" />
<property name="className" value="MyPluginImpl" />
<property name="pluginDescription" value="Description for MyPlugin" />
<property name="jarFiles">
<list>
<value>log4j.jar</value>
<value>someOtherLib.jar</value>
</list>
</property>
<property name="dependencies">
<list>
<value>anotherPlugin</value>
</list>
</property>
<property name="buildDependencies" value="true" />
<property name="pluginLocations">
<list>
<bean class="com.sri.iris.tools.pluginsdk.PluginLocations">
<property name="destDir" value="../plugins" />
<property name="destRefParam" value="${iris.plugins.dir}" />
<property name="srcDir" value="../plugins-src" />
<property name="srcRefParam" value="${iris.plugins.src.dir}" />
</bean>
</list>
</property>
</bean>
</beans>This configuration file specifies the following properties:
This must be a unique name for the plugin. It will end up being the directory name under iris/plugins which is why it must be unique.
The package where to put the IPlugin stub implementation.
The class name for the stub IPlugin
This ends up being the bean description and plugin ApplicationContext description, and also gets put in the Javadoc for the stub implementation and unit test case.
If the plugin needs any third-party jar files that are not already in iris/lib, then they can be listed here. These jar files will be used to initialize the plugin's ClassLoader. Note that it is easy to add/remove jar files by editing the generated "plugin.xml". Also note that this assumes jar files will live in the "lib" directory of the generated plugin directory tree structure.
Plugins this plugin depends on. The "pluginLocations" are searched in order to find the dependent plugins.
If this is true and IRIS detects that any plugins depended on exist under the source directories listed in "pluginLocations", then the generated build files will also build dependent plugins.
Tells the SDK where to find plugin output, plugin source and what tokens to substitute into the generated build.xml files. The above "pluginLocations" configuration works if your plugin will be located in iris/plugins-src. If generating into desktop/plugins-src for CALO development, use the following:
<property name="pluginLocations">
<list>
<bean class="com.sri.iris.tools.pluginsdk.PluginLocations">
<property name="destDir" value="../external/iris/plugins" />
<property name="destRefParam" value="${iris.plugins.dir}" />
</bean>
<bean class="com.sri.iris.tools.pluginsdk.PluginLocations">
<property name="destDir" value="../plugins" />
<property name="destRefParam" value="${desktop.plugins.dir}" />
<property name="srcDir" value="../plugins-src" />
<property name="srcRefParam" value="${desktop.plugins.src.dir}" />
</bean>
</list>
</property> Ths only applies to CALO Desktop development. Note that
in the above config, IRIS plugins are referred to in the exernal
directory. This makes it possible to depend on IRIS plugins that
have been copied over to the Desktop build.Note that the plugin-sdk has unit tests that test generation of plugins and execution of unit tests for the generated plugins. To run the unit tests, execute "ant -f plugin-sdk.xml test" from IRIS or Desktop. After running the plugins will be left intact. The Plugin Tutorial walks through how these plugins are created. The IRIS plugin SDK test is run with each nightly build.
This section gives a tutorial on how to create plugins using the SDK, and also demonstrates how to create a UI plugin for IRIS. The plugins discussed here are automatically generated as part of the unit tests for the plugin SDK. To generate these plugins, execute "ant -f plugin-sdk.xml test" in the iris/plugins-src directory. Note that the plugins generated as part of the unit tests use the convention "unittest_PLUGINNAME" as the plugin name. This is done because these plugins are auto-generated in unit tests and therefore should not conflict with existing plugins. In this tutorial the "unitttest_" convention is omitted.
As a very simple example, suppose we want to create an IRIS plugin that exposes a service other plugins can use. In particular, this service adds 2 numbers together. The first step in writing a plugin is to define the initialization file for the plugin SDK. Here it is:
<beans>
<bean id="com.sri.iris.tools.pluginsdk.PluginParams" class="com.sri.iris.tools.pluginsdk.PluginParams">
<property name="pluginName" value="adder" />
<property name="sourcePackage" value="com.sri.iris.plugins.adder" />
<property name="className" value="AdderImpl" />
<property name="pluginDescription" value="Service used to add numbers" />
<property name="jarFiles">
<list>
<!-- No external jars needed for this plugin -->
</list>
</property>
<property name="dependencies">
<list>
<!-- No dependencies -->
</list>
</property>
<property name="buildDependencies" value="true" />
<property name="pluginLocations">
<list>
<bean class="com.sri.iris.tools.pluginsdk.PluginLocations">
<property name="destDir" value="../plugins" />
<property name="destRefParam" value="${iris.plugins.dir}" />
<property name="srcDir" value="../plugins-src" />
<property name="srcRefParam" value="${iris.plugins.src.dir}" />
</bean>
</list>
</property>
</bean>
</beans>In the iris/plugins-src directory, put this configuration into a file named "pluginInit-adder.xml" and then execute the following:
Note: The "pluginInit-adder.xml" file must also include the <?xml> and <!DOCTYPE> tags as shown in the iris/plugins-src/pluginInit-sample.xml file.
ant.bat -f plugin-sdk.xml generate-plugin -Dplugin.initFile=pluginInit-adder.xml
This invokes the plugin SDK. Note that some Velocity context errors may be reported; this is to be expected and they are not actually errors. The plugin SDK will create the following directory structure under iris/plugins-src:
Contains the ApplicationContext definition for the adder plugin.
Sample beans file. This will need to be modified as the plugin is developed.
Ant build file used to build the plugin
A place to put any jar file dependencies the plugin depends on. In this example there are no external jar files needed. Note that if any external jar files are added to the lib directory then "plugin.xml" must be edited to include those jar files in the classpath.
Stub source for the plugin
Test harness which contains the Ant "test" target
Used to build the test source
Any JUnit tests can inherit from this class which bootstraps IRIS for the unit tests. This class also may be modified as needed for proper testing of the plugin.
Unit tests for the plugin. TODO statements are generated in this file and it must be modified.
The adder plugin is then implemented in the following steps:
Write the source. The adder plugin is intended to be a service used by other plugins. Therefore according to the conventions listed in this document we must define an interface for it which starts with "I". In this case we can define the interface "IAdder.java" as follows:
package com.sri.iris.plugins.adder;
/**
* Interface for adding 2 numbers together.
*/
public interface IAdder {
/**
* Adds 2 bytes and returns a result bounded to the byte range.
*
* @param n1 the first number to add
* @param n2 the second number to add to the first
* @return the sum of the 2 numbers
* @throws NumberOverflowException if the 2 numbers add and result in an overflow. Simple
* overflow is detected by comparing signs
*/
byte computeSum(byte n1, byte n2) throws NumberOverflowException;
/**
* Adds 2 ints and returns a result bounded to the int range.
*
* @param n1 the first number to add
* @param n2 the second number to add to the first
* @return the sum of the 2 numbers
* @throws NumberOverflowException if the 2 numbers add and result in an overflow. Simple
* overflow is detected by comparing signs
*/
int computeSum(int n1, int n2) throws NumberOverflowException;
}In addition we can define "NumberOverflowException.java" as follows:
package com.sri.iris.plugins.adder;
/**
* Thrown if overflow is detected.
*/
public class NumberOverflowException extends Exception {
}Next make AdderImpl implement IAdder and define the implementation. Here we can also add some assertions which will make sure the plugin loaded properly during unit testing:
package com.sri.iris.plugins.adder;
import com.sri.iris.kernel.serviceloader.IPlugin;
import org.apache.log4j.Logger;
/**
* Service used to add numbers together.
*/
public class AdderImpl implements IPlugin, IAdder {
private Logger log = Logger.getLogger(AdderImpl.class);
private boolean started = false;
public void startup() throws Exception {
log.info("Starting up AdderImpl plugin");
started = true;
}
public void shutdown () {
log.info("Shutting down AdderImpl plugin");
started = false;
}
public byte computeSum(byte n1, byte n2) throws NumberOverflowException {
if (!started) {
throw new IllegalStateException("Plugin was not started!");
}
byte result = (byte)(n1+n2);
// If the signs are the same then the resulting sign must also be the same
if (n1>0 && n2>0 && result<=0) {
throw new NumberOverflowException();
}
if (n1<0 && n2<0 && result>=0) {
throw new NumberOverflowException();
}
return result;
}
public int computeSum(int n1, int n2) throws NumberOverflowException {
if (!started) {
throw new IllegalStateException("Plugin was not started!");
}
int result = n1+n2;
// If the signs are the same then the resulting sign must also be the same
if (n1>0 && n2>0 && result<=0) {
throw new NumberOverflowException();
}
if (n1<0 && n2<0 && result>=0) {
throw new NumberOverflowException();
}
return result;
}
}Edit the configuration. Now we must edit "adder.xml" to properly initialize and register our plugin. Here are the changes:
<beans>
<!-- Beans that will be used by other beans should expose interfaces and the bean name
is the interface class name. This is a convention encouraged in IRIS -->
<bean name="com.sri.iris.plugins.adder.IAdder"
class="com.sri.iris.plugins.adder.AdderImpl"
init-method="startup" destroy-method="shutdown" lazy-init="true">
<description>
Service used to add numbers together.
</description>
</bean>
</beans>Note the following regarding this configuration file:
According to the guidelines for writing beans, the bean must have lazy-init set to true.
Again according to the guidelines, our plugin exposes the IAdder interface so the name of the bean must be the IAdder interface class name.
The bean lifecycle methods are set to "startup" and "shutdown".
Write the tests. According to your programming methodology this might actually be the first step. With the source written now we can add the unit tests. Note that it is often best to write unit tests before the implementation, but that is up to the developer to decide. Here we can edit the file "AdderImpl_T.java". It must be changed to load our bean using our modified adder.xml configuration file. Here are the changes:
package com.sri.iris.plugins.adder;
import org.springframework.context.ApplicationContext;
/**
* Unit tests for IRIS plugin adder.
*/
public class AdderImpl_T extends IrisTestCase {
private IAdder adder;
public AdderImpl_T(String name) {
super(name);
}
protected void setUp() throws Exception {
// Note that here IRIS is only started once for all unit tests. This can be overridden by calling
// super.setReloadIRISForEachTest(false), which will then reload IRIS with each test.
// Note that it is much more efficient to load IRIS only once though or else the unit tests
// will take a long time to complete.
super.setUp();
// Get the plugins ApplicationContext
ApplicationContext pluginAppContext = (ApplicationContext)rootContext.getBean("com.sri.iris.plugins.ApplicationContext");
// Make sure the plugin AppContext is created
ApplicationContext adderAppContext = (ApplicationContext)pluginAppContext.getBean("com.sri.iris.plugins.adder.ApplicationContext");
// Get the IAdder for testing
adder = (IAdder)adderAppContext.getBean(IAdder.class.getName());
}
public void testAddIntSimple() throws Exception {
assertTrue(adder.computeSum(1,1)==2);
assertTrue(adder.computeSum(1,-1)==0);
assertTrue(adder.computeSum(1,-10)==-9);
assertTrue(adder.computeSum(-5,-10)==-15);
}
public void testAddByteSimple() throws Exception {
assertTrue(adder.computeSum((byte)1,(byte)1)==(byte)2);
assertTrue(adder.computeSum((byte)1,-(byte)1)==(byte)0);
assertTrue(adder.computeSum((byte)1,(byte)-10)==(byte)-9);
assertTrue(adder.computeSum((byte)-5,(byte)-10)==-(byte)15);
}
public void testAddIntOverflow() throws Exception {
try {
int res = adder.computeSum(1, Integer.MAX_VALUE);
assertTrue("got res<"+res+">", false);
} catch (NumberOverflowException e) {
// OK
}
try {
int res = adder.computeSum(-1, Integer.MIN_VALUE);
assertTrue("got res<"+res+">", false);
} catch (NumberOverflowException e) {
// OK
}
try {
int res = adder.computeSum(Integer.MAX_VALUE, Integer.MAX_VALUE);
assertTrue("got res<"+res+">", false);
} catch (NumberOverflowException e) {
// OK
}
try {
int res = adder.computeSum(Integer.MIN_VALUE, Integer.MIN_VALUE);
assertTrue("got res<"+res+">", false);
} catch (NumberOverflowException e) {
// OK
}
}
public void testAddByteOverflow() throws Exception {
try {
byte res = adder.computeSum((byte)1, Byte.MAX_VALUE);
assertTrue("got res<"+res+">", false);
} catch (NumberOverflowException e) {
// OK
}
try {
byte res = adder.computeSum((byte)-1, Byte.MIN_VALUE);
assertTrue("got res<"+res+">", false);
} catch (NumberOverflowException e) {
// OK
}
try {
byte res = adder.computeSum(Byte.MAX_VALUE, Byte.MAX_VALUE);
assertTrue("got res<"+res+">", false);
} catch (NumberOverflowException e) {
// OK
}
try {
byte res = adder.computeSum(Byte.MIN_VALUE, Byte.MIN_VALUE);
assertTrue("got res<"+res+">", false);
} catch (NumberOverflowException e) {
// OK
}
}
}Now the plugin can be tested. Before testing, note that Ant must be compiled with JUnit support. To do this, make sure junit.jar is placed in the Ant lib directory before compiling Ant, or download the JUnit libraries for Ant and install them.
To test the plugin, open a prompt and navigate to the "iris/plugins-src/adder/test" directory. Then execute "ant test". Make sure IRIS is not already running, because the unit test will bootstrap IRIS and if IRIS is running this can cause resource conflicts. Note that executing the test target automatically builds the plugin first. When the tests are complete the following HTML unit test report file will be generated:
adder/test/dest/testresults/html/index.html
Next suppose we want to write a plugin that multiplies number. Just for kicks let use implement a plugin that multiplies over addition using the adder plugin. Although you probably would never do this it demonstrates simple plugin dependency. Here is the plugin initialization file for the SDK:
<beans>
<bean id="com.sri.iris.tools.pluginsdk.PluginParams" class="com.sri.iris.tools.pluginsdk.PluginParams">
<property name="pluginName" value="multiplier" />
<property name="sourcePackage" value="com.sri.iris.plugins.multiplier" />
<property name="className" value="MultiplierImpl" />
<property name="pluginDescription" value="Service used to multiply numbers" />
<property name="jarFiles">
<list>
<!-- No external jars needed for this plugin -->
</list>
</property>
<property name="dependencies">
<list>
<value>adder</value>
</list>
</property>
<property name="buildDependencies" value="true" />
<property name="pluginLocations">
<list>
<bean class="com.sri.iris.tools.pluginsdk.PluginLocations">
<property name="destDir" value="../plugins" />
<property name="destRefParam" value="${iris.plugins.dir}" />
<property name="srcDir" value="../plugins-src" />
<property name="srcRefParam" value="${iris.plugins.src.dir}" />
</bean>
</list>
</property>
</bean>
</beans>Here the "adder" plugin is put in the dependencies list. To be brief, the IMultiplier source, implementation and unit test code is left out here. This code can be viewed after running the plugin SDK unit tests described above. Here is the modified configuration file "multiplier.xml":
<beans>
<bean name="com.sri.iris.plugins.multiplier.IMultiplier"
class="com.sri.iris.plugins.multiplier.MultiplierImpl"
init-method="startup" destroy-method="shutdown" lazy-init="true">
<description>
Service used to multiply numbers
</description>
<property name="adder">
<description>
The IMultiplier services implements multiplication on top of addition.
This is done to demonstrate and test single-plugin dependency.
</description>
<ref bean="com.sri.iris.plugins.adder.IAdder" />
</property>
</bean>
</beans>Note that the adder plugin is specified as a dependency by referring to its bean (the IAdder class name). All that MultiplierImpl needs to do to get the dependency is define a setter method "setAdder" which takes the IAdder instance as the one parameter. From MultiplierImpl.java:
public void setAdder(IAdder adder) {
this.adder = adder;
}Up until now the "plugin.xml" file has not been discussed. The "plugin.xml" for the multiplier plugin demonstrates the use of a custom ClassLoader and ApplicationContext inheritance to allow plugin dependencies:
<beans>
<bean id="com.sri.iris.plugins.multiplier.ClassLoader"
class="java.net.URLClassLoader"
dependency-check="none" lazy-init="true">
<constructor-arg>
<list>
<value>${iris.plugins.dir}/multiplier/multiplier.jar</value>
</list>
</constructor-arg>
<constructor-arg>
<ref bean="com.sri.iris.plugins.adder.ClassLoader" />
</constructor-arg>
</bean>
<bean id="com.sri.iris.plugins.multiplier.ApplicationContext"
class="org.springframework.context.support.FileSystemXmlApplicationContext"
init-method="refresh" lazy-init="true">
<description>Service used to multiply numbers.
This is the config file which sets up classpath and dependencies for
the plugin. See multiplier.xml for the plugin beans.</description>
<constructor-arg>
<list><value>${iris.plugins.dir}/multiplier/multiplier.xml</value></list>
</constructor-arg>
<constructor-arg>
<value>false</value>
</constructor-arg>
<constructor-arg>
<ref bean="com.sri.iris.plugins.adder.ApplicationContext"/>
</constructor-arg>
<property name="classLoader">
<ref bean="com.sri.iris.plugins.multiplier.ClassLoader"/>
</property>
</bean>
</beans>Note the following about this configuration file:
All beans are lazy-inited as is the convention already established.
The ClassLoader for the plugin is defined in the bean "com.sri.iris.plugins.multiplier.ClassLoader". This is defined as a URLClassLoader where the parent is the class loader "com.sri.iris.plugins.adder.ClassLoader", which is the ClassLoader for the adder plugin. In this way the multiplier plugin is able to access the classes of the adder plugin.
In order to properly set up the class loader the beans for the multiplier plugin must be loaded into an ApplicationContext. This is necessary because it is how Spring allows a custom ClassLoader to be specified. Here a "FileSystemXmlApplicationContext" is sued and the parent ApplicationContext is set to the bean "com.sri.iris.plugins.adder.ApplicationContext". In this way the multiplier beans can access the beans for the adder plugin (that are defined in adder.xml).
The FileSystemXmlApplicationContext needs a Spring configuration file to load for the multiplier beans. These are defined in "multiplier.xml".
In order to properly set the Classloader for a FileSystemXmlApplicationContext , you must pass "false" in the constructor to instruct the context not to refresh, and then specify the init-method to "refresh". This guarantees the class loader will be set before the context refreshes (and tries to load classes).
Note the use of the token "${iris.plugins.dir}". This token is define in iris.properties, the global properties that get applied to every ApplicationContex t
This plugin displays UI which means it involves user interaction. Due to the aforementioned legacy code requirement, UI plugins need to declare remote references to server beans. The specification for the plugin configuration file differs when generating UI plugins. In particular, the arithmeticui plugin config specifies the property:
<property name="isClientPlugin" value="true" />
When generating a client plugin, IRIS does not generate a "plugin.xml" file. Instead IRIS generates all other files mentioned and in addition:
The plugin-client.xml file is listed below:
<beans>
<bean id="com.sri.iris.plugins.client.arithmeticui.ClassLoader"
class="com.sri.iris.kernel.serviceloader.PluginClassLoader"
dependency-check="none" lazy-init="true">
<constructor-arg>
<list>
<value>${iris.plugins.dir}/arithmeticui/arithmeticui.jar</value>
</list>
</constructor-arg>
<constructor-arg>
<ref bean="com.sri.iris.plugins.client.ui.ClassLoader"/>
</constructor-arg>
<property name="prereqLoaders">
<list>
<bean class="com.sri.iris.kernel.DelegateClassLoader"
dependency-check="none" lazy-init="true">
<description>
This section only applies if the plugin has server beans. This usually is not the
case and so this section can be ignored.
When running IRIS in all-in-one mode everything runs under one Java VM. In this case
no remoting is needed but there is a class loader issue. To solve the problem of duplicate
class loaders, declare a prereq class loader that refers to the server class loader
(if present). If the server class loader is not present, then class will be loaded from the
client class loader.
Note: can also set appCtxList to the list of app contexts to traverse. The default is to look at
com.sri.iris.plugins.ApplicationContext for the classloader bean. This is the IRIS server
app contex containing the server plugins.
</description>
<property name="clBeanName" value="com.sri.iris.plugins.arithmeticui.ClassLoader" />
</bean>
</list>
</property>
</bean>
<bean id="com.sri.iris.plugins.client.arithmeticui.ApplicationContext"
class="org.springframework.context.support.FileSystemXmlApplicationContext"
init-method="refresh" lazy-init="true">
<description>Puts up an IRIS UI for the adder and multiplier unittest plugins.
This is the config file which sets up classpath and dependencies for
the plugin. See arithmeticui.xml for the plugin beans.</description>
<constructor-arg>
<list>
<value>${iris.plugins.dir}/arithmeticui/arithmeticui.xml</value>
<value>${iris.plugins.dir}/arithmeticui/arithmeticui-remote.xml</value>
</list>
</constructor-arg>
<constructor-arg>
<value>false</value>
</constructor-arg>
<constructor-arg>
<bean class="com.sri.iris.kernel.serviceloader.CombinedApplicationContext">
<constructor-arg>
<list>
<ref bean="com.sri.iris.plugins.client.ui.ApplicationContext" />
</list>
</constructor-arg>
<constructor-arg>
<ref bean="com.sri.iris.ApplicationContext" />
</constructor-arg>
</bean>
</constructor-arg>
<property name="classLoader">
<ref bean="com.sri.iris.plugins.client.arithmeticui.ClassLoader"/>
</property>
</bean>
</beans>Note the following differences between this file and the "plugin.xml" file which is generated for non-client plugins:
Any "plugin-client.xml" file found in a plugin folder will be loaded when IRIS runs.
A dependent class loader "com.sri.iris.kernel.DelegateClassLoader" is set. This class loader is used only if the plugin also supplies a "plugin.xml" file, meaning there are server beans. It is not recommended that a plugin provide both client and server beans, but it is supported. The delegate class laoder is used when IRIS client and server is run ion the same Java VM. It resolves the reference to the server class loader in the Java VM so that the same class loader can be used by the client.
Client plugins automatically depend on the "ui" plugin in IRIS, and the class loader and application context dependencies for the "ui" dependency are set in plugin-client.xml.
Plugin dependencies are not set in plugin-client.xml. This is because it is ambiguous whether or not the client plugin depends on other server plugins or other client plugins. IRIS assumes the dependency list refers to server plugins, but also provides an extra parameter to refer to client plugin dependencies. To specify dependencies on other client plugins, use the property "clientDependencies" in the plugin config which is a list of plugin names.
The plugin-client.xml file also loads the "arithmeticui-remote.xml" file which declares remote references.
This file specifies any remote references for the client plugin. It is recommended that all remote references be declared in this file, and the plugin bean file (in this case "arithmeticui.xml" refer to the remote references. In this way the remoting is cleanly separated from the plugin configuration. For example, the following is the arithmeticui-remote.xml file:
<beans default-lazy-init="true">
<bean id="remoteProxyReference-adder" abstract="true" dependency-check="none" parent="remoteProxyReferenceBase">
<property name="serviceUrl">
<description>
The service URL is registered by the remote proxy when the initial connection is made.
</description>
<ref bean="iris.proxy.plugins.url" />
</property>
<property name="serviceContext">
<description>
The service Context which gets added onto the service URL.
</description>
<value>com.sri.iris.plugins.unittest_adder.ApplicationContext</value>
</property>
<property name="connectionId"><ref bean="iris.proxy.singletonConnectionId" /></property>
</bean>
<bean id="remoteProxyReference-multiplier" abstract="true" dependency-check="none" parent="remoteProxyReferenceBase">
<property name="serviceUrl">
<description>
The service URL is registered by the remote proxy when the initial connection is made.
</description>
<ref bean="iris.proxy.plugins.url" />
</property>
<property name="serviceContext">
<description>
The service Context which gets added onto the service URL.
</description>
<value>com.sri.iris.plugins.unittest_multiplier.ApplicationContext</value>
</property>
<property name="connectionId"><ref bean="iris.proxy.singletonConnectionId" /></property>
</bean>
<bean id="com.sri.iris.plugins.adder.IAdder" parent="remoteProxyReference-adder"
dependency-check="none" depends-on="IrisClientRemoteServices">
<property name="beanName" value="com.sri.iris.plugins.adder.IAdder" />
</bean>
<bean id="com.sri.iris.plugins.multiplier.IMultiplier" parent="remoteProxyReference-multiplier"
dependency-check="none" depends-on="IrisClientRemoteServices">
<property name="beanName" value="com.sri.iris.plugins.multiplier.IMultiplier" />
</bean>
</beans>Here remote references are declared using the IRIS proxy plugin. This is a prototype which allows reeentrant RMI capability between Java and C# applications. Note that any remoting capability could be used here. For example, Spring has built-in facilities for declaring remote references using Hessian and other protocols. Note that when using the proxy for remoting, IRIS automatically resolves runtime discovery issues using the keychain server. If another protocol is used, the keychain server should also be used for discovery. Fr example, the server beans can register ports it runs servers on in the keychain, and the client can look up that information from the keychain on startup.
The unit tests differ slightly because the application context hierarchy is different for UI plugins:
ApplicationContext baseContext = (ApplicationContext)rootContext.getBean("com.sri.iris.ApplicationContext");
ApplicationContext pluginAppContext = (ApplicationContext)baseContext.getBean("com.sri.iris.plugins.client.ApplicationContext");
// Make sure the plugin AppContext is created
ApplicationContext arithmeticuiAppContext = (ApplicationContext)pluginAppContext.getBean("com.sri.iris.plugins.client.arithmeticui.ApplicationContext");
ui = (ArithmeticUIImpl)arithmeticuiAppContext.getBean("arithmeticui");The UI plugins are loaded as part of the bootstrap process into a separate application context. See this link for details.
The following is the arithmeticui.xml configuration file:
<beans>
<bean class="com.sri.iris.plugins.arithmeticui.ArithmeticUIImpl" id="arithmeticui"
init-method="startup" destroy-method="shutdown" lazy-init="true">
<description>
Puts up an IRIS UI for the adder and multiplier unittest plugins
</description>
<property name="adder">
<ref bean="com.sri.iris.plugins.adder.IAdder" />
</property>
<property name="multiplier">
<ref bean="com.sri.iris.plugins.multiplier.IMultiplier" />
</property>
<property name="viewerHost">
<ref bean="com.sri.iris.ui.IViewerHost" />
</property>
<property name="appUri"><value>http://iris.sri.com/app/arithmeticui</value></property>
<property name="appName"><value>Airthmetic UI</value></property>
<property name="appFolder"><value>Debug</value></property>
</bean>
</beans>Here com.sri.iris.ui.IViewerHost is the IRIS plugin which controls viewing of IRIS applications. In order to install the UI, a URI, application name and folder must be specified. Here is the source ArithmeticUIImpl.java:
public class ArithmeticUIImpl implements IPlugin
{
private IAdder adder;
private IMultiplier multiplier;
private IViewerHost viewerHost;
private String appUri;
private String appFolder;
private String appName;
private boolean started = false;
private Logger log = Logger.getLogger(ArithmeticUIImpl.class);
public void startup() throws Exception {
if (getAdder() == null) {
throw new AbortException("IAdder not set");
}
if (getMultiplier() == null) {
throw new AbortException("IMultiplier not set");
}
if (viewerHost == null) {
throw new AbortException("IViewerHost not set");
}
log.info("Starting up ArithmeticUIImpl plugin");
viewerHost.installNamedViewer(appUri, new ArithmeticUIPane(adder, multiplier), null, null, false);
viewerHost.addToApplicationTree(appName, appUri, appFolder);
started = true;
}
public void shutdown () {
log.info("Shutting down ArithmeticUIImpl plugin");
}
protected IViewerHost getViewerHost() {
return viewerHost;
}
protected boolean isStarted() {
return started;
}
// ... <snip />
}The class "ArithmeticUIPane.java" is not listed here but can be found after running the plugin SDK tests. It simply displays a UI with text fields and buttons that allows the user to add or multiply numbers using the IAdder and IMultiplier services.
The arithmeticui plugin can be tested by executing "ant test" in its test directory. This will make sure the adder, multiplier and arithmeticui plugins are compiled, and then will execute unit tests for arithmeticui. The unit tests written for arithmeticui are not shown here, but they simply test integration. They access the IAdder and IMultiplier interfaces to make sure they will work when the arithmeticui plugin is loaded into IRIS. The next time IRIS is run, under the "Debug" folder will be an application named "Arithmetic UI". To see this app, execute the plugin SDK unit tests and then run IRIS.