LAHA und die Latenzfalle

Kleines Update zu LAHA, dem Multiroom Sound und PulseAudio.

Stand der Dinge

Wie man es drehen und wenden will, die fertigen Tools zum Abspielen von PCM Sound haben alle irgendeine Macke.

PAPLAY benutzt PulseAudio. Das ist praktisch ein Todesurteil für eine stabile Latenz.
APLAY   benutzt Alsa, das defaultmäßig … PulseAudio benutzt. Siehe erstens 🙂
*JACK*  nun Jack möchte gern alleine tätig sein, ohne Konkurrenz. Fällt auch aus.

Da APLAY und PAPLAY per Default einen PA Stream zum Abspielen benutzen, haben beide in der Form das gleiche Problem: Die Audiolatenz des Playstreams steigt mit der Zeit an. d.b. alle anderen Geräte müßten mitwandern. Blöd nur, das Androids gar keine Latenzwanderung haben und selbst wenn Sie es hätten, wäre das eine blödsinnige Lösung. Jetzt fragt Ihr Euch natürlich: was labbert der da? Da muß man jetzt weiiiiiit ausholen.

Also, wenn man mit PAPLAY Sound ausgibt, geht PAPLAY zum PAServer und sagt dem, das DER eine Latenz wählen soll. Das macht der dann auch, nachdem die ersten Daten geflossen sind und die pegelt sich mit 16Bit Stereo und 48000″hz“ bei rund 1,9s ein. Richtig gelesen 1,9 Sekunden. Nach 40 Minuten sind wir bei knappen 5 Sekunden, wenns dumm läuft. Wenns gut läuft bei 2,4s . Das entscheidet PA selbst, ich nehme an, nach internen Fehlberechnungen aller gestarteten, gestoppten, bewegten etc. Streams die auf dem System drauf sind. Ich hab es noch nicht im Source gefunden. Es ist eigentlich das Ziel eines Audioservers die Latenz niedrig zu halten, aus irgendeinem Grund, ist dem PA-Latenzalgorithmus das egal.

Wenn man jetzt denkt, daß der Befehl ja eine OPTION für die gewünschte Latenz hat und sich schon freut, daß die dann ja als Ziel eingehalten wird .. ähm ja, also wie soll ich das Schreiben ohne verklagt zu werden??? Lieber nicht, hier ein Beispiel: 500ms angegeben, Latenz beginnt bei 320ms und wandert pö-â-pö so Richtung 500ms, durchbricht den Median, und verschwindet alsbald jenseits von Gut und Böse im Sekundenbereich.

Wenn man auf die glorreiche Idee kommt, da das anzugeben, was der Algo vorher selbst ausgerechnet hat, dann bekommt man nicht 1,9s , nö, mehr so 1s+- und dann kommt das gleiche Spiel wie vorher bei 500ms.
Ja, man könnte jetzt die PAPLAY-Routine kapern, den anderen Geräten die Latenzen mitteilen und denen somit zu einem Sync verhelfen. ABER.. die Latenz wird ja immer größer, was bedeutet, daß bei jedem neuen Sync mehr Zeit vergeht, bis man was hört. Also auch mal 5 Sekunden schwarzes nichts. Das ist hochgradig Inakzeptabel.

Bugreports sind raus, werden nicht helfen, weil (C) endet 2006 . Sieht nicht so aus, als wenn da wer dran arbeitet.

Kommen wir zu ALSA

APLAY kann ALSA-Devices direkt ansprechen. Warum nutzen wir dann nicht APLAY, statt PAPLAY ? Gesagt, getan. Versuchts mal, viel Glück dabei. Das geht mit etwas Glück genau einmal und auch nur auf einem Device, aus das PA grade nichts ausgibt. Ist auch klar, weil PA ja ALSA als Unterbau hat. Wir erinnern uns, daß PAPLAY 1,9s Latenz hatte. APLAY, wenn man ein Device findet, das geht, hat 0ms und das Startdelay ist nicht ganz so funktional, wie sich APLAY das wünscht aka. auch buggy. ABER, 0ms sind cool, weil Android auch 0ms haben kann, ohne dabei drauf zu gehen. Der Lesezugriff für Netzwerkdaten für „16 Bit Stereo 48k“ auf einem Android liegt bei 1ms. d.b. nimmt man ALSA als Player und bekommt das Device frei, hat man eine echt geile Latenz von 1-2ms und das hört man nicht!

Der Resync

Jetzt gibt es allerlei Hürden, die man umschiffen muß. Androids fliegt das eigene Multitasking um die Ohren, da fängt der Ton dann an zu stottern. Vorwarnung : keine. Gegenmaßnahmen: derzeit unbekannt.Das funktioniert auf dem Desktop etwas besser.

