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