Skip to content

SQL Server JDBC Probleme

Von den Änderungen in Java SE 6.0 Update 29 zum Schutz vor SSL BEAST Angriffen hatte ich schon berichtet. Ein Opfer dieser Kompatibilitätsänderung sind die JDBC Treiber für den Microsoft SQL Server (jTDS und Microsoft JDBC Driver for SQL Server sind betroffen). Beim Aufbau der Verbindung (TCP) direkt mit dem jTDS Treiber kommt es zu folgendem Fehler:
java.sql.SQLException: I/O Error: Software caused connection abort: recv failed
  State  : 08S01
  Error  : 0
Und die folgende Exception wirft der Microsoft JDBC Driver for SQL Server:
com.microsoft.sqlserver.jdbc.SQLServerException: Connection reset
  State  : 08S01
  Error  : 0
Wenn die Treiber durch einen Connection Pool benutzt werden, oder innerhalb einer Datasource, so kann es sogar zum Hängen (wegen Endlosschleife) kommen. Eine Möglichkeit ist es, beim jTDS Treiber anzugeben, dass man kein SSL machen möchte (sollte aber eigentlich auch der default sein, laut jTDS FAQ). Dies kann man mit dem JDBC URL Property ";ssl=no" erreichen. Wenn der Server allerdings auf "Force Encryption" konfiguriert ist, so wird er dann die Logins ablehnen. Beim Microsoft Treiber würde das property "encrypt=false" lauten, dies half aber in meinen Versuchen (mit MS SQL Server 2008 R2 Express) nicht. Eine weitere Möglichkeit ist es den SSL/TLS CBC-Fix per Java System Property abzuschalten: -Djsse.enableCBCProtection=false Dies wirkt sich aber auf alle anderen SSL Verbindungen innerhalb dieser VM ebenfalls aus. Es gibt Berichte, dass dieses Problem mit JavaSE 6.0 Update 30 behoben sei, das kann ich aber weder nachvollziehen, noch lassen die ReleaseNotes darauf schließen. Ich habe mal einen Fehlerbericht bei jTDS dazu geöffnet.

Java Stream mit Puffer einlesen

Bin heute zufällig über folgende Methode gestolpert:
String readToString1(InputStream in)  throws IOException
{
    byte[]buf = new byte[256];
    StringBuilder sb = new StringBuilder();
    int n;
    do {
      n = in.read(buf, 0, 256);
      if (n > 0) {
        String s = new String(buf, 0, n, "UTF-8");
        sb.append(s);
      }
    } while(n > 0);
    return sb.toString();
}
Diese Funktion soll einen InputStream dessen Zeichen UTF-8 codiert sind in einen String lesen. Problem (mal abgesehen von der unnötigen Verwendung des if und den temporär angelegten String Objekten) bei dieser Funktion ist allerdings, dass der 256-byte Puffer in einen String umgewandelt wird, denn dabei werden ein oder mehrere Bytes durch den Zeichenkonverter gelesen. Falls der Zeichenkonverter dabei am ende des Puffers anlangt, so ist das Zeichen unvollständig. Das führt dann dazu dass ein Ersetzungszeichen (das Fragezeichen) am Ende des String steht. Man sollte solche starren bytepuffer also vermeiden, wenn man diese in Zeichen umwandeln will. Besser ist folgendes Vorgehen:
String readToString2(InputStream in) throws IOException
{
    char[] buf = new char[128];
    StringBuilder sb = new StringBuilder();
    Reader r = new InputStreamReader(in, "UTF-8");
    int n;
    while((n = r.read(buf, 0, 128)) != -1)
    {
       sb.append(buf, 0, n);
    }
    return sb.toString();
}
Um das unterschiedliche Verhalten zu testen, erzeuge ich einen InputStream der an ungeraden Byte Positionen Umlaute hat (also zuerst ein 1 byte Zeichen und dann lauter 2 byte Umlaute) und übergebe beiden Testmethoden diese Streams. Zusätzlich benutze ich noch Mockito, um zu ermitteln welche Methoden wie aufgerufen wurden:
import static org.mockito.Mockito.*;
public static void main(String[] args) throws IOException
{
    byte[] umlaut = "ö".getBytes("UTF-8");
    byte[] inbuf   =  new byte[513];
    inbuf[0] = 'X'; for(int i = 0;i<256;i++)
    {inbuf[i*2+1] = umlaut[0]; inbuf[i*2+2] = umlaut[1]; }
		
    InputStream in1 = spy(new ByteArrayInputStream(inbuf));
    System.out.println(" readToString1()=" + readToString1(in1));
	// make sure the inputstream was used with efficient block reads
	verify(in1, times(4)).read(any(byte[].class), eq(0), eq(256));
	verifyNoMoreInteractions(in1);

    InputStream in2 = spy(new ByteArrayInputStream(inbuf));
    System.out.println(" readToString2()=" + readToString2(in2));
	// make sure the inputstream was used with efficient block reads
	verify(in2, atMost(2)).read(any(byte[].class), eq(0), eq(8192));
	verify(in2, atMost(1)).available();
	verifyNoMoreInteractions(in2);
}
Und hier das Ergebnis (gekürtzt):
 readToString1()=Xööö...ööö??ööö...ööö??
 readToString2()=Xööö...öööööööö...ööööö