Bei 0ms Latenz ist der Resync instant, da könnte man versucht sein, einfach pauschal alle paar Sekunden mal helfend durch einen Socket.close();Socket.connect() einzugreifen. Könnte klappen, muß nicht.Ist aber eine Option, wenn der Wiedergabetask das nicht merkt. Geheimtip: Vorsicht vor doppelten Daten im AudioBuffer. Vergleicht mal, ob Ihr nach dem connect()+read() einen Teil davon schon habt und schmeißt den raus.

LAHA

Unser ControllCenter steuert jetzt bereits beliebig viele Devices, kann Audiostreams von laufenden Apps kapern, z.b. MPV, FireFox etc. , hat diverse Backends zum Abspielen auf dem Desktop, könnte verschiedene Musikplayer als Quelle nutzen und damit auch Last.FM, Spotify etc. realisieren. Er verschiebt Metadaten, findet neue Geräte im Netz, erlaubt Endgeräten mit Screen ihn fernzusteuern und hat bereits erfolgreich ein Handy als Drahtloskopfhörer für Videos laufen gehabt. Dank MPVs negativer Latenz und der frei einstellbaren Endgeräte Latenz, kann man alles perfekt ausrichten 🙂

Wir sind also auf dem richtigen Weg. Ob das allerdings vor Weihnachten 100% zuverlässig läuft, kann ich nicht garantieren. Trotz des ganzen Frustes mit den Bugs der Anderen, hat das Projekt endlich mal wieder Spaß beim Programmieren beschert. Und das ist doch, weswegen man es tut, oder nicht 😀

Wie man in Java die lokale IP ermittelt

Da das Ermitteln der lokalen IPs über alle Interface angeblich Voodoo ist, hier mal eine Lösung ohne Voodoo 🙂

import java.net.InetAddress;
import java.net.NetworkInterface;

                try {
 			Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces();
 			while (networkInterfaces.hasMoreElements()) {
 		    	     NetworkInterface networkInterface = (NetworkInterface)networkInterfaces.nextElement();
 		    	     Enumeration ips = networkInterface.getInetAddresses();
 		    	     while ( ips.hasMoreElements() ) {
 		    		// InetAddress ipa = (InetAddress)ips.nextElement();
 		    		String ip = ((InetAddress)ips.nextElement()).toString();
 		    		ip = ip.replaceAll("/", "").replaceAll("%.*$","");
 		    		... HIER mit der IP was machen ... 
 		    	      }
 			}
 		} catch (Exception e) {
 			e.printStackTrace();
		}

Eine kleine Anmerkung dazu. Das ist ja der Cast auf InetAddress, es werden an der Stelle aber IPv4 und IPv6 Objekte zurück gegeben, die sich eben ohne Cast-Fehlermeldung nur als InetAddress verarbeiten lassen. Wenn Ihr wissen wollt, was es ist, ohne eine RegEx über das Ergebnis zu legen, lest einfach die Klasse des Objekts aus:

InetAddress ipa = (InetAddress)ips.nextElement();
log( ipa.getClass().getName() );
String ip = ipa.toString().replaceAll("/", "").replaceAll("%.*$","");

Anstatt getName() kann man auch getSimpleName() oder getType() benutzen, ganz wie es Euch gefällt.

Alternative:

		Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
		while (networkInterfaces.hasMoreElements()) {
		   	NetworkInterface networkInterface = (NetworkInterface)networkInterfaces.nextElement();
		   	
		   	for (InterfaceAddress address : networkInterface.getInterfaceAddresses()) {
		   	    String ip = address.getAddress().toString();
	   		    int netmask = address.getNetworkPrefixLength();
	   		    log( netmask + ip );
		   	    ip = ip.replaceAll("/", "").replaceAll("%.*$","");
	   		}
		}

Da bekommt man auch gleich die Netzwerkmaske mit, ist wohl der bessere Weg 🙂

Projekt: Laß das Telefon vorlesen

Android Apps wie „Talk – Free“ lesen dem Benutzer einen getippten Text vor und haben meistens das Problem, daß Sie mit großen Dateien nicht zurechtkommen. Mit Dateien in meiner Größenordnung sterben die Apps weg wie Fliegen, wenn sie die Dateien nebst Inhalt überhaupt öffnen können 🙂

