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.TemplateManagerUtil;
026    import com.liferay.portal.kernel.template.TemplateResource;
027    import com.liferay.portal.kernel.util.GetterUtil;
028    import com.liferay.portal.kernel.util.JavaConstants;
029    import com.liferay.portal.kernel.util.ObjectValuePair;
030    import com.liferay.portal.kernel.util.StringBundler;
031    import com.liferay.portal.kernel.util.StringPool;
032    import com.liferay.portal.kernel.util.StringUtil;
033    import com.liferay.portal.kernel.util.Validator;
034    import com.liferay.portal.layoutconfiguration.util.velocity.CustomizationSettingsProcessor;
035    import com.liferay.portal.layoutconfiguration.util.velocity.TemplateProcessor;
036    import com.liferay.portal.layoutconfiguration.util.xml.ActionURLLogic;
037    import com.liferay.portal.layoutconfiguration.util.xml.PortletLogic;
038    import com.liferay.portal.layoutconfiguration.util.xml.RenderURLLogic;
039    import com.liferay.portal.layoutconfiguration.util.xml.RuntimeLogic;
040    import com.liferay.portal.model.LayoutTemplate;
041    import com.liferay.portal.model.LayoutTemplateConstants;
042    import com.liferay.portal.model.Portlet;
043    import com.liferay.portal.model.PortletConstants;
044    import com.liferay.portal.service.LayoutTemplateLocalServiceUtil;
045    import com.liferay.portal.servlet.ThreadLocalFacadeServletRequestWrapperUtil;
046    import com.liferay.portal.util.ClassLoaderUtil;
047    import com.liferay.portal.util.PropsValues;
048    import com.liferay.portal.util.WebKeys;
049    import com.liferay.taglib.servlet.PipingServletResponse;
050    import com.liferay.taglib.util.DummyVelocityTaglib;
051    import com.liferay.taglib.util.VelocityTaglib;
052    import com.liferay.taglib.util.VelocityTaglibImpl;
053    
054    import java.io.Closeable;
055    
056    import java.util.HashMap;
057    import java.util.List;
058    import java.util.Map;
059    import java.util.concurrent.Callable;
060    import java.util.concurrent.CancellationException;
061    import java.util.concurrent.ExecutionException;
062    import java.util.concurrent.ExecutorService;
063    import java.util.concurrent.Future;
064    import java.util.concurrent.FutureTask;
065    import java.util.concurrent.RejectedExecutionException;
066    import java.util.concurrent.TimeUnit;
067    import java.util.concurrent.TimeoutException;
068    import java.util.concurrent.locks.Lock;
069    import java.util.concurrent.locks.ReentrantLock;
070    
071    import javax.portlet.PortletResponse;
072    import javax.portlet.RenderResponse;
073    
074    import javax.servlet.ServletContext;
075    import javax.servlet.http.HttpServletRequest;
076    import javax.servlet.http.HttpServletResponse;
077    
078    import org.apache.commons.lang.time.StopWatch;
079    
080    /**
081     * @author Brian Wing Shun Chan
082     * @author Raymond Aug??
083     * @author Shuyang Zhou
084     */
085    @DoPrivileged
086    public class RuntimePageImpl implements RuntimePage {
087    
088            @Override
089            public StringBundler getProcessedTemplate(
090                            HttpServletRequest request, HttpServletResponse response,
091                            String portletId, TemplateResource templateResource)
092                    throws Exception {
093    
094                    return doDispatch(request, response, portletId, templateResource, true);
095            }
096    
097            @Override
098            public void processCustomizationSettings(
099                            HttpServletRequest request, HttpServletResponse response,
100                            TemplateResource templateResource)
101                    throws Exception {
102    
103                    doDispatch(request, response, null, templateResource, false);
104            }
105    
106            @Override
107            public void processTemplate(
108                            HttpServletRequest request, HttpServletResponse response,
109                            String portletId, TemplateResource templateResource)
110                    throws Exception {
111    
112                    StringBundler sb = doDispatch(
113                            request, response, portletId, templateResource, true);
114    
115                    sb.writeTo(response.getWriter());
116            }
117    
118            @Override
119            public void processTemplate(
120                            HttpServletRequest request, HttpServletResponse response,
121                            TemplateResource templateResource)
122                    throws Exception {
123    
124                    processTemplate(request, response, 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                            HttpServletRequest request, HttpServletResponse response,
251                            String portletId, TemplateResource templateResource,
252                            boolean processTemplate)
253                    throws Exception {
254    
255                    ClassLoader pluginClassLoader = null;
256    
257                    LayoutTemplate layoutTemplate = getLayoutTemplate(
258                            templateResource.getTemplateId());
259    
260                    if (layoutTemplate != null) {
261                            String pluginServletContextName = GetterUtil.getString(
262                                    layoutTemplate.getServletContextName());
263    
264                            ServletContext pluginServletContext = ServletContextPool.get(
265                                    pluginServletContextName);
266    
267                            if (pluginServletContext != null) {
268                                    pluginClassLoader = pluginServletContext.getClassLoader();
269                            }
270                    }
271    
272                    ClassLoader contextClassLoader =
273                            ClassLoaderUtil.getContextClassLoader();
274    
275                    try {
276                            if ((pluginClassLoader != null) &&
277                                    (pluginClassLoader != contextClassLoader)) {
278    
279                                    ClassLoaderUtil.setContextClassLoader(pluginClassLoader);
280                            }
281    
282                            if (processTemplate) {
283                                    return doProcessTemplate(
284                                            request, response, portletId, templateResource, false);
285                            }
286                            else {
287                                    doProcessCustomizationSettings(
288                                            request, response, templateResource, false);
289    
290                                    return null;
291                            }
292                    }
293                    finally {
294                            if ((pluginClassLoader != null) &&
295                                    (pluginClassLoader != contextClassLoader)) {
296    
297                                    ClassLoaderUtil.setContextClassLoader(contextClassLoader);
298                            }
299                    }
300            }
301    
302            protected void doProcessCustomizationSettings(
303                            HttpServletRequest request, HttpServletResponse response,
304                            TemplateResource templateResource, boolean restricted)
305                    throws Exception {
306    
307                    CustomizationSettingsProcessor processor =
308                            new CustomizationSettingsProcessor(request, response);
309    
310                    Template template = TemplateManagerUtil.getTemplate(
311                            TemplateConstants.LANG_TYPE_VM, templateResource, restricted);
312    
313                    template.put("processor", processor);
314    
315                    // Velocity variables
316    
317                    template.prepare(request);
318    
319                    // liferay:include tag library
320    
321                    VelocityTaglib velocityTaglib = new DummyVelocityTaglib();
322    
323                    template.put("taglibLiferay", velocityTaglib);
324                    template.put("theme", velocityTaglib);
325    
326                    try {
327                            template.processTemplate(response.getWriter());
328                    }
329                    catch (Exception e) {
330                            _log.error(e, e);
331    
332                            throw e;
333                    }
334            }
335    
336            protected StringBundler doProcessTemplate(
337                            HttpServletRequest request, HttpServletResponse response,
338                            String portletId, TemplateResource templateResource,
339                            boolean restricted)
340                    throws Exception {
341    
342                    TemplateProcessor processor = new TemplateProcessor(
343                            request, response, portletId);
344    
345                    Template template = TemplateManagerUtil.getTemplate(
346                            TemplateConstants.LANG_TYPE_VM, templateResource, restricted);
347    
348                    template.put("processor", processor);
349    
350                    // Velocity variables
351    
352                    template.prepare(request);
353    
354                    // liferay:include tag library
355    
356                    UnsyncStringWriter unsyncStringWriter = new UnsyncStringWriter();
357    
358                    VelocityTaglib velocityTaglib = new VelocityTaglibImpl(
359                            request.getServletContext(), request,
360                            new PipingServletResponse(response, unsyncStringWriter), template);
361    
362                    template.put("taglibLiferay", velocityTaglib);
363                    template.put("theme", velocityTaglib);
364    
365                    try {
366                            template.processTemplate(unsyncStringWriter);
367                    }
368                    catch (Exception e) {
369                            _log.error(e, e);
370    
371                            throw e;
372                    }
373    
374                    boolean portletParallelRender = GetterUtil.getBoolean(
375                            request.getAttribute(WebKeys.PORTLET_PARALLEL_RENDER));
376    
377                    Lock lock = null;
378    
379                    Map<String, StringBundler> contentsMap =
380                            new HashMap<String, StringBundler>();
381    
382                    Map<Integer, List<PortletRenderer>> portletRenderersMap =
383                            processor.getPortletRenderers();
384    
385                    for (Map.Entry<Integer, List<PortletRenderer>> entry :
386                                    portletRenderersMap.entrySet()) {
387    
388                            if (_log.isDebugEnabled()) {
389                                    _log.debug(
390                                            "Processing portlets with render weight " + entry.getKey());
391                            }
392    
393                            List<PortletRenderer> portletRenderers = entry.getValue();
394    
395                            StopWatch stopWatch = new StopWatch();
396    
397                            stopWatch.start();
398    
399                            if (portletParallelRender && (portletRenderers.size() > 1)) {
400                                    if (_log.isDebugEnabled()) {
401                                            _log.debug("Start parallel rendering");
402                                    }
403    
404                                    if (lock == null) {
405                                            lock = new ReentrantLock();
406                                    }
407    
408                                    request.setAttribute(
409                                            WebKeys.PARALLEL_RENDERING_MERGE_LOCK, lock);
410    
411                                    ObjectValuePair<HttpServletRequest, Closeable> objectValuePair =
412                                            ThreadLocalFacadeServletRequestWrapperUtil.inject(request);
413    
414                                    try {
415                                            parallelyRenderPortlets(
416                                                    objectValuePair.getKey(), response, processor,
417                                                    contentsMap, portletRenderers);
418                                    }
419                                    finally {
420                                            Closeable closeable = objectValuePair.getValue();
421    
422                                            closeable.close();
423                                    }
424    
425                                    request.removeAttribute(WebKeys.PARALLEL_RENDERING_MERGE_LOCK);
426    
427                                    if (_log.isDebugEnabled()) {
428                                            _log.debug(
429                                                    "Finished parallel rendering in " +
430                                                            stopWatch.getTime() + " ms");
431                                    }
432                            }
433                            else {
434                                    if (_log.isDebugEnabled()) {
435                                            _log.debug("Start serial rendering");
436                                    }
437    
438                                    for (PortletRenderer portletRenderer : portletRenderers) {
439                                            Portlet portlet = portletRenderer.getPortlet();
440    
441                                            contentsMap.put(
442                                                    portlet.getPortletId(),
443                                                    portletRenderer.render(request, response));
444    
445                                            if (_log.isDebugEnabled()) {
446                                                    _log.debug(
447                                                            "Serially rendered portlet " +
448                                                                    portlet.getPortletId() + " in " +
449                                                                            stopWatch.getTime() + " ms");
450                                            }
451                                    }
452    
453                                    if (_log.isDebugEnabled()) {
454                                            _log.debug(
455                                                    "Finished serial rendering in " + stopWatch.getTime() +
456                                                            " ms");
457                                    }
458                            }
459                    }
460    
461                    if (portletParallelRender && (_waitTime == Integer.MAX_VALUE)) {
462                            _waitTime = PropsValues.LAYOUT_PARALLEL_RENDER_TIMEOUT;
463                    }
464    
465                    StringBundler sb = StringUtil.replaceWithStringBundler(
466                            unsyncStringWriter.toString(), "[$TEMPLATE_PORTLET_", "$]",
467                            contentsMap);
468    
469                    return sb;
470            }
471    
472            protected LayoutTemplate getLayoutTemplate(String velocityTemplateId) {
473                    String separator = LayoutTemplateConstants.CUSTOM_SEPARATOR;
474                    boolean standard = false;
475    
476                    if (velocityTemplateId.contains(
477                                    LayoutTemplateConstants.STANDARD_SEPARATOR)) {
478    
479                            separator = LayoutTemplateConstants.STANDARD_SEPARATOR;
480                            standard = true;
481                    }
482    
483                    String layoutTemplateId = null;
484    
485                    String themeId = null;
486    
487                    int pos = velocityTemplateId.indexOf(separator);
488    
489                    if (pos != -1) {
490                            layoutTemplateId = velocityTemplateId.substring(
491                                    pos + separator.length());
492    
493                            themeId = velocityTemplateId.substring(0, pos);
494                    }
495    
496                    pos = layoutTemplateId.indexOf(PortletConstants.INSTANCE_SEPARATOR);
497    
498                    if (pos != -1) {
499                            layoutTemplateId = layoutTemplateId.substring(
500                                    pos + PortletConstants.INSTANCE_SEPARATOR.length() + 1);
501    
502                            pos = layoutTemplateId.indexOf(StringPool.UNDERLINE);
503    
504                            layoutTemplateId = layoutTemplateId.substring(pos + 1);
505                    }
506    
507                    return LayoutTemplateLocalServiceUtil.getLayoutTemplate(
508                            layoutTemplateId, standard, themeId);
509            }
510    
511            protected void parallelyRenderPortlets(
512                            HttpServletRequest request, HttpServletResponse response,
513                            TemplateProcessor processor, Map<String, StringBundler> contentsMap,
514                            List<PortletRenderer> portletRenderers)
515                    throws Exception {
516    
517                    ExecutorService executorService =
518                            PortalExecutorManagerUtil.getPortalExecutor(
519                                    RuntimePageImpl.class.getName());
520    
521                    Map<Future<StringBundler>, PortletRenderer> futures =
522                            new HashMap<Future<StringBundler>, PortletRenderer>(
523                                    portletRenderers.size());
524    
525                    for (PortletRenderer portletRenderer : portletRenderers) {
526                            if (_log.isDebugEnabled()) {
527                                    Portlet portlet = portletRenderer.getPortlet();
528    
529                                    _log.debug(
530                                            "Submit portlet " + portlet.getPortletId() +
531                                                    " for parallel rendering");
532                            }
533    
534                            Callable<StringBundler> renderCallable =
535                                    portletRenderer.getCallable(request, response);
536    
537                            Future<StringBundler> future = null;
538    
539                            try {
540                                    future = executorService.submit(renderCallable);
541                            }
542                            catch (RejectedExecutionException ree) {
543    
544                                    // This should only happen when user configures an AbortPolicy
545                                    // (or some other customized RejectedExecutionHandler that
546                                    // throws RejectedExecutionException) for this
547                                    // ThreadPoolExecutor. AbortPolicy is not the recommended
548                                    // setting, but to be more robust, we take care of this by
549                                    // converting the rejection to a fallback action.
550    
551                                    future = new FutureTask<StringBundler>(renderCallable);
552    
553                                    // Cancel immediately
554    
555                                    future.cancel(true);
556                            }
557    
558                            futures.put(future, portletRenderer);
559                    }
560    
561                    long waitTime = _waitTime;
562    
563                    for (Map.Entry<Future<StringBundler>, PortletRenderer> entry :
564                                    futures.entrySet()) {
565    
566                            Future<StringBundler> future = entry.getKey();
567                            PortletRenderer portletRenderer = entry.getValue();
568    
569                            Portlet portlet = portletRenderer.getPortlet();
570    
571                            if (future.isCancelled()) {
572                                    if (_log.isDebugEnabled()) {
573                                            _log.debug(
574                                                    "Reject portlet " + portlet.getPortletId() +
575                                                            " for parallel rendering");
576                                    }
577                            }
578                            else if ((waitTime > 0) || future.isDone()) {
579                                    try {
580                                            long startTime = System.currentTimeMillis();
581    
582                                            StringBundler sb = future.get(
583                                                    waitTime, TimeUnit.MILLISECONDS);
584    
585                                            long duration = System.currentTimeMillis() - startTime;
586    
587                                            waitTime -= duration;
588    
589                                            contentsMap.put(portlet.getPortletId(), sb);
590    
591                                            portletRenderer.finishParallelRender();
592    
593                                            if (_log.isDebugEnabled()) {
594                                                    _log.debug(
595                                                            "Parallely rendered portlet " +
596                                                                    portlet.getPortletId() + " in " + duration +
597                                                                            " ms");
598                                            }
599    
600                                            continue;
601                                    }
602                                    catch (ExecutionException ee) {
603                                            throw ee;
604                                    }
605                                    catch (InterruptedException ie) {
606    
607                                            // On interruption, stop waiting, force all pending portlets
608                                            // to fall back to ajax loading or an error message.
609    
610                                            waitTime = -1;
611                                    }
612                                    catch (TimeoutException te) {
613    
614                                            // On timeout, stop waiting, force all pending portlets to
615                                            // fall back to ajax loading or an error message.
616    
617                                            waitTime = -1;
618                                    }
619                                    catch (CancellationException ce) {
620    
621                                            // This should only happen on a concurrent shutdown of the
622                                            // thread pool. Simply stops the render process.
623    
624                                            if (_log.isDebugEnabled()) {
625                                                    _log.debug(
626                                                            "Asynchronized cancellation detected that should " +
627                                                                    "only be caused by a concurrent shutdown of " +
628                                                                            "the thread pool",
629                                                            ce);
630                                            }
631    
632                                            return;
633                                    }
634    
635                                    // Cancel by interrupting rendering thread
636    
637                                    future.cancel(true);
638                            }
639    
640                            StringBundler sb = null;
641    
642                            if (processor.isPortletAjaxRender() && portlet.isAjaxable()) {
643                                    if (_log.isDebugEnabled()) {
644                                            _log.debug(
645                                                    "Fall back to ajax rendering of portlet " +
646                                                            portlet.getPortletId());
647                                    }
648    
649                                    sb = portletRenderer.renderAjax(request, response);
650                            }
651                            else {
652                                    if (_log.isDebugEnabled()) {
653                                            if (processor.isPortletAjaxRender()) {
654                                                    _log.debug(
655                                                            "Fall back to an error message for portlet " +
656                                                                    portlet.getPortletId() +
657                                                                            " since it is not ajaxable");
658                                            }
659                                            else {
660                                                    _log.debug(
661                                                            "Fall back to an error message for portlet " +
662                                                                    portlet.getPortletId() +
663                                                                            " since ajax rendering is disabled");
664                                            }
665                                    }
666    
667                                    sb = portletRenderer.renderError(request, response);
668                            }
669    
670                            contentsMap.put(portlet.getPortletId(), sb);
671    
672                            portletRenderer.finishParallelRender();
673                    }
674            }
675    
676            private static Log _log = LogFactoryUtil.getLog(RuntimePageImpl.class);
677    
678            private int _waitTime = Integer.MAX_VALUE;
679    
680    }