001/* 002 * (C) Copyright 2011 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Anahide Tchertchian 016 */ 017package org.nuxeo.ecm.platform.contentview.json; 018 019import java.io.Serializable; 020import java.io.UnsupportedEncodingException; 021import java.net.URLDecoder; 022import java.net.URLEncoder; 023import java.text.DateFormat; 024import java.text.ParseException; 025import java.text.SimpleDateFormat; 026import java.util.ArrayList; 027import java.util.Calendar; 028import java.util.Collection; 029import java.util.Date; 030import java.util.HashMap; 031import java.util.Iterator; 032import java.util.List; 033 034import net.sf.json.JSONArray; 035import net.sf.json.JSONException; 036import net.sf.json.JSONNull; 037import net.sf.json.JSONObject; 038 039import org.apache.commons.logging.Log; 040import org.apache.commons.logging.LogFactory; 041import org.nuxeo.ecm.core.api.DocumentModel; 042import org.nuxeo.ecm.core.api.DocumentModelFactory; 043import org.nuxeo.ecm.core.api.SortInfo; 044import org.nuxeo.ecm.core.query.sql.model.Literal; 045import org.nuxeo.ecm.platform.contentview.jsf.ContentViewLayout; 046import org.nuxeo.ecm.platform.contentview.jsf.ContentViewLayoutImpl; 047import org.nuxeo.ecm.platform.contentview.jsf.ContentViewState; 048import org.nuxeo.ecm.platform.contentview.jsf.ContentViewStateImpl; 049import org.nuxeo.ecm.platform.forms.layout.io.Base64; 050 051/** 052 * Exporter/importer in JSON format of a {@link ContentViewState}. 053 * 054 * @since 5.4.2 055 */ 056public class JSONContentViewState { 057 058 private static final Log log = LogFactory.getLog(JSONContentViewState.class); 059 060 public static final String ENCODED_VALUES_ENCODING = "UTF-8"; 061 062 /** 063 * Returns the String serialization in JSON format of a content view state. 064 * 065 * @param state the state to serialize 066 * @param encode if true, the resulting String will be zipped and encoded in Base-64 format. 067 * @throws UnsupportedEncodingException 068 */ 069 public static String toJSON(ContentViewState state, boolean encode) throws UnsupportedEncodingException { 070 if (state == null) { 071 return null; 072 } 073 if (log.isDebugEnabled()) { 074 log.debug("Encoding content view state: " + state); 075 } 076 077 // build json 078 JSONObject jsonObject = new JSONObject(); 079 jsonObject.element("contentViewName", state.getContentViewName()); 080 jsonObject.element("pageProviderName", state.getPageProviderName()); 081 jsonObject.element("pageSize", state.getPageSize()); 082 jsonObject.element("currentPage", state.getCurrentPage()); 083 084 JSONArray jsonQueryParams = new JSONArray(); 085 Object[] queryParams = state.getQueryParameters(); 086 if (queryParams != null) { 087 // NXP-10347 + NXP-17544: serialize to String all params that will be serialized to String by 088 // NXQLQueryBuilder anyway, for consistency 089 List<Object> serParams = new ArrayList<Object>(); 090 for (Object queryParam : queryParams) { 091 if (queryParam == null) { 092 serParams.add(null); 093 } else if (queryParam instanceof Object[] || queryParam instanceof Collection 094 || queryParam instanceof Boolean || queryParam instanceof Number 095 || queryParam instanceof Literal) { 096 serParams.add(queryParam); 097 } else { 098 serParams.add(queryParam.toString()); 099 } 100 } 101 jsonQueryParams.addAll(serParams); 102 } 103 jsonObject.element("queryParameters", jsonQueryParams); 104 105 jsonObject.element("searchDocument", getDocumentModelToJSON(state.getSearchDocumentModel())); 106 107 JSONArray jsonSortInfos = new JSONArray(); 108 List<SortInfo> sortInfos = state.getSortInfos(); 109 if (sortInfos != null) { 110 for (SortInfo sortInfo : sortInfos) { 111 jsonSortInfos.add(getSortInfoToJSON(sortInfo)); 112 } 113 } 114 jsonObject.element("sortInfos", jsonSortInfos); 115 116 jsonObject.element("resultLayout", getContentViewLayoutToJSON(state.getResultLayout())); 117 118 List<String> resultColumns = state.getResultColumns(); 119 if (resultColumns != null) { 120 jsonObject.element("resultColumns", resultColumns); 121 } 122 123 String jsonString = jsonObject.toString(); 124 125 if (log.isDebugEnabled()) { 126 log.debug("Encoded content view state: " + jsonString); 127 } 128 129 // encoding 130 if (encode) { 131 String encodedValues = Base64.encodeBytes(jsonString.getBytes(), Base64.GZIP | Base64.DONT_BREAK_LINES); 132 jsonString = URLEncoder.encode(encodedValues, ENCODED_VALUES_ENCODING); 133 } 134 return jsonString; 135 } 136 137 /** 138 * Returns the content view state from its String serialization in JSON format. 139 * 140 * @param json the state to de-serialize 141 * @param decode if true, the input String is decoded from Base-64 format and unzipped. 142 * @throws UnsupportedEncodingException 143 */ 144 @SuppressWarnings("unchecked") 145 public static ContentViewState fromJSON(String json, boolean decode) throws UnsupportedEncodingException { 146 if (json == null || json.trim().length() == 0) { 147 return null; 148 } 149 // decoding 150 if (decode) { 151 String decodedValues = URLDecoder.decode(json, ENCODED_VALUES_ENCODING); 152 json = new String(Base64.decode(decodedValues)); 153 } 154 155 if (log.isDebugEnabled()) { 156 log.debug("Decoding content view state: " + json); 157 } 158 159 // parse json 160 JSONObject jsonObject = JSONObject.fromObject(json); 161 ContentViewState state = new ContentViewStateImpl(); 162 163 state.setContentViewName(jsonObject.getString("contentViewName")); 164 state.setPageProviderName(jsonObject.optString("pageProviderName", null)); 165 state.setPageSize(Long.valueOf(jsonObject.optLong("pageSize", -1))); 166 state.setCurrentPage(Long.valueOf(jsonObject.optLong("currentPage", -1))); 167 168 JSONArray jsonQueryParams = jsonObject.getJSONArray("queryParameters"); 169 if (jsonQueryParams != null && !jsonQueryParams.isEmpty()) { 170 List<Object> queryParams = new ArrayList<Object>(); 171 for (Object item : jsonQueryParams) { 172 if (item instanceof JSONNull) { 173 queryParams.add(null); 174 } else if (item instanceof JSONArray) { 175 queryParams.add(JSONArray.toCollection((JSONArray) item)); 176 } else { 177 queryParams.add(item); 178 } 179 } 180 state.setQueryParameters(queryParams.toArray(new Object[queryParams.size()])); 181 } 182 183 JSONObject jsonDoc = jsonObject.getJSONObject("searchDocument"); 184 DocumentModel searchDoc = getDocumentModelFromJSON(jsonDoc); 185 state.setSearchDocumentModel(searchDoc); 186 187 JSONArray jsonSortInfos = jsonObject.getJSONArray("sortInfos"); 188 189 if (jsonSortInfos != null && !jsonSortInfos.isEmpty()) { 190 List<SortInfo> sortInfos = new ArrayList<SortInfo>(); 191 for (Object item : jsonSortInfos) { 192 sortInfos.add(getSortInfoFromJSON((JSONObject) item)); 193 } 194 state.setSortInfos(sortInfos); 195 } 196 197 state.setResultLayout(getContentViewLayoutFromJSON(jsonObject.getJSONObject("resultLayout"))); 198 199 JSONArray jsonResultColumns = jsonObject.optJSONArray("resultColumns"); 200 if (jsonResultColumns != null) { 201 List<String> resultColumns = new ArrayList<String>(); 202 resultColumns.addAll(jsonResultColumns); 203 state.setResultColumns(resultColumns); 204 } 205 206 if (log.isDebugEnabled()) { 207 log.debug("Decoded content view state: " + state); 208 } 209 return state; 210 } 211 212 protected static JSONObject getSortInfoToJSON(SortInfo sortInfo) { 213 JSONObject res = new JSONObject(); 214 res.element(SortInfo.SORT_COLUMN_NAME, sortInfo.getSortColumn()); 215 res.element(SortInfo.SORT_ASCENDING_NAME, sortInfo.getSortAscending()); 216 return res; 217 } 218 219 protected static SortInfo getSortInfoFromJSON(JSONObject jsonSortInfo) { 220 String sortColumn = jsonSortInfo.getString(SortInfo.SORT_COLUMN_NAME); 221 boolean sortAscending = jsonSortInfo.getBoolean(SortInfo.SORT_ASCENDING_NAME); 222 return new SortInfo(sortColumn, sortAscending); 223 } 224 225 protected static JSONObject getDocumentModelToJSON(DocumentModel doc) { 226 if (doc == null) { 227 return null; 228 } 229 JSONObject res = new JSONObject(); 230 res.element("type", doc.getType()); 231 JSONObject props = (new DocumentModelToJSON()).run(doc); 232 res.element("properties", props); 233 return res; 234 } 235 236 @SuppressWarnings("unchecked") 237 protected static DocumentModel getDocumentModelFromJSON(JSONObject jsonDoc) { 238 if (jsonDoc == null || jsonDoc.isNullObject()) { 239 return null; 240 } 241 String docType = jsonDoc.getString("type"); 242 DocumentModel doc = DocumentModelFactory.createDocumentModel(docType); 243 JSONObject props = jsonDoc.getJSONObject("properties"); 244 Iterator<String> keys = props.keys(); 245 while (keys.hasNext()) { 246 String key = keys.next(); 247 doc.setPropertyValue(key, getDocumentPropertyValue(props.get(key))); 248 } 249 return doc; 250 } 251 252 protected static JSONObject getContentViewLayoutToJSON(ContentViewLayout cvLayout) { 253 if (cvLayout == null) { 254 return null; 255 } 256 JSONObject res = new JSONObject(); 257 res.element("name", cvLayout.getName()); 258 res.element("title", cvLayout.getTitle()); 259 res.element("translateTitle", cvLayout.getTranslateTitle()); 260 res.element("iconPath", cvLayout.getIconPath()); 261 res.element("showCSVExport", cvLayout.getShowCSVExport()); 262 return res; 263 } 264 265 protected static ContentViewLayout getContentViewLayoutFromJSON(JSONObject jsonCvLayout) { 266 if (jsonCvLayout == null || jsonCvLayout.isNullObject()) { 267 return null; 268 } 269 String name = jsonCvLayout.optString("name", null); 270 String title = jsonCvLayout.optString("title", null); 271 boolean translateTitle = jsonCvLayout.optBoolean("translateTitle"); 272 String iconPath = jsonCvLayout.optString("iconPath", null); 273 boolean showCSVExport = jsonCvLayout.optBoolean("showCSVExport"); 274 return new ContentViewLayoutImpl(name, title, translateTitle, iconPath, showCSVExport); 275 } 276 277 @SuppressWarnings("unchecked") 278 protected static Serializable getDocumentPropertyValue(Object o) throws JSONException { 279 if (o instanceof String) { 280 Calendar calendar = null; 281 try { 282 DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZ"); 283 Date date = df.parse((String) o); 284 calendar = Calendar.getInstance(); 285 calendar.setTime(date); 286 } catch (ParseException e) { 287 } 288 289 if (calendar != null) { 290 return calendar; 291 } else { 292 return (Serializable) o; 293 } 294 } else if (o instanceof JSONArray) { 295 JSONArray jsonArray = (JSONArray) o; 296 ArrayList<Serializable> list = new ArrayList<Serializable>(); 297 for (Object aJsonArray : jsonArray) { 298 list.add(getDocumentPropertyValue(aJsonArray)); 299 } 300 return list; 301 } else if (o instanceof JSONObject) { 302 JSONObject jsonObject = (JSONObject) o; 303 HashMap<String, Serializable> map = new HashMap<String, Serializable>(); 304 Iterator<String> keys = jsonObject.keys(); 305 while (keys.hasNext()) { 306 String key = keys.next(); 307 map.put(key, getDocumentPropertyValue(jsonObject.get(key))); 308 } 309 return map; 310 } else { 311 return (Serializable) o; 312 } 313 } 314 315}