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