Eclipse Gyrex

Los gehts, Workspace einrichten!

Genug der einführenden Worte. Im Folgenden soll Gyrex anhand einiger einfacher Beispiele demonstriert werden. Wer möchte, kann sich die folgenden Schritte auch per Video erklären lassen. Die passenden Screencasts samt Quellcode der Beispiele sind online verfügbar [10]. Um Gyrex-basierte Anwendungen entwickeln zu können, benötigt man das Eclipse Plug-in Development Environment (PDE). Wer also bereits Eclipse-Plug-ins entwickelt, für den sollten Plug-ins/Bundles und Features nichts Neues sein. Eclipse PDE ist beispielsweise im Paket „Eclipse for RCP and RAP Developers“ [11] enthalten. Weiterhin sollte sichergestellt sein, dass ein aktuelles Java Development Kit in Version 6 benutzt wird.

Zuerst ist es notwendig, sich einen frischen Workspace mit einer passenden Target Platform [12] einzurichten. Dazu legt man über WINDOW | PREFERENCES | PLUG-IN DEVELOPMENT | TARGET PLATFORM eine neue Target Platform an. Dieser Target Platform fügt man das Gyrex Target Components und das Dependencies Feature hinzu, die von der Gyrex Releases Software Site [13] bereitgestellt werden (Abb. 3). Als Nächstes wird ein erstes Plug-in-Projekt angelegt, das unseren Beispielcode enthalten wird. Ein neues Projekt wird mittels FILE | NEW | PROJECT | PLUG-IN DEVELOPMENT | PLUG-IN PROJECT erstellt. Allerdings sollte man darauf achten, dass im darauffolgenden Plug-in Project Wizard das Häkchen bei „This plug-in will make contributions to the UI“ nicht gesetzt ist. Weil es in einem realen Projekt meist nicht bei einem einzelnen Plug-in bleiben wird, erstellen wir auch noch gleich ein Featureprojekt. Features ermöglichen es, zusammengehörende Plug-ins zu gruppieren. Damit erlauben sie eine leichtere Verwaltung, wenn es an die Themen Build und Deployment geht.

Abb. 3: Einrichten einer Target PlatformAbb. 3: Einrichten einer Target Platform (Vergrößern)

„Hallo Server“-Welt

Ein sehr beliebtes Szenario für serverbasierte Lösungen sind Webanwendungen. Deshalb soll es im folgenden Beispiel auch um eine Webanwendungen gehen. Über ein Formular werden Grußbotschaften angezeigt und neue Grüße eingegeben. Sie werden von einem Hintergrundprozess asynchron moderiert. Im Anschluss wollen wir betrachten, wie das Ganze skaliert werden kann (z. B. Ausführung des Hintergrundprozesses auf eigenen Nodes in der Cloud).

Da wir von vorne herein auf OSGi setzen, empfiehlt sich der Einsatz von OSGi Services für unsere Anwendung. Als Erstes legen wir fest, wie der Service funktionieren soll. Dafür wird der GreetingService als Java-Interface definiert. Der Service bekommt genau drei Methoden: eine zum Grüßen, eine zum Verarbeiten der Grußbotschaften und eine zum Abrufen von Grußbotschaften. Dieser Service erhält eine einfache Implementierung, die das Eclipse Preferences API nutzt. Das Eclipse Preferences API erlaubt das Ablegen von Information (einfache Strings, Zahlen, Flags oder binäre Daten) anhand eines Key in eine Hierarchie für einen bestimmten Scope. Equinox selber implementiert den Default, die Configuration und den Instance Scope. Der Default Scope hält die Daten im RAM und wird beim Starten der Anwendungen (sinngemäß) initialisiert. Der Configuration Scope legt die Daten standardmäßig im Installationsverzeichnis der Anwendung ab und der Instance Scope im Workspace-Verzeichnis. Durch Gyrex kommt noch ein Cloud Scope hinzu. Er erlaubt das Ablegen der Daten „in der Cloud“. Damit ist primär der Apache-ZooKeeper-Cluster gemeint. Allerdings ist auch eine alternative Implementierung denkbar, die die Daten in einem Cloud-Anbieter-spezifischen Speicher ablegt. Als erste Funktion von Gyrex wird unser Service den Cloud Scope nutzen, um Daten im Apache ZooKeeper abzulegen (Listing 1).

