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