Samstag, 26. Mai 2012

Oracle JDK 7 für Mac OS X

Vor genau einem Monat hat Oracle nun endlich  das offizielle JDK 7 für Mac OS X freigegeben, zeitgleich mit Java SE 7u4 für alle anderen Systeme. (Wir erinnern uns: Ab Version 7 wird Java für Mac OS X von Oracle und nicht mehr von Apple bereitgestellt.) Die genauen Versionsinformationen habe ich wie immer in meiner Liste der Mac OS X- und Java-Versionen zusammengetragen.
Die DMG-Datei enthält ein Installationsprogramm, das das JDK 7 ohne großen Aufwand ins Verzeichnis /Library/Java/JavaVirtualMachines/1.7.0.jdk schreibt, d.h. das JDK steht für alle Benutzer auf dem Rechner zur Verfügung.
Nach der Installation muss man das JDK 7 in den "Java-Einstellungen" in den Dienstprogrammen aktivieren, ansonsten bleibt das nach wie vor von Apple gepflegte Java 6 die Standard-Version. Entwickler – gerade im Bereich Server-/Enterprise-Programmierung – können das JDK 7 bereits gut verwenden. Für Endanwender mag es im Moment noch besser sein, Java 6 als die aktive (d.h. in der Liste oberste) Java-Version einzustellen, da das JDK 7 noch das ein oder andere Problem mit Applets bzw. der GUI zu haben scheint. Das ist wohl auch der Grund, warum Oracle das Mac-JRE für Endanwender erst später im Jahr veröffentlichen wird.

Abschließend noch ein paar Hinweise zu Problemen und deren mögliche Lösung.

Falls im Terminal das JDK 7 nicht gefunden wird (d.h. java -version und /usr/libexec/java_home liefern noch Java 6), obwohl es in den Java-Einstellungen als System-Default konfiguriert wurde, sollte man prüfen, ob die Umgebungsvariable JAVA_HOME (falsch) gesetzt ist. Das kann beispielsweise grafisch im RCEnvironment geschehen sein, falls man diese Erweiterung der Systemeinstellungen installiert hat. Häufiger ist dies wohl aber in der Shell-Konfigurationsdatei (z.B. .bash_profile) erfolgt. Entweder setzt man den Eintrag für JAVA_HOME auf

export JAVA_HOME=`/usr/libexec/java_home`

(Wichtig: Mit den "Backticks" um den Programmaufruf), oder aber man entfernt den Eintrag für JAVA_HOME komplett. Nach einem Rechner-Neustart (aus- und einloggen hat bei mir nicht geholfen) sollte dann alles funktionieren.

In Eclipse 3.7.2 sollte das JDK 7 bei einem neuen Workspace automatisch erkannt werden, wenn Java 7 die System-Voreinstellung ist. Ansonsten muss man das JDK manuell in den Eclipse-Preferences auf der Seite "Installed JREs" hinzufügen. Mit dem "Add"-Button fügt man eine "Standard VM" hinzu und trägt als "JRE Home" den Pfad

/Library/Java/JavaVirtualMachines/1.7.0.jdk/Contents/Home

ein – fertig.

In NetBeans 7.1.2 sollte ein installiertes JDK 7 automatisch erkannt werden. Ansonsten muss man das JDK auch hier manuell ergänzen, und zwar im Menü "Tools / Java-Platforms". Mit dem Button "Add Platform" kann man das gewünschte Basisverzeichnis eingeben (/Library/Java/JavaVirtualMachines oder /System/Library/Java/JavaVirtualMachines) und anschließend eine der dort installierten Java-Laufzeitumgebungen auswählen.

Freitag, 27. April 2012

Flashback und Apples Java-Updates

Apples (späte) Antwort auf den "Flashback" Virus/Trojaner waren diverse Java-Updates in schneller Folge (bzw. ein Hilfsprogramm zum Entfernen des Virus für Mac OS X-Systeme ohne installiertes Java, was seit Mac OS X 10.7 "Lion" möglich ist). Sowohl die Java-Updates als auch das Removal-Tool entfernen Flashback, wenn es auf einem System gefunden wird.