Am Ende jeden Puffers zerschneidet die erste Methode die 2 Bytes eines Umlauts, und deswegen erscheinen an diesen Stellen das Füllzeichen des Character Konverters. Bei UTF-8 streams ist es unwahrscheinlich dass ein multi-byte Zeichen ausgerechnet genau auf eine Blockgrenze fällt - umso unwahrscheinlicher ist es, dass ein Problem damit beim Testen auffällt. Übrigens ist es nicht notwendig hier einen BufferedInputStream oder BufferedReader zu verwenden. Der Reader wird ja bereits mit einem char array buffer (und nicht einzelnen Zeichen) gelesen. Zudem liest der InputSreamReader() aus dem darunterliegenden InputStream mit einem StreamDecoder der einen eigenen Lesepuffer (bei den Sun Klassen ist das ein 8kb Puffer) hat.

SSL/TLS BEAST Lücke

Im September 2011 haben die Sicherheitsforscher Duong und Rizzo nachgewiesen, dass eine - seit 2002 bekannte und in TLS 1.1 behobene - Schwachstelle in den SSL 3.0 und TLS 1.0 Verschlüsselungsprotokollen nicht nur theoretisch ist, sonder wirklich ausgenutzt werden können. Unter bestimmten Bedingungen erlaubt diese Schwachstelle einem Angreifer Teile aus einer SSL/TLS geschützten Übertragung zu ermitteln.

Gezeigt wurde dies am Beispiel eines abgefangenen paypal http Session Cookies, was erlaubte eine Browser Sitzung zu übernehmen. Das Problem ist unter dem Namen „BEAST“ (Browser Exploit Against SSL/TLS) bekannt, und wird unter der Common Vulnerability ID  CVE-2011-3389 geführt. Bei der Demonstration wurde ein Java Applet verwendet um die notwendigen Daten einzuschleusen, nach Duong/Rizzo es soll aber auch mit WebSockets oder JavaScript XHR Aufrufen möglich sein.

Dies zugrundeliegende kryptografische Schwäche ist ein generelles Problem vom SSL 3.0/TLS 1.0. Es wurde aber als 2002 nur als theoretischer Angriff gesehen, jetzt sind Angriffe mit Hilfe der erweiterten Funktionen des WebBrowsers bekannt geworden. Da es für einen erfolgreichen Angriff nicht nur notwendig ist, dass die verschlüsselte Verbindung abgehört werden kann, sondern auch, dass der Angreifer in den Klartext eigene Stück einfügen kann (Chosen Plaintext). Dies ist durch die Verwendung von JavaScript auf Webseiten relativ einfach möglich.

Wird bei SSL/TLS eine Blockchiffre im CBC (Cipher Block Chaining) Modus verwendet, so benutzt SSL 3.0 und TLS 1.0 einen vom Vorgängerblock abgeleiteten Initialisierungsvektor. Da sich dieser ermitteln lässt ist hier ein Problem gegeben, das durch geschicktes einfügen von Füllzeichen in den Klartext erlaubt Inhalte zeichenweise an den Blockgrenzen zu ermitteln.

