/*
* (C) Copyright 2011 Nuxeo SA (http://nuxeo.com/) and contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl.html
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* Contributors:
* Anahide Tchertchian
*/
package org.nuxeo.ecm.platform.contentview.json;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import net.sf.json.JSONArray;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.api.ClientException;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.DocumentModelFactory;
import org.nuxeo.ecm.core.api.SortInfo;
import org.nuxeo.ecm.platform.contentview.jsf.ContentViewLayout;
import org.nuxeo.ecm.platform.contentview.jsf.ContentViewLayoutImpl;
import org.nuxeo.ecm.platform.contentview.jsf.ContentViewState;
import org.nuxeo.ecm.platform.contentview.jsf.ContentViewStateImpl;
import org.nuxeo.ecm.platform.forms.layout.io.Base64;
/**
* Exporter/importer in JSON format of a {@link ContentViewState}.
*
* @since 5.4.2
*/
public class JSONContentViewState {
private static final Log log = LogFactory.getLog(JSONContentViewState.class);
public static final String ENCODED_VALUES_ENCODING = "UTF-8";
/**
* Returns the String serialization in JSON format of a content view state.
*
* @param state the state to serialize
* @param encode if true, the resulting String will be zipped and encoded
* in Base-64 format.
* @throws ClientException
* @throws UnsupportedEncodingException
*/
public static String toJSON(ContentViewState state, boolean encode)
throws ClientException, UnsupportedEncodingException {
if (state == null) {
return null;
}
if (log.isDebugEnabled()) {
log.debug("Encoding content view state: " + state);
}
// build json
JSONObject jsonObject = new JSONObject();
jsonObject.element("contentViewName", state.getContentViewName());
jsonObject.element("pageSize", state.getPageSize());
jsonObject.element("currentPage", state.getCurrentPage());
JSONArray jsonQueryParams = new JSONArray();
Object[] queryParams = state.getQueryParameters();
if (queryParams != null) {
// serialize to String before
List<String> stringParams = new ArrayList<String>();
for (Object queryParam : queryParams) {
if (queryParam == null) {
stringParams.add(null);
} else {
stringParams.add(queryParam.toString());
}
}
jsonQueryParams.addAll(stringParams);
}
jsonObject.element("queryParameters", jsonQueryParams);
jsonObject.element("searchDocument",
getDocumentModelToJSON(state.getSearchDocumentModel()));
JSONArray jsonSortInfos = new JSONArray();
List<SortInfo> sortInfos = state.getSortInfos();
if (sortInfos != null) {
for (SortInfo sortInfo : sortInfos) {
jsonSortInfos.add(getSortInfoToJSON(sortInfo));
}
}
jsonObject.element("sortInfos", jsonSortInfos);
jsonObject.element("resultLayout",
getContentViewLayoutToJSON(state.getResultLayout()));
List<String> resultColumns = state.getResultColumns();
if (resultColumns != null) {
jsonObject.element("resultColumns", resultColumns);
}
String jsonString = jsonObject.toString();
if (log.isDebugEnabled()) {
log.debug("Encoded content view state: " + jsonString);
}
// encoding
if (encode) {
String encodedValues = Base64.encodeBytes(jsonString.getBytes(),
Base64.GZIP | Base64.DONT_BREAK_LINES);
jsonString = URLEncoder.encode(encodedValues,
ENCODED_VALUES_ENCODING);
}
return jsonString;
}
/**
* Returns the content view state from its String serialization in JSON
* format.
*
* @param json the state to de-serialize
* @param decode if true, the input String is decoded from Base-64 format
* and unzipped.
* @throws ClientException
* @throws UnsupportedEncodingException
*/
@SuppressWarnings("unchecked")
public static ContentViewState fromJSON(String json, boolean decode)
throws UnsupportedEncodingException, ClientException {
if (json == null || json.trim().length() == 0) {
return null;
}
// decoding
if (decode) {
String decodedValues = URLDecoder.decode(json,
ENCODED_VALUES_ENCODING);
json = new String(Base64.decode(decodedValues));
}
if (log.isDebugEnabled()) {
log.debug("Decoding content view state: " + json);
}
// parse json
JSONObject jsonObject = JSONObject.fromObject(json);
ContentViewState state = new ContentViewStateImpl();
state.setContentViewName(jsonObject.getString("contentViewName"));
state.setPageSize(Long.valueOf(jsonObject.optLong("pageSize", -1)));
state.setCurrentPage(Long.valueOf(jsonObject.optLong("currentPage", -1)));
JSONArray jsonQueryParams = jsonObject.getJSONArray("queryParameters");
if (jsonQueryParams != null && !jsonQueryParams.isEmpty()) {
List<Object> queryParams = new ArrayList<Object>();
for (Object item : jsonQueryParams) {
queryParams.add(item);
}
state.setQueryParameters(queryParams.toArray(new Object[queryParams.size()]));
}
JSONObject jsonDoc = jsonObject.getJSONObject("searchDocument");
DocumentModel searchDoc = getDocumentModelFromJSON(jsonDoc);
state.setSearchDocumentModel(searchDoc);
JSONArray jsonSortInfos = jsonObject.getJSONArray("sortInfos");
if (jsonSortInfos != null && !jsonSortInfos.isEmpty()) {
List<SortInfo> sortInfos = new ArrayList<SortInfo>();
for (Object item : jsonSortInfos) {
sortInfos.add(getSortInfoFromJSON((JSONObject) item));
}
state.setSortInfos(sortInfos);
}
state.setResultLayout(getContentViewLayoutFromJSON(jsonObject.getJSONObject("resultLayout")));
JSONArray jsonResultColumns = jsonObject.optJSONArray("resultColumns");
if (jsonResultColumns != null) {
List<String> resultColumns = new ArrayList<String>();
resultColumns.addAll(jsonResultColumns);
state.setResultColumns(resultColumns);
}
if (log.isDebugEnabled()) {
log.debug("Decoded content view state: " + state);
}
return state;
}
protected static JSONObject getSortInfoToJSON(SortInfo sortInfo) {
JSONObject res = new JSONObject();
res.element(SortInfo.SORT_COLUMN_NAME, sortInfo.getSortColumn());
res.element(SortInfo.SORT_ASCENDING_NAME, sortInfo.getSortAscending());
return res;
}
protected static SortInfo getSortInfoFromJSON(JSONObject jsonSortInfo) {
String sortColumn = jsonSortInfo.getString(SortInfo.SORT_COLUMN_NAME);
boolean sortAscending = jsonSortInfo.getBoolean(SortInfo.SORT_ASCENDING_NAME);
return new SortInfo(sortColumn, sortAscending);
}
protected static JSONObject getDocumentModelToJSON(DocumentModel doc)
throws ClientException {
if (doc == null) {
return null;
}
JSONObject res = new JSONObject();
res.element("type", doc.getType());
JSONObject props = (new DocumentModelToJSON()).run(doc);
res.element("properties", props);
return res;
}
@SuppressWarnings("unchecked")
protected static DocumentModel getDocumentModelFromJSON(JSONObject jsonDoc)
throws ClientException {
if (jsonDoc == null || jsonDoc.isNullObject()) {
return null;
}
String docType = jsonDoc.getString("type");
DocumentModel doc = DocumentModelFactory.createDocumentModel(docType);
JSONObject props = jsonDoc.getJSONObject("properties");
Iterator<String> keys = props.keys();
while (keys.hasNext()) {
String key = keys.next();
doc.setPropertyValue(key, getDocumentPropertyValue(props.get(key)));
}
return doc;
}
protected static JSONObject getContentViewLayoutToJSON(
ContentViewLayout cvLayout) {
if (cvLayout == null) {
return null;
}
JSONObject res = new JSONObject();
res.element("name", cvLayout.getName());
res.element("title", cvLayout.getTitle());
res.element("translateTitle", cvLayout.getTranslateTitle());
res.element("iconPath", cvLayout.getIconPath());
res.element("showCSVExport", cvLayout.getShowCSVExport());
return res;
}
protected static ContentViewLayout getContentViewLayoutFromJSON(
JSONObject jsonCvLayout) {
if (jsonCvLayout == null || jsonCvLayout.isNullObject()) {
return null;
}
String name = jsonCvLayout.optString("name", null);
String title = jsonCvLayout.optString("title", null);
boolean translateTitle = jsonCvLayout.optBoolean("translateTitle");
String iconPath = jsonCvLayout.optString("iconPath", null);
boolean showCSVExport = jsonCvLayout.optBoolean("showCSVExport");
return new ContentViewLayoutImpl(name, title, translateTitle, iconPath,
showCSVExport);
}
@SuppressWarnings("unchecked")
protected static Serializable getDocumentPropertyValue(Object o)
throws JSONException {
if (o instanceof String) {
Calendar calendar = null;
try {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZ");
Date date = df.parse((String) o);
calendar = Calendar.getInstance();
calendar.setTime(date);
} catch (ParseException e) {
}
if (calendar != null) {
return calendar;
} else {
return (Serializable) o;
}
} else if (o instanceof JSONArray) {
JSONArray jsonArray = (JSONArray) o;
ArrayList<Serializable> list = new ArrayList<Serializable>();
for (Object aJsonArray : jsonArray) {
list.add(getDocumentPropertyValue(aJsonArray));
}
return list;
} else if (o instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) o;
HashMap<String, Serializable> map = new HashMap<String, Serializable>();
Iterator<String> keys = jsonObject.keys();
while (keys.hasNext()) {
String key = keys.next();
map.put(key, getDocumentPropertyValue(jsonObject.get(key)));
}
return map;
} else {
return (Serializable) o;
}
}
}