Overview

Add p2 update functionality to an e(fx)clipse application – Eclipse RCP Cookbook

No Comments

The Equinox p2 project provides a provisioning infrastructure that can be used to update or install features into an OSGi application.

The previous recipe showed how to add p2 update functionality to an Eclipse 4 application in general. This recipe focuses on how to add an update mechanism to a JavaFX Eclipse 4 application using the e(fx)clipse 2.0.0 Runtime.

Cookware

  • e(fx)clipse IDE 2.0.0 (nightly build based on Eclipse Mars at the time of writing this blog post)
    • JavaFX tooling and runtime for Eclipse and OSGi
    • http://downloads.efxclipse.bestsolution.at/downloads/nightly/sdk/
    • Ensure the IDE is started with Java 8 if you have multiple Java versions installed
    • After starting the IDE and choosing a workspace, update the IDE to ensure the latest service release is installed. This is necessary to get the latest bugfixes and security patches.
      • Main Menu → Help → Check for Updates

Ingredients

To get started fast with this recipe, you can use the JavaFX version of the Eclipse RCP Cookbook – Basic Recipe we have prepared for you on GitHub.

To use the prepared basic recipe, import the project by cloning the Git repository:

  • File → Import → Git → Projects from Git
  • Click Next
  • Select Clone URI
  • Enter URI https://github.com/fipro78/e4-cookbook-basic-recipe-fx.git
  • Click Next
  • Select the master branch (or the services branch which adds OSGi declarative services)
  • Click Next
  • Choose a directory where you want to store the checked out sources
  • Click Next
  • Select Import existing projects
  • Click Next
  • Click Finish

Preparation

Step 1: Update the Target Platform

  • Open the target definition de.codecentric.eclipse.tutorial.target.target in the project de.codecentric.eclipse.tutorial.target
  • Remove the Software Site http://download.eclipse.org/efxclipse/runtime-released/1.2.0/site by selecting it in the Locations section and then clicking Remove
  • Add a new Software Site by clicking Add… in the Locations section
    • Select Software Site
    • Software Site for the e(fx)clipse 2.0.0 nightly build
      http://download.eclipse.org/efxclipse/runtime-nightly/site
    • Expand FX Target and check Target Platform Feature
    • Uncheck Include required software
    • Click Finish 
  • Activate the target platform by clicking Set as Target Platform in the upper right corner of the Target Definition Editor

Step 2: Prepare the application plug-in

To keep it simple, an update handler will be added to the application plug-in.

  • Update the bundle dependencies
    • Open the file META-INF/MANIFEST.MF in the project de.codecentric.eclipse.tutorial.app
    • Switch to the Dependencies tab
    • Add the following bundles so we can make use of the extended e(fx)clipse services
      • org.eclipse.core.runtime
      • org.eclipse.fx.core
      • org.eclipse.fx.core.p2
  • Update the application model
    • Open the file Application.e4xmi in the project de.codecentric.eclipse.tutorial.app
    • Add a command
      • Application → Commands → Add
      • Set Name to Update EFX
      • Set ID to de.codecentric.eclipse.tutorial.app.command.updateEfx
    • Add a handler
      • Application → Handlers → Add
      • Set ID to de.codecentric.eclipse.tutorial.app.handler.updateEfx
      • Set the Command reference to de.codecentric.eclipse.tutorial.app.command.updateEfx via Find… dialog
      • Create a handler implementation by clicking on the Class URI link
        • Set Package to de.codecentric.eclipse.tutorial.app.handler
        • Set Name to FXUpdateHandler
    • Add a main menu to the application for making the update command accessible for a user
      • Application → Windows and Dialogs → Trimmed Window
      • Check Main Menu in the details view of the Trimmed Window
      • Select the now visible Main Menu in the Application Model tree below the Trimmed Window
        • Set ID to org.eclipse.ui.main.menu
      • Add a Menu
        • Set ID to org.eclipse.ui.file.menu
        • Set Label to File
      • Add a Handled Menu Item to the File Menu
        • Set the label to Update EFX
        • Set the Command reference to the Update EFX command via Find … dialog