Dies zu behaben bedarf es einer neuen Protokollversion: TLS  1.1. Aber in TLS 1.0 und SSL 3.0 kann es nicht so einfach verhindert werden. Somit hilft mittelfristig nur ein Update auf diese „neuen“ Protokolle, die zwar schon Jahrelang verfügbar sind, sich aber in der Praxis in den meisten WebServern und WebBrowsern noch nicht durchgesetzt haben (vor allem nicht per default aktiviert sind).

Mögliche Gegenmaßnahmen zu BEAST (und deren Probleme) sind:

  1. SSL/TLS Ciphers nicht im CBC Modus verwenden. Diese können in den gängigen Browsern und Servern abgeschalten werden. Die Gefahr dass dann Gegenstellen keine gemeinsame Verschlüsselung aushandeln können ist allerdings groß. Sollte also nur bei einer kleinen Anzahl von bekannten Kommunikationsgegenstellen benutzt werden.
  2. Statt die CBC Chiffren abzuschalten kann auf jedenfall die Stromchiffre RC4 (TLS_RSA_WITH_RC4_128_SHA) bevorzugt werden. Diese verwendet kein CBC und ist damit nicht betroffen. Dies macht Google z.B. schon seit einiger Zeit. RC4 ist nicht unumstritten, es gibt Angriffe gegen das Verfahren, die aber in SSL nicht auftreten.
  3. Oracle Java (JSSE) ab Version 1.6.0_29 und 7.0.0_1 implementiert einen CBC Schutz (der mit dem System Property jsse.enableCBCProtection aus kompatibilitätsgründen wieder abgeschalten werden kann) bei dem der erste Block in zwei mit der Länge 1 und (n-1) gesplittet wird. Erst mit Java 7 wird TLS 1.1 und 1.2 unterstützt.
  4. Der gleiche Fix wird gerade in Chrome Beta 15 getestet, es gab schon Kompatiblitätsprobleme.
  5. Für Opera 10.51 war der Fix geplant, ist aktuell noch nicht vorhanden (Opera benutzt keine WebSockets).
  6. Dieser Schutz wird auch in Mozilla Firefox (via NSS library) eingebaut, wartet dort aber noch auf eine Lösung der Kompatibilitätsprobleme und ist somit nicht Bestandteil von Firefox 7 (Mozilla sagt die WebSockets sind nicht verwundbar).
  7. Es ist zu erwarten dass Microsoft für den Internet Explorer nachzieht, bisher empfehlen sie nur die RC4 Chiffre zu bevorzugen (Windows XP scheint dies zu tun), Vorsicht bei dem Umgang mit http Seiten walten zu lassen und ggf. TLS 1.1 auf Client und Server zu aktivieren (immerhin unterstützen Microsoft Produkte dies schon, wird nur aus Kompatibilitätsgründen nicht aktiviert).
  8. Die Option TLS 1.1 (oder neuer) zu verwenden wird leider noch einige Zeit auf sich warten lassen. Besonders da SSL 3.0 und TLS 1.0 abgeschalten werden müssten, um zu verhindern das Angreifer diese erzwingen. Leider hängt openssl der TLS 1.1 Entwicklung nach, so dass auch Apache hier nur mit der RC4 Cipher gerettet werden kann (alternativ kann man mod_gnutls oder mod_nss verwenden, die sind aber beide weniger stark in der Nutzung).

IPv6 default address selection in Java

