EclipseCon 2009 submission

November 20th, 2008

In the spirit of transparency and openness, the submission process for EclipseCon 2009 conference is very refreshing. As the main site says

The selection is done transparently and in the open. Specifically, all submissions are made through our submission system. Everyone in the community (including you) is invited to review the submissions, ask for more information, provide comments and critiques – just as everyone in community is invited and encouraged to do for Eclipse bugs and features.

I have just submitted a proposal to talk about our experiences in developing a visual designer for form-based applications, built on top of rich Eclipse tools such as the core platform itself, as well as JDT, EMF, GEF, VE and JEM. The proposal is called “On The Shoulders of Giants: Harnessing the Power of Eclipse Enterprise Ecosystem“, and you are more than welcome to leave comments on the entry. Here is the abstract:


Code reuse in large projects is not just a trendy buzzword. If you can build upon solid, evolving and well tested foundations that are developed and maintained by committed teams, you have much less code to test, integrate and support. The Eclipse Ecosystem is a prime example of an extremely rich foundation for building enterprise grade applications, and this talk is going to show the diverse, yet interoperable technologies that allow businesses to concentrate on their specific requirements without reinventing the plumbing layers.

A part of a larger client-facing offering, Amdocs Smart Client Designer is an advanced visual designer that allows seamless collaboration between designers and developers in creating complex business form-based applications for Support Call Centers in the telecommunication industry. Harnessing the power of such technologies as JDT, GEF, EMF, JEM and VE has allowed us to dramatically reduce the effort to build the basic blocks of the tool (such as persistence, code generation and java syntax tree manipulations). In addition, core platform features such as task and job managers, builders, natures, markers and many more are enabling user-centric asynchronous business flows in a clean, simple and maintainable way.

Building on top of a vibrant and evolving ecosystem has been a pleasant experience, further strengthened by a recent migration to Java 6, Eclipse 3.4 and the latest version of the dependent plugins. In addition, we are going to talk about the “Eclipse way” of designing the flows, where the existing core features guide the design process to facilitate familiar flows and simpler implementation.

If you are developing a large Eclipse-based offering, or considering Eclipse as the vehicle for your next enterprise-grade tool, come to our session to hear about our experiences in this area.

The usual way of creating a Java project is by showing a wizard that guides the user through the different configuration options. In case you want to customize the existing wizard (add a new page, for example), you specify the org.eclipse.ui.newWizards extension point in your plugin.xml and specify a new wizard section with project attribute set to true. But what if you want to create a new Java project in a purely programmatical way without showing any wizard dialog?

The published API to configure the Java project is the org.eclipse.jdt.ui.wizards.JavaCapabilityConfigurationPage. It has two public utility methods, createProject and configureJavaProject that do most of the heavy lifting. The first problem is that the second method is not static (which means that you need an instance of this page to call it), and the second problem is that the implementation of these methods is calling into internal classes. Fortunately, all the APIs that are required to create a new custom Java project are published. Let’s take a look at the sequence of steps.

First, call ResourcesPlugin.getWorkspace().getRoot().getProject(projectName) to get the IProject handle. Calling exists() will let you know whether the project named projectName already exists. All the following steps assume that this project does not yet exist.

Next, call project.create and project.open. This will create and open the new project.

Now it’s time to create the source folders for the project. Suppose you want to create a source folder named custom. Start by creating a new IPath instance with IPath customPath = new Path(“custom“). Next, call IFolder customFolder = project.getFolder(customPath) and finally customFolder.create(true, true, null). If you have more than one source folder, repeat the steps above for each one of them. A source folder can be a link to a directory on your hard disk. To mark the source folder as link, call IFolder.createLink API, passing the path to the hard disk location. The second parameter can be IResource.ALLOW_MISSING_LOCAL to allow the creation of the link source folder even if the linked target folder does not (yet) exist.

Next step is configuring the classpath entries for the new project. The classpath entries include:

  • your source folders
  • your custom jars
  • the JRE container

To create an IClasspathEntry instance that corresponds to your source folder, call JavaCore.newSourceEntry(project.getFullPath().append(customPath)) where customPath is the IPath instance created above.

