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