Deswegen hatte ich gestern die fixe Idee, ich müßte eine TTS App (Text-To-Speech) für mein Handy bauen. Ok, satte 4 Stunden hat es dann gedauert, dem bescheuerten Eclipse bei zu bringen, überhaupt wieder Apps, AVD’s SDK’s und anderes anzuzeigen, upzudaten, zubenutzen und zu bauen. Von den vielen, vielen Rückschlägen, Flüchen, Verwünschungen und :Facepalm:s  mal abgesehen, kam am Ende etwas brauchbares raus.

Ich dachte immer Eclipse wäre das Tool zum Bauen von Android, oder irgendwelchen Anwendungen, aber so beschissen wie gestern hat es sich noch nie angestellt. Das SDK Update entfernte dann gleich mal die SDK TOOLS inkl. dem SDK Manager selbst von der Platte, nachdem es min. 400 MB unnütze Files auf die Platte gesaugt hatte. Dann löschte es den einzigen API Stand, der mich als Zielplattform wirklich interessiert hatte: Android 3.2+ … AAAAAAARGG…nein, ruhig, nicht schon wieder… tief luftholen… Android 3.2 ist API Level 13 und wird, wieso auch immer, nicht mehr im SDK Manager aufgeführt. Außer dem Tools Ordner wurden auch noch die Systemimages entfernt, so daß die bislang angelegten Virtual Devices aka Debugger-Umgebungen auch weg waren und auch nicht mehr angelegt werden konnten. Nachdem ich dann einen neuen SDK benutzt hatte, konnte ich zwar einen AVD anlegen , aber nicht benutzen, weil der sich nicht in LAN geklinkt hat und ohne LAN Verbindung war mein kleines Programm komplett nutzlos.

Was nun folgte war zum Glück etwas einfacher, aber nicht weniger aufwändig. Statt der netten Tante auf meinem Tablet, mußte mein neues Handy als Zielplattform herhalten. Leider hat Google das USB Debugging, ohne das man am PC nicht sehen kann, wo es denn genau bei der Liveanwendung knallt, versteckt und zwar dämlich versteckt. Es ist ok um Dumbos davon abzuhalten, das USB Debugging und die ganzen anderen coolen Entwicklerfunktionen zu aktivieren, aber es bleibt dämlich. Am Ende wollte das Handy dann auch per ADB angesprochen werden und es konnte endlich losgehen.

Das Projekt

Oben habe ich bereits verraten, daß es um TTS und große Files geht. Ich rede von Textfiles mit einer Größe von 500kb+ . Sich die vorlesen zu lassen, bietet keine „kostenlose“ App an. Damit man sich das vorstellen kann, 500kb sind ca. 500 Seiten DIN A4 Papier, also eine Menge Buchseiten. Will man sich die vorlesen lassen, dauert das Stunden und genau das habe ich vor.

Aber wieso will man sich das überhaupt  vorlesen lassen ? Naja zum einen kann man dann die Handyausgabe als Hörbuch verkaufen 😀 zum Anderen, und da komme ich derzeit ins Spiel, ist es richtig toll zum Korrekturlesen meines Romans. Die Rechtschreibkontrolle vom Officeprogramm kann zwar Fehler entdecken, aber falsche Worte, die richtig geschrieben wurden nicht. Außerdem findet diese auch keine sprachlichen Unschönheiten z.b. zu komplizierte Texte, falsch nieder geschriebene Gedankengänge usw. . Und da setzt jetzt die App ein, denn wenn es sich vorgelesen schräg anhört, ist es schräg, auch wenn es beim Lesen ok war. Nichts ist blöder für einen Romanschreiber, als wenn man den Roman nacher nicht vorlesen kann, ohne selbst zu stocken.(Auch dieser Absatz wurde Ihnen gespondert von Hardcore – der TTS App)

Das Problem

Die freien Apps lesen vor, was andere Apps an sie teilen oder per Copy&Paste vom User eingegeben wird.

Wer Handies kennt, kann sich vorstellen, daß alleine das Markieren des Textes den man hören will, Stunden braucht, wenn man es überhaupt fehlerfrei schafft. Vom Tippen auf dem Handy ganz zu schweigen 😉

Die Idee

Verlagern wir doch das Markieren und Ausschneiden des Textblockes auf den Desktop-PC, sprich meinen Linuxrechner. Den Text sendet man einfach per Netzwerk an die Sprachausgabe.

Die Stolperfallen

Natürlich liegen die alle im Android begraben und sind mehr oder weniger :faceplam:  . Wann kommt endlich der Tag, wo jemand ein Fedora ROM für Samsungsphones veröffentlicht, es wäre das Ende von Android wie wir es kennen und hassen.