The simplest way to create the classpath entries for the JRE container is to use the PreferenceConstants.getDefaultJRELibrary() method. This will return the classpath entries for the default JRE of the workspace. If you want to use another JRE, you would need to iterate through the VM install types returned by the JavaRuntime.getVMInstallTypes(), and iterate through the VM installs for each one of them. The IVMInstall does not expose an API to check its Java compliance. Cast it to AbstractVMInstall and check its getJavaVersion() to the specific compliance spec string (such as JavaCore.VERSION_1_6 for Java 6.0, for example). Once you find a matching JRE, you can get its classpath entries with the following:

  • Get the JRE container path with IPath containerPath=new Path(JavaRuntime.JRE_CONTAINER)
  • Assuming that you have found a matching AbstractVMInstall, create its path by IPath vmPath = containerPath.append(vmInstall.getVMInstallType().getId()).append(vmInstall.getName())
  • Now, the classpath entry of that JRE is simply JavaCore.newContainerEntry(vmPath)

To create a classpath entry for a custom jar (with optional javadoc and source attachments), use JavaCore.newLibraryEntry API. The first parameter is the path to the jar file that can be constructed with the various FileLocator APIs. The sources can be specified via the second and the third parameters. The javadoc attachment is passed in the classpath attribute array (fifth parameter). To construct an IClasspathAttribute entry, use JavaCore.newClasspathAttribute API with IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME name and “file:filePath” value where the filePath is the URL of your javadoc file.

Next step is to create a IJavaProject by using IJavaProject javaProject=JavaCore.create(project).

Next step is to add the Java nature to the new project. This is done by getting the IProjectDescription of the project with the IProject.getDescription, creating a new String[] array to hold an additional entry, copying all the existing nature IDs (from IProjectDescription.getNatureIds() API), adding the JavaCore.NATURE_ID entry and setting back the description.

Next step is to create the default output folder for the project. The name of the default output folder can be taken from the PreferenceConstants.getPreferenceStore().getString(PreferenceConstants.SRCBIN_NAME) and the folder itself is created with the same IProject.getFolder and IFolder.create APIs as for the source folders. The only difference is in that the output folder needs to be marked as derived. In order to do this, pass IResource.FORCE | IResource.DERIVED as the first parameter to the IFolder.create method and call IFolder.setDerived(true) afterward.

At this point, the project is ready to be refreshed (before the actual classpath is set). Call project.refreshLocal with IResource.DEPTH_INFINITE).

Next step is to set the classpath on the java project. Call javaProject.setRawClasspath passing the array of all the classpath entries created before.

The next optional step involves setting custom natures and moving the Java builder in the builder sequence. If your project is going to have a custom nature with custom builders, you first need to add the custom nature(s) to the project description. This is done in exactly the same way as with the java nature above. After setting the nature, you might want to move the default Java builder down in the sequence. This is relevant if the Java builder (the one that compiles the source Java classes) needs to be invoked after your custom builder(s) – the ones that generate additional Java classes, perhaps. To do so, get the build spec sequence from IProjectDescription.getBuildSpec array and shuffle the commands as necessary.

The final step is to set the Java compliance settings on the project. This is done to enforce the different Java rules. Get the current settings from IJavaProject.getJavaOptions. Next, set the following entries in the map:

  • JavaCore.COMPILER_COMPLIANCE to the matching JavaCore constant, such as JavaCore.VERSION_1_6.
  • JavaCore.COMPILER_SOURCE to the matching JavaCore constant, such as JavaCore.VERSION_1_6.
  • JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM to the matching JavaCore constant, such as JavaCore.VERSION_1_6.
  • JavaCore.COMPILER_PB_ASSERT_IDENTIFIER to JavaCore.ERROR to prevent using assert as a valid identifier.
  • JavaCore.COMPILER_PB_ENUM_IDENTIFIER to JavaCore.ERROR to prevent using enum as a valid identifier.

After setting these entries, store the options back with IJavaProject.setOptions

Congratulations – now you have a new Java project with custom source folders (some of them linked), custom jars, an output folder and custom builders set. And all of this without the user having to click through the wizard screens.

The previous entry has talked about the implementation details of the Eclipse Help view, and how it is using an embedded HTTP server powered by Jetty to serve dynamic content from a number of sources. Once you have such a custom server in place, you can embed a Browser control in any view / editor and call its setUrl method to show the required content. The links in the browser control will be handled in the same way as in the usual browser control – relative links will be served by the same HTTP server, and absolute links can point to content on the intranet or even external internet pages. But what about the live help actions?