Da bei IPv6 Nodes (also Rechner und andere Teilnehmer an einem Netzwerk) schon konzeptionell mehrere Adressen haben ist die Auswahl welche Quell- und Zieladresse verwendet werden soll eine wichtige Funktion. Insbesondere dann wenn ein Rechner im Dual-Stack Betrieb sowohl IPv4 als auch IPv6 Adressen erreichen kann. Bei IPv6 kann jedes Netzwerkinterface mehrere IPv6 Adressen haben, die dann entweder in unterschiedlichen Bereichen (Scopes) genutzt werden können (localhost, link, site, global) oder die unterschiedliche Bevorzugt (valid, prefered) sind. Durch die Unterstützung von Renumbering (stateless autoconfiguration) haben Adressen unterschiedliche Lebenszeiten. Zudem gibt es Adressen die über eine Migrations-/Tunnel Technologie wie Toredo, ISATAP oder 6to4 bereitgestellt werden, und nicht immer benutzt werden sollen. Idealerweise würde eine Anwendung oder das Betriebssystem alle möglichen Quell/Ziel-Adresskombinationen ermitteln, und alle (aussichtsreichsten zuerst) durchprobieren. RFC 3484 beschreibt ein Verfahren für die Default Address Selection für IPv6. Der von Microsoft Research verfasste Entwurf gibt Regeln vor wie die Auswahl von Ziel- und Quelladressen zu geschehen hat, und definiert auch eine Möglichkeit dass der Administrator eines Systems eigene Gewichtungen definieren kann. Ideal wäre eine Laufzeit Funktion, der man einen Hostnamen übergibt, und die dann die Verbindung zur Gegenstelle herstellt und dabei alle Regeln des RFC 3484 (und dringend notwendiger zukünftiger Verbesserungen) beachtet. Durch die Trennung zwischen Kernel und Usermode, und aus Gründen der Kompatibilität mit existierendem Netzwerkcode verwenden die meisten* Systeme allerdings ein anderes Verfahren. Bestehende Funktionen wie z.B. getaddrinfo(3) wurden erweitert: die Auflösung von Hostnamen in Adressen liefert jetzt eine nach Präferenzen sortierte Liste der Zieladressen zurück. Dabei greift die Bibliotheksfunktion auf Adress- und Routinginformationen des Kernels zurück. Denn es müssen für jede zurückgelieferte Zieladresse auch die potentiellen Quelladressen bestimmt und bewertet werden. Unter Windows kann die Sortierung mit der prefixpolicy (netsh.exe interface ipv6 show prefixpolicies) angezeigt werden. Linux Systeme speichern die Konfiguration in /etc/gai.conf, aktuelle Einstellungen können mit dem iproute2 Paket angesehen werden (ip addrlabel). Das ganze ist im Kernel und der glibc implementiert. Auch bei Java wurde kein komplett neues Verfahren für den Verbindungsaufbau für IPv6 definiert**. Die Anwendung selbst ist dafür zuständig alle möglichen Zieladressen der Reihe nach durchzuprobieren. Wenn die Anwendung keine Quelladresse angibt (was sie vermeiden sollte) so wird dann der Kernel für jeden der Zieladressen eine Quelladresse auswählen. Wenn eine Adresse nicht erreichbar ist, so muss die nächste Adresse verwendet werden. Wenn alle Adressen nicht erreichbar sind, so sollte eine Fehlermeldung zurückgegeben werden die alle probierten Zieladressen benennt und den ersten (oder alle) Fehlermeldungen benennt. Beispielhaft kann dies so aussehen:
Socket connectToHost(String host, int port)
	throws UnknownHostException, SocketException
{
	IOException firstException = null;

	InetAddress[] addressArray = InetAddress.getAllByName(host);
	for(InetAddress addr : addressArray)
	{
		try {
			return new Socket(addr, port);
		} catch (IOException ex) {
			if (firstException == null)
				firstException = ex;
		}
	}

        // build informative error message
	StringBuilder msg = new StringBuilder("Unable to connect to host=");
	msg.append(host); msg.append(" port="); 
        msg.append(String.valueOf(port)); msg.append(" [");
	
	for(int i=0;i < addressArray.length;i++)
	{
		if (i != 0)
			msg.append(',');
		msg.append(addressArray[i]);
	}
	msg.append("]: "); msg.append(firstException.getMessage());
	SocketException se = new SocketException(msg.toString());
	se.initCause(firstException);
	throw se;
}
Dieser Code überlässt die Auswahl einer Quelle dem Kernel (es werden also nicht alle möglichen Kombinationen durchprobiert). Ebenso ist kein Handling für Timeouts enthalten, und ein Cache der Verbindungszustände erinnert oder gar ein paralleler Aufbau zu mehreren Zielen ist noch nicht enthalten. Trotzdem ist der Code schon recht komplex, sollte also nicht mehrfach implementiert werden müssen. Von InetAddress.getByName(String host) würde ich auf jeden Fall Abstand nehmen. Diese Methode gibt nur die bevorzugte Addresse zurück, und führt bei DualStack Anwendungen dazu, dass nicht IPv6 und IPv4 Adressen durchprobiert werden. * Microsoft ist typischerweise Entwicklerfreundlicher und muss weniger Rücksicht nehmen auf etablierte APIs, deswegen gibt es die Funktion WSAConnectByName() die alle Addressen selbst durchprobiert. ** Java kennt Socket(String name, int port), dieser Konstruktor verwendet aber keine Schleife um alle möglichen Adressen zu kontaktieren.