Weniger offensichtlich ist, dass durch das letzte der Java-Updates die Ausführung von Java-Applets und Java Web Start generell deaktiviert wird... Der Anwender muss Applets und Web-Start-Anwendungen in den Java-Einstellungen (im Ordner /Programme/Dienstprogramme) explizit wieder aktivieren:
Dies ist ebenfalls notwendig, wenn der Anwender mehr als 35 Tage weder mit Applets noch mit Java Web Start gearbeitet hat – dann wird beides wiederum automatisch deaktiviert.

Damit diese Änderungen nun nicht gleich das Ende vom Desktop-Java auf Mac OS X bedeuten, zeigt Apples Java-Plugin immerhin einen Hinweis an, warum eine Webseite nicht ganz so aussieht bzw. funktioniert wie erwartet:

Der Hinweistext ist gleichzeitig ein Link, mit dem sich das Java-Plugin wieder aktivieren lässt, ohne dass man die Java-Einstellungen aufrufen muss:
Das funktioniert soweit auch ganz prima – nur leider wird vermutlich die Hälfte der Anwender den kleingedruckten Text übersehen, dass der Browser anschließend neu gestartet werden muss... Abgesehen von der Frage, warum überhaupt ein Neustart notwendig ist, wäre es sicherlich gut, wenn sich der Hinweis-Link nach dem Aktivieren in einen statischen Text "Inaktives Plug-In (bitte Browser neu starten)" o.ä. verwandeln würde.

Donnerstag, 15. März 2012

Typinferenz in Java

Durch das verstärkte Interesse an funktionalen Sprachen wird auch immer häufiger von Typinferenz gesprochen, also vom automatischen Ermitteln eines passenden Datentyps durch den Compiler.

Beispielsweise legt man in Scala eine Liste ganzer Zahlen mit folgendem Ausdruck an:

val zahlen = List[Int](1,2,3)

Oder noch kürzer:

val zahlen = List(1,2,3)

Der Compiler ermittelt also einen möglichst spezifischen Typen für die rechte Seite des Ausdrucks (hier eine Liste von Ints) und verwendet ihn automatisch als Datentyp für die Wertdeklaration auf der linken Seite.

Seit Version 7 beherrscht nun auch Java eine offensichtliche Form der Typinferenz:

List<Integer> liste = new ArrayList<>();

Man kann auf der rechten Seite die Typangabe bei der generischen ArrayList weglassen (die Angabe der leeren spitzen Klammern, des "Diamanten", ist aber notwendig), und der passende Typ (Integer) wird automatisch von der linken Seite übernommen. Die Inferenz von links nach rechts ist auch der wichtigste Unterschied zu der üblichen Typinferenz funktionaler Sprachen – in der Praxis ist deren Typinferenz von rechts nach links praktischer, da sie mehr Schreibarbeit spart.

Was nicht so offensichtlich ist: Bereits seit Java 5 gibt es eine ähnliche Form der Typinferenz. Definiert man eine Methode mit lokalem generischen Parameter:

public static <T> T typecast(Object obj) {
@SuppressWarnings("unchecked")
T gecastet = (T) obj;
return gecastet;
}

kann man sie wie folgt aufrufen:

String s = typecast("Hallo");

Die Methode "typecast" hat in diesem Fall tatsächlich den Rückgabetyp "String", abhängig vom Typ der deklarierten Variablen auf der linken Seite. Ob diese Methode in der Praxis sinnvoll ist, sei mal dahingestellt – bei Übergabe eines Nicht-String-Objekts geht der Typecast in der Methode zur Laufzeit mit einer ClassCastException schief. Aber sie demonstriert sehr gut die seit langem in Java vorhandene (wenn auch sehr eingeschränkte) Typinferenz.

Donnerstag, 16. Februar 2012

Konstanten für JPA-NamedQueries

NamedQueries sind ein wichtiges Mittel zur JPA-Performance-Optimierung. Im letzten Workshop kam die Frage auf, wie man String-Literale für den Namen einer NamedQuery vermeidet:
@Entity
@NamedQueries({
@NamedQuery(
name = "findBenutzerByName",
query = "select b from Benutzer b where b.name=:name")
})
public class Benutzer {
...
}
Denn in einem Fall wie oben müsste derselbe String an den Stellen geschrieben werden, an denen die Abfrage aufgerufen werden soll:

EntityManager manager = ...;

TypedQuery<Benutzer> query =
manager.createNamedQuery(
"findBenutzerByName",
Benutzer.class);

query.setParameter("name", ...);

List<Benutzer> ergebnisse = query.getResultList();

