BookNBPlatformCookbookCH08

Contents

Miscellaneous

Introduction

This chapter is a type of 'bucket' of recipes that were not enough to be a chapter on their own but important enough to be part of the book. Most common application now days interact with a server one way or the other. Web application servers are sufficient for most complex applications but there special cases where you need a customized server. Either what's out there doesn't fit your needs, is too expensive or is in a different programming language. Most of the times you'll be tempted by developing your own. Not much time will pass before you realize that's easier said than done. Here's my solution: Marauroa (http://arianne.sourceforge.net/engine/marauroa.html). Here are some feature highlights:

  • Communications via TCP
  • Account System (user name/password)
  • Persistence via Database (MySQL or H2 supported out of the box)
  • Great Performance using Delta^2 ideology: simply send to the client what has changed
  • Turn based system
  • The engine is designed for gaming but it can be used for other purposes.

This recipe will be divided as follows:

  • Setup the default server (out of the box)
  • Use Simple-Marauroa project to create a text client without any configuration
  • Explain how to customize Marauroa to fit your needs

We'll be using code discussed on the Nodes and Explorer API chapter, so please refer to that chapter when needed.

External Execution API

This API provides NetBeans platform to control external processes. It uses the ProcessBuilder java native class and wraps it up in a nice and convenient little GUI on the Output location. You can check the available documentation for this API here: http://bits.netbeans.org/dev/javadoc/org-openide-execution/org/openide/execution/doc-files/api.html Here are some blogs that will give you more details if interested:

To summarize all the information above it comes down to this: This API allows to integrate external application into yours by providing control on stating and stopping those applications. They are executed on their own JVM, meaning that your application is not aware of what that application is doing by default.

Preparation

For this recipe you'll need to download the Marauroa libraries from the web: http://arianne.sourceforge.net/engine/marauroa.html

How to

Summarizing what we did on the Nodes and Explorer API chapter:

  • Created an embedded database for internal use of the application.
  • Created a File System structure to store our servers.
  • Created a tree, using nodes and Explorer APIs.
  • Created actions for each node.
  • Created a visualization of the server using the Visual Library API.

Now let's work on how to start the default application we defined on the Nodes and Explorer API. I won't explain the details on how Marauroa works, that would need its own book, but for now and for the purposes of this chapter the only thing we need to know is how to start a Marauroa server. Marauroa server applications are shipped with a script to start the server. Inspecting the script it basically defines the jars to be used as libraries and starts the main method of marauroa.jar with the necessary parameters. This is exactly what we'll do from our application using the External Execution API. Let's go back to the MarauroaApplicationNode class from within the GUI module we created earlier. It contains a couple of actions that we ignored last time but are important now:

  • StartServerAction
  • StopServerAction

As you probably guessed already, those are used to start and stop the server respectively. Let's take a look at the code:

private class StartServerAction extends AbstractAction {
        public StartServerAction() {
            putValue(NAME, NbBundle.getMessage(MarauroaApplicationNode.class, "start.server"));
        }
        @Override
        public void actionPerformed(ActionEvent ae) {
            IMarauroaApplication app = getLookup().lookup(IMarauroaApplication.class);
            try {
                app.start();
            } catch (Exception ex) {
                Exceptions.printStackTrace(ex);
            }
        }
    }

The constructor just assigns the display name for the action when the user right clicks on the node by using the putValue method. The actionPerformed method get's the correct application from the Node's lookup and calls its start method. If you take a look at the StopServerAction you'll notice that its exactly the same but calling the application's shutdown method instead. All the action is defined on the MarauroaApplication within our core module. Here's the code:

@Override
    public boolean start() {
        process = new DefaultMarauroaProcess(this);
        try {
            process.execute();
        } catch (ExecutionException ex) {
            Exceptions.printStackTrace(ex);
            return false;
        } catch (InterruptedException ex) {
            Exceptions.printStackTrace(ex);
            return false;
        }
        return true;
    }

