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.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
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
315
316 template.prepare(request);
317
318
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
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
542
543
544
545
546
547
548 future = new FutureTask<>(renderCallable);
549
550
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
605
606
607 waitTime = -1;
608 }
609 catch (TimeoutException te) {
610
611
612
613
614 waitTime = -1;
615 }
616 catch (CancellationException ce) {
617
618
619
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
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 }