jarsigner mit Tokens

jarsigner mit ASEKey Token Dialog unter Windows 7Um Java Applets und JAR Files zu signieren nutzt man den jarsigner, der normalerweise ein Java-Keystore (.jks) file nutzt. Es ist allerdings ungünstig Codesigning Zertifikate einfach so in Files herumliegen zu haben (auch wenn man diese gesondert sichert). Es ist anzuraten ein Zertifikat besser in einer SmartCard oder einem Token zu speichern. Der Oracle jarsigner.exe verwendet die Java Security Provider der JCE API. Um damit ein Hardware Token anzusprechen gibt es im wesentlichen 2 Methoden. Entweder den PKCS#11 Provider (benötigt eine PKCS#11 library (.dll oder .so) des Hardwareherstellers) oder unter Windows den Microsoft CryptoAPI (MSCAPI) treiber. Letzteres hat den Vorteil dass die meisten Smartcards unter Windows 7 oder 2008 bereits Treiber beim einstecken installieren. Diese Minitreiber sind nur begrenzt nutzbar, aber für das signieren reicht es aus. Bei meinen Versuchen habe ich aber einige Einschränkungen gefunden:
  • Es wird Java 5,6 oder 7(>b144) benötigt.
  • Ich habe nur die Java Distribution von Sun/Oracle getestet
  • In der 64bit Variante wird der Provider aktuell nur von Java 7 ausgeliefert
  • Über MSCAPI wird immer in getrenntem Dialog nach PIN gefragt
Anbei Beispielbefehle zum auslesen des Keystores (in dem auch das Codesigning Zertifikat das auf dem USB Token gespeichert ist sichtbar ist) sowie dem Signaturvorgang:
c:\Program Files (x86)\Java\jdk1.6.0_24\bin>keytool -list -storetype Windows-MY

Keystore-Typ: Windows-MY
Keystore-Provider: SunMSCAPI

