Feeds:
Posts
Comments

Archive for the ‘How To's’ Category

My previous couple of posts covered basic usage and security aspects of Apache Thrift. You can also use Thrift Servlet based transport to expose a Thrift service via a Servlet. This can be useful if you are in a OSGi environment since you can expose it to the outside world using OSGi HTTPService. But unfortunately it’s not possible to use this Servlet implementation directly in a web application(which should have been very useful) for reasons I will describe in the latter part of this post. For this implementation it is required to extend ‘TServlet‘ along side with your service implementation. I will be using the same Thrift service(arithmetic.thrift) and respective implementation and generated code from my earlier blog posts for this example as well.

Thrift Servlet

public class ArithmeticServiceServlet extends TServlet {

    public ArithmeticServiceServlet(TProcessor processor, TProtocolFactory inProtocolFactory,
                           TProtocolFactory outProtocolFactory) {
        super(processor, inProtocolFactory, outProtocolFactory);
    }

    public ArithmeticServiceServlet(TProcessor processor, TProtocolFactory protocolFactory) {
        super(processor, protocolFactory);
    }

}

No implementation of doGet or doPost is necessary by default since mapping of your service implementation class to respective doGet and doPost methods is done inside TServlet.

Registering the Servlet

This entails getting the OSGi HTTPService and registering the Servlet with it. This code snippet assumes you have already obtained a HTTPService reference using a preferred method (e.g: Using declarative service etc.).

    public void register() throws Exception{
        ArithmeticService.Processor processor = new ArithmeticService.Processor(
                new ArithmeticServiceImpl());
        TBinaryProtocol.Factory inProtFactory = new TBinaryProtocol.Factory(true, true);
        TBinaryProtocol.Factory outProtFactory = new TBinaryProtocol.Factory(true, true);

        httpServiceInstance.registerServlet("/arithmeticService", new ArithmeticServiceServlet(
                processor, inProtFactory, outProtFactory), new Hashtable()
                , httpServiceInstance.createDefaultHttpContext());

    }

Servlet is registered with “/arithmeticService” context.

Consuming the Service

Now let’s write the client to consume the service. Here THttpClient class from Thrift is used.

public class ServletClient {

    public void invoke() throws Exception {
        TTransport client = new THttpClient("http://localhost/arithmeticService");
        TProtocol protocol = new TBinaryProtocol(client);
        ArithmeticService.Client serviceClient = new ArithmeticService.Client(protocol);
        client.open();

        long addResult = serviceClient.add(100, 200);
        System.out.println("Add result: " + addResult);
        long multiplyResult = serviceClient.multiply(20, 40);
        System.out.println("Multiply result: " + multiplyResult);

        client.close();

    }

    public static void main(String[] args) throws Exception {
        ServletClient client = new ServletClient();
        client.invoke();
    }

}

Problem with Web Apps