Natürlich möchten wir dafür Konstanten verwenden – was auch bei Annotationen erlaubt ist. Die Frage ist nur, wo legen wir die Konstanten ab? Mein Vorschlag ist, dies direkt in der Entity zu tun:

@Entity
@NamedQueries({
@NamedQuery(
name = Benutzer.QUERY_FINDBYNAME,
query = "select b from Benutzer b where b.name=:"
+ Benutzer.QUERYPARAM_NAME)
})
public class Benutzer {

public static final String QUERY_FINDBYNAME = "findBenutzerByName";

public static final String QUERYPARAM_NAME = "name";

@Id @GeneratedValue(strategy=GenerationType.AUTO)
private Long id;

@Column
private String name;

...
}
Dadurch wird an der Aufrufstelle klar, dass man passende Query-Strings und -Parameter verwendet:

TypedQuery<Benutzer> query =
manager.createNamedQuery(
Benutzer.QUERY_FINDBYNAME,
Benutzer.class);

query.setParameter(Benutzer.QUERYPARAM_NAME, ...);

Donnerstag, 22. Dezember 2011

JAXB und HashMaps

Die Technik ist zwar schon älter, aber vielleicht ist folgender Tipp trotzdem nützlich – zumal die entsprechende JAXB-Eigenschaft nicht sonderlich gut dokumentiert ist.

Wenn man Schlüssel-Wert-Paare aus einer XML-Datei in eine HashMap einlesen soll, ist das recht einfach, sofern man Einfluss auf die Struktur des XML-Dokuments hat und dieses in etwa so aussehen kann:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<meineEntitaet>
<paarliste>
<entry>
<key>MeinSchlüssel</key>
<value>MeinWert</value>
</entry>
<entry>
<key>...</key>
<value>...</value>
</entry>
...
</paarliste>
</meineEntitaet>

Die zugehörige Klasse muss dann wie folgt aufgebaut und mit JAXB-Annotationen versehen sein:
import java.util.*;
import javax.xml.bind.annotation.*;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class MeineEntitaet {

@XmlElementWrapper(name="paarliste")
private Map<String,String> map
= new HashMap<String,String>();

public Map<String,String> getMap() {
return map;
}

public void setMap(Map<String,String> map) {
this.map = map;
}
}

Die XML-Datei lässt sich dann mit folgender Anweisung in ein Objekt einlesen:
MeineEntitaet obj =
JAXB.unmarshal(
new File("beispiel.xml"),
MeineEntitaet.class);
System.out.println( obj.getMap() );

Genauso einfach lässt sich natürlich auch eine HashMap als XML schreiben:
MeineEntitaet obj = new MeineEntitaet();
obj.getMap().put("MeinSchlüssel", "MeinWert");
// JAXB.marshal(obj, System.out);
JAXB.marshal(obj, new File("beispiel.xml"));

Während man den XML-Element-Namen der Schlüssel-Wert-Paare-Liste mit der Annotation @XmlElementWrapper konfigurieren kann, sind die Elemente entry, key und value fest benannt. Um auch diese Namen zu verändern, muss man einen eigenen XmlAdapter implementieren.

Montag, 28. November 2011

Message Driven Beans anhalten und starten

Message Driven Beans (MDBs) werden meistens schon aktiv (d.h. vom Container aufgerufen, wenn über JMS Nachrichten für die Bean vorhanden sind), während das Deployment der Anwendung noch läuft. Das ist normalerweise unkritisch, aber es gibt Situationen, in denen man sicherstellen möchte/muss, dass die MDBs erst nach vollständigem Deployment (oder irgend einem anderen definierten Zeitpunkt) aktiv werden.

Nahezu jedes Messaging-System (JMS-Implementierung) bietet die Möglichkeit, über produktspezifische API oder externe Kommandos eine Queue anzuhalten und später wieder zu starten. Das betrifft aber die komplette Queue, d.h. es ist dann auch nicht mehr möglich, Nachrichten in die Queue zu schreiben.

Bei einigen Messaging-Systeme kann man daher via JMX die MBean einer MDB ermitteln und damit tatsächlich nur die MDB anhalten – also die Zustellung aus der Queue unterbinden. Das Schreiben in die Queue wäre dann immer noch möglich. (Das Problem, wie damit die MDB gleich zu Beginn des Deployments gestoppt wird, lassen wir mal außen vor.)