Ihr Keystore enthält 4 Einträge.
...
FIRMA AG, PrivateKeyEntry,
Zertifikatsfingerabdruck (MD5): 57:EF:97:04:EA:91:EE:FF:CF:BF:7F:75:AE:E1:A2:7D
...
c:\Program Files (x86)\Java\jdk1.6.0_24\bin>jarsigner.exe -storetype Windows-MY
%TEMP%\test.jar "FIRMA AG"
Als Speicher für die Zertifikate verwende ich ein Athena ASEKey Crypto Token in das ich mit den Athena IDProtect Tools auf einem anderen Computer das Codesigning Zertifikat von Verisign (Promo:WINQUAL99) importiert habe. Das Zertifikat ist übrigens ein Authenticode Class 3 Zertifikat das es aktuell für Teilnehmer am Microsoft WinQual Programm für ein Jahr günstig ($99) gibt. Entgegen den Angaben von Verisign lässt sich dieses auch problemlos für Java Signig verwenden. Einziges Problem bei der Sache, mit -storepass oder -keypass lässt sich die PIN für die SmartCard nicht angeben, diese fragt immer mit einem Popup nach entsprechender Freigabe. Ich habe in den Athena Tools keine Möglichkeit gefunden diese abzuschalten (allerdings geht es mit der PKCS#11 API, wie ich im nächsten Beitrag berichten werde).
c:\Program Files (x86)\Java\jdk1.6.0_24\bin>jarsigner -verify -certs -verbose %T
EMP%\test.jar

         134 Thu Jun 02 23:27:48 CEST 2011 META-INF/MANIFEST.MF
         258 Fri Jun 03 00:04:02 CEST 2011 META-INF/FIRMA_AG.SF
        4764 Fri Jun 03 00:04:02 CEST 2011 META-INF/FIRMA_AG.RSA
           0 Thu Jun 02 23:23:38 CEST 2011 META-INF/
sm     15104 Sun Mar 06 04:56:06 CET 2011 jarsigner.exe

      X.509, CN=FIRMA AG, OU=Software Development, 
                OU=Digital ID Class 3 - Microsoft Software Validation v2,
                  O=FIRMA AG, ST=Baden-Wuerttemberg, C=DE
      [certificate is valid from 21.12.10 01:00 to 22.12.11 00:59]
      X.509, CN=VeriSign Class 3 Code Signing 2010 CA
      [certificate is valid from 08.02.10 01:00 to 08.02.20 00:59]
      [KeyUsage extension does not support code signing]
      X.509, CN=VeriSign Class 3 Public Primary Certification Authority - G5
      [certificate is valid from 08.11.06 01:00 to 17.07.36 01:59]
      [KeyUsage extension does not support code signing]


  s = signature was verified
  m = entry is listed in manifest
  k = at least one certificate was found in keystore
  i = at least one certificate was found in identity scope

jar verified.
Update: Ich habe das ganze gerade mit der b144 von Java 7 getestet, und damit lassen sich sowohl mit 32bit als auch mit der 64bit Version der MSCAPI Provider für die Signatur von JAR Files via Smart Token nutzen.

Amazon Web Services als PaaS Anbieter

Mit dem neuen (Beta) Dienst Beanstalk steigt Amazon in das Platform-as-a-Service Geschäft ein. Vergleichbar mit dem Google AppEngine for Java Dienst können Java Entwickler einfach Web Anwendungen in WAR Files verpacken und in die Cloud hochladen. Amazon kümmert sich um die Skalierung aller Komponenten. Man könnte fast annehmen ursprünglich war das WAR Deployment Modell genau so gedacht. Amazon rechnet die unterliegenden Infrastrukturdienste ab. Das ergibt sich aus den Freiheitsgraden die man hat, macht die Rechnung aber komplex - und vor allem für Low-Traffic Anwendungen eventuell nicht gerade preiswert. Die Amazon Beispielrechnung kommt z.B. auf $30/Month. Immerhin können die sehr günstigen EC2 Micro Instanzen ($0.02/h) genutzt werden.

Lambdas in Java

Gerade bastle ich an einem Wrapper für DataSources der im Umgang mit Datenbanken helfen soll (zusätzliche Statistiken, blocking bei DB Fehlern und pausieren von DB Anfragen). Dabei wrappe ich zwei Methoden getConnection() und getConnection(String, String). Nur der Aufruf der realen Methode unterscheidet sich, der restliche Code ist in beiden Methoden gleich:

CODE:
public Connection getConnection(String user, String pass) ... {     verifyState();       while(true) {         try {             Connection c = ds.getConnection(user, pass);             verifyConnection(c); // throws SQLEx             return c;         } catch (SQLException e) {             handleException(e); // throws SQLEx         }     } } public Connection getConnection() ... {     verifyState();       while(true) {         try {             Connection c = ds.getConnection();             verifyConnection(c); // throws SQLEx             return c;         } catch (SQLException e) {             handleException(e); // throws SQLEx         }     } }

Im Sinne von DRY (don't repeat yourself) ist dies aber unschön, weil der (in Realität noch komplexere) Retry code doppelt vorkommt, und ich immer beide Methoden anpassen muss. Dieses Problem kommt oft bei Frameworks vor, und nennt sich "the whole in the middle" Muster. Eine Lösung wäre:

CODE:
public Connection getConnection(String user, String pass) {     return smartConnect(true, user, pass); } public Connection getConnection() {     return smartConnect(false, null, null); } Connection smartConnect(boolean hasArgs, String user, String pass) {     verifyState();       while(true) {         try {             Connection c;             // --- the whole in the middle             if (hasArgs)                 c = ds.getConnection(user, pass);             else                 c = ds.getConnection();             // ---             verifyConnection(c); // throws SQLEx             return c;         } catch (SQLException e) {             handleException(e);         }     } }

Das funktioniert aber nur im einfachsten Fall und es verkompliziert leider den Framework code, was auch wieder der Verständlichkeit schadet.

In C# 3.0 kann man das (wie ich grade gesehen habe) mit einer Lambda Action lösen (man kann also im Prinzip anonyme Funktionsblöcke übergeben). In Java müßte man dazu ein Objekt übergeben:

CODE:
public Connection getConnection(String user, String pass) {     return smartConnect(         new ConnectionProvider() {              Connection provide() { return ds.getConnection(user, pass); }}); } public Connection getConnection() {     return smartConnect(         new ConnectionProvider() {             Connection provide() { return ds.getConnection(); }}); } Connection smartConnect(ConectionProvider cp) {     verifyState();       while(true) {         try {             Connection c = cp.provide();             verifyConnection(c); // throws SQLEx             return c;         } catch (SQLException e) {             handleException(e); // throws SQLEx         }     } } abstract class ConnectionProvider {   Connection provide(); }

Der smartConnect() code wird damit lesbarer, und man kann auch komplexere Aktivitäten injizieren (eventuell mit Argumenten zur provide() methode), aber man muss jetzt noch eine extra Klasse definieren und alles in allem wird es auch mehr Code. Bei jedem Aufruf wird ein zusätzliches Objekt erzeugt. Ich denke ich werde also eher bei dem "if" Ansatz bleiben.

Ich vermute mal der Syntaktische Zucker "Lambda Expression" erzeugt bei C# auch ein extra Call Objekt, aber es fällt wesentlich weniger zusätzlichen Code beim Aufruf an.

Warum geht Wissen verloren

In einem geschlossenen System geht keine Energie verloren. Schade dass Unternehmen kein geschlossenes System sind, denn es ist erschreckend wie viel Wissen und Erfahrung in einem Unternehmen verloren gehen kann. Einer meiner Aufgaben ist die Beratung von Key Account Kunden bei komplexeren Projekten, um sicherzustellen dass Sie von unserer Erfahrung profitieren und natürlich um unsere Roadmap besser auf die Kundenbedürfnisse abzustimmen. Die Installation von Middleware ist meistens verknüpft mit Konsolidierungs- und Migrationsprojekten (in denen bestehende Anwendungen unterschiedlicher Hersteller auf eine neue Plattform zusammengefasst werden, ganz beliebt sind SAP Einführungen). Deswegen bekomme ich oft (am Rande) mit, wie schmerzhaft es sein kann in einem Unternehmen eine genaue Analyse der (Legacy) IST-Prozesse zu erstellen, oder für ein geplantes neues System die Mindestanforderungen zu definieren. Ich treffe immer wieder auf die selben Muster:
  • Es werden andere (neue) Mitarbeiter mit der Umsetzung der neuen Projekte betraut. Entweder weil man den alten Hasen die neue Technologie nicht zutraut, weil die bestehende Mannschaft nicht aus dem Tagesgeschäft entlassen werden solll, oder weil die Verantwortlichkeiten schleichend neu verteilt werden sollen. Egal welchen Grund es gibt die neuen Mitarbeiter haben keine Erfahrung mit dem Gesamtproblem, und die erfahrenen Mitarbeiter haben entweder keine Motivation oder Gelegenheit mehr zu helfen.
  • Die Vordenker die das alte System entworfen und vor allem weiterentwickelt haben sind nicht mehr im Unternehmen. Kleine Änderungen und Pflege wird vom Stammpersonal vorgenommen, aber keiner kann die Prozesse komplett überblicken.
  • Selbst die detaillierteste Systemdokumentation kann ein System (vor allem die Überlegungen bei der Umsetzung) voll beschreiben. Und selbst wenn sie es könnte, so ist sie nicht mehr vollständig erfassbar.
  • Der Teufel steckt im Detail: die Projekte sind meistens so angelegt dass keine Zeit für Fehler oder Verbesserungen eingeplant werden. Das Second System muss gleich perfekt sein, auch wenn die bestehende Anwendung über Jahrzehnte gewachsen ist. Das sind unrealistische Anforderungen, im besten Fall werden im Laufe des Projektes der Umfang der Pilotprojekte immer kleiner - und damit irgendwann machbar.
  • Mitarbeiter die für die Umsetzung Hauptverantwortlich sind werden so mit Fakten und Entscheidungen überschüttet, dass Verdrängungs- und Überschätzungsmechanismen einsetzen. Das Gesamtsystem aus einer anderen Sichtweise (z.B. aus Sicht des Controllings oder des Operatings, oder des Supports, oder oder) zu betrachten wird als unnötig oder zu aufwändig angesehen. Daraus ergeben sich oft Entscheidungen die sich im späteren Betrieb als sehr ineffektiv herausstellen. Oftmals werden einfach zu wenige zentrale Rollen besetzt. Keyuser und Stakeholder aus unterschiedlichen Bereichen gibt es nicht, oder werden nicht gefördert.
  • Ein weiteres Problem sind knappe Deadlines und neue Technologien. Beide führen dazu dass Unternehmen externe Berater in allen Ebenen beauftragen. Das führt zu dem Effekt, dass die Überlegungen zur Umsetzung nicht immer im Besten Interesse der Firma sind, und die gemachten Erfahrungen auch mit dem Ende des Migrationsprojektes das Unternehmen wieder verlassen. Mehrere hundert Business und Technologie Consultants sind keine Seltenheit.
  • Ein Problem mit dem man fertig werden muss, ist auch die Tatsache dass die funktionierenden Prozesse nicht notwendigerweise effizient sind. Bei einer genauen Analyse werden die Schwachstellen entdeckt. Oftmals wird die Entscheidung zum Re-Engeneering aber zu leichtfertig getroffen. Der Projekt-Scope weitet sich somit aus.
  • Pilotprojekte werden oft auch falsch ausgesucht: zwar ist es Sinnvoll nicht mit dem Core Business als erstes auf eine neue Plattform umzuziehen, jedoch ist das bisher manuell betriebene Business einer ausländischen Zweigstelle nicht unbedingt repräsentativ. Von den Problemen wie Zeitverschiebung, kulturelle und Sprachlichen Barrieren oder fehlende Erfahrung der Stammbelegschaft ganz zu schweigen.
Vermutlich lassen sich all diese Probleme nur bedingt vermeiden. Aber wenn man immer wieder obige Punkte vor Augen hält kann man frühzeitig gegensteuern. Zum Glück verliefen alle Projekte die ich so kenne mehr oder weniger erfolgreich. Hilfreiche waren dabei folgende Faktoren:
  • Heroes die Verantwortung und Kommunikation übernehmen. Immer wieder alle Parteien an einen Tisch holen.
  • Manager die Dank Sachverstand Abschätzungen hinterfragen können.
  • Starke Einbindung von internen Ressourcen in allen Phasen.
  • Iterative Umsetzung in kleinen Schritten.
  • Die Auswahl eines Leistungsstarken und flexiblen Softwarepartners mit herausragenden Mitarbeitern (*grins*)

Eclipse MAT und IBM Heapdumps (Cont.)

In meinem letzten Artikel zum IBM Plugin für den Eclipse Memory (Dump) Analyser (MAT) hatte ich noch beschrieben, dass der Import nur mit IBM Systemdumps zurechtkommt. IBM hat jetzt nachgelegt und kann nun auch das Portable Heap Dump (PHD) Format verarbeiten. Dabei gibt es aber Einschränkungen zu beachten. Dieses Format enthält zum Beispiel keine detaillierten Feld Inhalte und ohne Javacore Files fehlen Classloader Details. ibm.com/developerworks/java/jdk/tools/mat.html Das System Format sollte man bevorzugen, die Out of Memory Dumps werden aber normalerweise im PHD Format abgelegt, und diese können jetzt auch gelesen werden.

Java Concurrency Termine

Gleich zwei Termine zum Thema Java Concurrency stehen in der Region an: Java User Group Karlsruhe - "The Secrets of Concurrency" Mittwoch, 2009-02-11 19:15 Uhr Dr Heinz Kabutz (PhD CompSci) Author des The Java(tm) Specialists' Newsletter Ort: Uni Karlsruhe, ATIS, Am Fasanengarten 5, Geb. 50.34, 76131 Karlsruhe, Raum UG102 und ObjektForum Stuttgart - Herausforderung Multikern-Systeme Montag, 2009-02-16, 18.30 Uhr Prof. Dr. Walter F. Tichy Universität Karlsruhe / FZI Ort: Alte Scheuer, Degerloch