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