com.liferay.portal.template.soy.internal.SoyTemplate.java Source code

Java tutorial

Introduction

Here is the source code for com.liferay.portal.template.soy.internal.SoyTemplate.java

Source

/**
 * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 */

package com.liferay.portal.template.soy.internal;

import com.google.common.io.CharStreams;
import com.google.template.soy.SoyFileSet;
import com.google.template.soy.SoyFileSet.Builder;
import com.google.template.soy.data.SanitizedContent;
import com.google.template.soy.data.SoyMapData;
import com.google.template.soy.msgs.SoyMsgBundle;
import com.google.template.soy.tofu.SoyTofu;
import com.google.template.soy.tofu.SoyTofu.Renderer;
import com.google.template.soy.tofu.SoyTofuOptions;

import com.liferay.portal.kernel.bean.BeanPropertiesUtil;
import com.liferay.portal.kernel.json.JSONObject;
import com.liferay.portal.kernel.language.LanguageUtil;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.template.StringTemplateResource;
import com.liferay.portal.kernel.template.TemplateConstants;
import com.liferay.portal.kernel.template.TemplateException;
import com.liferay.portal.kernel.template.TemplateResource;
import com.liferay.portal.kernel.util.AggregateResourceBundleLoader;
import com.liferay.portal.kernel.util.ClassResourceBundleLoader;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.ResourceBundleLoader;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.template.AbstractMultiResourceTemplate;
import com.liferay.portal.template.TemplateContextHelper;
import com.liferay.portal.template.soy.constants.SoyTemplateConstants;
import com.liferay.portal.template.soy.utils.SoyRawData;

import java.io.Reader;
import java.io.Writer;

import java.lang.reflect.Array;

import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.TreeMap;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.ClassUtils;

import org.json.JSONArray;

import org.osgi.framework.Bundle;
import org.osgi.framework.wiring.BundleWiring;

/**
 * @author Bruno Basto
 */
public class SoyTemplate extends AbstractMultiResourceTemplate {

    public SoyTemplate(List<TemplateResource> templateResources, TemplateResource errorTemplateResource,
            Map<String, Object> context, SoyTemplateContextHelper templateContextHelper, boolean privileged,
            SoyTofuCacheHandler soyTofuCacheHandler) {

        super(templateResources, errorTemplateResource, context, templateContextHelper,
                TemplateConstants.LANG_TYPE_SOY, 0);

        _templateContextHelper = templateContextHelper;
        _privileged = privileged;

        _soyMapData = new SoyMapData();
        _injectedSoyMapData = new SoyMapData();
        _soyTofuCacheHandler = soyTofuCacheHandler;
    }

    @Override
    public void clear() {
        _soyMapData = new SoyMapData();
        _injectedSoyMapData = new SoyMapData();

        super.clear();
    }

    @Override
    public void prepare(HttpServletRequest request) {
        Map<String, Object> injectedDataObjects = new HashMap<>();

        _templateContextHelper.prepare(injectedDataObjects, request);

        for (Map.Entry<String, Object> entry : injectedDataObjects.entrySet()) {
            putInjectedData(entry.getKey(), entry.getValue());
        }
    }

    @Override
    public Object put(String key, Object value) {
        TemplateContextHelper templateContextHelper = getTemplateContextHelper();

        Set<String> restrictedVariables = templateContextHelper.getRestrictedVariables();

        Object currentValue = get(key);

        if (!restrictedVariables.contains(key) && !Objects.equals(value, currentValue)) {

            Object soyMapValue = getSoyMapValue(value);

            _soyMapData.put(key, soyMapValue);
        }

        return super.put(key, value);
    }

    @Override
    public void putAll(Map<? extends String, ? extends Object> map) {
        for (String key : map.keySet()) {
            put(key, map.get(key));
        }
    }

    public void putInjectedData(String key, Object value) {
        _injectedSoyMapData.put(key, getSoyMapValue(value));
    }

    @Override
    public Object remove(Object key) {
        if (SoyTemplateConstants.INJECTED_DATA.equals(key)) {
            _injectedSoyMapData = new SoyMapData();
        }

        _soyMapData.remove((String) key);

        return super.remove(key);
    }

    protected SoyMsgBundleBridge createSoyMsgBundleBridge(SoyFileSet soyFileSet, Locale locale) {

        SoyMsgBundle soyMsgBundle = soyFileSet.extractMsgs();

        ResourceBundle languageResourceBundle = _getLanguageResourceBundle(locale);

        SoyMsgBundleBridge soyMsgBundleBridge = new SoyMsgBundleBridge(soyMsgBundle, locale,
                languageResourceBundle);

        return soyMsgBundleBridge;
    }

