001    /**
002     * Copyright (c) 2000-present 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.ServletContextPool;
023    import com.liferay.portal.kernel.template.Template;
024    import com.liferay.portal.kernel.template.TemplateConstants;
025    import com.liferay.portal.kernel.template.TemplateManager;
026    import com.liferay.portal.kernel.template.TemplateManagerUtil;
027    import com.liferay.portal.kernel.template.TemplateResource;
028    import com.liferay.portal.kernel.util.ClassLoaderUtil;
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.kernel.util.WebKeys;
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.service.LayoutTemplateLocalServiceUtil;
047    import com.liferay.portal.servlet.ThreadLocalFacadeServletRequestWrapperUtil;
048    import com.liferay.portal.util.PropsValues;
049    import com.liferay.taglib.servlet.PipingServletResponse;
050    import com.liferay.taglib.util.DummyVelocityTaglib;
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    
077    import org.apache.commons.lang.time.StopWatch;
078    
079    /**
080     * @author Brian Wing Shun Chan
081     * @author Raymond Aug??
082     * @author Shuyang Zhou
083     */
084    @DoPrivileged
085    public class RuntimePageImpl implements RuntimePage {
086    
087            @Override
088            public StringBundler getProcessedTemplate(
089                            HttpServletRequest request, HttpServletResponse response,
090                            String portletId, TemplateResource templateResource)
091                    throws Exception {
092    
093                    return doDispatch(request, response, portletId, templateResource, true);
094            }
095    
096            @Override
097            public void processCustomizationSettings(
098                            HttpServletRequest request, HttpServletResponse response,
099                            TemplateResource templateResource)
100                    throws Exception {
101    
102                    doDispatch(request, response, null, templateResource, false);
103            }
104    
105            @Override
106            public void processTemplate(
107                            HttpServletRequest request, HttpServletResponse response,
108                            String portletId, TemplateResource templateResource)
109                    throws Exception {
110    
111                    StringBundler sb = doDispatch(
112                            request, response, portletId, templateResource, true);
113    
114                    sb.writeTo(response.getWriter());
115            }
116    
117            @Override
118            public void processTemplate(
119                            HttpServletRequest request, HttpServletResponse response,
120                            TemplateResource templateResource)
121                    throws Exception {
122    
123                    processTemplate(request, response, null, templateResource);
124            }
125    
126            @Override
127            public String processXML(
128                            HttpServletRequest request, HttpServletResponse response,
129                            String content)
130                    throws Exception {
131    
132                    PortletResponse portletResponse = (PortletResponse)request.getAttribute(
133                            JavaConstants.JAVAX_PORTLET_RESPONSE);
134    
135                    if ((portletResponse != null) &&
136                            !(portletResponse instanceof RenderResponse)) {
137    
138                            throw new IllegalArgumentException(
139                                    "processXML can only be invoked in the render phase");
140                    }
141    
142                    RuntimeLogic portletLogic = new PortletLogic(request, response);
143    
144                    content = processXML(request, content, portletLogic);
145    
146                    if (portletResponse == null) {
147                            return content;
148                    }
149    
150                    RenderResponse renderResponse = (RenderResponse)portletResponse;
151    
152                    RuntimeLogic actionURLLogic = new ActionURLLogic(renderResponse);
153                    RuntimeLogic renderURLLogic = new RenderURLLogic(renderResponse);
154    
155                    content = processXML(request, content, actionURLLogic);
156                    content = processXML(request, content, renderURLLogic);
157    
158                    return content;
159            }
160    
161            @Override
162            public String processXML(
163                            HttpServletRequest request, String content,
164                            RuntimeLogic runtimeLogic)
165                    throws Exception {
166    
167                    if (Validator.isNull(content)) {
168                            return StringPool.BLANK;
169                    }
170    
171                    int index = content.indexOf(runtimeLogic.getOpenTag());
172    
173                    if (index == -1) {
174                            return content;
175                    }
176    
177                    Portlet renderPortlet = (Portlet)request.getAttribute(
178                            WebKeys.RENDER_PORTLET);
179    
180                    Boolean renderPortletResource = (Boolean)request.getAttribute(
181                            WebKeys.RENDER_PORTLET_RESOURCE);
182    
183                    String outerPortletId = (String)request.getAttribute(
184                            WebKeys.OUTER_PORTLET_ID);
185    
186                    if (outerPortletId == null) {
187                            request.setAttribute(
188                                    WebKeys.OUTER_PORTLET_ID, renderPortlet.getPortletId());
189                    }
190    
191                    try {
192                            request.setAttribute(WebKeys.RENDER_PORTLET_RESOURCE, Boolean.TRUE);
193    
194                            StringBundler sb = new StringBundler();
195    
196                            int x = 0;
197                            int y = index;
198    
199                            while (y != -1) {
200                                    sb.append(content.substring(x, y));
201    
202                                    int close1 = content.indexOf(runtimeLogic.getClose1Tag(), y);
203                                    int close2 = content.indexOf(runtimeLogic.getClose2Tag(), y);
204    
205                                    if ((close2 == -1) || ((close1 != -1) && (close1 < close2))) {
206                                            x = close1 + runtimeLogic.getClose1Tag().length();
207                                    }
208                                    else {
209                                            x = close2 + runtimeLogic.getClose2Tag().length();
210                                    }
211    
212                                    String runtimePortletTag = content.substring(y, x);
213    
214                                    if ((renderPortlet != null) &&
215                                            runtimePortletTag.contains(renderPortlet.getPortletId())) {
216    
217                                            return StringPool.BLANK;
218                                    }
219    
220                                    sb.append(runtimeLogic.processXML(runtimePortletTag));
221    
222                                    y = content.indexOf(runtimeLogic.getOpenTag(), x);
223                            }
224    
225                            if (y == -1) {
226                                    sb.append(content.substring(x));
227                            }
228    
229                            return sb.toString();
230                    }
231                    finally {
232                            if (outerPortletId == null) {
233                                    request.removeAttribute(WebKeys.OUTER_PORTLET_ID);
234                            }
235    
236                            request.setAttribute(WebKeys.RENDER_PORTLET, renderPortlet);
237    
238                            if (renderPortletResource == null) {
239                                    request.removeAttribute(WebKeys.RENDER_PORTLET_RESOURCE);
240                            }
241                            else {
242                                    request.setAttribute(
243                                            WebKeys.RENDER_PORTLET_RESOURCE, renderPortletResource);
244                            }
245                    }
246            }
247    
248            protected StringBundler doDispatch(
249                            HttpServletRequest request, HttpServletResponse response,
250                            String portletId, TemplateResource templateResource,
251                            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 = pluginServletContext.getClassLoader();
268                            }
269                    }
270    
271                    ClassLoader contextClassLoader =
272                            ClassLoaderUtil.getContextClassLoader();
273    
274                    try {
275                            if ((pluginClassLoader != null) &&
276                                    (pluginClassLoader != contextClassLoader)) {
277    
278                                    ClassLoaderUtil.setContextClassLoader(pluginClassLoader);
279                            }
280    
281                            if (processTemplate) {
282                                    return doProcessTemplate(
283                                            request, response, portletId, templateResource, false);
284                            }
285                            else {
286                                    doProcessCustomizationSettings(
287                                            request, response, templateResource, false);
288    
289                                    return null;
290                            }
291                    }
292                    finally {
293                            if ((pluginClassLoader != null) &&
294                                    (pluginClassLoader != contextClassLoader)) {
295    
296                                    ClassLoaderUtil.setContextClassLoader(contextClassLoader);
297                            }
298                    }
299            }
300    
301            protected void doProcessCustomizationSettings(
302                            HttpServletRequest request, HttpServletResponse response,
303                            TemplateResource templateResource, boolean restricted)
304                    throws Exception {
305    
306                    CustomizationSettingsProcessor processor =
307                            new CustomizationSettingsProcessor(request, response);
308    
309                    Template template = TemplateManagerUtil.getTemplate(
310                            TemplateConstants.LANG_TYPE_VM, templateResource, restricted);
311    
312                    template.put("processor", processor);
313    
314                    // Velocity variables
315    
316                    template.prepare(request);
317    
318                    // liferay:include tag library
319    
320                    VelocityTaglib velocityTaglib = new DummyVelocityTaglib();
321    
322                    template.put("taglibLiferay", velocityTaglib);
323                    template.put("theme", velocityTaglib);
324    
325                    try {
326                            template.processTemplate(response.getWriter());
327                    }
328                    catch (Exception e) {
329                            _log.error(e, e);
330    
331                            throw e;
332                    }
333            }
334    
335            protected StringBundler doProcessTemplate(
336                            HttpServletRequest request, HttpServletResponse response,
337                            String portletId, TemplateResource templateResource,
338                            boolean restricted)
339                    throws Exception {
340    
341                    TemplateProcessor processor = new TemplateProcessor(
342                            request, response, portletId);
343    
344                    TemplateManager templateManager =
345                            TemplateManagerUtil.getTemplateManager(
346                                    TemplateConstants.LANG_TYPE_VM);
347    
348                    Template template = TemplateManagerUtil.getTemplate(
349                            TemplateConstants.LANG_TYPE_VM, templateResource, restricted);
350    
351                    template.put("processor", processor);
352    
353                    // Velocity variables
354    
355                    template.prepare(request);
356    
357                    UnsyncStringWriter unsyncStringWriter = new UnsyncStringWriter();
358    
359                    templateManager.addTaglibTheme(
360                            template, "taglibLiferay", request,
361                            new PipingServletResponse(response, unsyncStringWriter));
362    
363                    try {
364                            template.processTemplate(unsyncStringWriter);
365                    }
366                    catch (Exception e) {
367                            _log.error(e, e);
368    
369                            throw e;
370                    }
371    
372                    boolean portletParallelRender = GetterUtil.getBoolean(
373                            request.getAttribute(WebKeys.PORTLET_PARALLEL_RENDER));
374    
375                    Lock lock = null;
376    
377                    Map<String, StringBundler> contentsMap = new HashMap<>();
378    
379                    Map<Integer, List<PortletRenderer>> portletRenderersMap =
380                            processor.getPortletRenderers();
381    
382                    for (Map.Entry<Integer, List<PortletRenderer>> entry :
383                                    portletRenderersMap.entrySet()) {
384    
385                            if (_log.isDebugEnabled()) {
386                                    _log.debug(
387                                            "Processing portlets with render weight " + entry.getKey());
388                            }
389    
390                            List<PortletRenderer> portletRenderers = entry.getValue();
391    
392                            StopWatch stopWatch = new StopWatch();
393    
394                            stopWatch.start();
395    
396                            if (portletParallelRender && (portletRenderers.size() > 1)) {
397                                    if (_log.isDebugEnabled()) {
398                                            _log.debug("Start parallel rendering");
399                                    }
400    
401                                    if (lock == null) {
402                                            lock = new ReentrantLock();
403                                    }
404    
405                                    request.setAttribute(
406                                            WebKeys.PARALLEL_RENDERING_MERGE_LOCK, lock);
407    
408                                    ObjectValuePair<HttpServletRequest, Closeable> objectValuePair =
409                                            ThreadLocalFacadeServletRequestWrapperUtil.inject(request);
410    
411                                    try {
412                                            parallelyRenderPortlets(
413                                                    objectValuePair.getKey(), response, processor,
414                                                    contentsMap, portletRenderers);
415                                    }
416                                    finally {
417                                            Closeable closeable = objectValuePair.getValue();
418    
419                                            closeable.close();
420                                    }
421    
422                                    request.removeAttribute(WebKeys.PARALLEL_RENDERING_MERGE_LOCK);
423    
424                                    if (_log.isDebugEnabled()) {
425                                            _log.debug(
426                                                    "Finished parallel rendering in " +
427                                                            stopWatch.getTime() + " ms");
428                                    }
429                            }
430                            else {
431                                    if (_log.isDebugEnabled()) {
432                                            _log.debug("Start serial rendering");
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                    return sb;
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(
494                            LayoutTemplateConstants.INSTANCE_SEPARATOR);
495    
496                    if (pos != -1) {
497                            layoutTemplateId = layoutTemplateId.substring(
498                                    pos + LayoutTemplateConstants.INSTANCE_SEPARATOR.length() + 1);
499    
500                            pos = layoutTemplateId.indexOf(StringPool.UNDERLINE);
501    
502                            layoutTemplateId = layoutTemplateId.substring(pos + 1);
503                    }
504    
505                    return LayoutTemplateLocalServiceUtil.getLayoutTemplate(
506                            layoutTemplateId, standard, themeId);
507            }
508    
509            protected void parallelyRenderPortlets(
510                            HttpServletRequest request, HttpServletResponse response,
511                            TemplateProcessor processor, Map<String, StringBundler> contentsMap,
512                            List<PortletRenderer> portletRenderers)
513                    throws Exception {
514    
515                    ExecutorService executorService =
516                            PortalExecutorManagerUtil.getPortalExecutor(
517                                    RuntimePageImpl.class.getName());
518    
519                    Map<Future<StringBundler>, PortletRenderer> futures = new HashMap<>(
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<>(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 portlets
605                                            // to fall back to ajax loading or an error message.
606    
607                                            waitTime = -1;
608                                    }
609                                    catch (TimeoutException te) {
610    
611                                            // On timeout, stop waiting, force all pending portlets to
612                                            // fall back to ajax loading or an error message.
613    
614                                            waitTime = -1;
615                                    }
616                                    catch (CancellationException ce) {
617    
618                                            // This should only happen on a concurrent shutdown of the
619                                            // thread pool. Simply stops the render process.
620    
621                                            if (_log.isDebugEnabled()) {
622                                                    _log.debug(
623                                                            "Asynchronized cancellation detected that should " +
624                                                                    "only be caused by a concurrent shutdown of " +
625                                                                            "the thread pool",
626                                                            ce);
627                                            }
628    
629                                            return;
630                                    }
631    
632                                    // Cancel by interrupting rendering thread
633    
634                                    future.cancel(true);
635                            }
636    
637                            StringBundler sb = null;
638    
639                            if (processor.isPortletAjaxRender() && portlet.isAjaxable()) {
640                                    if (_log.isDebugEnabled()) {
641                                            _log.debug(
642                                                    "Fall back to ajax rendering of portlet " +
643                                                            portlet.getPortletId());
644                                    }
645    
646                                    sb = portletRenderer.renderAjax(request, response);
647                            }
648                            else {
649                                    if (_log.isDebugEnabled()) {
650                                            if (processor.isPortletAjaxRender()) {
651                                                    _log.debug(
652                                                            "Fall back to an error message for portlet " +
653                                                                    portlet.getPortletId() +
654                                                                            " since it is not ajaxable");
655                                            }
656                                            else {
657                                                    _log.debug(
658                                                            "Fall back to an error message for portlet " +
659                                                                    portlet.getPortletId() +
660                                                                            " since ajax rendering is disabled");
661                                            }
662                                    }
663    
664                                    sb = portletRenderer.renderError(request, response);
665                            }
666    
667                            contentsMap.put(portlet.getPortletId(), sb);
668    
669                            portletRenderer.finishParallelRender();
670                    }
671            }
672    
673            private static final Log _log = LogFactoryUtil.getLog(
674                    RuntimePageImpl.class);
675    
676            private int _waitTime = Integer.MAX_VALUE;
677    
678    }