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