package fr.inouk.OpenERPJSONRPCClient;
/*
Copyright (c) 2013 Cyril MORISSE ( @cmorisse )
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-- MIT License (MIT)
*/
import com.github.kevinsawicki.http.HttpRequest;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.net.HttpCookie;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Created with IntelliJ IDEA.
* User: cmorisse
* Date: 12/10/13
* Time: 08:01
* To change this template use File | Settings | File Templates.
*/
public class OpenERPJSONRPCClient implements Serializable {
private transient Logger logger = LoggerFactory.getLogger(OpenERPJSONRPCClient.class);
private int _rid = 0;
private String _baseUrl = null;
private List<HttpCookie> _cookies = Collections.emptyList();
private String _sessionId = null;
private JSONObject _sessionInfo; // updated by authenticate
private JSONObject _defaultContext = new JSONObject();
public JSONObject get_sessionInfo() {
return _sessionInfo;
}
public void set_sessionInfo(JSONObject _sessionInfo) {
this._sessionInfo = _sessionInfo;
}
public int get_rid() {
return _rid;
}
public OpenERPJSONRPCClient(String baseUrl) {
this._baseUrl = baseUrl;
JSONObject postData = new JSONObject();
postData.put("session_id", JSONObject.NULL);
postData.put("context", new JSONObject());
HttpRequest firstConnection = this.JsonRpc(getURLForMethod("session", "get_session_info"), "call", postData );
// We retrieve and store werkzeug session cookie
String cookies = firstConnection.header("Set-Cookie");
_cookies = HttpCookie.parse(cookies);
// We retrieve and store OpenERP session_id
JSONObject body = new JSONObject(firstConnection.body());
this._sessionId = (String) body.getJSONObject("result").get("session_id");
}
public JSONObject getDefaultContext() {
return _defaultContext;
}
public void setLanguage(String language) {
_defaultContext.put("lang", language);
}
public HttpRequest JsonRpc( String url, String method, JSONObject args) {
HttpRequest request = HttpRequest.post(url);
// We re-inject sid cookie
if( !this._cookies.isEmpty() ) {
StringBuffer cookieHeader = new StringBuffer();
for( HttpCookie cookie : this._cookies) {
cookieHeader.append(cookie.toString());
cookieHeader.append(';');
}
request.header("Cookie", cookieHeader.toString());
};
JSONObject postData = new JSONObject();
postData.put("json-rpc", "2.0");
postData.put("method", method);
postData.put("params", args);
postData.put("id", "r"+this._rid);
this._rid++;
// set content type to json
request.contentType(HttpRequest.CONTENT_TYPE_JSON);
request.acceptJson();
request.send(postData.toString());
// We force method call
int statusCode = request.code();
if( statusCode != 200 ) {
// Here, user failed to provide a valid URL so either, method or service is incorrect
throw new OpenERPServiceOrMethodException("URL="+url);
};
// We return a valid request because we want to return more information than the content.
return request;
}
//*****( 'database' service )***************************************************************************************
/**
* Executes an OpenERP flavored JSON-RPC call:
* - passes OpenERP session_id in params dict
* - return the result key of the response dict if any or raise an error
* @param url
* @param method
* @param params
* @return
*/
public Object OEJsonRpc(String url, String method, JSONObject params) {
if(this._sessionId!=null) {
params.put("session_id", this._sessionId);
}
HttpRequest request = JsonRpc( url, method, params);
JSONObject jsonResponse = new JSONObject(request.body());
if (logger.isTraceEnabled()) logger.trace("OpenERP Response: {} ", jsonResponse );
try {
Object result = jsonResponse.get("result");
return result;
} catch(org.json.JSONException ex) {
logger.error("Error while parsing OpenERP result", ex);
logger.error(jsonResponse.toString());
throw new OpenERPJSONRPCClientException(jsonResponse);
}
}
/**
* Compute the openerp url to call given a service and a method name
* @param serviceName
* @param methodName
* @return URL to call
*/
private String getURLForMethod( String serviceName, String methodName) {
return this._baseUrl + "/web/" + serviceName + "/" + methodName;
}
//*****( 'database' service )**************************************************************************************/
/**
* Returns a list of available database on the server (Do not require to Authenticated)
* @param context Optional Context
* @return an ArrayList<string> of databases available on the server.
*/
public ArrayList<String> databaseGetList(JSONObject context) {
if(context==null)
context = new JSONObject();
JSONObject params = new JSONObject();
params.put("context", context);
Object jsonResult = this.OEJsonRpc(getURLForMethod("database", "get_list"), "call", params);
if(!(jsonResult instanceof JSONArray))
return new ArrayList<String>();
ArrayList<String> dbList = new ArrayList<String>();
int i=0;
while( i < ((JSONArray) jsonResult).length()) {
dbList.add(((JSONArray)jsonResult).getString(i));
i++;
}
return dbList;
}
/**
* Drop a database.
* Notice the particular encoding of the fields.
* @param superAdminPassword OpenERP Master Password required to access database menu
* @param databaseName
* @param context
* @return
*/
public boolean databaseDrop(String superAdminPassword, String databaseName, JSONObject context) {
if(context==null)
context = new JSONObject();
JSONObject params = new JSONObject();
JSONArray fields = new JSONArray();
final String strPlaceHolder = "{'name': %s, 'value': %s }";
fields.put(new JSONObject( String.format( strPlaceHolder, "drop_pwd", superAdminPassword)));
fields.put(new JSONObject( String.format( strPlaceHolder, "drop_db", databaseName)));
params.put("context", context);
params.put("fields", fields);
Object callResult = this.OEJsonRpc(getURLForMethod("database", "drop"), "call", params);
return ((Boolean) callResult).booleanValue();
}
/**
* Create a new database
* @param superAdminPassword OpenERP Master Password required to access database menu
* @param databaseName
* @param demoData Should the system install demo data
* @param databaseLang Langage in iso format. Eg. 'FR_fr'
* @param newUserPassword Password assigned to the admin user of the created database
* @param context
* @return
*/
public boolean databaseCreate(String superAdminPassword, String databaseName, boolean demoData, String databaseLang,
String newUserPassword,
JSONObject context) {
if(context==null)
context = new JSONObject();
JSONObject params = new JSONObject();
JSONArray fields = new JSONArray();
final String strPlaceHolder = "{'name': %s, 'value': %s }";
fields.put(new JSONObject( String.format( strPlaceHolder, "super_admin_pwd", superAdminPassword)));
fields.put(new JSONObject( String.format( strPlaceHolder, "db_name", databaseName)));
fields.put(new JSONObject( String.format( strPlaceHolder, "demo_data", demoData)));
fields.put(new JSONObject( String.format( strPlaceHolder, "db_lang", databaseLang)));
fields.put(new JSONObject( String.format( strPlaceHolder, "create_admin_pwd", newUserPassword)));
params.put("context", context);
params.put("fields", fields);
Object callResult = this.OEJsonRpc(getURLForMethod("database", "create"), "call", params);
return ((Boolean) callResult).booleanValue();
}
//****( 'session' service )*****************************************************************************************
/**
* Authenticate a user against a database.
* @param db database to log against
* @param login
* @param password
* @param base_location Optional
* @param context Optional
* @return session_info JSONObject with uid set (> 0) if login ok or -1 if login failed.
*/
public JSONObject sessionAuthenticate( String db, String login, String password, String base_location, JSONObject context ) {
if( base_location == null )
base_location = this._baseUrl;
if( context==null)
context=new JSONObject();
JSONObject params = new JSONObject();
params.put("db", db);
params.put("login", login);
params.put("password",password);
params.put("base_location", base_location);
params.put("context", context);
JSONObject sessionInfo = (JSONObject) this.OEJsonRpc(getURLForMethod("session", "authenticate"), "call", params);
/*
* In case of Authentication failure (bad username or password),
* OpenERP returns an empty sessionInfo like this:
* {
* "username": "admin",
* "user_context": {},
* "db": "openerp_jsonrpc_client",
* "uid": false, "
* session_id": "502e5d58d20c4c6e8034f9353fe3dd2d"
* }
* Note uid value. uid = false is the signature of a failed login
*
* In order to simplify Authenticate usage and returned value test, we replace uid false value with -1
*/
Object uid = sessionInfo.get("uid");
if (uid instanceof Boolean)
sessionInfo.put("uid", -1);
return sessionInfo;
// TODO: Return a dedicated sessionInfo object where only context is a JSONObject
}
/**
* Retrieve current session info
* @param context Optional
* @return session_info JSONObject with uid set (> 0) if login ok or -1 if login failed.
*/
public JSONObject sessionGetInfo(JSONObject context ) {
if( context==null)
context=new JSONObject();
JSONObject params = new JSONObject();
params.put("context", context);
JSONObject sessionInfo = (JSONObject) this.OEJsonRpc(getURLForMethod("session", "get_session_info"), "call", params);
/*
* If user is currently un-authenticated, OpenERP returns an empty sessionInfo of this kind:
* {
* "username": "admin",
* "user_context": {},
* "db": "openerp_jsonrpc_client",
* "uid": false, "
* session_id": "502e5d58d20c4c6e8034f9353fe3dd2d"
* }
* Note uid value. uid = false is the signature of an unauthenticated user
*
* In order to simplify sessionInfo usage and returned value test, we replace uid false value with -1
*/
Object uid = sessionInfo.get("uid");
// TODO: update against OE Deserializer
if (uid instanceof Boolean)
sessionInfo.put("uid", -1);
this._sessionInfo = sessionInfo;
return sessionInfo;
// TODO: Instead of a JSONObject return a dedicated sessionInfo object where only context is a JSONObject
}
//*****( 'dataset' service )****************************************************************************************
/**
* Peform a 'search' followed by a 'read' in one server roundtrip
* @param model name of the model
* @param fields A String array of field names to fetch value for. If null all fields will be fetched
* @param offset index of the first object to return (used in combination with limit to implement pagination)
* @param limit max number of record to return (used in combination with offset to implement pagination)
* @param domain An OpenERP domain. OpenERP domains are used to express queries. Take a look at the tests for
* various examples. Note that OpenERP requires an empty array [] for an empty domain. So we generate
* one is user provides null.
* @param sort optional list of object attributes (columns) the result will be sorted by. Example "id, name"
* or "name DESC"
* @param context
* @return a json object (dict) with two keys:
* - length => number of returned record
* - records => an array of JSON objects. 1 objects per record.
* Example:
{
"length": 1,
"records": [
{
"id": 1,
"name": "Administrator",
"comment": false,
"parent_id": false,
"partner_id": [ 3, "Administrator" ], // many2one, OE embeds the display name
"birthdate": false,
"company_ids": [ // one2many, OE returns only an id list
1
],
"contact_address": "\n\n \n",
"country_id": false, // many2one null values are returned as false !!!
"credit_limit": 0,
"customer": false,
"lang": "en_US",
"login": "admin",
"login_date": "2013-10-19",
"street": false, // null string
"street2": false,
"supplier": false,
"type": "contact",
"tz": false,
"tz_offset": "+0000",
"zip": false
}
]
}
*/
public JSONObject modelSearchRead(String model, String[] fields, int offset, int limit, JSONArray domain, String sort,
JSONObject context) {
if(context==null)
context=new JSONObject();
// we add only defined parameters to params object sent to server
JSONObject params = new JSONObject();
params.put("model", model);
if(fields != null)
params.put("fields", fields);
if(offset != 0)
params.put("offset", offset);
if(limit != 0)
params.put("limit", limit);
if(domain == null)
domain = new JSONArray();
params.put("domain", domain);
if(sort != null)
params.put("sort", sort);
params.put("context", context);
JSONObject objects = (JSONObject) this.OEJsonRpc(getURLForMethod("dataset", "search_read"), "call", params);
// TODO: Derived JSON deserializer to handle false as null value for string, integers and date
return objects;
}
/**
* Loads all fields of exactly one id. Use with caution as it's a performance trap !!
* @param model Name of the model to load
* @param id id of the object to load
* @param context optional
* @return an object containing "value" object containing an object with all fields values
*/
public JSONObject modelLoad(String model, long id, JSONObject context) {
// we add only defined parameters to params object sent to server
JSONObject params = new JSONObject();
params.put("model", model);
params.put("id", id);
params.put("fields", new JSONArray()); // OpenERP expect fields parameter. But doesn't use it !!!!
if(context==null)
context=new JSONObject();
params.put("context", context);
JSONObject object = (JSONObject) this.OEJsonRpc(getURLForMethod("dataset", "load"), "call", params);
// TODO: Implement a derived JSON deserializer to handle false as null value for string, integers and date
return object;
}
/**
* Generic method allowing to access any model property. modelCallKW is a low level method. You should use it
* inside a facade object as it's quite boring to use in Java.
* @param model The model you want to work on
* @param method The method you want to call. It can be _any_ method of the object ; inherited one (create, write
* or object level ones)
* @param args An array of positional arguments
* @param kwargs An object containing Keywords Arguments
* @return The method result
*/
public Object modelCallKW(String model, String method, JSONArray args, JSONObject kwargs, JSONObject context) {
/**
* Note:
* - When used with a dyna language, context is usually embedded in kwargs since it's usually passed
* as a keyword argument. Here to avoid knitting with params, we declare it apart.
*/
JSONObject params = new JSONObject();
params.put("model", model);
params.put("method", method);
if(args==null)
args=new JSONArray();
params.put("args", args);
if(kwargs==null)
kwargs=new JSONObject();
params.put("kwargs", kwargs);
if(context==null)
context=new JSONObject();
params.put("context", context);
Object response = this.OEJsonRpc(getURLForMethod("dataset", "call_kw"), "call", params);
return response;
}
/**
* Trigger 'signal' on record identified by 'id' of 'model'
* @param model Model name
* @param id oject on which trigger the signal
* @param signal name of the signal
* @return false ! always !!!!
*/
public Object modelExecWorkflow(String model, long id, String signal, JSONObject context) {
if(context==null)
context=new JSONObject();
JSONObject params = new JSONObject();
params.put("model", model);
params.put("id", id);
params.put("signal", signal);
params.put("context", context);
Object response = this.OEJsonRpc(getURLForMethod("dataset", "exec_workflow"), "call", params);
return response;
}
}