Showing posts with label eclipse. Show all posts
Showing posts with label eclipse. Show all posts

Monday, March 10, 2014

Creating Eclipse Plugin Project programmatically

In this article, I will explain how to create an eclipse plugin project programmatically.

Do check out the deployable plugin if you want to see the whole code or if you want to install that in your eclipse. The source plugin is also available if you want to check out the code.

The steps to create a plugin project are:
  1.  Create a IRunnableWithProgress and write your code inside it's run() method to create the plugin since is it a long running process.
  2. Create a IJavaProject and attach IProjectDescription to it.
  3. Set required Natures to the project
    • "org.eclipse.jdt.core.javanature" and "org.eclipse.pde.PluginNature" are
      mandatory for any plugin project
  4. Set required Builders to the project
    • "org.eclipse.jdt.core.javabuilder", "org.eclipse.pde.ManifestBuilder",
      "org.eclipse.pde.SchemaBuilder" are mandatory for
      any plugin project
  5. Open the project
  6. Create required source folders (eg, src folder)
  7. Set raw Classpath to the plugin project.
  8. Set output location for the project
  9. Create Manifest file
  10. Create Build.props file
  11. Create an activator class for the project
  12. Create any required artifacts, for ex, Java files, props files, etc
Continue reading the rest of the article if you want to check for code snippets for each of the above steps or download the plugin from here

