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.portlet.layoutconfiguration.util;
016    
017    import com.liferay.portal.kernel.executor.PortalExecutorManagerUtil;
018    import com.liferay.portal.kernel.io.unsync.UnsyncStringWriter;
019    import com.liferay.portal.kernel.log.Log;
020    import com.liferay.portal.kernel.log.LogFactoryUtil;
021    import com.liferay.portal.kernel.servlet.PipingServletResponse;
022    import com.liferay.portal.kernel.servlet.PluginContextListener;
023    import com.liferay.portal.kernel.servlet.ServletContextPool;
024    import com.liferay.portal.kernel.template.Template;
025    import com.liferay.portal.kernel.template.TemplateContextType;
026    import com.liferay.portal.kernel.template.TemplateManager;
027    import com.liferay.portal.kernel.template.TemplateManagerUtil;
028    import com.liferay.portal.kernel.template.TemplateResource;
029    import com.liferay.portal.kernel.util.GetterUtil;
030    import com.liferay.portal.kernel.util.JavaConstants;
031    import com.liferay.portal.kernel.util.ObjectValuePair;
032    import com.liferay.portal.kernel.util.StringBundler;
033    import com.liferay.portal.kernel.util.StringPool;
034    import com.liferay.portal.kernel.util.StringUtil;
035    import com.liferay.portal.kernel.util.Validator;
036    import com.liferay.portal.model.LayoutTemplate;
037    import com.liferay.portal.model.LayoutTemplateConstants;
038    import com.liferay.portal.model.Portlet;
039    import com.liferay.portal.model.PortletConstants;
040    import com.liferay.portal.security.pacl.PACLClassLoaderUtil;
041    import com.liferay.portal.service.LayoutTemplateLocalServiceUtil;
042    import com.liferay.portal.servlet.ThreadLocalFacadeServletRequestWrapperUtil;
043    import com.liferay.portal.util.PropsValues;
044    import com.liferay.portal.util.WebKeys;
045    import com.liferay.portlet.layoutconfiguration.util.velocity.CustomizationSettingsProcessor;
046    import com.liferay.portlet.layoutconfiguration.util.velocity.TemplateProcessor;
047    import com.liferay.portlet.layoutconfiguration.util.xml.ActionURLLogic;
048    import com.liferay.portlet.layoutconfiguration.util.xml.PortletLogic;
049    import com.liferay.portlet.layoutconfiguration.util.xml.RenderURLLogic;
050    import com.liferay.portlet.layoutconfiguration.util.xml.RuntimeLogic;
051    import com.liferay.taglib.util.VelocityTaglib;
052    
053    import java.io.Closeable;
054    
055    import java.util.HashMap;
056    import java.util.List;
057    import java.util.Map;
058    import java.util.concurrent.Callable;
059    import java.util.concurrent.CancellationException;
060    import java.util.concurrent.ExecutionException;
061    import java.util.concurrent.ExecutorService;
062    import java.util.concurrent.Future;
063    import java.util.concurrent.FutureTask;
064    import java.util.concurrent.RejectedExecutionException;
065    import java.util.concurrent.TimeUnit;
066    import java.util.concurrent.TimeoutException;
067    import java.util.concurrent.locks.Lock;
068    import java.util.concurrent.locks.ReentrantLock;
069    
070    import javax.portlet.PortletResponse;
071    import javax.portlet.RenderResponse;
072    
073    import javax.servlet.ServletContext;
074    import javax.servlet.http.HttpServletRequest;
075    import javax.servlet.http.HttpServletResponse;
076    import javax.servlet.jsp.PageContext;
077    
078    import org.apache.commons.lang.time.StopWatch;
079    
080    /**
081     * @author Brian Wing Shun Chan
082     * @author Raymond Augé
083     * @author Shuyang Zhou
084     */
085    public class RuntimePageImpl implements RuntimePage {
086    
087            public StringBundler getProcessedTemplate(
088                            PageContext pageContext, String portletId,
089                            TemplateResource templateResource)
090                    throws Exception {
091    
092                    return doDispatch(pageContext, portletId, templateResource, true);
093            }
094    
095            public void processCustomizationSettings(
096                            PageContext pageContext, TemplateResource templateResource)
097                    throws Exception {
098    
099                    doDispatch(pageContext, null, templateResource, false);
100            }
101    
102            public void processTemplate(
103                            PageContext pageContext, String portletId,
104                            TemplateResource templateResource)
105                    throws Exception {
106    
107                    StringBundler sb = doDispatch(
108                            pageContext, portletId, templateResource, true);
109    
110                    sb.writeTo(pageContext.getOut());
111            }
112    
113            public void processTemplate(
114                            PageContext pageContext, TemplateResource templateResource)
115                    throws Exception {
116    
117                    processTemplate(pageContext, null, templateResource);
118            }
119    
120            public String processXML(
121                            HttpServletRequest request, HttpServletResponse response,
122                            String content)
123                    throws Exception {
124    
125                    PortletResponse portletResponse = (PortletResponse)request.getAttribute(
126                            JavaConstants.JAVAX_PORTLET_RESPONSE);
127    
128                    if (!(portletResponse instanceof RenderResponse)) {
129                            throw new IllegalArgumentException(
130                                    "processXML can only be invoked in the render phase");
131                    }
132    
133                    RenderResponse renderResponse = (RenderResponse)portletResponse;
134    
135                    RuntimeLogic portletLogic = new PortletLogic(request, response);
136                    RuntimeLogic actionURLLogic = new ActionURLLogic(renderResponse);
137                    RuntimeLogic renderURLLogic = new RenderURLLogic(renderResponse);
138    
139                    content = processXML(request, content, portletLogic);
140                    content = processXML(request, content, actionURLLogic);
141                    content = processXML(request, content, renderURLLogic);
142    
143                    return content;
144            }
145    
146            public String processXML(
147                            HttpServletRequest request, String content,
148                            RuntimeLogic runtimeLogic)
149                    throws Exception {
150    
151                    if (Validator.isNull(content)) {
152                            return StringPool.BLANK;
153                    }
154    
155                    int index = content.indexOf(runtimeLogic.getOpenTag());
156    
157                    if (index == -1) {
158                            return content;
159                    }
160    
161                    Portlet renderPortlet = (Portlet)request.getAttribute(
162                            WebKeys.RENDER_PORTLET);
163    
164                    Boolean renderPortletResource = (Boolean)request.getAttribute(
165                            WebKeys.RENDER_PORTLET_RESOURCE);
166    
167                    String outerPortletId = (String)request.getAttribute(
168                            WebKeys.OUTER_PORTLET_ID);
169    
170                    if (outerPortletId == null) {
171                            request.setAttribute(
172                                    WebKeys.OUTER_PORTLET_ID, renderPortlet.getPortletId());
173                    }
174    
175                    try {
176                            request.setAttribute(WebKeys.RENDER_PORTLET_RESOURCE, Boolean.TRUE);
177    
178                            StringBundler sb = new StringBundler();
179    
180                            int x = 0;
181                            int y = index;
182    
183                            while (y != -1) {
184                                    sb.append(content.substring(x, y));
185    
186                                    int close1 = content.indexOf(runtimeLogic.getClose1Tag(), y);
187                                    int close2 = content.indexOf(runtimeLogic.getClose2Tag(), y);
188    
189                                    if ((close2 == -1) || ((close1 != -1) && (close1 < close2))) {
190                                            x = close1 + runtimeLogic.getClose1Tag().length();
191                                    }
192                                    else {
193                                            x = close2 + runtimeLogic.getClose2Tag().length();
194                                    }
195    
196                                    String runtimePortletTag = content.substring(y, x);
197    
198                                    if ((renderPortlet != null) &&
199                                            runtimePortletTag.contains(renderPortlet.getPortletId())) {
200    
201                                            return StringPool.BLANK;
202                                    }
203    
204                                    sb.append(runtimeLogic.processXML(runtimePortletTag));
205    
206                                    y = content.indexOf(runtimeLogic.getOpenTag(), x);
207                            }
208    
209                            if (y == -1) {
210                                    sb.append(content.substring(x));
211                            }
212    
213                            return sb.toString();
214                    }
215                    finally {
216                            if (outerPortletId == null) {
217                                    request.removeAttribute(WebKeys.OUTER_PORTLET_ID);
218                            }
219    
220                            request.setAttribute(WebKeys.RENDER_PORTLET, renderPortlet);
221    
222                            if (renderPortletResource == null) {
223                                    request.removeAttribute(WebKeys.RENDER_PORTLET_RESOURCE);
224                            }
225                            else {
226                                    request.setAttribute(
227                                            WebKeys.RENDER_PORTLET_RESOURCE, renderPortletResource);
228                            }
229                    }
230            }
231    
232            protected StringBundler doDispatch(
233                            PageContext pageContext, String portletId,
234                            TemplateResource templateResource, boolean processTemplate)
235                    throws Exception {
236    
237                    LayoutTemplate layoutTemplate = getLayoutTemplate(
238                            templateResource.getTemplateId());
239    
240                    String pluginServletContextName = GetterUtil.getString(
241                            layoutTemplate.getServletContextName());
242    
243                    ServletContext pluginServletContext = ServletContextPool.get(
244                            pluginServletContextName);
245    
246                    ClassLoader pluginClassLoader = null;
247    
248                    if (pluginServletContext != null) {
249                            pluginClassLoader =
250                                    (ClassLoader)pluginServletContext.getAttribute(
251                                            PluginContextListener.PLUGIN_CLASS_LOADER);
252                    }
253    
254                    ClassLoader contextClassLoader =
255                            PACLClassLoaderUtil.getContextClassLoader();
256    
257                    try {
258                            TemplateContextType templateContextType =
259                                    TemplateContextType.STANDARD;
260    
261                            if ((pluginClassLoader != null) &&
262                                    (pluginClassLoader != contextClassLoader)) {
263    
264                                    PACLClassLoaderUtil.setContextClassLoader(pluginClassLoader);
265    
266                                    templateContextType = TemplateContextType.CLASS_LOADER;
267                            }
268    
269                            if (processTemplate) {
270                                    return doProcessTemplate(
271                                            pageContext, portletId, templateResource,
272                                            templateContextType);
273                            }
274                            else {
275                                    doProcessCustomizationSettings(
276                                            pageContext, templateResource, templateContextType);
277    
278                                    return null;
279                            }
280                    }
281                    finally {
282                            if ((pluginClassLoader != null) &&
283                                    (pluginClassLoader != contextClassLoader)) {
284    
285                                    PACLClassLoaderUtil.setContextClassLoader(contextClassLoader);
286                            }
287                    }
288            }
289    
290            protected void doProcessCustomizationSettings(
291                            PageContext pageContext, TemplateResource templateResource,
292                            TemplateContextType templateContextType)
293                    throws Exception {
294    
295                    HttpServletRequest request =
296                            (HttpServletRequest)pageContext.getRequest();
297                    HttpServletResponse response =
298                            (HttpServletResponse)pageContext.getResponse();
299    
300                    CustomizationSettingsProcessor processor =
301                            new CustomizationSettingsProcessor(pageContext);
302    
303                    Template template = TemplateManagerUtil.getTemplate(
304                            TemplateManager.VELOCITY, templateResource,
305                            TemplateContextType.STANDARD);
306    
307                    template.put("processor", processor);
308    
309                    // Velocity variables
310    
311                    template.prepare(request);
312    
313                    // liferay:include tag library
314    
315                    VelocityTaglib velocityTaglib = new VelocityTaglib(
316                            pageContext.getServletContext(), request, response, pageContext,
317                            template);
318    
319                    template.put("taglibLiferay", velocityTaglib);
320                    template.put("theme", velocityTaglib);
321    
322                    try {
323                            template.processTemplate(pageContext.getOut());
324                    }
325                    catch (Exception e) {
326                            _log.error(e, e);
327    
328                            throw e;
329                    }
330            }
331    
332            protected StringBundler doProcessTemplate(
333                            PageContext pageContext, String portletId,
334                            TemplateResource templateResource,
335                            TemplateContextType templateContextType)
336                    throws Exception {
337    
338                    HttpServletRequest request =
339                            (HttpServletRequest)pageContext.getRequest();
340                    HttpServletResponse response =
341                            (HttpServletResponse)pageContext.getResponse();
342    
343                    TemplateProcessor processor = new TemplateProcessor(
344                            request, response, portletId);
345    
346                    Template template = TemplateManagerUtil.getTemplate(
347                            TemplateManager.VELOCITY, templateResource, templateContextType);
348    
349                    template.put("processor", processor);
350    
351                    // Velocity variables
352    
353                    template.prepare(request);
354    
355                    // liferay:include tag library
356    
357                    UnsyncStringWriter unsyncStringWriter = new UnsyncStringWriter();
358    
359                    VelocityTaglib velocityTaglib = new VelocityTaglib(
360                            pageContext.getServletContext(), request,
361                            new PipingServletResponse(response, unsyncStringWriter),
362                            pageContext, template);
363    
364                    template.put("taglibLiferay", velocityTaglib);
365                    template.put("theme", velocityTaglib);
366    
367                    try {
368                            template.processTemplate(unsyncStringWriter);
369                    }
370                    catch (Exception e) {
371                            _log.error(e, e);
372    
373                            throw e;
374                    }
375    
376                    boolean portletParallelRender = GetterUtil.getBoolean(
377                            request.getAttribute(WebKeys.PORTLET_PARALLEL_RENDER));
378    
379                    Lock lock = null;
380    
381                    Map<String, StringBundler> contentsMap =
382                            new HashMap<String, StringBundler>();
383    
384                    Map<Integer, List<PortletRenderer>> portletRenderersMap =
385                            processor.getPortletRenderers();
386    
387                    for (Map.Entry<Integer, List<PortletRenderer>> entry :
388                                    portletRenderersMap.entrySet()) {
389    
390                            if (_log.isDebugEnabled()) {
391                                    _log.debug(
392                                            "Processing portlets with render weight " + entry.getKey());
393                            }
394    
395                            List<PortletRenderer> portletRenderers = entry.getValue();
396    
397                            if (portletParallelRender && (portletRenderers.size() > 1)) {
398                                    StopWatch stopWatch = null;
399    
400                                    if (_log.isDebugEnabled()) {
401                                            _log.debug("Start parallel rendering");
402    
403                                            stopWatch = new StopWatch();
404    
405                                            stopWatch.start();
406                                    }
407    
408                                    if (lock == null) {
409                                            lock = new ReentrantLock();
410                                    }
411    
412                                    request.setAttribute(
413                                            WebKeys.PARALLEL_RENDERING_MERGE_LOCK, lock);
414    
415                                    ObjectValuePair<HttpServletRequest, Closeable> objectValuePair =
416                                            ThreadLocalFacadeServletRequestWrapperUtil.inject(request);
417    
418                                    try {
419                                            parallelyRenderPortlets(
420                                                    objectValuePair.getKey(), response, processor,
421                                                    contentsMap, portletRenderers);
422                                    }
423                                    finally {
424                                            Closeable closeable = objectValuePair.getValue();
425    
426                                            closeable.close();
427                                    }
428    
429                                    request.removeAttribute(WebKeys.PARALLEL_RENDERING_MERGE_LOCK);
430    
431                                    if (_log.isDebugEnabled()) {
432                                            _log.debug(
433                                                    "Finished parallel rendering in " +
434                                                            stopWatch.getTime() + " ms");
435                                    }
436                            }
437                            else {
438                                    StopWatch stopWatch = null;
439    
440                                    if (_log.isDebugEnabled()) {
441                                            _log.debug("Start serial rendering");
442    
443                                            stopWatch = new StopWatch();
444    
445                                            stopWatch.start();
446                                    }
447    
448                                    for (PortletRenderer portletRenderer : portletRenderers) {
449                                            Portlet portlet = portletRenderer.getPortlet();
450    
451                                            contentsMap.put(
452                                                    portlet.getPortletId(),
453                                                    portletRenderer.render(request, response));
454    
455                                            if (_log.isDebugEnabled()) {
456                                                    _log.debug(
457                                                            "Serially rendered portlet " +
458                                                                    portlet.getPortletId() + " in " +
459                                                                            stopWatch.getTime() + " ms");
460                                            }
461                                    }
462    
463                                    if (_log.isDebugEnabled()) {
464                                            _log.debug(
465                                                    "Finished serial rendering in " + stopWatch.getTime() +
466                                                            " ms");
467                                    }
468                            }
469                    }
470    
471                    if (portletParallelRender && (_waitTime == Integer.MAX_VALUE)) {
472                            _waitTime = PropsValues.LAYOUT_PARALLEL_RENDER_TIMEOUT;
473                    }
474    
475                    StringBundler sb = StringUtil.replaceWithStringBundler(
476                            unsyncStringWriter.toString(), "[$TEMPLATE_PORTLET_", "$]",
477                            contentsMap);
478    
479                    return sb;
480            }
481    
482            protected LayoutTemplate getLayoutTemplate(String velocityTemplateId) {
483                    String separator = LayoutTemplateConstants.CUSTOM_SEPARATOR;
484                    boolean standard = false;
485    
486                    if (velocityTemplateId.contains(
487                                    LayoutTemplateConstants.STANDARD_SEPARATOR)) {
488    
489                            separator = LayoutTemplateConstants.STANDARD_SEPARATOR;
490                            standard = true;
491                    }
492    
493                    String layoutTemplateId = null;
494    
495                    String themeId = null;
496    
497                    int pos = velocityTemplateId.indexOf(separator);
498    
499                    if (pos != -1) {
500                            layoutTemplateId = velocityTemplateId.substring(
501                                    pos + separator.length());
502    
503                            themeId = velocityTemplateId.substring(0, pos);
504                    }
505    
506                    pos = layoutTemplateId.indexOf(PortletConstants.INSTANCE_SEPARATOR);
507    
508                    if (pos != -1) {
509                            layoutTemplateId = layoutTemplateId.substring(
510                                    pos + PortletConstants.INSTANCE_SEPARATOR.length() + 1);
511    
512                            pos = layoutTemplateId.indexOf(StringPool.UNDERLINE);
513    
514                            layoutTemplateId = layoutTemplateId.substring(pos + 1);
515                    }
516    
517                    return LayoutTemplateLocalServiceUtil.getLayoutTemplate(
518                            layoutTemplateId, standard, themeId);
519            }
520    
521            protected void parallelyRenderPortlets(
522                            HttpServletRequest request, HttpServletResponse response,
523                            TemplateProcessor processor, Map<String, StringBundler> contentsMap,
524                            List<PortletRenderer> portletRenderers)
525                    throws Exception {
526    
527                    ExecutorService executorService =
528                            PortalExecutorManagerUtil.getPortalExecutor(
529                                    RuntimePageImpl.class.getName());
530    
531                    Map<Future<StringBundler>, PortletRenderer> futures =
532                            new HashMap<Future<StringBundler>, PortletRenderer>(
533                                    portletRenderers.size());
534    
535                    for (PortletRenderer portletRenderer : portletRenderers) {
536                            if (_log.isDebugEnabled()) {
537                                    Portlet portlet = portletRenderer.getPortlet();
538    
539                                    _log.debug(
540                                            "Submit portlet " + portlet.getPortletId() +
541                                                    " for parallel rendering");
542                            }
543    
544                            Callable<StringBundler> renderCallable =
545                                    portletRenderer.getCallable(request, response);
546    
547                            Future<StringBundler> future = null;
548    
549                            try {
550                                    future = executorService.submit(renderCallable);
551                            }
552                            catch (RejectedExecutionException ree) {
553    
554                                    // This should only happen when user configures an AbortPolicy
555                                    // (or some other customized RejectedExecutionHandler that
556                                    // throws RejectedExecutionException) for this
557                                    // ThreadPoolExecutor. AbortPolicy is not the recommended
558                                    // setting, but to be more robust, we take care of this by
559                                    // converting the rejection to a fallback action.
560    
561                                    future = new FutureTask<StringBundler>(renderCallable);
562    
563                                    // Cancel immediately
564    
565                                    future.cancel(true);
566                            }
567    
568                            futures.put(future, portletRenderer);
569                    }
570    
571                    long waitTime = _waitTime;
572    
573                    for (Map.Entry<Future<StringBundler>, PortletRenderer> entry :
574                                    futures.entrySet()) {
575    
576                            Future<StringBundler> future = entry.getKey();
577                            PortletRenderer portletRenderer = entry.getValue();
578    
579                            Portlet portlet = portletRenderer.getPortlet();
580    
581                            if (future.isCancelled()) {
582                                    if (_log.isDebugEnabled()) {
583                                            _log.debug(
584                                                    "Reject portlet " + portlet.getPortletId() +
585                                                            " for parallel rendering");
586                                    }
587                            }
588                            else if ((waitTime > 0) || future.isDone()) {
589                                    try {
590                                            long startTime = System.currentTimeMillis();
591    
592                                            StringBundler sb = future.get(
593                                                    waitTime, TimeUnit.MILLISECONDS);
594    
595                                            long duration = System.currentTimeMillis() - startTime;
596    
597                                            waitTime -= duration;
598    
599                                            contentsMap.put(portlet.getPortletId(), sb);
600    
601                                            portletRenderer.finishParallelRender();
602    
603                                            if (_log.isDebugEnabled()) {
604                                                    _log.debug(
605                                                            "Parallely rendered portlet " +
606                                                                    portlet.getPortletId() + " in " + duration +
607                                                                            " ms");
608                                            }
609    
610                                            continue;
611                                    }
612                                    catch (ExecutionException ee) {
613                                            throw ee;
614                                    }
615                                    catch (InterruptedException ie) {
616    
617                                            // On interruption, stop waiting, force all pending
618                                            // portlets to fall back to ajax loading or an error
619                                            // message.
620    
621                                            waitTime = -1;
622                                    }
623                                    catch (TimeoutException te) {
624    
625                                            // On timeout, stop waiting, force all pending portlets to
626                                            // fall back to ajax loading or an error message.
627    
628                                            waitTime = -1;
629                                    }
630                                    catch (CancellationException ce) {
631    
632                                            // This should only happen on a concurrent shutdown of the
633                                            // thread pool. Simply stops the render process.
634    
635                                            if (_log.isDebugEnabled()) {
636                                                    _log.debug(
637                                                            "Asynchronized cancellation detected that should " +
638                                                                    "only be caused by a concurrent shutdown of " +
639                                                                            "the thread pool",
640                                                            ce);
641                                            }
642    
643                                            return;
644                                    }
645    
646                                    // Cancel by interrupting rendering thread
647    
648                                    future.cancel(true);
649                            }
650    
651                            StringBundler sb = null;
652    
653                            if (processor.isPortletAjaxRender() && portlet.isAjaxable()) {
654                                    if (_log.isDebugEnabled()) {
655                                            _log.debug(
656                                                    "Fall back to ajax rendering of portlet " +
657                                                            portlet.getPortletId());
658                                    }
659    
660                                    sb = portletRenderer.renderAjax(request, response);
661                            }
662                            else {
663                                    if (_log.isDebugEnabled()) {
664                                            if (processor.isPortletAjaxRender()) {
665                                                    _log.debug(
666                                                            "Fall back to an error message for portlet " +
667                                                                    portlet.getPortletId() +
668                                                                            " since it is not ajaxable");
669                                            }
670                                            else {
671                                                    _log.debug(
672                                                            "Fall back to an error message for portlet " +
673                                                                    portlet.getPortletId() +
674                                                                            " since ajax rendering is disabled");
675                                            }
676                                    }
677    
678                                    sb = portletRenderer.renderError(request, response);
679                            }
680    
681                            contentsMap.put(portlet.getPortletId(), sb);
682    
683                            portletRenderer.finishParallelRender();
684                    }
685            }
686    
687            private static Log _log = LogFactoryUtil.getLog(RuntimePageImpl.class);
688    
689            private int _waitTime = Integer.MAX_VALUE;
690    
691    }