Extending push-to-client

An independent software vendor (ISV) can contribute additional extensions, allowing other types of IDE configurations to be propagated. This information describes how to provide a push-to-client extension.

The push-to-client feature provides a means of sharing IDE configurations between users. Developers can export their own configurations locally so that at a later time they are able to restore a previous IDE environment. For a team of developers, a single IDE can be set up and then, by way of push-to-client, shared on a common server. When developers connect to that server, they automatically have their IDE synchronized with the previously exported configuration. In this way, common settings can be shared across teams.

This mechanism consists of a base framework to handle the user interface and most of the processing along with extensions to handle specific types of configurations. Out of the box, push-to-client provides the following extensions:

How to contribute to push-to-client

You will typically want to create a new plugin project for the extension. In the manifest, you will need to include at least the following dependencies:
com.ibm.etools.systems.pushtoclient.core
org.eclipse.core.resources
org.eclipse.core.runtime
org.eclipse.ui
Next, a configurationExtension extension point should be defined in the project plugin.xml file. For example:
<extension
      point="com.ibm.etools.systems.pushtoclient.core.configurationExtensions">
   <configurationExtension
         class="p2c.test.ConfigurationExtension1"
         description="Custom Elements"
         icon="icons/obj.gif"
         id="p2c.test.configurationExtension1"
         name="Test Extension">
   </configurationExtension>
</extension>
Each extension needs to have a class, description, icon, id, and name. The functionality of the contribution is embedded in the class. The class needs to implement IConfigurationExtension. In most cases it is preferable to extend the ConfigurationExtension implementation.
Initially your code will look similar to this example:
package p2c.test;

import com.ibm.etools.systems.pushtoclient.core.extensions.IConfigurationElement;
import com.ibm.etools.systems.pushtoclient.core.extensions.ConfigurationElement;
import com.ibm.etools.systems.pushtoclient.core.extensions.ConfigurationExtension;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.runtime.IProgressMonitor;

public class ConfigurationExtension1 extends ConfigurationExtension {

	public ConfigurationExtension1(String id, String name, String description){
		super(id, name, description);
	}
Within a configuration extension, the model object to work with is IConfigurationElement and its default implementation, ConfigurationElement. These elements represent units of information that are displayed to users for the purpose of selecting or deselecting their neighboring check boxes. Each element has descriptive information and optional children. Using configuration elements, you will need to define each of the following four methods:
  • public void populateExportElements(IConfigurationElement parent)
  • public void exportToCache(IConfigurationElement element,IFolder cacheFolder, IProgressMonitor monitor)
  • public void populateImportElements(IConfigurationElement parent)
  • public void importToWorkspace(IConfigurationElement element, IProgressMonitor monitor)
The method populateExportElements() allows a user to determine which elements of a configuration are to be exported in a wizard. In the simplest case, you just need to indicate whether the parent should be set for export by default. For example:
public void populateExportElements(IConfigurationElement parent){
	parent.setToExport(true);
}
In this case, users will only have the choice of taking all the configuration information or none.
In the following slightly more complicated example, elements are created to represent discrete artifacts for export:
@Override
public void populateExportElements(
		IConfigurationElement parent) {
	// folder representing one category of items
	ConfigurationElement folderElement = new ConfigurationElement("Element X", "Custom properties X");
	parent.add(folderElement);

	// contents of first category
	ConfigurationElement stuff = new ConfigurationElement("X child element", "");
	stuff.setToExport(true);
	folderElement.add(stuff);
	folderElement.setToExport(true);

	// folder representing another category of items
	ConfigurationElement folderElement2 = new ConfigurationElement("Element Y", "Custom properties Y");
	parent.add(folderElement2);

	// contents of the second category
	ConfigurationElement thing1 = new ConfigurationElement("Y child element 1", "");
	folderElement2.add(thing1);
	thing1.setToExport(true);

	ConfigurationElement thing2 = new ConfigurationElement("Y child element 2", "");
	folderElement2.add(thing2);
	folderElement2.setToExport(true);

	parent.setToExport(true);
}

When exporting via a push-to-client wizard, the elements will appear like this:

Export wizard customization

The name and description of the root element for this extension, Test Extension, comes from the extension point, whereas the child elements are produced via the implementation of populateExportElements().

The exportToCache() method is called when a user finishes the export wizard. The elements of the configuration extension are passed in and, depending on the items that were selected, the corresponding configuration artifacts are saved in a format under the supplied cachedFolder directory. The format is determined by the implementer. This cachedFolder directory is unique for each configuration extension and uses the configuration id as the name.

The following is an example implementation of exportToCache():
@Override
public void exportToCache(IConfigurationElement element,
		IFolder cacheFolder, IProgressMonitor monitor) {			
	// find all the elements that are "set to export" and
	// then save to the cache folder
	if (element.hasChildren()){			
		IConfigurationElement[] folders = element.getChildren();
		for (IConfigurationElement folder: folders){
			if (folder.isSetToExport()){
				IConfigurationElement[] children = folder.getChildren();
				for (IConfigurationElement child: children){
					if (child.isSetToExport()){
						// store in model for export
						...
					}						
				}
			}
		}
		
		// create file to store exported information
		IFile file = cacheFolder.getFile(new Path("test.properties"));
		...
	}
}

After a configuration has been exported to the cache directory, the base push-to-client framework takes care of the rest of the operation. If you are exporting to a local location, the cached information will be archived in that location If you are exporting to a common server, the changed information will be archived and transferred to that server.

When importing configurations, the push-to-client framework first downloads the exported configuration archive to the Eclipse workspace and then extracts the archive contents under the RemoteSystemsTempFiles folder. As with export, with import there is a cached folder for each configuration extension; this is the same folder that is used for export. The import wizard needs to prompt the user for elements to import. As with populateExportElements(), each configuration extension implements populateImportElements() by creating configuration elements to represent the incoming information. The sole parameter for this method is an element which will return the cached folder for your configuration extension.

In the following example, the element hierarchy for the configuration is produced by extracting information out of the test.properties file.
@Override
public void populateImportElements(
		IConfigurationElement parent) {

	// parent file is the cached folder for this configuration
	File parentFile = parent.getFile();
	
	// test.properties is contained in the parent folder
	File testFile = new File(parentFile, "test.properties");
	
	// read and parse the file and populate import elements based on contents of testFile
	...

The following dialog will show up in response to the import. Typically everything being imported is selected by default.

Customized import wizard

The final method to implement is importToWorkspace(). This method is invoked after the user clicks OK on the import dialog. The selected elements for the configuration are passed into this method and it is the job of the implementer to take the information from the configuration extension cache folder and apply it to the workspace.
@Override
public void importToWorkspace(IConfigurationElement element,
		IProgressMonitor monitor) {
	// find all the elements that are "set to export" (same as "set to import")
	if (element.hasChildren()){			
		IConfigurationElement[] folders = element.getChildren();
		for (IConfigurationElement folder: folders){
			if (folder.isSetToExport()){
				IConfigurationElement[] children = folder.getChildren();
				for (IConfigurationElement child: children){
					if (child.isSetToExport()){
						// transfer the corresponding information to the workspace					...
					}						
				}
			}
		}
	}
}

Feedback