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.xml.xml2json.renderer; 029 030import org.opencms.configuration.CmsParameterConfiguration; 031import org.opencms.file.CmsObject; 032import org.opencms.json.JSONArray; 033import org.opencms.json.JSONException; 034import org.opencms.json.JSONObject; 035import org.opencms.main.CmsException; 036import org.opencms.main.OpenCms; 037import org.opencms.relations.CmsLink; 038import org.opencms.relations.I_CmsCustomLinkRenderer; 039import org.opencms.xml.content.CmsXmlContent; 040import org.opencms.xml.types.I_CmsXmlContentValue; 041import org.opencms.xml.xml2json.CmsJsonResourceHandler; 042import org.opencms.xml.xml2json.CmsXmlContentTree; 043import org.opencms.xml.xml2json.CmsXmlContentTree.Field; 044import org.opencms.xml.xml2json.CmsXmlContentTree.Node; 045import org.opencms.xml.xml2json.I_CmsJsonFormattableValue; 046import org.opencms.xml.xml2json.handler.CmsJsonHandlerContext; 047 048import java.util.AbstractMap.SimpleEntry; 049import java.util.List; 050import java.util.Locale; 051 052/** 053 * Converts an XML content to JSON by creating a CmsXmlContentTree and then recursively processing its nodes. 054 * 055 * <p>This specific renderer class does not need to be initialized with a CmsJsonHandlerContext, you can 056 * just initialize it with a CmsObject. 057 */ 058public class CmsJsonRendererXmlContent implements I_CmsJsonRendererXmlContent { 059 060 /** The CMS context. */ 061 private CmsObject m_cms; 062 063 /** The root Cms context. */ 064 private CmsObject m_rootCms; 065 066 /** 067 * Creates a new instance. 068 * 069 * If this constructor is used, you still have to call one of the initialize() methods before rendering XML content to JSON. 070 */ 071 public CmsJsonRendererXmlContent() { 072 073 // do nothing 074 } 075 076 /** 077 * Creates a new instance. 078 * 079 * @param cms the CMS context to use 080 * @throws CmsException if something goes wrong 081 */ 082 public CmsJsonRendererXmlContent(CmsObject cms) 083 throws CmsException { 084 085 initialize(cms); 086 } 087 088 /** 089 * Builds a simple JSON object with link and path fields whose values are taken from the corresponding parameters. 090 * 091 * <p>If path is null, it will not be added to the result JSON. 092 * 093 * @param link the value for the link field 094 * @param path the value for the path field 095 * @return the link-and-path object 096 * @throws JSONException if something goes wrong 097 */ 098 public static JSONObject linkAndPath(String link, String path, CmsObject cms) throws JSONException { 099 100 JSONObject result = new JSONObject(); 101 result.put("link", link); 102 if (path != null) { 103 int paramPos = path.indexOf("?"); 104 if (paramPos != -1) { 105 path = path.substring(0, paramPos); 106 } 107 path = OpenCms.getLinkManager().getRootPath(cms, path); 108 result.put("path", path); 109 } 110 return result; 111 } 112 113 /** 114 * Helper method to apply renderer to all locales of an XML content, and put the resulting objects into a JSON object with the locales as keys. 115 * 116 * @param content the content 117 * @param renderer the renderer to use 118 * @return the result JSON 119 * @throws JSONException if something goes wrong 120 */ 121 public static JSONObject renderAllLocales(CmsXmlContent content, I_CmsJsonRendererXmlContent renderer) 122 throws JSONException { 123 124 List<Locale> locales = content.getLocales(); 125 JSONObject result = new JSONObject(true); 126 for (Locale locale : locales) { 127 Object jsonForLocale = renderer.render(content, locale); 128 result.put(locale.toString(), jsonForLocale); 129 } 130 return result; 131 } 132 133 /** 134 * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#addConfigurationParameter(java.lang.String, java.lang.String) 135 */ 136 public void addConfigurationParameter(String paramName, String paramValue) { 137 138 // do nothing 139 } 140 141 /** 142 * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#getConfiguration() 143 */ 144 public CmsParameterConfiguration getConfiguration() { 145 146 return null; 147 } 148 149 /** 150 * @see org.opencms.configuration.I_CmsConfigurationParameterHandler#initConfiguration() 151 */ 152 public void initConfiguration() { 153 154 // do nothing 155 } 156 157 /** 158 * @see org.opencms.xml.xml2json.renderer.I_CmsJsonRendererXmlContent#initialize(org.opencms.xml.xml2json.handler.CmsJsonHandlerContext) 159 */ 160 public void initialize(CmsJsonHandlerContext context) throws CmsException { 161 162 initialize(context.getCms()); 163 } 164 165 /** 166 * Initializes the renderer. 167 * 168 * @param cms the CMS context to use 169 * 170 * @throws CmsException if something goes wrong 171 */ 172 public void initialize(CmsObject cms) throws CmsException { 173 174 m_cms = OpenCms.initCmsObject(cms); 175 Object context = cms.getRequestContext().getAttribute(CmsJsonResourceHandler.ATTR_CONTEXT); 176 m_rootCms = OpenCms.initCmsObject(m_cms); 177 m_rootCms.getRequestContext().setSiteRoot(""); 178 if (context != null) { 179 for (CmsObject currentCms : new CmsObject[] {m_cms, m_rootCms}) { 180 currentCms.getRequestContext().setAttribute(CmsJsonResourceHandler.ATTR_CONTEXT, context); 181 } 182 } 183 I_CmsCustomLinkRenderer linkRenderer = CmsJsonResourceHandler.getLinkRenderer(cms); 184 if (linkRenderer != null) { 185 m_cms.getRequestContext().setAttribute(CmsLink.CUSTOM_LINK_HANDLER, linkRenderer); 186 } 187 } 188 189 /** 190 * @see org.opencms.xml.xml2json.renderer.I_CmsJsonRendererXmlContent#render(org.opencms.xml.content.CmsXmlContent, java.util.Locale) 191 */ 192 @Override 193 public Object render(CmsXmlContent content, Locale locale) throws JSONException { 194 195 CmsXmlContentTree tree = new CmsXmlContentTree(content, locale); 196 m_cms.getRequestContext().setLocale(locale); 197 m_rootCms.getRequestContext().setLocale(locale); 198 Node root = tree.getRoot(); 199 return renderNode(root); 200 201 } 202 203 /** 204 * Renders a tree node as JSON. 205 * 206 * @param node the tree node 207 * @return the JSON (may be JSONObject, JSONArray, or String) 208 * 209 * @throws JSONException if something goes wrong 210 */ 211 public Object renderNode(Node node) throws JSONException { 212 213 switch (node.getType()) { 214 case sequence: 215 List<Field> fields = node.getFields(); 216 JSONObject result = new JSONObject(true); 217 for (Field field : fields) { 218 SimpleEntry<String, Object> keyAndValue = renderField(field); 219 if (keyAndValue != null) { 220 result.put(keyAndValue.getKey(), keyAndValue.getValue()); 221 } 222 } 223 return result; 224 case choice: 225 JSONArray array = new JSONArray(); 226 for (Field field : node.getFields()) { 227 228 SimpleEntry<String, Object> keyAndValue = renderField(field); 229 if (keyAndValue != null) { 230 JSONObject choiceObj = new JSONObject(true); 231 choiceObj.put(keyAndValue.getKey(), keyAndValue.getValue()); 232 array.put(choiceObj); 233 } 234 235 } 236 return array; 237 case simple: 238 Object valueJson = renderSimpleValue(node); 239 return valueJson; 240 default: 241 throw new IllegalArgumentException("Unsupported node: " + node.getType()); 242 243 } 244 245 } 246 247 /** 248 * Renders a tree field as a field in the given JSON object. 249 * 250 * @param field the field to render 251 * @return the key/value pair for the field 252 * 253 * @throws JSONException if something goes wrong 254 */ 255 protected SimpleEntry<String, Object> renderField(Field field) throws JSONException { 256 257 String name = field.getName(); 258 if (field.isMultivalue()) { 259 // If field is *potentially* multivalue, 260 // we always generate a JSON array for the sake of consistency, 261 // no matter how many actual values we currently have 262 JSONArray array = new JSONArray(); 263 if (field.getFieldDefinition().isChoiceType()) { 264 265 // Multiple choice values can be represented by either a multivalued field of single-valued choices, 266 // or a single-valued field of multivalue choices. Using both in combination doesn't seem to make sense. 267 // So we collapse consecutive choice values in a multivalue field to give a uniform JSON syntax for the two 268 // cases to make the JSON easier to work with, but we lose some information about the structure of the XML. 269 for (Node subNode : field.getNodes()) { 270 JSONArray choiceJson = (JSONArray)renderNode(subNode); 271 array.append(choiceJson); 272 } 273 } else { 274 for (Node subNode : field.getNodes()) { 275 array.put(renderNode(subNode)); 276 } 277 278 } 279 return new SimpleEntry<>(name, array); 280 } else if (field.getNodes().size() == 1) { 281 if (field.getFieldDefinition().isChoiceType() && !field.isMultiChoice()) { 282 // field *and* choice single-valued, so we can unwrap the single value 283 JSONArray array = (JSONArray)renderNode(field.getNode()); 284 if (array.length() == 1) { 285 return new SimpleEntry<>(name, array.get(0)); 286 287 } 288 } else { 289 return new SimpleEntry<>(name, renderNode(field.getNodes().get(0))); 290 } 291 } 292 return null; 293 } 294 295 /** 296 * Renders a simple value (i.e. not a nested content). 297 * 298 * @param node the node 299 * @return the JSON representation for the value 300 * @throws JSONException if something goes wrong 301 */ 302 protected Object renderSimpleValue(Node node) throws JSONException { 303 304 I_CmsXmlContentValue value = node.getValue(); 305 if (value instanceof I_CmsJsonFormattableValue) { 306 return ((I_CmsJsonFormattableValue)value).toJson(m_cms); 307 } else { 308 return node.getValue().getStringValue(m_cms); 309 } 310 } 311 312}