1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.jetspeed.aggregator.impl;
18
19 import java.util.Collection;
20 import java.util.HashMap;
21 import java.util.Iterator;
22 import java.util.Map;
23
24 import javax.servlet.http.HttpServletRequest;
25 import javax.servlet.http.HttpServletResponse;
26
27 import org.apache.commons.lang.StringEscapeUtils;
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 import org.apache.jetspeed.JetspeedActions;
31 import org.apache.jetspeed.PortalReservedParameters;
32 import org.apache.jetspeed.aggregator.ContentDispatcher;
33 import org.apache.jetspeed.aggregator.ContentDispatcherCtrl;
34 import org.apache.jetspeed.aggregator.FailedToRenderFragmentException;
35 import org.apache.jetspeed.aggregator.PortletAccessDeniedException;
36 import org.apache.jetspeed.aggregator.PortletContent;
37 import org.apache.jetspeed.aggregator.PortletRenderer;
38 import org.apache.jetspeed.aggregator.PortletTrackingManager;
39 import org.apache.jetspeed.aggregator.RenderingJob;
40 import org.apache.jetspeed.aggregator.UnknownPortletDefinitionException;
41 import org.apache.jetspeed.aggregator.WorkerMonitor;
42 import org.apache.jetspeed.cache.CacheElement;
43 import org.apache.jetspeed.cache.ContentCacheKey;
44 import org.apache.jetspeed.cache.JetspeedCache;
45 import org.apache.jetspeed.components.portletentity.PortletEntityNotStoredException;
46 import org.apache.jetspeed.container.window.FailedToRetrievePortletWindow;
47 import org.apache.jetspeed.container.window.PortletWindowAccessor;
48 import org.apache.jetspeed.om.common.LocalizedField;
49 import org.apache.jetspeed.om.common.portlet.MutablePortletEntity;
50 import org.apache.jetspeed.om.common.portlet.PortletDefinitionComposite;
51 import org.apache.jetspeed.om.page.ContentFragment;
52 import org.apache.jetspeed.request.RequestContext;
53 import org.apache.jetspeed.security.SecurityAccessController;
54 import org.apache.jetspeed.services.title.DynamicTitleService;
55 import org.apache.jetspeed.statistics.PortalStatistics;
56 import org.apache.pluto.PortletContainer;
57 import org.apache.pluto.om.entity.PortletEntity;
58 import org.apache.pluto.om.window.PortletWindow;
59
60 /***
61 * <h4>PortletRendererService <br />
62 * Jetspeed-2 Rendering service.</h4>
63 * <p>
64 * This service process all portlet rendering requests and interfaces with the
65 * portlet container to generate the resulting markup
66 * </p>
67 *
68 * @author <a href="mailto:raphael@apache.org">Rapha?l Luta </a>
69 * @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
70 * @author <a>Woonsan Ko</a>
71 * @version $Id: PortletRendererImpl.java,v 1.30 2005/05/20 14:54:22 ate Exp $
72 */
73 public class PortletRendererImpl implements PortletRenderer
74 {
75 protected final static Log log = LogFactory.getLog(PortletRendererImpl.class);
76
77 protected WorkerMonitor workMonitor;
78 protected PortletContainer container;
79 protected PortletWindowAccessor windowAccessor;
80 protected PortalStatistics statistics;
81 protected DynamicTitleService addTitleService;
82
83 protected PortletTrackingManager portletTracking;
84
85 /***
86 * flag indicating whether to check jetspeed-portlet.xml security constraints
87 * before rendering a portlet. If security check fails, do not display portlet content
88 */
89 protected boolean checkSecurityConstraints;
90 /***
91 * For security constraint checks
92 */
93 protected SecurityAccessController accessController;
94
95 /***
96 * JSR 168 Portlet Content Cache
97 */
98 protected JetspeedCache portletContentCache;
99
100 /***
101 * OutOfService Cache
102 */
103 protected boolean overrideTitles = false;
104 public static final String OUT_OF_SERVICE_MESSAGE = "Portlet is not responding and has been taken out of service.";
105
106 public PortletRendererImpl(PortletContainer container,
107 PortletWindowAccessor windowAccessor,
108 WorkerMonitor workMonitor,
109 PortalStatistics statistics,
110 DynamicTitleService addTitleService,
111 PortletTrackingManager portletTracking,
112 boolean checkSecurityConstraints,
113 SecurityAccessController accessController,
114 JetspeedCache portletContentCache,
115 boolean overrideTitles)
116 {
117 this.container = container;
118 this.windowAccessor = windowAccessor;
119 this.workMonitor = workMonitor;
120 this.statistics = statistics;
121 this.addTitleService = addTitleService;
122 this.portletTracking = portletTracking;
123 this.checkSecurityConstraints = checkSecurityConstraints;
124 this.accessController = accessController;
125 this.portletContentCache = portletContentCache;
126 this.overrideTitles = overrideTitles;
127 }
128
129 public PortletRendererImpl(PortletContainer container,
130 PortletWindowAccessor windowAccessor,
131 WorkerMonitor workMonitor,
132 PortalStatistics statistics,
133 DynamicTitleService addTitleService,
134 PortletTrackingManager portletTracking,
135 boolean checkSecurityConstraints,
136 SecurityAccessController accessController,
137 JetspeedCache portletContentCache)
138 {
139 this(container, windowAccessor, workMonitor, statistics,
140 addTitleService, portletTracking, checkSecurityConstraints,
141 accessController, portletContentCache, false);
142 }
143
144 public PortletRendererImpl(PortletContainer container,
145 PortletWindowAccessor windowAccessor,
146 WorkerMonitor workMonitor,
147 PortalStatistics statistics,
148 DynamicTitleService addTitleService)
149 {
150 this(container, windowAccessor, workMonitor, statistics, null, null, false, null, null, true);
151 }
152
153 public PortletRendererImpl(PortletContainer container,
154 PortletWindowAccessor windowAccessor,
155 WorkerMonitor workMonitor,
156 PortalStatistics statistics)
157 {
158 this( container, windowAccessor, workMonitor, statistics, null );
159 }
160
161 public PortletRendererImpl(PortletContainer container,
162 PortletWindowAccessor windowAccessor,
163 WorkerMonitor workMonitor)
164 {
165 this( container, windowAccessor, workMonitor, null );
166 }
167
168 public void start()
169 {
170
171 }
172
173 public void stop()
174 {
175
176 }
177
178 /***
179 * Render the specified Page fragment. Result is returned in the
180 * PortletResponse.
181 *
182 * @throws FailedToRenderFragmentException
183 * @throws FailedToRetrievePortletWindow
184 * @throws UnknownPortletDefinitionException
185 */
186 public void renderNow( ContentFragment fragment, RequestContext requestContext )
187 {
188 HttpServletRequest servletRequest =null;
189 HttpServletResponse servletResponse = null;
190 ContentDispatcherCtrl dispatcher = null;
191 boolean contentIsCached = false;
192 try
193 {
194 PortletWindow portletWindow = getPortletWindow(fragment);
195 PortletDefinitionComposite portletDefinition =
196 (PortletDefinitionComposite) portletWindow.getPortletEntity().getPortletDefinition();
197 if (checkSecurityConstraints && !checkSecurityConstraint(portletDefinition, fragment))
198 {
199 throw new PortletAccessDeniedException("Access Denied.");
200 }
201 if (portletTracking.isOutOfService(portletWindow))
202 {
203 log.info("Taking portlet out of service: " + portletDefinition.getUniqueName() + " for window " + fragment.getId());
204 fragment.overrideRenderedContent(OUT_OF_SERVICE_MESSAGE);
205 return;
206 }
207 long timeoutMetadata = this.getTimeoutOnJob(portletDefinition);
208 portletTracking.setExpiration(portletWindow, timeoutMetadata);
209 int expirationCache = getExpirationCache(portletDefinition);
210 if (expirationCache != 0)
211 {
212 if (retrieveCachedContent(requestContext, fragment, portletWindow, expirationCache, portletDefinition))
213 return;
214 contentIsCached = true;
215 }
216 if (dispatcher == null)
217 {
218 dispatcher = createDispatcher(requestContext, fragment, expirationCache);
219 }
220 servletRequest = requestContext.getRequestForWindow(portletWindow);
221 servletResponse = dispatcher.getResponseForWindow(portletWindow, requestContext);
222 RenderingJob rJob =
223 buildRenderingJob(portletWindow, fragment, servletRequest, servletResponse,
224 requestContext, false, portletDefinition, dispatcher, null,
225 expirationCache, contentIsCached, timeoutMetadata);
226 rJob.execute();
227 addTitleToHeader( portletWindow, fragment, servletRequest, servletResponse, dispatcher, contentIsCached);
228 }
229 catch (PortletAccessDeniedException e)
230 {
231 fragment.overrideRenderedContent(e.getLocalizedMessage());
232 }
233 catch (Exception e)
234 {
235 fragment.overrideRenderedContent(e.getLocalizedMessage());
236 log.error(e.toString(), e);
237 }
238 }
239
240 /***
241 * Render the specified Page fragment. Result is returned in the
242 * PortletResponse.
243 *
244 * @throws FailedToRenderFragmentException
245 * @throws FailedToRetrievePortletWindow
246 * @throws UnknownPortletDefinitionException
247 * @throws PortletAccessDeniedException
248 */
249 public void renderNow( ContentFragment fragment, HttpServletRequest request, HttpServletResponse response )
250 {
251 RequestContext requestContext = (RequestContext) request
252 .getAttribute(PortalReservedParameters.REQUEST_CONTEXT_ATTRIBUTE);
253 renderNow(fragment, requestContext);
254 }
255
256 protected int getExpirationCache(PortletDefinitionComposite portletDefinition)
257 {
258 if (portletDefinition == null)
259 return 0;
260 String expiration = portletDefinition.getExpirationCache();
261 if (expiration == null)
262 return 0;
263 return Integer.parseInt(expiration);
264 }
265
266 /***
267 * Render the specified Page fragment. The method returns before rendering
268 * is complete, rendered content can be accessed through the Content Dispatcher
269 *
270 * @return the asynchronous portlet rendering job to synchronize
271 */
272 public RenderingJob render( ContentFragment fragment, RequestContext requestContext )
273 {
274 RenderingJob job = null;
275
276 try
277 {
278 job = createRenderingJob(fragment, requestContext);
279 }
280 catch (Exception e)
281 {
282 log.error("render() failed: " + e.toString(), e);
283 fragment.overrideRenderedContent(e.getLocalizedMessage());
284 }
285
286 if (job != null)
287 {
288 processRenderingJob(job, true);
289 }
290
291 return job;
292 }
293
294 /***
295 *
296 * Create a rendering job for the specified Page fragment.
297 * The method returns a rendering job which should be passed to 'processRenderingJob(RenderingJob job)' method.
298 * @return portlet rendering job to pass to render(RenderingJob job) method
299 * @throws FailedToRetrievePortletWindow
300 * @throws UnknownPortletDefinitionException
301 * @throws PortletAccessDeniedException
302 */
303 public RenderingJob createRenderingJob(ContentFragment fragment, RequestContext requestContext)
304 {
305 RenderingJob job = null;
306 boolean contentIsCached = false;
307 try
308 {
309 PortletWindow portletWindow = getPortletWindow(fragment);
310 PortletDefinitionComposite portletDefinition =
311 (PortletDefinitionComposite) portletWindow.getPortletEntity().getPortletDefinition();
312
313 long timeoutMetadata = this.getTimeoutOnJob(portletDefinition);
314 portletTracking.setExpiration(portletWindow, timeoutMetadata);
315
316 if (checkSecurityConstraints && !checkSecurityConstraint(portletDefinition, fragment))
317 {
318 throw new PortletAccessDeniedException("Access Denied.");
319 }
320 if (portletTracking.isOutOfService(portletWindow))
321 {
322 fragment.overrideRenderedContent(OUT_OF_SERVICE_MESSAGE);
323 return null;
324 }
325 int expirationCache = getExpirationCache(portletDefinition);
326 if (expirationCache != 0)
327 {
328 portletTracking.setExpiration(portletWindow, expirationCache);
329 contentIsCached = retrieveCachedContent(requestContext, fragment, portletWindow,
330 expirationCache, portletDefinition);
331 if (contentIsCached)
332 {
333 return null;
334 }
335 }
336 job = buildRenderingJob( portletWindow, fragment, requestContext, true,
337 portletDefinition, null, contentIsCached, timeoutMetadata );
338 }
339 catch (Exception e)
340 {
341 throw new RuntimeException("Failed to create rendering job", e);
342 }
343
344 return job;
345 }
346
347 /***
348 *
349 * Render the specified rendering job.
350 * The method returns before rendering is complete when the job is processed in parallel mode.
351 * When it is not parallel mode, it returns after rendering is complete.
352 * @throws FailedToRenderFragmentException
353 */
354 public void processRenderingJob(RenderingJob job)
355 {
356 processRenderingJob(job, false);
357 }
358
359 protected void processRenderingJob(RenderingJob job, boolean parallelOnly)
360 {
361 ContentFragment fragment = null;
362
363 try
364 {
365 if (parallelOnly || job.getTimeout() > 0)
366 {
367 workMonitor.process(job);
368 }
369 else
370 {
371 job.execute();
372 addTitleToHeader(job.getWindow(), job.getFragment(),
373 job.getRequest(), job.getResponse(), job.getDispatcher(),
374 job.isContentCached());
375 }
376 }
377 catch (Exception e1)
378 {
379 log.error("render() failed: " + e1.toString(), e1);
380 fragment.overrideRenderedContent(e1.getLocalizedMessage());
381 }
382 }
383
384 /***
385 * Retrieve cached content, if content retrieved successfully return true, if no content found return false
386 * @param requestContext
387 * @param fragment
388 * @param portletWindow
389 * @return true when content found, otherwise false
390 */
391 protected boolean retrieveCachedContent(RequestContext requestContext, ContentFragment fragment,
392 PortletWindow portletWindow, int expiration,
393 PortletDefinitionComposite portletDefinition)
394 throws Exception
395 {
396 ContentCacheKey cacheKey = portletContentCache.createCacheKey(requestContext, fragment.getId());
397 CacheElement cachedElement = portletContentCache.get(cacheKey);
398 if (cachedElement != null)
399 {
400 PortletContent portletContent = (PortletContent)cachedElement.getContent();
401 fragment.setPortletContent(portletContent);
402 ContentDispatcherCtrl dispatcher = new ContentDispatcherImpl(portletContent);
403 HttpServletRequest servletRequest = requestContext.getRequestForWindow(portletWindow);
404
405 this.addTitleService.setDynamicTitle(portletWindow, servletRequest, dispatcher.getPortletContent(fragment).getTitle());
406 return true;
407 }
408 return false;
409 }
410
411 public ContentDispatcherCtrl createDispatcher(RequestContext request, ContentFragment fragment, int expirationCache)
412 {
413 ContentCacheKey cacheKey = portletContentCache.createCacheKey(request, fragment.getId());
414 PortletContent content = new PortletContentImpl(this, cacheKey, expirationCache);
415 ContentDispatcherCtrl dispatcher = new ContentDispatcherImpl(content);
416 return dispatcher;
417 }
418
419 /***
420 * Retrieve the ContentDispatcher for the specified request
421 */
422 public ContentDispatcher getDispatcher( RequestContext request, boolean isParallel )
423 {
424 return request.getContentDispatcher();
425 }
426
427
428 protected PortletWindow getPortletWindow( ContentFragment fragment ) throws FailedToRetrievePortletWindow, PortletEntityNotStoredException
429 {
430
431 PortletWindow portletWindow = windowAccessor.getPortletWindow(fragment);
432
433 if (portletWindow == null)
434 {
435 throw new FailedToRetrievePortletWindow("Portlet Window creation failed for fragment: "
436 + fragment.getId() + ", " + fragment.getName());
437 }
438
439 PortletEntity portletEntity = portletWindow.getPortletEntity();
440 ((MutablePortletEntity)portletEntity).setFragment(fragment);
441
442 return portletWindow;
443 }
444
445 protected RenderingJob buildRenderingJob( PortletWindow portletWindow, ContentFragment fragment,
446 RequestContext requestContext, boolean isParallel,
447 PortletDefinitionComposite portletDefinition,
448 PortletContent portletContent, boolean contentIsCached, long timeoutMetadata)
449 throws PortletAccessDeniedException, FailedToRetrievePortletWindow, PortletEntityNotStoredException
450 {
451 int expirationCache = getExpirationCache(portletDefinition);
452 ContentDispatcherCtrl dispatcher = createDispatcher(requestContext, fragment, expirationCache);
453 HttpServletRequest request = requestContext.getRequestForWindow(portletWindow);
454 HttpServletResponse response = dispatcher.getResponseForWindow(portletWindow, requestContext);
455
456 return buildRenderingJob( portletWindow, fragment, request, response,
457 requestContext, isParallel,
458 portletDefinition, dispatcher,
459 portletContent, expirationCache, contentIsCached, timeoutMetadata );
460 }
461
462 protected RenderingJob buildRenderingJob( PortletWindow portletWindow, ContentFragment fragment,
463 HttpServletRequest request, HttpServletResponse response,
464 RequestContext requestContext, boolean isParallel,
465 PortletDefinitionComposite portletDefinition,
466 ContentDispatcherCtrl dispatcher,
467 PortletContent portletContent,
468 int expirationCache, boolean contentIsCached, long timeoutMetadata)
469 throws PortletAccessDeniedException, FailedToRetrievePortletWindow, PortletEntityNotStoredException
470 {
471 RenderingJob rJob = null;
472
473 request.setAttribute(PortalReservedParameters.PAGE_ATTRIBUTE, requestContext.getPage());
474 request.setAttribute(PortalReservedParameters.FRAGMENT_ATTRIBUTE, fragment);
475 request.setAttribute(PortalReservedParameters.CONTENT_DISPATCHER_ATTRIBUTE, dispatcher);
476 request.setAttribute(PortalReservedParameters.REQUEST_CONTEXT_ATTRIBUTE, requestContext);
477 request.setAttribute(PortalReservedParameters.REQUEST_CONTEXT_OBJECTS, requestContext.getObjects());
478 request.setAttribute(PortalReservedParameters.PATH_ATTRIBUTE, requestContext.getAttribute(PortalReservedParameters.PATH_ATTRIBUTE));
479 request.setAttribute(PortalReservedParameters.PORTLET_WINDOW_ATTRIBUTE, portletWindow);
480
481 if (portletContent == null)
482 {
483 portletContent = dispatcher.getPortletContent(fragment);
484 fragment.setPortletContent(portletContent);
485 }
486
487 if (isParallel)
488 {
489 Map workerAttrs = new HashMap();
490 workerAttrs.put(PortalReservedParameters.PAGE_ATTRIBUTE, requestContext.getPage());
491 workerAttrs.put(PortalReservedParameters.FRAGMENT_ATTRIBUTE, fragment);
492 workerAttrs.put(PortalReservedParameters.CONTENT_DISPATCHER_ATTRIBUTE, dispatcher);
493 workerAttrs.put(PortalReservedParameters.REQUEST_CONTEXT_ATTRIBUTE, requestContext);
494 workerAttrs.put(PortalReservedParameters.REQUEST_CONTEXT_OBJECTS, requestContext.getObjects());
495 workerAttrs.put(PortalReservedParameters.PATH_ATTRIBUTE, requestContext.getAttribute(PortalReservedParameters.PATH_ATTRIBUTE));
496 workerAttrs.put(PortalReservedParameters.PORTLET_WINDOW_ATTRIBUTE, portletWindow);
497
498
499
500 workerAttrs.put(PortalReservedParameters.PORTLET_DEFINITION_ATTRIBUTE, portletDefinition);
501
502 rJob = new RenderingJobImpl(container, this, portletDefinition, portletContent, fragment, dispatcher,
503 request, response, requestContext, portletWindow,
504 statistics, expirationCache, contentIsCached, workerAttrs);
505
506 }
507 else
508 {
509 rJob = new RenderingJobImpl(container, this, portletDefinition, portletContent, fragment, dispatcher,
510 request, response, requestContext, portletWindow,
511 statistics, expirationCache, contentIsCached );
512
513 }
514 setTimeoutOnJob(timeoutMetadata, rJob);
515 return rJob;
516 }
517
518 protected long getTimeoutOnJob(PortletDefinitionComposite portletDefinition)
519 {
520 long timeoutMetadata = 0;
521 Collection timeoutFields = null;
522
523 if (portletDefinition != null)
524 {
525 timeoutFields = portletDefinition.getMetadata().getFields(PortalReservedParameters.PORTLET_EXTENDED_DESCRIPTOR_RENDER_TIMEOUT);
526 }
527
528 if (timeoutFields != null)
529 {
530 Iterator it = timeoutFields.iterator();
531
532 if (it.hasNext())
533 {
534 LocalizedField timeoutField = (LocalizedField) timeoutFields.iterator().next();
535
536 try
537 {
538 timeoutMetadata = Long.parseLong(timeoutField.getValue());
539 }
540 catch (NumberFormatException nfe)
541 {
542 log.warn("Invalid timeout metadata: " + nfe.getMessage());
543 }
544 }
545 }
546 return timeoutMetadata;
547 }
548
549 protected void setTimeoutOnJob(long timeoutMetadata, RenderingJob rJob)
550 {
551
552 if (timeoutMetadata > 0)
553 {
554 rJob.setTimeout(timeoutMetadata);
555 }
556 else if (this.portletTracking.getDefaultPortletTimeout() > 0)
557 {
558 rJob.setTimeout(this.portletTracking.getDefaultPortletTimeout());
559 }
560 }
561
562 public void addTitleToHeader( PortletWindow portletWindow, ContentFragment fragment,
563 HttpServletRequest request, HttpServletResponse response,
564 ContentDispatcherCtrl dispatcher, boolean isCacheTitle )
565 {
566 if (overrideTitles)
567 {
568 try
569 {
570 String title = fragment.getTitle();
571
572 if ( title == null )
573 {
574 title = addTitleService.getDynamicTitle( portletWindow, request );
575 }
576
577 response.setHeader( "JS_PORTLET_TITLE", StringEscapeUtils.escapeHtml( title ) );
578 dispatcher.getPortletContent(fragment).setTitle(title);
579 }
580 catch (Exception e)
581 {
582 log.error("Unable to reteive portlet title: " + e.getMessage(), e);
583 }
584 }
585 else
586 {
587 String title = null;
588
589 if (isCacheTitle)
590 {
591 title = fragment.getTitle();
592
593 if ( title == null )
594 {
595 title = addTitleService.getDynamicTitle(portletWindow, request);
596 }
597
598 dispatcher.getPortletContent(fragment).setTitle(title);
599 }
600
601 if (title == null)
602 {
603 title = addTitleService.getDynamicTitle(portletWindow, request);
604 dispatcher.getPortletContent(fragment).setTitle(title);
605 }
606 }
607 }
608
609 protected boolean checkSecurityConstraint(PortletDefinitionComposite portlet, ContentFragment fragment)
610 {
611 if (fragment.getType().equals(ContentFragment.PORTLET))
612 {
613 if (accessController != null)
614 {
615 return accessController.checkPortletAccess(portlet, JetspeedActions.MASK_VIEW);
616 }
617 }
618 return true;
619 }
620
621 protected void addToCache(PortletContent content)
622 {
623 CacheElement cachedElement = portletContentCache.createElement(content.getCacheKey(), content);
624 if (content.getExpiration() == -1)
625 {
626 cachedElement.setTimeToIdleSeconds(portletContentCache.getTimeToIdleSeconds());
627 cachedElement.setTimeToLiveSeconds(portletContentCache.getTimeToLiveSeconds());
628 }
629 else
630 {
631 cachedElement.setTimeToIdleSeconds(content.getExpiration());
632 cachedElement.setTimeToLiveSeconds(content.getExpiration());
633 }
634 portletContentCache.put(cachedElement);
635 }
636
637 public void notifyContentComplete(PortletContent content)
638 {
639 if (content.getExpiration() != 0)
640 addToCache(content);
641 }
642
643 public PortletTrackingManager getPortletTrackingManager()
644 {
645 return this.portletTracking;
646 }
647 }