There's not much happening here either. The important thing is the the process is a private variable within the application. This will allow us to stop it later at will. Lets explore the contents of the DefaultMarauroaProcess. It implements ImarauroaProcess for convenience purposes so other Marauroa applications can implement their own custom processes. The execute method is in charge of defining and starting the the Marauroa application. Here's the code:

@Override
    public Integer execute() throws ExecutionException, InterruptedException {
        final Callable<Process> marauroaProcess = new Callable<Process>() {

            @Override
            public Process call() throws IOException {
                ArrayList<String> commands= new ArrayList<String>();
                commands.add("java");
                commands.add("-cp");
                commands.add(app.getLibraries());
                commands.add("marauroa.server.marauroad");
                commands.add("-c");
                commands.add(app.getAppINIFileName());
                commands.add("-l");
                if (app.getCommandLine() != null && !app.getCommandLine().isEmpty()){
                    StringTokenizer st = new StringTokenizer(app.getCommandLine(), " ");
                    while (st.hasMoreTokens()) {
                        commands.add(st.nextToken());
                    }
                }
                for (int i = 0; i < commands.size(); i++) {
                    Logger.getLogger(DefaultMarauroaProcess.class.getSimpleName()).debug(
                            commands.get(i));
                }
                ProcessBuilder pb = new ProcessBuilder(commands);
                pb.directory(new File(app.getApplicationPath()));
                pb.redirectErrorStream(true);
                process = pb.start();
                return process;
            }
        };
        ThreadImpl thread = new ThreadImpl(marauroaProcess);
        thread.start();
        return thread.getTaskResult();
    }

First we define the process as a Callable<Process>() and overwriting the call() method. First we define the arguments into an ArrayList<String>.Each element being a separate argument of the command. This array will be the parameter for the ProcessBuilder. Next we define the path where the command will be executed and set the redirectErrorStream method to true. To avoid deadlocks we wrap the process in a thread:

private class MarauroaProcessThread extends Thread {

        private final Callable<Process> marauroaProcess;

        public MarauroaProcessThread(Callable<Process> marauroaProcess) {
            this.marauroaProcess = marauroaProcess;
        }
        int taskResult = 1;

        @Override
        public void run() {
            try {
                ExecutionDescriptor descriptor = new ExecutionDescriptor().frontWindow(true).controllable(true);

                ExecutionService service = ExecutionService.newService(marauroaProcess,
                        descriptor, getProcessName());

                Future<Integer> task = service.run();
                taskResult = task.get();
            } catch (InterruptedException ex) {
                Exceptions.printStackTrace(ex);
            } catch (ExecutionException ex) {
                Exceptions.printStackTrace(ex);
            } catch (CancellationException ex){
                //Do nothing. The user just cancelled it.
            }
        }

        public int getTaskResult() {
            return taskResult;
        }
    }

The following lines define the process. FrontWindow set to true makes the process show in the output window location. Controllable enables/disables the play/stop buttons on that screen.

ExecutionDescriptor descriptor = new ExecutionDescriptor().frontWindow(true).controllable(true);

Let's Explain!

The External Execution module provides the ExternalExecutionAPI that contains support for execution of external processes in the IDE. It also provide support class for the actual creation of the external process and support for destroying the process tree. Also it provides the GUI elements out of the box making it more appealing.

More

Here are some other points of interest.

External Execution Input API

Define interfaces for input processing (character or line based) and provides common implementations of these with factory methods. See : http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-extexecution/org/netbeans/api/extexecution/input/package-summary.html for more details.


Use custom platform

When you are in controlled environments where you need to reliably reproduce a build or you plan on having continuous integration server build and test your application you will need the platform somehow under version control (VCS). This involves having the parts of your platform under a version control system. But as you might guess, having the whole platform is not a good idea. On this recipe we'll see my approach on a real life application.

Preparation

Of course you'll need a VCS. There are various free options like Subversion (http://subversion.tigris.org/ ) and Mercurial (http://mercurial.selenic.com/ ). There are various others but this are well documented. Download and install your choice and get used to it's system. The internals on how each system works won't be discussed here.

