001
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.security.pacl.DoPrivileged;
024 import com.liferay.portal.kernel.servlet.ServletContextPool;
025 import com.liferay.portal.kernel.template.Template;
026 import com.liferay.portal.kernel.template.TemplateConstants;
027 import com.liferay.portal.kernel.template.TemplateManager;
028 import com.liferay.portal.kernel.template.TemplateManagerUtil;
029 import com.liferay.portal.kernel.template.TemplateResource;
030 import com.liferay.portal.kernel.util.ClassLoaderUtil;
031 import com.liferay.portal.kernel.util.GetterUtil;
032 import com.liferay.portal.kernel.util.JavaConstants;
033 import com.liferay.portal.kernel.util.ObjectValuePair;
034 import com.liferay.portal.kernel.util.StringBundler;
035 import com.liferay.portal.kernel.util.StringPool;
036 import com.liferay.portal.kernel.util.StringUtil;
037 import com.liferay.portal.kernel.util.Validator;
038 import com.liferay.portal.kernel.util.WebKeys;
039 import com.liferay.portal.layoutconfiguration.util.velocity.CustomizationSettingsProcessor;
040 import com.liferay.portal.layoutconfiguration.util.velocity.TemplateProcessor;
041 import com.liferay.portal.layoutconfiguration.util.xml.ActionURLLogic;
042 import com.liferay.portal.layoutconfiguration.util.xml.PortletLogic;
043 import com.liferay.portal.layoutconfiguration.util.xml.RenderURLLogic;
044 import com.liferay.portal.model.LayoutTemplate;
045 import com.liferay.portal.model.LayoutTemplateConstants;
046 import com.liferay.portal.model.Portlet;
047 import com.liferay.portal.service.LayoutTemplateLocalServiceUtil;
048 import com.liferay.portal.servlet.ThreadLocalFacadeServletRequestWrapperUtil;
049 import com.liferay.portal.util.PropsValues;
050 import com.liferay.taglib.servlet.PipingServletResponse;
051 import com.liferay.taglib.util.DummyVelocityTaglib;
052 import com.liferay.taglib.util.VelocityTaglib;
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
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
316
317 template.prepare(request);
318
319
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 TemplateManager templateManager =
346 TemplateManagerUtil.getTemplateManager(
347 TemplateConstants.LANG_TYPE_VM);
348
349 Template template = TemplateManagerUtil.getTemplate(
350 TemplateConstants.LANG_TYPE_VM, templateResource, restricted);
351
352 template.put("processor", processor);
353
354
355
356 template.prepare(request);
357
358 UnsyncStringWriter unsyncStringWriter = new UnsyncStringWriter();
359
360 templateManager.addTaglibTheme(
361 template, "taglibLiferay", request,
362 new PipingServletResponse(response, unsyncStringWriter));
363
364 try {
365 template.processTemplate(unsyncStringWriter);
366 }
367 catch (Exception e) {
368 _log.error(e, e);
369
370 throw e;
371 }
372
373 boolean portletParallelRender = GetterUtil.getBoolean(
374 request.getAttribute(WebKeys.PORTLET_PARALLEL_RENDER));
375
376 Lock lock = null;
377
378 Map<String, StringBundler> contentsMap = new HashMap<>();
379
380 Map<Integer, List<PortletRenderer>> portletRenderersMap =
381 processor.getPortletRenderers();
382
383 for (Map.Entry<Integer, List<PortletRenderer>> entry :
384 portletRenderersMap.entrySet()) {
385
386 if (_log.isDebugEnabled()) {
387 _log.debug(
388 "Processing portlets with render weight " + entry.getKey());
389 }
390
391 List<PortletRenderer> portletRenderers = entry.getValue();
392
393 StopWatch stopWatch = new StopWatch();
394
395 stopWatch.start();
396
397 if (portletParallelRender && (portletRenderers.size() > 1)) {
398 if (_log.isDebugEnabled()) {
399 _log.debug("Start parallel rendering");
400 }
401
402 if (lock == null) {
403 lock = new ReentrantLock();
404 }
405
406 request.setAttribute(
407 WebKeys.PARALLEL_RENDERING_MERGE_LOCK, lock);
408
409 ObjectValuePair<HttpServletRequest, Closeable> objectValuePair =
410 ThreadLocalFacadeServletRequestWrapperUtil.inject(request);
411
412 try {
413 parallelyRenderPortlets(
414 objectValuePair.getKey(), response, processor,
415 contentsMap, portletRenderers);
416 }
417 finally {
418 Closeable closeable = objectValuePair.getValue();
419
420 closeable.close();
421 }
422
423 request.removeAttribute(WebKeys.PARALLEL_RENDERING_MERGE_LOCK);
424
425 if (_log.isDebugEnabled()) {
426 _log.debug(
427 "Finished parallel rendering in " +
428 stopWatch.getTime() + " ms");
429 }
430 }
431 else {
432 if (_log.isDebugEnabled()) {
433 _log.debug("Start serial rendering");
434 }
435
436 for (PortletRenderer portletRenderer : portletRenderers) {
437 Portlet portlet = portletRenderer.getPortlet();
438
439 contentsMap.put(
440 portlet.getPortletId(),
441 portletRenderer.render(request, response));
442
443 if (_log.isDebugEnabled()) {
444 _log.debug(
445 "Serially rendered portlet " +
446 portlet.getPortletId() + " in " +
447 stopWatch.getTime() + " ms");
448 }
449 }
450
451 if (_log.isDebugEnabled()) {
452 _log.debug(
453 "Finished serial rendering in " + stopWatch.getTime() +
454 " ms");
455 }
456 }
457 }
458
459 if (portletParallelRender && (_waitTime == Integer.MAX_VALUE)) {
460 _waitTime = PropsValues.LAYOUT_PARALLEL_RENDER_TIMEOUT;
461 }
462
463 StringBundler sb = StringUtil.replaceWithStringBundler(
464 unsyncStringWriter.toString(), "[$TEMPLATE_PORTLET_", "$]",
465 contentsMap);
466
467 return sb;
468 }
469
470 protected LayoutTemplate getLayoutTemplate(String velocityTemplateId) {
471 String separator = LayoutTemplateConstants.CUSTOM_SEPARATOR;
472 boolean standard = false;
473
474 if (velocityTemplateId.contains(
475 LayoutTemplateConstants.STANDARD_SEPARATOR)) {
476
477 separator = LayoutTemplateConstants.STANDARD_SEPARATOR;
478 standard = true;
479 }
480
481 String layoutTemplateId = null;
482
483 String themeId = null;
484
485 int pos = velocityTemplateId.indexOf(separator);
486
487 if (pos != -1) {
488 layoutTemplateId = velocityTemplateId.substring(
489 pos + separator.length());
490
491 themeId = velocityTemplateId.substring(0, pos);
492 }
493
494 pos = layoutTemplateId.indexOf(
495 LayoutTemplateConstants.INSTANCE_SEPARATOR);
496
497 if (pos != -1) {
498 layoutTemplateId = layoutTemplateId.substring(
499 pos + LayoutTemplateConstants.INSTANCE_SEPARATOR.length() + 1);
500
501 pos = layoutTemplateId.indexOf(StringPool.UNDERLINE);
502
503 layoutTemplateId = layoutTemplateId.substring(pos + 1);
504 }
505
506 return LayoutTemplateLocalServiceUtil.getLayoutTemplate(
507 layoutTemplateId, standard, themeId);
508 }
509
510 protected void parallelyRenderPortlets(
511 HttpServletRequest request, HttpServletResponse response,
512 TemplateProcessor processor, Map<String, StringBundler> contentsMap,
513 List<PortletRenderer> portletRenderers)
514 throws Exception {
515
516 ExecutorService executorService =
517 PortalExecutorManagerUtil.getPortalExecutor(
518 RuntimePageImpl.class.getName());
519
520 Map<Future<StringBundler>, PortletRenderer> futures = new HashMap<>(
521 portletRenderers.size());
522
523 for (PortletRenderer portletRenderer : portletRenderers) {
524 if (_log.isDebugEnabled()) {
525 Portlet portlet = portletRenderer.getPortlet();
526
527 _log.debug(
528 "Submit portlet " + portlet.getPortletId() +
529 " for parallel rendering");
530 }
531
532 Callable<StringBundler> renderCallable =
533 portletRenderer.getCallable(request, response);
534
535 Future<StringBundler> future = null;
536
537 try {
538 future = executorService.submit(renderCallable);
539 }
540 catch (RejectedExecutionException ree) {
541
542
543
544
545
546
547
548
549 future = new FutureTask<>(renderCallable);
550
551
552
553 future.cancel(true);
554 }
555
556 futures.put(future, portletRenderer);
557 }
558
559 long waitTime = _waitTime;
560
561 for (Map.Entry<Future<StringBundler>, PortletRenderer> entry :
562 futures.entrySet()) {
563
564 Future<StringBundler> future = entry.getKey();
565 PortletRenderer portletRenderer = entry.getValue();
566
567 Portlet portlet = portletRenderer.getPortlet();
568
569 if (future.isCancelled()) {
570 if (_log.isDebugEnabled()) {
571 _log.debug(
572 "Reject portlet " + portlet.getPortletId() +
573 " for parallel rendering");
574 }
575 }
576 else if ((waitTime > 0) || future.isDone()) {
577 try {
578 long startTime = System.currentTimeMillis();
579
580 StringBundler sb = future.get(
581 waitTime, TimeUnit.MILLISECONDS);
582
583 long duration = System.currentTimeMillis() - startTime;
584
585 waitTime -= duration;
586
587 contentsMap.put(portlet.getPortletId(), sb);
588
589 portletRenderer.finishParallelRender();
590
591 if (_log.isDebugEnabled()) {
592 _log.debug(
593 "Parallely rendered portlet " +
594 portlet.getPortletId() + " in " + duration +
595 " ms");
596 }
597
598 continue;
599 }
600 catch (ExecutionException ee) {
601 throw ee;
602 }
603 catch (InterruptedException ie) {
604
605
606
607
608 waitTime = -1;
609 }
610 catch (TimeoutException te) {
611
612
613
614
615 waitTime = -1;
616 }
617 catch (CancellationException ce) {
618
619
620
621
622 if (_log.isDebugEnabled()) {
623 _log.debug(
624 "Asynchronized cancellation detected that should " +
625 "only be caused by a concurrent shutdown of " +
626 "the thread pool",
627 ce);
628 }
629
630 return;
631 }
632
633
634
635 future.cancel(true);
636 }
637
638 StringBundler sb = null;
639
640 if (processor.isPortletAjaxRender() && portlet.isAjaxable()) {
641 if (_log.isDebugEnabled()) {
642 _log.debug(
643 "Fall back to ajax rendering of portlet " +
644 portlet.getPortletId());
645 }
646
647 sb = portletRenderer.renderAjax(request, response);
648 }
649 else {
650 if (_log.isDebugEnabled()) {
651 if (processor.isPortletAjaxRender()) {
652 _log.debug(
653 "Fall back to an error message for portlet " +
654 portlet.getPortletId() +
655 " since it is not ajaxable");
656 }
657 else {
658 _log.debug(
659 "Fall back to an error message for portlet " +
660 portlet.getPortletId() +
661 " since ajax rendering is disabled");
662 }
663 }
664
665 sb = portletRenderer.renderError(request, response);
666 }
667
668 contentsMap.put(portlet.getPortletId(), sb);
669
670 portletRenderer.finishParallelRender();
671 }
672 }
673
674 private static final Log _log = LogFactoryUtil.getLog(
675 RuntimePageImpl.class);
676
677 private int _waitTime = Integer.MAX_VALUE;
678
679 }