Montag, 2. Juli 2012

Präsentationen mit reveal.js und Groovy


Gut aussehende Online-Präsentationen können mit reveal.js (https://github.com/hakimel/reveal.js) erstellt werden. Diese werden in dabei HTML geschrieben. Natürlich kann man auch Groovy's MarkupBuilder verwenden.

Der HTML-Code wird dann von Groovy erstellt. Das hat folgende Vorteile:
  • Trennung und Auslagerung von wiederkehrenden und gleichbleibenden Codeabschnitten
  • Auslagern vom Beispielcode in die entsprechenden Quellcodedateien
  • Auslagerung von Einstellungen, wie zum Beispiel die Ziel-URL
  • Der Funktionsumfang wird durch den MarkupBuilder nicht eingeschränkt
  • Builder-Code ist meiner Meinung nach etwas übersichtlicher als HTML-Code

Head-Daten, Cover und eine Seite der Präsentation können dann wie folgt aussehen:

// this script contains data of the presentation

// header data
data.head.title = 'reveal.js'
data.head.description = 'An easy to use CSS 3D slideshow tool for quickly creating good looking HTML presentations.'
data.head.author = 'Hakim El Hattab'

// title page
data.cover.title = 'Reveal.js'
data.cover.subtitle = 'CSS 3D Presentations'

// sections
sections.section {
    h2 'Heads Up'
    p """reveal.js is an easy to use, HTML based, presentation tool. You'll need a modern browser with
            support for CSS 3D transforms to see it in its full glory."""
    p {
        i {
            small {
                mkp.yield '-'
                a(href: 'http://hakim.se', 'Hakim El Hattab')
                mkp.yield ' /'
                a(href: 'http://twitter.com/hakimel', '@hakimel')
            }
        }
    }
}
...

Hierbei wird die Seite mit dem MarkupBuilder sections erstellt.

Die Initialisierung der Variablen data (Daten der Präsentation) und sections (MarkupBuilder für die einzelnen Seiten) erfolgen im allgemeinen, bei jeder Präsentation gleichbleibenden, Script index-html-builder.groovy:

...
// Map and builder initialisation (presentation data)
def data = [:]
data.head = [:]
data.cover = [:]
data.sectionWriter = new StringWriter()
def sections = new groovy.xml.MarkupBuilder(data.sectionWriter)
...

Anschließend werden die Daten der Präsentation (siehe oben) aus dem Script index-data.groovy bezogen:

...
// call script with presentation data
def scriptEngine = new GroovyScriptEngine([scriptPath] as String[])

def binding = new Binding()
binding.setVariable 'data', data
binding.setVariable 'sections', sections
binding.setVariable 'scriptPath', scriptPath
binding.setVariable 'targetName', targetName
binding.setVariable 'targetUrl', targetUrl

data = scriptEngine.run 'index-data.groovy', binding
...

Jetzt sind alle Daten der Präsentation vorhanden und die gesamte HTML-Datei wird mit dem MarkupBuilder erstellt:

...
// builder initialisation (html file)
def fileName = "$targetDir/$targetName"
def fileWriter = new FileWriter(fileName)
def builder = new groovy.xml.MarkupBuilder(fileWriter)

// presentation build
builder.html(lang: 'en') {
    head {
        meta(charset: 'UTF-8')

        title data.head.title

        meta(name: 'description', content: data.head.description)
        meta(name: 'author', content: data.head.author)

        meta(name: 'apple-mobile-web-app-capable', content: 'yes')
        meta(name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent')

        link(href: 'http://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic', rel: 'stylesheet', type: 'text/css')


        link(rel: 'stylesheet', href: 'css/reset.css')
        link(rel: 'stylesheet', href: 'css/main.css')
        link(rel: 'stylesheet', href: 'css/print.css', type: 'text/css', media: 'print')

        link(rel: 'stylesheet', href: 'lib/zenburn.css')

    }

    body {
        div(class: 'reveal') {

            // Used to fade in a background when a specific slide state is reached
            div(class: 'state-background')

            // Any section element inside of this container is displayed as a slide
            div(class: 'slides') {


                section(class: 'present', style: 'display: block;') {
                    h1 data.cover.title
                    h3(class: 'inverted', data.cover.subtitle)

                    // Delicously hacky. Look away.
                    script """if( navigator.userAgent.match( /(iPhone|iPad|iPod|Android)/i ) ) document.write( '<p style="color: rgba(0,0,0,0.3); text-shadow: none;">('+'Tap to navigate'+')</p>' );"""
                }

                mkp.yieldUnescaped data.sectionWriter
            }
...

Mit der Zeile "mkp.yieldUnescaped data.sectionWriter" werden die einzelnen Seiten aus dem Builder der Seiten in dem Builder für das gesamte HTML übernommen.

Grundlegende Einstellungen werden in der Datei gradle.properties vorgenommen.

Hier das Script index-data.groovy für die Beispielpräsentation (http://www.aonnet.de/reveal-js-groovy):

// this script contains data of the presentation

// header data
data.head.title = 'reveal.js'
data.head.description = 'An easy to use CSS 3D slideshow tool for quickly creating good looking HTML presentations.'
data.head.author = 'Hakim El Hattab'

// title page
data.cover.title = 'Reveal.js'
data.cover.subtitle = 'CSS 3D Presentations'

// sections
sections.section {
    h2 'Heads Up'
    p """reveal.js is an easy to use, HTML based, presentation tool. You'll need a modern browser with
            support for CSS 3D transforms to see it in its full glory."""
    p {
        i {
            small {
                mkp.yield '-'
                a(href: 'http://hakim.se', 'Hakim El Hattab')
                mkp.yield ' /'
                a(href: 'http://twitter.com/hakimel', '@hakimel')
            }
        }
    }
}

sections.section {
    h2 'What\'s the deal with Groovy?'
    p {
        mkp.yield 'Groovy is an agile and dynamic language for the Java Virtual Machine.'
        a(href: 'http://groovy.codehaus.org/Creating+XML+using+Groovy%27s+MarkupBuilder', "Groovy's MarkupBuilder")
        mkp.yield " supports Groovy's builder pattern with XML/HTML."
    }

    p {
        i {
            small {
                mkp.yield '-'
                a(href: 'http://www.aonnet.de', 'Thomas Westphal')
                mkp.yield ' /'
                a(href: 'http://twitter.com/twest72', '@twest72')
                mkp.yield ' /'
                a(href: 'https://github.com/twest72', 'twest72@github')
            }
        }
    }
}

sections.section {
    h2 'Pretty Groovy Code for reveal.js'
    p "The following script contains the code for this page. 'example.groovy' is a Groovy script."
    pre {
        code(class:'java', contenteditable: '') {
            mkp.yieldUnescaped new File("$scriptPath/example.groovy").text
        }
    }
    p {
        mkp.yield 'Courtesy of'
        a(href: 'http://softwaremaniacs.org/soft/highlight/en/description/', 'highlight.js')
    }
}

// Example of nested vertical slides
sections.section {
    section {
        h2 'Vertical Slides'
        p {
            mkp.yield 'Slides can be nested inside of other slides,'
            br()
            mkp.yield 'try pressing'
            a(href: '#/2/1', 'down.')
        }
        a(href: '#/2/1', class: 'image') {
            img(src: 'https://s3.amazonaws.com/hakim-static/reveal-js/arrow.png', height: '238', width: '178')
        }
    }
    section {
        h2 'Basement Level 1'

        p 'Press down or up to navigate.'
    }
    section {
        h2 'Basement Level 2'
        p 'Cornify'
        a(href: 'http://cornify.com', class: 'test') {
            img(src: 'https://s3.amazonaws.com/hakim-static/reveal-js/cornify.gif', height: '326', width: '280')
        }
    }
    section {
        h2 'Basement Level 3'
        p '''That's it, time to go back up.'''
        a(href: '#/2', class: 'image') {
            img(src: 'https://s3.amazonaws.com/hakim-static/reveal-js/arrow.png', height: '238', width: '178')
        }
    }
}


sections.section {
    h2 'Holistic Overview'
    p {
        mkp.yield 'Press'
        strong 'ESC'
        mkp.yield ' to enter the slide overview!'
    }
}


sections.section {
    h2 'Works in Mobile Safari'
    p 'Try it out! You can swipe through the slides pinch your way to the overview.'
}


sections.section {
    h2 'Transition Styles'
    p 'You can select from different transitions, like:'
    ul {
        li { a(href: "${ targetUrl }?transition=cube", 'Cube') }
        li { a(href: "${ targetUrl }?transition=page", 'Page') }
        li { a(href: "${ targetUrl }?transition=concave", 'Concave') }
        li { a(href: "${ targetUrl }?transition=linear", 'Linear') }
    }
}


sections.section {
    h2 'Marvelous Unordered List'
    ul {
        li 'No order here'
        li 'Or here'
        li 'Or here'
        li 'Or here'
    }
}


sections.section {
    h2 'Fantastic Ordered List'
    ol {
        li 'One is smaller than...'
        li 'Two is smaller than...'
        li 'Three!'
    }
}


sections.section {
    h2 'Clever Quotes'
    p {
        mkp.yield 'These guys come in two forms, inline:'
        q(cite: 'http://searchservervirtualization.techtarget.com/definition/Our-Favorite-Technology-Quotations', 'The nice thing about standards is that there are so many to choose from')
        br()
        mkp.yield 'and block:'
    }
    blockquote(cite: 'http://searchservervirtualization.techtarget.com/definition/Our-Favorite-Technology-Quotations', 'For years there has been a theory that millions of monkeys typing at random on millions of typewriters would reproduce the entire works of Shakespeare. The Internet has proven this theory to be untrue.')
}


sections.section {
    h2 'Pretty Code'
    pre {
        code(contenteditable: '') {
            mkp.yieldUnescaped new File("$scriptPath/example.js").text
        }
    }
    p {
        mkp.yield 'Courtesy of'
        a(href: 'http://softwaremaniacs.org/soft/highlight/en/description/', 'highlight.js')
    }
}


sections.section {
    h2 'Stellar Links'
    ul {
        li {
            a(href: 'https://github.com/hakimel/reveal.js', 'Source code on github')
        }
        li {
            a(href: 'http://hakim.se/projects/reveal-js', 'Read more on my site')
        }
        li {
            a(href: 'http://twitter.com/hakimel', 'Follow me on Twitter')
        }
    }
}


sections.section {
    h1 'THE END'
    h3(class: 'inverted', 'BY Hakim El Hattab / hakim.se')
}

// returns the presentation data
return data

Man kann sich somit auf das Wesendliche, auf den Inhalt konzentrieren.



Mittwoch, 8. Februar 2012

Groovy Beans To Json

Groovy bietet mit dem groovy.json.JsonBuilder und dem groovy.json.JsonBuilder sehr gute und einfache Möglichkeiten zur Json-Verarbeitung. Hier sollen einfache Groovy Beans (POGO's - Plain Old Groovy Objects) in Json Objekte und wieder zurück transformiert werden.

Dies ist ja dank Builder und Slurper kein Hexenwerk mehr. Das Beispiel-Bean ist folgendes (beliebig austauschbar):

class Book {
    String title
    String author
}



Bean To Json

String toJsonImpl(def bean) {

    JsonBuilder builder = new JsonBuilder()
    builder {
        // Hier folgen Schlüssel und Wert des Eintrags
        // dabei ist eine Variable als Schlüssel nicht möglich
        // deshalb "${ ... }"
        "${ bean.class.getName() }" bean
    }

    return builder.toString()
}

Nun ist folgender Aufruf möglich:

assert '{"Book":{"title":"Die Entw...","author":"G.S."}}'
    == JsonUtil.toJsonImpl(new Book(title: "Die Entw...", author: "G.S."))

Um die Klassen eindeutig identifizieren zu können, wird hier als Json-Key auf oberster Ebene der Name der Klasse benutzt. Und nun der Weg zurück...



Json To Bean

def toBeanImpl(String json) {

    JsonSlurper slurper = new JsonSlurper()
    def beanData = slurper.parseText(json)

    String beanClassName
    Map beanParameter
    beanData.eachWithIndex { key, value, index ->
        if (index == 0) {
            beanClassName = key
            beanParameter = value
        }
    }

    def beanInstance = Class.forName(beanClassName)
            .newInstance(beanParameter)
    return beanInstance
}

Damit ist der Aufruf möglich:

assert JsonUtil.toBeanImpl(
        '{"Book":{"title":"Die Entw...","author":"G.S."}}').properties
        == new Book(title: "Die Entw...", author: "G.S.").properties



Meta Programming

Jetzt können die beiden Funktionen noch an die Beispielklasse “angehängt“ werden. Dass kann zum Beispiel beim automatischen Anhängen an eine Reihe von Klassen interessant sein:

Book.metaClass.toJson = {
    return JsonUtil.toJsonImpl(delegate)
}

Book.metaClass.'static'.toBean = { String json ->
    return JsonUtil.toBeanImpl(json)
}

Die Methode “toBean“ wurde dabei statisch (an der Klasse) hinzugefügt.
Jetzt ist der Aufruf toJson() direkt an der Instanz und toBean(...) an der Klasse möglich:

Book bookBean = new Book(title: "Groovy in Action", author: "Dierk Konig")
String jsonBook = bookBean.toJson()
Book bookBean2 = Book.toBean(jsonBook)
assert bookBean.properties == bookBean2.properties


Na dann viel Spaß beim “Bauen“ und “Schlürfen“.



Mittwoch, 26. Oktober 2011

Der schöne sudo-Dialog mit Ubuntu & GNOME3 - pkexec

Manche Anwendungen möchte man einfach als root starten. Beispielsweise benötige ich einen zweiten Texteditor (Gedit) mit root-Rechten um bequem Systemeinstellungen zu ändern.

Bisher habe ich diese Anwendungen unter Gnome 2 und 3 mit gksudo gestartet. Dazu habe ich die Datei gedit-root.desktop angelegt:

[Desktop Entry]
Name=gedit (root)
GenericName=Text Editor (root)
Comment=Edit text files
Exec=gksodu /usr/bin/gedit
Terminal=false
Type=Application
StartupNotify=true
MimeType=text/plain;
Icon=accessories-text-editor
Categories=GNOME;GTK;Utility;TextEditor;
X-GNOME-DocPath=gedit/gedit.xml
X-GNOME-FullName=Text Editor (root)
X-GNOME-Bugzilla-Bugzilla=GNOME
X-GNOME-Bugzilla-Product=gedit
X-GNOME-Bugzilla-Component=general
X-GNOME-Bugzilla-Version=3.0.0
X-GNOME-Bugzilla-ExtraInfoScript=/usr/share/gedit-2/gedit-bugreport
X-Ubuntu-Gettext-Domain=gedit

Der Dialog zur Autorisierung sieht dann ähnlich wie dieser aus:


Startet man unter GNOME 3 Anwendungen wie synaptic, fällt sofort der neue Dialog auf um diese Anwendung als root zu auszuführen.


Dieser Dialog wird mit dem Befehl pkexec aufgerufen. In der Datei gedit-root.desktop wird der Aufruf wie folgt geändert:

Exec=pkexec "/usr/bin/gedit"

Das genügt jedoch noch nicht ganz. Es muss noch eine Policy-Datei für den Aufruf angelegt werden (siehe http://hal.freedesktop.org/docs/polkit/pkexec.1.html). Diese Datei sieht dann wie folgt aus:



 
  Run Gedit as root
  Gedit als root starten
  Authentication is required to run Gedit
  Authorisierung ist erforderlich um Gedit zu starten
  gedit
  
  
   auth_admin
   auth_admin
   auth_admin
  

  /usr/bin/gedit
  true
 

Der Name der Datei ergibt sich aus der action id und der Endung policy. Sie wird dann unter "/usr/share/polkit-1/actions/de.aonnet.pkexec.gedit.policy" gespeichert.

Jetzt funktioniert der pkexec Aufruf und es erscheint der gewünschte Dialog:


Mittwoch, 19. Oktober 2011

Epiphany Webanwendungen mit eigenen Icons

Mit dem GNOME Browser Epiphany können Webanwendungen als eigene Anwendungen bereitgestellt werden. Diese können dann wie normale Anwendungen in einem eigenene Fenster gestartet werden (siehe Anmerkungen zur GNOME-Veröffentlichung 3.2). Für mich ist das zum Beispiel bei Google+ interessant, denn ich möchte Google+ die ganze Zeit unabhängig vom Browser (und Abstürzen) laufen lassen.

In Ubuntu 11.10 muss der Browser Epiphany 3.2.0 nachinstalliert werden. Das ist beispielsweise aus dem WebUpd8 GNOME 3 PPA möglich (siehe Things To Tweak After Installing Ubuntu 11.10 Oneiric Ocelot).

sudo add-apt-repository ppa:webupd8team/gnome3
sudo apt-get update
sudo apt-get install epiphany-browser

Dann Epiphany öffnen, gewünschte Webseite aufrufen und als Webanwendung speichern (Datei -> "Als Webanwendung speichern"). Der Name der neuen Anwendung kann jetzt geändert werden, das Icon leider nicht. Dies kann aber nach dem Speichern manuell erfolgen.


Ersteinaml ein passendes Icon besorgen (für Google+ zum Beispiel hier) und speichern. Jetzt genügt es folgendes Icon auszutauschen:


<USER_HOME>/.gnome2/epiphany/app-epiphany-<NAME_DER_ANWENDUNG>-<UUID>/app-icon.png


Das Ergebnis sieht in GNOME 3.2 so aus:


Die "Anwendung" Google+ kann jetzt wie jede andere Anwendung gestartet werden.

Mittwoch, 20. Juli 2011

Merkzettel: @NotNull Check mit aspectj

Möchte man nicht mit assert arbeiten um Parameter auf null zu prüfen, bietet sich aspectj an um einen komfortablen NotNull-Check zu implementieren. Vor allem wenn aspectj schon im Projekt vorhanden ist.

Anforderung: Alle Parameter in Methoden und Konstruktoren, die die Annotation @NotNull besitzen sollen geprüft werden. Wenn ein so markierter Parameter null ist, soll eine IllegalArgumentException ausgelöst werden.

Hier die Beispielimplementierung:

Als erstes die Annotation @NotNull für die zu prüfenden Parameter:

@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {

  String parameterName() default "";
}

Die Annotation @NotNull besitzt das Attribut parameterName. Hier kann der Name des Parameters übergeben werden. Dieser wird für die Fehlermeldung verwendet.



Dann benötigen wir einen Aspect, der alle Methoden und Konstruktoren mit der Annotation @NotNull "trifft".

Pointcut für alle Methoden mit der Annotation @NotNull: execution(* *(..,@NotNull (*),..))

Pointcut für alle Konstruktoren mit der Annotation @NotNull: execution(new(..,@NotNull (*),..))

@Aspect
public class CheckArgumentsAspect {

  /**
   * Der Pointcut trifft auf alle Methoden in allen Packages zu, die
   * mindestens einen Parameter mit der Annotation {@link NotNull} besitzen.
   */
  @Around("execution(* *(..,@NotNull (*),..))")
  public Object checkArgsForMethod(ProceedingJoinPoint joinPoint)
      throws Throwable {

    if (joinPoint.getSignature() instanceof MethodSignature) {

      MethodSignature methodSignature = (MethodSignature) joinPoint
          .getSignature();
      Method method = methodSignature.getMethod();
      Class<?>[] parameterTypes = method.getParameterTypes();
      Annotation[][] parameterAnnotationArray = method
          .getParameterAnnotations();

      System.out.println("Prüfe Parameter der Methode '"
          + methodSignature.toLongString() + "' mit den Parameterwerten '"
          + joinPoint.getArgs() + "'.");

      // Prüft alle Parameter
      checkParameter(joinPoint.getArgs(), parameterTypes,
          parameterAnnotationArray, methodSignature.toLongString());
    }

    return joinPoint.proceed();
  }

  /**
   * Der Pointcut trifft auf alle Konstruktoren zu, die mindestens einen
   * Parameter mit der Annotation {@link NotNull} besitzen.
   */
  @Around("execution(new(..,@NotNull (*),..))")
  public Object checkArgsForConstructor(ProceedingJoinPoint joinPoint)
      throws Throwable {

    if (joinPoint.getSignature() instanceof ConstructorSignature) {

      ConstructorSignature constructorSignature = 
          (ConstructorSignature) joinPoint.getSignature();
      Constructor<?> constructor = constructorSignature.getConstructor();
      Class<?>[] parameterTypes = constructor.getParameterTypes();
      Annotation[][] parameterAnnotationArray = constructor
          .getParameterAnnotations();

      System.out.println("Prüfe Parameter des Konstruktors '"
          + constructorSignature.toLongString()
          + "' mit den Parameterwerten '" + joinPoint.getArgs() + "'.");

      // Prüft alle Parameter
      checkParameter(joinPoint.getArgs(), parameterTypes,
          parameterAnnotationArray, constructorSignature.toLongString());
    }

    return joinPoint.proceed();
  }

  /**
   * Prüft alle Parameter.
   * 
   * @param parameter
   *          Alle Parameter.
   * @param parameterTypes
   *          Die Typen zu den Parametern.
   * @param parameterAnnotationArray
   *          Die Annotations zu den Parametern.
   * @param signature
   *          Die Signatur der Methode oder des Konstruktors.
   */
  private void checkParameter(Object[] parameter, Class<?>[] parameterTypes,
      Annotation[][] parameterAnnotationArray, String signature) {

    for (int i = 0; i < parameterTypes.length; i++) {

      Annotation[] parameterAnnotations = parameterAnnotationArray[i];

      for (Annotation annotation : parameterAnnotations) {

        if (annotation instanceof NotNull) {

          // Hier wird das Argument auf null geprüft
          checkNotNull(parameter[i], parameterTypes[i], i,
              ((NotNull) annotation).parameterName(), signature);
        }
      }
    }
  }

  /**
   * Prüft den Parameter auf null.
   * 
   * @param parameter
   *          Der Parameter.
   * @param parameterType
   *          Der Typ des Parameters.
   * @param parameterIndex
   *          Der Index des Parameters.
   * @param parameterName
   *          Der Name des Parameters.
   * @param signature
   *          Die Signatur der Methode oder des Konstruktors.
   */
  private void checkNotNull(Object parameter, Class<?> parameterType,
      int parameterIndex, String parameterName, String signature) {

    // Hier wird das Argument auf null geprüft
    if (parameter == null) {

      if (StringUtils.isBlank(parameterName)) {
        parameterName = "-";
      }

      String longMsg = MessageFormat.format(
          "Fehler: Der {0}. Parameter (Name: {1}, Typ: {2}) von {3} "
              + "ist null.", parameterIndex + 1, parameterName,
          parameterType.getName(), signature);
      throw new IllegalArgumentException(longMsg);
    }
  }
}


Eine Klasse mit zu prüfenden Parametern könnte zum Beispiel so aussehen:

public class TestClass {

  public TestClass() {
    super();
  }

  public TestClass(@NotNull(parameterName = "kNotNull") Integer kNotNull) {
    super();
    System.out.println("    kNotNull: " + kNotNull);
  }

  public void methodWithOneNotNullArg(
      @NotNull(parameterName = "aNotNull") Integer aNotNull) {
    System.out.println("    aNotNull: " + aNotNull);
  }

  public void methodWithOneNotNullArg(Long b,
      @NotNull(parameterName = "cNotNull") Integer cNotNull) {
    System.out.println("    b       : " + b);
    System.out.println("    cNotNull: " + cNotNull);
  }

  public void methodWithTwoNotNullArg(
      @NotNull(parameterName = "dNotNull") Integer dNotNull,
      @NotNull Integer eNotNull) {
    System.out.println("    dNotNull: " + dNotNull);
    System.out.println("    eNotNull: " + eNotNull);
  }

  public void methodWithoutNotNullArg(Long f) {
    System.out.println("    f       : " + f);
  }
}


Jetzt sollten alle Parameter mit der Annotation @NotNull geprüft werden. Hier noch der passende JUnit-Test dazu:

public class TestClassTest {

  private TestClass testClass = new TestClass();

  @Test
  public void testMethodWithOneNotNullArgInteger() {
    testClass.methodWithOneNotNullArg(1);
  }

  @Test
  public void testMethodWithOneNotNullArgIntegerNull() {
    try {
      testClass.methodWithOneNotNullArg(null);
      Assert.fail("Hier wurde eine IllegalArgumentException erwartet.");
    } catch (IllegalArgumentException e) {
      Assert.assertNotNull(e);
      System.err.println("    " + e.getMessage());
    }
  }

  @Test
  public void testMethodWithOneNotNullArgIntegerInteger() {
    testClass.methodWithOneNotNullArg(11l, 11);
  }

  @Test
  public void testMethodWithOneNotNullArgIntegerIntegerNull1() {
    testClass.methodWithOneNotNullArg(null, 11);
  }

  @Test
  public void testMethodWithOneNotNullArgIntegerIntegerNull2() {
    try {
      testClass.methodWithOneNotNullArg(11l, null);
      Assert.fail("Hier wurde eine IllegalArgumentException erwartet.");
    } catch (IllegalArgumentException e) {
      Assert.assertNotNull(e);
      System.err.println("    " + e.getMessage());
    }
  }

  @Test
  public void testMethodWithOneNotNullArgIntegerIntegerNullNull() {
    try {
      testClass.methodWithOneNotNullArg(null, null);
      Assert.fail("Hier wurde eine IllegalArgumentException erwartet.");
    } catch (IllegalArgumentException e) {
      Assert.assertNotNull(e);
      System.err.println("    " + e.getMessage());
    }
  }

  @Test
  public void testMethodWithTwoNotNullArg() {
    testClass.methodWithTwoNotNullArg(111, 111);
  }

  @Test
  public void testMethodWithTwoNotNullArgNull1() {
    try {
      testClass.methodWithTwoNotNullArg(null, 222);
      Assert.fail("Hier wurde eine IllegalArgumentException erwartet.");
    } catch (IllegalArgumentException e) {
      Assert.assertNotNull(e);
      System.err.println("    " + e.getMessage());
    }
  }

  @Test
  public void testMethodWithTwoNotNullArgNull2() {
    try {
      testClass.methodWithTwoNotNullArg(222, null);
      Assert.fail("Hier wurde eine IllegalArgumentException erwartet.");
    } catch (IllegalArgumentException e) {
      Assert.assertNotNull(e);
      System.err.println("    " + e.getMessage());
    }
  }

  @Test
  public void testMethodWithTwoNotNullArgNullNull() {
    try {
      testClass.methodWithTwoNotNullArg(null, null);
      Assert.fail("Hier wurde eine IllegalArgumentException erwartet.");
    } catch (IllegalArgumentException e) {
      Assert.assertNotNull(e);
      System.err.println("    " + e.getMessage());
    }
  }

  @Test
  public void testMethodWithoutNotNullArg() {
    testClass.methodWithoutNotNullArg(null);
  }

  @Test
  public void testMethodWithoutNotNullArgNull() {
    testClass.methodWithoutNotNullArg(null);
  }

  @Test
  public void testConstructorWithNotNullArg() {
    new TestClass(999);
  }

  @Test
  public void testConstructorWithNotNullArgNull() {
    try {
      new TestClass(null);
      Assert.fail("Hier wurde eine IllegalArgumentException erwartet.");
    } catch (IllegalArgumentException e) {
      Assert.assertNotNull(e);
      System.err.println("    " + e.getMessage());
    }
  }
}

Und jetzt oft @NotNull verwenden und weitere Annotations zum Prüfen von Argumenten erstellen!

Das Projekt befindet sich hier.

Mittwoch, 6. Juli 2011

Merkzettel: Java Prozesse beenden

Auch Java-Programme müssen manchmal mit Systemmitteln beendet werden. Dafür benötigt man die Prozess-ID PID. Diese kann beispielsweise auf einem UNIX oder LINUX-System mit dem Befehl ps angezeigt werden. Aber der Befehl führt oft nicht zum gewünschten Ergebnis:
> ps -A |grep java
  2016 ?           2:41 java
  5875 pts/2       0:05 java
 29278 ?           2:42 java
 26519 ?           0:23 java
 26623 ?           0:28 java
 26480 ?           0:24 java
 26146 ?           2:36 java
 26568 ?           0:24 java
 26668 ?           0:34 java
 26667 ?           1:10 java
  5588 pts/2       4:07 java
 26145 ?           0:10 java
  
Um herauszufinden, welcher Java-Prozess beendet werden soll bietet sich der Befehl <JAVA_HOME>/bin/jps an:
> /opt/java/jdk1.5/bin/jps
5996 Jps
26667 jar
5588 start.jar
5875 Main
29278 start.jar
5995 Main
  
Mehr Informationen erhält man mit den Optionen -v für die Argumente und -l für den Package Namen der Main-Klasse.
> /opt/java/jdk1.5/bin/jps -lv
6160 sun.tools.jps.Jps -Dapplication.home=/opt/regr/java/jdk1.5 -Xms8m
26667 XyzServer.jar
29278 /opt/jetty7.1.6/start.jar -Xmx1024M -Xms512M -Djetty.port=8080 -DSTOP.PORT=8079
  
Jetzt weiß ich das zu meinen Jetty-Server (mit Port 8080) die PID 29278 gehört!
> kill -9 29278
Und weg ist er! 

Dienstag, 21. Juni 2011

Grails Cloud Foundry Integration

Trotz kleiner Fallen geht es sehr einfach, eine Gails-App auf Cloud Foundry laufen zu lassen, denn es gibt ja für alles ein Grails Plugin. Hier zum Beispiel das Plugin Cloud Foundry Integration von Burt Beckwith.

Ich habe das ganze mal mit Eclipse (STS) + Grails-Plugin ausprobiert, geht aber genauso auf der Kommandozeile. Hier die notwendigen Schritte:
  1. Bei Cloud Foundry anmelden
  2. Neues Grails-Projekt cftwest
  3. Grails Command Prompt: install-plugin cloud-foundry
  4. Anmeldedaten von Cloud Foundry in der Datei /cftwest/grails-app/conf/Config.groovy eintragen:

    grails.plugin.cloudfoundry.username = 'xyz@xyz.de'
    grails.plugin.cloudfoundry.password = 'xyz'
  5. Ein kleiner Test:
    Grails Command Prompt: cf-info

    VMware's Cloud Application Platform
    For support visit support@cloudfoundry.com

    Target:   http://api.cloudfoundry.com (v0.999)

    User:     xyz@xyz.de
    Usage:    Memory   (512,0M of 2,0G total)
              Services (1 of 16 total)
              Apps     (1 of 20 total)
  6. Und deployen:
    • Grails Command Prompt: prod cf-push
    • Auf die Frage nicht mit y sondern mit der URL antworten:
      Application Deployed URL: 'cftwest.cloudfoundry.com'?
      cftwest.cloudfoundry.com
    • Service wird noch nicht benötigt:
      Would you like to bind the 'mysql-68c6b11' service? ([y], n)
      n
  7. Jetzt unter http://cftwest.cloudfoundry.com/ aufrufen!
  8. Update der Anwendung mit:
    Grails Command Prompt: prod cf-update

Weitere Infos zum Plugin findet man unter Cloud Foundry Plugin - Reference Documentation und weitere Links zum Thema in Graeme Rocher's Blog.

Als nächstes werde ich das mal zusammen mit CouchDB und Cloudant ausprobieren...