Falle 1 : Die App muß einen öffentlichen Serverport öffnen, damit man etwas hinschicken kann.
Falle 2 :  Man muß die Handy  IP Adresse kennen, sonst kann man Falle 1 nicht umschiffen.
Falle 3 : Netzwerkfunktionen in der MainActivity sind Tabu, man braucht einen Netzwerktask.
Falle 4 : GUI Elemente dürfen nur von dem Task benutzt werden, der den View geöffnet hat.

Falle 5: Wie bekommt man den Textblock eigentlich zum Handy ?

Fangen wir mal mit #5 an :

[marius@eve ~]$ nc 192.168.0.117 3000
Wie die Webseite TheHackernews.com heute berichtet, ist es Forschern der Standford University bereits in 2015 gelungen, einen Super-Super-Cookie zu nutzen, in dem sie über die Browser API der mobilen Versionen von Chrome, Opera und Firefox den Batteriestatus abgefragt haben. Durch die Kombination von angezeigter "verbleibender Zeit in Sekunden" und dem Prozentwert der Ladung, ergeben sich bis zu 14 Millionen Kombinationen, die man Geräten zu ordnen kann.

Genau, das Schweizer Armeemesser „nc“ (NetCat) muß zumindest in Phase 1 der Enwicklung reichen und genau das tut es. Man öffnet also einfach eine TCP Verbindung zur App und schreibt den zu sprechenden Text rein. Wenn alles klappt, kommt Sprache raus.

Das war einfach, kommen wir zu Falle #2 :

        
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
@SuppressWarnings("deprecation")
String ip = Formatter.formatIpAddress(wm.getConnectionInfo().getIpAddress());
log("MY IP is "+ip);

Das funktioniert übrigens nur für LAN IPs. Wenn man WAN, also in dem Mobilen Internet unterwegs ist, bekommt man i.d.R. eine IPv6 Adresse und damit kommt die Routine oben nicht zurecht. Da ich WAN nicht brauche, reicht das hier.

Die TTS Engine ist leicht zu öffnen:

tts = new TextToSpeech(getApplicationContext(), new TextToSpeech.OnInitListener() {
   @Override
   public void onInit(int status) {
      if(status == TextToSpeech.SUCCESS ) {
         tts.setLanguage(Locale.GERMAN);
      }
   }
});

Nun zum Starten des Netzwerkthreads, den dem muß ich die IP mitgeben:

Thread serverThread = null;
...
this.serverThread = new Thread(new ServerThread(ip));
this.serverThread.start();

Der Serverthread an sich ist simpel. Ich nehme hier Port 3000, 10 Connections gleichzeitig auf meinem Socket und lege fest, daß die externe IP im LAN zum Binden des Interfaces genutzt werden soll, weil dumbo Android das sonst auf 127.0.0.1 aufmacht, da natürlich keiner was von 🙂  Der Code basiert auf einem Beispiel zu Serversockets, das aber wie dort beschrieben gar nicht funktioneiren kann 😉 Nehmt die Version hier:

public void run() {
			Socket socket = null;
			try {
				serverSocket = new ServerSocket(3000,10,InetAddress.getByName(ip));
				this.log("ServerSocket gestartet:"+ serverSocket);
			} catch (IOException e) {
				e.printStackTrace();
				this.log(e.toString());	
				return;
			} catch (Exception e) {
				e.printStackTrace();
				this.log(e.toString());
				return;
			}
			while (!Thread.currentThread().isInterrupted()) {
				this.log("ServerSocket waiting for accept:");
				try {
					if (serverSocket.isClosed()) serverSocket = new ServerSocket(3000,10,InetAddress.getByName(ip));
					
					socket = serverSocket.accept();
					System.err.println("Socket connection: "+ socket);
					this.log("Connection accepted");

					CommunicationThread commThread = new CommunicationThread(socket);
					new Thread(commThread).start();

				} catch (IOException e) {
					e.printStackTrace();
					this.log(e.toString());
				} catch (Exception e) {
					e.printStackTrace();
					this.log(e.toString());
				}
			}
		}
	}

Die doppelten Catch-Anweisungen sind natürlich nur zu Demozwecken drin. Ist die App mal gebaut, kann man unterscheiden zwischen einem echten IO Fehler und anderen Fehlern, z.b. OOM ( Out Of Memory ) und verschiedene Meldungen erzeugen.

Der CommunicationThread öffnet das Socket, daß beim Accept()  rausgekommen ist, extrahiert den InputStream(), macht einen BufferedReader daraus und ließt den Text zeilenweise ein, bis das Socket geschlossen wird:

       this.input = new BufferedReader(new InputStreamReader(this.clientSocket.getInputStream()));

