001    /**
002     * Copyright (c) 2000-2012 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.PipingServletResponse;
021    import com.liferay.portal.kernel.servlet.PluginContextListener;
022    import com.liferay.portal.kernel.servlet.ServletContextPool;
023    import com.liferay.portal.kernel.template.Template;
024    import com.liferay.portal.kernel.template.TemplateContextType;
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.templateparser.TemplateContext;
030    import com.liferay.portal.kernel.util.GetterUtil;
031    import com.liferay.portal.kernel.util.StringPool;
032    import com.liferay.portal.kernel.util.ThemeHelper;
033    import com.liferay.portal.kernel.util.UnsyncPrintWriterPool;
034    import com.liferay.portal.kernel.util.Validator;
035    import com.liferay.portal.kernel.util.WebKeys;
036    import com.liferay.portal.model.PortletConstants;
037    import com.liferay.portal.model.Theme;
038    import com.liferay.portal.theme.PortletDisplay;
039    import com.liferay.portal.theme.ThemeDisplay;
040    import com.liferay.util.freemarker.FreeMarkerTaglibFactoryUtil;
041    
042    import freemarker.ext.servlet.HttpRequestHashModel;
043    import freemarker.ext.servlet.ServletContextHashModel;
044    
045    import freemarker.template.ObjectWrapper;
046    import freemarker.template.TemplateHashModel;
047    
048    import java.io.IOException;
049    import java.io.Writer;
050    
051    import javax.servlet.GenericServlet;
052    import javax.servlet.RequestDispatcher;
053    import javax.servlet.Servlet;
054    import javax.servlet.ServletContext;
055    import javax.servlet.ServletException;
056    import javax.servlet.ServletRequest;
057    import javax.servlet.ServletResponse;
058    import javax.servlet.http.HttpServletRequest;
059    import javax.servlet.http.HttpServletResponse;
060    import javax.servlet.jsp.PageContext;
061    
062    import org.apache.struts.taglib.tiles.ComponentConstants;
063    import org.apache.struts.tiles.ComponentContext;
064    
065    /**
066     * @author Brian Wing Shun Chan
067     * @author Brian Myunghun Kim
068     * @author Raymond Augé
069     * @author Mika Koivisto
070     * @author Shuyang Zhou
071     */
072    public class ThemeUtil {
073    
074            public static String getPortletId(HttpServletRequest request) {
075                    String portletId = null;
076    
077                    ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
078                            WebKeys.THEME_DISPLAY);
079    
080                    if (themeDisplay != null) {
081                            PortletDisplay portletDisplay = themeDisplay.getPortletDisplay();
082    
083                            portletId = portletDisplay.getId();
084                    }
085    
086                    return portletId;
087            }
088    
089            public static void include(
090                            ServletContext servletContext, HttpServletRequest request,
091                            HttpServletResponse response, PageContext pageContext, String path,
092                            Theme theme)
093                    throws Exception {
094    
095                    String extension = theme.getTemplateExtension();
096    
097                    if (extension.equals(ThemeHelper.TEMPLATE_EXTENSION_FTL)) {
098                            includeFTL(servletContext, request, pageContext, path, theme, true);
099                    }
100                    else if (extension.equals(ThemeHelper.TEMPLATE_EXTENSION_VM)) {
101                            includeVM(servletContext, request, pageContext, path, theme, true);
102                    }
103                    else {
104                            path = theme.getTemplatesPath() + StringPool.SLASH + path;
105    
106                            includeJSP(servletContext, request, response, path, theme);
107                    }
108            }
109    
110            public static String includeFTL(
111                            ServletContext servletContext, HttpServletRequest request,
112                            PageContext pageContext, String path, Theme theme, boolean write)
113                    throws Exception {
114    
115                    return doDispatch(
116                            servletContext, request, null, pageContext, path, theme, write,
117                            ThemeHelper.TEMPLATE_EXTENSION_FTL);
118            }
119    
120            public static void includeJSP(
121                            ServletContext servletContext, HttpServletRequest request,
122                            HttpServletResponse response, String path, Theme theme)
123                    throws Exception {
124    
125                    doDispatch(
126                            servletContext, request, response, null, path, theme, true,
127                            ThemeHelper.TEMPLATE_EXTENSION_JSP);
128            }
129    
130            public static String includeVM(
131                            ServletContext servletContext, HttpServletRequest request,
132                            PageContext pageContext, String path, Theme theme, boolean write)
133                    throws Exception {
134    
135                    return doDispatch(
136                            servletContext, request, null, pageContext, path, theme, write,
137                            ThemeHelper.TEMPLATE_EXTENSION_VM);
138            }
139    
140            protected static String doDispatch(
141                            ServletContext servletContext, HttpServletRequest request,
142                            HttpServletResponse response, PageContext pageContext, String path,
143                            Theme theme, boolean write, String extension)
144                    throws Exception {
145    
146                    String pluginServletContextName = GetterUtil.getString(
147                            theme.getServletContextName());
148    
149                    ServletContext pluginServletContext = ServletContextPool.get(
150                            pluginServletContextName);
151    
152                    ClassLoader pluginClassLoader = null;
153    
154                    if (pluginServletContext != null) {
155                            pluginClassLoader =
156                                    (ClassLoader)pluginServletContext.getAttribute(
157                                            PluginContextListener.PLUGIN_CLASS_LOADER);
158                    }
159    
160                    Thread currentThread = Thread.currentThread();
161    
162                    ClassLoader contextClassLoader = currentThread.getContextClassLoader();
163    
164                    TemplateContextType templateContextType = TemplateContextType.STANDARD;
165    
166                    if ((pluginClassLoader != null) &&
167                            (pluginClassLoader != contextClassLoader)) {
168    
169                            currentThread.setContextClassLoader(pluginClassLoader);
170    
171                            templateContextType = TemplateContextType.CLASS_LOADER;
172                    }
173    
174                    try {
175                            if (extension.equals(ThemeHelper.TEMPLATE_EXTENSION_FTL)) {
176                                    return doIncludeFTL(
177                                            servletContext, request, pageContext, path, theme,
178                                            templateContextType, write);
179                            }
180                            else if (extension.equals(ThemeHelper.TEMPLATE_EXTENSION_JSP)) {
181                                    doIncludeJSP(servletContext, request, response, path, theme);
182                            }
183                            else if (extension.equals(ThemeHelper.TEMPLATE_EXTENSION_VM)) {
184                                    return doIncludeVM(
185                                            servletContext, request, pageContext, path, theme,
186                                            templateContextType, write);
187                            }
188    
189                            return null;
190                    }
191                    finally {
192                            if ((pluginClassLoader != null) &&
193                                    (pluginClassLoader != contextClassLoader)) {
194    
195                                    currentThread.setContextClassLoader(contextClassLoader);
196                            }
197                    }
198            }
199    
200            protected static String doIncludeFTL(
201                            ServletContext servletContext, HttpServletRequest request,
202                            PageContext pageContext, String path, Theme theme,
203                            TemplateContextType templateContextType, boolean write)
204                    throws Exception {
205    
206                    // The servlet context name will be null when the theme is deployed to
207                    // the root directory in Tomcat. See
208                    // com.liferay.portal.servlet.MainServlet and
209                    // com.liferay.portlet.PortletContextImpl for other cases where a null
210                    // servlet context name is also converted to an empty string.
211    
212                    String servletContextName = GetterUtil.getString(
213                            theme.getServletContextName());
214    
215                    if (ServletContextPool.get(servletContextName) == null) {
216    
217                            // This should only happen if the FreeMarker template is the first
218                            // page to be accessed in the system
219    
220                            ServletContextPool.put(servletContextName, servletContext);
221                    }
222    
223                    String portletId = getPortletId(request);
224    
225                    String resourcePath = theme.getResourcePath(
226                            servletContext, portletId, path);
227    
228                    if (Validator.isNotNull(portletId) &&
229                            PortletConstants.hasInstanceId(portletId) &&
230                            !TemplateResourceLoaderUtil.hasTemplateResource(
231                                    TemplateManager.FREEMARKER, resourcePath)) {
232    
233                            String rootPortletId = PortletConstants.getRootPortletId(portletId);
234    
235                            resourcePath = theme.getResourcePath(
236                                    servletContext, rootPortletId, path);
237                    }
238    
239                    if (Validator.isNotNull(portletId) &&
240                            !TemplateResourceLoaderUtil.hasTemplateResource(
241                                    TemplateManager.FREEMARKER, resourcePath)) {
242    
243                            resourcePath = theme.getResourcePath(servletContext, null, path);
244                    }
245    
246                    if (!TemplateResourceLoaderUtil.hasTemplateResource(
247                                    TemplateManager.FREEMARKER, resourcePath)) {
248    
249                            _log.error(resourcePath + " does not exist");
250    
251                            return null;
252                    }
253    
254                    TemplateResource templateResource =
255                            TemplateResourceLoaderUtil.getTemplateResource(
256                                    TemplateManager.FREEMARKER, resourcePath);
257    
258                    Template template = TemplateManagerUtil.getTemplate(
259                            TemplateManager.FREEMARKER, templateResource,
260                            TemplateContextType.STANDARD);
261    
262                    // FreeMarker variables
263    
264                    template.prepare(request);
265    
266                    // Theme servlet context
267    
268                    ServletContext themeServletContext = ServletContextPool.get(
269                            servletContextName);
270    
271                    template.put("themeServletContext", themeServletContext);
272    
273                    // Tag libraries
274    
275                    HttpServletResponse response =
276                            (HttpServletResponse)pageContext.getResponse();
277    
278                    Writer writer = null;
279    
280                    if (write) {
281    
282                            // Wrapping is needed because of a bug in FreeMarker
283    
284                            writer = UnsyncPrintWriterPool.borrow(pageContext.getOut());
285                    }
286                    else {
287                            writer = new UnsyncStringWriter();
288                    }
289    
290                    VelocityTaglib velocityTaglib = new VelocityTaglib(
291                            servletContext, request,
292                            new PipingServletResponse(response, writer), pageContext, template);
293    
294                    template.put(TemplateContext.WRITER, writer);
295                    template.put("taglibLiferay", velocityTaglib);
296                    template.put("theme", velocityTaglib);
297    
298                    // Portal JSP tag library factory
299    
300                    TemplateHashModel portalTaglib =
301                            FreeMarkerTaglibFactoryUtil.createTaglibFactory(servletContext);
302    
303                    template.put("PortalJspTagLibs", portalTaglib);
304    
305                    // Theme JSP tag library factory
306    
307                    TemplateHashModel themeTaglib =
308                            FreeMarkerTaglibFactoryUtil.createTaglibFactory(
309                                    themeServletContext);
310    
311                    template.put("ThemeJspTaglibs", themeTaglib);
312    
313                    // FreeMarker JSP tag library support
314    
315                    final Servlet servlet = (Servlet)pageContext.getPage();
316    
317                    GenericServlet genericServlet = null;
318    
319                    if (servlet instanceof GenericServlet) {
320                            genericServlet = (GenericServlet) servlet;
321                    }
322                    else {
323                            genericServlet = new GenericServlet() {
324    
325                                    @Override
326                                    public void service(
327                                                    ServletRequest servletRequest,
328                                                    ServletResponse servletResponse)
329                                            throws IOException, ServletException {
330    
331                                            servlet.service(servletRequest, servletResponse);
332                                    }
333    
334                            };
335    
336                            genericServlet.init(pageContext.getServletConfig());
337                    }
338    
339                    ServletContextHashModel servletContextHashModel =
340                            new ServletContextHashModel(
341                                    genericServlet, ObjectWrapper.DEFAULT_WRAPPER);
342    
343                    template.put("Application", servletContextHashModel);
344    
345                    HttpRequestHashModel httpRequestHashModel = new HttpRequestHashModel(
346                            request, response, ObjectWrapper.DEFAULT_WRAPPER);
347    
348                    template.put("Request", httpRequestHashModel);
349    
350                    // Merge templates
351    
352                    template.processTemplate(writer);
353    
354                    if (write) {
355                            return null;
356                    }
357                    else {
358                            return writer.toString();
359                    }
360            }
361    
362            protected static void doIncludeJSP(
363                            ServletContext servletContext, HttpServletRequest request,
364                            HttpServletResponse response, String path, Theme theme)
365                    throws Exception {
366    
367                    insertTilesVariables(request);
368    
369                    if (theme.isWARFile()) {
370                            ServletContext themeServletContext = servletContext.getContext(
371                                    theme.getContextPath());
372    
373                            if (themeServletContext == null) {
374                                    _log.error(
375                                            "Theme " + theme.getThemeId() + " cannot find its " +
376                                                    "servlet context at " + theme.getServletContextName());
377                            }
378                            else {
379                                    RequestDispatcher requestDispatcher =
380                                            themeServletContext.getRequestDispatcher(path);
381    
382                                    if (requestDispatcher == null) {
383                                            _log.error(
384                                                    "Theme " + theme.getThemeId() + " does not have " +
385                                                            path);
386                                    }
387                                    else {
388                                            requestDispatcher.include(request, response);
389                                    }
390                            }
391                    }
392                    else {
393                            RequestDispatcher requestDispatcher =
394                                    servletContext.getRequestDispatcher(path);
395    
396                            if (requestDispatcher == null) {
397                                    _log.error(
398                                            "Theme " + theme.getThemeId() + " does not have " + path);
399                            }
400                            else {
401                                    requestDispatcher.include(request, response);
402                            }
403                    }
404            }
405    
406            protected static String doIncludeVM(
407                            ServletContext servletContext, HttpServletRequest request,
408                            PageContext pageContext, String page, Theme theme,
409                            TemplateContextType templateContextType, boolean write)
410                    throws Exception {
411    
412                    // The servlet context name will be null when the theme is deployed to
413                    // the root directory in Tomcat. See
414                    // com.liferay.portal.servlet.MainServlet and
415                    // com.liferay.portlet.PortletContextImpl for other cases where a null
416                    // servlet context name is also converted to an empty string.
417    
418                    String servletContextName = GetterUtil.getString(
419                            theme.getServletContextName());
420    
421                    if (ServletContextPool.get(servletContextName) == null) {
422    
423                            // This should only happen if the Velocity template is the first
424                            // page to be accessed in the system
425    
426                            ServletContextPool.put(servletContextName, servletContext);
427                    }
428    
429                    String portletId = getPortletId(request);
430    
431                    String resourcePath = theme.getResourcePath(
432                            servletContext, portletId, page);
433    
434                    boolean checkResourceExists = true;
435    
436                    if (Validator.isNotNull(portletId)) {
437                            if (PortletConstants.hasInstanceId(portletId) &&
438                                    (checkResourceExists = !
439                                    TemplateResourceLoaderUtil.hasTemplateResource(
440                                            TemplateManager.VELOCITY, resourcePath))) {
441    
442                                    String rootPortletId = PortletConstants.getRootPortletId(
443                                            portletId);
444    
445                                    resourcePath = theme.getResourcePath(
446                                            servletContext, rootPortletId, page);
447                            }
448    
449                            if (checkResourceExists &&
450                                    (checkResourceExists = !
451                                    TemplateResourceLoaderUtil.hasTemplateResource(
452                                            TemplateManager.VELOCITY, resourcePath))) {
453    
454                                    resourcePath = theme.getResourcePath(
455                                            servletContext, null, page);
456                            }
457                    }
458    
459                    if (checkResourceExists &&
460                            !TemplateResourceLoaderUtil.hasTemplateResource(
461                                    TemplateManager.VELOCITY, resourcePath)) {
462    
463                            _log.error(resourcePath + " does not exist");
464    
465                            return null;
466                    }
467    
468                    TemplateResource templateResource =
469                            TemplateResourceLoaderUtil.getTemplateResource(
470                                    TemplateManager.VELOCITY, resourcePath);
471    
472                    if (templateResource == null) {
473                            throw new Exception(
474                                    "Unable to load template resource " + resourcePath);
475                    }
476    
477                    Template template = TemplateManagerUtil.getTemplate(
478                            TemplateManager.VELOCITY, templateResource,
479                            TemplateContextType.STANDARD);
480    
481                    // Velocity variables
482    
483                    template.prepare(request);
484    
485                    // Page context
486    
487                    template.put("pageContext", pageContext);
488    
489                    // Theme servlet context
490    
491                    ServletContext themeServletContext = ServletContextPool.get(
492                            servletContextName);
493    
494                    template.put("themeServletContext", themeServletContext);
495    
496                    // Tag libraries
497    
498                    HttpServletResponse response =
499                            (HttpServletResponse)pageContext.getResponse();
500    
501                    Writer writer = null;
502    
503                    if (write) {
504                            writer = pageContext.getOut();
505                    }
506                    else {
507                            writer = new UnsyncStringWriter();
508                    }
509    
510                    VelocityTaglib velocityTaglib = new VelocityTaglib(
511                            servletContext, request,
512                            new PipingServletResponse(response, writer), pageContext, template);
513    
514                    template.put(TemplateContext.WRITER, writer);
515                    template.put("taglibLiferay", velocityTaglib);
516                    template.put("theme", velocityTaglib);
517    
518                    // Merge templates
519    
520                    template.processTemplate(writer);
521    
522                    if (write) {
523                            return null;
524                    }
525                    else {
526                            return ((UnsyncStringWriter)writer).toString();
527                    }
528            }
529    
530            protected static void insertTilesVariables(HttpServletRequest request) {
531                    ComponentContext componentContext =
532                            (ComponentContext)request.getAttribute(
533                                    ComponentConstants.COMPONENT_CONTEXT);
534    
535                    if (componentContext == null) {
536                            return;
537                    }
538    
539                    ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
540                            WebKeys.THEME_DISPLAY);
541    
542                    String tilesTitle = (String)componentContext.getAttribute("title");
543                    String tilesContent = (String)componentContext.getAttribute("content");
544                    boolean tilesSelectable = GetterUtil.getBoolean(
545                            (String)componentContext.getAttribute("selectable"));
546    
547                    themeDisplay.setTilesTitle(tilesTitle);
548                    themeDisplay.setTilesContent(tilesContent);
549                    themeDisplay.setTilesSelectable(tilesSelectable);
550            }
551    
552            private static Log _log = LogFactoryUtil.getLog(ThemeUtil.class);
553    
554    }