Archive for the ‘Java’ Category
Depuis quelques jours, je m'intéresse à de la génération automatique d'une hiérarchie de topics en utilisant Flickr.
Pourquoi Flickr ? pour ses données textuelles (le tagging social des images par les utilisateurs du website) et ses données images. J'intègre tout cela à mon modèle de donnée pour construire des collections d'image, et j'explore ces collections avec des outils que j'ai développé il y a quelques mois, je voudrais pouvoir explorer facilement le dataset et gérer les documents (retrait d'image ou de tag).
C'est ainsi que comparativement à mes autres expérimentations, le modèle textuel associé à mes images contient:
- des documents faiblement taggés (weakly tagged).
- des informations textuelles qui ne sont pas forcément en anglais, ni basé sur un alphabet latin.
Jusqu'à présent mes images étaient annotées en anglais et avec l'objectif explicite de construire un jeu de donnée image pour la vision par ordinateur, on peut donc dire que ses images étaient annotées par des
iconographes de l'ordinaire en le sens que les annotations sont pertinentes (i.e. fiable, c'est pour cela que je dis
iconographe) et portent sur des objets de la vie de tous les jours (d'où
iconographe de l'ordinaire).
Le sujet de ce billet est le second point, Java gère très bien l'UTF, mais il est évident que l'index des fonts disponibles pour les UI graphique est délégué à l'installeur de Java sur la machine, car on ne peut pas localiser les fonts de façon standard pour tous les
OS pour lesquels Java est disponible.
Sur ma machine, j'installe toujours Java à la main, dans
/opt, je trouve cela plus propre, je suis contre ce qui est fait pour java-6-openjdk par exemple (mettre les fichiers de configuration de Java dans
/etc... pourquoi vouloir complètement intégrer Java à l'
OS, je trouve que c'est particulièrement microsoftien comme façon de faire... ).
Le fichier
~jrehome/lib/fontconfig.properties qui renseigne Java sur les fonts disponibles sur le système n'est donc pas renseigné quand on installe Java à la main: dans le cas d'Ubuntu il suffit de piocher le fichier installé pour java-6-openjdk
/etc/java-6-openjdk/fontconfig.properties et le copier dans
~jrehome/lib/fontconfig.properties.
En Java pour exécuter des commandes système, on peut se servir des classes
Runtime et
Process.
C'est ainsi que j'ai programmé dans ma toolbox perso une classe
SystemCommandHandler.
En réalité, sur une plateforme GNU/Linux l'objet
Process retourné par
runtime.exec(String command) utilise un
$sh -c command, ce qui n'autorise pas une séquence de commande (i.e. plusieurs commandes séparées par un semicolon) car
$sh -c ne prend qu'une seule et unique commande, et les commandes en background ne sont pas tolérées non plus: si la commande est lancée en background le shell se termine et un signal HUP est lancé.
De plus pour certaines commandes utiliser un NOHUP ne fonctionne pas, comme sshpass par exemple qui se tue à la réception d'un SIGHUP avec ou sans NOHUP.
De plus, le gros désavantage de cette méthode est qu'on ne connait pas le pid du process lancé.
Une autre méthode est de lancer un shell et d'injecter les commandes dans l'entrée du shell, ainsi tant que le flux d'entrée n'est pas fermé le shell ne se termine pas (lecture bloquante).
Ainsi, on peut envoyer sur l'entrée un
$echo $$, pour récupérer le pid du shell, et ensuite envoyer la commande que l'on veut exécuter (et éventuellement en background :-) ). On ne connait pas exactement le pid de la commande que l'on veut lancer, mais on connait le pid de son père qui est le shell: si on veut killer la commande, on n'a qu'a killer le shell via son pid.
Il y a un comportement de
GUI qui diffère selon la version du
JDK, soit SUN soit OpenJDK.
Lorsque l'on utilise un JTree avec CheckBox dans les TreeNode, avec une implémentation de l'interface TreeCellEditor, le JRE de OpenJDK a un comportement d'interaction utilisateur étrange: lors du premier clic souris dans le JTree, si le clic concerne la CheckBox, le clic n'est pas pris en compte, et donne uniquement le focus au noeud du JTree concerné. Les clics suivants sont bien attribués à la CheckBox.
Tant que le dernier élément qui a obtenu le focus dans le JTree n'a pas changé, les clics suivant sont bien gérés même si entre temps le focus passe à une autre fenêtre: cela ne concerne uniquement le premier clic souris sur le node d'un JTree, cela ressemble fort à un bug...
Le JRE de SUN à un comportement normal: si un clic souris survient au dessus de la JCheckBox d'un noeud (dans le JTree), un ActionEvent est levé concernant la JCheckBox même si le composant associé au noeud n'avait pas le focus.
(Voir
l'épisode précédent).
Nouvelle version de Galatee, mon petit projet de navigation dans une collection d'image en Java:
Galatee_0.3.jar. Le projet est sous licence
GPL.
(Attention: Je n'ai pas vérifié que l'application fonctionne bien sous d'autres systèmes d'exploitation (comme windows), je développe exclusivement sous GNU/Linux, et j'avoue que par exemple je n'utilise pas
System.getProperty("path.separator")).
- correction de bugs,
- ajout et suppression de colonnes à la table d'affichage avec les touches '+' et '-',
- activation du modèle de table dynamique (nombre de colonnes dans la table d'affichage fonction de la largeur de la fenêtre),
- activation de la navigation dans la liste d'images avec pagination.
Pour tester le jar:
java -jar Galatee-0.3.jar $path
L'application test est codée pour lancer la commande
display de ImageMagick pour les images double-clickées.
Il reste encore un petit problème que je voudrais régler: une queue bornée pour les images chargées en mémoire (avec une gestion multi-pages, i.e. prenant en considération la pagination des documents), mais une stratégie intelligente (et la plus légère possible) pour cela, n'est pas facile à écrire.
Dans
ce billet, je parlais d'une application SWT basée sur le widget
SWT Nebula Gallery pour naviguer dans une collection en Java/SWT.
J'avais codé un thread de chargement pour avoir un
load-when-you-see, et le tout fonctionne plutôt bien.
MAIS, c'est du SWT et cela m'embête un brin. Le but de ce projet est de naviguer dans de grande collection d'images, et il faut aussi que je puisse afficher un certain nombre de metadata associées à chaque image, ainsi je voulais afficher un petit ScrollPane pour chaque image contenant les metadata sous forme textuelle, et faire cela en SWT est apparemment une chose ardue.
C'est ainsi que je me suis mis à coder un widget similaire pour swing, j'ai renommé mon projet utilisant SWT Nebula Gallery en SWTGalatee, et mon nouveau projet swing devient Galatee.
Je pensais utiliser pour cela une JTable et un custom renderer pour les cellules de cette table. Ce custom renderer prendrait son rendu sur un JPanel qui contiendrait une image et un JScrollPane, mais c'est sans savoir comment fonctionne un
Renderer en swing (voir
ça), en fait un renderer ne produit que des images de composant swing: il ne faut pas s'attendre à une réponse d'événements souris ou clavier sur une image de composant.
Et un JScrollPane sans interaction via la souris, c'est inutile !
Solution adoptée: ne pas utiliser de JScrollPane pour afficher mes metadata, mais plutôt faire un JTextArea de la hauteur nécessaire pour afficher toutes les metadata.
Ainsi, la JTable à des lignes de hauteurs différentes, chaque ligne est de la hauteur de la cellule sur cette ligne qui a la hauteur max.
Le résultat sur la base MIRFlickr:
Plusieurs points sont à préciser tout de même:
- pour le moment, je conserve en mémoire les thumbnails calculées pour le rendu, en fonction de leurs tailles, on peut vite être confronté à un
java.lang.OutOfMemoryError: Java heap space.
Pour le moment, utiliser les paramètres de la JVM -Xms<initial heap size> -Xmx<maximum heap size> est une solution à court terme.
Une amélioration pourrait être d'améliorer le thread d'affichage en utilisant une liste bornée en taille pour les items chargés en mémoire.
- le widget swing est plus lent que le widget SWT Nebula Gallery (sauf pour le temps de chargement du widget en lui même, swing est beaucoup plus rapide), sur deux points:
- pour le calcul des thumbnails: dans un premier temps, j'ai utilisé
BufferedImage.scaleInstance(...), puis dans un second temps JAI (j'ai testé avec et sans la library C, d'ailleurs étonnamment la library native de JAI est plus lente que la library pure java). Dans les deux cas SWT fait appel à des routines plus rapides.
- pour l'affichage, la JTable est un widget assez rigide, il y a peut être des paramètres pour assouplir le scrolling mais je ne les connais pas. J'ai quand même positionné l'unité d'incrémentation du scroll (via
sp.getVerticalScrollBar().setUnitIncrement(42), sp étant le JScrollPane de ma JTable, pour que le scrolling ne se fasse pas row par row mais sur une quantité de pixels.
- le layout: avec SWT Nebula Gallery, le layout est dynamique, si la largeur de la fenêtre change le nombre de colonne de la grille d'images s'adapte pour afficher autant de colonnes que possible. Dans mon widget, la JTable empêche ce genre de chose.
Je pourrais faire écouter le redimensionnement de la JTable et changer son modèle en fonction, mais j'ai peur que cela alourdisse cruellement l'affichage.
Une solution à adopter serait peut être de changer le modèle sur demande de l'utilisateur, par exemple avec les touches '+' et '-', pour ajouter ou retirer des colonnes au modèle.
edit 09-05-16: j'ai ajouté les deux comportements (1) '+' et '-' pour modifier le nombre de colonne, et (2) un modèle de table dynamique en fonction de la taille du JScrollPane contenant la JTable, ça se fait bien en codant futé pour modifier au minimum le modèle.
Ainsi j'ai dû coder aussi l'update de la cellule sélectionnée dans le nouveau modèle (i.e. nouveau modèle car redimensionnement de la table, il faut que la cellule qui était sélectionnée dans l'ancien modèle soit toujours une cellule sélectionnée dans le nouveau modèle), et un scroll de la JTable pour que la cellule sélectionnée soit affichée (un scroll du rendu du nouveau modèle pour que la cellule sélectionnée soit affichée).
Archives:
- Galatee-0.1.jar
- edit 09-05-17: nouvelle version avec l'edit 09-05-16 (voir plus haut): Galatee-0.2.jar.
Ajout également d'une interface GalateeListener à implémenter pour répondre aux événements sur la grille d'image:
public interface GListener extends EventListener {
public void itemClicked(GEvent e);
public void itemDoubleClicked(GEvent e);
public void selectionChanged(GEvent e);
public void focusChanged(GEvent e);
}
- SWTGalatee-0.1.jar
Autres approches éventuellement possibles (mais que je n'ai pas investigué) pour réaliser un tel widget:
- utiliser un Panel avec un GridLayout, spécifier la taille de la grille du layout en fonction du nombre d'images. Trouver un moyen pour savoir quand une cellule du panel doit être affichée et quand elle n'est plus sur l'écran. Quand elle doit être affichées faire un
setComponentAt(...) de la cellule avec un custom panel pour mon image+metadata. Dès que la cellule quitte l'affichage de la fenêtre (par un scrolling), faire un setComponentAt(..., ..., null).
- ne pas utiliser une table mais une succession de JList, ce qui permettrait de ne pas avoir des lignes de tailles fixes (et pas de ligne du tout d'ailleurs), cependant ce n'est peut être pas visuellement très louable.
Pour rediriger les sorties standard
output et
error de la console eclipse vers un fichier de log:
- configuration du lancement du main > onglet common
- cocher File
- indiquer le chemin vers le fichier de log
[
source]
Ces derniers temps, je cherchais un widget graphique en Java pour naviguer dans une collection d'image (éventuellement assez grande, genre 25.000 documents), quelque chose à la
GThumb (en
stand alone, pas une application web), avec un chargement dynamique et qui ne vampirise pas le thread d'affichage.
Mais je n'ai rien trouvé qui me convienne vraiment, car de plus, je voudrais pouvoir afficher près de chaque image, des métadonnées issues de traitement d'image (des informations stockées dans un fichier
XML à la mpeg-7).
Dans le toolkit SWT, il y a le
Nebula project qui a été lancé pour apporter des
customs widgets à SWT: CalendarCombo, CDateTime, CollapsibleButtons, CompositeTable, DateChooser, DateChooserCombo, FormattedText, Gallery, Grid, GanttChart, PGroup, PShelf.
Le widget
Gallery est très intéressant:
En partant d'un snippet pour afficher une collection d'image, j'ai fait un petit projet nommé Galatee, affichant les images avec un chargement dynamique des documents
load-when-you-see.
J'ai codé un ImageLoaderThreadPoolThread qui est un thread Java classique, qui vide une queue d'images à charger, et qui démarre un thread SWT
GUI de mise à jour de l'affichage une fois qu'une image est chargée (et redimensionnée pour son affichage).
L'application sur le jeu de donnée MIRFlickr:
Un problème cependant: l'intégration de widgets graphique en SWT est une chose un brin... problématique: je voudrais utiliser un scrollpane dans chaque cellule de ma grille d'images, et ajouter la liste de mes métadonnées dans ce scrollpane. En swing on peut faire cela avec un custom components (le widget est instancié une seule fois mais dessiné plusieurs fois, dans un JTableRenderer), mais en SWT c'est plus délicat car les widgets sont des widgets système...
Dans le cadre de mes travaux de thèse, j'implémente une méthode d'annotation d'image basée sur des graphes et un modèle markovien.
J'ai codé un premier prototype en Java, je construis mon graphe avec la bibliothèque Jung, et obtiens ma matrice d'adjacence ainsi:
SparseDoubleMatrix2D matrix = GraphMatrixOperations.graphToSparseMatrix(g);
où
SparseDoubleMatrix2D est un objet de la bibliothèque Colt du CERN.
Cependant:
RWR.runRWR: begin.
RWR.runRWR: loading the graph.
RWR.runRWR: loading the LIRE index associated to the graph.
RWR.runRWR: loading the query image in a BufferedImage.
RWR.runRWR: search by similarity in the LIRE index.
RWR.runRWR: we search the vertex which is the NN in the graph.
RWR.runRWR: retrieve the indice of the image query for building the restart vector.
RWR.runRWR: preparing the RWR computing.
RWR.runRWR: the graph contains 49197 nodes, 144484 edges.
Exception in thread "main" java.lang.IllegalArgumentException: matrix too large
at cern.colt.matrix.impl.AbstractMatrix2D.setUp(Unknown Source)
at cern.colt.matrix.impl.AbstractMatrix2D.setUp(Unknown Source)
at cern.colt.matrix.impl.SparseDoubleMatrix2D.<init>(Unknown Source)
at cern.colt.matrix.impl.SparseDoubleMatrix2D.<init>(Unknown Source)
at edu.uci.ics.jung.algorithms.GraphMatrixOperations.graphToSparseMatrix(GraphMatrixOperations.java:244)
at edu.uci.ics.jung.algorithms.GraphMatrixOperations.graphToSparseMatrix(GraphMatrixOperations.java:223)
at net.trevize.cbiaproto.dataformer.RWR.runRWR(RWR.java:155)
at net.trevize.cbiaproto.dataformer.RWR.main(RWR.java:382)
Ce qui me parait assez étrange, le graphe fait environ 50.000 nœuds mais la matrice d'adjacence est très creuse, et la matrice demandée est sparse...
Apparemment ce n'est pas un problème de mémoire.
A propos de Colt:
http://www.semiophore.net/v4/index.php?option=com_myblog&show=Extension-pour-librairie-Colt.html&Itemid=1&lang=en.
Pour un algorithme de désambiguisation du sens des mots d'une phrase, que j'essaye douloureusement d'implémenter, il me faut un analyseur syntaxique de grammaire LL(k) qui puisse manger un token à la fois, et à un moment donné, quand je le décide, je voudrais que l'analyseur me prédise quelles productions sont possibles en l'état de l'analyse...
- avec ANTLR et une astuce un brin bancal.
- façon barbarian avec ANTLR ou JavaCC: essayer toutes les possiblités sur les types de tokens (j'en ai peu, la grammaire est courte, donc c'est envisageable), et si aucune erreur alors le type de token est possible... Le problème est que ce n'est pas que le type de token que je veux, mais aussi les productions possibles!
Notes:
- dans la dernière version d'ANTLR, dans le package Java, il y a la version ANTLR 2.7.7 dans un package
antlr, et la version 3.1.1 dans un package org.antlr, attention donc à bien utiliser la classe org.antlr.Tool et non antlr.Tool...
Voici une façon de le faire rapidement et proprement.
Ici le but du jeu est de mettre à jour le contenu texte du nœud
name (en lui appliquant un trim-plus-plus, version améliorée d'un
String.trim()).
String file_path = dir_dataset_path + "/" + dir_path + "/" + file_name;
Document d = null;
try {
d = dbfactory.newDocumentBuilder().parse(new InputSource(file_path));
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ParserConfigurationException e) {
e.printStackTrace();
}
NodeList nl = d.getElementsByTagName("name");
for (int i = 0; i < nl.getLength(); ++i) {
Node n = nl.item(i);
n.setTextContent(trimpp(n.getTextContent()));
}
Result result = new StreamResult(new File(file_path));
Source source = new DOMSource(d);
try {
transformer.transform(source, result);
} catch (TransformerException e) {
e.printStackTrace();
}
Mais cela reste du
DOM, quand j'aurais le temps je garderai du côté de StAX ou WAX...
Pour écrire des scripts XQuery sous Eclipse, il n'y a pas pléthore de plugins pour apporter de l'autocomplétion, coloration syntaxique, et de la vérification de syntaxe.
- DataDirect XQuery
- XQDT
- xqIde: coloration syntaxique, vérification de syntaxe, génération de xqDoc, et intégration avec la database Marklogic pour l'exécution de requête XQuery.
Bug avec XQDT:
Si dans votre précédente session d'Eclipse, le Workbench contenait un ficher .xq ouvert, le prochain lancement d'Eclipse est bloqué par l'exception suivante:
Exception in thread "Thread-2" org.eclipse.swt.SWTException: Device is
disposed
at org.eclipse.swt.SWT.error(SWT.java:3777)
at org.eclipse.swt.SWT.error(SWT.java:3695)
at org.eclipse.swt.SWT.error(SWT.java:3666)
at org.eclipse.swt.widgets.Display.error(Display.java:1142)
at org.eclipse.swt.widgets.Display.wake(Display.java:4068)
at
org.eclipse.ui.application.WorkbenchAdvisor$1.run(WorkbenchAdvisor.java:797
)
Cette exception bloque complètement le chargement d'Eclipse.
Si il n'y a pas de .xq d'ouvert dans la session précédente de Eclipse, il n'y a pas de problèmes, le plugin fonctionne correctement.
Problème rencontré avec Java1.6, Ubuntu 8.04 Hardy Heron et Eclipse Ganymede for
Java developer Linux GTK.
Pour faire des notifications Desktop depuis Java sur un système GNU/Linux:
- Un moyen de faire est d'utiliser le
TrayIcon introduit dans Java1.6 pour ajouter une icône de notification dans le System Tray (nommé notification area sous GNU/Linux, et Taskbar Status Area sous Windows), puis utiliser la méthode:
TrayIcon.displayMessage(caption, text, TrayIcon.MessageType.INFO);.
Cette approche a le mérite d'être multi-plateforme.
voir http://java.sun.com/developer/technicalArticles/J2SE/Desktop/javase6/systemtray/
- Utiliser libnotify:
- soit par un script python par exemple:
#!/usr/bin/python
from pynotify import *
import sys
def notify(title="", message=""):
n = Notification(title, message)
n.show()
#n.set_hint("x", 0)
#n.set_hint("y", 0)
init("cli notify")
if len(sys.argv)>2:
notify(sys.argv[1], sys.argv[2])
else:
notify()
voir http://roscidus.com/desktop/node/336.
- soit en utilisant une commande système avec
notify-send (apportée par le paquet libnotify-bin sous Ubuntu).
Notes: pour changer le thème de libnotify, cela peut être fait directement dans le gconf-editor (apps > notification-daemon), la variable theme, par défaut à "ubuntu", peut être mise à "normal".
J'utilise beaucoup jconsole pour monitorer certaines de mes applications Java, notamment quand elles traitent beaucoup de données et consomment une proportion non négligeable de la RAM disponible sur la machine.
Sur mon portable du boulot équipé d'un
JDK 1.6.0_10-beta l'outil jconsole fonctionne parfaitement, mais sur ma machine perso équipée d'un
JDK 1.6.0_10-rc, jconsole n'arrive pas à se connecter au process Java...
Après installation d'une 1.6.0_10-beta à la place, et ça fonctionne... étrange...
Pour un petit projet j'ai intégré
une grammaire XSD de DublinCore avec l'élément import dans une grammaire perso issue de mes travaux,
imageLibrary.xsd.
A propos: une ressource documentaire très intéressante sur les XSD est:
http://www.datypic.com/books/defxmlschema/chapter04.html.
Dans le cas présent, c'est une application Web de recherche d'imagette de livre depuis les AWS, Amazon Web Services, puis Ajout ou Retrait d'une imagette à un profil de lecture. Les imagettes sont stockées en local sur le serveur, et pour chaque profil je construis un fichier
XML contenant les informations sur les imagettes (metadata physiques, i.e. location, size etc.) ainsi que les données relatives au document obtenues par les AWS et intégrées dans des éléments
XML DublinCore (creator, title etc.).
Je génère des pages
XHTML qui contiennent mes éléments DublinCore marshallés, il faut ainsi que JAXB génère des fragments
XML et non un document
XML complet.
Ainsi pour faire en sorte que JAXB ne génère pas l'
instruction processing du document
XML lors de l'opération de marshalling, il faut utiliser la propriété suivante:
m.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
m étant un Marshaller.
Autre point: bien que j'ai spécifié que mon élément contenant mes données DublinCore comme élément du document
XML, (i.e. il fait partie des éléments déclarés dans ma XSD), JAXB ne veut pas marshaller uniquement cet élément.
Une exception est levée disant qu'il manque l'annotation @XmlRootElement dans la classe de binding de cet élément.
Ce post sur java.net en parle mais ne répond pas vraiment au problème.
Ajouter cette annotation à la classe de binding résout le problème mais l'élément étant déclaré dans ma XSD, cette modification ne devrait pas être nécessaire...
Cependant, pour Unmarshaller un fragment
XML, on peut passer par un
XMLStreamReader, et passer cet objet à l'unmarshaller (une rapide
overview), on doit pouvoir en faire de même pour le marshalling.
Mémo de l'installation sur mon serveur: Je ne veux pas installer les paquets tomcat-5.5, tomcat5.5-admin et tomcat5.5-webapps présent dans les dépôts Ubuntu. Car avec ces paquets:
- le fichier de configuration du service tomcat est
/etc/default/tomcat.
- les fichiers
server.xml et web.xml se trouvent dans /etc/tomcat, mais le reste des fichiers de configuration sont dans /var/lib/tomcat.
- les webapps sont dans
/var/lib/tomcat/webapps, mais l'application Tomcat se trouve dans /usr/share/tomcat.
- on a un script de gestion du service
/etc/init.d/tomcat.
Cette intégration dans le système peut être intéressante sous certains abords, mais je n'en ai pas l'utilité et puis, c'est surtout pas pratique du tout. Il est beaucoup plus propre d'installer manuellement un Tomcat dans
/home/tomcat, et donc sur la partition
/home du système sous l'utilisateur et le groupe
www-data. Ainsi, ça ne pourri pas
/usr, et de plus les applications Web à déployer peuvent être volumineuses, ou si une application Web écrit des données dans son
webContent (le répertoire où elle est déployée), c'est pas très pro. sans doute, mais c'est pratique. Pour créer un service système pour un Tomcat installé manuellement, il suffit d'écrire un fichier
/etc/init.d/tomcat tel que:
# Tomcat auto-start
#
# description: Auto-starts tomcat
# processname: tomcat
# pidfile: /var/run/tomcat.pid
export JAVA_HOME=/opt/jdk1.6.0_10
case "$1" in
start)
start-stop-daemon --start --chuid www-data:www-data --exec /home/tomcat/apache-tomcat-6.0.16/bin/startup.sh -- -d -r /home/tomcat/apache-tomcat-6.0.16/
;;
stop)
#start-stop-daemon --stop --exec /home/tomcat/apache-tomcat-6.0.16/bin/shutdown.sh
/home/tomcat/apache-tomcat-6.0.16/bin/shutdown.sh
;;
restart)
$0 stop
$0 start
;;
esac
exit 0
Il faut disposer les droits idoines sur le fichier:
chmod 755 /etc/init.d/tomcat
Puis l'ajouter aux scripts systèmes:
ln -s /etc/init.d/tomcat /etc/rc1.d/K99tomcat
ln -s /etc/init.d/tomcat /etc/rc2.d/S99tomcat
L'étape suivante pour une installation qui va bien, est d'utiliser le
connecteur Tomcat jk_connector. Pour Apache, c'est le module mod_jk pour accéder à Tomcat depuis Apache, et ainsi l'abstraire du port 8080 pour accéder aux applications web déployées sous Tomcat. Ce qui en plus d'être plus simple et pratique dans l'écriture des
URL, peut s'avérer nécessaire, par exemple si vous faites un appel à une servlet depuis un script
PHP qui est lui, hébergé sur un autre serveur sur lequel la création d'une socket sur un port autre que 80 est interdit (par exemple, les serveurs de free.fr). Pour mod_jk, installer le paquet libapache2-mod-jk, et éditer le fichier
/etc/apache2/mods-available/jk.load:
LoadModule jk_module /usr/lib/apache2/modules/mod_jk.so
JkLogFile /var/log/apache2/mod_jk.log
JkLogLevel debug
JkLogStampFormat "[%a %b %d %H:%M:%S %Y] "
JkMount / worker1
JkMount /* worker1
Les JkMount sont les points de montages des applications Web que vous voulez monter. Editer ensuite le fichier
/etc/apache2/workers.properties
:
workers.tomcat_home=/home/tomcat/apache-tomcat-6.0.16
workers.java_home=/opt/jdk1.6.0_10
ps=/
worker.list=worker1
worker.worker1.port=8009
worker.worker1.host=localhost
worker.worker1.type=ajp13
worker.worker1.lbfactor=1
Il ne suffit plus qu'a redémarrer Apache. Cette configuration fonctionne bien, mais n'est pas sensitive aux sous-domaines éventuels de votre configuration d'Apache, ainsi
http://trevize.net/Bouquinoscope et
http://njames.trevize.net/Bouquinoscope pointe vers la même webapp Tomcat. Une solution assez simple est de créer un VirtualHost pour notre Tomcat, par exemple
http://webapps.trevize.net, pour cela éditer le fichier worker.properties:
workers.tomcat_home=/home/tomcat/apache-tomcat-6.0.16
workers.java_home=/opt/jdk1.6.0_10
ps=/
worker.list=worker1
worker.webapps.port=8009
worker.webapps.host=localhost
worker.webapps.type=ajp13
worker.webapps.lbfactor=1
Commentez les points de montage JkMount indiqués précédemment dans
/etc/apache2/mods-available/jk.load. Pour finir, il suffit d'ajouter le VirtualHost en éditant un fichier
/etc/apache2/sites-available/webapps:
<VirtualHost *:80>
ServerAdmin nicolas.james@gmail.com
ServerName webapps.trevize.net
# Send servlet for context /servlets-examples to worker named worker1
JkMount /* worker1
# Send JSPs for context /jsp-examples to worker named worker1
JkMount /*.jsp worker1
</VirtualHost>
Il ne suffit plus qu'a monter le site avec
a2ensite webapps, et redémarrer Apache. Configuré ainsi, Tomcat est accessible uniquement sur
http://webapps.trevize.net. Pour que l'accès aux webapps sur le site soit plus eye-candy, un coup d'
URL-rewriting avec le module
rewrite pourra par exemple faire pointer
http://trevize.net/Bouquinoscope vers
http://webapps.trevize.net/Bouquinoscope.
J'ai expérimenté le problème intéressant des modes headless/headful en Java en codant une application de crawling de GoogleImage.
Mon application crawl les pages de résultats de GoogleImage, télécharge les images proposées dans les résultats pour les stocker en local, et renvoie une page contenant des miniatures des images retrouvées.
Au moment de stocker les images, j'utilise le programme
convert du projet ImageMagick pour les retailler, et JIU, Java Imaging Utilities, pour obtenir leur résolution.
Le projet est une servlet sur une machine distante où X est installé, mais est tomcat est lancé dans une session non X.
JIU semble avoir besoin de la variable DISPLAY, car l'exception suivante est levée:
java.awt.HeadlessException:
No X11 DISPLAY variable was set, but this program performed an operation which requires it.
java.awt.GraphicsEnvironment.checkHeadless(Unknown Source)
java.awt.Window.(Unknown Source)
java.awt.Frame.(Unknown Source)
java.awt.Frame.(Unknown Source)
net.sourceforge.jiu.gui.awt.ToolkitLoader.load(ToolkitLoader.java:88)
net.sourceforge.jiu.gui.awt.ToolkitLoader.loadAsRgb24Image(ToolkitLoader.java:114)
net.trevize.Bouquinoscope.processQuery(Bouquinoscope.java:651)
net.trevize.Bouquinoscope.doPost(Bouquinoscope.java:298)
javax.servlet.http.HttpServlet.service(HttpServlet.java:710)
javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
D'après
ce forum, c'est un problème rencontré aussi avec JMF, la solution est de préciser la propriété système demandant à Java d'utiliser le mode headless:
ou seulement pour le Toolkit AWT:
awt.toolkit=sun.awt.HeadlessToolkit
mais cela ne peut fonctionner dans mon projet car j'utilise dans mon code la classe ToolkitLoader de JIU qui, apparement, nécessite un mode headful.
Du coup, pour obtenir la résolution j'utilise un
java.awt.Image obtenu par
ImageIO en mode headless.
Pour plus d'info sur les mode headless et headful de Java, voir
ce tutoriel.