001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.jsp.util; 029 030import org.opencms.json.JSONArray; 031import org.opencms.json.JSONException; 032import org.opencms.json.JSONObject; 033import org.opencms.util.CmsStringUtil; 034 035import java.util.AbstractCollection; 036import java.util.ArrayList; 037import java.util.Arrays; 038import java.util.Collections; 039import java.util.Iterator; 040import java.util.List; 041 042/** 043 * Wrapper for accessing JSON in JSPs. 044 */ 045public class CmsJspJsonWrapper extends AbstractCollection<Object> { 046 047 /** 048 * Helper class representing a "handle" to a JSON object's entry through which it is possible to either access the entry's value or remove the entry. 049 */ 050 private static class JSONObjectEntry { 051 052 /** The key for the entry. */ 053 private String m_key; 054 055 /** The parent JSON object. */ 056 private JSONObject m_object; 057 058 /** 059 * Creates a new instance. 060 * 061 * @param obj the parent JSON object 062 * @param key the key in the JSON object 063 */ 064 public JSONObjectEntry(JSONObject obj, String key) { 065 066 m_object = obj; 067 m_key = key; 068 } 069 070 /** 071 * Helper method for getting the list of entries of a JSON object with the given key. 072 * 073 * <p>If the key just normally occurs in the given object, a wrapper for that entry will be returned. 074 * If the key is the wildcard '*', a list of all entries for the object is returned. If the key does not exist 075 * in the object, an empty list is returned. 076 * 077 * @param obj a JSON object 078 * @param key a JSON key (can be the wildcard '*') 079 * 080 * @return the list of entries for the given key 081 */ 082 static List<JSONObjectEntry> getEntriesForKey(JSONObject obj, String key) { 083 084 List<JSONObjectEntry> result = new ArrayList<>(); 085 if ("*".equals(key)) { 086 for (String actualKey : obj.keySet()) { 087 result.add(new JSONObjectEntry(obj, actualKey)); 088 } 089 return result; 090 } else { 091 Object child = obj.opt(key); 092 if (child != null) { 093 return Collections.singletonList(new JSONObjectEntry(obj, key)); 094 } else { 095 return Collections.emptyList(); 096 } 097 } 098 } 099 100 /** 101 * Gets the JSON key. 102 * 103 * @return the JSON key 104 */ 105 public String getKey() { 106 107 return m_key; 108 } 109 110 /** 111 * Gets the entry's value. 112 * 113 * @return the value 114 */ 115 public Object getValue() { 116 117 return m_object.opt(m_key); 118 } 119 120 /** 121 * Removes the entry from the parent JSON object. 122 */ 123 public void remove() { 124 125 m_object.remove(m_key); 126 } 127 } 128 129 /** The wrapped value. */ 130 private Object m_value; 131 132 /** 133 * Creates a new JSON wrapper. 134 * 135 * @param value the value to wrap 136 */ 137 public CmsJspJsonWrapper(Object value) { 138 139 m_value = value; 140 } 141 142 /** 143 * Helper method for removing parts from a JSON object with a given path, which is already split into path components. 144 * 145 * See {@link #removePath(String)} for how the path works. 146 * 147 * @param obj the JSON object 148 * @param pathComponents the path components 149 */ 150 public static void removePathInJson(Object obj, List<String> pathComponents) { 151 152 if (pathComponents.isEmpty()) { 153 return; 154 } 155 String key = pathComponents.get(0); 156 List<String> remainingPath = pathComponents.subList(1, pathComponents.size()); 157 if (CmsStringUtil.isEmptyOrWhitespaceOnly(key)) { 158 removePathInJson(obj, remainingPath); 159 return; 160 } 161 if (obj instanceof JSONArray) { 162 JSONArray array = (JSONArray)obj; 163 for (int i = 0; i < array.length(); i++) { 164 removePathInJson(array.opt(i), pathComponents); 165 } 166 } else if (obj instanceof JSONObject) { 167 List<JSONObjectEntry> childrenForKey = JSONObjectEntry.getEntriesForKey((JSONObject)obj, key); 168 if (pathComponents.size() == 1) { 169 for (JSONObjectEntry child : childrenForKey) { 170 child.remove(); 171 } 172 } else { 173 for (JSONObjectEntry child : childrenForKey) { 174 removePathInJson(child.getValue(), remainingPath); 175 } 176 } 177 } 178 } 179 180 /** 181 * Returns the JSON text as single line, that is as compact as possible. 182 * 183 * @return the JSON text 184 */ 185 public String getCompact() { 186 187 try { 188 return JSONObject.valueToString(m_value); 189 } catch (JSONException e) { 190 throw new RuntimeException(e); 191 } 192 } 193 194 /** 195 * Returns the wrapped JSON object. 196 * 197 * This is an alias for {@link #getObject()}. 198 * 199 * @return the wrapped JSON object 200 * 201 * @see #getObject() 202 */ 203 public Object getJson() { 204 205 return getObject(); 206 } 207 208 /** 209 * Returns the wrapped JSON object. 210 * 211 * Useful in case you want to insert an existing JSON object into another JSON object. 212 * 213 * @return the wrapped JSON object 214 */ 215 public Object getObject() { 216 217 return m_value; 218 } 219 220 /** 221 * Returns the JSON text in pretty-printed and indented format. 222 * 223 * @return the pretty-printed and indented JSON 224 */ 225 public String getPretty() { 226 227 try { 228 return JSONObject.valueToString(m_value, 4, 0); 229 } catch (JSONException e) { 230 throw new RuntimeException(e); 231 } 232 } 233 234 /** 235 * Synonym for {@link #getPretty()}. 236 * 237 * @return the pretty-printed and indented JSON 238 * 239 * @see #getPretty() 240 */ 241 public String getVerbose() { 242 243 return getPretty(); 244 } 245 246 /** 247 * Supports the use of the <code>empty</code> operator in the JSP EL by implementing the Collection interface.<p> 248 * 249 * @see java.util.AbstractCollection#isEmpty() 250 */ 251 @Override 252 public boolean isEmpty() { 253 254 if (m_value instanceof JSONObject) { 255 return ((JSONObject)m_value).length() < 1; 256 } else if (m_value instanceof JSONArray) { 257 return ((JSONArray)m_value).length() < 1; 258 } else if (m_value instanceof String) { 259 return CmsStringUtil.isEmptyOrWhitespaceOnly((String)m_value); 260 } else { 261 return m_value == null; 262 } 263 } 264 265 /** 266 * Supports the use of the <code>empty</code> operator in the JSP EL by implementing the Collection interface.<p> 267 * 268 * @return an empty Iterator in case {@link #isEmpty()} is <code>true</code>, 269 * otherwise an Iterator that will return the String value of this wrapper exactly once.<p> 270 * 271 * @see java.util.AbstractCollection#size() 272 */ 273 @Override 274 public Iterator<Object> iterator() { 275 276 Iterator<Object> it = new Iterator<Object>() { 277 278 private boolean isFirst = true; 279 280 @Override 281 public boolean hasNext() { 282 283 return isFirst && !isEmpty(); 284 } 285 286 @Override 287 public Object next() { 288 289 isFirst = false; 290 return getObject(); 291 } 292 293 @Override 294 public void remove() { 295 296 throw new UnsupportedOperationException(); 297 } 298 }; 299 return it; 300 } 301 302 /** 303 * Removes the parts from the JSON object which match the given path. 304 * 305 * <p> 306 * The path is a slash-separated sequence of path components, where each path component is either the name of a JSON field, or the wildcard '*'. 307 * The removal process locates all JSON objects matching the parent path of the given path, and then removes the entry given by the last path component from it. 308 * <ul> 309 * <li>If a JSON array is encountered while descending the path, the rest of the path is processed for all elements of the array. 310 * <li>If an object is encountered which does not have an entry with the same name as the next path component, it and its contents are left unchanged. 311 * <li> The wildcard '* matches all keys in a JSON object. 312 * </ul> 313 * 314 * @param path the path which should be deleted 315 */ 316 public void removePath(String path) { 317 318 path = path.trim(); 319 removePathInJson(m_value, Arrays.asList(path.split("/"))); 320 321 } 322 323 /** 324 * Supports the use of the <code>empty</code> operator in the JSP EL by implementing the Collection interface.<p> 325 * 326 * @return always returns 0.<p> 327 * 328 * @see java.util.AbstractCollection#size() 329 */ 330 @Override 331 public int size() { 332 333 return isEmpty() ? 0 : 1; 334 } 335 336 /** 337 * @see java.lang.Object#toString() 338 */ 339 @Override 340 public String toString() { 341 342 return getCompact(); 343 } 344}