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