Step 3: Update the Product Configuration

  • Open the file de.codecentric.eclipse.tutorial.app.product in the project de.codecentric.eclipse.tutorial.product
  • Switch to the Dependencies tab
    •  Add
      • org.eclipse.equinox.p2.core.feature
    • Click Add Required to add the ECF features required by the p2 core feature
  • Switch to the Overview tab
    • Ensure that a proper version is set to the Version field, e.g. 1.0.0

Step 4: Implement the update handler

At this point we start making use of the extended OSGi declarative services that are provided by the e(fx)clipse runtime. Since we will use the org.eclipse.fx.core.update.UpdateService which abstracts out the p2 API, performing a p2 update basically consists of two steps (instead of three):

  1. Check if there are available updates for the specified update operation
  2. Perform the update by executing a provisioning job if updates are available

For a good user experience it is best practice to execute those operations in background threads to keep the UI responsive. To keep the focus on p2 updates in this recipe, I will not go into the details of background processing here. But we will show the usage of the extended org.eclipse.fx.ui.services.sync.UISynchronize implementation, which will grant us a really nice opportunity for using a Callable.

You should also consider giving feedback to the user about the update operation results between the steps. For example, if updates are available, the user should be asked whether to perform the update or not. For this the JavaFX dialogs API is used.

4.1 Update handler preparation

  • Get the following values injected in FXUpdateHandler#execute()
    • org.eclipse.fx.core.update.UpdateService
      OSGi service that is used to perform application updates via p2.
    • org.eclipse.fx.ui.services.sync.UISynchronize
      Extended helper class for executing code in the UI thread.
    • org.eclipse.fx.ui.services.restart.RestartService
      OSGi service that allows to restart the application by granting the ability to clear the persisted state.
public class FXUpdateHandler {
 
	@Execute
	public void execute(
            UpdateService updateService, 
            UISynchronize sync, 
            RestartService restartService) {
		...
	}
 
}

4.2 Check if there are updates available

Via UpdateService#checkUpdate(ProgressReporter) you can check whether updates for the installable units are available or not. It will return a org.eclipse.fx.core.operation.CancelableOperation on which “handlers” can be registered to be able to react on different results. For example, to give the user feedback if the operation is cancelled, a Runnable can be registered via CancelableOperation#onCancel(Runnable). If an error occurs, feedback can be shown by registering a Consumer via CancelableOperation#onException(Consumer<StatusException>).

CancelableOperation<Optional<UpdatePlan>> check = 
	updateService.checkUpdate(ProgressReporter.NULLPROGRESS_REPORTER);
check.onCancel(() -> showMessage(sync, "Operation cancelled"));
check.onException(t -> {
	String message = t.getStatus().getMessage();
	showError(sync, message);
});

4.3 Check if an update can be performed

When the update check completes, it is possible to check if there are updates available and therefore an update can be performed. This can be done via CancelableOperation#onComplete(Consumer<T>), where T is typically of type org.eclipse.fx.core.update.UpdateService.UpdatePlan, which is the result of the update check. The UpdatePlan result is wrapped in a java.util.Optional, so if it is present, an update can be performed.

check.onComplete((updatePlan) -> {
	if (!updatePlan.isPresent()) {
		showMessage(sync, "Nothing to update");
	}
	else {
		...
	}
});

4.4 Ask the user if an update should be performed

While with the default Eclipse 4 UISynchronize implementation it is only possible to use Runnables that are executed in the UI thread, the extended UISynchronize of the e(fx)clipse runtime also supports the usage of Callables. This way it is possible to show a confirmation dialog in the UI thread, while the update operation in the background thread is not disturbed. Compared with the usage of the Eclipse 4 UISynchronize, this means there is no need for additional nesting of Runnables.

private boolean showConfirmation(
		UISynchronize sync, final String title, final String message) {
	return sync.syncExec(() -> {
		Alert alert = new Alert(AlertType.CONFIRMATION);
		alert.setTitle(title);
		alert.setContentText(message);
		Optional result = alert.showAndWait();
		return (result.get() == ButtonType.OK);
	}, false);
}

This method can simply be used like the showMessage(UISynchronize, String) or showError(UISynchronize, String) methods for simple dialogs.

if (showConfirmation(
	sync, 
	"Updates available", 
	"There are updates available. Do you want to install them now?")) {
	...
}