    protected SoyFileSet getSoyFileSet(List<TemplateResource> templateResources) throws Exception {

        SoyFileSet soyFileSet = null;

        if (_privileged) {
            soyFileSet = AccessController.doPrivileged(new TemplatePrivilegedExceptionAction(templateResources));
        } else {
            Builder builder = SoyFileSet.builder();

            for (TemplateResource templateResource : templateResources) {
                String templateContent = getTemplateContent(templateResource);

                builder.add(templateContent, templateResource.getTemplateId());
            }

            soyFileSet = builder.build();
        }

        return soyFileSet;
    }

    protected SoyMapData getSoyMapData() {
        return _soyMapData;
    }

    protected SoyMapData getSoyMapInjectedData() {
        if (containsKey(SoyTemplateConstants.INJECTED_DATA)) {
            Map<String, Object> injectedData = (Map<String, Object>) get(SoyTemplateConstants.INJECTED_DATA);

            for (Map.Entry<String, Object> entry : injectedData.entrySet()) {
                putInjectedData(entry.getKey(), entry.getValue());
            }
        }

        return _injectedSoyMapData;
    }

    protected Object getSoyMapValue(Object value) {
        if (value == null) {
            return null;
        }

        Class<?> clazz = value.getClass();

        if (ClassUtils.isPrimitiveOrWrapper(clazz) || value instanceof String) {
            return value;
        }

        if (clazz.isArray()) {
            List<Object> newList = new ArrayList<>();

            for (int i = 0; i < Array.getLength(value); i++) {
                Object object = Array.get(value, i);

                newList.add(getSoyMapValue(object));
            }

            return newList;
        }

        if (value instanceof Iterable) {
            @SuppressWarnings("unchecked")
            Iterable<Object> iterable = (Iterable<Object>) value;

            List<Object> newList = new ArrayList<>();

            for (Object object : iterable) {
                newList.add(getSoyMapValue(object));
            }

            return newList;
        }

        if (value instanceof JSONArray) {
            JSONArray jsonArray = (JSONArray) value;

            List<Object> newList = new ArrayList<>();

            for (int i = 0; i < jsonArray.length(); i++) {
                Object object = jsonArray.opt(i);

                newList.add(getSoyMapValue(object));
            }

            return newList;
        }

        if (value instanceof Map) {
            Map<Object, Object> map = (Map<Object, Object>) value;

            Map<Object, Object> newMap = new TreeMap<>();

            for (Map.Entry<Object, Object> entry : map.entrySet()) {
                Object newKey = getSoyMapValue(entry.getKey());

                if (newKey == null) {
                    continue;
                }

                Object newValue = getSoyMapValue(entry.getValue());

                newMap.put(newKey, newValue);
            }

            return newMap;
        }

        if (value instanceof JSONObject) {
            JSONObject jsonObject = (JSONObject) value;

            Map<String, Object> newMap = new TreeMap<>();

            Iterator<String> iterator = jsonObject.keys();

            while (iterator.hasNext()) {
                String key = iterator.next();

                Object object = jsonObject.get(key);

                Object newValue = getSoyMapValue(object);

                newMap.put(key, newValue);
            }

            return newMap;
        }

        if (value instanceof org.json.JSONObject) {
            org.json.JSONObject jsonObject = (org.json.JSONObject) value;

            Map<Object, Object> newMap = new TreeMap<>();

            Iterator<String> iterator = jsonObject.keys();

            while (iterator.hasNext()) {
                String key = iterator.next();

                Object object = jsonObject.opt(key);

                Object newValue = getSoyMapValue(object);

                newMap.put(key, newValue);
            }

            return newMap;
        }

        if (value instanceof SoyRawData) {
            SoyRawData soyRawData = (SoyRawData) value;

            return soyRawData.getValue();
        }

        Map<String, Object> newMap = new TreeMap<>();

        BeanPropertiesUtil.copyProperties(value, newMap);

        if (newMap.isEmpty()) {
            return null;
        }

        return getSoyMapValue(newMap);
    }

    protected Optional<SoyMsgBundle> getSoyMsgBundle(SoyFileSet soyFileSet, SoyTofuCacheBag soyTofuCacheBag) {

        Locale locale = (Locale) get("locale");

        if (locale != null) {
            SoyMsgBundle soyMsgBundle = soyTofuCacheBag.getMessageBundle(locale);

            if (soyMsgBundle == null) {
                soyMsgBundle = createSoyMsgBundleBridge(soyFileSet, locale);

                soyTofuCacheBag.putMessageBundle(locale, soyMsgBundle);
            }

            return Optional.of(soyMsgBundle);
        }

        return Optional.empty();
    }

    protected SoyTofuCacheBag getSoyTofuCacheBag(List<TemplateResource> templateResources) throws Exception {

        SoyTofuCacheBag soyTofuCacheBag = _soyTofuCacheHandler.get(templateResources);

        if (soyTofuCacheBag == null) {
            SoyTofuOptions soyTofuOptions = new SoyTofuOptions();

            soyTofuOptions.setUseCaching(true);

            SoyFileSet soyFileSet = getSoyFileSet(templateResources);

            SoyTofu soyTofu = soyFileSet.compileToTofu(soyTofuOptions);

            soyTofuCacheBag = _soyTofuCacheHandler.add(templateResources, soyFileSet, soyTofu);
        }

        return soyTofuCacheBag;
    }