Below are the code snippets for the steps mentioned above:
  1. Since creating a plugin project could be a long running process, it is better to run it in a background thread using IRunnableWithProgress:
  2. final IRunnableWithProgress runnableOp = new IRunnableWithProgress()
    {
    
      @Override
      public void run(final IProgressMonitor monitor) throws InvocationTargetException,
                                                               InterruptedException
      {
        try
        {
          monitor.beginTask("Creating a plugin project "
                                          + projectName, 2);
          IProject project = null;
          try
          {
            monitor.beginTask("", 10);
            monitor.subTask("Creating plugin project : "
                                            + projectName);
            
          } catch (final Exception e)
          {
          MessageDialog.openError(Display.getCurrent().getActiveShell(),
                                  "Error creating Project",
                                  "Error creating Project: \n" + e.getMessage());
          } finally
          {
            monitor.done();
          }
    
          monitor.worked(1);
        } finally
        {
          monitor.done();
        }
    }
    try
    {
      IRunnableContext context = PlatformUI.getWorkbench()
                                                     .getProgressService();
      context.run(false, true, runnableOp);
    } catch (InvocationTargetException | InterruptedException e)
    {
      MessageDialog.openError(Display.getCurrent().getActiveShell(),
                              "Creation problem",
                              "Project creation failed\n"+ e.getMessage());
    }
    
    
  3. Create a IJavaProject and attach IProjectDescription to it.
    final IWorkspace workspace = ResourcesPlugin.getWorkspace();
    project = workspace.getRoot().getProject(projectName);
    
    final IJavaProject javaProj = JavaCore.create(project);
    final IProjectDescription projDesc = ResourcesPlugin.getWorkspace().newProjectDescription(projectName);
    
    projDesc.setLocation(null);
    project.create(projDesc, monitor);
    
  4.  Set required Natures to the project
    private static void setNatures(final Set natureIdsSet,
                                       final IProjectDescription projDesc,
                                       boolean isMavenProject)
    {
       Set natureIds = new HashSet<>();
       natureIds.add(JavaCore.NATURE_ID);
       natureIds.add("org.eclipse.pde.PluginNature");
       if (isMavenProject)
       {
          natureIds.add("org.eclipse.m2e.core.maven2Nature");
       }
       natureIds.addAll(natureIdsSet);
    
       projDesc.setNatureIds(natureIds.toArray(new String[natureIds.size()]));
    }
    
  5.  Set required Builders to the project
    private static void setBuilders(final Set builderNamesSet,
                                        final IProjectDescription projDesc,
                                        boolean isMavenProject)
        {
            List builders = new ArrayList<>();
    
            final ICommand java = projDesc.newCommand();
            java.setBuilderName(JavaCore.BUILDER_ID);
            builders.add(java);
    
            final ICommand manifest = projDesc.newCommand();
            manifest.setBuilderName("org.eclipse.pde.ManifestBuilder");
            builders.add(manifest);
    
            final ICommand schema = projDesc.newCommand();
            schema.setBuilderName("org.eclipse.pde.SchemaBuilder");
            builders.add(schema);
    
            if (isMavenProject)
            {
                final ICommand mvn_schema = projDesc.newCommand();
                mvn_schema.setBuilderName("org.eclipse.m2e.core.maven2Builder");
                builders.add(mvn_schema);
            }
    
            for (String builderName : builderNamesSet)
            {
                final ICommand newBuilder = projDesc.newCommand();
                newBuilder.setBuilderName(builderName);
                builders.add(newBuilder);
            }
    
            projDesc.setBuildSpec(builders.toArray(new ICommand[builders.size()]));
        }
    
  6.  Open the project
    project.open(new SubProgressMonitor(monitor, 1));
                            project.setDescription(projDesc,
                            new SubProgressMonitor(monitor, 1));
    
  7.  Create required source folders and add classpath entries
    final List classpathEntries = new ArrayList();
    
    Collections.reverse(srcFolders);
    for (final String srcFolder : srcFolders)
    {
        final IFolder src = project.getFolder(srcFolder);
        if (!src.exists())
        {
           src.create(false, true, new SubProgressMonitor(monitor, 1));
        }
        final IClasspathEntry srcClasspathEntry = JavaCore.newSourceEntry(src.getFullPath());
        classpathEntries.add(0, srcClasspathEntry);
    }
    
    classpathEntries.addAll(Arrays.asList(NewJavaProjectPreferencePage.getDefaultJRELibrary()));
    classpathEntries.add(JavaCore.newContainerEntry(new Path("org.eclipse.pde.core.requiredPlugins")));
    
    if (isMavenProject)
    {
       final IClasspathEntry mavenDependencies = JavaCore.newContainerEntry(new Path("org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"), ClasspathEntry.NO_ACCESS_RULES, new IClasspathAttribute[] { new ClasspathAttribute("maven.pomderived", "true") }, false);
       classpathEntries.add(mavenDependencies);
    }
    
    javaProj.setRawClasspath(classpathEntries.toArray(new IClasspathEntry[classpathEntries.size()]),
                                                     new SubProgressMonitor(monitor, 1));
    
    
  8.  Set output location for the project
    javaProj.setOutputLocation(new Path("/" + projectName + "/bin"), new SubProgressMonitor(monitor, 1));
    
  9.  Create Manifest file
    private static void createManifest(final String projectName,
                                           final Set requiredBundles,
                                           final IProgressMonitor monitor,
                                           final IProject project) throws Exception
        {
            final StringBuilder maniContent = new StringBuilder("Manifest-Version: 1.0\n");
            maniContent.append("Bundle-ManifestVersion: 2\n");
            maniContent.append("Bundle-Name: " + projectName + "\n");
            maniContent.append("Bundle-SymbolicName: "
                               + projectName
                               + "; singleton:=true\n");
            maniContent.append("Bundle-Version: 1.0.0\n");
    
            if (!requiredBundles.isEmpty())
            {
                maniContent.append("Require-Bundle:");
                for (final Iterator iterator = requiredBundles.iterator(); iterator.hasNext();)
                {
                    maniContent.append(iterator.next());
                    if (iterator.hasNext())
                    {
                        maniContent.append(",\n");
                    } else
                    {
                        maniContent.append("\n");
                    }
                }
            }
    
            maniContent.append("Import-Package: org.osgi.framework\r\n");
            maniContent.append("Bundle-RequiredExecutionEnvironment: JavaSE-1.7\r\n");
            maniContent.append("Bundle-ActivationPolicy: lazy\r\n");
            maniContent.append("Bundle-Activator: org.eclipse.ppc.Activator\r\n");
    
            final IFolder metaInf = project.getFolder("META-INF");
            metaInf.create(false, true, new SubProgressMonitor(monitor, 1));
    
            FileGenerator.createFile("MANIFEST.MF",
                                     metaInf,
                                     maniContent.toString(),
                                     monitor);
        }
    
  10.  Create Build.props file
    private static void createBuildProps(final IProgressMonitor monitor,
                                             final IProject project,
                                             final List srcFolders) throws Exception
        {
            final StringBuilder bpContent = new StringBuilder("source.. = ");
            for (final Iterator iterator = srcFolders.iterator(); iterator.hasNext();)
            {
                bpContent.append(iterator.next()).append('/');
                if (iterator.hasNext())
                {
                    bpContent.append(",");
                }
            }
            bpContent.append("\n");
            bpContent.append("bin.includes = META-INF/,.\n");
            FileGenerator.createFile("build.properties",
                                     project,
                                     bpContent.toString(),
                                     monitor);
        }
    
  11.  Create an activator class for the project
    protected static void createJavaFiles(IProject pluginProject) throws Exception
        {
            String sourceFolderPath = "resrc/org/eclipse/ppc";
            FileGenerator.createFile("org/eclipse/ppc/Activator.java",
                                     pluginProject.getFolder("src"),
                                     FileGenerator.getContents(sourceFolderPath,
                                                               "Activator.java",
                                                               PluginProjectCreator.class),
                                     new NullProgressMonitor());
    
        }
    
    This method can be used to create any file in the destination plugin project by copying the content from inside the source plugin project. You might also want to replace some contents in the generated file (before writing into it), based on the values given in the wizard. 
Hope this helps!

Tuesday, March 2, 2010

Eclipse WTP Web Service Wizard tweaks

If you are planning to create your custom Web Service solution, I would suggest to check out the Web Services project which is a sub-project in the Eclipse WTP Top-Level Project. We can leverage many features and standards which are already built-in by WTP by extending WTP Web Service wizard to provide our custom Web Services solution. Though it has few problems/bugs, we can live with them and ask WTP guys to patch in their next releases :-)