4.5 Perform an update

The update itself can be performed by executing UpdatePlan#runUpdate(ProgressReporter), which again returns a CancelableOperation to be able to react on different operation results.

CancelableOperation result = 
	updatePlan.get().runUpdate(ProgressReporter.NULLPROGRESS_REPORTER);

4.6 Restart the application after the update is finished

After an update was performed, it is good practice to restart the application so the updates are applied correctly. This can be done using org.eclipse.fx.ui.services.restart.RestartService. Using this service it is possible to specify whether the persisted state should be cleared, so changes to the application model are visible after the restart.

result.onComplete((r) -> {
	if (showConfirmation(
			sync, 
			"Updates installed, restart?", 
			"Updates have been installed successfully, do you want to restart?")) {
 
		sync.syncExec(() -> restartService.restart(true));
	}
});

Note: RestartService#restart(boolean) needs to be executed in the UI thread.

The complete FXUpdateHandler can be found on GitHub.

Step 5: Configure the repository location

To perform update operations, it is necessary to configure the repositories to check against. You need to specify the artifact repositories, which contain the actual content being installed or managed, and the metadata repositories, which contain the installable units (IUs) that describe things that can be installed, the capabilities they provide, and the requirements they have.

Note: The artifact and metadata repositories don’t need to be at the same locations, but typically they are.

