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