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.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
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 Template template = TemplateManagerUtil.getTemplate(
346 TemplateConstants.LANG_TYPE_VM, templateResource, restricted);
347
348 template.put("processor", processor);
349
350
351
352 template.prepare(request);
353
354
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
545
546
547
548
549
550
551 future = new FutureTask<StringBundler>(renderCallable);
552
553
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
608
609
610 waitTime = -1;
611 }
612 catch (TimeoutException te) {
613
614
615
616
617 waitTime = -1;
618 }
619 catch (CancellationException ce) {
620
621
622
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
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 }