1

I am looking for a way to (re)deploy an exploded bundle (meaning not jarred but in a folder) to a running Apache Felix OSGi container from within Eclipse, preferably using a launch task.

I found this question, which has an answer that comes close but it depends on typing commands into a Gogo shell, which is not convenient for long-term development use. I'd like to use Eclipse's launch task mechanism for this, but if there are alternatives that are equally fast and convenient I am open to that as well.

Now I think that if I can fire Gogo shell commands from an Eclipse launch tasks, that would be a solution, but I also can't get my head around how to do that. I presume I need the Remote Shell bundle for that right?

I am starting to think about writing a telnet client in Java that can connect to the Remote Shell bundle and execute Gogo commands in an automated fashion. I have seen some example of that already which I can modify to suit my needs... However I am getting a 'reinventing the wheel' kind of feeling from that. Surely there is a better way?

Some background to help you understand what I am doing:

I have set up an Eclipse 'OSGiContainer' project which basically contains the Apache Felix jar and the third party bundles I want to deploy (like Gogo shell), similar to the project setup described here. Then I created a second 'MyBundle' project that contains my bundle. I want to start the OSGi container by launching the OSGiContainer project, and then just develop on my bundle and test my changes by launching the MyBundle project into the OSGiContainer that I just want to keep running the whole time during development.

Project layout:

  • OSGiContainer
    • bin (contains felix jar)
    • bundles (third party bundles)
    • conf (Felix' config.properties file)
  • MyBundle
    • src
    • target
      • classes

I am then able to deploy my bundle to the OSGi container by invoking these commands on the Gogo shell:

install reference:file:../MyBundle/target/classes
start <bundleId>

To re-deploy, I invoke these commands:

stop <bundleId>
uninstall <bundleId>
install reference:file:../MyBundle/target/classes
start <bundleId>

You can imagine having to invoke 4 commands on the shell each time is not that much fun... So even if you can give me a way to boil this down to less commands to type it would be a great improvement already.

UPDATE

I hacked around a bit and came up with the class below. It's an adaptation of the telnet example with some small changes and a main method with the necessary commands to uninstall a bundle and then re-install and start it. The path to the bundle should be given as an argument to the program and would look like:

reference:file:../MyBundle/target/classes

I still very much welcome answers to this question, as I don't really like this solution at all. I have however verified that this works:

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.SocketException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;

import org.apache.commons.net.telnet.TelnetClient;

public class GogoDeployer {
    static class Responder extends Thread {
        private StringBuilder builder = new StringBuilder();
        private final GogoDeployer checker;
        private CountDownLatch latch;
        private String waitFor = null;
        private boolean isKeepRunning = true;

        Responder(GogoDeployer checker) {
            this.checker = checker;
        }

        boolean foundWaitFor(String waitFor) {
            return builder.toString().contains(waitFor);
        }

        public synchronized String getAndClearBuffer() {
            String result = builder.toString();
            builder = new StringBuilder();
            return result;
        }

        @Override
        public void run() {
            while (isKeepRunning) {
                String s;

                try {
                    s = checker.messageQueue.take();
                } catch (InterruptedException e) {
                    break;
                }

                synchronized (Responder.class) {
                    builder.append(s);
                }

                if (waitFor != null && latch != null && foundWaitFor(waitFor)) {
                    latch.countDown();
                }
            }
            System.out.println("Responder stopped.");
        }

        public String waitFor(String waitFor) {
            synchronized (Responder.class) {
                if (foundWaitFor(waitFor)) {
                    return getAndClearBuffer();
                }
            }

            this.waitFor = waitFor;
            latch = new CountDownLatch(1);
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
                return null;
            }

            String result = null;
            synchronized (Responder.class) {
                result = builder.toString();
                builder = new StringBuilder();
            }

            return result;
        }
    }

    static class TelnetReader extends Thread {
        private boolean isKeepRunning = true;
        private final GogoDeployer checker;
        private final TelnetClient tc;

        TelnetReader(GogoDeployer checker, TelnetClient tc) {
            this.checker = checker;
            this.tc = tc;
        }

        @Override
        public void run() {
            InputStream instr = tc.getInputStream();

            try {
                byte[] buff = new byte[1024];
                int ret_read = 0;

                do {
                    if (instr.available() > 0) {
                        ret_read = instr.read(buff);
                    }
                    if (ret_read > 0) {
                        checker.sendForResponse(new String(buff, 0, ret_read));
                        ret_read = 0;
                    }
                } while (isKeepRunning && (ret_read >= 0));
            } catch (Exception e) {
                System.err.println("Exception while reading socket:" + e.getMessage());
            }

            try {
                tc.disconnect();
                checker.stop();
                System.out.println("Disconnected.");
            } catch (Exception e) {
                System.err.println("Exception while closing telnet:" + e.getMessage());
            }
        }
    }

    private static final String prompt = "g!";
    private static GogoDeployer client;


    private String host;
    private BlockingQueue<String> messageQueue = new LinkedBlockingQueue<String>();
    private int port;
    private TelnetReader reader;
    private Responder responder;
    private TelnetClient tc;

    public GogoDeployer(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void stop() {
        responder.isKeepRunning = false;
        reader.isKeepRunning = false;

        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
        }
        responder.interrupt();
        reader.interrupt();
    }

    public void send(String command) {
        PrintStream ps = new PrintStream(tc.getOutputStream());
        ps.println(command);
        ps.flush();
    }

    public void sendForResponse(String s) {
        messageQueue.add(s);
    }

    public void connect() throws SocketException, IOException {
        tc = new TelnetClient();
        tc.connect(host, port);
        reader = new TelnetReader(this, tc);
        reader.start();
        responder = new Responder(this);
        responder.start();
    }

    public String waitFor(String s) {
        return responder.waitFor(s);
    }

    private static String exec(String cmd) {
        String result = "";
        System.out.println(cmd);
        client.send(cmd);
        result = client.waitFor(prompt);
        return result;
    }

    public static void main(String[] args) {
        try {
            String project = args[0];
            client = new GogoDeployer("localhost", 6666);
            client.connect();
            System.out.println(client.waitFor(prompt));
            System.out.println(exec("uninstall " + project));
            String result = exec("install " + project);
            System.out.println(result);
            int start = result.indexOf(":");
            int stop = result.indexOf(prompt);
            String bundleId = result.substring(start + 1, stop).trim();
            System.out.println(exec("start " + bundleId));
            client.stop();
        } catch (SocketException e) {
            System.err.println("Unable to conect to Gogo remote shell: " + e.getMessage());
        } catch (IOException e) {
            System.err.println("Unable to conect to Gogo remote shell: " + e.getMessage());
        }
    }
}
Community
  • 1
  • 1