Now it would have been great if we can use this Servlet in one of our web applications. But as you can see from our ‘ArithmeticServiceServlet’ implementation it hasn’t got the default no argument constructor which is a deal breaker for using this Servlet in a web application. The web container needs a no argument constructor in order to initialize the Servlet. So for now no for web apps. :(.

Advertisements

Read Full Post »

Securing a Thrift service

A previous post explained how to create a Thrift service and consume it in different modes such as blocking, non blocking etc. Now if you are thinking of using Apache Thrift for client-server data exchange over a public network, the chances are that you may run in to the requirement of securing the data transmitted over Thrift. This post explains how SSL can be used to secure communication between a Thrift server and a Thrift client.

Prerequisites

1. I will be using the same service definition (arithmetic.thrift) and generated codes plus the service implementation (ArithmeticServiceImpl) from earlier post.

2. You need to have a key store with server private key at server-side and a trust store containing server’s public key at client side. For this example let’s create a key store and trust store using JDK keytool.

Creating key store

1. Go to Java installation bin directory in command line and execute following.

keytool -genkeypair -alias certificatekey -keyalg RSA -validity 7 -keystore keystore.jks

2. Give a suitable password and answers to the prompts. After that it will create the key store keystore.jks containing generated private/ public key pair.

3. Export the certificate (cret.cer) containing the public key from the key store using following command.

keytool -export -alias certificatekey -keystore keystore.jks -rfc -file cert.cer

Create trust store

1. Now let’s create the trust store (truststore.jks) and import the certificate to it. This can be done using single command line as given below.


keytool -import -alias certificatekey -file cert.cer -keystore truststore.jks

Again give a password and say yes to the prompt asking whether to trust this certificate. Now the certificate setup is complete. Let’s create the secure Thrift server and client to consume it.

Secure Thrift Server

Code for the secure server is given below. It uses TSSLTransportFactory to obtain a secure socket. Key store location is set as a parameter. Change the “path to the keystore.jks” and “keystore.jks password” parameters to suitable values in the code.

public class SecureServer {

    private void start() {
        try {
            TSSLTransportFactory.TSSLTransportParameters params =
                    new TSSLTransportFactory.TSSLTransportParameters();
            params.setKeyStore("path to keystore.jks", "keystore.jks password");

            TServerSocket serverTransport = TSSLTransportFactory.getServerSocket(
                    7911, 10000, InetAddress.getByName("localhost"), params);
            ArithmeticService.Processor processor = new ArithmeticService.Processor(new ArithmeticServiceImpl());

            TServer server = new TThreadPoolServer(new TThreadPoolServer.Args(serverTransport).
                    processor(processor));
            System.out.println("Starting server on port 7911 ...");
            server.serve();
        } catch (TTransportException e) {
            e.printStackTrace();
        } catch (UnknownHostException e) {

        }
    }

    public static void main(String[] args) {
        SecureServer srv = new SecureServer();
        srv.start();
    }

Secure Thrift client

Client code is given below.  As with the server replace “path to trustore.jks” and “truststore.jks password” parameters to actual values.

public class SecureClient {

    private void invoke() {
        TTransport transport;
        try {

            TSSLTransportFactory.TSSLTransportParameters params =
                    new TSSLTransportFactory.TSSLTransportParameters();
            params.setTrustStore("path to truststore.jks", "truststore.jks password");

            transport = TSSLTransportFactory.getClientSocket("localhost", 7911, 10000, params);
            TProtocol protocol = new TBinaryProtocol(transport);

            ArithmeticService.Client client = new ArithmeticService.Client(protocol);

            long addResult = client.add(100, 200);
            System.out.println("Add result: " + addResult);
            long multiplyResult = client.multiply(20, 40);
            System.out.println("Multiply result: " + multiplyResult);

            transport.close();
        } catch (TTransportException e) {
            e.printStackTrace();
        } catch (TException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        SecureClient c = new SecureClient();
        c.invoke();

    }

}

That’s it. You have now secured your Thrift service with SSL and created a secure client to talk with your secure service.

Apart from this method you can also use TServlet transport to expose the Thrift service as Servlet and expose your Servlet securely to the outside world. I will describe this method in an upcoming post.

Read Full Post »

Thrift is a cross language RPC framework initially developed at Facebook, now open sourced as an Apache project. This post will describe how to write a thrift service and client in different modes such as blocking, non blocking and asynchronous.

(I felt latter two modes are less documented and needed some tutorial type introduction, hence the motivation of this post). To easily follow the tutorial it’s beneficial that you have a basic understanding of Thrift architecture consisting of Transports, Protocols and Processors. (A good paper can be found at [1]). Here I will be using Thrift version 0.7 and Thrift’s Java binding.

Thrift Installation

Installation instructions can be found at http://wiki.apache.org/thrift/ThriftInstallation.

To sum up Ubuntu installation steps.

1. Install required dependencies.

     $ sudo apt-get install libboost-dev libboost-test-dev libboost-program-options-dev libevent-dev automake libtool flex bison pkg-config g++ libssl-dev

2. Go to the installation root directory.

3.  $ ./configure

4.  $ make

5. Become super user and

      $ make install

Now let’s get on with creating the service and consuming it.

Service Definition

Here a service with simple arithmetic operations is defined. Note the use of typedef directive to declare alternative names for base types i64 and i32. Add following in a file named  ‘arithmetic.thrift’.

namespace java tutorial.arithmetic.gen  // define namespace for java code

typedef i64 long
typedef i32 int
service ArithmeticService {  // defines simple arithmetic service
            long add(1:int num1, 2:int num2),
            long multiply(1:int num1, 2:int num2),
}

Code will be generated under ‘tutorial.arithmetic.gen’ package.

Now generate Java code using following command line.

$ thrift –gen java arithmetic.thrift

The source tutorial.arithmetic.gen.ArithmeticService.java will be generated.

Blocking Mode

Let’s create a blocking mode server and a client to consume the service.

First we need to implement the service using generated service skeleton. The interface to implement is ArithmeticService.Iface.

public class ArithmeticServiceImpl implements ArithmeticService.Iface {

    public long add(int num1, int num2) throws TException {
        return num1 + num2;
    }

    public long multiply(int num1, int num2) throws TException {
        return num1 * num2;
    }

}

Now that being done let’s create the Thrift server which would server request for this service. Remember this is a blocking server so the server threads doing I/O will wait.

public class Server {

    private void start() {
        try {
            TServerSocket serverTransport = new TServerSocket(7911);

            ArithmeticService.Processor processor = new ArithmeticService.Processor(new ArithmeticServiceImpl());

            TServer server = new TThreadPoolServer(new TThreadPoolServer.Args(serverTransport).
                    processor(processor));
            System.out.println("Starting server on port 7911 ...");
            server.serve();
        } catch (TTransportException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server srv = new Server();
        srv.start();
    }

}

Here TThreadPoolServer implementation is used which would utilize a thread pool to serve incoming requests.

Now let’s write the client.

public class ArithmeticClient {

    private void invoke() {
        TTransport transport;
        try {
            transport = new TSocket("localhost", 7911);

            TProtocol protocol = new TBinaryProtocol(transport);

            ArithmeticService.Client client = new ArithmeticService.Client(protocol);
            transport.open();

            long addResult = client.add(100, 200);
            System.out.println("Add result: " + addResult);
            long multiplyResult = client.multiply(20, 40);
            System.out.println("Multiply result: " + multiplyResult);

            transport.close();
        } catch (TTransportException e) {
            e.printStackTrace();
        } catch (TException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        ArithmeticClient c = new ArithmeticClient();
        c.invoke();

    }
}

TBinaryProtocol is used for encoding data transferred between server and client. Now start the server and invoke the service using client to results.

Non Blocking Mode

Now lets create a non blocking server which uses Java non blocking I/O underneath. We can use the same service implementation as before (ArithmeticServiceImpl).

public class NonblockingServer {

    private void start() {
        try {
            TNonblockingServerTransport serverTransport = new TNonblockingServerSocket(7911);
            ArithmeticService.Processor processor = new ArithmeticService.Processor(new ArithmeticServiceImpl());

            TServer server = new TNonblockingServer(new TNonblockingServer.Args(serverTransport).
                    processor(processor));
            System.out.println("Starting server on port 7911 ...");
            server.serve();
        } catch (TTransportException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        NonblockingServer srv = new NonblockingServer();
        srv.start();
    }
}

Here TNonblockingServerSocket is used which encapsulates a ServerSocketChannel.

Code for the non blocking client is as follows.

public class NonblockingClient {

    private void invoke() {
        TTransport transport;
        try {
            transport = new TFramedTransport(new TSocket("localhost", 7911));
            TProtocol protocol = new TBinaryProtocol(transport);

            ArithmeticService.Client client = new ArithmeticService.Client(protocol);
            transport.open();

            long addResult = client.add(100, 200);
            System.out.println("Add result: " + addResult);
            long multiplyResult = client.multiply(20, 40);
            System.out.println("Multiply result: " + multiplyResult);

            transport.close();
        } catch (TTransportException e) {
            e.printStackTrace();
        } catch (TException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        NonblockingClient c = new NonblockingClient();
        c.invoke();
    }

}

Note the usage of TFramedTransport wrapping normal TSocket transport. Non blocking server requires client to use TFramedTransport which would frame the data sent over the wire. Fire up the server and send a request using the client. You will see the same results as before, this time using non blocking mode.

Asynchronous Mode

We can write asynchronous clients to call a Thrift service. A callback needs to be registered which will get invoked at successful completion of the request. Blocking mode server didn’t work (method invocation returns with an empty response) with the asynchronous client (May be it’s because we are using TNonblockingSocket at the client side. See construction of ArithmeticService.AsyncClient. So this may be the proper behaviour). Non blocking mode  server seems to work without an issue. So you can use the non blocking server from earlier with the client shown below.

public class AsyncClient {

    private void invoke() {
        try {
            ArithmeticService.AsyncClient client = new ArithmeticService.
                    AsyncClient(new TBinaryProtocol.Factory(), new TAsyncClientManager(),
                                new TNonblockingSocket("localhost", 7911));

            client.add(200, 400, new AddMethodCallback());

            client = new ArithmeticService.
                    AsyncClient(new TBinaryProtocol.Factory(), new TAsyncClientManager(),
                                new TNonblockingSocket("localhost", 7911));
            client.multiply(20, 50, new MultiplyMethodCallback());

        } catch (TTransportException e) {
            e.printStackTrace();
        } catch (TException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        AsyncClient c = new AsyncClient();
        c.invoke();

    }

    class AddMethodCallback
            implements AsyncMethodCallback<ArithmeticService.AsyncClient.add_call> {

        public void onComplete(ArithmeticService.AsyncClient.add_call add_call) {
            try {
                long result = add_call.getResult();
                System.out.println("Add from server: " + result);
            } catch (TException e) {
                e.printStackTrace();
            }
        }

        public void onError(Exception e) {
            System.out.println("Error : ");
            e.printStackTrace();
        }

    }

    class MultiplyMethodCallback
            implements AsyncMethodCallback<ArithmeticService.AsyncClient.multiply_call> {

        public void onComplete(ArithmeticService.AsyncClient.multiply_call multiply_call) {
            try {
                long result = multiply_call.getResult();
                System.out.println("Multiply from server: " + result);
            } catch (TException e) {
                e.printStackTrace();
            }
        }

        public void onError(Exception e) {
            System.out.println("Error : ");
            e.printStackTrace();
        }

    }

}

Two callbacks have been defined corresponding to each operation of the service. Note the usage of two client instances for the two invocations. Each invocation needs a separate client instance or otherwise client will fail with following exception

Exception in thread “main” java.lang.IllegalStateException: Client is currently executing another method: tutorial.arithmetic.gen.ArithmeticService$AsyncClient$add_call

So this wraps up my quick start on Thrift with different modes of operation. Hope somebody may find this useful. For any suggestions or corrections do not hesitate to comment.

[1] http://thrift.apache.org/static/thrift-20070401.pdf

Read Full Post »

Recently I came across to the need to write a custom Message Formatter to handle a custom message type in Axis2.  As it turned out writing a Message Formatter is a darn simple thing. Mainly you only have to implement three methods. :).

My scenario was to extract out a set of attachments within an incoming SOAP (not SwA or MTOM. A custom XML format to hold attachments) and send out a HTTP MIME multi-part message without SOAP. It has a custom content type “application/x-mime”. Incoming XML format is roughly as follows.


<soap:Body>

<attachments>

<!-- 1 or more occurrances -->

<attachment>[base64 encoded binary]</attachment>

</attachments>

<text>[xml content here]</text>

</soap:Body>

Attachments are of “application/pdf” content-type while text content is normal “application/xml” content-type.  Out bound message should be  a HTTP MIME multi-part (with multipart/related content-type) message as follows. (this listing is without root http headers)


--MIME-Boundary1223233

Content-Type: application/xml

[xml content here]

--MIME-Boundary1223233

Content-Type: application/pdf

Content-Transfer-Encoding: base64

Content-ID: 1.urn:uuid:2F3457E2A5DC1F05631300801064296@apache.org>

[base64 attachment-1]

.

.

.

--MIME-Boundary1223233

Content-Type: application/pdf

Content-Transfer-Encoding: base64

Content-ID: 1.urn:uuid:3243457E2A5DC1F05631364801064523@apache.org>

[base64 attachment-n]

--MIME-Boundary1223233--

Now with this requirement in mind let’s go through how to achieve this with an Axis2 Message Formatter. The interface to implement is org.apache.axis2.transport.MessageFormatter.  It’s methods are..

public byte[] getBytes(MessageContext messageContext, OMOutputFormat format)
throws AxisFault;

public void writeTo(MessageContext messageContext, OMOutputFormat format,
OutputStream outputStream, boolean preserve) throws AxisFault;

public String getContentType(MessageContext messageContext, OMOutputFormat format,
String soapAction);

public URL getTargetAddress(MessageContext messageContext, OMOutputFormat format,
URL targetURL) throws AxisFault;

public String formatSOAPAction(MessageContext messageContext, OMOutputFormat format,
String soapAction);

Main message formatting logic goes inside writeTo method and content type of output message should be returned from the getContentType method implementation. The getBytes method should return output message as a byte array formatted according to the given message format.. Other methods are auxiliary and in my case I just left empty implementations in them.
Now let’s go through the main implementation..

public void writeTo(MessageContext messageContext, OMOutputFormat omOutputFormat,
OutputStream outputStream, boolean b) throws AxisFault {

   MultipartWriter mpWriter = JavaMailMultipartWriterFactory.INSTANCE.createMultipartWriter(
   outputStream, omOutputFormat.getMimeBoundary());

   // Write text content
   OMElement textElement = body.getFirstChildWithName(
   new QName("text"));

   if (textElement != null) {
      writeText(mpWriter, textElement, getContentID());
   }

   // Write attachments..
   SOAPBody body = messageContext.getEnvelope().getBody();
   OMElement attachmentsElement = body.getFirstChildWithName(new QName("attachments"));

   if (attachmentsElement != null) {

      Iterator<OMElement> attachments = attachmentsElement.getChildElements();
      while (attachments.hasNext()) {
        writeAttachment(mpWriter, attachments.next(), getContentID());
      }
   }

   // Complete writing the message and flush the stream
   try {
      mpWriter.complete();
   } catch (IOException e) {
      throw new AxisFault("Failed to complete writing the MIME message..", e);
   }
}

Here I have used MultipartWriter API from Axiom to write MIME multi-parts to the output stream. After initialization of the MultipartWriter text content and attachments are written using private helper methods shown in the next listing. Finally the call to mpWriter.complete() flushes the stream and finish writing the MIME message.

private String getContentID() {
   return "1.urn:uuid:" + UUID.randomUUID() + "@altisource.com>";
}

private void writeAttachment(MultipartWriter mpWriter, OMElement attachment, String contentID)
throws AxisFault {
   try {
      mpWriter.writePart(new DataHandler(new ByteArrayDataSource(attachment.getText()
      .getBytes(), "application/pdf")),
      "base64", contentID);
   } catch (IOException e) {
      throw new AxisFault("Failed to write attachment..", e);
   }
}

private void writeText(MultipartWriter mpWriter, OMElement text, String contentID)
throws AxisFault {
   try {
      mpWriter.writePart(new DataHandler(new ByteArrayDataSource(text.toString().getBytes(),
      "application/xml")), "",
      contentID);
   } catch (IOException e) {
      throw new AxisFault("Failed to write text content..", e);
   }
}

The getContentID() method returns a unique ID for each part using java.util.UUID. The getBytes method uses writeTo implementation to a ByteArrayStream and return a byte array.

public byte[] getBytes(MessageContext messageContext, OMOutputFormat omOutputFormat)
throws AxisFault {

   ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
   writeTo(messageContext, omOutputFormat, bytesOut, false);

   return bytesOut.toByteArray();

}

The complete listing is shown below.

import org.apache.axiom.attachments.ByteArrayDataSource;
import org.apache.axiom.mime.MultipartWriter;
import org.apache.axiom.mime.impl.javamail.JavaMailMultipartWriterFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMOutputFormat;
import org.apache.axiom.soap.SOAPBody;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.transport.MessageFormatter;

import javax.activation.DataHandler;
import javax.xml.namespace.QName;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.util.Iterator;
import java.util.UUID;

public class MIMEFormatter implements MessageFormatter {

public byte[] getBytes(MessageContext messageContext, OMOutputFormat omOutputFormat)
throws AxisFault {

   ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
   writeTo(messageContext, omOutputFormat, bytesOut, false);

   return bytesOut.toByteArray();

}

public void writeTo(MessageContext messageContext, OMOutputFormat omOutputFormat,
OutputStream outputStream, boolean b) throws AxisFault {

   MultipartWriter mpWriter = JavaMailMultipartWriterFactory.INSTANCE.createMultipartWriter(
   outputStream, omOutputFormat.getMimeBoundary());

   // Write attachments..
   SOAPBody body = messageContext.getEnvelope().getBody();
   OMElement attachmentsElement = body.getFirstChildWithName(new QName("attachments"));

   if (attachmentsElement != null) {

      Iterator<OMElement> attachments = attachmentsElement.getChildElements();
      while (attachments.hasNext()) {
      writeAttachment(mpWriter, attachments.next(), getContentID());
   }

   // Write text content
   OMElement textElement = body.getFirstChildWithName(new QName("text"));

   if (textElement != null) {
      writeText(mpWriter, textElement, getContentID());
   }

   // Complete writing the message and flush the stream
   try {
      mpWriter.complete();
   } catch (IOException e) {
      throw new AxisFault("Failed to complete writing the MIME message..", e);
   }
}

public String getContentType(MessageContext messageContext, OMOutputFormat omOutputFormat,
String s) {
   return "multipart/related; " + " boundary=\"" + omOutputFormat.getMimeBoundary() + "\"; " +
   "type=\"application/xml\"";
}

public URL getTargetAddress(MessageContext messageContext, OMOutputFormat omOutputFormat,
URL url) throws AxisFault {
   return null;
}

public String formatSOAPAction(MessageContext messageContext, OMOutputFormat omOutputFormat,
String s) {
   return null;
}

/*******  Private Helper Methods *****/

private String getContentID() {
   return "1.urn:uuid:" + UUID.randomUUID() + "@altisource.com>";
}

private void writeAttachment(MultipartWriter mpWriter, OMElement attachment, String contentID)
throws AxisFault {
   try {
      mpWriter.writePart(new DataHandler(new ByteArrayDataSource(attachment.getText()
      .getBytes(), "application/pdf")),
      "base64", contentID);
   } catch (IOException e) {
      throw new AxisFault("Failed to write attachment..", e);
   }
}

private void writeText(MultipartWriter mpWriter, OMElement text, String contentID)
throws AxisFault {
   try {
      mpWriter.writePart(new DataHandler(new ByteArrayDataSource(text.toString().getBytes(),
      "application/xml")), "",
      contentID);
   } catch (IOException e) {
      throw new AxisFault("Failed to write text content..", e);
   }
}

}

Now you have to make a jar out of this class and put it in the classpath of Axis2 installation. Put the following entry under <messageFormatters> section in axis2.xml to engage this formatter for the content type “application/x-mime”.


<messageFormatter contentType="application/x-mime" class="MIMEFormatter"/>

That’s it. Pretty simple. Isn’t it. :).

Read Full Post »

An earlier post dealt with how a file can be copied to a product during a feature installation during a p2 provisioning action. This post expands on that. Although natives touchpoint has got a copy instruction it only works with files. But it has got an unzip instruction which can be used to unzip a zip file to a given target at feature installation time. So we can workaround the limitation of copy instruction using unzip by having a zip file of the folder to be copied inside the feature.

Following is a sample p2.inf configuration to unzip a folder called configFolder to configurations directory in the product.

instructions.configure = \

org.eclipse.equinox.p2.touchpoint.natives.unzip(source:${installFolder}/

configFolder.zip,target:${installFolder}/configurations);

Read Full Post »

This post will describe how to install additional files such as configuration files, licenses etc. to a product along with a equinox feature installation. The files of interest is described below.

1. p2.inf

This file can be used to declare provisioning actions. More information about p2.inf format can be found at [1].  Various installation actions can be done by using touchpoints. A touchpoint presents an interface between p2 and a particular runtime configuration. Currently there are two touchpoints which contain instructions to performed, org.eclipse.equinox.p2.touchpoint.natives (os level instructions) and org.eclipse.equinox.p2.touchpoint.eclipse (p2 level instructions). Instructions fall in to one of the phases “install”, “uninstall”, “configure”, “unconfigure” in the p2 engine phases. Many of these instructions accept arguments.
Comprehensive documentation about touchpoints can be found at [2].

For this discussion we use natives touchpoint’s copy instruction. There is a particular syntax that has to be followed when specifying a instruction.

Citing from documentation..

 As an example – an “install” instruction for a bundle might consist of the following statement:

installBundle(bundle:${artifact});

* installBundle is the action name
* bundle is the parameter name
* ${artifact} is the parameter value. The value ${artifact} signifies the use of a pre-defined variable named “artifact”.

Now let’s specify we need to copy conf.xml file to configuration folder upon feature installation.

instructions.configure =  \ org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/

conf.xml,target:${installFolder}/configuration/conf.xml,overwrite:true);

Here we have specified this instruction should be carried out in “configure” phase using instructions.configure directive. ${installFolder} is one of the predefined variables defined in all the phases which denotes the root folder for current profile.

2. build.properties

The role of the build.properties file is to map development-time structures in a bundle’s project onto the structures described in the bundle’s manifest and needed at runtime. It’s documentation can be found at [3]. In this case we configure it to copy our configuration file to the generated feature during feature build. Important thing to note is that build.properties file is used at build time while p2.inf is used at feature installation time to perform similar jobs.

We use a feature specific property root to indicate to list the files and folders to be copied to the root of the generated artifacts. Documentation about rootfiles can be found at [4].

Here are the entries in the build.properties used in this example.

custom=true
root=file:conf.xml

From documentation..

‘custom=true’  – indicates that the build script is hand-crafted as opposed to automatically generated. Therefore no other value is consulted.

“file:” prefix is used to denote a relative path while “absolute:” prefix is used to denote an absolute path. Relative paths are taken relative to the containing feature.

With these configurations in place we are ready to build the feature and provision it in to the respective profile.

[1] http://wiki.eclipse.org/Equinox/p2/Customizing_Metadata

[2] http://help.eclipse.org/helios/index.jsp?topic=/org.eclipse.platform.doc.isv/guide/p2_actions_touchpoints.html

[3] http://help.eclipse.org/helios/topic/org.eclipse.pde.doc.user/reference/pde_feature_generating_build.htm

[4] http://help.eclipse.org/helios/index.jsp?topic=/org.eclipse.pde.doc.user/tasks/pde_rootfiles.htm

Read Full Post »

Building Apache ODE

It seems building ODE is not the easiest thing to do. I found this the hard way when I really messed up all sorts of things when trying to build ODE in my office machine. (Funny thing is that it went to the extent of reinstalling the OS). The process is quite strange if you are new to the Ruby world like me, because ODE uses a Ruby build system called buildr. Before installing buildr following packages has to be installed if Ruby is not installed in your system.

Ruby – packages related to the language
RubyGems – the Ruby package manager

buildr internally uses the Ruby build program Rake which is a simple ruby build program with capabilities similar to make.

So the second time I was prepared. I metaculously went through the instructions in the ODE and buildr sites. Since the information is scattered a bit across sites, here is the exact the steps I went through for a succesful ODE build. Note that ODE branch and ODE trunk uses different versions of buildr. So both versions of buildr has to be installed first.

Important: I am using an Ubuntu box. So these steps are for an Ubuntu system.

Installing buildr

Step 1 – Installing Ruby

$ sudo apt-get install ruby-full ruby1.8-dev libopenssl-ruby build-essential

Step 2 – Installing RubyGems

$ wget http://rubyforge.org/frs/download.php/38646/rubygems-1.2.0.tgz
$ tar xzf rubygems-1.2.0.tgz
$ cd rubygems-1.2.0
$ sudo ruby setup.rb
$ sudo ln -s /usr/bin/gem1.8 /usr/bin/gem

Step 3 – Installing buildr

$ sudo env JAVA_HOME=$JAVA_HOME gem install buildr -v 1.2.10
//this version is used to build ODE branch

$ sudo env JAVA_HOME=$JAVA_HOME gem install buildr -v 1.3.2
//this version is used to build ODE trunk

Step 4 – Source checkout

Now installing buildr has been done ODE can be built from its source. To checkout ODE.

Branch

$> svn checkout http://svn.apache.org/repos/asf/ode/branches/APACHE_ODE_1.X ode-1.X

Trunk

$> svn checkout http://svn.apache.org/repos/asf/ode/trunk ode-trunk

Step 5 – Build ODE

Go to the checkout directory. In this case ode-1.x in case of branch and ode-trunk in case of trunk.

Building branch

$> buildr _1.2.10_ clean install TEST=no

Building trunk

$> buildr _1.3.2_ clean install TEST=no

Generating Eclipse project files

$> buildr eclipse

Generating IDEA project files

$> buildr idea

Note: It also work even if you don’t mention the buildr version in trunk build. But in the branch build version number has to be used. Otherwise you may see an error similar to this.

rake aborted!
can’t activate buildr (~> 1.2.4, runtime), already activated buildr-1.3.2
~/ode-1.x/Rakefile:17

Read Full Post »