/** * 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.data.UnsafeSanitizedContentOrdainer; 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.cache.PortalCache; 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.LocaleUtil; 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.soy.utils.SoyHTMLContextValue; import com.liferay.portal.template.soy.utils.SoyTemplateResourcesProvider; import java.io.Reader; import java.io.Writer; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; 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 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, PortalCache<HashSet<TemplateResource>, SoyTofuCacheBag> portalCache) { super( templateResources, errorTemplateResource, context, templateContextHelper, TemplateConstants.LANG_TYPE_SOY, 0); _templateContextHelper = templateContextHelper; _privileged = privileged; _soyMapData = new SoyMapData(); _soyTofuCacheHandler = new SoyTofuCacheHandler(portalCache); } @Override public void clear() { for (String key : _soyMapData.getKeys()) { _soyMapData.remove(key); } super.clear(); } @Override public Object put(String key, Object value) { Set<String> restrictedVariables = _templateContextHelper.getRestrictedVariables(); Object currentValue = get(key); if (!restrictedVariables.contains(key) && !Objects.equals(value, currentValue)) { Object soyMapValue = null; if (value == null) { soyMapValue = null; } else if (value instanceof SoyHTMLContextValue) { SoyHTMLContextValue htmlValue = (SoyHTMLContextValue)value; soyMapValue = UnsafeSanitizedContentOrdainer.ordainAsSafe( htmlValue.toString(), SanitizedContent.ContentKind.HTML); } else { soyMapValue = _templateContextHelper.deserializeValue(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)); } } @Override public Object remove(Object key) { _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 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); } @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()); 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 = SoyTemplateResourcesProvider.getTemplateResourceBundle( templateResource); 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()])); String languageId = LocaleUtil.toLanguageId(locale); return aggregateResourceBundleLoader.loadResourceBundle(languageId); } private static final Log _log = LogFactoryUtil.getLog(SoyTemplate.class); private final boolean _privileged; private final 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; } }