    protected String getTemplateContent(TemplateResource templateResource) throws Exception {

        Reader reader = templateResource.getReader();

        return CharStreams.toString(reader);
    }

    protected TemplateContextHelper getTemplateContextHelper() {
        return _templateContextHelper;
    }

    @Override
    protected void handleException(Exception exception, Writer writer) throws TemplateException {

        put("exception", exception.getMessage());

        StringBundler sb = new StringBundler();

        for (TemplateResource templateResource : templateResources) {
            if (templateResource instanceof StringTemplateResource) {
                StringTemplateResource stringTemplateResource = (StringTemplateResource) templateResource;

                sb.append(stringTemplateResource.getContent());
            }
        }

        put("script", sb.toString());

        try {
            processTemplates(Arrays.asList(errorTemplateResource), writer);
        } catch (Exception e) {
            throw new TemplateException("Unable to process Soy template " + errorTemplateResource.getTemplateId(),
                    e);
        }
    }

    @Override
    protected void processTemplates(List<TemplateResource> templateResources, Writer writer) throws Exception {

        try {
            String namespace = GetterUtil.getString(get(TemplateConstants.NAMESPACE));

            if (Validator.isNull(namespace)) {
                throw new TemplateException("Namespace is not specified");
            }

            SoyTofuCacheBag soyTofuCacheBag = getSoyTofuCacheBag(templateResources);

            SoyTofu soyTofu = soyTofuCacheBag.getSoyTofu();

            Renderer renderer = soyTofu.newRenderer(namespace);

            renderer.setData(getSoyMapData());
            renderer.setIjData(getSoyMapInjectedData());

            SoyFileSet soyFileSet = soyTofuCacheBag.getSoyFileSet();

            Optional<SoyMsgBundle> soyMsgBundle = getSoyMsgBundle(soyFileSet, soyTofuCacheBag);

            if (soyMsgBundle.isPresent()) {
                renderer.setMsgBundle(soyMsgBundle.get());
            }

            boolean renderStrict = GetterUtil.getBoolean(get(TemplateConstants.RENDER_STRICT), true);

            if (renderStrict) {
                SanitizedContent sanitizedContent = renderer.renderStrict();

                writer.write(sanitizedContent.stringValue());
            } else {
                writer.write(renderer.render());
            }
        } catch (PrivilegedActionException pae) {
            throw pae.getException();
        }
    }

    private ResourceBundle _getLanguageResourceBundle(Locale locale) {
        List<ResourceBundleLoader> resourceBundleLoaders = new ArrayList<>();

        for (TemplateResource templateResource : templateResources) {
            try {
                Bundle templateResourceBundle = SoyProviderCapabilityBundleRegister
                        .getTemplateBundle(templateResource.getTemplateId());

                BundleWiring bundleWiring = templateResourceBundle.adapt(BundleWiring.class);

                resourceBundleLoaders
                        .add(new ClassResourceBundleLoader("content.Language", bundleWiring.getClassLoader()));
            } catch (Exception e) {
                if (_log.isDebugEnabled()) {
                    String templateId = templateResource.getTemplateId();

                    _log.debug(
                            "Unable to get language resource bundle for template " + StringUtil.quote(templateId),
                            e);
                }
            }
        }

        resourceBundleLoaders.add(LanguageUtil.getPortalResourceBundleLoader());

        AggregateResourceBundleLoader aggregateResourceBundleLoader = new AggregateResourceBundleLoader(
                resourceBundleLoaders.toArray(new ResourceBundleLoader[resourceBundleLoaders.size()]));

        return aggregateResourceBundleLoader.loadResourceBundle(locale);
    }

    private static final Log _log = LogFactoryUtil.getLog(SoyTemplate.class);

    private SoyMapData _injectedSoyMapData;
    private final boolean _privileged;
    private SoyMapData _soyMapData;
    private final SoyTofuCacheHandler _soyTofuCacheHandler;
    private final SoyTemplateContextHelper _templateContextHelper;

    private class TemplatePrivilegedExceptionAction implements PrivilegedExceptionAction<SoyFileSet> {

        public TemplatePrivilegedExceptionAction(List<TemplateResource> templateResources) {

            _templateResources = templateResources;
        }

        @Override
        public SoyFileSet run() throws Exception {
            Builder builder = SoyFileSet.builder();

            for (TemplateResource templateResource : _templateResources) {
                String templateContent = getTemplateContent(templateResource);

                builder.add(templateContent, templateResource.getTemplateId());
            }

            return builder.build();
        }

        private final List<TemplateResource> _templateResources;

    }

}