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.document;
029
030import org.opencms.file.CmsFile;
031import org.opencms.file.CmsResource;
032import org.opencms.file.CmsResourceFilter;
033import org.opencms.file.types.CmsResourceTypeXmlContainerPage;
034import org.opencms.file.types.CmsResourceTypeXmlContent;
035import org.opencms.i18n.CmsLocaleManager;
036import org.opencms.json.JSONArray;
037import org.opencms.json.JSONException;
038import org.opencms.json.JSONObject;
039import org.opencms.main.CmsException;
040import org.opencms.main.OpenCms;
041import org.opencms.relations.CmsRelation;
042import org.opencms.relations.CmsRelationFilter;
043import org.opencms.util.CmsStringUtil;
044import org.opencms.xml.content.CmsXmlContent;
045import org.opencms.xml.content.CmsXmlContentFactory;
046import org.opencms.xml.content.I_CmsXmlContentHandler;
047import org.opencms.xml.content.I_CmsXmlContentHandler.JsonRendererSettings;
048import org.opencms.xml.xml2json.CmsJsonRequest;
049import org.opencms.xml.xml2json.handler.CmsJsonHandlerException;
050import org.opencms.xml.xml2json.handler.CmsJsonHandlerXmlContent.PathNotFoundException;
051import org.opencms.xml.xml2json.renderer.CmsJsonRendererXmlContent;
052import org.opencms.xml.xml2json.renderer.I_CmsJsonRendererXmlContent;
053
054import java.util.Collections;
055import java.util.List;
056import java.util.Locale;
057import java.util.Map;
058
059import org.apache.commons.lang3.StringUtils;
060
061/**
062 * Class representing a JSON document for an XML content.
063 */
064public class CmsJsonDocumentXmlContent extends CmsJsonDocumentResource {
065
066    /** The XML content. */
067    protected CmsXmlContent m_xmlContent;
068
069    /** Whether to throw exceptions. */
070    protected boolean m_throwException = true;
071
072    /** Whether to embed linked contents. */
073    protected boolean m_embedLinkedModelgroup = true;
074
075    /** The JSON part in the case of a path request. */
076    private Object m_jsonPart;
077
078    /** The XML content renderer. */
079    private I_CmsJsonRendererXmlContent m_renderer;
080
081    /**
082     * Creates a new JSON document.<p>
083     *
084     * @param jsonRequest the JSON request
085     * @param xmlContent the XML content
086     * @throws Exception if something goes wrong
087     */
088    public CmsJsonDocumentXmlContent(CmsJsonRequest jsonRequest, CmsXmlContent xmlContent)
089    throws Exception {
090
091        this(jsonRequest, xmlContent, true);
092    }
093
094    /**
095     * Creates a new JSON document.<p>
096     *
097     * @param jsonRequest the JSON request
098     * @param xmlContent the XML content
099     * @param embedLinkedModelgroup whether to embed linked model groups
100     * @throws Exception if something goes wrong
101     */
102    public CmsJsonDocumentXmlContent(
103        CmsJsonRequest jsonRequest,
104        CmsXmlContent xmlContent,
105        boolean embedLinkedModelgroup)
106    throws Exception {
107
108        super(jsonRequest, xmlContent.getFile());
109        m_xmlContent = xmlContent;
110        m_embedLinkedModelgroup = embedLinkedModelgroup;
111        initRenderer();
112    }
113
114    /**
115     * @see org.opencms.xml.xml2json.document.CmsJsonDocumentResource#getJson()
116     */
117    @Override
118    public Object getJson()
119    throws JSONException, CmsException, CmsJsonHandlerException, PathNotFoundException, Exception {
120
121        insertJsonContent();
122        insertJsonWrapper();
123        if (isShowLinkedContentsRequest()) {
124            insertJsonLinkedContents();
125        }
126        return m_jsonPart != null ? m_jsonPart : m_json;
127    }
128
129    /**
130     * Inserts a JSON representation of a linked content into this JSON document.<p>
131     *
132     * @param resource the resource
133     * @throws Exception if something goes wrong
134     */
135    protected void insertJsonLinkedContent(CmsResource resource) throws Exception {
136
137        JSONObject jsonObject = (JSONObject)m_json.get(FIELD_LINKED_CONTENTS);
138        String key = resource.getRootPath();
139        try {
140            CmsFile file = m_context.getCms().readFile(resource);
141            CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(m_context.getCms(), file);
142            Object value = null;
143            if (CmsResourceTypeXmlContainerPage.isContainerPage(resource) && m_embedLinkedModelgroup) {
144                CmsJsonDocumentContainerPage document = new CmsJsonDocumentContainerPage(
145                    m_jsonRequest,
146                    xmlContent,
147                    false);
148                value = document.getJson();
149            } else if (CmsResourceTypeXmlContent.isXmlContent(resource)) {
150                CmsJsonDocumentXmlContent document = new CmsJsonDocumentXmlContent(m_jsonRequest, xmlContent, false);
151                value = document.getJson();
152            }
153            jsonObject.put(key, value);
154        } catch (Exception e) {
155            JSONObject exception = new JSONObject();
156            exception.put("exception", e.getLocalizedMessage());
157            jsonObject.put(key, exception);
158        }
159    }
160
161    /**
162     * For each linked content, inserts a JSON representation of the linked content into this JSON document.<p>
163     *
164     * @throws Exception if something goes wrong
165     */
166    protected void insertJsonLinkedContents() throws Exception {
167
168        List<CmsRelation> relationList = m_context.getCms().getRelationsForResource(
169            m_xmlContent.getFile(),
170            CmsRelationFilter.TARGETS);
171        m_json.put(FIELD_LINKED_CONTENTS, new JSONObject());
172        for (CmsRelation relation : relationList) {
173            CmsResource resource = relation.getTarget(m_context.getCms(), CmsResourceFilter.DEFAULT);
174            if (CmsResourceTypeXmlContent.isXmlContent(resource)
175                || CmsResourceTypeXmlContainerPage.isContainerPage(resource)) {
176                insertJsonLinkedContent(resource);
177            }
178        }
179    }
180
181    /**
182     * Inserts a wrapper with resource information into this JSON document.<p>
183     *
184     * @throws JSONException if JSON rendering fails
185     * @throws CmsException if reading resource properties fails
186     */
187    protected void insertJsonWrapper() throws JSONException, CmsException {
188
189        if (isShowWrapperRequest()) {
190            insertJsonLocales();
191            insertJsonResource();
192        }
193    }
194
195    /**
196     * Whether all locales of this XML content are requested.<p>
197     *
198     * @return whether all or not
199     */
200    protected boolean isLocaleAllRequest() {
201
202        String paramLocale = m_jsonRequest.getParamLocale();
203        String paramPath = m_jsonRequest.getParamPath();
204        return (paramLocale == null) && (paramPath == null);
205    }
206
207    /**
208     * Whether one locale of this XML content is requested.<p>
209     *
210     * @return whether one locale or not
211     */
212    protected boolean isLocalePathRequest() {
213
214        String paramLocale = m_jsonRequest.getParamLocale();
215        String paramPath = m_jsonRequest.getParamPath();
216        return (paramLocale != null) && (paramPath != null);
217
218    }
219
220    /**
221     * Whether a part of a locale of this XML content is requested.<p>
222     *
223     * @return whether a part of a locale or not
224     */
225    protected boolean isLocaleRequest() {
226
227        String paramLocale = m_jsonRequest.getParamLocale();
228        String paramPath = m_jsonRequest.getParamPath();
229        return (paramLocale != null) && (paramPath == null);
230    }
231
232    /**
233     * Whether the default locale content shall be shown in the case
234     * the requested locale is not available.<p>
235     *
236     * @return whether to show the default locale or not
237     */
238    protected boolean isShowFallbackLocaleRequest() {
239
240        return m_jsonRequest.getParamFallbackLocale().booleanValue();
241    }
242
243    /**
244     * Whether all linked contents shall be embedded into this document.
245     *
246     * @return whether to embed the linked contents or not
247     */
248    protected boolean isShowLinkedContentsRequest() {
249
250        return m_jsonRequest.getParamContent().booleanValue();
251    }
252
253    /**
254     * Whether to show the wrapper with resource information. For backward
255     * compatibility the wrapper is shown for the all locale request but not for
256     * the locale and locale path request as a default. This default behavior
257     * can be changed by means of the "wrapper" request parameter. For locale
258     * path requests, wrapper information is not available.
259     *
260     * @return whether to show the wrapper or not
261     */
262    protected boolean isShowWrapperRequest() {
263
264        boolean showWrapper = true;
265        String paramWrapperRaw = m_context.getParameters().get(CmsJsonRequest.PARAM_WRAPPER);
266        Boolean paramWrapper = m_jsonRequest.getParamWrapper();
267        if (isLocaleAllRequest()) {
268            showWrapper = true;
269            if ((paramWrapperRaw != null) && (paramWrapperRaw.equals("false"))) {
270                showWrapper = false;
271            }
272        } else if (isLocaleRequest()) {
273            showWrapper = false;
274            if (paramWrapper.booleanValue()) {
275                showWrapper = true;
276            }
277        } else if (isLocalePathRequest()) {
278            showWrapper = false;
279        }
280        return showWrapper;
281    }
282
283    /**
284     * Initializes the content renderer.<p>
285     *
286     * @throws Exception if something goes wrong
287     */
288    private void initRenderer() throws Exception {
289
290        I_CmsXmlContentHandler handler = m_xmlContent.getContentDefinition().getContentHandler();
291        JsonRendererSettings settings = handler.getJsonRendererSettings();
292        if (settings == null) {
293            m_renderer = new CmsJsonRendererXmlContent();
294        } else {
295            m_renderer = (I_CmsJsonRendererXmlContent)Class.forName(settings.getClassName()).newInstance();
296            for (Map.Entry<String, String> entry : settings.getParameters().entrySet()) {
297                m_renderer.addConfigurationParameter(entry.getKey(), entry.getValue());
298            }
299            m_renderer.initConfiguration();
300        }
301        m_renderer.initialize(m_context.getCms());
302    }
303
304    /**
305     * Inserts a JSON representation of this XML content into this JSON document.<p>
306     *
307     * @throws CmsJsonHandlerException if something goes wrong
308     * @throws JSONException if JSON rendering fails
309     * @throws PathNotFoundException if path selection fails
310     */
311    private void insertJsonContent() throws CmsJsonHandlerException, JSONException, PathNotFoundException {
312
313        if (isLocaleAllRequest()) {
314            insertJsonContentAllLocales();
315        } else if (isLocaleRequest()) {
316            insertJsonContentLocale();
317        } else if (isLocalePathRequest()) {
318            insertJsonContentLocalePath();
319        } else {
320            throw new CmsJsonHandlerException("Can not use path parameter without locale parameter.");
321        }
322    }
323
324    /**
325     * Inserts all locale contents into this JSON document.<p>
326     *
327     * @throws JSONException if JSON rendering fails
328     */
329    private void insertJsonContentAllLocales() throws JSONException {
330
331        m_json = CmsJsonRendererXmlContent.renderAllLocales(m_xmlContent, m_renderer);
332    }
333
334    /**
335     * Inserts one locale content into this JSON document.<p>
336     *
337     * @throws JSONException if JSON rendering fails
338     * @throws PathNotFoundException if the selected locale does not exist and
339     * no fallbackLocale parameter is given
340     */
341    private void insertJsonContentLocale() throws JSONException, PathNotFoundException {
342
343        String paramLocale = m_jsonRequest.getParamLocale();
344        Locale locale = CmsLocaleManager.getLocale(paramLocale);
345        Locale selectedLocale = OpenCms.getLocaleManager().getBestMatchingLocale(
346            locale,
347            Collections.emptyList(),
348            m_xmlContent.getLocales());
349        boolean localeExists = true;
350        if ((selectedLocale == null) || !m_xmlContent.hasLocale(selectedLocale)) {
351            localeExists = false;
352        }
353        JSONObject jsonObject = new JSONObject(true);
354        if (localeExists) {
355            jsonObject = (JSONObject)m_renderer.render(m_xmlContent, selectedLocale);
356        } else if (isShowFallbackLocaleRequest()) {
357            List<Locale> localeList = m_xmlContent.getLocales();
358            if (!localeList.isEmpty()) {
359                jsonObject = (JSONObject)m_renderer.render(m_xmlContent, localeList.get(0));
360                m_json.put("localeFallback", localeList.get(0).toString());
361            }
362        } else if (m_throwException) {
363            throw new PathNotFoundException("Locale <" + this.m_jsonRequest.getParamLocale() + "> not found.");
364        }
365        if (isShowWrapperRequest()) {
366            m_json.put("localeContent", jsonObject);
367        } else {
368            m_json = jsonObject;
369        }
370    }
371
372    /**
373     * Inserts a fragment of one locale content into this JSON document.<p>
374     *
375     * @throws PathNotFoundException if the path selection fails
376     * @throws JSONException if JSON rendering fails
377     */
378    private void insertJsonContentLocalePath() throws PathNotFoundException, JSONException {
379
380        insertJsonContentLocale();
381        String paramPath = m_jsonRequest.getParamPath();
382        String[] tokens = paramPath.split("[/\\[\\]]");
383        Object current = m_json;
384        for (String token : tokens) {
385            if (CmsStringUtil.isEmptyOrWhitespaceOnly(token)) {
386                continue;
387            }
388            if (StringUtils.isNumeric(token) && (current instanceof JSONArray)) {
389                current = ((JSONArray)current).get(Integer.parseInt(token));
390            } else if (current instanceof JSONObject) {
391                current = ((JSONObject)current).get(token);
392            } else {
393                throw new PathNotFoundException("Path not found");
394            }
395        }
396        m_jsonPart = current;
397    }
398
399    /**
400     * Insert a list of all locale names available for this content.<p>
401     *
402     * @throws JSONException if JSON rendering fails
403     */
404    private void insertJsonLocales() throws JSONException {
405
406        JSONArray locales = new JSONArray();
407        for (Locale locale : m_xmlContent.getLocales()) {
408            locales.put(locale.toString());
409        }
410        m_json.put("locales", locales);
411    }
412}