001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portlet.journal.util;
016    
017    import com.liferay.portal.kernel.configuration.Filter;
018    import com.liferay.portal.kernel.io.unsync.UnsyncStringWriter;
019    import com.liferay.portal.kernel.log.Log;
020    import com.liferay.portal.kernel.log.LogFactoryUtil;
021    import com.liferay.portal.kernel.mobile.device.Device;
022    import com.liferay.portal.kernel.mobile.device.UnknownDevice;
023    import com.liferay.portal.kernel.portlet.PortletRequestModel;
024    import com.liferay.portal.kernel.template.StringTemplateResource;
025    import com.liferay.portal.kernel.template.Template;
026    import com.liferay.portal.kernel.template.TemplateConstants;
027    import com.liferay.portal.kernel.template.TemplateManagerUtil;
028    import com.liferay.portal.kernel.template.TemplateResource;
029    import com.liferay.portal.kernel.template.URLTemplateResource;
030    import com.liferay.portal.kernel.templateparser.TemplateNode;
031    import com.liferay.portal.kernel.templateparser.TransformException;
032    import com.liferay.portal.kernel.templateparser.TransformerListener;
033    import com.liferay.portal.kernel.util.Constants;
034    import com.liferay.portal.kernel.util.GetterUtil;
035    import com.liferay.portal.kernel.util.InstanceFactory;
036    import com.liferay.portal.kernel.util.LocaleUtil;
037    import com.liferay.portal.kernel.util.LocalizationUtil;
038    import com.liferay.portal.kernel.util.PortalClassLoaderUtil;
039    import com.liferay.portal.kernel.util.PropertiesUtil;
040    import com.liferay.portal.kernel.util.SetUtil;
041    import com.liferay.portal.kernel.util.StringBundler;
042    import com.liferay.portal.kernel.util.StringPool;
043    import com.liferay.portal.kernel.util.StringUtil;
044    import com.liferay.portal.kernel.util.Validator;
045    import com.liferay.portal.kernel.xml.Attribute;
046    import com.liferay.portal.kernel.xml.Document;
047    import com.liferay.portal.kernel.xml.DocumentException;
048    import com.liferay.portal.kernel.xml.Element;
049    import com.liferay.portal.kernel.xml.SAXReaderUtil;
050    import com.liferay.portal.model.Company;
051    import com.liferay.portal.security.permission.PermissionThreadLocal;
052    import com.liferay.portal.service.CompanyLocalServiceUtil;
053    import com.liferay.portal.theme.ThemeDisplay;
054    import com.liferay.portal.util.PropsUtil;
055    import com.liferay.portal.xsl.XSLTemplateResource;
056    import com.liferay.portal.xsl.XSLURIResolver;
057    
058    import java.io.IOException;
059    
060    import java.net.URL;
061    
062    import java.util.ArrayList;
063    import java.util.HashMap;
064    import java.util.HashSet;
065    import java.util.List;
066    import java.util.Locale;
067    import java.util.Map;
068    import java.util.Set;
069    
070    /**
071     * @author Brian Wing Shun Chan
072     * @author Raymond Aug??
073     * @author Wesley Gong
074     * @author Angelo Jefferson
075     * @author Hugo Huijser
076     * @author Marcellus Tavares
077     * @author Juan Fern??ndez
078     * @author Eduardo Garcia
079     */
080    public class JournalTransformer {
081    
082            public JournalTransformer(
083                    String errorTemplatePropertyKey, boolean restricted) {
084    
085                    Set<String> langTypes = TemplateManagerUtil.getSupportedLanguageTypes(
086                            errorTemplatePropertyKey);
087    
088                    for (String langType : langTypes) {
089                            String errorTemplateId = PropsUtil.get(
090                                    errorTemplatePropertyKey, new Filter(langType));
091    
092                            if (Validator.isNotNull(errorTemplateId)) {
093                                    _errorTemplateIds.put(langType, errorTemplateId);
094                            }
095                    }
096    
097                    _restricted = restricted;
098            }
099    
100            public JournalTransformer(
101                    String transformerListenerPropertyKey, String errorTemplatePropertyKey,
102                    boolean restricted) {
103    
104                    this(errorTemplatePropertyKey, restricted);
105    
106                    ClassLoader classLoader = PortalClassLoaderUtil.getClassLoader();
107    
108                    Set<String> transformerListenerClassNames = SetUtil.fromArray(
109                            PropsUtil.getArray(transformerListenerPropertyKey));
110    
111                    for (String transformerListenerClassName :
112                                    transformerListenerClassNames) {
113    
114                            try {
115                                    if (_log.isDebugEnabled()) {
116                                            _log.debug(
117                                                    "Instantiating transformer listener " +
118                                                            transformerListenerClassName);
119                                    }
120    
121                                    TransformerListener transformerListener =
122                                            (TransformerListener)InstanceFactory.newInstance(
123                                                    classLoader, transformerListenerClassName);
124    
125                                    _transformerListeners.add(transformerListener);
126                            }
127                            catch (Exception e) {
128                                    _log.error(e, e);
129                            }
130                    }
131            }
132    
133            public String transform(
134                            ThemeDisplay themeDisplay, Map<String, Object> contextObjects,
135                            Map<String, String> tokens, String viewMode, String languageId,
136                            Document document, PortletRequestModel portletRequestModel,
137                            String script, String langType, boolean propagateException)
138                    throws Exception {
139    
140                    return doTransform(
141                            themeDisplay, contextObjects, tokens, viewMode, languageId,
142                            document, portletRequestModel, script, langType,
143                            propagateException);
144            }
145    
146            public String transform(
147                            ThemeDisplay themeDisplay, Map<String, String> tokens,
148                            String viewMode, String languageId, Document document,
149                            PortletRequestModel portletRequestModel, String script,
150                            String langType)
151                    throws Exception {
152    
153                    return doTransform(
154                            themeDisplay, null, tokens, viewMode, languageId, document,
155                            portletRequestModel, script, langType, false);
156            }
157    
158            public String transform(
159                            ThemeDisplay themeDisplay, Map<String, String> tokens,
160                            String viewMode, String languageId, Document document,
161                            PortletRequestModel portletRequestModel, String script,
162                            String langType, boolean propagateException)
163                    throws Exception {
164    
165                    return doTransform(
166                            themeDisplay, null, tokens, viewMode, languageId, document,
167                            portletRequestModel, script, langType, propagateException);
168            }
169    
170            protected String doTransform(
171                            ThemeDisplay themeDisplay, Map<String, Object> contextObjects,
172                            Map<String, String> tokens, String viewMode, String languageId,
173                            Document document, PortletRequestModel portletRequestModel,
174                            String script, String langType, boolean propagateException)
175                    throws Exception {
176    
177                    // Setup listeners
178    
179                    if (_log.isDebugEnabled()) {
180                            _log.debug("Language " + languageId);
181                    }
182    
183                    if (Validator.isNull(viewMode)) {
184                            viewMode = Constants.VIEW;
185                    }
186    
187                    if (_logTokens.isDebugEnabled()) {
188                            String tokensString = PropertiesUtil.list(tokens);
189    
190                            _logTokens.debug(tokensString);
191                    }
192    
193                    if (_logTransformBefore.isDebugEnabled()) {
194                            _logTransformBefore.debug(document);
195                    }
196    
197                    for (TransformerListener transformerListener : _transformerListeners) {
198    
199                            // Modify XML
200    
201                            if (_logXmlBeforeListener.isDebugEnabled()) {
202                                    _logXmlBeforeListener.debug(document);
203                            }
204    
205                            if (transformerListener != null) {
206                                    document = transformerListener.onXml(
207                                            document, languageId, tokens);
208    
209                                    if (_logXmlAfterListener.isDebugEnabled()) {
210                                            _logXmlAfterListener.debug(document);
211                                    }
212                            }
213    
214                            // Modify script
215    
216                            if (_logScriptBeforeListener.isDebugEnabled()) {
217                                    _logScriptBeforeListener.debug(script);
218                            }
219    
220                            if (transformerListener != null) {
221                                    script = transformerListener.onScript(
222                                            script, document, languageId, tokens);
223    
224                                    if (_logScriptAfterListener.isDebugEnabled()) {
225                                            _logScriptAfterListener.debug(script);
226                                    }
227                            }
228                    }
229    
230                    // Transform
231    
232                    String output = null;
233    
234                    if (Validator.isNull(langType)) {
235                            output = LocalizationUtil.getLocalization(
236                                    document.asXML(), languageId);
237                    }
238                    else {
239                            long companyId = 0;
240                            long companyGroupId = 0;
241                            long articleGroupId = 0;
242                            long classNameId = 0;
243    
244                            if (tokens != null) {
245                                    companyId = GetterUtil.getLong(tokens.get("company_id"));
246                                    companyGroupId = GetterUtil.getLong(
247                                            tokens.get("company_group_id"));
248                                    articleGroupId = GetterUtil.getLong(
249                                            tokens.get("article_group_id"));
250                                    classNameId = GetterUtil.getLong(
251                                            tokens.get(TemplateConstants.CLASS_NAME_ID));
252                            }
253    
254                            long scopeGroupId = 0;
255                            long siteGroupId = 0;
256    
257                            if (themeDisplay != null) {
258                                    companyId = themeDisplay.getCompanyId();
259                                    companyGroupId = themeDisplay.getCompanyGroupId();
260                                    scopeGroupId = themeDisplay.getScopeGroupId();
261                                    siteGroupId = themeDisplay.getSiteGroupId();
262                            }
263    
264                            String templateId = tokens.get("template_id");
265    
266                            templateId = getTemplateId(
267                                    templateId, companyId, companyGroupId, articleGroupId);
268    
269                            Template template = getTemplate(
270                                    templateId, tokens, languageId, document, script, langType);
271    
272                            if (contextObjects != null) {
273                                    for (String key : contextObjects.keySet()) {
274                                            template.put(key, contextObjects.get(key));
275                                    }
276                            }
277    
278                            UnsyncStringWriter unsyncStringWriter = new UnsyncStringWriter();
279    
280                            try {
281                                    if (document != null) {
282                                            Element rootElement = document.getRootElement();
283    
284                                            List<TemplateNode> templateNodes = getTemplateNodes(
285                                                    themeDisplay, rootElement);
286    
287                                            if (templateNodes != null) {
288                                                    for (TemplateNode templateNode : templateNodes) {
289                                                            template.put(templateNode.getName(), templateNode);
290                                                    }
291                                            }
292    
293                                            if (portletRequestModel != null) {
294                                                    template.put("request", portletRequestModel.toMap());
295    
296                                                    if (langType.equals(TemplateConstants.LANG_TYPE_XSL)) {
297                                                            Document requestDocument = SAXReaderUtil.read(
298                                                                    portletRequestModel.toXML());
299    
300                                                            Element requestElement =
301                                                                    requestDocument.getRootElement();
302    
303                                                            template.put("xmlRequest", requestElement.asXML());
304                                                    }
305                                            }
306                                            else {
307                                                    Element requestElement = rootElement.element("request");
308    
309                                                    template.put(
310                                                            "request", insertRequestVariables(requestElement));
311    
312                                                    if (langType.equals(TemplateConstants.LANG_TYPE_XSL)) {
313                                                            template.put("xmlRequest", requestElement.asXML());
314                                                    }
315                                            }
316                                    }
317    
318                                    template.put("articleGroupId", articleGroupId);
319                                    template.put("company", getCompany(themeDisplay, companyId));
320                                    template.put("companyId", companyId);
321                                    template.put("device", getDevice(themeDisplay));
322    
323                                    String templatesPath = getTemplatesPath(
324                                            companyId, articleGroupId, classNameId);
325    
326                                    Locale locale = LocaleUtil.fromLanguageId(languageId);
327    
328                                    template.put("locale", locale);
329    
330                                    template.put(
331                                            "permissionChecker",
332                                            PermissionThreadLocal.getPermissionChecker());
333                                    template.put(
334                                            "randomNamespace",
335                                            StringUtil.randomId() + StringPool.UNDERLINE);
336                                    template.put("scopeGroupId", scopeGroupId);
337                                    template.put("siteGroupId", siteGroupId);
338                                    template.put("templatesPath", templatesPath);
339                                    template.put("viewMode", viewMode);
340    
341                                    // Deprecated variables
342    
343                                    template.put("groupId", articleGroupId);
344                                    template.put("journalTemplatesPath", templatesPath);
345    
346                                    mergeTemplate(template, unsyncStringWriter, propagateException);
347                            }
348                            catch (Exception e) {
349                                    if (e instanceof DocumentException) {
350                                            throw new TransformException(
351                                                    "Unable to read XML document", e);
352                                    }
353                                    else if (e instanceof IOException) {
354                                            throw new TransformException("Error reading template", e);
355                                    }
356                                    else if (e instanceof TransformException) {
357                                            throw (TransformException)e;
358                                    }
359                                    else {
360                                            throw new TransformException("Unhandled exception", e);
361                                    }
362                            }
363    
364                            output = unsyncStringWriter.toString();
365                    }
366    
367                    // Postprocess output
368    
369                    for (TransformerListener transformerListener : _transformerListeners) {
370    
371                            // Modify output
372    
373                            if (_logOutputBeforeListener.isDebugEnabled()) {
374                                    _logOutputBeforeListener.debug(output);
375                            }
376    
377                            output = transformerListener.onOutput(output, languageId, tokens);
378    
379                            if (_logOutputAfterListener.isDebugEnabled()) {
380                                    _logOutputAfterListener.debug(output);
381                            }
382                    }
383    
384                    if (_logTransfromAfter.isDebugEnabled()) {
385                            _logTransfromAfter.debug(output);
386                    }
387    
388                    return output;
389            }
390    
391            protected Company getCompany(ThemeDisplay themeDisplay, long companyId)
392                    throws Exception {
393    
394                    if (themeDisplay != null) {
395                            return themeDisplay.getCompany();
396                    }
397    
398                    return CompanyLocalServiceUtil.getCompany(companyId);
399            }
400    
401            protected Device getDevice(ThemeDisplay themeDisplay) {
402                    if (themeDisplay != null) {
403                            return themeDisplay.getDevice();
404                    }
405    
406                    return UnknownDevice.getInstance();
407            }
408    
409            protected TemplateResource getErrorTemplateResource(String langType) {
410                    try {
411                            Class<?> clazz = getClass();
412    
413                            ClassLoader classLoader = clazz.getClassLoader();
414    
415                            String errorTemplateId = _errorTemplateIds.get(langType);
416    
417                            URL url = classLoader.getResource(errorTemplateId);
418    
419                            return new URLTemplateResource(errorTemplateId, url);
420                    }
421                    catch (Exception e) {
422                    }
423    
424                    return null;
425            }
426    
427            protected Template getTemplate(
428                            String templateId, Map<String, String> tokens, String languageId,
429                            Document document, String script, String langType)
430                    throws Exception {
431    
432                    TemplateResource templateResource = null;
433    
434                    if (langType.equals(TemplateConstants.LANG_TYPE_XSL)) {
435                            XSLURIResolver xslURIResolver = new JournalXSLURIResolver(
436                                    tokens, languageId);
437    
438                            templateResource = new XSLTemplateResource(
439                                    templateId, script, xslURIResolver, document.asXML());
440                    }
441                    else {
442                            templateResource = new StringTemplateResource(templateId, script);
443                    }
444    
445                    TemplateResource errorTemplateResource = getErrorTemplateResource(
446                            langType);
447    
448                    return TemplateManagerUtil.getTemplate(
449                            langType, templateResource, errorTemplateResource, _restricted);
450            }
451    
452            protected String getTemplateId(
453                    String templateId, long companyId, long companyGroupId, long groupId) {
454    
455                    StringBundler sb = new StringBundler(5);
456    
457                    sb.append(companyId);
458                    sb.append(StringPool.POUND);
459    
460                    if (companyGroupId > 0) {
461                            sb.append(companyGroupId);
462                    }
463                    else {
464                            sb.append(groupId);
465                    }
466    
467                    sb.append(StringPool.POUND);
468                    sb.append(templateId);
469    
470                    return sb.toString();
471            }
472    
473            protected List<TemplateNode> getTemplateNodes(
474                            ThemeDisplay themeDisplay, Element element)
475                    throws Exception {
476    
477                    List<TemplateNode> templateNodes = new ArrayList<>();
478    
479                    Map<String, TemplateNode> prototypeTemplateNodes = new HashMap<>();
480    
481                    List<Element> dynamicElementElements = element.elements(
482                            "dynamic-element");
483    
484                    for (Element dynamicElementElement : dynamicElementElements) {
485                            Element dynamicContentElement = dynamicElementElement.element(
486                                    "dynamic-content");
487    
488                            String data = StringPool.BLANK;
489    
490                            if (dynamicContentElement != null) {
491                                    data = dynamicContentElement.getText();
492                            }
493    
494                            String name = dynamicElementElement.attributeValue(
495                                    "name", StringPool.BLANK);
496    
497                            if (name.length() == 0) {
498                                    throw new TransformException(
499                                            "Element missing \"name\" attribute");
500                            }
501    
502                            String type = dynamicElementElement.attributeValue(
503                                    "type", StringPool.BLANK);
504    
505                            Map<String, String> attributes = new HashMap<>();
506    
507                            if (dynamicContentElement != null) {
508                                    for (Attribute attribute : dynamicContentElement.attributes()) {
509                                            attributes.put(attribute.getName(), attribute.getValue());
510                                    }
511                            }
512    
513                            TemplateNode templateNode = new TemplateNode(
514                                    themeDisplay, name, StringUtil.stripCDATA(data), type,
515                                    attributes);
516    
517                            if (dynamicElementElement.element("dynamic-element") != null) {
518                                    templateNode.appendChildren(
519                                            getTemplateNodes(themeDisplay, dynamicElementElement));
520                            }
521                            else if ((dynamicContentElement != null) &&
522                                             (dynamicContentElement.element("option") != null)) {
523    
524                                    List<Element> optionElements = dynamicContentElement.elements(
525                                            "option");
526    
527                                    for (Element optionElement : optionElements) {
528                                            templateNode.appendOption(
529                                                    StringUtil.stripCDATA(optionElement.getText()));
530                                    }
531                            }
532    
533                            TemplateNode prototypeTemplateNode = prototypeTemplateNodes.get(
534                                    name);
535    
536                            if (prototypeTemplateNode == null) {
537                                    prototypeTemplateNode = templateNode;
538    
539                                    prototypeTemplateNodes.put(name, prototypeTemplateNode);
540    
541                                    templateNodes.add(templateNode);
542                            }
543    
544                            prototypeTemplateNode.appendSibling(templateNode);
545                    }
546    
547                    return templateNodes;
548            }
549    
550            protected String getTemplatesPath(
551                    long companyId, long groupId, long classNameId) {
552    
553                    StringBundler sb = new StringBundler(7);
554    
555                    sb.append(TemplateConstants.TEMPLATE_SEPARATOR);
556                    sb.append(StringPool.SLASH);
557                    sb.append(companyId);
558                    sb.append(StringPool.SLASH);
559                    sb.append(groupId);
560                    sb.append(StringPool.SLASH);
561                    sb.append(classNameId);
562    
563                    return sb.toString();
564            }
565    
566            protected Map<String, Object> insertRequestVariables(Element element) {
567                    Map<String, Object> map = new HashMap<>();
568    
569                    if (element == null) {
570                            return map;
571                    }
572    
573                    for (Element childElement : element.elements()) {
574                            String name = childElement.getName();
575    
576                            if (name.equals("attribute")) {
577                                    Element nameElement = childElement.element("name");
578                                    Element valueElement = childElement.element("value");
579    
580                                    map.put(nameElement.getText(), valueElement.getText());
581                            }
582                            else if (name.equals("parameter")) {
583                                    Element nameElement = childElement.element("name");
584    
585                                    List<Element> valueElements = childElement.elements("value");
586    
587                                    if (valueElements.size() == 1) {
588                                            Element valueElement = valueElements.get(0);
589    
590                                            map.put(nameElement.getText(), valueElement.getText());
591                                    }
592                                    else {
593                                            List<String> values = new ArrayList<>();
594    
595                                            for (Element valueElement : valueElements) {
596                                                    values.add(valueElement.getText());
597                                            }
598    
599                                            map.put(nameElement.getText(), values);
600                                    }
601                            }
602                            else {
603                                    List<Element> elements = childElement.elements();
604    
605                                    if (!elements.isEmpty()) {
606                                            map.put(name, insertRequestVariables(childElement));
607                                    }
608                                    else {
609                                            map.put(name, childElement.getText());
610                                    }
611                            }
612                    }
613    
614                    return map;
615            }
616    
617            protected void mergeTemplate(
618                            Template template, UnsyncStringWriter unsyncStringWriter,
619                            boolean propagateException)
620                    throws Exception {
621    
622                    if (propagateException) {
623                            template.doProcessTemplate(unsyncStringWriter);
624                    }
625                    else {
626                            template.processTemplate(unsyncStringWriter);
627                    }
628            }
629    
630            private static final Log _log = LogFactoryUtil.getLog(
631                    JournalTransformer.class);
632    
633            private static final Log _logOutputAfterListener = LogFactoryUtil.getLog(
634                    JournalTransformer.class.getName() + ".OutputAfterListener");
635            private static final Log _logOutputBeforeListener = LogFactoryUtil.getLog(
636                    JournalTransformer.class.getName() + ".OutputBeforeListener");
637            private static final Log _logScriptAfterListener = LogFactoryUtil.getLog(
638                    JournalTransformer.class.getName() + ".ScriptAfterListener");
639            private static final Log _logScriptBeforeListener = LogFactoryUtil.getLog(
640                    JournalTransformer.class.getName() + ".ScriptBeforeListener");
641            private static final Log _logTokens = LogFactoryUtil.getLog(
642                    JournalTransformer.class.getName() + ".Tokens");
643            private static final Log _logTransformBefore = LogFactoryUtil.getLog(
644                    JournalTransformer.class.getName() + ".TransformBefore");
645            private static final Log _logTransfromAfter = LogFactoryUtil.getLog(
646                    JournalTransformer.class.getName() + ".TransformAfter");
647            private static final Log _logXmlAfterListener = LogFactoryUtil.getLog(
648                    JournalTransformer.class.getName() + ".XmlAfterListener");
649            private static final Log _logXmlBeforeListener = LogFactoryUtil.getLog(
650                    JournalTransformer.class.getName() + ".XmlBeforeListener");
651    
652            private final Map<String, String> _errorTemplateIds = new HashMap<>();
653            private final boolean _restricted;
654            private final Set<TransformerListener> _transformerListeners =
655                    new HashSet<>();
656    
657    }