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