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