I will share here a few tweaks and customizations you can do with the Eclipse Web Service wizard and few problems that exist as of WTP 3.1 release.

Following links could be useful if you are new to this:
> Contributing Web Service Runtime in WTP
> Consuming Web service using Web Service Client
> New Help for Old Friends III

Firstly, some basic pieces you need to know to contribute to Eclipse Web Service Wizard:

We can contribute Web Service Type using the extension point org.eclipse.jst.ws.consumption.ui.wsImpl.

Eg:
<extension point="org.eclipse.jst.ws.consumption.ui.wsImpl">
<webserviceimpl id="org.sample.contribution" label="My Sample Web Service" resourcetypemetadata="File IResource CompilationUnit" extensionmetadata=".extn" objectselectionwidget="com.sample.MySelectionWidget">
</webserviceimpl>
</extension>

Each such contribution will add two entries to the Web Service type combo on the Web Service wizard: Bottom Up My Sample Web Service and Top Down My Sample Web Service.

The extensions of the artifacts using which you would like to generate Web Service should be mentioned for the extensionMetadata attribute, separated by spaces.
For example, if you mention .abc as value for this attribute; when you right-click on an artifact in your workspace named sample.abc, and launch Web Service wizard, you would get a callback on in your transformer with TreeSelection object containing sample.abc , so that you can modify/perform some operation. See transformer.

objectSelectionWidget attribute takes the id of the extension point:org.eclipse.jst.ws.consumption.ui.objectSelectionWidget.

Below is an example for this extension point:
<extension point="org.eclipse.jst.ws.consumption.ui.objectSelectionWidget">
<objectSelectionWidget
class="com.sample.MySelectionWidget"
external_modify="true"
id="com.sample.MySelectionWidget"
transformer="com.sample.Transformer">
</objectSelectionWidget>
</extension>


This class com.sample.MySampleWidget should extend AbstractObjectSelectionWidget and/or implement IObjectSelectionWidget.

This class needs to implement the following methods:

public WidgetDataEvents addControls(Composite parent, Listener statusListener):
UI code you need to show up when user clicks on 'Browse' buttom for Service implementation should be written here. This method can return 'this' (the current object).

public void setInitialSelection(IStructuredSelection initialSelection)
:
Suppose you have selected Project1/Folder1/abc.xxx as your service implementation, which is
already populated on the Web Service Wizard first page :







initialSelection parameter holds the the value of the Service Implementation which can be used to populate your UI (provided by addControls() )

public IStructuredSelection getObjectSelection()
This method should return a StructuredSelection object which holds the service implementation.
Tweak 1:
Optionally, you can also send some more values in this StructuredSelection. This will be useful if you want to get multiple inputs from the user from the MySelectionWidget's UI.

public IStatus validateSelection(IStructuredSelection objectSelection)
You can return Status.OK_STATUS or Status.CANCEL_STATUS depending on your logic.

public IProject getProject()
Depending on the service implementation selected or depending on your logic you can return an appropriate Project in the workspace. This Project will be set for the Service Project.
If we don't override this method or return null, by default first project in the workspace will be selected.

public String getObjectSelectionDisplayableString()
The string returned from this method will be displayed as the Service implementation. You can return any string depending on you logic (eg., your custom file system URL).

public Point getWidgetSize()
eg: return new Point(400, 300);

public boolean validate(String s)
This method gets callback whenever text in the Service Implementation changes. Eg: When user types in, or when he selects from MySelectionWidget. Returning false will error out the wizard saying "The service implementation selected is invalid."


The attribute transformer of takes the fully qualified path of the class that implements org.eclipse.wst.command.internal.env.core.data.Transformer.

Using Transformer, we can transform the given object from one class to another. For example, you can return a StructuredSelection consisting the artifact selected in the project explorer. This would be passed to the MySelectionWidget.setInitialSelection().

This class need to implement public Object transform(Object value):
The parameter value contains the StructuredSelection returned from MySelectionWidget.getObjectSelection().
You can return whole new StructuredSelection() object: new StructuredSelection(< selection >);

Tweak 2 and Tweak 3 :
Setting your contribution to Service Type and Client type as defaults and Setting Service slider and Client Slider to a particular stage (eg: Develop stage):