The org.eclipse.help.ILiveHelpAction interface defines an entry point from Eclipse Help view “back” into Eclipse itself. If you include the PLUGINS_ROOT/org.eclipse.help/livehelp.js in your HTML document, you can attach a javascript:liveAction handler on the links in your document. This handler gets three parameters:

  1. ID of the plugin that contains the class defined by the second parameter
  2. Fully qualified class name that must implement the ILiveHelpAction interface
  3. Possibly empty parameter passed to the setInitializationString of the live help action instance

When such a link is clicked, the setInitializationString() and run() methods of the specified class are called. How does this work, and how can this be extended to work outside the Help view in any embedded browser control?

Here is the flow of execution in Eclipse 3.4 (all of these details are internal and subject to change in the future versions).

  • The Help view is a collection of HTML pages. These are served from the org.eclipse.help.webapp plugin.
  • One of the pages has an IFrame with 0*0 dimension named liveHelpFrame.
  • The implementation of liveAction JavaScript method in the livehelp.js locates that frame and sets its location to the URL that encodes the three parameters (plugin ID, class name and argument string)
  • A special livehelp servlet registered on the Jetty server gets this request. This servlet is implemented in org.eclipse.help.internal.webapp.servlet.LiveHelpServlet class.
  • The doGet method checks that the embedded web server is running, and that the first two parameters are not null.
  • The helper BaseHelpSystem.runLiveHelp locates the plugin bundle and loads the specified class. An instance of this class is created (the implicit assumption is that there is a public no-arg constructor).
  • If specified, the argument string is passed to the setInitializationString of the created instance.
  • A daemon thread based on this instance is created and started. Note that if your live help action needs access to the UI layer (to display a dialog, for example), the logic in the run method needs to be wrapped in Display.runAsync call.

rent a car bulgaria
Note that the above flow does not mention the actual contents of the 0*0 IFrame being used in the process. The whole reason for this frame’s existence is to make a call to the live help servlet. The LiveHelpServlet.doGet does not make anything with the resp object.

As can be seen from this flow, including the livehelp.js and putting a javascript:liveAction on the HTML anchor is not enough. The implementation of the JavaScript liveAction method makes certain checks to ensure that the browser is the one from the Help view. As such, if you click on this link in a browser control placed in another view, it will not result in invoking the specified action.

Until this functionality is supported by Eclipse in a published way, we can mimic this flow in a custom view / editor. Here are the basic steps – assuming that you have a browser control and a Jetty server / servlet already configured.

  • Add two more servlets. The first will serve the “core” content, such as a custom livehelp.js, the initial blank content of the invisible IFrame and possibly a custom CSS. The second will handle the live help requests.
  • Modify your existing servlet (that serves the usual HTML pages) to inject two pieces into every HTML file that it serves.
    • The first piece is a HEAD tag to include the livehelp.js from the new core servlet. This piece can also include a link to the custom global CSS file.
    • The second piece is a 0*0 IFrame named liveHelpFrame with initial content set to a blank page served by the core servlet. It is very important to obey the cross-scripting limitations placed by the browsers to make sure that a javascript handler on the main content can change the location of this IFrame.
  • Implement the livehelp.js based on the core Eclipse script. You can simplify the implementation since you know exactly where to look for that IFrame. Get the URL of the window, strip away the trailing parts and replace them to point to your live help servlet. Add plugin ID, class name and the argument to the resulting URL to make sure that these are passed to the servlet. Finally, set the complete URL to be the location of the invisible IFrame. This will result in a call to the servlet, but leave the visible browser content unchanged.
  • The implementation of the core servlet is very simple, fetching the required content from the bundled files (just like the servlet that is serving the rest of your files).
  • The implementation of the live help servlet is the same as the core Eclipse one. Parse the parameters, make sure that the plugin ID and class name are present, load the class, create an instance of that class and then run its run method on a new daemon thread.

Congratulations – you have your own browser that is able to display a rich collection of interlinked documents and invoke actions defined in your plugin classes, interacting with other parts of your plugins / application.

How is Eclipse Help view implemented? This view is a rich collection of interlinked documents that provide the usual functionality of embedding images and navigating between different pages. In addition, it supports live help actions – hyperlinks that can call Eclipse actions (Java code). How is this implemented?

In a usual setup, the HTML content is stored in the file system. If the requested HTML page contains embedded images, these are stored as separate files, and requested separately by the browser. However, the main contents of Eclipse Help view come from one single file – the org.eclipse.platform.doc.user.jar in the plugins folder. This jar contains around one thousand files (in Eclipse 3.4), including HTML and PNG images. How do these get displayed in the Eclipse Help view?