try {
	String read = input.readLine();
	if ( read == null ) return; // wichtig, weil läuft sonst in einer Endlosschleife

	updateUIThread ui = new updateUIThread(read);
	updateConversationHandler.post(ui);

} catch (IOException e) {
	e.printStackTrace();
	this.log(e.toString());
	return;
} catch (Exception e) {
	e.printStackTrace();
	this.log(e.toString());
	return;
}

Der UIUpdateThread macht dann nichts weiter als die Textnachricht in Zeilen umzuwandeln und dann die TTS Engine zu füttern. Dabei wartet die Anwendung solange bis nicht mehr gesprochen wird, wofür der Thread 100ms schläft. Das schont die Batterie.

public void run() {
	String[] lines = msg.split("\\.");
	for(int i=0;i<lines.length;i++) { if ( lines[i].trim().length() > 0 ) {
			Toast.makeText(getApplicationContext(), lines[i],Toast.LENGTH_SHORT).show();
			tts.speak(lines[i], TextToSpeech.QUEUE_ADD, null);
			while( tts.isSpeaking() ) {
				try {
					Thread.sleep(100);
				} catch (Exception e) {
					e.printStackTrace();
					Toast.makeText(getApplicationContext(), e.toString() ,Toast.LENGTH_LONG).show();
				}
			}
		}
       }
}

Fertig ist unser Remote-TTS-Reader 🙂

Nun leiten wir das Audio Signal des Telefons noch an den Mikrofonanschluß vom PC und starten FFMPEG:

ffmpeg -f pulse -i default -c:a:0 libmp3lame -b:a:0 128k test.mp3

Fertig ist das Hörbuch für unterwegs 🙂

Ihr könnt die fehlenden Teile aus dem Tutorial extrahieren, es ist der gleiche Aufbau. Nur habe ich noch einiges erweitert, z.b. logging, weil seine Routinen halt nicht funktioniert haben. Die relevanten Änderungen sind oben drin, so daß Eurer Projekt funktionieren sollte.

Anmerkung: Man kann von 10 Rechnern aus die APP auf dem Handy gleichzeitig ansprechen, aber die Sprachengine wird das nur nacheinander wiedergeben.

 

Event: Real World Java Web Security

Folgende Veranstaltung der Java User Group Braunschweig, findet am 2. Juni bei LINEAS statt:

Real World Java Web Security

Es geht um die Sicherheit von Java Webanwendungen. Wir dürfen gespannt sein, was uns da präsentiert wird. Als Sprecher kommt Dominik Schadow , welcher als Senior Consultant beim IT-Beratungsunternehmen bridgingIT arbeitet.

Er ist spezialisiert auf die Architektur und Entwicklung von Java Enterprise Applikationen, Enterprise Application Integration und die sichere Softwareentwicklung mit Java. In Trainings und Coachings überzeugt er andere Entwickler von der sicheren Entwicklung mit Java.

Ort:  LINEAS Informationstechnik, Theodor-Heuss-Str. 2, 38122 Braunschweig

Jasper kompilierte Programme beschleunigen.

Jasper hat eine unschöne Seite. Benutzt man in einer JSP eine Direktausgabe von HTML Code, was sehr praktisch ist, kommt es zu einer unschönen Situation. Kleines Beispiel:

<%
<div class=olddefect>
 <table border="0">
      ...
 </table>
 </div>
%>

Je mehr Code man in den <% %> Block hat, desto mehr Zeilen hiervon werden erzeugt :

 out.write("\n");
 out.write("<div class=olddefect>\n");
 out.write("\t<table border=\"0\">\n");
 ...
 out.write("\t</table>\n");
 out.write("</div\n");
 out.write("\n");

Und wie man sehen kann, ist das höllisch inperformant. Zeit es zu optimieren.

Will man nun eine WebApp schneller bekommen, bleiben einem nur zwei Wege:

a) Mehr IO investieren z.b. in Form eines Ladevorgangs. Der kann natürlich im RAM gecacht worden sein, aber je nach Umfang der Webanwendung kann das vielleicht nicht funktionieren.
daher b) man verzichtet auf Zeilenumbrüche im HTML Code, so erzeugt Jasper genau eine Zeile out.write(„…“). Die ist zwar unschön, aber performanter.

Diverse Codeverkürzungstools könnte man dafür einsetzen.

Fall jemand rein zufällig einen Jasper Entwickler kennt, schlagt b) mal zur automatischen Optimierung vor. Danke.

Servlet und JSP verbinden