Leider sind beide Lösungen produktspezifisch. Eine portable Lösung ist bis inkl. Java EE 6 für Message Driven Beans nicht vorgesehen. Wie könnte eine selbst gebaute, portable Lösung aussehen?

Java SE 5 hat im Paket java.util.concurrent viele neue Klassen rund um Thread-Synchronisation und Sperren in die Java-Welt gebracht. Für unsere Zwecke ist ein CountDownLatch geeignet:

import java.util.concurrent.CountDownLatch;

@Singleton
public class AppStatus {

private static final CountDownLatch startSignal
= new CountDownLatch(1);

public void waitForAppInitialized() {
try {
startSignal.await();
} catch (InterruptedException e) {
throw new RuntimeException(
"Fehler beim Warten auf Sperren-Freigabe", e);
}
}

public void setAppIsInitialized() {
startSignal.countDown();
}

public boolean isAppInitialized() {
return (startSignal.getCount() == 0L);
}
}

Der CountDownLatch ist zu Beginn gesperrt. Zur Freigabe muss die Anwendung zu einem geeigneten Zeitpunkt die Methode setAppIsInitialized() aufrufen, beispielsweise in einem ServletContextListener oder in einem EJB-Timer.

Die zu blockierende MDB wartet dann bei jedem Aufruf zunächst darauf, ob die Sperre schon freigegeben wurde:

@MessageDriven
public class StartbareMDB implements MessageListener {

@EJB
private AppStatus status;

public void onMessage(Message msg) {

status.waitForAppInitialized();

// ... der eigentliche MDB-Code ...
}
}
Eine einfache, funktionierende Lösung mit Java-Bordmitteln.

Nachteil dieser Lösung ist, dass hier Threads des Containers blockiert werden. Man muss aufpassen, dass die Maximalzahl der blockierten MDB-Threads den Threadpool nicht überfordert (was ein Grund ist, warum man in Java EE-Containern nicht mit Threads herumspielen soll).

Außerdem ist der Performanceverlust nicht riesig, aber messbar. Für Höchstlastsysteme ist das also evtl. nicht geeignet. Zumal dort oft Clustering zum Einsatz kommt – und obige Lösung ist nur clusterfähig, wenn man sicherstellen kann, dass die Freigabe-Methode auf jedem Cluster-Knoten aufgerufen wird.

Als weitere (bewusste) Einschränkung ist zu beachten, dass der CountDownLatch nur einmalig nutzbar ist, er dient also wirklich nur als Start-Signal. Wenn man Message Driven Beans beliebig anhalten und starten möchte, muss man andere Sperren verwenden.

Fazit: Die hier vorgestellte Lösung funktioniert in vielen praktischen Situationen gut. Aber gibt es andere Ideen / Lösungen, die ähnlich einfach zu realisieren sind, die gezeigten Einschränkungen aber nicht aufweisen? Oder müssen wir auf Java EE 7 (oder eher 8) warten?

Donnerstag, 24. November 2011

Java für Mac OS X 10.7 "Lion"

Da Apple vor kurzem Java-Updates für Mac OS X veröffentlicht hat, gibt es an dieser Stelle eine kleine Erinnerung an die wichtige Änderung, die mit Mac OS X 10.7 "Lion" eingeführt wurde: Im Gegensatz zu allen anderen Mac OS X-Versionen davor (*) ist keine Java-Laufzeitumgebung mehr vorinstalliert.
(* Von der 10.0-Public Beta, die wir uns 2000 von der Apple Expo Paris mitgenommen haben, mal abgesehen.)

Beim ersten Aufruf einer Java-Anwendung erhält man einen Hinweis, dass die Java-Runtime benötigt wird. Bestätigt man dies, wird Java SE 6 heruntergeladen und installiert. Danach wird die ursprünglich aufgerufene Java-Anwendung gestartet.

Das aktuelle Java-Update von Apple enthält Java 1.6.0_29 und ist damit zeitlich erfreulich nah an Oracles Java 6-Update für andere Systeme. Java 7, für andere Systeme mittlerweile als erstes Update freigegeben, ist leider immer noch nicht final für Mac OS X verfügbar (immerhin gibt es eine Developer Preview von Oracle). Java 8, was sich derzeit noch in der Entwicklung befindet, ist aber schon als Build des OpenJDK 8 für Mac OS X erhältlich.