A straightforward approach would be to “explode” the contents of this jar at runtime (the first time the Help view is activated) and then link directly to the files in the filesystem. This is simple to implement, but would require some bookkeeping to clean the files on closing the workspace. Also, you’re going to pay the performance penalty the first time the files need to be extracted from the archive and written to the disk. Instead, Eclipse 3.4 uses an embedded Jetty HTTP server with custom servlets to serve the content of the Help view.

While the implementation of the Help view is deep inside the internal packages of org.eclipse.help.webapp plugin, this functionality can be recreated by using public extension points (thus ensuring upgradability to the next Eclipse versions). The steps below describe the general setup of an embedded Help HTTP server.

First, you need to specify three extension points in your plugin.xml. For the complete example see the plugin.xml of the org.eclipse.help.webapp plugin. The extension points are:

Jumping a little ahead of time (the complete structure will be explained later), the URLs requested from the HTTP server will look like this:

http://127.0.0.1:12345/primaryID/secondaryID/relative/path/to/your.html

The primaryID is the HTTP context ID specified in the first extension point above. It is also used in the specification of the second and third extension points. The secondaryID allows mapping your content via different servlets (see later). In the simplest example (where all content is coming from the same archive), you will have only one servlet specified in the third extension point. The last identification string is specified on the second extension point – it is the other.info filter on the service selector. This string must be the same as the one set during the initialization of Jetty server (see below).

Your MANIFEST.MF will need two changes. The first one is the Import-Packages section and should have the following entries:

  • javax.servlet
  • javax.servlet.http
  • org.osgi.service.http

The second one is in the Require-Bundle section and should have the following entries:

  • org.eclipse.equinox.http.jetty
  • org.eclipse.equinox.http.servlet
  • org.eclipse.equinox.http.registry

These sections will make sure that you will be able to use the relevant classes in your custom Jetty server and servlets.

The next step is the class that controls the lifecycle of the embedded Jetty server. This is the 127.0.0.1:12345 part in the URL above – it is a local HTTP server that is listening on port 12345. Since this specific port may be taken by another application, we are going to ask Jetty to auto select an available port. The complete implementation of the Jetty configurator can be found in the org.eclipse.help.internal.server.WebappServer class, and the main steps are:

  • Make sure that you’re running only one instance of the HTTP server (instead of creating a new instance for each HTTP request).
  • The http.port parameter should be set to 0 to allow Jetty to auto-select an available port.
  • The context.path parameter should be set to the HTTP context ID (primaryID in the example above).
  • The other.info parameter should be set to the same value as the service selector filter in the second extension point in the plugin.xml.
  • INFO / DEBUG messages of Jetty should be suppressed.
  • To check that Jetty has successfully started, get the org.eclipse.equinox.http.registry bundle and check that its state is RESOLVED.
  • To get the Jetty port (for creating the URLs), get the service reference for org.osgi.service.http.HttpService class and (other.info=yourServiceSelectorFilter) filter. Then, get the http.port property and cast it to Integer.

The next step is to create a custom servlet that will intercept the relevant HTTP requests and load the content from your archive. The complete (and very simple) example can be found in the org.eclipse.help.internal.webapp.servlet.ContentServlet class (registered with the third extension point above). In its init() method it creates a custom connector instance (more info below) and uses it in the doGet() and doPost() methods.

The last piece is the connector itself. It analyzes the incoming request, maps it to the corresponding resource and then transfers the resource contents to the response output stream. The beauty of this connector is that the content can come from anywhere. It can be a local file, a file in an archive, or it can be dynamically generated (corresponding to the requested resource, of course).

While EclipseConnector looks at a variety of sources to get the content and provides a custom error page implementation, the logic is very simple. In a simple example where all the content is coming from one archive, you create a URLClassLoader pointing to that jar (this should be done in the constructor to make the subsequent requests faster) and use the getResourceAsStream passing the trailing portion of the URL (stripping away the host, port, primary ID and secondary ID parts). If the returned InputStream is null, you can return a custom error page.

While the above may sound an overkill, it is quite useful and much more flexible than shipping a huge collection of separate files. With a custom HTTP server and a servlet you can control the contents of error page, fetch the content from multiple locations or even create the content dynamically from a database or another source.