Kleine Exkursion in die Welt von JavaServerPages und Servlets und wie man dazwischen Daten austauschen kann. Zunächst mal die einfache Seite, die JSP :

<%@ page import="java.net.IDN,misc.*,web.*,java.util.*,rsi.*,hash.*,sendmail.Sendmail,java.io.*,server.*,org.json.JSONArray,org.json.JSONObject,ordb.Pandora_server" pageEncoding="UTF-8"
%><%@ include file="cookies.jsp"
%><jsp:useBean id="daten" scope="session" class="hash.StringHash"
/>

Der JSP:useBean Tag ermöglicht u.a. sessionbasierte Objekte in einer JSP zu verarbeiten. Überall wo man den Tag angibt, ist das Objekt verfügbar. Da kann man dann ganz normal drauf zugreifen und mit arbeiten.

if ( daten.checkContent() ) out.write("Daten wurden geprüft und für OK befunden");

Wenn nun ein zweiter Teil der Anwendung in einem Servlet geschrieben wurde,  möchte man ggf. Daten zwischen beiden Teilen der Anwendung austauschen. Ein Servlet ist nichts anderes als eine Klasse, deren vordefinierte Methoden das Request- und Responseobjekt des Webaufrufes bekommen.

Ein kleines, stark vereinfachtes Beispiel:

public class HTMLDispatcher extends HttpServlet {

public void doGet(HttpServletRequest req, HttpServletResponse res)
throws IOException {
    doRequest(req,res);
}

public void doPut(HttpServletRequest req, HttpServletResponse res)
throws IOException {
    doRequest(req,res);
}

public void doDelete(HttpServletRequest req, HttpServletResponse res)
throws IOException {
    doRequest(req,res);
}

public void doPost(HttpServletRequest req, HttpServletResponse res)
throws IOException {
    doRequest(req,res);
}

...
}

Das Servlet kann man dann im Tomcat ( WebApplicationServer ) als Ziel für verschiedene Webaufrüfe definieren (siehe unten). In meinem Fall habe ich gesagt, daß mein Servlet für HTML Aufrüfe zuständig ist. Mein Servlet wertet dann die URL aus und macht mit dem HTML File noch einiges, bevor es ausgegeben wird.