How to

First we need a different structure for your project. Usually a RCP application has the following structure:

  1. Root folder
  • Module 1
  • Module 2

My proposed structure is:

  1. Root folder
  • netbeans
  • application root folder
  • Module 1
  • Module 2

The netbeans folder (or any name you pick) will contain the platform files. How to create your platform is well documented at: http://wiki.netbeans.org/DevFaqGeneralWhereIsPlatformHowToBuild. After following that FAQ your platform.properties file should end something like this:

# NOTE: You must remove the nbplatform.default line which might already exist in this file.
# Also note that editing the properties of your suite via the suite customizer (dialog)
# can add that line back in, so you'll need to watch for this and delete it again in this case.

# where the suite is located; you don't need to change this.  It exists
# to allow us to use relative paths for the other values
suite.dir=${basedir}

# just a helper property pointing to the same location as netbeans.dest.dir did before;
# Referenced only in this properties file, has no meaning for NB harness.
platform.base=../netbeans

# classpath-like list of absolute or relative paths to individual clusters
# against which you want your suite to build; Note that you can use
# "bare", i.e. not numbered cluster names, which simplifies later transitions
# to newer version of the platform. E.g:
cluster.path=\
    ../netbeans/harness:\
    ../netbeans/platform:\
    ../netbeans/nb:\
    ../netbeans/nb-plugins

# path to the build harness you want to use.  This is typically in the
# harness subdirectory of your platform, but you could point to a directory
# containing customized build scripts if you want to.
harness.dir=${suite.dir}/../netbeans/harness

nbplatform.active=myPlatformName
nbplatform.oit.netbeans.dest.dir=${platform.base}

The above assumes that the platform folder is named “netbeans” change accordingly if you change it to a different name. As you can tell from the section: Some automation anyone? On the mentioned FAQ entry, the process is really manual so the contribution of the automation was made by me. Let's take a look at the details.

Let's Explain

Here's a summary of the targets and what they do:

  • init-netbeans/init-hudson: Configures the ant-contrib lib used in other tasks. For some reason Hudson doesn't work with the init-netbeans approach.
  • getAntContribJar: Looks in the suite's tools folder for the ant-contrib jar file. This file name is then used by other tasks
  • check-env: Basically to decide if we're in Netbeans or in Hudson. While in Hudson just pass the -DHudson=true parameter to the ant job. Having this variable set (not the value) tells this task that we are in Hudson.
  • update-env: The task to call. This one updates the cluster.path values in nbproject/platform.properties to set it up as mentioned in this FAQ. Why you might ask? This just takes care of updating any later addition of a module via using Netbeans and converts it to the format discussed in this FAQ. Basically no need to manually modify the nbproject/platform.properties file after the initial change!
  • update-platform: This will grab the current's IDE modules defined in cluster.path and zip them in a netbeans folder parallel to the suite's root folder. No need to do it manually!
  • unzip-compilation-env: this unzips the zips created in the above task to their proper place.

Keep in mind that after making the changes proposed earlier in this FAQ the project won't work (i.e. build, run, etc) if the environment is not set. That's the reason of doing all this in another xml file. Attempting any of this from the suite's build file won't work since you are messing with the platform files it is working from.

Notes:

  • Make sure to have an ant-contrib file in <suite's root>/tools folder for the above to work.
  • Current release of ant-contrib has an error. To fix it unpack the jar and add this entry to the net/sf/antcontrib/antcontrib.properties file in the Logic tasks section:
for=net.sf.antcontrib.logic.ForTask


Using source in a Continuous Integration Server

The first thing to do, before even compiling the project, is to use the unzip-compilation-env task to prepare the environment for compilation.

Unit Testing

One of the main purposes for using a Continuous Integration server is for unit testing. See this tutorial for hints: http://platform.netbeans.org/tutorials/nbm-test.html java


Navigation

Not logged in. Log in, Register

By use of this website, you agree to the NetBeans Policies and Terms of Use. © 2012, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo