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