public void doRequest(HttpServletRequest request, HttpServletResponse res)
throws IOException {

res.setContentType("text/html");
Writer out = new BufferedWriter(new OutputStreamWriter(res.getOutputStream(), "UTF-8"));

String method = request.getMethod();
String uri = request.getRequestURI();
String[] args = uri.split("/",-1); // Alles splitten, auch wenn es leer ist

StringHash daten = (StringHash) request.getSession().getAttribute("daten");
if ( daten == null ) daten = new StringHash();

Um an die Daten aus der JSP zu kommen, setze ich „request.getSession().getAttribute(„daten“)“ ein. Das geht aber nur mit Sessionobjekten so. Da die Methode getAttribute() ein klassenloses Objekt zurück gibt, muß selbst den TypeCast machen. Wenn noch keine Session aufgebaut wurde, kann es auch noch kein Bean geben, das wir uns krallen könnten. Damit die Anwendung dann nicht abschmiert, lege ich hier ein Objekt an. Natürlich kann man an der Stelle auch eine Fehlermeldung oder einen Redirect auslösen. Das ist situationsabhängig.

O== Warum überhaupt Servlet und JSP mischen ?

Servlets müssen kompiliert und der WAS Server üblicherweise neu gestartet werden, um die Änderungen anzunehmen. Wenn man JSP Seiten benutzt kümmert sich Jasper darum. Das spart im Einzelfall eine Menge Zeit und man verliert dabei auch keine Sessionobjekte, d.h. kleinere Fehler kann man direkt beheben und weitermachen. Zweifelsfrei eine zeitsparende Methode seine Webseiten zu bauen.

O== Wofür setzt man ein Servlet ein ?

Servlets können auf spezielle Webanfragen eingestellt werden, d.b. bspw. alle Files die auf .jos enden, könnten eine serialisierte Repräsentation des JavaObjekts einer Seite ausgeben, statt HTML Code. Das bekommt man mit reinen JSP Seiten nicht hin.

O== Wie aktiviert man ein Servlet ?

Im WEB-INF Verzeichnis der Anwendung liegt die web.xml Datei. In dieser Datei kann man ein Servlet ganz leicht hinzufügen:

    <servlet>
        <servlet-name>HTMLDispatcher</servlet-name>
        <servlet-class>servlet.HTMLDispatcher</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>HTMLDispatcher</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

Die nötigen Klassen müssen dann im dortigen classes Verzeichnis hinterlegt sein.

Java Swing und das Repainten von Komponenten

Von Zeit zu Zeit muß man Dinge tun, welche die Entwickler von Swing nicht vorgesehen hatten. Da wäre z.B. das Updaten einer JTextArea, während das Programm läuft.

Kleines Beispiel:

        for(int i=0;i<names.length;i++ ) {
            infotext = "Uploading file ... "+names[i];
            if ( infotext.length()> 55 ) 
                    infotext = infotext.substring(0, 55);
            updateInfo( infotext );
            String link = p.uploadFile(names[i].trim(),"/");
            result += "File: "+names[i].trim()+"\nShare: "+link+"\n";
        }
    JTextArea text = new JTextArea("");

    public void updateInfo(String text) {
            this.text.setText(text);
    }

Jetzt sollte man annehmen, daß die JTextArea jeweil nach dem setText() den neuen Text anzeigt. Tut sie aber nicht und das betrifft nicht nur JTextArea, sondern so ziemlich alle Komponenten von Swing.

Warum ist das so ?

Swing cacht quasi die ganzen Änderungen und führt diese erst durch, wenn Ruhe eingekehrt ist, d.b. wenn das Programm wartet. Das passiert z.b. wenn eine Dialogbox angezeigt wird. Die Verarbeitung des Programms ist dann eingestellt und es wartet z.B. auf das Drücken des OK Buttons in einem InfoRequester.

Solange das Programm aktiv läuft, gibt es keine Updates, weil das Rechenleistung kostet und sich mit der Zeit einige Inhalteänderungen ergeben haben können, die man dann gemeinsam durchführen kann. So spart das Guisystem Zeit und Resourcen.

Nun ist das Delay einer solchen Sparmaßnahme natürlich kontraproduktiv, wenn es um die kontinuierliche Anzeige neuer Inhalte geht, wie z.b. einem Progressbar oder eben einem textlichen Infofeld wie einer JTextArea.

Die Swing API bietet für Komponenten nun eine Methode repaint() an, die Swing mitteilt, daß die Komponente sich geändert hat und neu gezeichnet werden muß. Soweit, so gut. Leider wird der Rendercall wie schon erwähnt irgendwann gemacht und damit kann man repaint() solange aufrufen, bis man schwarz wird.

Dies Problem haben viele Javaentwickler. Auf den diversen Hilfeseiten findet man die wildesten und vor allem komplett falschen Lösungen für das Problem, in dem Mantraähnlich repaint() oder validate() empfohlen wird.

Alles falsch Leute, die Lösung sieht so aus:

	
        public void updateInfo(String text) {
			this.text.setText(text);
			this.paintComponents( this.getGraphics() );
	}

this bezieht sich dabei auf den eigentlichen Frame z.B. das Panel in dem die Komponente untergebracht ist. paintComponents(g) zeichnet alles neu und damit auch den aktualisierten Text der Area. Der Grund wieso Swing mit Delays arbeitet zeigt sich deutlich, wenn man mehrfach pro Sekunde paintComponents(g) aufruft. Nicht nur daß man die Updates im Viewport sehen kann, es dauert auch so lange, daß man tatsächlich Performanceeinbrüche haben kann. Damit wäre geklärt, wieso es normalerweise delayed ist.

Fazit: Swing ist eindeutig zu langsam konstruiert.

Jersey, Pydio & the next Big thing – Part II

Ja, es hat länger gedauert als erwartet. Nicht nur Jersey steckt voller Ungereimtheiten, auch Pydio hat tolle Bugs auf Lager. Kombiniert man das, verschwendet man richtig viel Zeit.

Status: Es geht.

Update der verwendeten Tools: Jersey 1.18 ( jsr311-api-1.1.jar, asm-3.1.jar, jersey-bundle-1.18.jar, NEU: jersey-multipart-1.18.jar)

Der Fileupload:

Der FileUpload stellt sich in Jersey schwieriger dar, als man annehmen sollte, denn die spärlichen Beispiele sind einfach mal falsch, solange man nicht die Suchbegriffe benutzt,
die einen ohnehin schon auf die richtige Fährte gebracht haben : MultiPart.

Aber auch da gibt es Fallstricke die es zu umschiffen geht und weil das einfach nur nervt, gibts hier gleich die Lösung der Probleme:

Schritt 1:

                FormDataMultiPart form = new FormDataMultiPart();
                FormDataBodyPart p = new FormDataBodyPart(
                            FormDataContentDisposition
                                .name("userfile_0")
                                .fileName("Dateiname")
                                .build()
                            , new File("Dateiname")
                            , MediaType.MULTIPART_FORM_DATA_TYPE);
                form.bodyPart(p);

Damit man FormDataMultiPart benutzen kann, muß oben erwähntes JAR-File zum Projekt hinzugefügt werden. Danach ist es aber ganz einfach das File dann wirklich per REST hochzuladen. Wobei REST hier wohl der falsche Ausdruck ist, denn es ist ein simples MultiPart-Form, daß per POST hochgeladen wird. Das macht jeder Webbrowser genauso.

Schritt 2:

                response = resource.queryParam("get_action","upload")
                           .queryParam("dir","/")
                           .queryParam("secure_token", securetoken )
                           .header("Cookie", cookieheader )
                           .accept(MediaType.APPLICATION_XML)
                           .type(MediaType.MULTIPART_FORM_DATA)
                           .post(ClientResponse.class, form);

Jetzt muß man natürlich noch den Share Call per API iniziieren werden, damit ein Downloadlink für die Datei erzeugt wird. Das sagt sich jetzt so leicht, denn Pydio 5.2.0 und 5.2.1 sind an der Stelle einfach mal defekt. Da wir mit der REST API arbeiten, erwartet unser Client zurecht auch eine korrekt formulierte Antwort und dazu gehört auch die korrekte Länge der Antwort vom Server. Die Entwickler von Pydio haben es nun geschafft diesen Teil der API erfolgreich zu entschärfen. Statt der korrekten Länge des Links, kommt ein

Content-Length:  0

und damit kann Jersey nicht umgehen. Daher darf man nun erstmal das Pydio Quickfixen.Von der Isolation des Fehlers bis zum funktionierenden Fix der PHP Anwendung, vergingen rund 80 Minuten.

Ich hege ja die Hoffnung, daß mein Bugreport gelesen und bearbeitet wird. Zur Sicherheitslücke die ich letzte Woche an das Team von Charles geschickt habe, kam nur ein : „Wir schicken Dir Deine Mail zurück-kam also an“ Nachricht zurück. Ein Umgang mit Securityproblemen muß sich wohl erst noch entwickeln. Was übrigens für den an sich sicheren Code von Pydio spricht. „Gut“ ist allerdings was anderes. Kommentare fehlen da fast vollständig 🙂

Nachdem nun diese Klippen umschifft sind, kann die eigentliche Arbeit an der Anwendung beginnen.

JAXB XML Parsing kann man übrigens getrost vergessen, der XML Code der von Pydio kommt überfordert den Parser, so daß Elemente einfach verloren gehen. Ob da der Fehler bei Pydio liegt, oder der Parser Bugs hat, mag ich nicht beurteilen. Ich habe mir dann flugs einen eigenen SimpleXML-Parser mit XML-zu-Objekt Komponente geschrieben. Reflektion ist ne schöne Sache in Java.

Wenn Sie also vorhaben sollten, für Pydio in Java zu entwickeln, kommt einiges auf Sie zu. Sie dürfen sich aber gern melden, wenn Sie Fragen haben. Aufgrund der Bots die mein Blog zum Abladen von Schrott missbrauchen wollen, kann ich leider keine Kommentarfunktion freischalten 🙁

 

Javaprogramme könnten schneller laufen

Mal wieder fiel sofort auf, als ich einmal mehr strace bemühen mußte um festzustellen,
was genau das von Java aus gestartete Bashscript so treibt.

Dabei weckten wieder diese Zeilen meine Aufmerksamkeit:

[pid  3488] gettimeofday({1371633845, 44988}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 45163}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 45283}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 45401}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 45648}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 45776}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 45917}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 46044}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 46156}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 46286}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 46509}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 46629}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 46746}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 46857}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 46967}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 47120}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 47231}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 47343}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 47465}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 47612}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 47750}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 47870}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 48008}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 48121}, NULL) = 0
[pid  3488] gettimeofday({1371633845, 48232}, NULL) = 0

Wie man leicht erkennen kann, finden diese Zugriffe im Milli- bis Microsekundentakt statt.

Wenn man sich das strace log vornimmt, kommt das dabei raus:

[root]# wc --lines  /tmp/log
38896 /tmp/log
[root]# grep -c gettimeofday /tmp/log
25366

Also rund 2/3 aller Logzeilen entfallen nur auf gettimeofday() . Auch wenn der Aufruf an sich nur wenige Takte der CPU benötigen sollte, die Masse der Aufrüfe an sich stellt ein nicht zu verachtendes Problem dar. Vermutlich ist das völlig unnötig. Mal sehen, ob sich Oracle der Meinung anschliesst.

Im Einsatz war Java 6 latest.