Invoke the below method from your plugin's start() method:

private void setWebServiceScenarioDefaults() {
ScenarioContext context = WebServicePlugin.getInstance().getScenarioContext();

context.setWebServiceType( < Id of webServiceImpl > );
context.setGenerateWebService(ScenarioContext.WS_DEVELOP);

context.setClientWebServiceType( < Id of webServiceClientImpl > );
context.setGenerateClient(ScenarioContext.WS_DEVELOP);
}

Here, Id of webServiceImpl should be the Service type number / id of webServiceImpl (see extension point="org.eclipse.jst.ws.consumption.ui.wsImpl")
Eg: 0/
org.sample.contribution for Bottom Up My Sample Web Service or 1/org.sample.contribution for Top Down My Sample Web Service.

Id of webServiceClientImpl
should be the id of the webServiceClientImpl (see Extension point "org.eclipse.jst.ws.consumption.ui.wsClientImpl")
Eg: org.sample.client.contribution

























Tweak 4: Validating service project selection:

< extension point="org.eclipse.jst.ws.consumption.ui.serviceRuntimes" >
has an attribute runtimeChecker which takes fully qualified class name which extends org.eclipse.wst.ws.internal.wsrt.AbstractWebServiceRuntimeChecker.

This class can implement 2 methods:

public IStatus checkRuntimeCompatibility(String serverTypeId, String serverInstanceId,
String projectName, String projectTypeId, String earProjectName) {
return Status.OK_STATUS;
}

public IStatus checkServiceClientCompatibility(boolean serviceNeedEAR,
String serviceEARName, String serviceProjectName,
boolean clientNeedEAR, String clientEARName,
String clientProjectName) {
return Status.OK_STATUS;
}

Here, we can validate based on the
serverTypeId, serverInstanceId, projectName, projectTypeId and earProjectName and return Status.OK_STATUS or Status.CANCEL_STATUS.

Some of the usability issues with WTP Web Services wizard:
  • There are few components on the Web Service Wizard first page on which do not have any control:
    1. Cannot change the Title and message when the Web Service Type selection changes (Eg: Select a Service implementation)
    2. Cannot change the names of the Bottom Up My Sample Service and Top Down My Sample Web Service to something like Java to WSDL Web Service and WSDL to Java Web Service.
    3. Cannot hook in custom dialogs for the Top Down Scenario for "Select Service Implementation" page (on clicking Browse button). This always opens up the WSDL chooser dialog. Suppose I need to start with an artifact other than WSDL for Top Down case, there is no option to customize it.
    4. Even if we do not need any Server, cannot disable the Server hyperlink.
    5. The checkboxes 'Publish the Web Service' and 'Monitor the Web Service' cannot be disabled or removed. The 'Publish Web service' page always comes up even when the 'Publish the Web Service' checkbox is not selected. See this bug for more details. Moreover if the the checkbox 'Monitor the Web Service' is selected, an exception would be thrown later in the wizard, if do not mention any server.
    6. If user selects any other project by clicking on the 'Service Project' link, this will create an EAR project as well. (This can be avoided by setting the Service slider to develop stage. See Tweak 3)
    7. 'Undo' operation for client scenario might also be required, because, if the user selects the client scale to 'Test', extra pages will be added to the wizard. This is because, once all your pages have been served to the wizard, extra pages (Publish Web Service page, Start server page, etc) will be added to the wizard. Since you get final callback only when Next is clicked on your last page and not on the Finish of the wizard, if you generate some artifacts here, and user cancels the wizard while he is navigating the extra pages served by WTP, you need to Undo the artifact generation.
    8. The whole Web Serice Client section on the first page is not needed at all, as this doesn't generate anything specific to the selected Client type while generating a Service type.
  • 'Web Service Publication' page will be added at the end of the wizard and we cannot be disable this.
  • There is no way to get callback when user clicks finish on the 'Web Service Publication' page. But, If user clicks Finish on one of the pages you serve, you do get callback on your command. It would be better if we can get callback on Finish of the wizard in all cases. This would avoid the overhead of handing Undo operations.


Few bugs which I have raised in bugzilla for the above mentioned points:
  • 295695:In Web Service Wizard, Server field should be cleared if Web Service type doesn't need a Server
  • 296430: Null Pointer Exception occurs when Web Service Wizard is launched with a resource from Java Project
  • 304805: Need support to set custom error/info message on the Web Service Wizard first page
  • 304808: Disable Web Service Client section on the Web Service wizard.
  • 280447: Need support for disabling few stages of webservice in the service side




Commands ( those that extend org.eclipse.wst.common.frameworks.datamodel.AbstractDataModelOperation) can be plugged in to your framework to do some validation and resource/artifact generation.
I will discuss this in my next blog entry...