Android : Connexion à OpenERP en json-rpc

OpenERP expose ses services web en json-rpc, qui sont utilisés par le client web pour se connecter, récupérer le modèle des données, les données... Il est donc possible d’utiliser ce même protocole depuis Android pour se connecter et utiliser ces services.
Paramétrage du projet Android :
L'exemple ci dessous à été effectué avec l'IDE Eclipse en utilisant la plateforme 4.3 d'Android API niveau 18.
Il est possible de télécharger le projet d'exemple depuis le gestionnaire de source.
git clone https://bitbucket.org/petrus-v/openerpconnection.git
Il existe 2 branches :
- Une branche pour la connexion http :
git fetch && git checkout http-example
- une deuxième pour tester la conexion https :
git fetch && git checkout https-example
Ajouter la librairie json-rpc
au projet en copiant le fichier android-json-rpc-0.3.4.jar
dans le répertoire /libs/
de votre projet.
Dans le fichier manifeste /AndroidManifest.xml
, ajouter l'élément suivant pour autoriser l'application à accéder à internet :
<uses-permission android:name="android.permission.INTERNET"/>
Connexion http :
Création de la classe asynchrone qui effectue la connexion à OpenERP, elle étend la classe AsynTask disponible dans le Framework d’Android.
/src/fr.anybox.openerpconnection/OpenERPConnection.java
:
package fr.anybox.openerpconnection;
import org.alexd.jsonrpc.JSONRPCException;
import org.alexd.jsonrpc.JSONRPCHttpClient;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
public class OpenERPConnection extends AsyncTask{
static final String TAG = "OERP";
static final String HOST = "http://test.openerp.verkest.fr"; //won't work, this url is not set as http
static final String REQUEST_AUTHENTICATE = "/web/session/authenticate";
static final String REQUEST_SEARCH_READ = "/web/dataset/search_read";
static final String DATABASE = "oerp";
static final String USER = "demo";
static final String PASSWORD = "demo";
static final String method = "call";
@Override
protected Void doInBackground(Context... params) {
//The http client that allow to keep the session open over requests
HttpClient httpClient = new DefaultHttpClient();
JSONRPCHttpClient jsonRPC_client = new JSONRPCHttpClient(httpClient, HOST + REQUEST_AUTHENTICATE);
jsonRPC_client.setConnectionTimeout(2000);
jsonRPC_client.setSoTimeout(2000);
jsonRPC_client.setEncoding("UTF-8");
try {
//set params to send to OpenERP to create the connection
JSONObject jsonParams = new JSONObject();
jsonParams.put("db", DATABASE);
jsonParams.put("login", USER);
jsonParams.put("password",USER);
//Do the connection with openERP
JSONObject json_result = jsonRPC_client.callJSONObject(method, jsonParams);
Log.d(TAG, "We are connect to OpenERP, session id: " + json_result.getString("session_id"));
//Now we can request partner using the same session (http client)
jsonRPC_client = new JSONRPCHttpClient(httpClient, HOST + REQUEST_SEARCH_READ);
jsonRPC_client.setConnectionTimeout(2000);
jsonRPC_client.setSoTimeout(2000);
jsonRPC_client.setEncoding("UTF-8");
//get the user_context from the connection result, to send to OpenERP in the next request
JSONObject context = json_result.getJSONObject("user_context");
//set params to send to OpenERP service
jsonParams = new JSONObject();
jsonParams.put("session_id", json_result.getString("session_id"));
jsonParams.put("context", context);
jsonParams.put("model", "res.partner");
jsonParams.put("limit",10);
//Domain is use in openerp to define the where sql
JSONArray domain = new JSONArray();
JSONArray customer = new JSONArray();
JSONArray person = new JSONArray();
//Get only customers
customer.put("customer");
customer.put("=");
customer.put("1");
domain.put(customer);
//Get only person (no companies)
person.put("is_company");
person.put("=");
person.put("0");
domain.put(customer);
//domain : [[customer, =, 1],[is_company, =, 0]]
//SQL likes : WHERE customer = 1 AND is_company = 0
jsonParams.put("domain",domain);
//choose fields you want to get
JSONArray fields = new JSONArray();
fields.put("name");
fields.put("city");
jsonParams.put("fields", fields);
//jsonParams.put("offset",0);
jsonParams.put("sort","write_date desc");
//send the request to openerp
json_result = jsonRPC_client.callJSONObject(method, jsonParams);
JSONArray customers = json_result.getJSONArray("records");
JSONObject oerp_customer;
for(int i =0 ; i<10;i++){
oerp_customer = customers.getJSONObject(i);
Log.d(TAG, oerp_customer.getString("name") + " lives in " + oerp_customer.getString("city"));
}
} catch (JSONException e) {
Log.e(TAG,"Json exception: " + e.getMessage(), e);
} catch (JSONRPCException e) {
Log.e(TAG,"Json-rpc exception: " + e.getMessage(), e);
}
return null;
}
}
Un bouton a été ajouté dans la définition de la vue de l'activité principale :
/res/layout/activity_main.xml
:
...
<Button
android:id="@+id/do_connection"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/do_connection" />
...
Enfin, dans la méthode onCreate
de l’activité, l’événement onClick
du bouton est redéfini.
Fichier /src/fr.anybox.openerpconnection/MainActivity.java
:
...
OpenERPConnection oc = new OpenERPConnection();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// get the button and set it's listener
Button connect = (Button)findViewById(R.id.do_connection);
OnClickListener l = new OnClickListener() {
@Override
public void onClick(View v) {
if(oc.getStatus()==Status.FINISHED){
//if AsyncTask is done, we have to create a new instance
oc = new OpenERPConnection();
}
if(oc.getStatus()== Status.PENDING){
// AsyncTask is ready to run
oc.execute(getApplicationContext());
}else if(oc.getStatus()== Status.PENDING) {
//if AsynTask is already running, just wait and click again
Toast.makeText(getApplicationContext(), R.string.it_s_already_running_try_again_later,
Toast.LENGTH_LONG ).show();
}
}
};
connect.setOnClickListener(l );
}
...
La sortie LogCat :
10-21 23:08:10.568: D/OERP(3393): We are connect to OpenERP, session id: 548d76c73ecd47bca7e3dd37dd1d3505
10-21 23:08:19.528: D/OERP(3393): Pierre Verkest lives in Orléans
10-21 23:08:19.528: D/OERP(3393): Demo Portal User lives in false
10-21 23:08:19.528: D/OERP(3393): Thomas Passot lives in Wavre
10-21 23:08:19.528: D/OERP(3393): Axelor lives in Champs sur Marne
10-21 23:08:19.528: D/OERP(3393): Chamber Works lives in Detroit
10-21 23:08:19.528: D/OERP(3393): Millennium Industries lives in London
10-21 23:08:19.528: D/OERP(3393): Zhi Ch'ang lives in Shanghai
10-21 23:08:19.528: D/OERP(3393): Nebula Business lives in Rosario
10-21 23:08:19.528: D/OERP(3393): Paul Williams lives in Fremont
10-21 23:08:19.528: D/OERP(3393): Vauxoo lives in Caracas
Connexion https :
Pour une connexion https, avec un certificat auto-signé. Une méthode est de créer un fichier Bouncy Castle KeyStore (.BKS) contenant la liste des certificats de confiance des serveurs openERP auxquels vous devez vous connecter. Un outil comme portecle peut être utilisé pour générer ce fichier.
Dans les fichiers sources, un exemple de BKS est présent (/res/raw/verkest.bks), il contient le certificat auto-signé pour le site test.openerp.verkest.fr, le mot de passe du porteclé est verkest.
Le compte openerp utilisé pour cette connexion est demo / demo.
La classe AnyboxHttpsClient permet d'étendre les fonctionnalités de la classe DefaultHttpClient afin de se connecter à des serveur http ou https. Il ajoute le certificat auto-signé à la liste des certificats de confiance.
/src/fr.anybox.openerpconnection/AnyboxHttpsClient.java
:
package fr.anybox.openerpconnection;
import java.io.InputStream;
import java.security.KeyStore;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.SingleClientConnManager;
import android.content.Context;
public class AnyboxHttpsClient extends DefaultHttpClient {
final Context context;
public AnyboxHttpsClient(Context context) {
this.context = context;
}
@Override
protected ClientConnectionManager createClientConnectionManager() {
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
// Register for port 443 our SSLSocketFactory with our keystore
// to the ConnectionManager
registry.register(new Scheme("https", newSslSocketFactory(), 443));
return new SingleClientConnManager(getParams(), registry);
}
private SSLSocketFactory newSslSocketFactory() {
try {
// Get an instance of the Bouncy Castle KeyStore format
KeyStore trusted = KeyStore.getInstance("BKS");
// Get the raw resource, which contains the keystore with
// your trusted certificates (root and any intermediate certs)
InputStream in = context.getResources().openRawResource(R.raw.verkest);
try {
// Initialize the keystore with the provided trusted certificates
// Also provide the password of the keystore
trusted.load(in, "verkest".toCharArray());
} finally {
in.close();
}
// Pass the keystore to the SSLSocketFactory. The factory is responsible
// for the verification of the server certificate.
SSLSocketFactory sf = new SSLSocketFactory(trusted);
// Hostname verification from certificate
// http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506
// http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d5e445
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
return sf;
} catch (Exception e) {
throw new AssertionError(e);
}
}
}
Dans la méthode DoInBackground
de la classe OpenERPConnection
, remplacer la ligne de création du client http :
//The http client that allow to keep the session open over requests
HttpClient httpClient = new DefaultHttpClient();
Par :
//The https client that allow to keep the session open over requests
HttpClient httpClient = new AnyboxHttpsClient();
Tester de nouveau, le résultat est le même qu'en http, si vous utilisez la même base.