Adding New Project Types

You add support for a new project type by building a project type extension. By default, WebLogic Workshop includes support for project types such as Web Project, Control Project, Java Project, EJB Project, and others. You can add a new project type that features its own characteristics for allowable files, its own run time behavior, what the user gets when they create a new project of the type, and so on.

Strictly speaking, a project type extension itself defines only a few of the characteristics that might make up the user's sense of projects created from the type. The project type characteritics include high-level UI (such as the project type's name) and drivers for things like run-time behavior. But if projects of the type will include special debugging support for certain data types, support for new kinds of documents, a custom project properties panel, and so on, you will need to include that support in additional extensions. All of these related extensions can be packaged together, of course, sharing a single extension.xml file.

The following lists a few suggestions for other extension types you might want to include with a project type extension:

Building Project Type Extensions

As with many other extensions, the heart of a project type extension is an extension.xml file that defines extension basics and connects the extension with the IDE.

Adding support for new type of project typically includes the following steps:

Extension XML for Project Type Extensions

The extension.xml file for a project type extension specifies the following:

The following example, from the CustomProject sample, illustrates a project extension XML for a "PHP" project type that specifies an attribute declaring support for PHP (see the section below on defininhg attributes) and specifies drivers that WebLogic Workshop should load with projects created from this type.

<extension-xml id="urn:com-bea-ide:project">
    <project-type id="urn:com-bea-ide:project.type:php" label="PHP"
        icon="images/workshop/sourceeditor/project/html.gif"
        openfoldericon="images/workshop/workspace/project/web_proj_o.gif"
        closedfoldericon="images/workshop/workspace/project/web_proj_c.gif">
        <attribute name="supportsPhp" value="true" />
            <driver type="com.bea.ide.workspace.project.IProjectDriver" 
                class="ideExtensions.customProject.PhpProjectDriver"/>
            <driver type="com.bea.ide.workspace.project.IBuildDriver" 
                class="ideExtensions.customProject.PhpProjectBuildDriver"/>
            <driver type="com.bea.ide.workspace.project.IRunDriver" 
                class="ideExtensions.customProject.PhpProjectRunDriver"/>
    </project-type>
</extension-xml> 

Defining and Using Attributes

In the context of a project extension, attributes are a way for the extension to declare support for some characteristic. That characteristic's meaning is defined in part by the components that query for it. For example, take a look at this attribute from the preceding example:

<attribute name="supportsPhp" value="true" />

In the sample, this attribute is used in two places. It's used in the PhpProjectListener as a way to discover if there are "projects that support PHP" in the application. Here's the code:

IProject[] phpProjects = workspace.getProjects("supportsPhp");

Theoretically, other PHP-related project types might include an attribute named supportsPhp whose value is "true"; these would also be found by the listener's code. A broader attribute, such as supportsWebApp (which is defined by the IDE), is an example of an attribute whose scope of use is fairly broad.

The supportsPhp attribute is also referred to by the document extension that accompanies this project extension. That extension's XML includes a <project-attributes> element with the following child element:

<requires name="supportsPhp" value="true" />

This tells the IDE that documents of this type should only be created within projects whose types include the supportsPhp attribute with a value of "true". In other words, you can right-click a PHP project to create a new PHP file, but you won't get that option for other project types — unless their extensions declare the supportsPhp attribute.

In addition to supportsWebApp, there are other attributes defined by WebLogic Workshop. The IDE will query for this attribute's presence to discover whether a project may be added to a web app. For more information on attributes defined by WebLogic Workshop see the information about the <attribute> element in the Project Extension XML Reference.

Implementing Logic for Project Type Extensions

Within the context of a project extension alone, you'll be writing a Java class for each driver you want your project to use. A driver is loaded by the IDE for each project of the type. Drivers provide support for things like building and running the project, and for hooking in other code you want to have executing while a project of the type is present.

If you accompany the project with other extensions, of course, you'll have Java code for those, too. This section offers implementation guidelines for common drivers you might specify as part of a project type extension. For more on extensions used with project type extensions, see the list at the top of this topic.

Adding Project Build Support

Through a build driver, you collect the supporting information WebLogic Workshop needs to build your project. This includes the classpaths you want the IDE to use when building. You also provide supporting functionality, such as the property panel through which the user can set build properties and support through which the user can generate a build file by clicking "Export to Ant file" in the Build properties panel.

A build driver implements the IBuildDriver interface. WebLogic Workshop provides an implementation in the abstract DefaultBuildDriver class, which you can extend. Your driver implementation

Like IRunDriver, the IBuildDriver interface extends IProjectDriver, including the activate and deactivate methods. The IDE will call these methods when loading and unloading a project of your type. For more information, see Associating Code with a Project's Presence in an Application.

