package mongodb.tools;
import mongodb.tools.annotation.Id;
import mongodb.tools.annotation.Ignore;
import mongodb.tools.annotation.Version;
import mongodb.tools.exception.VersionMismatchException;
import org.bson.Document;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
import java.beans.Introspector;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;
/**
* Created by emeric_martineau on 11/03/2015.
*/
public class MongoDbMapping {
/**
* Field name of version.
*/
public static final String VERSION_FIELD = "<version>";
/**
* Map to map primitive class.
*/
private static final Map<Class<?>, Class<?>> primitiveMap = new HashMap<Class<?>, Class<?>>();
static {
primitiveMap.put(Boolean.TYPE, Boolean.class);
primitiveMap.put(Byte.TYPE, Byte.class);
primitiveMap.put(Character.TYPE, Character.class);
primitiveMap.put(Short.TYPE, Short.class);
primitiveMap.put(Integer.TYPE, Integer.class);
primitiveMap.put(Long.TYPE, Long.class);
primitiveMap.put(Double.TYPE, Double.class);
primitiveMap.put(Float.TYPE, Float.class);
primitiveMap.put(Void.TYPE, Void.TYPE);
}
/**
* Convert object to MondogDB object. Support direct object (int, String...) and Iterable. Not support array, Map...
*
* @see mongodb.tools.annotation.Ignore
* @see mongodb.tools.annotation.Id
*
* @param obj object to convert
*
* @return mongodb object
*
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
public static Document toMongo(final Object obj)
throws InvocationTargetException, IllegalAccessException {
Document result;
if (obj == null) {
result = null;
} else {
result = new Document();
Class tClass = obj.getClass();
Version versionAnnotation = (Version) tClass.getAnnotation(Version.class);
if (versionAnnotation != null) {
result.append(VERSION_FIELD, versionAnnotation.version());
}
Method[] methods = tClass.getMethods();
String methodName;
// Fielname
String fieldName;
// Data of field
Object data;
// Use if data is iterable
List temporaryList;
// Current object of list
Object objList;
for (int i = 0; i < methods.length; i++) {
methodName = methods[i].getName();
if (methodName.startsWith("get") && !"getClass".equals(methodName)
&& !methods[i].isAnnotationPresent(Ignore.class)
&& !methods[i].isAnnotationPresent(Id.class)) {
// Found getter.
fieldName = Introspector.decapitalize(methods[i].getName().substring(3));
if (methods[i].getParameterCount() == 0) {
data = methods[i].invoke(obj);
if (data instanceof Iterator) {
Iterator it = ((List) data).iterator();
temporaryList = new LinkedList();
while (it.hasNext()) {
objList = it.next();
temporaryList.add(toMongo(objList));
}
}
result.append(fieldName, data);
}
}
}
}
return result;
}
/**
* Convert MongoDb object to POJO
*
* @param mongoObj mongodb object
* @param clazz class to instanciate
*
* @return object
*
* @throws IllegalAccessException
* @throws InstantiationException
* @throws VersionMismatchException if object is strict and version not match
*/
public static Object fromMongoDb(final Document mongoObj, final Class clazz)
throws IllegalAccessException, InstantiationException, VersionMismatchException, InvocationTargetException, ClassNotFoundException {
Object result;
if (mongoObj == null) {
result = null;
} else {
result = clazz.newInstance();
Class tClass = result.getClass();
Method[] methods = tClass.getMethods();
/* Class annotation */
Version versionAnnotation = (Version) tClass.getAnnotation(Version.class);
String property;
Object value;
/* Setter method */
Method setter;
/* Parameter of setter */
Class<?> parameter;
Type[] type;
for (Map.Entry<String, Object> setKey : mongoObj.entrySet()) {
property = setKey.getKey();
value = setKey.getValue();
if (VERSION_FIELD.equals(property)) {
// Check version
if (versionAnnotation != null && versionAnnotation.strict()) {
int version = Integer.valueOf((String) value);
if (version != versionAnnotation.version()) {
throw new VersionMismatchException(String.format("Version %d found, version %d expected.",
version, versionAnnotation.version()));
}
}
} else {
setter = findSetter(methods, property);
if (setter != null) {
parameter = setter.getParameterTypes()[0];
type = setter.getGenericParameterTypes();
if (parameter.isPrimitive()) {
invokePrimitive(result, setter, parameter, value);
} else if (parameter.isEnum()) {
invokeEnum(result, setter, parameter, value);
} else if (parameter.isArray()) {
// TODO
} else if (parameter.getName().equals("java.lang.String")) {
setter.invoke(result, value);
} else if (parameter == List.class) {
invokeList(result, setter, type, value);
}
System.out.println(parameter);
// si c'est un talbeau
// si c'est une map
}
}
}
}
return result;
}
/**
* Find method setter with only one parameter.
*
* @param methods methods list
* @param fieldName fieldName
*
* @return method
*/
private static Method findSetter(final Method[] methods, final String fieldName) {
String methodName = "set".concat(Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1));
System.out.println(fieldName);
// Search method
Method m = null;
for (Method currentMethod : methods) {
if (methodName.equals(currentMethod.getName())) {
// Now check only one parameter
if (currentMethod.getParameterCount() == 1) {
m = currentMethod;
break;
}
}
}
return m;
}
/**
* Invoke primitive setter.
*
* @param obj object to call setter
* @param m method to call
* @param parameter parametter of setter
* @param value value to setup
*
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
private static void invokePrimitive(Object obj, Method m, Class<?> parameter, Object value)
throws InvocationTargetException, IllegalAccessException {
if (parameter == Boolean.TYPE) {
m.invoke(obj, Boolean.valueOf((String) value));
} else if (parameter == Byte.TYPE) {
m.invoke(obj, Byte.valueOf((String) value));
} else if (parameter == Character.TYPE) {
m.invoke(obj, Character.valueOf((char) value));
} else if (parameter == Short.TYPE) {
m.invoke(obj, Short.valueOf((String) value));
} else if (parameter == Integer.TYPE) {
m.invoke(obj, Integer.valueOf((String) value));
} else if (parameter == Long.TYPE) {
m.invoke(obj, Long.valueOf((String) value));
} else if (parameter == Double.TYPE) {
m.invoke(obj, Double.valueOf((String) value));
} else if (parameter == Float.TYPE) {
m.invoke(obj, Float.valueOf((String) value));
}
}
/**
* Invoke enum setter.
*
* @param obj object to call setter
* @param m method to call
* @param parameter parametter of setter
* @param value value to setup
*
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
private static void invokeEnum(Object obj, Method m, Class<?> parameter, Object value)
throws InvocationTargetException, IllegalAccessException {
Enum enumValue = Enum.valueOf((Class<Enum>) parameter, (String) value);
m.invoke(obj, enumValue);
}
/**
* Invoke collection setter.
*
* @param obj object to call setter
* @param m method to call
* @param parameter parametter of setter
* @param value value to setup
*
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws InstantiationException
*/
private static void invokeList(Object obj, Method m, Type[] parameter, Object value)
throws InvocationTargetException, IllegalAccessException, InstantiationException,
VersionMismatchException, ClassNotFoundException {
List srcList = (List) value;
List destList = null;
Class classOfList = getGenericClass(parameter);
if (value != null) {
destList = new ArrayList(srcList.size());
for (Object item : srcList) {
if (item == null) {
destList.add(null);
}else if (item instanceof Document) {
fromMongoDb((Document) item, classOfList);
} else {
destList.add(item);
}
}
}
m.invoke(obj, destList);
}
/**
* Found type of generic
*
* @param parameter parameter of method
*
* @return class to be instanciate
*
* @throws ClassNotFoundException
*/
private static Class getGenericClass(Type[] parameter) throws ClassNotFoundException {
Type type = ((ParameterizedTypeImpl) parameter[0]).getActualTypeArguments()[0];
return Class.forName(type.getTypeName());
}
}
jeudi 11 juin 2015
Java et la réflexion [4/4]
Maintenant que vous maîtriser les annotations et la réflexion en java, voici le code source complet du convertissant Java -> Mongo/Mongo -> Java :
vendredi 5 juin 2015
Java et la réflexion [3/4]
Une partie intéressant de la reflexion est la lecture des annotations.
Qu'est-ce que les annotation ?
Les annotation sont des métadonnées (des données supplémentaires) ajoutée à une classe, un attribut, une méthode.
Il existe 3 types d'annotations :
Reprenons l'exemple de MongoDB. Nous allons créer un simple mapper entre des objets java et du mongo.
Pour cela nous allons créer 3 annotations.
mongodb.tools.annotation.Id :
mongodb.tools.annotation.Ignore :
mongodb.tools.annotation.Version :
Imaginons la méthode toMongo() qui convertit un object java en object MondoDB.
Nous prenons donc la classe de l'objet et sur cette classe nous récupérons l'annotation de type Version.class.
Il est important de noter que les annotations ne sont pas héritable. C'est à dire que si la classe Toto à l'annotation Version et que la classe Titi hérite de la classe Toto, l'annotation n'est pas reporter sur la classe Toto.
Il faudra remonter dans la hiérarchie des héritages pour prendre toutes les annotations.
Pour les méthodes, c'est à peu près identique :
Voilà, ce n'est pas plus difficile que ça.
Qu'est-ce que les annotation ?
Les annotation sont des métadonnées (des données supplémentaires) ajoutée à une classe, un attribut, une méthode.
Il existe 3 types d'annotations :
- SOURCE : ces annotations sont ignorées par le compilateur mais produisent par exemple un message à la compilation comme @Override, @SuppressWarnings,
- CLASS : ajoute des métadonnées à la classe mais ne sont pas accessibles à l'exécution,
- RUNTIME : c'est ce type d'annotation qu'on va utiliser.
Reprenons l'exemple de MongoDB. Nous allons créer un simple mapper entre des objets java et du mongo.
Pour cela nous allons créer 3 annotations.
- @Id : qui permet d'indiquer que la méthode est le getter/setter de l'ID MongoDB,
- @Ignore : qui indique de ne pas mapper l'attribut de la classe java,
- @Version : qui indique la version de l'objet
mongodb.tools.annotation.Id :
package mongodb.tools.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* To indicate that setter is for MongoDb Id.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Id {
}
mongodb.tools.annotation.Ignore :
package mongodb.tools.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Ignore method.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Ignore {
}
mongodb.tools.annotation.Version :
package mongodb.tools.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* To indicate that setter is for MongoDb Id.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Version {
/**
* Version of object.
*
* @return
*/
public int version();
/**
* If strict when unserialize. Raise error if not same version.
* @return
*/
public boolean strict() default false;
}
Imaginons la méthode toMongo() qui convertit un object java en object MondoDB.
public static Document toMongo(final Object obj)
throws InvocationTargetException, IllegalAccessException {
Document result;
if (obj == null) {
result = null;
} else {
result = new Document();
Class tClass = obj.getClass();
Version versionAnnotation = (Version) tClass.getAnnotation(Version.class);
if (versionAnnotation != null) {
result.append(VERSION_FIELD, versionAnnotation.version());
}
...
Nous prenons donc la classe de l'objet et sur cette classe nous récupérons l'annotation de type Version.class.
Il est important de noter que les annotations ne sont pas héritable. C'est à dire que si la classe Toto à l'annotation Version et que la classe Titi hérite de la classe Toto, l'annotation n'est pas reporter sur la classe Toto.
Il faudra remonter dans la hiérarchie des héritages pour prendre toutes les annotations.
Pour les méthodes, c'est à peu près identique :
...
Method[] methods = tClass.getMethods();
for (int i = 0; i < methods.length; i++) {
methodName = methods[i].getName();
if (methodName.startsWith("get") && !"getClass".equals(methodName)
&& !methods[i].isAnnotationPresent(Ignore.class)
&& !methods[i].isAnnotationPresent(Id.class)) {
...
Voilà, ce n'est pas plus difficile que ça.
vendredi 17 avril 2015
Java et la réflexion [2/4]
Les méthodes
Récupération des méthodes public
Afin de récupérer les méthodes publics d'une classe, il faut utiliser la méthode getMethods(). Cette méthode ne retourne pas les méthodes privées !Vous récupérez un object Method. Cet objet récupéré, va permettre d'appeler la méthode avec ou sans paramètre, connaitre les types de paramètres attendus...
Récupération des paramètres des méthodes
Avec Java 8, il est très facile de savoir si une méthode attend des paramètes ou non avec la méthode Method.getParameterCount().Si la méthode attend des paramètres, il est possible de les récupérer via la méthode getParameterTypes().
Détecter si le paramètre est de type primitif
Une fois le paramètre obtenu, il est important de savoir si ce paramètre est un primitif au non, afin de convertir correctement la donnée à entrer.Partons du postulat que nous avons une chaine de caracètre que nous souhaitons convertir vers un type primitif :
// L'objet sur lequel on fait de la reflexion
Object obj = ...;
// Le setter a utiliser
Method setter = ....;
// On suppose qu'il n'y a paramètre au setter
Class parameter = setter.getParameterTypes()[0];
if (parameter.isPrimitive()) {
if (parameter == Boolean.TYPE) {
m.invoke(obj, Boolean.valueOf(value));
} else if (parameter == Byte.TYPE) {
m.invoke(obj, Byte.valueOf(value));
} else if (parameter == Character.TYPE) {
m.invoke(obj, Character.valueOf(value.substring(0, 1));
} else if (parameter == Short.TYPE) {
m.invoke(obj, Short.valueOf(value));
} else if (parameter == Integer.TYPE) {
m.invoke(obj, Integer.valueOf(value));
} else if (parameter == Long.TYPE) {
m.invoke(obj, Long.valueOf(value));
} else if (parameter == Double.TYPE) {
m.invoke(obj, Double.valueOf(value));
} else if (parameter == Float.TYPE) {
m.invoke(obj, Float.valueOf(value));
}
}
Détecter si le paramètre est de type tableau
Pour savoir s'il s'agit d'un tableau en entrée de la méthode, il est possible d'utiliser la méthode isArray() sur le paramètre, comme pour isPrimitive()Détecter si le paramètre est une énumération
Pour savoir s'il s'agit d'un enum, il est possible d'utiliser la méthode isEnum() sur le parametre, comme pour isPrimitive()Détecter si le paramètre est de type List/Map...
A présent, pour savoir si le paramètre est un list, la solution est simple, comparer avec le classe en question, comme nous l'avons fait avec les primitifs. A savoir :
if (parameter == List.class)
Récupération du générique de la liste
Pour récupérer le type du générique de la liste, là pas de longue explication, c'est en recherchant sur internet qu'on trouve des pistes, puis les expérimentations font le reste. Pour vous faire gagner du temps, voilà la solution :Type[] parameter = setter.getGenericParameterTypes(); Type type = ((ParameterizedTypeImpl) parameter[0]).getActualTypeArguments()[0]; return Class.forName(type.getTypeName());Oui, ça ne s'invente pas. A retenir, ((ParameterizedTypeImpl) parameter[0]) veut dire qu'on prend le premier paramettre de la méthode, getActualTypeArguments()[0] indique qu'on prend le premier paramètre générique. Et oui, pour une Map, il faudra prendre les 2 premiers.
dimanche 15 mars 2015
Java et la réflexion [1/4]
La reflexion en Java est un élément essentiel à connaitre. Ce n'est pas difficile en soit, mais certaines parties peuvent paraître un peu obscure.
A quoi la reflexion peut-elle servir ?
Par exemple, à faire un mapping d'objet facilement.
Prenons l'exemple de MongoDB. Actuellement le projet MongoJack qui permet de mapper les objets MongoDb en Java ne propose pas le support du driver MongoDb 3.
Nous allons donc écrire un petit mapper pour le driver MongoDb 3. L'exemple ne prendra pas en compte tous les types, seulement ceux intéressants dans le cadre d'un apprentissage.
Vous récupérez un object Field sur lequel vous pourrez utiliser la méthode get() pour récupérer la valeur et set() pour modifier la valeur.
Exemple :
Voici le résultat :
On voit que le champ a bien été modifié.
Il faut noter que getFields() ou getDeclaredFields() ne retourne pas les attributs hérités. Pour cela il faudra utiliser la classe mère en la récupérant via getSuperclass().
Exemple :
A quoi la reflexion peut-elle servir ?
Par exemple, à faire un mapping d'objet facilement.
Prenons l'exemple de MongoDB. Actuellement le projet MongoJack qui permet de mapper les objets MongoDb en Java ne propose pas le support du driver MongoDb 3.
Nous allons donc écrire un petit mapper pour le driver MongoDb 3. L'exemple ne prendra pas en compte tous les types, seulement ceux intéressants dans le cadre d'un apprentissage.
Les base de la reflexion
La réflexion permet de découvrir les caractéristiques d'une classe (attributs, méthodes, annotation...). Ceci est très pratique pour faire un traitement générique. Dans notre cas, découvrir les getter pour créer un object Document du driver version 3 de MongoDb.Les attributs
Récupération des attributs publics
Afin de récupérer les attributs publics d'une classe, il faut utiliser la méthode getFields(). Cette méthode ne retourne pas les attributs privés !Vous récupérez un object Field sur lequel vous pourrez utiliser la méthode get() pour récupérer la valeur et set() pour modifier la valeur.
Exemple :
import java.lang.reflect.Field;
public class TestField {
public String field1;
private String field2;
public TestField(String f1, String f2) {
this.field1 = f1;
this.field2 = f2;
}
public String getField2() {
return this.field2;
}
public static void main(String[] args) throws IllegalAccessException {
// Le type est volontairement objet pour prouver que même si on
// ne connait pas le type, il est possible d'avoir les informations.
Object o = new TestField("1", "2");
// On ne connait pas l'objet mais tout object à getClass() qui retourne
// la classe. De là on récupère les attributs public.
Field[] fields = o.getClass().getFields();
// On itère sur les attributs public
for (Field f : fields) {
System.out.println("Name : " + f.getName());
System.out.println("Old Value : " + f.get(o));
f.set(o, "9");
}
System.out.println("New Value : " + ((TestField) o).field1);
}
}
Voici le résultat :
Name : field1 Old Value : 1 New Value : 9
On voit que le champ a bien été modifié.
Récupération des attributs privés
Pour récupérer les attributs privés, il faut utiliser la méthode getDeclaredFields() au lieu de getFields(). Toutefois, il faut, avant d'appeler la méthode get() rendre le champs accessible par setAccessible(true). Voici l'exemple :
Field[] fields = o.getClass().getDeclaredFields();
// On itère sur les attributs public
for (Field f : fields) {
// Rend le champs accessible
f.setAccessible(true);
System.out.println("Name : " + f.getName());
System.out.println("Old Value : " + f.get(o));
f.set(o, "9");
}
Il faut noter que getFields() ou getDeclaredFields() ne retourne pas les attributs hérités. Pour cela il faudra utiliser la classe mère en la récupérant via getSuperclass().
Détecter si l'attribut est de type primitif
Pour savoir si un attribut est de type primitif et donc à ne pas instancier, il faut utiliser f.getType().isPrimitive(). Après, pour savoir de quel primitif il s'agit, il faut comparer le type avec le type primitif Boolean.TYPE, Byte.TYPE...Détecter si l'attribut est de type tableau
Même chose avec les tableaux qu'avec les primitifs, il faut utiliser f.getType().isArray().Détecter si l'attribut est de type List
Pour savoir si un attribut est de type List (ou autre), il faut simplement comparer le type avec la classe liste : f.getType() == List.class.Récupération du générique de la liste
Pour savoir s'il s'agit d'un générique, il faut vérifier que le type renvoyé par f.getGenericType() est de type ParameterizedTypeImpl.Exemple :
if (f.getGenericType() instanceof ParameterizedTypeImpl) {
System.out.println(
((ParameterizedTypeImpl) f.getGenericType()).getActualTypeArguments()[0].getTypeName());
}
Ensuite, il faut récupérer les arguments des types (getActualTypeArguments). Il s'agit d'une table. Dans le cas d'une liste, il n'y en a qu'un élément. Dans le cas d'une Map, il y en aura 2.
mardi 2 septembre 2014
Personnaliser la couche transport http avec Apache CXF 2.7.11
Cxf permet de personnaliser le transpoport http. Quel intérêt ?
En relation avec l'utilisation de Apache HttpClient pour l'authentification NTLM par exemple.
Dans un contexte Spring, rien de plus facile, il faut dans la configuration Spring :
Attention, redéfinir le HttpTransportFactory pour toutes les valeurs sans quoi sous aurez des surprises.
En relation avec l'utilisation de Apache HttpClient pour l'authentification NTLM par exemple.
Dans un contexte Spring, rien de plus facile, il faut dans la configuration Spring :
<bean class="org.test.cxf.transport.MyHttpTransportFactory" lazy-init="false">
<property name="transportIds">
<list>
<value>http://cxf.apache.org/transports/http</value>
<value>http://cxf.apache.org/transports/http/configuration</value>
<value>http://cxf.apache.org/transports/https</value>
<value>http://cxf.apache.org/transports/https/configuration</value>
<value>http://schemas.xmlsoap.org/wsdl/http</value>
<value>http://schemas.xmlsoap.org/wsdl/http/</value>
<value>http://schemas.xmlsoap.org/soap/http</value>
</list>
</property>
</bean>
Attention, redéfinir le HttpTransportFactory pour toutes les valeurs sans quoi sous aurez des surprises.
mardi 19 août 2014
Connexion à un serveur Http en utilisant une authentification NTLM [3/3]
Maintenant que nous savons qu'il n'est pas possible de faire du mutli-user avec une authentification NTLM en environnement mutli-thread, nativement avec la JVM, nous allons utiliser le framework Apache Http Client.
Pour rappel, voici un exemple d'authentification NTLM :
La première fois, évidement le server rejete le contenu.
Ce qui est important dans le code ci-dessous c'est :
1 - définir les crédential
2 - bien définir les paramètres de connexion et en particulier ne pas avoir l'entête Expect:
3 - fournir un entity repétable. En effet, la première fois, le contenu de la requête est envoyée, mais le serveur répond un 401
Code complet :
Attention, toutefois, il ne faut surtout pas activer l'authentification preemptive, le composant Http Client envoye un authentification basic.
L'authentification preemptive consiste à envoyer les credentials avant que le serveur ne les demande.
Attention aussi, dans l'exemple, il n'y a pas de configuration du pool manager. Il faut savoir que NTLM identifie au niveau de la connexion TCP, donc s'il y a réutilisation de la connexion, il n'y aura pas d'authentification puisque la connexion TCP est déjà identifié. Du coup, il peut y avoir un mélange de connexion.
Pour rappel, voici un exemple d'authentification NTLM :
Client -> Server POST ...
Server -> Client 401 Unauthorized
WWW-Authenticate: NTLM
Client -> Server POST ...
Authorization: NTLM TlRMTVNTUAABAAAAA7IAAAoACgApAAAACQAJACAAAABMSUdIVENJVFlVUlNBLU1JTk9S
Server -> Client 401 Unauthorized
WWW-Authenticate: NTLM TlRMTVNTUAACAAAAAAAAACgAAAABggAAU3J2Tm9uY2UAAAAAAAAAAA==
Client -> Server POST ...
Authorization: NTLM TlRMTVNTUAADAAAAGAAYAHIAAAAYABgAigAAABQAFABAAAAADAAMAFQAAAASABIAYAAAAAAAAACiAAAAAYIAAFUAUgBTAEEALQBNAEkATgBPAFIAWgBhAHAAaABvAGQATABJAEcASABUAEMASQBUAFkArYfKbe/jRoW5xDxHeoxC1gBmfWiS5+iX4OAN4xBKG/IFPwfH3agtPEia6YnhsADT
Server -> Client 200 Ok
On remarque qu'on envoie deux fois le contenu de la requête.La première fois, évidement le server rejete le contenu.
Ce qui est important dans le code ci-dessous c'est :
1 - définir les crédential
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(AuthScope.ANY,
new NTCredentials("", "", null, ""));
2 - bien définir les paramètres de connexion et en particulier ne pas avoir l'entête Expect:
.setExpectContinueEnabled(false)
3 - fournir un entity repétable. En effet, la première fois, le contenu de la requête est envoyée, mais le serveur répond un 401
HttpEntity reqEntity = new FileEntity(new File(xmlFile2Send));
Code complet :
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.NTCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.FileEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
/**
* Test NTLM with Apache HttpClient 4.3.x.
*
* HttpClient have pool, becarefull. NTLM is statefull. When TCP connection is avaible, no authentification are requiere.
*
* Add JVM argument to show all http request debug :
* -Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog
* -Dorg.apache.commons.logging.simplelog.showdatetime=true
* -Dorg.apache.commons.logging.simplelog.log.org.apache.http.impl.conn=DEBUG
* -Dorg.apache.commons.logging.simplelog.log.org.apache.http.impl.client=DEBUG
* -Dorg.apache.commons.logging.simplelog.log.org.apache.http.client=DEBUG
* -Dorg.apache.commons.logging.simplelog.log.org.apache.http=DEBUG
*/
public class TestNtlmSoap {
public static void main(String[] args) throws ClientProtocolException, IOException {
String SOAPUrl = "http://mywebservice.com/ws";
String xmlFile2Send = "file_to_send";
String SOAPAction = "http://tempuri.org/echo";
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(AuthScope.ANY,
new NTCredentials("", "", null, ""));
// Create global request configuration
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setRedirectsEnabled(true)
.setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM))
// Remove Http Header Expect: 100-continue
// http://stackoverflow.com/questions/14459704/does-empty-expect-header-mean-anything
.setExpectContinueEnabled(false)
.build();
CloseableHttpClient httpclient = HttpClients.custom()
.setDefaultCredentialsProvider(credsProvider)
.setDefaultRequestConfig(defaultRequestConfig)
.build();
try {
HttpPost httppost = new HttpPost(SOAPUrl);
httppost.addHeader("Content-Type","text/xml; charset=utf-8");
httppost.addHeader("SOAPAction", SOAPAction);
// Read request file.
// WARNING : must be repeatable ! At fist connection, content send, then authentification done, finaly resend file
HttpEntity reqEntity = new FileEntity(new File(xmlFile2Send));
httppost.setEntity(reqEntity);
System.out.println("executing request " + httppost.getRequestLine());
HttpResponse response = httpclient.execute(httppost);
HttpEntity resEntity = response.getEntity();
System.out.println("----------------------------------------");
System.out.println(response.getStatusLine());
if (resEntity != null) {
System.out.println("Response content length: " + resEntity.getContentLength());
System.out.println("Chunked?: " + resEntity.isChunked());
}
if (resEntity != null && resEntity.isStreaming()) {
InputStream instream = resEntity.getContent();
if (instream != null) {
InputStreamReader isr =
new InputStreamReader(instream);
BufferedReader in = new BufferedReader(isr);
String inputLine;
while ((inputLine = in.readLine()) != null)
System.out.println(inputLine);
instream.close();
}
}
} finally {
// When HttpClient instance is no longer needed,
// shut down the connection to ensure
// immediate deallocation of all system resources
httpclient.close();
}
}
}
Attention, toutefois, il ne faut surtout pas activer l'authentification preemptive, le composant Http Client envoye un authentification basic.
L'authentification preemptive consiste à envoyer les credentials avant que le serveur ne les demande.
Attention aussi, dans l'exemple, il n'y a pas de configuration du pool manager. Il faut savoir que NTLM identifie au niveau de la connexion TCP, donc s'il y a réutilisation de la connexion, il n'y aura pas d'authentification puisque la connexion TCP est déjà identifié. Du coup, il peut y avoir un mélange de connexion.
mardi 5 août 2014
Connexion à un serveur Http en utilisant une authentification NTLM [2/3]
Comme vu dans l'article précédent, la JVM stocke les credentials en cache. La question est comment vider ce cache ?
La réponse : ce n'est pas prévu.
Après des recherches sur internet :
Il va donc falloir trouver une autre solution.
Cette solution sera le framework Apache Http Client qui sera traiter dans le prochaine billet.
La réponse : ce n'est pas prévu.
Après des recherches sur internet :
- http://stackoverflow.com/questions/24497494/soap-webservice-client-using-apache-cxf-for-ntlm-authentication
- http://stackoverflow.com/questions/6184881/can-javas-single-sign-on-use-credentials-from-credential-manager-on-windo
- http://stackoverflow.com/questions/480895/reset-the-authenticator-credentials
- http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6626700
AuthCacheValue.setAuthCache(new AuthCacheImpl());Mais il y a encore un autre soucis, ce n'est pas thread safe. C'est a dire quedans un environnement multi-thread, avec accès concurrent, le résultat est aléatoire.
Il va donc falloir trouver une autre solution.
Cette solution sera le framework Apache Http Client qui sera traiter dans le prochaine billet.
Inscription à :
Articles (Atom)