Stijn de Witt
  • 40,192
  • 13
  • 79
  • 80
  • 1
    Any reason why you want to live with such a complex setup?? With bndtools all you do is ^s after a source change and every bundle that is changed gets redeployed in seconds. And it uses the real jars, not something that is 'almost' looking like it. – Peter Kriens Apr 01 '13 at 07:55
  • Actually, the setup of my OSGiContainer project comes straight from the Apache website itself. The other bundle project is as simple as can be. Are you saying without BND tools it will become very complex to redeploy? I am currently using Maven with m2eclipse, so I am hesitating to add yet another build tool to the mix... – Stijn de Witt Apr 01 '13 at 09:54
  • Clarification: I am using Maven with maven-bundle-plugin to build the bundle via BND, and integrating Maven into Eclipse with the m2eclipse plugin... I will try out bndtools. – Stijn de Witt Apr 01 '13 at 10:12
  • @PeterKriens : Ok, so I found this post: https://blogs.paremus.com/2011/03/towards-maven-support-in-bndtools/ however, it links to an update site that Eclipse can't use... It's a year old so this might be in the released version now? Can I just install Bndtools via the marketplace as detailed here http://bndtools.org/installation.html ? Does this include the Maven integration? I really want to use Maven for building outside of Eclipse and m2eclipse allows me to reuse the POM inside Eclipse. I'd like Bndtools to use that too like the article under the first link describes... – Stijn de Witt Apr 01 '13 at 15:12
  • Well, OSGi is a dream for deployment and it is definitely possible. However, there are always subtle differences between a JAR and a directory layout. Since bndtools seems to be doing exactly what you need, you do not need to reinvent the wheel ... – Peter Kriens Apr 01 '13 at 15:14
  • @PeterKriens I really want to be able to build my project separate from Eclipse... Can this be done with Bndtools? It's an Eclipse specific thing right? – Stijn de Witt Apr 01 '13 at 15:17
  • bndtools is based on bnd. So you can use maven, ant, sbt, or virtually any tool to build it separate from Eclipse. That was the primary design goal of of bnd. However, especially with maven there are some catches. This is inevitable, a GUI and a offline build tool are very different beasts. There is some really interesting work going on to minimize those hiccups with maven. – Peter Kriens Apr 02 '13 at 12:48
  • Yeah, but when I take my Maven project (with maven-bundle-plugin configured to use Bnd to build the bundle), and ask Bndtools to 'Add Bndtools Project Nature', it creates a bnd.bnd file, asks me to create a configuration project called 'conf' and basically starts out as a blank project, ignoring all dependencies in my Maven pom.xml... As I said, I can find an old blog post, but there is no word on the official site about Maven apart from Bndtools being able to fetch bundles from Maven repository. – Stijn de Witt Apr 02 '13 at 19:58
  • 1
    @StijndeWitt correct. Maven support was attempted but was killed: https://github.com/bndtools/bnd/issues/629. See https://github.com/bndtools/bndtools/wiki/Maven-Integration-Requirements for details. – btiernay Mar 15 '15 at 15:09

1 Answers1

1

When I met the same requirement (deploy bundle from target/classes as fast as I can) my first thought was also extending my container with some shell functionality. My second thought was, however, to write a simple bundle that opens up an always-on-top window and I can simply drag-and-drop any project(s) from Eclipse (or total commander or whatever) to that window. The code than checks if the folder(s) that was dropped has a target/classes folder and if it has it will be deployed.

The source code is available at https://github.com/everit-org/osgi-richconsole

The dependency is available from the maven-central.

The dependency is:

<dependency>
    <groupId>org.everit.osgi.dev</groupId>
    <artifactId>org.everit.osgi.dev.richconsole</artifactId>
    <version>1.2.0</version>
</dependency>

You can use the bundle it while you develop and remove it when you set up your live server. However it is not necessary as if the container is running in a headless mode the pop-up window is not shown.

I called it richconsole as I would like to have more features in the future (not just deployment) :)

Balazs Zsoldos
  • 6,036
  • 2
  • 23
  • 31
  • Thanks, cool idea, I will try it out once I start up OSGi bundle development again (currently working on something very different) – Stijn de Witt Feb 25 '14 at 14:17