Extending ICE with custom Items
What you'll need:
- Eclipse - You should have a copy of the Eclipse IDE installed. You need to download the Eclipse IDE for RCP/RAP developers so that you will have the Plug-in Development Environment (PDE) installed by default. If you have a different version of Eclipse installed, you can install the PDE with the built-in "Install New Software" tool.
- ICE built from source - You will need the ICE source code downloaded into your workspace and a working build. You can achieve this by following our ICE build guide.
- Command Line Interface - Access to a command line interface for testing, either CMD.exe on Windows, or your favorite shell on Unix-like systems. This tutorial will not have much to say on how you use these, but if you're totally clueless, you can learn here.
- Experience programming Java, but not too much.
There is some good news and bad news when it comes to extending ICE. The good news is that it is really easy once you know the process and understand a little bit about ICE's architecture. The bad news is that you have to learn all of that! We created this tutorial series to ease the learning curve and put developers on the "fast track." We have left out as much filler and fluff as possible to keep from wasting your time, but we have put as many links to external reference sources as possible in case you want to learn more. Our hope is that these tutorials are quick, fun and that you will be off writing your own plug-in in no time!
ICE is built on an implementation of the Services Gateway initiative (OSGi) framework called Equinox, which is a part of Eclipse. It is extended by writing plug-ins, and it has a fairly typical plug-in-based architecture. A plug-in, also called a bundle, is a collection of a specific set of files, all of which can be generated and manipulated from within Eclipse. Each plug-in publishes one or more services that are registered and distributed where they are needed by the OSGi framework in ICE. (The exact process of doing that is covered later.) As you may have guessed, there are many services from many plug-ins in ICE! The structure of a plug-in is fairly simple:
- A META-INF directory that contains a MANIFEST.mf file
- An OSGI-INF directory that contains OSGi component definitions
- A pom.xml to enable the Maven build
- A directory containing the source code files of the bundle
If some of the things in this list are not immediately familiar to you, don't worry. We will go over each piece in detail before the tutorial is over.
Each ICE plug-in must subclass the Item class and implement the ItemBuilder interface found in the eclipse.org.ice.item bundle. The ItemBuilder for your plug-in is the piece that the OSGi will register as a service with ICE, and which will be responsible for constructing and serving your Item. The OSGi parts of the plug-in are necessary to see that the plug-in is booted by the framework and the ItemBuilder service is registered, but the custom Item that you have written does all of the actual work, such as launching a simulation or creating an input file.
As you read the tutorial, keep in mind that "plug-in" and "bundle" are used interchangeably. They are the exact same thing. "Framework" and "platform" are not the same things, though. ICE is the platform (itself based on the Eclipse platform) that exposes and delivers services and capabilities wired together by the Equinox/OSGi framework.
The ICE Item Class
The Item class is a very important class in ICE and is indeed the heart of the project. Any service provided by ICE—whether it is responsible for launching a job, creating an input file, visualizing output data, performing analysis studies or data mining—is an Item. Even services that create Items, such as the Job Profile Editor, are themselves Items! The exact details of how the Item class works are published elsewhere, but the important point is that the Item does everything it can to make it very simple for you to provide a description of your activities to clients, receive feedback and perform a task or tasks in response. You do not have to worry about how your Item is written to the database or if it is transferred across the network in the proper way because these things are automatically handled by the class for 99% of business cases. Items communicate with the rest of the platform via Forms (eclipse.org.ice.datastructures.form), special data structures that represent the Item outside of the class in the graphical user interface and other places. All of the information that you want to be exposed to clients and available for modification should be stored in your Form, but any internal state that you require should be stored inside the Item or on disk where it can not be modified by users. When someone creates an instance of your Item using the platform, they will be presented with your Form, asked to edit it and submit their changes and then allowed to direct the Item to perform some operation with the data. The whole workflow between the user, ICE and your plug-in goes something like this:
In reality there are a lot of other things happening behind the scenes, like persistence, and there is no direct link between the client and the Item. ICE always acts as a proxy so that it can do things like preserve the state of user data, transmit across the network, or draw to the screen. This is essentially how it works, though. If you look at the MyFirstItem partition closely, you'll notice that there are only three activities in that partition and it is these three activities that make up the bulk of any Item subclass. Keep in mind that at each of the decision points, a client can review and modify the information on the Form and resubmit it if they want to change something. Items do not really care about those activities, but they can and do happen very often. Each of the activities in the MyFirstItem partition has its own operation on the Item class and they can be modified by overriding them, as shown in the UML class diagram below.
If you are not familiar with the UML, that's OK. This picture probably still makes perfect sense to you. Note the relationship between MyFirstItem and Item, depicted by the solid line with a clear arrow head at the top. This shows that MyFirstItem inherits directly from Item. That is true for most Items, but in some cases, such as JobLaunchers, there are intermediate subclasses of Items that provide additional utilities and functionality. MyFirstItem also has four operations on it: a constructor with an argument, setupForm(), reviewEntries(), and process(). Each operation does the following:
public MyFirstItem(IProject project)
- The constructor for your Item. It is used to instantiate your Item and it must take an org.eclipse.resource.IProject as an argument.
public void setupForm()
- The operation that configures your Forms that will represent your Item to the rest of the platform. This operation is called when your Item is constructed. You should fully specify the data that your Item will require here and put it in your Form, but not the behavior that your Item will perform when it is told to perform an action.
protected FormStatus reviewEntries(Form preparedForm)
- This is a protected operation, meaning that it can not be publicly accessed by clients outside of the class, that is responsible for handling any special dependency management that you might need to perform. The Item can handle all sorts of dependencies by itself, based on the data structures you give it, but if you need to do something special, do it here. This operation is called each time an updated version of the Form is submitted to the Item.
public FormStatus process(String actionName)
- This operation is responsible for performing a specific task, given by the action name, using the data provided to the Item on an updated Form. This operation is responsible for all of the business logic and domain behavior of your Item. Your Item can perform multiple tasks based on its data and the exact one to perform is given as an input argument.
If you consider the Item Manipulation Activity Diagram and operations together, it is pretty clear to see what does what. The constructor is called by ICE when it instantiates your Item. The setupForm() operation is called during construction to and is first activity in the MyFirstItem partition. The second activity is reviewEntries(), if it is needed, and the final activity is process(), with a specified action to perform. There is some work that goes on inside of your Item before reviewEntries() is called, but it is mostly related to verifying that theForm that has been given to your Item is the same Form that it gave when it was requested.
The ICE ItemBuilder Interface
Every plug-in in ICE is required to provide an Item and an ItemBuilder that knows how to create that Item. Builders are used in ICE to provide both dynamic services and prevent tight dependency coupling between the platform and subclasses of Items. The builder pattern is used instead of a simple factory because there can be, depending on the situation, a lot of logic required to properly construct and setup an Item. ItemBuilders have to provide a small amount of information to ICE so that it can manage its set of builders internally, including the name of the Item that can be instantiated and the type of that Item. The name can be anything, but the type must come from a preset enumeration of types that will be discussed later. Since ItemBuilder is an interface, your MyItemBuilder class realizes it will have to store this information and implement the operations. The operations themselves are fairly straight forward:
public String getItemName()
- ICE must know the default name or descriptive type of your Item and this operation provides that. You can change the name of your Item, but the name returned from this operation should stay consistent.
public ItemType getItemType()
- This operation returns the type of your Item. More on this later.
public Item build(IProject project)
- This operation is responsible for constructing and finalizing the configuration of your Item and it must take an org.eclipse.resource.IProject as an argument (which is in turn passed to your Item). If additional steps are required to constructor your Item than simply calling the constructor, they must be done here. There is no other place where they could be called in ICE. Simply put, what happens in build(), stays in build()!
It is not necessary that MyItemBuilder provide a constructor because Java will generate a default one.
ItemBuilders do not normally play much more of a role than this in ICE, but their function is critical. Their only purpose is to create Items, so they do not have much else to do after that. There are some cases where they are used for trickier things, but those will be explained later.
Simple steps to create your own Item
With all of this technical mumbo-jumbo in mind, you are almost ready to create your own plug-in for ICE! The steps to do that are the same for almost every plug-in:
- Generate an OSGi bundle with Eclipse. This is a very simple, semi-automated operation in Eclipse PDE.
- Import the required plug-ins and packages in the Dependencies tab of your MANIFEST.mf file.
- Create your custom builder that realizes the ItemBuilder interface in your bundle.
- Create a subclass of Item that provides its own constructor and overridden implementations of setupForm(), reviewEntries(), and process().
- Create an OSGi component definition to register your ItemBuilder as a service.
- Add your plug-in to the ICE launch configuration for testing and debugging and to the main build configuration for deployment.
These steps are covered in detail in the rest of the tutorial, but before going through them we will take a brief detour to discuss the special case of JobLaunchers. If you need to create a plug-in for ICE that launches a code or task on some computer, ICE can actually generate this plug-in for you if you provide it with a little information. We'll look at that process next and we will use it as an example when we actually write Java code for a plug-in from scratch.
Please continue to Part 2: Using the Job Profile Editor to create Job Launchers