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