API overview

This section provides an overview of the p2 API and introduces some of the key concepts that should be understood to work with the p2 API. The API can be generally thought of as three layers of API. The top most layer requires understanding of very few concepts, and each successive layer provides more flexibility, but is a bit more complex.

This section provides a description of each of these categories and introduces some of the key concepts.

The User Interface API

The UI provides wizards for installing, updating, and uninstalling software in a system. It also provides dialog pages for describing the installation and manipulating the repositories that are used to access software. Most of these building blocks are private ("black box") implementations, not intended to be extended by clients. A small package, org.eclipse.equinox.p2.ui, defines the API, which provides hooks for customizing the behaviour of the UI components, and class definitions that can be used in a plug-in's UI contributions.

Applications that simply want to provide their users a way to update or install additional software into the system need only include the relevant UI bundles. No deep knowledge of the p2 API is required. Customizing the p2 UI describes this level of working with the p2 UI. The The Policy class defines aspects of the UI that can be customized.

Operations API

The operations API provides high-level API for installing, updating, and uninstalling software in a headless system. The operations API is defined with "progressive disclosure" in mind. That is, the simplest and most common operations require little knowledge of underlying concepts.

Clients work with the operation in two passes. The operation must first be resolved, which means that the requirements and dependencies are calculated to determine if the desired operation is compatible with what is already installed in the system. If the operation can be resolved, then the actual provisioning work (downloading of artifacts, updating the system) can be performed. The two-pass nature of an operation allows the resolution status to be reported to a client to determine whether the operation should proceed. For example, the client can determine if the resolution is acceptable, or examine the detailed plan of what needs to be installed in order to continue with the operation. The resolution and provisioning phases can be performed synchronously or in the background as a job.

A simple example can help demonstrate the simplicity of operations. A common operation in many applications is to simply update the application to get the latest version of everything. This is possible with very little code. The following snippet shows the sequence for updating everything in the running application.

   // create an operation. We already have a provisioning agent (to be explained later)
   UpdateOperation op = new UpdateOperation(new ProvisioningSession(agent));
   // resolve the operation to see if we can update
   IStatus result = op.resolveModal(new NullProgressMonitor());
   if (result.isOK()) {
     // get a job that will perform the actual work and schedule it
     op.getProvisioningJob(monitor).schedule();
   }

If there is a problem resolving an operation, or the client code wants to do something slightly different, then a little more code might be needed. For example, an operation can be configured to update an application that is not the running application, or to consult a specific list of software repositories for the update.

Inside the operation, the underlying p2 IPlanner is performing the resolution phase, and the underlying p2 IEngine is performing the actual install. However, these subsystems do not need to be understood by a client working with operations.

For more information about operations, consult the javadoc for ProfileChangeOperation. Code snippets for working with operations can be found in the javadoc for InstallOperation, UpdateOperation, and UninstallOperation.

The Core API

The core API contains all the subsystems on which the Operation and UI APIs are built. Some of these constructs feed into the other API layers. For example, we saw above that an agent was needed in order to build an update operation. Now we'll take a look at these core concepts.

The Provisioning Agent

All access to the p2 API happens through the agent, IProvisioningAgent. The agent is the starting point of everything. One way to think about the IProvisioningAgent is that it is an "executable" representation of the p2 area (e.g. the p2 folder at the root of an eclipse installation). Among other things, the agent can be used to acquire p2 services for managing repositories, creating provisioning plans and perform installation requests.

The provisioning agent is acquired using the IProvisioningAgentProvider. This is generally done in one place in the client code, with the rest of the code simply accessing the agent through some variable or helper code. The following snippet shows how to acquire the provisioning agent.

   ServiceReference sr = Activator.sContext.getServiceReference(IProvisioningAgentProvider.SERVICE_NAME);
   IProvisioningAgentProvider agentProvider = null;
   if (sr == null)
      return;
   agentProvider = (IProvisioningAgentProvider) Activator.sContext.getService(sr);
   IProvisioningAgent agent = agentProvider.createAgent(new URI("file:/Applications/eclipse36/p2"));

Accessing all p2 services through an agent allows for multiple instances of p2 to be running in isolation in the same VM. Note that the client creating the agent is responsible for destroying it.

Metadata

The Installable Unit (IU) is a chunk of metadata describing something that is installable. An IU is used throughout the API to describe something that is being installed, updated, or removed. An IU describes something that can be installed, including the name, description, license, copyright information, installation processing steps, and the requirements that must be satisfied. The IInstallableUnit javadoc describes all of the things one can do with an IU. You will notice that you can't change the properties of an IU. That is because an IU is immutable. Once created, it should never change.