It is possible to configure the repositories programmatically via UpdateOperation#getProvisioningContext().setArtifactRepositories(URI[]) and UpdateOperation#getProvisioningContext().setMetadataRepositories(URI[]). But the best practice is to configure them via p2.inf configuration files.

  • Create the file p2.inf in the project de.codecentric.eclipse.tutorial.product
  • Add the following lines to configure the repository locations (e.g. C:/Development/tmp/repository)
    • ${#58} is the variable for ‘:’
instructions.configure=\
  addRepository(type:0,location:file${#58}/C${#58}/Development/tmp/repository);\
  addRepository(type:1,location:file${#58}/C${#58}/Development/tmp/repository/);

Further information on how to create and configure a p2.inf file can be found here:

Step 6: Export the product

  • Open the Product Configuration in the de.codecentric.eclipse.tutorial.product project
  • Select the Overview tab
  • Click Eclipse Product export wizard in the Exporting section
  • Select a directory to export to in the Destination section of the export wizard (e.g. C:/Development/tmp/base_export)
  • Ensure Generate p2 repository is checked
    • This also creates the p2 cache that is necessary to make updates work
    • Copy the export to another directory (e.g. C:/Development/tmp/app)
  • Leave the other options unchanged
  • Click Finish

Note: If you face issues on exporting regarding a missing plug-in dependency, try to add org.eclipse.equinox.concurrent to the Plug-ins section of de.codecentric.eclipse.tutorial.feature. This is due to platform changes explained here.

Note: The export works only for Windows and Linux if the Eclipse Platform Launcher Executables are added to the Target Definition. Mac users should stick to exporting without native launchers and start the application via command line or create a deliverable using the JavaFX Packager Tool. Futher information on both ways can be found in the SWT to JavaFX migration recipe.

Step 7: Create an application update

  • Open the file Application.e4xmi in the project de.codecentric.eclipse.tutorial.app
  • Add an exit command
    • Application → Commands → Add
    • Set Name to Exit
    • Set ID to de.codecentric.eclipse.tutorial.app.command.exit
  • Add a handler
    • Application → Handlers → Add
    • Set ID to de.codecentric.eclipse.tutorial.app.handler.exit
    • Set the Command reference to de.codecentric.eclipse.tutorial.app.command.exit via Find… dialog
    • Create a handler implementation by clicking on the Class URI link
      • Set Package to de.codecentric.eclipse.tutorial.app.handler
      • Set Name to ExitHandler
      @Execute
      public void execute(IWorkbench workbench) {
          workbench.close();
      }
  • Add a Handled Menu Item to the File Menu
    • Set the label to Exit
    • Set the Command reference to the Exit command via Find … dialog
  • Increase the version of the plug-in de.codecentric.eclipse.tutorial.app
    • 1.0.1.qualifier
  • Increase the version of the feature de.codecentric.eclipse.tutorial.feature
    • 1.0.1.qualifier
  • Update the product configuration in de.codecentric.eclipse.tutorial.product
    • Increase the version of the product to 1.0.1
    • Update product feature dependencies
      • Edit Properties of the feature de.codecentric.eclipse.tutorial.feature and set the version to 1.0.1.qualifier
  • Export the updated product
    • Open the Product Configuration in the de.codecentric.eclipse.tutorial.product project
    • Select the Overview tab
    • Click Eclipse Product export wizard in the Exporting section
    • Select a directory to export to in the Destination section of the export wizard (e.g. C:/Development/tmp/update_export)
    • Ensure Generate p2 repository is checked
    • Click Finish
  • Copy the generated p2 repository located at C:/Development/tmp/update_export/repository to (e.g. C:/Development/tmp/repository)

Step 8: Taste

  • Start the application that was exported first via C:/Development/tmp/app/eclipse/eclipse.exe
  • Execute the update via File → FXUpdate
  • Check that an update is performed and that after the restart an Exit menu item is available in the File menu

This recipe showed how to make use of the extended e(fx)clipse services to perform a p2 update of an e(fx)clipse application. Using those services makes it a lot easier to perform an application update using p2. And it also adds great features like restarting with clearing the workspace and executing Callables on the UI thread.

You can find the full source code of this recipe on GitHub. The p2 branch contains the sources to create the base export with the p2 update handler. The p2_update branch contains the sources with a simple modification to verify the update.

Appendix:

It is also possible to use the above recipe in an SWT based application with some slight modifications. As explained in Add JavaFX controls to a SWT Eclipse 4 application, you need to add the RCP e4 Target Platform Feature of the e(fx)clipse software site to the target definition. The UpdateService is UI toolkit neutral so it can simply be used as described above. The extended UISynchronize is not UI toolkit neutral, but the e(fx)clipse 2.0.0 runtime offers the ThreadSynchronize service which is implemented in the org.eclipse.fx.ui.di.interopt plug-in. To use a Callable in an SWT based Eclipse 4 application, you can therefore use ThreadSynchronize instead of UISynchronize.

The RestartService is not simply usable in an SWT based Eclipse 4 application, and there is no interopt implementation because of the tight startup integration. You can, however, make use of a similar RestartService created and provided by Tom Schindl.

    • Add a new Software Site to the target definition
      • http://downloads.foss.bestsolution.at/e4-extensions/nightly/site
    • Add the following feature to the product dependencies
      • at.bestsolution.e4.extensions.feature
    • Add the following plug-in to the dependencies of the de.codecentric.eclipse.tutorial.app plug-in
      • at.bestsolution.e4.extensions.core.services
    • Modify the FXUpdateHandler to import and use at.bestsolution.e4.extensions.core.services.RestartService
import java.util.Optional;
 
import org.eclipse.e4.core.di.annotations.Execute;
import org.eclipse.fx.core.ProgressReporter;
import org.eclipse.fx.core.ThreadSynchronize;
import org.eclipse.fx.core.operation.CancelableOperation;
import org.eclipse.fx.core.update.UpdateService;
import org.eclipse.fx.core.update.UpdateService.UpdatePlan;
import org.eclipse.fx.core.update.UpdateService.UpdateResult;
import org.eclipse.jface.dialogs.MessageDialog;
 
import at.bestsolution.e4.extensions.core.services.RestartService;
 
public class FXUpdateHandler {
 
	@Execute
	public void execute(
			UpdateService updateService, 
			ThreadSynchronize sync, 
			RestartService restartService) {
		...
}
  • Register a lifecycle handler or addon to be able to clear on restart
    • e.g. add a property to the org.eclipse.core.runtime.products extension point in the de.codecentric.eclipse.tutorial.app plug-in
      • name: lifeCycleURI
      • value: bundleclass://at.bestsolution.e4.extensions.lifecycle/at.bestsolution.e4.extensions.lifecycle.ExtensibleLifecycle

The appendix sources are also available on GitHub.

Hopefully the above and even more of the general services will soon be ported to the Eclipse platform itself, so even more people will get to use those really valuable services!

Comment

Your email address will not be published. Required fields are marked *