Listing 1: Ein Service für Grußbotschaften
public interface GreetingService {
  /**
   * Returns a list of all available greetings.
   * @return a collection of all greetings
   * @throws Exception in case of errors
   */
  Collection getGreetings() throws Exception;
  /**
   * Processes all greeting.
   * @throws Exception in case of errors
   */
  void processGreetings() throws Exception;
  /**
   * Adds a new greeting.
   * @param greeting
   *            the greeting to add (may not be null)
   * @throws Exception in case of errors
   */
  void sayHello(final String greeting) throws Exception;
}
// * Implementaion of {@link GreetingService} 
public class GreetingServiceImpl implements GreetingService {
  .
  /**
   * The preference node where all greetings are saved.
   * @return the greetings preference node
   */
  private IEclipsePreferences getGreetingsNode() {
    return CloudScope.INSTANCE.getNode("hello.servlet.cloud");
  }

  @Override
  public void sayHello(final String greeting) throws Exception {
    if (null == greeting) {
      throw new IllegalArgumentException("greeting must not be null");
    }

    //  create new node with greeting
    final IEclipsePreferences greetingsNode = getGreetingsNode();
    final Preferences node = greetingsNode.node(DateFormatUtils.format(System.currentTimeMillis(), "yyyyMMddHHmmssSSS"));
    // populate
    node.put(KEY_TEXT, greeting);
    node.put(KEY_SUBMITTED_BY, StringUtils.trimToEmpty(myNodeId));
    // flush
    greetingsNode.flush();
  }
  ...
}

In Java ist die elementarste Form der Webanwendung das Servlet (einmal von purem IO und der HTTP-Verbindung abgesehen). In OSGi erfolgt das Registrieren von Servlets dynamisch über den HttpService [14]. Das geht sowohl programmatisch als auch deklarativ über OSGi Declarative Services. Beim Registrieren muss der Alias (z. B. /hello) angegeben werden. Der Alias ist das Pfadpräfix des URL, unter dem das Servlet später erreichbar sein soll. Des Servlet selbst ist relativ einfach gehalten. Es stellt das Formular dar und gibt die bereits freigegebenen Grußbotschaften aus (Listing 2). Dazu nutzt es den Service, den wir zuvor implementiert haben.

Listing 2: Servlet und OSGi DS Component
public class HelloCloudServlet extends HttpServlet {
  private static final String MSG_ERROR_MISSING_PARAMETER = "errorMissingParameter";
  private static final String MSG_ACTION_ADDED = "added";
  private final GreetingServiceProvider provider;
  /**
   * Creates a new instance.
   * @param provider the service provider
   */
  public HelloCloudServlet(final GreetingServiceProvider provider) {
    this.provider = provider;
  }
  @Override
  protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
    resp.setContentType("text/html");
    resp.setCharacterEncoding(CharEncoding.UTF_8);

    // read greetings
    Collection greetings;
    try {
      greetings = getService().getGreetings();
    } catch (final IllegalStateException e) {
      resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, e.getMessage());
      return;
    } catch (final Exception e) {
      resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ExceptionUtils.getRootCauseMessage(e));
      return;
    }

    // render HTML
    final PrintWriter writer = resp.getWriter();

    printHeader(writer);
    printMessage(req, writer);

    printGreetings(writer, greetings);
    printAddGreetingForm(writer);

    printFooter(writer);
  }

  @Override
  protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
    final String greeting = req.getParameter("greeting");
    if (StringUtils.isBlank(greeting)) {
      resp.sendRedirect(req.getRequestURL().append("?").append(MSG_ERROR_MISSING_PARAMETER).toString());
      return;
    }

    // post to service
    try {
      getService().sayHello(greeting);
    } catch (final IllegalStateException e) {
      resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, e.getMessage());
      return;
    } catch (final Exception e) {
      resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ExceptionUtils.getRootCauseMessage(e));
      return;
    }

    // redirect and show success message
resp.sendRedirect(req.getRequestURL().append("?").append(MSG_ACTION_ADDED).toString());
  }
  ...
}

/**
 * OSGi service component for registering {@link HelloCloudServlet} with any
 * available {@link HttpService}.
 */
public class ServletComponent implements GreetingServiceProvider {
  private static final String ALIAS = "/hellocloud";
  private GreetingService service;

  @Override
  public GreetingService getService() {
    return service;
  }

  public void registerWithHttpService(final HttpService httpService) {
    try {
      System.out.printf("Registering %s with %s%n", ALIAS, String.valueOf(httpService));
      httpService.registerServlet(ALIAS, new HelloCloudServlet(this), null, null);
    } catch (final Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * Sets the node environment.
   * @param environment 
   *            the environment to set
   */
  public void setEnvironment(final INodeEnvironment environment) {
    service = new GreetingServiceImpl(environment.getNodeId());
  }

  public void unregisterFromHttpService(final HttpService httpService) {
    System.out.printf("Unregistering %s from %s%n", ALIAS, String.valueOf(httpService));
    httpService.unregister(ALIAS);
  }
}

servlets.xml:
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.