ActiveMQ

jeudi 29 novembre 2007

ActiveMQ est un broker de messages open source qui peut se présenter comme une bonne solution alternative à WebSphere MQ

Installation

  • Télécharger la dernière version http://activemq.apache.org/download.html
  • Diziper dans le répertoire de votre choix %ACTIVEMQ_HOME%
  • Démarrer le script %ACTIVEMQ_HOME%/bin/activemq.bat
  • Vérifier l’ouverture du port d’écoute:
netstat -an|find “61616” (sous Windows)

Création d’un consommateur de messages

Deux types de consommation de messages sont possibles:

  • Dans cet exemple, je présente le mode publication / abonnement, le consommateur ne recoit qu’un message et se ferme:
package com.jfhelie.activemq;
import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
public class SimpleConsumer implements MessageListener, ExceptionListener {
	private String user = ActiveMQConnection.DEFAULT_USER;
	private String password = ActiveMQConnection.DEFAULT_PASSWORD;
	private String url = ActiveMQConnection.DEFAULT_BROKER_URL;
	private String subject = “TOOL.DEFAULT”;
	private boolean transacted = false;
	private Connection connection;
	private Session session;
	private MessageConsumer consumer;
	public static void main(String[] args) throws Exception {
		SimpleConsumer simpleConsumer = new SimpleConsumer();
		simpleConsumer.run();
	}
	public void run() throws Exception {
		// Create the connection
		ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
				user, password, url);
		connection = connectionFactory.createConnection();
		connection.setExceptionListener(this);
		connection.start();
		// Create the session
		session = connection
				.createSession(transacted, Session.AUTO_ACKNOWLEDGE);
		Destination destination = session.createTopic(subject);
		// Create the consumer
		consumer = session.createConsumer(destination);
		consumer.setMessageListener(this);
		System.out.println(“Consommateur de messages démarré”);
	}
	public void onMessage(Message message) {
		if (message instanceof TextMessage) {
			TextMessage txtMsg = (TextMessage) message;
			try {
				System.out.println(txtMsg.getText());
				consumer.close();
				session.close();
				connection.close();
			} catch (JMSException e) {
				e.printStackTrace();
			}
		}
	}
	public void onException(JMSException exception) {
		System.out.println(exception.getMessage());
	}
}

Création d’un producteur de messages

  • voici un exemple de producteur simple:
package com.jfhelie.activemq;
import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
public class SimpleProducer {
	private String user = ActiveMQConnection.DEFAULT_USER;
	private String password = ActiveMQConnection.DEFAULT_PASSWORD;
	private String url = ActiveMQConnection.DEFAULT_BROKER_URL;
	private String subject = “TOOL.DEFAULT”;
	private boolean transacted = false;
	private Destination destination;
	public static void main(String[] args) throws Exception {
		SimpleProducer producer = new SimpleProducer();
		producer.run();
	}
	public void run() throws Exception {
		// Create the connection
		ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(user, password, url);			
		Connection connection = connectionFactory.createConnection();
		connection.start();
		// Create the session
		Session session = connection.createSession(transacted, Session.AUTO_ACKNOWLEDGE);
		destination = session.createTopic(subject);
		// Create the producer
		MessageProducer producer = session.createProducer(destination);
		producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
		// Create the message
		TextMessage message = session.createTextMessage(“Hello world”);
		// Send the message
		producer.send(message);
		System.out.println(“Message envoyé par le producteur”);
		producer.close();
		session.close();
		connection.close();
	}
}

Ressources

ApacheSynapseWSSecurity

mardi 27 novembre 2007

Les web services présentent l’avantage d’être plus simple à développer que les EJBs, néanmoins certains aspects comme la sécurité peuvent devenir vraiment complexes. Dans la plupart des cas, il faut pouvoir découpler le développement du web service de la sécurité. La sécurité doit être vue comme de l’intégration et non comme du développement logiciel, elle doit être transverse, c’est à dire qu’ajouter de la sécurité pour un web service ne doit pas nécessiter un redéveloppement du web service. Cette problématique technique doit être gérée par un ESB au même titre que la répartition de charges et le cache. Apache Synapse permet facilement de gérer la sécurité en mode proxy. L’article suivant s’attachera à montrer comment sécuriser les web services à l’aide d’Apache Synapse. Le web service utilisé est celui présenté dans l’article Introduction à Axis 2 et Apache Synapse est présenté dans l’article Introduction Apache Synapse

Mise en place d’Apache Synapse en mode proxy

  • Dans un premier temps, il faut pouvoir mettre en place Apache Synapse en mode proxy au dessus d’un serveur Axis 2 existant.
  • Par exemple, le serveur Axis 2 écoute sur le port 8090 (défini dans %AXIS2_HOME%/conf/axis2.xml)
  • La mise en place d’Apache Synapse en mode proxy au dessus du serveur Axis 2 se fait de la façon suivante dans le fichier %SYNAPSE_HOME%/conf/synapse.xml:
<definitions xmlns=“http://ws.apache.org/ns/synapse”>
<proxy name=“usermanager”>
 <target>
  <endpoint>
   <address uri=“http://localhost:8090/axis2/services/usermanager”>
   </address>
  </endpoint>
  <outSequence>
   <send/>
  </outSequence>
 </target>
</proxy>
</definitions>
  • Démarrer synapse en lancant le script %SYNAPSE_HOME%/bin/synapse.bat
  • Mettre à jour le client Axis 2 de l’exemple pour appeler le web service Axis 2:
UsermanagerStub stub = new UsermanagerStub(
  “http://localhost:8080/soap/usermanager”);
GetUser request = new GetUser();
request.setEmail(“user1@gmail.com”);
GetUserResponse response = stub.getUser(request);
UsermanagerStub.User user = response.get_return();
System.out.println(user.getFirstName() + “ ” + user.getLastName() + “ ”
  + user.getEmail());

Création d’un entrepôt de clés

L’outil keytool fournit par Sun permet de gérer des entrepôts de clés privées. il est préférable d’utiliser l’algorithme RSA car certaines librairies java ne supportent pas DSA. De plus par défaut, le hachage est fait par défaut en MD5 qui n’est plus sécurisé. Il faut donc utiliser SHA1 pour la signature:

“%JAVA_HOME%\bin\keytool.exe” -genkey -keystore store.jks -alias axis -keyalg RSA -sigalg SHA1withRSA
Tapez le mot de passe du Keystore :  secret
Quels sont vos prénom et nom ?
  [Unknown] : 
Quel est le nom de votre unité organisationnelle ?
  [Unknown] :
Quelle est le nom de votre organisation ?
  [Unknown] :
Quel est le nom de votre ville de résidence ?
  [Unknown] :
Quel est le nom de votre état ou province ?
  [Unknown] :
Quel est le code de pays à deux lettres pour cette unité ?
  [Unknown] :
Est-ce CN=Jeff Helie, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown ?
  [non] :  oui
Spécifiez le mot de passe de la clé pour <axis>
        (appuyez sur Entrée s’il s’agit du mot de passe du Keystore) :  axispwd

On a donc généré un keystore store.jks sécurisé par le mot de passe secret avec une nouvelle entrée (alias axis) correspond à une nouvelle paire de clé privée / publique. La clé publique est encapsulé dans un certificat X509 pour l’utilisateur axis avec comme mot de passe axispwd.

Définition des politiques de sécurité avec WS-Policy

  • Un client de web service ne peut pas accéder à un web service si celui ci a défini des règles demandant à ce que les appels soient cryptés et signés d’une certaine façon. WS-Policy permet de définir les règles de consommation d’un web service par un client.
  • Un exemple de politique de service:
<wsp:Policy
xmlns:wsp=“http://schemas.xmlsoap.org/ws/2004/09/policy”
xmlns:sec=“http://schemas.xmlsoap.org/ws/2002/12/secext” >
  <wsp:ExactlyOne>
    <wsp:All>
      <sec:SecurityToken>
        <sec:TokenType>sec:X509v3</sec:TokenType>
      </sec:SecurityToken>
    </wsp:All>
  </wsp:ExactlyOne>
</wsp:Policy>
  • WS-Policy ajoute une sécurité supplémentaire à WS-Security car un client de web service ne peut pas appeler un web service sans respecter les règles de sécurité établis dans la politique WS-Security (WS-SecurityPolicy)
  • Pour définir une politique de sécurité pour les web services dans Apache Synapse, créer un fichier policy.xml dans %SYNAPSE_HOME%/repository/conf avec le contenu suivant:
<wsp:Policy wsu:Id=“SigEncr” xmlns:wsu=“http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd” xmlns:wsp=“http://schemas.xmlsoap.org/ws/2004/09/policy”>
 <wsp:ExactlyOne>
  <wsp:All>
   <sp:AsymmetricBinding xmlns:sp=“http://schemas.xmlsoap.org/ws/2005/07/securitypolicy”>
    <wsp:Policy>
     <sp:InitiatorToken>
      <wsp:Policy>
       <sp:X509Token sp:IncludeToken=“http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient”>
        <wsp:Policy>
         <sp:WssX509V3Token10/>
        </wsp:Policy>
       </sp:X509Token>
      </wsp:Policy>
     </sp:InitiatorToken>
     <sp:RecipientToken>
      <wsp:Policy>
       <sp:X509Token sp:IncludeToken=“http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/Never”>
        <wsp:Policy>
         <sp:WssX509V3Token10/>
        </wsp:Policy>
       </sp:X509Token>
      </wsp:Policy>
     </sp:RecipientToken>
     <sp:AlgorithmSuite>
      <wsp:Policy>
       <sp:Basic256/>
      </wsp:Policy>
     </sp:AlgorithmSuite>
     <sp:Layout>
      <wsp:Policy>
       <sp:Strict/>
      </wsp:Policy>
     </sp:Layout>
     <sp:IncludeTimestamp/>
     <sp:OnlySignEntireHeadersAndBody/>
    </wsp:Policy>
   </sp:AsymmetricBinding>
   <sp:Wss10 xmlns:sp=“http://schemas.xmlsoap.org/ws/2005/07/securitypolicy”>
    <wsp:Policy>
     <sp:MustSupportRefKeyIdentifier/>
     <sp:MustSupportRefIssuerSerial/>
    </wsp:Policy>
   </sp:Wss10>
   <sp:SignedParts xmlns:sp=“http://schemas.xmlsoap.org/ws/2005/07/securitypolicy”>
    <sp:Body/>
   </sp:SignedParts>
   <sp:EncryptedParts xmlns:sp=“http://schemas.xmlsoap.org/ws/2005/07/securitypolicy”>
    <sp:Body/>
   </sp:EncryptedParts>
   <ramp:RampartConfig xmlns:ramp=“http://ws.apache.org/rampart/policy”> 
    <ramp:user>axis</ramp:user>
    <ramp:encryptionUser>axis</ramp:encryptionUser>
    <ramp:passwordCallbackClass>fr.jfhelie.axis2.common.PWCallback</ramp:passwordCallbackClass>
    <ramp:signatureCrypto>
     <ramp:crypto provider=“org.apache.ws.security.components.crypto.Merlin”>
      <ramp:property name=“org.apache.ws.security.crypto.merlin.keystore.type”>JKS</ramp:property>
      <ramp:property name=“org.apache.ws.security.crypto.merlin.file”>repository/conf/store.jks</ramp:property>
      <ramp:property name=“org.apache.ws.security.crypto.merlin.keystore.password”>secret</ramp:property>
     </ramp:crypto>
    </ramp:signatureCrypto>
    <ramp:encryptionCypto>
     <ramp:crypto provider=“org.apache.ws.security.components.crypto.Merlin”>
      <ramp:property name=“org.apache.ws.security.crypto.merlin.keystore.type”>JKS</ramp:property>
      <ramp:property name=“org.apache.ws.security.crypto.merlin.file”>repository/conf/store.jks</ramp:property>
      <ramp:property name=“org.apache.ws.security.crypto.merlin.keystore.password”>secret</ramp:property>
     </ramp:crypto>
    </ramp:encryptionCypto>
   </ramp:RampartConfig>
  </wsp:All>
 </wsp:ExactlyOne>
</wsp:Policy>
  • <sp:AsymmetricBinding> permet de définir une sécurité de type X509 contenu dans le message contrairement à <sp:TransportBinding> qui définit une sécurité basé sur le transport
  • <sp:InitiatorToken> permet de définir le type de jeton pour le client du web service
  • <sp:RecipientToken> permet de définir le type de jeton pour le web service
  • <sp:AlgorithmSuite> permet de définir le type d’algorithme requis, par exemple <sp:Basic256/> pour du RSA 256 bits et du SHA1
  • <sp:Strict/> indique que les règles sont ordonnées
  • <sp:IncludeTimestamp/> indique qu’un timestamp sur le message est requis
  • <sp:OnlySignEntireHeadersAndBody/> indique que la signature se fait sur l’entête et le corps du message seulement
  • <sp:MustSupportRefKeyIdentifier/>
  • <sp:MustSupportRefIssuerSerial/>
  • Dans <sp:SignedParts>, on indique que le body est signé
  • Dans <sp:EncryptedParts>, on indique que le body est crypté
  • Dans <ramp:RampartConfig>, on indique la configuration rempart

Mise en place de WS-Security sur Apache Synapse

Configuration du fichier synapse.xml

  • Le fichier %SYNAPSE_HOME%/repository/conf/synapse.xml possède le contenu suivant:
<definitions xmlns=“http://ws.apache.org/ns/synapse”>
<localEntry key=“sec_policy” src=“file:repository/conf/policy.xml”/>
<proxy name=“usermanager”>
 <target>
  <inSequence>
   <header name=“wsse:Security” action=“remove” xmlns:wsse=“http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd”/>
   <send>
    <endpoint>
     <address uri=“http://localhost:8090/axis2/services/usermanager”/>
    </endpoint>
   </send>
  </inSequence>
  <outSequence>
   <send/>
  </outSequence>
 </target>
 <policy key=“sec_policy”/>
 <enableSec/>
</proxy>
</definitions>
  • Le tag <localEntry> précise le chemin du fichier de définition des règles de sécurité
  • Le tag <policy> indique que le médiateur de gestion des politiques de sécurité est initialisé
  • Le tag <enableSec/> active la gestion de la sécurité

Ajout du code permettant la gestion des mots de passe

  • Créer la classe fr.jfhelie.axis2.common.PWCallback et déployer le jar contenant cette classe dans le répertoire %SYNAPSE_HOME%/lib:
package fr.jfhelie.axis2.common;
import org.apache.ws.security.WSPasswordCallback;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import java.io.IOException;
public class PWCallback implements CallbackHandler {
 public void handle(Callback[] callbacks) throws IOException,
   UnsupportedCallbackException {
  for (int i = 0; i < callbacks.length; i++) {
   if (callbacks[i] instanceof WSPasswordCallback) {
    WSPasswordCallback pc = (WSPasswordCallback) callbacks[i];
    if (pc.getIdentifer().equals(“axis”)) {
     pc.setPassword(“axispwd”);
    } else {
     throw new IllegalStateException(“Unrecognized Identifier:” + pc.getIdentifer());
    }
   } else {
    throw new UnsupportedCallbackException(callbacks[i],
      “Unrecognized Callback”);
   }
  }
 }
}

Modifier le client pour activer la sécurité

  • Le code de l’exemple repris est celui utilisé dans l’article Introduction à Axis 2
  • Ajouter le répertoire où se trouve les modules addressing.mar et rampart.mar en copiant le répertoire modules dans le projet eclipse client se trouvant dans %SYNAPSE_HOME%/repository
  • Modifier le code source du client comme suit, où le répertoire resources/repository correspond au répertoire où se trouvent les modules (modules/addressing et modules/rempart)
ConfigurationContext configContext = ConfigurationContextFactory.createConfigurationContextFromFileSystem(“resources/repository”,null);
UsermanagerStub stub = new UsermanagerStub(configContext,“http://localhost:8080/soap/usermanager”);
// activation du module addressing
stub._getServiceClient().engageModule(“addressing”);
// activation du module rampart
stub._getServiceClient().engageModule(“rampart”);
// activation des polices de sécurité
stub._getServiceClient().getOptions().setProperty(RampartMessageData.KEY_RAMPART_POLICY,
   loadPolicy(“./resources/policy.xml”));
GetUser request = new GetUser();
request.setEmail(“user1@gmail.com”);
GetUserResponse response = stub.getUser(request);
UsermanagerStub.User user = response.get_return();
System.out.println(user.getFirstName() + “ ” + user.getLastName() + “ ”
  + user.getEmail());
// méthode permettant de charger les polices à partir de neethi, une librairie java facilitant la gestion des polices WS-Policy
private static Policy loadPolicy(String xmlPath) throws Exception {
 StAXOMBuilder builder = new StAXOMBuilder(xmlPath);
 return PolicyEngine.getPolicy(builder.getDocumentElement());
}
  • Le fichier policy.xml est plus ou moins le même que celui positionné sur Apache Synapse, il faut juste vérifier le bon chemin du fichier store.jks
  • Ajouter dans le classpath les librairies d’Apache Synapse situées dans %SYNAPSE_HOME%/lib
  • Lancer le client du web service, utiliser TCPMon pour vérifier le contenu des messages entre le client et Apache Synapse puis entre Apache Synapse et Axis 2

Ressources

MavenProfiles

jeudi 22 novembre 2007

Les profiles Maven permettent de gérer des contextes différents de build en fonction de l’environnement: developpement, intégration, production grâce à des profiles. Plusieurs méthodes de définitions des profiles Maven sont possibles, soit dans le fichier settings.xml, soit dans le fichier pom.xml; ici je ne présenterais que dans le fichier settings.xml. Dans mon cas, j’ai besoin de charger un fichier de propriétés dont le chemin est différent en fonction de l’environnement. Pour réussir ce peu, j’utilise les profiles maven et les filtres.

  • Mon fichier applicationContext.xml est dans le répertoire src/main/resources avec la variable prop.path que je souhaite modifier en fonction de mon environnement:
<beans>
	<bean id=“propertyConfigurer” class=“org.springframework.beans.factory.config.PropertyPlaceholderConfigurer”>
		<property name=“location”>
			<value>${prop.path}</value>
		</property>
	</bean>
</beans>
  • Dans mon fichier web.xml, je déclare le chemin (ici classpath) du fichier applicationContext.xml car par défaut le chemin est dans WEB-INF:
<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
	<listener-class>
		org.springframework.web.context.ContextLoaderListener
	</listener-class>
</listener>
  • J’active dans le fichier pom.xml le filtering sur les ressources dans /src/main/resources:
<resources>
	<resource>
		<directory>src/main/resources</directory>
		<filtering>true</filtering>
	</resource>
</resources>
  • Dans mon fichier de configuration Maven settings.xml qui se trouve dans C:maven-2.0.7conf par exemple, je déclare mes différents profiles:
<profiles>
<profile>
	<id>development</id>
	<activation>
	  <property>
		<name>environment</name>
		<value>development</value>
	  </property>
	</activation>
	<properties>
		<prop.path>file:///c:/prop.properties</prop.path>
	</properties>
</profile>
<profile>
	<id>test</id>
	<activation>
	  <property>
		<name>environment</name>
		<value>test</value>
	  </property>
	</activation>
	<properties>
		<prop.path>file:////usr/local/prop.properties</prop.path>
	</properties>
</profile>
</profiles>
  • Je lance la commande mvn -Denvironment=development
  • Le fichier applicationContext.xml qui se trouvent dans src/main/resources est copié dans target/classes mais la valeur ${prop.path} est remplacé par la valeur définie dans la balise <prop.path> au niveau du profile.

Ressources

Axis2AXIOM

lundi 19 novembre 2007

AXis Object Model (AXIOM) est un modèle léger de représentation XML. Il a été créé pour réduire la consommation mémoire et augmenter les performances.

Introduction

AXIOM fait suite à deux autres traitements XML:

  • DOM: Approche en arbre qui charge tout l’arbre XML en mémoire, c’était le mode de fonctionnement d’Apache SOAP
  • SAX: Approche basée sur les événements qui est difficile à maitriser, mode de fonctionnement d’Axis 1 Le traitement Streaming API for XML (StaX) sur lequel se base Axis 2 est un traitement du type arbre comme DOM mais l’arbre n’est chargé que quand cela est nécessaire et sur une partie des données.

AXIOM vient avec deux types d’implémentation:

  • une implémentation basée sur des listes chaînées
  • une implémentation DOOM (Document Object Model Over Object Model) qui fournit une interface DOM au dessus d’AXIOM

Exemples

Fichier XML

  • Lecture du fichier suivant:
<ns1:UserInfo xmlns:ns1=“http://jfhelie/axiomtest”>
	<ns1:User>
		<Name>Rod Johnson</Name>
		<Address type=“Home”>
			<City>New York</City>
			<Country>USA</Country>
		</Address>
	</ns1:User>
	<ns1:User>
		<Name>Matt Raible</Name>
		<Address type=“Home”>
			<City>Denver</City>
			<Country>USA</Country>
		</Address>
	</ns1:User>
</ns1:UserInfo>
  • Lecture d’un fichier XML avec AXIOM:
public void testRead() throws Exception {
	StAXOMBuilder builder = new StAXOMBuilder(getClass().getResourceAsStream(“/axiom1.xml”));
	OMElement documentElement = builder.getDocumentElement();
	QName qName = new QName(“http://jfhelie/axiomtest”, “User”);
	for(Iterator it=documentElement.getChildrenWithName(qName);it.hasNext();) {
		OMElement user = (OMElement) it.next();
		// Récupération d’un élement
		OMElement name = user.getFirstChildWithName(new QName(“Name”));
		System.out.println(“Name=”+name.getText());
		OMElement address = user.getFirstChildWithName(new QName(“Address”));
		// Récupération d’un attribut
		System.out.println(address.getAttributeValue(new QName(“type”)));
	}
}
  • Ecriture d’un fichier XML sur la console:
public void testWrite() throws Exception {
	XMLStreamWriter xmlStreamWriter = XMLOutputFactory.newInstance()
			.createXMLStreamWriter(System.out);
	StAXOMBuilder builder = new StAXOMBuilder(getClass()
			.getResourceAsStream(“/axiom1.xml”));
	OMElement documentElement = builder.getDocumentElement();
	documentElement.serialize(xmlStreamWriter);
}

Fichier SOAP

AXIOM fournit une API permettant de parser facilement des fichiers XML de type SOAP.

  • Parsing du fichier soap suivant:
<soapenv:Envelope xmlns:soapenv=“http://schemas.xmlsoap.org/soap/envelope/”
    xmlns:wsa=“http://schemas.xmlsoap.org/ws/2004/03/addressing”>
    <soapenv:Header>
        <wsa:MessageID soapenv:mustUnderstand=“0”>urn:uuid:920C5190-0B8F-11D9-8CED-F22EDEEBF7E5</wsa:MessageID>
        <wsa:To soapenv:mustUnderstand=“0”>http://localhost:8081/axis/services/BankPort</wsa:To>
        <wsa:Action>urn:DummyWSAAction</wsa:Action>
        <wsa:From soapenv:mustUnderstand=“0”>
            <Address>http://schemas.xmlsoap.org/ws/2004/03/addressing/role/anonymous</Address>
        </wsa:From>
    </soapenv:Header>
    <soapenv:Body>
        <axis2:echoVoid xmlns:axis2=“http://ws.apache.org/axis2”/>
    </soapenv:Body>
</soapenv:Envelope>
  • Affichage de la valeur de l’élement MessageId dans le header Soap:
public void testSoap() throws Exception {
	FileReader soapFileReader = new FileReader(“resources/soap.xml”);
	XMLStreamReader parser = XMLInputFactory.newInstance()
			.createXMLStreamReader(soapFileReader);
	StAXSOAPModelBuilder builder = new StAXSOAPModelBuilder(parser,null);
	SOAPEnvelope envelope = (SOAPEnvelope) builder.getDocumentElement();
	System.out.println(envelope.getHeader().getFirstChildWithName(
			new QName(“http://schemas.xmlsoap.org/ws/2004/03/addressing”,
					“MessageID”)).getText().trim());
}

Ressources

FastTDD

vendredi 16 novembre 2007

L’idée du FastTDD est de coder des applications le plus rapidement possible en suivant le principe du TDD, Test first, Code next. Le constat vient du fait que beaucoup de tâches prennent beaucoup de temps alors qu’elles n’apportent pas de valeur. De plus, l’idée est de trouver des astuces permettant de coder en continu, c’est à dire sans interruptions, comme par exemple par l’utilisation des raccourcis clavier. Pour gagner en efficacité, il est nécessaire d’éviter les interruptions pour maintenir sa concentration, c’est un des principes du Tai Chi Chuan, un art martial chinois qui consiste à continuer le geste sans interruption. J’essayerai au fur et à mesure des articles de trouver des astuces pour développer plus rapidement.

Ce premier article explique comment utiliser certains raccourcis Eclipse sans utiliser la souris.

  • Créer un projet Java: Alt-F-N-R ou Alt-F-N-Enter
  • Créer un répertoire source: Alt-F-N-S
  • Créer un package: Alt-F-N-P
  • Créer une classe JUnit: Alt-F-N-J
  • Correction rapide (import de junit.jar): Ctrl-1 sur la ligne en erreur
  • Créer la méthode setUp: setU + Ctrl-Espace
  • Créer une méthode de test: test + Ctrl-Espace
  • Création d’une classe, méthode, … en correction rapide: Ctrl-1 sur la ligne en erreur
  • Se déplacer entre fichiers: Ctrl-F6, Ctrl-PageBas ou Ctrl-PageHaut
  • Lancer un test unitaire la première fois: Alt-Shift-X-T
  • Lancer un test unitaire les fois suivantes: Ctrl-F11
  • Créer les méthodes get et set sur un attribut: Ctrl-Espace
  • Un raccourci que je n’ai pas utilisé qui permet de passer en mode plein écran pour l’éditeur est Ctrl-M et de même pour réduire.

Log4JNagios

Nagios est un outil de supervision open source très utilisé par les centres de production informatique des entreprises. Dans le cadre des applications J2EE ou de scripts batch, il est intéressant de pouvoir envoyer des notifications d’erreurs à Nagios. C’est ce que permet NagiosAppender pour Log4J.

<?xml version=“1.0” encoding=“UTF-8” ?>
<!DOCTYPE log4j:configuration SYSTEM “log4j.dtd”>
<log4j:configuration>
   <appender name=“NAGIOS” class=“org.apache.log4j.nagios.NagiosAppender”>
      <param name=“Host” value=“192.168.0.3”/>
      <param name=“Port” value=“5667”/>
      <param name=“ConfigFile” value=“send_xor.cfg”/>
      <param name=“ServiceNameDefault” value=“log4j”/>
      <param name=“useMDCServiceName” value=“false”/>
      <param name=“MDCServiceNameKey” value=“log4j”/>
      <param name=“useShortHostName” value=“false”/>
      <param name=“useMDCHostName” value=“false”/>
      <param name=“MDCHostNameKey” value=“windows”/>
      <!-- 
      <param name=“Log4j_Level_DEBUG”    value=“NAGIOS_UNKNOWN”/>
      <param name=“Log4j_Level_INFO”     value=“NAGIOS_OK”/>
      -->
      <param name=“Log4j_Level_WARN”     value=“NAGIOS_WARN”/>
      <param name=“Log4j_Level_ERROR”    value=“NAGIOS_FATAL”/>
      <param name=“Log4j_Level_FATAL”    value=“NAGIOS_FATAL”/>
      <param name=“IncludeFilterEnabled”    value=“false”/>
      <param name=“ExcludeFilterEnabled”    value=“false”/>
  </appender>
  <root>
       <priority value=“debug”/>
       <appender-ref ref=“NAGIOS”/>              
  </root>
</log4j:configuration>
  • Au niveau de nagios, j’ai configurer le service log4j, dans le fichier /etc/nagios/services.cfg:
define service {
        name                            log4j
        host_name                       webarizona  ; hostname de la machine à partir de laquelle je lance le script avec log4j
        active_checks_enabled           0
        use                             generic-service
        service_description             log4j
        check_command                   check_smtp  ;ne sert à rien, mais obligatoire d’avoir un check_command
}
  • Tester votre configuration:
import org.apache.log4j.Logger;
public class NagiosAppenderTest {
	private final static Logger LOG = Logger.getLogger(NagiosAppenderTest.class);
	public static void main(String args) {
		LOG.warn(“Warning log4j”);
	}
}

Nagios

Nagios est un logiciel open source permettant de surveiller le réseau, les machines et des applications. Dans le cas des applications ou des services, trois types de surveillances sont possibles:

  • Cas serveur -> client passif
    • Les commandes de vérification sont originaires du serveur nagios vers les clients passifs
    • Exemples: ping, ssh, telnet, etc
  • Cas serveur -> client actif
    • Les commandes de vérification sont encore à l’initiative du serveur nagios vers les clients mais le script est exécuté sur la machine cliente.
    • Pour fonctionner, il faut installer un serveur Nagios Remote Process Execution (NRPE)
    • Exemples: check_disk, check_users, check_load etc … .
  • Cas client -> serveur
    • Le client envoit les notifications au serveur nagios (NSCA), cette methode est utilisée pour des operations de verification relativement lourdes

Nagios Web

Installation sur Ubuntu

  • Pour installer le package nagios sur Ubuntu, rien de plus simple:
 sudo apt-get install nagios-text
  • Entrer le mot de passe pour nagiosadmin
  • Accéder à l’application avec le compte nagiosadmin et le mot de passe que vous avez saisi lors de l’installation
 http://localhost/nagios/

Configuration

La configuration de Nagios se fait de la manière suivante: ­* Définition des hôtes (ip, nom de machine, …) ­* Définition des groupes (groupes de serveurs)

  • Définition des services (templates)
  • Définition des contacts (groupes de contact, mail, …) ­ Les fichiers de configuration se font dans le répertoire /etc/nagios

Définition des hôtes

  • La définition des hôtes peut se faire dans le fichier /etc/nagios/hosts.cfg
  • Définissons un hôte générique pouvant être utilisé par la suite:
# Generic host definition template
define host {
        name                            generic-host
        register                        0
        notifications_enabled           1
        event_handler_enabled           1
        flap_detection_enabled          1
        process_perf_data               1
        retain_status_information       1
        retain_nonstatus_information    1
        check_command                   check-host-alive
        max_check_attempts              10
        notification_interval           60
        notification_period             24x7
        notification_options            d,u,r
}
  • Définissons l’hôte de la machine (ubuntu) que l’on souhaite superviser:
define host {
        host_name               ubuntu
        use                     generic-host
        alias                   serveur linux
        address                 192.168.0.4
}

Définition des groupes d’hôtes

  • La définition des groupes d’hôtes peut se faire dans le fichier /etc/nagios/hostgroups.cfg:
define hostgroup {
        hostgroup_name  servers_ubuntu
        alias           Ensemble des serveurs ubuntu
        contact_groups  helie
        members         ubuntu
}

Définition des services

  • La définition des services peut se faire dans le fichier /etc/nagios/services.cfg
  • Définissons un service générique pouvant être utilisé par la suite:
# Generic service definition template
define service {
        name                            generic-service
        register                        0
        active_checks_enabled           1
        passive_checks_enabled          1
        parallelize_check               1
        obsess_over_service             1
        check_freshness                 0
        notifications_enabled           1
        event_handler_enabled           1
        flap_detection_enabled          1
        process_perf_data               1
        retain_status_information       1
        retain_nonstatus_information    1
        is_volatile                     0
        check_period                    24x7
        max_check_attempts              3
        normal_check_interval           3
        retry_check_interval            1
        contact_groups                  helie
        notification_interval           240
        notification_period             24x7
        notification_options            c,r
}
  • Définition le service ping pour l’hôte ubuntu:
define service {
        host_name                       ubuntu
        use                             generic-service
        service_description             ping
        check_command                   check_ping!300.0,20%!500.0,60%
}

Définition des contacts

  • La définition des services peut se faire dans le fichier /etc/nagios/contacts.cfg:
define contact {
        contact_name                    jfh
        alias                           Jean-Francois Helie
        host_notification_period        24x7
        service_notification_period     24x7
        host_notification_options       d,u,r
        service_notification_options    w,u,c,r
        host_notification_commands      host-notify-by-email
        service_notification_commands   notify-by-email
        email                           heliejf@gmail.com
}

4 Définition des groupes

  • La définition des groupes de contacts peut se faire dans le fichier /etc/nagios/contactgroups.cfg:
define contactgroup {
        contactgroup_name       helie
        alias                   Groupe Helie
        members                 jfh
}

Vérification

  • Redémarrer Nagios:
 sudo /etc/init.d/nagios restart
  • L’état de l’hôte distant se vérifie à partir de la console Web gràce au menu Host Detail
  • L’état du service distant à partir de la console Web se fait gràce au menu Service Detail

Serveur NRPE

Installation sur Ubuntu

Le serveur NRPE s’installe sur la machine à superviser, il permet d’éxécuter de vérifier l’état de services sur la machine distante.

Activer les dépôts universe d’Ubuntu

  • Editer le fichier suivant:
sudo vi /etc/apt/sources.list
  • Repérer la ligne suivante:
deb http://fr.archive.ubuntu.com/ubuntu/ edgy main restricted
  • Modifier la ligne en ajoutant universe et multiverse:
 deb http://fr.archive.ubuntu.com/ubuntu/ edgy main restricted universe multiverse
  • Exécuter la mise à jour:
 sudo apt-get update

Installation sur le serveur de supervision Nagios

  • Installer le plugin NRPE sur le serveur de supervision Nagios
sudo apt-get install nagios-nrpe-plugin

Installation du serveur NRPE et des plugins Nagios sur la machine à superviser

sudo apt-get install nagios-nrpe-server
sudo apt-get install nagios-plugins

Configuration du serveur NRPE

  • Le fichier de configuration nrpe.cfg se trouve dans le répertoire /etc/nagios:
  • Modifier le fichier pour autoriser le serveur Nagios à communiquer avec le serveur NRPE:
allowed_hosts=192.168.0.3
  • Redémarrer le serveur NRPE:
sudo /etc/init.d/nagios-nrpe-server restart

Configurer un service sur Nagios utilisant NRPE

  • Par exemple, vérifions la charge CPU sur la machine distante:
define service {
        host_name                       ubuntu
        use                             generic-service
        service_description             charge CPU
        check_command                   check_nrpe_1arg!check_load
}
  • Redémarrer Nagios
 sudo /etc/init.d/nagios restart
  • Vérifier l’ajout et l’état du service dans Service Detail

Serveur NSCA

Le serveur NSCA permet à des clients de communiquer avec Nagios

Installation du serveur NSCA sur le serveur Nagios

sudo apt-get install nsca

Configuration

  • Le fichier de coniguration est /etc/nsca.cfg:
  • Modifier les machines hôtes qui peuvent accéder au serveur NSCA:
allowed_hosts=192.168.0.4
  • Un mot de passe peut être précisé pour chiffrer les flux échangés:
password=secret
  • Le mode de chiffrement se spécifie de la manière suivante:
decryption_method=1

Remarques complémentaires

  • Les commandes de vérification sont installées dans /usr/lib/nagios/plugins et des alias sont créés dans /etc/nagios-plugins
  • Dans /etc/nagios/nagios.cfg, il est possible d’ajouter des fichiers de configuration avec cfg_file
  • Dans /etc/nagios/nagios.cfg, il est possible d’ajouter un répertoire de fichiers de configuration avec cfg_dir
  • Les logs se trouvent dans /var/log/nagios/

Ressources

Unison

Un petit article sur Unison qui est un outil de synchronisation de fichiers sur Linux et Windows. Un outil qui peut s’avérer intéressant pour la sauvegarde de données personnelles. Comme rsync, il fait un différentiel de fichiers et donc est très performant. Cet article présente Unison sur Windows sinon sous Linux je vous conseille rsync.

Installation

GTK

  • Il est nécessaire d’installer gtk+ win32 pour qu’Unison fonctionne
  • Ensuite il faut ajouter le chemin de gtk+ C:\Program Files\Fichiers communs\GTK\2.0\bin dans la variable d’environnement PATH

Unison

  • Les versions sous Windows sont téléchargeables sur le lien suivant: http://alan.petitepomme.net/projets/unison/index.html
  • Téléchargeons par exemple Unison-2.26.14.zip
  • Dans le zip, on trouve deux fichiers: Unison-2.26.14 Gtk+.exe et Unison-2.26.14 Text.exe
  • Le premier est une interface graphique qui permet de configurer la synchronisation de fichiers
    • Ce qui est très amusant, c’est que l’on peut créer des profiles mais on peut les modifier ou les supprimer à partir de l’interface graphique. Pour cela, il faut effectuer les modifications dans les fichiers qui se trouvent dans C:\Documents and Settings\<votre-utilisateur>\.unison
  • le deuxième est un mode script qui peut être lancé par le plannificateur de tâches Windows.

Configuration

  • Chez moi, mon PC fixe est sous Windows et j’ai besoin de sauvegarder mes photos, j’ai déjà eu un disque dur qui a craché, heureusement j’ai pu récupérer les données avec Restorer 2000 Professional et GetDataBack for NTFS, mais maintenant j’utilise Unison pour faire une sauvegarde automatique sur un disque externe USB. Je pourrais faire des copies sur DVD mais bon ça demande de le faire régulièrement.
  • Dans C:\Documents and Settings\<votre-utilisateur>\.unison, j’ai mon fichier backup.prf:
root = D:\Photos
root = E:\backup
  • Et je lance la création d’une nouvelle tâche planifiée à partir de Démarrer>Panneau de Configuration>Tâches planifiées, je sélectionne Unison-2.26.14 Text.exe.
  • Pour fonctionner, il faut préciser à Unison de fonctionner en mode batch en lui ajoutant -batch. Allez dans les propréités de la tâche planifiée et ajouter “-batch” à la suite de la commande Exécutez
  • Et voilà !

Ressources

NTP

vendredi 9 novembre 2007

Network Time Protocol est un protocole permettant de synchroniser les horloges des systèmes informatiques à travers un réseau de paquets, dont la latence est variable. (c.f http://fr.wikipedia.org/wiki/Network_Time_Protocol)

Cet article décrit comment metre à jour l’heure et la date système via NTP.

Sur Ubuntu

  • Installer le client ntpdate:
sudo apt-get install ntpdate
  • Mettre à jour l’horloge:
 sudo /etc/network/if-up.d/ntpdate
  • Le mettre en crontab:
 sudo crontab -e
  • Ajouter le lancement toutes les heures:
@hourly /etc/network/if-up.d/ntpdate

Ressources

Apache Synapse

jeudi 8 novembre 2007

SpringMVCUpload

mardi 6 novembre 2007

Cet article décrit comment télécharger des fichiers sur le serveur à l’aide du framework Spring MVC et surtout décrit comment le tester unitairement sans utiliser des frameworks d’intégration comme Cargo.

Télécharger avec spring mvc

  • Déclarer la servlet DispatchServlet de Spring dans le fichier web.xml:
<?xml version=“1.0” encoding=“UTF-8”?>
<web-app xmlns=“http://java.sun.com/xml/ns/j2ee”
	xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”
	xsi:schemaLocation=“http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd”
	version=“2.4”>
	<display-name>springmvcupload</display-name>
	<servlet>
		<servlet-name>service</servlet-name>
		<servlet-class>
			org.springframework.web.servlet.DispatcherServlet
		</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>service</servlet-name>
		<url-pattern>/service/*</url-pattern>
	</servlet-mapping>
	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
	</welcome-file-list>
</web-app>
  • Créer un fichier service-servlet.xml:
<?xml version=“1.0” encoding=“UTF-8”?>
<!DOCTYPE beans PUBLIC “-//SPRING//DTD BEAN//EN” “http://www.springframework.org/dtd/spring-beans.dtd”>
<beans>
	<bean id=“multipartResolver”
		class=“org.springframework.web.multipart.commons.CommonsMultipartResolver”>
		<property name=“maxUploadSize” value=“2000000” />
	</bean>
	<bean name=“/handleUpload”
		class=“fr.jfhelie.springmvcupload.HandleUploadController”>
		<property name=“tempDirectory” value=“c:/temp” />
	</bean>
</beans>
  • Le bean multipartResolver permet de préciser la taille maximale du téléchargement acceptée
  • Créer le controlleur HandleUploadController:
package fr.jfhelie.springmvcupload;
import java.io.File;
import java.io.FileOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
public class HandleUploadController extends AbstractController implements
		InitializingBean {
	private String tempDirectory;
	public void afterPropertiesSet() throws Exception {
		if (tempDirectory == null) {
			throw new IllegalArgumentException(“Must specify destinationDir”);
		} else if (!new File(tempDirectory).isDirectory() && !new File(tempDirectory).mkdir()) {
			throw new IllegalArgumentException(tempDirectory + “ is not a ”
					+ “directory, or it couldn’t be created”);
		}
	}
	protected ModelAndView handleRequestInternal(HttpServletRequest req,
			HttpServletResponse res) throws Exception {
		res.setContentType(“text/plain”);
		if (!(req instanceof MultipartHttpServletRequest)) {
			res.sendError(HttpServletResponse.SC_BAD_REQUEST,
					“Expected multipart request”);
			return null;
		}
		MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) req;
		MultipartFile file = multipartRequest.getFile(“uploaded”);
		File destination = File.createTempFile(“file”, “uploaded”,
				new File(tempDirectory));
		FileCopyUtils.copy(file.getInputStream(), new FileOutputStream(
				destination));
		res.getWriter().write(“Success, wrote to ” + destination);
		res.flushBuffer();
		return null;
	}
	public void setTempDirectory(String tempDirectory) {
		this.tempDirectory = tempDirectory;
	}
}
  • Créer le fichier index.html où sera effectué le post:
<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.01//EN” “http://www.w3.org/TR/html4/strict.dtd”>
<html>
<head>
<title>Spring MVC Upload</title>
<meta http-equiv=“content-type” content=“text/html; charset=iso-8859-1”>
<body>
<div id=“upload”>
	<form action=“/springmvcupload/service/handleUpload” id=“upload” name=“uploadFrm” method=“post” enctype=“multipart/form-data”>
		File: <input type=“file” name=“uploaded” />
		<input type=“submit” />
	</form>
</div>
</body>
</html>
  • Tester manuellement

Test unitaire de l’upload Spring

  • La première partie n’a rien de magique, cette partie est plus subtile.
  • La classe de test HandleUploadTest:
package fr.jfhelie.springmvcupload;
import java.util.ArrayList;
import java.util.List;
import junit.framework.TestCase;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.multipart.MultipartHttpServletRequest;
public class HandleUploadTest extends TestCase {
	private HandleUploadController c;
	private MockHttpServletRequest request;
	private MockHttpServletResponse response;
	protected void setUp() throws Exception {
		c = new HandleUploadController();
		c.setTempDirectory(“c:/temp”);
		response = new MockHttpServletResponse();
	}
	public void testupload() throws Exception {
		request = new MockHttpServletRequest(“POST”,“/handleUpload”);
		List fileItems = new ArrayList();
		MockFileItem fileItem = new MockFileItem(“uploaded”,“text/plain”,“test.csv”,“1;1”);
		fileItems.add(fileItem);
		MockCommonsMultipartResolver resolver = new MockCommonsMultipartResolver(fileItems);
		request.setContentType(“multipart/form-data”);
		request.addHeader(“Content-type”, “multipart/form-data”);
		request.setParameter(“pageId”, “Accueil”);
		assertTrue(resolver.isMultipart(request));
		MultipartHttpServletRequest multipartRequest = resolver.resolveMultipart(request);
		c.handleRequest(multipartRequest,response);
	}
}
  • Avec deux mocks MockCommonsMultipartResolver et MockFileItem:
package fr.jfhelie.springmvcupload;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import org.apache.commons.fileupload.FileItem;
public class MockFileItem implements FileItem {
	private String fieldName;
	private String contentType;
	private String name;
	private String value;
	private File writtenFile;
	private boolean deleted;
	public MockFileItem(String fieldName, String contentType, String name,
			String value) {
		this.fieldName = fieldName;
		this.contentType = contentType;
		this.name = name;
		this.value = value;
	}
	public InputStream getInputStream() throws IOException {
		return new ByteArrayInputStream(value.getBytes());
	}
	public String getContentType() {
		return contentType;
	}
	public String getName() {
		return name;
	}
	public boolean isInMemory() {
		return true;
	}
	public long getSize() {
		return value.length();
	}
	public byte get() {
		return value.getBytes();
	}
	public String getString(String encoding)
			throws UnsupportedEncodingException {
		return new String(get(), encoding);
	}
	public String getString() {
		return value;
	}
	public void write(File file) throws Exception {
		this.writtenFile = file;
	}
	public File getWrittenFile() {
		return writtenFile;
	}
	public void delete() {
		this.deleted = true;
	}
	public boolean isDeleted() {
		return deleted;
	}
	public String getFieldName() {
		return fieldName;
	}
	public void setFieldName(String s) {
		this.fieldName = s;
	}
	public boolean isFormField() {
		return (this.name == null);
	}
	public void setFormField(boolean b) {
		throw new UnsupportedOperationException();
	}
	public OutputStream getOutputStream() throws IOException {
		throw new UnsupportedOperationException();
	}
}
package fr.jfhelie.springmvcupload;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
public class MockCommonsMultipartResolver extends CommonsMultipartResolver {
	private List fileItems;
	public MockCommonsMultipartResolver(List fileItems) {
		this.fileItems = fileItems;
	}
	protected FileUpload newFileUpload(FileItemFactory fileItemFactory) {
		return new MockFileUpload(fileItemFactory, fileItems);
	}
}
class MockFileUpload extends ServletFileUpload {
	private List fileItems;
	public MockFileUpload(FileItemFactory fileItemFactory, List fileItems) {
		super(fileItemFactory);
		this.fileItems = fileItems;
	}
	public List parseRequest(HttpServletRequest request)
			throws FileUploadException {
		return fileItems;
	}
}

Resources