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 void processCustomizationSettings(
088 PageContext pageContext, TemplateResource templateResource)
089 throws Exception {
090
091 doDispatch(pageContext, null, templateResource, false);
092 }
093
094 public void processTemplate(
095 PageContext pageContext, String portletId,
096 TemplateResource templateResource)
097 throws Exception {
098
099 doDispatch(pageContext, portletId, templateResource, true);
100 }
101
102 public void processTemplate(
103 PageContext pageContext, TemplateResource templateResource)
104 throws Exception {
105
106 processTemplate(pageContext, null, templateResource);
107 }
108
109 public String processXML(
110 HttpServletRequest request, HttpServletResponse response,
111 String content)
112 throws Exception {
113
114 PortletResponse portletResponse = (PortletResponse)request.getAttribute(
115 JavaConstants.JAVAX_PORTLET_RESPONSE);
116
117 if (!(portletResponse instanceof RenderResponse)) {
118 throw new IllegalArgumentException(
119 "processXML can only be invoked in the render phase");
120 }
121
122 RenderResponse renderResponse = (RenderResponse)portletResponse;
123
124 RuntimeLogic portletLogic = new PortletLogic(request, response);
125 RuntimeLogic actionURLLogic = new ActionURLLogic(renderResponse);
126 RuntimeLogic renderURLLogic = new RenderURLLogic(renderResponse);
127
128 content = processXML(request, content, portletLogic);
129 content = processXML(request, content, actionURLLogic);
130 content = processXML(request, content, renderURLLogic);
131
132 return content;
133 }
134
135 public String processXML(
136 HttpServletRequest request, String content,
137 RuntimeLogic runtimeLogic)
138 throws Exception {
139
140 if (Validator.isNull(content)) {
141 return StringPool.BLANK;
142 }
143
144 int index = content.indexOf(runtimeLogic.getOpenTag());
145
146 if (index == -1) {
147 return content;
148 }
149
150 Portlet renderPortlet = (Portlet)request.getAttribute(
151 WebKeys.RENDER_PORTLET);
152
153 Boolean renderPortletResource = (Boolean)request.getAttribute(
154 WebKeys.RENDER_PORTLET_RESOURCE);
155
156 String outerPortletId = (String)request.getAttribute(
157 WebKeys.OUTER_PORTLET_ID);
158
159 if (outerPortletId == null) {
160 request.setAttribute(
161 WebKeys.OUTER_PORTLET_ID, renderPortlet.getPortletId());
162 }
163
164 try {
165 request.setAttribute(WebKeys.RENDER_PORTLET_RESOURCE, Boolean.TRUE);
166
167 StringBundler sb = new StringBundler();
168
169 int x = 0;
170 int y = index;
171
172 while (y != -1) {
173 sb.append(content.substring(x, y));
174
175 int close1 = content.indexOf(runtimeLogic.getClose1Tag(), y);
176 int close2 = content.indexOf(runtimeLogic.getClose2Tag(), y);
177
178 if ((close2 == -1) || ((close1 != -1) && (close1 < close2))) {
179 x = close1 + runtimeLogic.getClose1Tag().length();
180 }
181 else {
182 x = close2 + runtimeLogic.getClose2Tag().length();
183 }
184
185 String runtimePortletTag = content.substring(y, x);
186
187 if ((renderPortlet != null) &&
188 runtimePortletTag.contains(renderPortlet.getPortletId())) {
189
190 return StringPool.BLANK;
191 }
192
193 sb.append(runtimeLogic.processXML(runtimePortletTag));
194
195 y = content.indexOf(runtimeLogic.getOpenTag(), x);
196 }
197
198 if (y == -1) {
199 sb.append(content.substring(x));
200 }
201
202 return sb.toString();
203 }
204 finally {
205 if (outerPortletId == null) {
206 request.removeAttribute(WebKeys.OUTER_PORTLET_ID);
207 }
208
209 request.setAttribute(WebKeys.RENDER_PORTLET, renderPortlet);
210
211 if (renderPortletResource == null) {
212 request.removeAttribute(WebKeys.RENDER_PORTLET_RESOURCE);
213 }
214 else {
215 request.setAttribute(
216 WebKeys.RENDER_PORTLET_RESOURCE, renderPortletResource);
217 }
218 }
219 }
220
221 protected void doDispatch(
222 PageContext pageContext, String portletId,
223 TemplateResource templateResource, boolean processTemplate)
224 throws Exception {
225
226 LayoutTemplate layoutTemplate = getLayoutTemplate(
227 templateResource.getTemplateId());
228
229 String pluginServletContextName = GetterUtil.getString(
230 layoutTemplate.getServletContextName());
231
232 ServletContext pluginServletContext = ServletContextPool.get(
233 pluginServletContextName);
234
235 ClassLoader pluginClassLoader = null;
236
237 if (pluginServletContext != null) {
238 pluginClassLoader =
239 (ClassLoader)pluginServletContext.getAttribute(
240 PluginContextListener.PLUGIN_CLASS_LOADER);
241 }
242
243 ClassLoader contextClassLoader =
244 PACLClassLoaderUtil.getContextClassLoader();
245
246 try {
247 TemplateContextType templateContextType =
248 TemplateContextType.STANDARD;
249
250 if ((pluginClassLoader != null) &&
251 (pluginClassLoader != contextClassLoader)) {
252
253 PACLClassLoaderUtil.setContextClassLoader(pluginClassLoader);
254
255 templateContextType = TemplateContextType.CLASS_LOADER;
256 }
257
258 if (processTemplate) {
259 doProcessTemplate(
260 pageContext, portletId, templateResource,
261 templateContextType);
262 }
263 else {
264 doProcessCustomizationSettings(
265 pageContext, templateResource, templateContextType);
266 }
267 }
268 finally {
269 if ((pluginClassLoader != null) &&
270 (pluginClassLoader != contextClassLoader)) {
271
272 PACLClassLoaderUtil.setContextClassLoader(contextClassLoader);
273 }
274 }
275 }
276
277 protected void doProcessCustomizationSettings(
278 PageContext pageContext, TemplateResource templateResource,
279 TemplateContextType templateContextType)
280 throws Exception {
281
282 HttpServletRequest request =
283 (HttpServletRequest)pageContext.getRequest();
284 HttpServletResponse response =
285 (HttpServletResponse)pageContext.getResponse();
286
287 CustomizationSettingsProcessor processor =
288 new CustomizationSettingsProcessor(pageContext);
289
290 Template template = TemplateManagerUtil.getTemplate(
291 TemplateManager.VELOCITY, templateResource,
292 TemplateContextType.STANDARD);
293
294 template.put("processor", processor);
295
296
297
298 template.prepare(request);
299
300
301
302 VelocityTaglib velocityTaglib = new VelocityTaglib(
303 pageContext.getServletContext(), request, response, pageContext,
304 template);
305
306 template.put("taglibLiferay", velocityTaglib);
307 template.put("theme", velocityTaglib);
308
309 try {
310 template.processTemplate(pageContext.getOut());
311 }
312 catch (Exception e) {
313 _log.error(e, e);
314
315 throw e;
316 }
317 }
318
319 protected void doProcessTemplate(
320 PageContext pageContext, String portletId,
321 TemplateResource templateResource,
322 TemplateContextType templateContextType)
323 throws Exception {
324
325 HttpServletRequest request =
326 (HttpServletRequest)pageContext.getRequest();
327 HttpServletResponse response =
328 (HttpServletResponse)pageContext.getResponse();
329
330 TemplateProcessor processor = new TemplateProcessor(
331 request, response, portletId);
332
333 Template template = TemplateManagerUtil.getTemplate(
334 TemplateManager.VELOCITY, templateResource, templateContextType);
335
336 template.put("processor", processor);
337
338
339
340 template.prepare(request);
341
342
343
344 UnsyncStringWriter unsyncStringWriter = new UnsyncStringWriter();
345
346 VelocityTaglib velocityTaglib = new VelocityTaglib(
347 pageContext.getServletContext(), request,
348 new PipingServletResponse(response, unsyncStringWriter),
349 pageContext, template);
350
351 template.put("taglibLiferay", velocityTaglib);
352 template.put("theme", velocityTaglib);
353
354 try {
355 template.processTemplate(unsyncStringWriter);
356 }
357 catch (Exception e) {
358 _log.error(e, e);
359
360 throw e;
361 }
362
363 boolean portletParallelRender = GetterUtil.getBoolean(
364 request.getAttribute(WebKeys.PORTLET_PARALLEL_RENDER));
365
366 Lock lock = null;
367
368 Map<String, StringBundler> contentsMap =
369 new HashMap<String, StringBundler>();
370
371 Map<Integer, List<PortletRenderer>> portletRenderersMap =
372 processor.getPortletRenderers();
373
374 for (Map.Entry<Integer, List<PortletRenderer>> entry :
375 portletRenderersMap.entrySet()) {
376
377 if (_log.isDebugEnabled()) {
378 _log.debug(
379 "Processing portlets with render weight " + entry.getKey());
380 }
381
382 List<PortletRenderer> portletRenderers = entry.getValue();
383
384 if (portletParallelRender && (portletRenderers.size() > 1)) {
385 StopWatch stopWatch = null;
386
387 if (_log.isDebugEnabled()) {
388 _log.debug("Start parallel rendering");
389
390 stopWatch = new StopWatch();
391
392 stopWatch.start();
393 }
394
395 if (lock == null) {
396 lock = new ReentrantLock();
397 }
398
399 request.setAttribute(
400 WebKeys.PARALLEL_RENDERING_MERGE_LOCK, lock);
401
402 ObjectValuePair<HttpServletRequest, Closeable> objectValuePair =
403 ThreadLocalFacadeServletRequestWrapperUtil.inject(request);
404
405 try {
406 parallelyRenderPortlets(
407 objectValuePair.getKey(), response, processor,
408 contentsMap, portletRenderers);
409 }
410 finally {
411 Closeable closeable = objectValuePair.getValue();
412
413 closeable.close();
414 }
415
416 request.removeAttribute(WebKeys.PARALLEL_RENDERING_MERGE_LOCK);
417
418 if (_log.isDebugEnabled()) {
419 _log.debug(
420 "Finished parallel rendering in " +
421 stopWatch.getTime() + " ms");
422 }
423 }
424 else {
425 StopWatch stopWatch = null;
426
427 if (_log.isDebugEnabled()) {
428 _log.debug("Start serial rendering");
429
430 stopWatch = new StopWatch();
431
432 stopWatch.start();
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 sb.writeTo(pageContext.getOut());
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(PortletConstants.INSTANCE_SEPARATOR);
494
495 if (pos != -1) {
496 layoutTemplateId = layoutTemplateId.substring(
497 pos + PortletConstants.INSTANCE_SEPARATOR.length() + 1);
498
499 pos = layoutTemplateId.indexOf(StringPool.UNDERLINE);
500
501 layoutTemplateId = layoutTemplateId.substring(pos + 1);
502 }
503
504 return LayoutTemplateLocalServiceUtil.getLayoutTemplate(
505 layoutTemplateId, standard, themeId);
506 }
507
508 protected void parallelyRenderPortlets(
509 HttpServletRequest request, HttpServletResponse response,
510 TemplateProcessor processor, Map<String, StringBundler> contentsMap,
511 List<PortletRenderer> portletRenderers)
512 throws Exception {
513
514 ExecutorService executorService =
515 PortalExecutorManagerUtil.getPortalExecutor(
516 RuntimePageImpl.class.getName());
517
518 Map<Future<StringBundler>, PortletRenderer> futures =
519 new HashMap<Future<StringBundler>, PortletRenderer>(
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<StringBundler>(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
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 Log _log = LogFactoryUtil.getLog(RuntimePageImpl.class);
675
676 private int _waitTime = Integer.MAX_VALUE;
677
678 }