An IU can be obtained either by querying a source of metadata (e.g. a repository) or by creating one programmatically.

Queries and Queriables

Every source of metadata is usually queryable (see org.eclipse.equinox.p2.query.IQueryable). To discover an IU, you can execute a query (see IQuery) against a metadata source. The result of a query is a collection of all of the IUs that meet the criteria of the query.

Queries can be created in multiple ways. The simplest way is to create a query using the QueryUtil. The following snippet creates a query that searches for all IUs that have the ID "org.eclipse.jdt":

   QueryUtil.createIUQuery("org.eclipse.jdt");

Depending on the specificity of the query, there may be one or many IUs that satisfy the query. For example, you could query for a specific version of a specific IU, or you could use a wildcard to query for IUs that match a particular pattern. QueryUtil has API for retrieving the most commonly used queries. Additional queries that are OSGi specific can be found in the package org.eclipse.equinox.p2.eclipse.touchpoint. Finally, should you need to write more complex queries, p2 comes with a query language called p2 QL

Repositories and Repository Managers

There are two main types of repositories, Metadata Repositories and Artifact Repositories. Metadata repositories hold metadata (Installable Units), while artifact repositories hold "artifacts" (the actual downloadable bytes that make up an install). Repositories can be remote or local. They can be edited and queried. The javadoc for IMetadataRepository and IArtifactRepository describe the repository API in more detail.

Repositories are managed (created, loaded, removed, cached, etc...) using a Repository Manager. The repository manager can be acquired using the provisioning agent.

The following snippet shows how to acquire the metadata repository manager using the agent, and subsequently load the Helios repository.

   IMetadataRepositoryManager manager = (IMetadataRepositoryManager) agent.getService(IMetadataRepositoryManager.SERVICE_NAME);
   IMetadataRepository repository = manager.loadRepository(new URI("http://download.eclipse.org/releases/helios"), new NullProgressMonitor());

Profiles and Profile Registries

A p2 profile tracks the set of software that composes the executable application. For example, your Eclipse Install has a profile that contains all the IUs that that comprise Eclipse. When you attempt to install new IUs, p2 modifies your current profile. If the new IUs conflict with your existing profile (or dependencies cannot be resolved), then p2 will report an error and the installation will not proceed. See the IProfile javadoc for a complete list of the Profile APIs.

The profile is managed by a profile registry. IProfileRegistry manages all the profiles for a given p2 agent. It can be acquired through the agent.

Putting it all together

The following snippet demonstrates everything that must be done to trigger the installation of an IU into the running application. We use the operations API to perform the install, so we don't have to work with the profile, planner, or engine subsystems. However, we do need to know enough about the core API to obtain an agent, and get some IUs from a repository. The operation manages the rest of the detail.


   //get the agent
   ServiceReference sr = Activator.sContext.getServiceReference(IProvisioningAgentProvider.SERVICE_NAME);
   IProvisioningAgentProvider agentProvider = null;
   if (sr == null)
      return;
   agentProvider = (IProvisioningAgentProvider) Activator.sContext.getService(sr);
   IProvisioningAgent agent = agentProvider.createAgent(new URI("file:/Applications/eclipse36/p2"));

   //get the repository managers and define our repositories
   IMetadataRepositoryManager manager = (IMetadataRepositoryManager) agent.getService(IMetadataRepositoryManager.SERVICE_NAME);
   IArtifactRepositoryManager artifactManager = (IArtifactRepositoryManager) agent.getService(IArtifactRepositoryManager.SERVICE_NAME);
   manager.addRepository(new URI("file:/Users/Pascal/tmp/demo/"));
   artifactManager.addRepository(new URI("file:/Users/Pascal/tmp/demo/"));

   //Load and query the metadata
   IMetadataRepository metadataRepo = manager.loadRepository(new URI("file:/Users/Pascal/tmp/demo/"), new NullProgressMonitor());
   Collection toInstall = metadataRepo.query(QueryUtil.createIUQuery("org.eclipse.equinox.p2.demo.feature.group"), new NullProgressMonitor()).toUnmodifiableSet();

   //Creating an operation
   InstallOperation installOperation = new InstallOperation(new ProvisioningSession(agent), toInstall);
   if (installOperation.resolveModal(new NullProgressMonitor()).isOK()) {
      Job job = installOperation.getProvisioningJob(new NullProgressMonitor());
      job.addJobChangeListener(new JobChangeAdapter() {
        public void done(IJobChangeEvent event) {agent.close()}});
      job.schedule();
   }