001    /**
002     * Copyright (c) 2000-present 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.taglib.util;
016    
017    import com.liferay.portal.kernel.io.unsync.UnsyncStringWriter;
018    import com.liferay.portal.kernel.log.Log;
019    import com.liferay.portal.kernel.log.LogFactoryUtil;
020    import com.liferay.portal.kernel.servlet.ServletContextPool;
021    import com.liferay.portal.kernel.servlet.taglib.DynamicIncludeUtil;
022    import com.liferay.portal.kernel.template.Template;
023    import com.liferay.portal.kernel.template.TemplateConstants;
024    import com.liferay.portal.kernel.template.TemplateContextContributor;
025    import com.liferay.portal.kernel.template.TemplateManager;
026    import com.liferay.portal.kernel.template.TemplateManagerUtil;
027    import com.liferay.portal.kernel.template.TemplateResource;
028    import com.liferay.portal.kernel.template.TemplateResourceLoaderUtil;
029    import com.liferay.portal.kernel.util.GetterUtil;
030    import com.liferay.portal.kernel.util.StringPool;
031    import com.liferay.portal.kernel.util.ThemeHelper;
032    import com.liferay.portal.kernel.util.UnsyncPrintWriterPool;
033    import com.liferay.portal.kernel.util.Validator;
034    import com.liferay.portal.kernel.util.WebKeys;
035    import com.liferay.portal.model.PortletConstants;
036    import com.liferay.portal.model.Theme;
037    import com.liferay.portal.theme.PortletDisplay;
038    import com.liferay.portal.theme.ThemeDisplay;
039    import com.liferay.registry.collections.ServiceTrackerCollections;
040    import com.liferay.registry.collections.ServiceTrackerList;
041    import com.liferay.taglib.servlet.PipingServletResponse;
042    
043    import java.io.Writer;
044    
045    import javax.servlet.RequestDispatcher;
046    import javax.servlet.ServletContext;
047    import javax.servlet.http.HttpServletRequest;
048    import javax.servlet.http.HttpServletResponse;
049    
050    /**
051     * @author Brian Wing Shun Chan
052     * @author Brian Myunghun Kim
053     * @author Raymond Augé
054     * @author Mika Koivisto
055     * @author Shuyang Zhou
056     */
057    public class ThemeUtil {
058    
059            public static String getPortletId(HttpServletRequest request) {
060                    String portletId = null;
061    
062                    ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
063                            WebKeys.THEME_DISPLAY);
064    
065                    if (themeDisplay != null) {
066                            PortletDisplay portletDisplay = themeDisplay.getPortletDisplay();
067    
068                            portletId = portletDisplay.getId();
069                    }
070    
071                    return portletId;
072            }
073    
074            public static void include(
075                            ServletContext servletContext, HttpServletRequest request,
076                            HttpServletResponse response, String path, Theme theme)
077                    throws Exception {
078    
079                    String extension = theme.getTemplateExtension();
080    
081                    if (extension.equals(ThemeHelper.TEMPLATE_EXTENSION_FTL)) {
082                            includeFTL(servletContext, request, response, path, theme, true);
083                    }
084                    else if (extension.equals(ThemeHelper.TEMPLATE_EXTENSION_VM)) {
085                            includeVM(servletContext, request, response, path, theme, true);
086                    }
087                    else {
088                            path = theme.getTemplatesPath() + StringPool.SLASH + path;
089    
090                            includeJSP(servletContext, request, response, path, theme);
091                    }
092            }
093    
094            public static String includeFTL(
095                            ServletContext servletContext, HttpServletRequest request,
096                            HttpServletResponse response, String path, Theme theme,
097                            boolean write)
098                    throws Exception {
099    
100                    return doDispatch(
101                            servletContext, request, response, path, theme, write,
102                            ThemeHelper.TEMPLATE_EXTENSION_FTL);
103            }
104    
105            public static void includeJSP(
106                            ServletContext servletContext, HttpServletRequest request,
107                            HttpServletResponse response, String path, Theme theme)
108                    throws Exception {
109    
110                    doDispatch(
111                            servletContext, request, response, path, theme, true,
112                            ThemeHelper.TEMPLATE_EXTENSION_JSP);
113            }
114    
115            public static String includeVM(
116                            ServletContext servletContext, HttpServletRequest request,
117                            HttpServletResponse response, String path, Theme theme,
118                            boolean write)
119                    throws Exception {
120    
121                    return doDispatch(
122                            servletContext, request, response, path, theme, write,
123                            ThemeHelper.TEMPLATE_EXTENSION_VM);
124            }
125    
126            protected static String doDispatch(
127                            ServletContext servletContext, HttpServletRequest request,
128                            HttpServletResponse response, String path, Theme theme,
129                            boolean write, String extension)
130                    throws Exception {
131    
132                    String pluginServletContextName = GetterUtil.getString(
133                            theme.getServletContextName());
134    
135                    ServletContext pluginServletContext = ServletContextPool.get(
136                            pluginServletContextName);
137    
138                    ClassLoader pluginClassLoader = null;
139    
140                    if (pluginServletContext != null) {
141                            pluginClassLoader = pluginServletContext.getClassLoader();
142                    }
143    
144                    Thread currentThread = Thread.currentThread();
145    
146                    ClassLoader contextClassLoader = currentThread.getContextClassLoader();
147    
148                    if ((pluginClassLoader != null) &&
149                            (pluginClassLoader != contextClassLoader)) {
150    
151                            currentThread.setContextClassLoader(pluginClassLoader);
152                    }
153    
154                    try {
155                            if (extension.equals(ThemeHelper.TEMPLATE_EXTENSION_FTL)) {
156                                    return doIncludeFTL(
157                                            servletContext, request, response, path, theme, false,
158                                            write);
159                            }
160                            else if (extension.equals(ThemeHelper.TEMPLATE_EXTENSION_JSP)) {
161                                    doIncludeJSP(servletContext, request, response, path, theme);
162                            }
163                            else if (extension.equals(ThemeHelper.TEMPLATE_EXTENSION_VM)) {
164                                    return doIncludeVM(
165                                            servletContext, request, response, path, theme, false,
166                                            write);
167                            }
168    
169                            return null;
170                    }
171                    finally {
172                            if ((pluginClassLoader != null) &&
173                                    (pluginClassLoader != contextClassLoader)) {
174    
175                                    currentThread.setContextClassLoader(contextClassLoader);
176                            }
177                    }
178            }
179    
180            protected static String doIncludeFTL(
181                            ServletContext servletContext, HttpServletRequest request,
182                            HttpServletResponse response, String path, Theme theme,
183                            boolean restricted, boolean write)
184                    throws Exception {
185    
186                    // The servlet context name will be null when the theme is deployed to
187                    // the root directory in Tomcat. See
188                    // com.liferay.portal.servlet.MainServlet and
189                    // com.liferay.portlet.PortletContextImpl for other cases where a null
190                    // servlet context name is also converted to an empty string.
191    
192                    String servletContextName = GetterUtil.getString(
193                            theme.getServletContextName());
194    
195                    if (ServletContextPool.get(servletContextName) == null) {
196    
197                            // This should only happen if the FreeMarker template is the first
198                            // page to be accessed in the system
199    
200                            ServletContextPool.put(servletContextName, servletContext);
201                    }
202    
203                    String portletId = getPortletId(request);
204    
205                    String resourcePath = theme.getResourcePath(
206                            servletContext, portletId, path);
207    
208                    if (Validator.isNotNull(portletId) &&
209                            PortletConstants.hasInstanceId(portletId) &&
210                            !TemplateResourceLoaderUtil.hasTemplateResource(
211                                    TemplateConstants.LANG_TYPE_FTL, resourcePath)) {
212    
213                            String rootPortletId = PortletConstants.getRootPortletId(portletId);
214    
215                            resourcePath = theme.getResourcePath(
216                                    servletContext, rootPortletId, path);
217                    }
218    
219                    if (Validator.isNotNull(portletId) &&
220                            !TemplateResourceLoaderUtil.hasTemplateResource(
221                                    TemplateConstants.LANG_TYPE_FTL, resourcePath)) {
222    
223                            resourcePath = theme.getResourcePath(servletContext, null, path);
224                    }
225    
226                    if (!TemplateResourceLoaderUtil.hasTemplateResource(
227                                    TemplateConstants.LANG_TYPE_FTL, resourcePath)) {
228    
229                            _log.error(resourcePath + " does not exist");
230    
231                            return null;
232                    }
233    
234                    TemplateResource templateResource =
235                            TemplateResourceLoaderUtil.getTemplateResource(
236                                    TemplateConstants.LANG_TYPE_FTL, resourcePath);
237    
238                    Template template = TemplateManagerUtil.getTemplate(
239                            TemplateConstants.LANG_TYPE_FTL, templateResource, restricted);
240    
241                    // FreeMarker variables
242    
243                    template.prepare(request);
244    
245                    // Custom theme variables
246    
247                    for (TemplateContextContributor templateContextContributor :
248                                    _templateContextContributors) {
249    
250                            templateContextContributor.prepare(template, request);
251                    }
252    
253                    // Theme servlet context
254    
255                    ServletContext themeServletContext = ServletContextPool.get(
256                            servletContextName);
257    
258                    template.put("themeServletContext", themeServletContext);
259    
260                    Writer writer = null;
261    
262                    if (write) {
263    
264                            // Wrapping is needed because of a bug in FreeMarker
265    
266                            writer = UnsyncPrintWriterPool.borrow(response.getWriter());
267                    }
268                    else {
269                            writer = new UnsyncStringWriter();
270                    }
271    
272                    TemplateManager templateManager =
273                            TemplateManagerUtil.getTemplateManager(
274                                    TemplateConstants.LANG_TYPE_FTL);
275    
276                    templateManager.addTaglibSupport(template, request, response);
277                    templateManager.addTaglibTheme(
278                            template, "taglibLiferay", request,
279                            new PipingServletResponse(response, writer));
280    
281                    template.put(TemplateConstants.WRITER, writer);
282    
283                    // Merge templates
284    
285                    template.processTemplate(writer);
286    
287                    if (write) {
288                            return null;
289                    }
290                    else {
291                            return writer.toString();
292                    }
293            }
294    
295            protected static void doIncludeJSP(
296                            ServletContext servletContext, HttpServletRequest request,
297                            HttpServletResponse response, String path, Theme theme)
298                    throws Exception {
299    
300                    DynamicIncludeUtil.include(
301                            request, response, ThemeUtil.class.getName() + "#doIncludeJSP",
302                            true);
303    
304                    if (theme.isWARFile()) {
305                            ServletContext themeServletContext = servletContext.getContext(
306                                    theme.getContextPath());
307    
308                            if (themeServletContext == null) {
309                                    _log.error(
310                                            "Theme " + theme.getThemeId() + " cannot find its " +
311                                                    "servlet context at " + theme.getServletContextName());
312                            }
313                            else {
314                                    RequestDispatcher requestDispatcher =
315                                            themeServletContext.getRequestDispatcher(path);
316    
317                                    if (requestDispatcher == null) {
318                                            _log.error(
319                                                    "Theme " + theme.getThemeId() + " does not have " +
320                                                            path);
321                                    }
322                                    else {
323                                            requestDispatcher.include(request, response);
324                                    }
325                            }
326                    }
327                    else {
328                            RequestDispatcher requestDispatcher =
329                                    servletContext.getRequestDispatcher(path);
330    
331                            if (requestDispatcher == null) {
332                                    _log.error(
333                                            "Theme " + theme.getThemeId() + " does not have " + path);
334                            }
335                            else {
336                                    requestDispatcher.include(request, response);
337                            }
338                    }
339            }
340    
341            protected static String doIncludeVM(
342                            ServletContext servletContext, HttpServletRequest request,
343                            HttpServletResponse response, String page, Theme theme,
344                            boolean restricted, boolean write)
345                    throws Exception {
346    
347                    // The servlet context name will be null when the theme is deployed to
348                    // the root directory in Tomcat. See
349                    // com.liferay.portal.servlet.MainServlet and
350                    // com.liferay.portlet.PortletContextImpl for other cases where a null
351                    // servlet context name is also converted to an empty string.
352    
353                    String servletContextName = GetterUtil.getString(
354                            theme.getServletContextName());
355    
356                    if (ServletContextPool.get(servletContextName) == null) {
357    
358                            // This should only happen if the Velocity template is the first
359                            // page to be accessed in the system
360    
361                            ServletContextPool.put(servletContextName, servletContext);
362                    }
363    
364                    String portletId = getPortletId(request);
365    
366                    String resourcePath = theme.getResourcePath(
367                            servletContext, portletId, page);
368    
369                    boolean checkResourceExists = true;
370    
371                    if (Validator.isNotNull(portletId)) {
372                            if (PortletConstants.hasInstanceId(portletId) &&
373                                    (checkResourceExists !=
374                                            TemplateResourceLoaderUtil.hasTemplateResource(
375                                                    TemplateConstants.LANG_TYPE_VM, resourcePath))) {
376    
377                                    String rootPortletId = PortletConstants.getRootPortletId(
378                                            portletId);
379    
380                                    resourcePath = theme.getResourcePath(
381                                            servletContext, rootPortletId, page);
382                            }
383    
384                            if (checkResourceExists &&
385                                    (checkResourceExists !=
386                                            TemplateResourceLoaderUtil.hasTemplateResource(
387                                                    TemplateConstants.LANG_TYPE_VM, resourcePath))) {
388    
389                                    resourcePath = theme.getResourcePath(
390                                            servletContext, null, page);
391                            }
392                    }
393    
394                    if (checkResourceExists &&
395                            !TemplateResourceLoaderUtil.hasTemplateResource(
396                                    TemplateConstants.LANG_TYPE_VM, resourcePath)) {
397    
398                            _log.error(resourcePath + " does not exist");
399    
400                            return null;
401                    }
402    
403                    TemplateResource templateResource =
404                            TemplateResourceLoaderUtil.getTemplateResource(
405                                    TemplateConstants.LANG_TYPE_VM, resourcePath);
406    
407                    if (templateResource == null) {
408                            throw new Exception(
409                                    "Unable to load template resource " + resourcePath);
410                    }
411    
412                    TemplateManager templateManager =
413                            TemplateManagerUtil.getTemplateManager(
414                                    TemplateConstants.LANG_TYPE_VM);
415    
416                    Template template = TemplateManagerUtil.getTemplate(
417                            TemplateConstants.LANG_TYPE_VM, templateResource, restricted);
418    
419                    // Velocity variables
420    
421                    template.prepare(request);
422    
423                    // Custom theme variables
424    
425                    for (TemplateContextContributor templateContextContributor :
426                                    _templateContextContributors) {
427    
428                            templateContextContributor.prepare(template, request);
429                    }
430    
431                    // Theme servlet context
432    
433                    ServletContext themeServletContext = ServletContextPool.get(
434                            servletContextName);
435    
436                    template.put("themeServletContext", themeServletContext);
437    
438                    // Tag libraries
439    
440                    Writer writer = null;
441    
442                    if (write) {
443                            writer = response.getWriter();
444                    }
445                    else {
446                            writer = new UnsyncStringWriter();
447                    }
448    
449                    templateManager.addTaglibTheme(
450                            template, "taglibLiferay", request,
451                            new PipingServletResponse(response, writer));
452    
453                    template.put(TemplateConstants.WRITER, writer);
454    
455                    // Merge templates
456    
457                    template.processTemplate(writer);
458    
459                    if (write) {
460                            return null;
461                    }
462                    else {
463                            return ((UnsyncStringWriter)writer).toString();
464                    }
465            }
466    
467            private static final Log _log = LogFactoryUtil.getLog(ThemeUtil.class);
468    
469            private static final ServiceTrackerList<TemplateContextContributor>
470                    _templateContextContributors = ServiceTrackerCollections.openList(
471                            TemplateContextContributor.class,
472                            "(type=" + TemplateContextContributor.TYPE_THEME + ")");
473    
474    }