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