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.freemarker;
016    
017    import com.liferay.portal.kernel.cache.PortalCache;
018    import com.liferay.portal.kernel.freemarker.FreeMarkerContext;
019    import com.liferay.portal.kernel.freemarker.FreeMarkerEngine;
020    import com.liferay.portal.kernel.freemarker.FreeMarkerVariablesUtil;
021    import com.liferay.portal.kernel.log.Log;
022    import com.liferay.portal.kernel.log.LogFactoryUtil;
023    import com.liferay.portal.kernel.security.pacl.DoPrivileged;
024    import com.liferay.portal.kernel.security.pacl.NotPrivileged;
025    import com.liferay.portal.kernel.util.ReflectionUtil;
026    import com.liferay.portal.kernel.util.StringPool;
027    import com.liferay.portal.kernel.util.Validator;
028    import com.liferay.portal.template.TemplateControlContext;
029    import com.liferay.portal.util.ClassLoaderUtil;
030    import com.liferay.portal.util.PropsValues;
031    
032    import freemarker.cache.ClassTemplateLoader;
033    import freemarker.cache.MultiTemplateLoader;
034    import freemarker.cache.TemplateCache;
035    import freemarker.cache.TemplateLoader;
036    
037    import freemarker.template.Configuration;
038    import freemarker.template.Template;
039    
040    import java.io.IOException;
041    import java.io.Writer;
042    
043    import java.lang.reflect.Constructor;
044    import java.lang.reflect.Field;
045    
046    import java.security.AccessController;
047    import java.security.PrivilegedExceptionAction;
048    
049    import java.util.Locale;
050    import java.util.Map;
051    import java.util.concurrent.ConcurrentHashMap;
052    
053    /**
054     * @author Mika Koivisto
055     * @author Raymond Aug??
056     */
057    @DoPrivileged
058    public class FreeMarkerEngineImpl implements FreeMarkerEngine {
059    
060            @Override
061            public void clearClassLoader(ClassLoader classLoader) {
062                    _classLoaderToolsContextsMap.remove(classLoader);
063            }
064    
065            @Override
066            public void flushTemplate(String freeMarkerTemplateId) {
067                    if (_configuration == null) {
068                            return;
069                    }
070    
071                    if (_stringTemplateLoader != null) {
072                            _stringTemplateLoader.removeTemplate(freeMarkerTemplateId);
073                    }
074    
075                    PortalCache portalCache = LiferayCacheStorage.getPortalCache();
076    
077                    portalCache.remove(_getResourceCacheKey(freeMarkerTemplateId));
078            }
079    
080            public TemplateControlContext getTemplateControlContext() {
081                    return _pacl.getTemplateControlContext();
082            }
083    
084            @NotPrivileged
085            @Override
086            public FreeMarkerContext getWrappedClassLoaderToolsContext() {
087                    return new FreeMarkerContextImpl(_doGetToolsContext(_STANDARD));
088            }
089    
090            @NotPrivileged
091            @Override
092            public FreeMarkerContext getWrappedRestrictedToolsContext() {
093                    return new FreeMarkerContextImpl(_doGetToolsContext(_RESTRICTED));
094            }
095    
096            @NotPrivileged
097            @Override
098            public FreeMarkerContext getWrappedStandardToolsContext() {
099                    return new FreeMarkerContextImpl(_doGetToolsContext(_STANDARD));
100            }
101    
102            @Override
103            public void init() throws Exception {
104                    if (_configuration != null) {
105                            return;
106                    }
107    
108                    LiferayTemplateLoader liferayTemplateLoader =
109                            new LiferayTemplateLoader();
110    
111                    liferayTemplateLoader.setTemplateLoaders(
112                            PropsValues.FREEMARKER_ENGINE_TEMPLATE_LOADERS);
113    
114                    _stringTemplateLoader = new StringTemplateLoader();
115    
116                    MultiTemplateLoader multiTemplateLoader =
117                            new MultiTemplateLoader(
118                                    new TemplateLoader[] {
119                                            new ClassTemplateLoader(getClass(), StringPool.SLASH),
120                                            _stringTemplateLoader, liferayTemplateLoader
121                                    });
122    
123                    _configuration = new Configuration();
124    
125                    _configuration.setDefaultEncoding(StringPool.UTF8);
126                    _configuration.setLocalizedLookup(
127                            PropsValues.FREEMARKER_ENGINE_LOCALIZED_LOOKUP);
128                    _configuration.setNewBuiltinClassResolver(
129                            new LiferayTemplateClassResolver());
130                    _configuration.setObjectWrapper(new LiferayObjectWrapper());
131                    _configuration.setSetting(
132                            "auto_import", PropsValues.FREEMARKER_ENGINE_MACRO_LIBRARY);
133                    _configuration.setSetting(
134                            "cache_storage", PropsValues.FREEMARKER_ENGINE_CACHE_STORAGE);
135                    _configuration.setSetting(
136                            "template_exception_handler",
137                            PropsValues.FREEMARKER_ENGINE_TEMPLATE_EXCEPTION_HANDLER);
138                    _configuration.setTemplateLoader(multiTemplateLoader);
139                    _configuration.setTemplateUpdateDelay(
140                            PropsValues.FREEMARKER_ENGINE_MODIFICATION_CHECK_INTERVAL);
141    
142                    try {
143    
144                            // This must take place after setting properties above otherwise the
145                            // cache is reset to the original implementation
146    
147                            Field field = ReflectionUtil.getDeclaredField(
148                                    Configuration.class, "cache");
149    
150                            TemplateCache templateCache = (TemplateCache)field.get(
151                                    _configuration);
152    
153                            templateCache = new LiferayTemplateCache(templateCache);
154    
155                            field.set(_configuration, templateCache);
156                    }
157                    catch (Exception e) {
158                            throw new Exception("Unable to Initialize Freemarker manager");
159                    }
160    
161                    _encoding = _configuration.getEncoding(_configuration.getLocale());
162                    _locale = _configuration.getLocale();
163    
164                    ClassLoader classLoader = TemplateCache.class.getClassLoader();
165    
166                    Class<?> templateKeyClass = classLoader.loadClass(
167                            TemplateCache.class.getName().concat("$TemplateKey"));
168    
169                    _templateKeyConstructor = templateKeyClass.getDeclaredConstructor(
170                            String.class, Locale.class, String.class, boolean.class);
171    
172                    _templateKeyConstructor.setAccessible(true);
173            }
174    
175            @NotPrivileged
176            @Override
177            public boolean mergeTemplate(
178                            String freeMarkerTemplateId, FreeMarkerContext freeMarkerContext,
179                            Writer writer)
180                    throws Exception {
181    
182                    return mergeTemplate(
183                            freeMarkerTemplateId, null, freeMarkerContext, writer);
184            }
185    
186            @NotPrivileged
187            @Override
188            public boolean mergeTemplate(
189                            String freeMarkerTemplateId, String freemarkerTemplateContent,
190                            FreeMarkerContext freeMarkerContext, Writer writer)
191                    throws Exception {
192    
193                    Template template = AccessController.doPrivileged(
194                            new DoGetTemplatePrivilegedAction(
195                                    freeMarkerTemplateId, freemarkerTemplateContent,
196                                    StringPool.UTF8));
197    
198                    FreeMarkerContextImpl freeMarkerContextImpl =
199                            (FreeMarkerContextImpl)freeMarkerContext;
200    
201                    template.process(freeMarkerContextImpl.getWrappedContext(), writer);
202    
203                    return true;
204            }
205    
206            @NotPrivileged
207            @Override
208            public boolean resourceExists(String resource) {
209                    try {
210                            Template template = _configuration.getTemplate(resource);
211    
212                            if (template != null) {
213                                    return true;
214                            }
215                            else {
216                                    return false;
217                            }
218                    }
219                    catch (IOException ioe) {
220                            if (_log.isWarnEnabled()) {
221                                    _log.warn(ioe, ioe);
222                            }
223    
224                            return false;
225                    }
226            }
227    
228            private FreeMarkerContextImpl _doGetToolsContext(
229                    String templateContextType) {
230    
231                    TemplateControlContext templateControlContext =
232                            getTemplateControlContext();
233    
234                    ClassLoader classLoader = templateControlContext.getClassLoader();
235    
236                    Map<String, FreeMarkerContextImpl> toolsContextMap =
237                            _classLoaderToolsContextsMap.get(classLoader);
238    
239                    if (toolsContextMap == null) {
240                            toolsContextMap =
241                                    new ConcurrentHashMap<String, FreeMarkerContextImpl>();
242    
243                            _classLoaderToolsContextsMap.put(classLoader, toolsContextMap);
244                    }
245    
246                    FreeMarkerContextImpl freeMarkerContextImpl = toolsContextMap.get(
247                            templateContextType);
248    
249                    if (freeMarkerContextImpl != null) {
250                            return freeMarkerContextImpl;
251                    }
252    
253                    freeMarkerContextImpl = new FreeMarkerContextImpl();
254    
255                    if (_RESTRICTED.equals(templateContextType)) {
256                            FreeMarkerVariablesUtil.insertHelperUtilities(
257                                    freeMarkerContextImpl,
258                                    PropsValues.JOURNAL_TEMPLATE_FREEMARKER_RESTRICTED_VARIABLES);
259                    }
260                    else {
261                            FreeMarkerVariablesUtil.insertHelperUtilities(
262                                    freeMarkerContextImpl, null);
263                    }
264    
265                    toolsContextMap.put(templateContextType, freeMarkerContextImpl);
266    
267                    return freeMarkerContextImpl;
268            }
269    
270            private String _getResourceCacheKey(String freeMarkerTemplateId) {
271                    try {
272                            Object object = _templateKeyConstructor.newInstance(
273                                    freeMarkerTemplateId, _locale, _encoding, Boolean.TRUE);
274    
275                            return object.toString();
276                    }
277                    catch (Exception e) {
278                            throw new RuntimeException(
279                                    "Failed to build FreeMarker internal resource cache key for " +
280                                            "template id " + freeMarkerTemplateId, e);
281                    }
282            }
283    
284            private static final String _RESTRICTED = "RESTRICTED";
285    
286            private static final String _STANDARD = "STANDARD";
287    
288            private static Log _log = LogFactoryUtil.getLog(FreeMarkerEngineImpl.class);
289    
290            private static PACL _pacl = new NoPACL();
291    
292            private Map<ClassLoader, Map<String, FreeMarkerContextImpl>>
293                    _classLoaderToolsContextsMap = new ConcurrentHashMap
294                            <ClassLoader, Map<String, FreeMarkerContextImpl>>();
295            private Configuration _configuration;
296            private String _encoding;
297            private Locale _locale;
298            private StringTemplateLoader _stringTemplateLoader;
299            private Constructor<?> _templateKeyConstructor;
300    
301            private static class NoPACL implements PACL {
302    
303                    public TemplateControlContext getTemplateControlContext() {
304                            ClassLoader contextClassLoader =
305                                    ClassLoaderUtil.getContextClassLoader();
306    
307                            return new TemplateControlContext(null, contextClassLoader);
308                    }
309    
310            }
311    
312            public static interface PACL {
313    
314                    public TemplateControlContext getTemplateControlContext();
315    
316            }
317    
318            private class DoGetTemplatePrivilegedAction
319                    implements PrivilegedExceptionAction<Template> {
320    
321                    public DoGetTemplatePrivilegedAction(
322                            String freeMarkerTemplateId, String freemarkerTemplateContent,
323                            String encoding) {
324    
325                            _freemarkerTemplateContent = freemarkerTemplateContent;
326                            _freeMarkerTemplateId = freeMarkerTemplateId;
327                            _encoding = encoding;
328                    }
329    
330                    public Template run() throws Exception {
331                            if (Validator.isNotNull(_freemarkerTemplateContent)) {
332                                    PortalCache portalCache = LiferayCacheStorage.getPortalCache();
333    
334                                    portalCache.remove(_getResourceCacheKey(_freeMarkerTemplateId));
335    
336                                    _stringTemplateLoader.putTemplate(
337                                            _freeMarkerTemplateId, _freemarkerTemplateContent);
338    
339                                    if (_log.isDebugEnabled()) {
340                                            _log.debug(
341                                                    "Added " + _freeMarkerTemplateId +
342                                                            " to the string based FreeMarker template " +
343                                                                    "repository");
344                                    }
345                            }
346    
347                            return _configuration.getTemplate(_freeMarkerTemplateId, _encoding);
348                    }
349    
350                    private String _encoding;
351                    private String _freemarkerTemplateContent;
352                    private String _freeMarkerTemplateId;
353    
354            }
355    
356    }