Implement IBuildDriver.getPathsPropertyPanel if your build driver supports configurable path settings (or return null if it does not). You should return an implementation of IProjectPropertyPanel. In a similar manner, you should return an IProjectPropertyPanel from IBuildDriver.getBuildPropertyPanel if your project type exposes custom build-related properties. For an IProjectPropertyPanel implementation example, see the FTPPrefsPanel class included with the PopupAction project in the IdeDevKit sample application included in the ExtensionDevKit.

Managing Build Classpath Settings

WebLogic Workshop provides APIs through which you can ensure that the project-, application-, and server-level classpaths are what they should be in order to build projects of your type. You set the project classpath settings yourself based on, for example, path properties your project type exposes to the user; application and server classpaths are managed by the IDE, but you get an opportunity to edit them. Finally, you can tell the IDE of any additions to the classpath for executing Ant tasks.

Specifying Project-Level Classpath

Your implementation of IBuildDriver.getProjectClassPath should return a similar array of strings that represent classpath additions as the project level. For example, if your project type provides a paths property panel (as described below) through which the user can add paths, you'll want to retrieve those paths and return them in your implementation. Here's a simple example:

public String[] getProjectClassPath()
{
    // Retrieve the paths from preferences, where they were stored by the properties panel.
    Preferences pathsNode = _project.systemNodeForPackage(ProjectPathsPrefsPanel.class);
    String path = pathsNode.get("project.class.path", "");

    // Break the path into members of a String array.
    while (path.length() > 0 && path.endsWith(File.pathSeparator))
        path = path.substring(0, path.length() - 1);
    if (path.length() == 0)
        return new String[0];
    Pattern pathSplitter = Pattern.compile(";");
    String[] paths = pathSplitter.split(path);

    // Load the paths itno an array to return.
    for (int i = 0; i < paths.length; i++)
    {
        IFile f = m_project.getWorkspace().newFile(paths[i]);
        paths[i] = f.getAbsoluteIFile().getDisplayPath();
    }
    return paths;
}
Specifying Application- and Server-Level Classpaths

You can implement IBuildDriver.getApplicationClassPath to edit the application-level classpath used when building. WebLogic Workshop passes to your implementation a classpath that includes JAR files in the application's Libraries and Modules folders. Each member of the array is a path to the JAR on the path, such as: "C:/Apps/MyApp/APP-INF/lib/SomeLibrary.jar". If your application puts build output in the Libraries folder, for example, your getApplicationClassPath should remove it from the classpath and return the edited path. The following is an example:

public String[] getApplicationClassPath(String[] cpApplication)
{
    if (cpApplication == null)
        throw new IllegalArgumentException("cpApplication cannot be null");
    /*
     * Create a string that looks something like this:
     * "C:/bea_sp3/user_projects/applications/ExtensionTestApp/APP-INF/lib/ThisProject.jar"
     */
    String jarPath = _project.getWorkspace().getLibrariesDirectory().getDisplayPath()
        .replace(File.separatorChar, '/')
        .concat(_project.getName())
        .concat(".jar");

    /* 
     * Loop through the list of paths, looking for the build output
     * JAR, removing it when it's found.
     */
    for (int pos = 0; pos < cpApplication.length; pos++)
    {
        if (cpApplication[pos].equals(jarPath))
        {
            ArrayList ncp = new ArrayList(Arrays.asList(cpApplication));
            ncp.remove(pos);
            cpApplication = (String[]) ncp.toArray(new String[ncp.size()]);
            break;
        }
    }

    return cpApplication;
}

In the same way, you can implement IBuildDriver.getServerClassPath if you want an opportunity to modify the server-level build classpath.

Specifying Ant Task Classpath

Lastly on the subject of classpaths, use IBuildDriver.getAntClassPath to ensure support for any custom tasks included in your project's Ant build file. Your implementation should return an array of absolute paths to items that should be on the classpath for executing Ant tasks.

Adding Project Run Support

Strictly speaking, a run driver simply provides a way for you to tell the IDE whether the Start and Stop buttons should be enabled, and what to do when the user clicks them (or uses the corresponding menu or keyboard commands). You do all this in a class that implements IRunDriver, a class that you specify in your project type's extension.xml, as shown in the extension.xml example above.

In addition to implementing IRunDriver, your run driver class must also provide a constructor that takes an IProject instance. When WebLogic Workshop opens an application in which a project of your type exists, it will construct your run driver, passing in an IProject representing the project. Multiple projects of the type mean multiple driver instances, each with a separate IProject instance. The IProject is handy for the information it contains about the project it represents, but it's also likely you'll use it when it's time to actually respond to a Start button click and run the project, as discussed below.

The IDE is likely to call your isRunnable implementation when the user navigates into your project. It calls this method to find out, not surprisingly, if the project is runnable. If your project requires certain state characteristics — that the project or application has certain files, that a server is running, and so on — this is a good place to ensure that conditions are suitable for running the project. Return true if they are.

In calling your doEnableChecks implementation, the IDE is prompting you to set the state of "run" controls — the Start, Start Without Debugging, Pause, and Stop buttons and their corresponding menu and keyboard commands. You must explicitly use the run service to set these each time your project's run mode changes. Here's an example that enables the Start button and Start Without Debugging controls while disabling the Stop control:

RunSvc.get().setRunDebugEnabled(Boolean.TRUE);
RunSvc.get().setRunEnabled(Boolean.TRUE);
RunSvc.get().setStopEnabled(Boolean.FALSE);

WebLogic Workshop call the runProject and or runFile methods to get your project running. Your code in these should do whatever is necessary to put the project into run mode, including changing the state of the run controls. By default, the IDE will call runProject, passing to your code a boolean indicating whether the user wants to debug or not (i.e., clicked the Start button). Here's an example of a simple runProject implementation:

public boolean runProject(boolean debugRequested)
{
    boolean isRunnable = false;    
    // Create a File object from a file name expected to be in the project.
    String indexFilePath = m_currentProject.getPath() + File.separator + "index.php";
    File indexFile = new File(indexFilePath);

    // If there's an index file, go ahead and run.
    if (indexFile.exists())
    {
        boolean isRunning = false;
        try
        {
            // Launch a browser to display the file, then set the buttons.
            Process browserProcess = BrowserSvc.get().invokeBrowser(fileUri.toURL(), true);
            RunSvc.get().setRunEnabled(Boolean.FALSE);
            RunSvc.get().setRunDebugEnabled(Boolean.FALSE);
            RunSvc.get().setStopEnabled(Boolean.TRUE);
            RunSvc.get().setRunningProject(m_currentProject);
            isRunning = true;
        } catch (MalformedURLException mue)
        {
            mue.printStackTrace();
        }
    } else
    {
        // If no index file, warn the user that they need one and don't run.
        showNoIndexAlert();
        isRunning = false;
    }
    return isRunning;
}

The runFile method, on the other hand, will only be called if you call it. This method is included in the interface to support scenarios where running the project and running an open file in the project are done in different ways. To see an example from an existing project type, look at a web project that includes a page flow, JSP files, and so on. When running a project of this type, you run the page flow controller, which specifies a file to use as a starting place. When running a file, such as a JSP file, you probably want to run the file without having to work through the page flow.

Under the covers, the IDE handles this by associating a popup action extension with JSP documents. Right-click a JSP and you get a "Run page" popup menu command designed to run just the file. Here's an example of what an IAction.actionPerformed implementation might look like for a popup command that featured a simple "Start" button:

public void actionPerformed(ActionEvent e)
{
    // Get the current project.
    IProject proj = RunSvc.get().getCurrentProject();
    if (proj != null)
    {
        /* 
         * Get the run driver associated with the project and call
         * runFile with its URI.
         */
        IRunDriver runner = (IRunDriver)proj.getDriver(IRunDriver.class);
        IDocument currDoc = Application.getActiveDocument();
        if (currDoc != null)
            runner.runFile(currDoc.getURI(), true); 
    }
}

Your stopProject method should take the project out of run mode and back to design mode. This will include resetting buttons to their design mode states.

For a run driver sample, see the PhpProjectRunDriver class in the CustomProject sample project.

Associating Code with a Project's Presence in an Application

Because IBuildDriver and IRunDriver extend IProjectDriver, build, run, and project drivers provide an opportunity to include code that WebLogic Workshop should execute when it discovers a project of your type in the open application, and code that it should execute when the project is about to be removed or the app is about to be closed. In other words, in all three you can implement activate and deactivate methods that will be called by the IDE as described below for project drivers. Note that if you provide implementations in all three drivers, they will be called in this order: build, run, project.

A project driver is commonly used to load listeners that should be present whenever the project is present in the application. Through a listener, you can capture events that WebLogic Workshop raises for documents, projects, the workspace, the file system, and and so on.

Implementing IProjectDriver

The two ends of the project's lifecycle in an open app are represented in the API by the IProjectDriver.activate and IProjectDriver.deactivate methods. Aside from implementing these, your implementation class also needs to provide a constructor that takes an IProject instance.

When WebLogic Workshop discovers a project of your type in the app, it will construct an instance of the type's driver with an IProject that represents the project itself. If there are multiple projects of the type in the app, there will of course be multiple instances of the driver, each with a separate IProject instance. You can use the IProject to find out more about the project's contents. For example, you can pass the IProject instance to code that uses it to retrieve properties specific to the project.

Listening for Events

WebLogic Workshop exposes events for the various abstractions it uses to represents items in the IDE. These include the following:

Within a project driver, listeners you add in the activate method should be removed in the deactivate method. To add a listener, you call the addPropertyChangeListener method.

Application.get().addPropertyChangeListener(Application.PROP_InitLevel, 
    myListener);

For a sample project driver, see the CustomProject sample project in the IdeDevKit sample application. There, PhpProjectDriver implements IProjectDriver, adding and removing PhpProjectListener for projects of the PHP project type.

Related Topics

CustomProject Sample

Project Extension XML Reference