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.IOException; 022import java.io.Serializable; 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 org.apache.commons.logging.Log; 037import org.apache.commons.logging.LogFactory; 038import org.nuxeo.ecm.core.api.DocumentModel; 039import org.nuxeo.ecm.core.api.DocumentModelFactory; 040import org.nuxeo.ecm.core.api.SortInfo; 041import org.nuxeo.ecm.core.query.sql.model.Literal; 042import org.nuxeo.ecm.platform.contentview.jsf.ContentViewLayout; 043import org.nuxeo.ecm.platform.contentview.jsf.ContentViewLayoutImpl; 044import org.nuxeo.ecm.platform.contentview.jsf.ContentViewState; 045import org.nuxeo.ecm.platform.contentview.jsf.ContentViewStateImpl; 046import org.nuxeo.ecm.platform.forms.layout.io.Base64; 047 048import net.sf.json.JSONArray; 049import net.sf.json.JSONException; 050import net.sf.json.JSONNull; 051import net.sf.json.JSONObject; 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 */ 070 public static String toJSON(ContentViewState state, boolean encode) throws IOException { 071 if (state == null) { 072 return null; 073 } 074 if (log.isDebugEnabled()) { 075 log.debug("Encoding content view state: " + state); 076 } 077 078 // build json 079 JSONObject jsonObject = new JSONObject(); 080 jsonObject.element("contentViewName", state.getContentViewName()); 081 jsonObject.element("pageProviderName", state.getPageProviderName()); 082 jsonObject.element("pageSize", state.getPageSize()); 083 jsonObject.element("currentPage", state.getCurrentPage()); 084 085 JSONArray jsonQueryParams = new JSONArray(); 086 Object[] queryParams = state.getQueryParameters(); 087 if (queryParams != null) { 088 // NXP-10347 + NXP-17544: serialize to String all params that will be serialized to String by 089 // NXQLQueryBuilder anyway, for consistency 090 List<Object> serParams = new ArrayList<Object>(); 091 for (Object queryParam : queryParams) { 092 if (queryParam == null) { 093 serParams.add(null); 094 } else if (queryParam instanceof Object[] || queryParam instanceof Collection 095 || queryParam instanceof Boolean || queryParam instanceof Number 096 || queryParam instanceof Literal) { 097 serParams.add(queryParam); 098 } else { 099 serParams.add(queryParam.toString()); 100 } 101 } 102 jsonQueryParams.addAll(serParams); 103 } 104 jsonObject.element("queryParameters", jsonQueryParams); 105 106 jsonObject.element("searchDocument", getDocumentModelToJSON(state.getSearchDocumentModel())); 107 108 JSONArray jsonSortInfos = new JSONArray(); 109 List<SortInfo> sortInfos = state.getSortInfos(); 110 if (sortInfos != null) { 111 for (SortInfo sortInfo : sortInfos) { 112 jsonSortInfos.add(getSortInfoToJSON(sortInfo)); 113 } 114 } 115 jsonObject.element("sortInfos", jsonSortInfos); 116 117 jsonObject.element("resultLayout", getContentViewLayoutToJSON(state.getResultLayout())); 118 119 List<String> resultColumns = state.getResultColumns(); 120 if (resultColumns != null) { 121 jsonObject.element("resultColumns", resultColumns); 122 } 123 124 jsonObject.element("executed", state.isExecuted()); 125 126 String jsonString = jsonObject.toString(); 127 128 if (log.isDebugEnabled()) { 129 log.debug("Encoded content view state: " + jsonString); 130 } 131 132 // encoding 133 if (encode) { 134 String encodedValues = Base64.encodeBytes(jsonString.getBytes(), Base64.GZIP | Base64.DONT_BREAK_LINES); 135 jsonString = URLEncoder.encode(encodedValues, ENCODED_VALUES_ENCODING); 136 } 137 return jsonString; 138 } 139 140 /** 141 * Returns the content view state from its String serialization in JSON format. 142 * 143 * @param json the state to de-serialize 144 * @param decode if true, the input String is decoded from Base-64 format and unzipped. 145 */ 146 @SuppressWarnings("unchecked") 147 public static ContentViewState fromJSON(String json, boolean decode) throws IOException { 148 if (json == null || json.trim().length() == 0) { 149 return null; 150 } 151 // decoding 152 if (decode) { 153 String decodedValues = URLDecoder.decode(json, ENCODED_VALUES_ENCODING); 154 json = new String(Base64.decode(decodedValues)); 155 } 156 157 if (log.isDebugEnabled()) { 158 log.debug("Decoding content view state: " + json); 159 } 160 161 // parse json 162 JSONObject jsonObject = JSONObject.fromObject(json); 163 ContentViewState state = new ContentViewStateImpl(); 164 165 state.setContentViewName(jsonObject.getString("contentViewName")); 166 state.setPageProviderName(jsonObject.optString("pageProviderName", null)); 167 state.setPageSize(Long.valueOf(jsonObject.optLong("pageSize", -1))); 168 state.setCurrentPage(Long.valueOf(jsonObject.optLong("currentPage", -1))); 169 170 JSONArray jsonQueryParams = jsonObject.getJSONArray("queryParameters"); 171 if (jsonQueryParams != null && !jsonQueryParams.isEmpty()) { 172 List<Object> queryParams = new ArrayList<Object>(); 173 for (Object item : jsonQueryParams) { 174 if (item instanceof JSONNull) { 175 queryParams.add(null); 176 } else if (item instanceof JSONArray) { 177 queryParams.add(JSONArray.toCollection((JSONArray) item)); 178 } else { 179 queryParams.add(item); 180 } 181 } 182 state.setQueryParameters(queryParams.toArray(new Object[queryParams.size()])); 183 } 184 185 JSONObject jsonDoc = jsonObject.getJSONObject("searchDocument"); 186 DocumentModel searchDoc = getDocumentModelFromJSON(jsonDoc); 187 state.setSearchDocumentModel(searchDoc); 188 189 JSONArray jsonSortInfos = jsonObject.getJSONArray("sortInfos"); 190 191 if (jsonSortInfos != null && !jsonSortInfos.isEmpty()) { 192 List<SortInfo> sortInfos = new ArrayList<SortInfo>(); 193 for (Object item : jsonSortInfos) { 194 sortInfos.add(getSortInfoFromJSON((JSONObject) item)); 195 } 196 state.setSortInfos(sortInfos); 197 } 198 199 state.setResultLayout(getContentViewLayoutFromJSON(jsonObject.getJSONObject("resultLayout"))); 200 201 JSONArray jsonResultColumns = jsonObject.optJSONArray("resultColumns"); 202 if (jsonResultColumns != null) { 203 List<String> resultColumns = new ArrayList<String>(); 204 resultColumns.addAll(jsonResultColumns); 205 state.setResultColumns(resultColumns); 206 } 207 208 state.setExecuted(jsonObject.optBoolean("executed")); 209 210 if (log.isDebugEnabled()) { 211 log.debug("Decoded content view state: " + state); 212 } 213 214 return state; 215 } 216 217 protected static JSONObject getSortInfoToJSON(SortInfo sortInfo) { 218 JSONObject res = new JSONObject(); 219 res.element(SortInfo.SORT_COLUMN_NAME, sortInfo.getSortColumn()); 220 res.element(SortInfo.SORT_ASCENDING_NAME, sortInfo.getSortAscending()); 221 return res; 222 } 223 224 protected static SortInfo getSortInfoFromJSON(JSONObject jsonSortInfo) { 225 String sortColumn = jsonSortInfo.getString(SortInfo.SORT_COLUMN_NAME); 226 boolean sortAscending = jsonSortInfo.getBoolean(SortInfo.SORT_ASCENDING_NAME); 227 return new SortInfo(sortColumn, sortAscending); 228 } 229 230 protected static JSONObject getDocumentModelToJSON(DocumentModel doc) { 231 if (doc == null) { 232 return null; 233 } 234 JSONObject res = new JSONObject(); 235 res.element("type", doc.getType()); 236 JSONObject props = (new DocumentModelToJSON()).run(doc); 237 res.element("properties", props); 238 return res; 239 } 240 241 @SuppressWarnings("unchecked") 242 protected static DocumentModel getDocumentModelFromJSON(JSONObject jsonDoc) { 243 if (jsonDoc == null || jsonDoc.isNullObject()) { 244 return null; 245 } 246 String docType = jsonDoc.getString("type"); 247 DocumentModel doc = DocumentModelFactory.createDocumentModel(docType); 248 JSONObject props = jsonDoc.getJSONObject("properties"); 249 Iterator<String> keys = props.keys(); 250 while (keys.hasNext()) { 251 String key = keys.next(); 252 doc.setPropertyValue(key, getDocumentPropertyValue(props.get(key))); 253 } 254 return doc; 255 } 256 257 protected static JSONObject getContentViewLayoutToJSON(ContentViewLayout cvLayout) { 258 if (cvLayout == null) { 259 return null; 260 } 261 JSONObject res = new JSONObject(); 262 res.element("name", cvLayout.getName()); 263 res.element("title", cvLayout.getTitle()); 264 res.element("translateTitle", cvLayout.getTranslateTitle()); 265 res.element("iconPath", cvLayout.getIconPath()); 266 res.element("showCSVExport", cvLayout.getShowCSVExport()); 267 return res; 268 } 269 270 protected static ContentViewLayout getContentViewLayoutFromJSON(JSONObject jsonCvLayout) { 271 if (jsonCvLayout == null || jsonCvLayout.isNullObject()) { 272 return null; 273 } 274 String name = jsonCvLayout.optString("name", null); 275 String title = jsonCvLayout.optString("title", null); 276 boolean translateTitle = jsonCvLayout.optBoolean("translateTitle"); 277 String iconPath = jsonCvLayout.optString("iconPath", null); 278 boolean showCSVExport = jsonCvLayout.optBoolean("showCSVExport"); 279 return new ContentViewLayoutImpl(name, title, translateTitle, iconPath, showCSVExport); 280 } 281 282 @SuppressWarnings("unchecked") 283 protected static Serializable getDocumentPropertyValue(Object o) throws JSONException { 284 if (o == null || o instanceof JSONNull) { 285 return null; 286 } else if (o instanceof String) { 287 Calendar calendar = null; 288 try { 289 DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZ"); 290 Date date = df.parse((String) o); 291 calendar = Calendar.getInstance(); 292 calendar.setTime(date); 293 } catch (ParseException e) { 294 } 295 296 if (calendar != null) { 297 return calendar; 298 } else { 299 return (Serializable) o; 300 } 301 } else if (o instanceof JSONArray) { 302 JSONArray jsonArray = (JSONArray) o; 303 ArrayList<Serializable> list = new ArrayList<Serializable>(); 304 for (Object aJsonArray : jsonArray) { 305 Serializable pValue = getDocumentPropertyValue(aJsonArray); 306 if (pValue != null) { 307 list.add(pValue); 308 } 309 } 310 return list; 311 } else if (o instanceof JSONObject) { 312 JSONObject jsonObject = (JSONObject) o; 313 HashMap<String, Serializable> map = new HashMap<String, Serializable>(); 314 Iterator<String> keys = jsonObject.keys(); 315 while (keys.hasNext()) { 316 String key = keys.next(); 317 map.put(key, getDocumentPropertyValue(jsonObject.get(key))); 318 } 319 return map; 320 } else { 321 return (Serializable) o; 322 } 323 } 324 325}