/* * Copyright 2000-2013 Enonic AS * http://www.enonic.com/license */ package com.enonic.cms.core.portal.rendering; import java.util.concurrent.locks.Lock; import org.jdom.Document; import org.joda.time.DateTime; import com.enonic.cms.framework.util.GenericConcurrencyLock; import com.enonic.cms.framework.xml.XMLDocument; import com.enonic.cms.core.CacheObjectSettings; import com.enonic.cms.core.CacheSettings; import com.enonic.cms.core.CachedObject; import com.enonic.cms.core.SiteURLResolver; import com.enonic.cms.core.TightestCacheSettingsResolver; import com.enonic.cms.core.portal.PortalInstanceKey; import com.enonic.cms.core.portal.cache.PageCache; import com.enonic.cms.core.portal.datasource.DataSourceType; import com.enonic.cms.core.portal.datasource.executor.DataSourceExecutor; import com.enonic.cms.core.portal.datasource.executor.DataSourceExecutorContext; import com.enonic.cms.core.portal.datasource.executor.DataSourceExecutorFactory; import com.enonic.cms.core.portal.datasource.executor.DataSourceInvocationCache; import com.enonic.cms.core.portal.instruction.PostProcessInstructionContext; import com.enonic.cms.core.portal.instruction.PostProcessInstructionExecutor; import com.enonic.cms.core.portal.instruction.PostProcessInstructionProcessor; import com.enonic.cms.core.portal.livetrace.InstructionPostProcessingTrace; import com.enonic.cms.core.portal.livetrace.InstructionPostProcessingTracer; import com.enonic.cms.core.portal.livetrace.LivePortalTraceService; import com.enonic.cms.core.portal.livetrace.PageRenderingTrace; import com.enonic.cms.core.portal.livetrace.PageRenderingTracer; import com.enonic.cms.core.portal.livetrace.ViewTransformationTrace; import com.enonic.cms.core.portal.livetrace.ViewTransformationTracer; import com.enonic.cms.core.portal.rendering.portalfunctions.PortalFunctionsContext; import com.enonic.cms.core.portal.rendering.portalfunctions.PortalFunctionsFactory; import com.enonic.cms.core.portal.rendering.tracing.PageTraceInfo; import com.enonic.cms.core.portal.rendering.tracing.RenderTrace; import com.enonic.cms.core.portal.rendering.tracing.TraceMarkerHelper; import com.enonic.cms.core.portal.rendering.viewtransformer.PageTemplateXsltViewTransformer; import com.enonic.cms.core.portal.rendering.viewtransformer.RegionTransformationParameter; import com.enonic.cms.core.portal.rendering.viewtransformer.TemplateParameterTransformationParameter; import com.enonic.cms.core.portal.rendering.viewtransformer.TransformationParameterOrigin; import com.enonic.cms.core.portal.rendering.viewtransformer.TransformationParams; import com.enonic.cms.core.portal.rendering.viewtransformer.ViewTransformationResult; import com.enonic.cms.core.portal.ticket.TicketConstants; import com.enonic.cms.core.resource.ResourceFile; import com.enonic.cms.core.resource.ResourceKey; import com.enonic.cms.core.resource.ResourceService; import com.enonic.cms.core.security.user.UserEntity; import com.enonic.cms.core.structure.SitePropertiesService; import com.enonic.cms.core.structure.TemplateParameter; import com.enonic.cms.core.structure.menuitem.MenuItemEntity; import com.enonic.cms.core.structure.page.Region; import com.enonic.cms.core.structure.page.template.PageTemplateEntity; import com.enonic.cms.core.stylesheet.StylesheetNotFoundException; import com.enonic.cms.core.time.TimeService; /** * This class must be insantiated for each use. */ public class PageRenderer { private TimeService timeService; private DataSourceExecutorFactory dataSourceExecutorFactory; private ResourceService resourceService; private PageTemplateXsltViewTransformer pageTemplateXsltViewTransformer; private DataSourceInvocationCache invocationCache; private PageRendererContext context; private PostProcessInstructionExecutor postProcessInstructionExecutor; private PageCache pageCache; private CacheSettings resolvedMenuItemCacheSettings = null; private SiteURLResolver siteURLResolver; private SitePropertiesService sitePropertiesService; private TightestCacheSettingsResolver tightestCacheSettingsResolver; private LivePortalTraceService livePortalTraceService; private PageRenderingTrace pageRenderingTrace; private static GenericConcurrencyLock<PageCacheKey> concurrencyLock = GenericConcurrencyLock.create(); protected PageRenderer( PageRendererContext pageRendererContext, LivePortalTraceService livePortalTraceService ) { this.context = pageRendererContext; this.invocationCache = new DataSourceInvocationCache(); this.livePortalTraceService = livePortalTraceService; } public RenderedPageResult renderPage( PageTemplateEntity pageTemplate ) { pageRenderingTrace = PageRenderingTracer.startTracing( livePortalTraceService ); try { if ( pageTemplate == null ) { throw new IllegalArgumentException( "pageTemplate cannot be null" ); } RenderedPageResult renderedPageResult; try { resolveMenuItemCacheSettings( pageTemplate ); PageRenderingTracer.traceRequester( pageRenderingTrace, context.getRunAsUser() ); enterPageTrace( context.getRunAsUser(), pageTemplate, resolvedMenuItemCacheSettings ); renderedPageResult = doRenderPageAndWindows( pageTemplate ); } finally { exitPageTrace(); } return renderedPageResult; } finally { PageRenderingTracer.stopTracing( pageRenderingTrace, livePortalTraceService ); } } private RenderedPageResult doRenderPageAndWindows( final PageTemplateEntity pageTemplate ) { RenderedPageResult renderedPageResult = doRenderPageTemplate( pageTemplate ); String renderedPageContentIncludingRenderedWindows = executePostProcessInstructions( pageTemplate, renderedPageResult.getContent(), renderedPageResult.getOutputMethod() ); renderedPageContentIncludingRenderedWindows = renderedPageContentIncludingRenderedWindows.replace( TicketConstants.PLACEHOLDER, context.getTicketId() ); renderedPageResult.setContent( renderedPageContentIncludingRenderedWindows ); CacheSettings normalizedPageCacheSettings = tightestCacheSettingsResolver.resolveTightestCacheSettingsForPage( context.getMenuItem(), context.getRegionsInPage(), pageTemplate ); DateTime requestTime = context.getRequestTime(); DateTime expirationTime = requestTime.plusSeconds( normalizedPageCacheSettings.getSpecifiedSecondsToLive() ); renderedPageResult.setExpirationTime( expirationTime ); return renderedPageResult; } private RenderedPageResult doRenderPageTemplate( PageTemplateEntity pageTemplate ) { if ( !useCache() ) { // render request is not cacheable final RenderedPageResult renderedPageResult = renderPageTemplateExcludingPortlets( pageTemplate ); renderedPageResult.setRetrievedFromCache( false ); PageRenderingTracer.traceUsedCachedResult( pageRenderingTrace, false, false ); return renderedPageResult; } PageCacheKey pageCacheKey = resolvePageCacheKey(); RenderedPageResult cachedPageResult = getFromCache( pageCacheKey ); if ( cachedPageResult != null ) { return cachedPageResult; } final Lock locker = concurrencyLock.getLock( pageCacheKey ); try { PageRenderingTracer.startConcurrencyBlockTimer( pageRenderingTrace ); locker.lock(); PageRenderingTracer.stopConcurrencyBlockTimer( pageRenderingTrace ); cachedPageResult = getFromCache( pageCacheKey ); if ( cachedPageResult != null ) { return cachedPageResult; } RenderedPageResult renderedPageResultToCache = renderPageTemplateExcludingPortlets( pageTemplate ); // Ensure to mark the result as retrieved from cache, before we put it in the cache renderedPageResultToCache.setRetrievedFromCache( true ); CacheObjectSettings cacheSettings = CacheObjectSettings.createFrom( resolvedMenuItemCacheSettings ); CachedObject cachedPage = pageCache.cachePage( pageCacheKey, renderedPageResultToCache, cacheSettings ); renderedPageResultToCache.setExpirationTime( cachedPage.getExpirationTime() ); // Have to return another instance since we did not retrieve this result from cache RenderedPageResult renderedPageResultToReturn = (RenderedPageResult) renderedPageResultToCache.clone(); renderedPageResultToReturn.setRetrievedFromCache( false ); PageRenderingTracer.traceUsedCachedResult( pageRenderingTrace, true, false ); return renderedPageResultToReturn; } finally { locker.unlock(); } } private RenderedPageResult getFromCache( final PageCacheKey pageCacheKey ) { CachedObject cachedPageHolder = pageCache.getCachedPage( pageCacheKey ); if ( cachedPageHolder != null ) { // Found the page in cache, return the clone to prevent further rendering of the cached object RenderedPageResult cachedPageResult = (RenderedPageResult) cachedPageHolder.getObject(); PageRenderingTracer.traceUsedCachedResult( pageRenderingTrace, true, true ); return (RenderedPageResult) cachedPageResult.clone(); } return null; } private RenderedPageResult renderPageTemplateExcludingPortlets( final PageTemplateEntity pageTemplate ) { final XMLDocument dataSourceResult = executeDataSources( pageTemplate ); final PortalFunctionsContext portalFunctionsContext = new PortalFunctionsContext(); portalFunctionsContext.setInvocationCache( invocationCache ); portalFunctionsContext.setSitePath( context.getSitePath() ); portalFunctionsContext.setOriginalSitePath( context.getOriginalSitePath() ); portalFunctionsContext.setSite( context.getSite() ); portalFunctionsContext.setMenuItem( context.getMenuItem() ); portalFunctionsContext.setEncodeURIs( context.isEncodeURIs() ); portalFunctionsContext.setLocale( context.getLocale() ); portalFunctionsContext.setPageTemplate( pageTemplate ); portalFunctionsContext.setPortalInstanceKey( resolvePortalInstanceKey() ); portalFunctionsContext.setRenderedInline( false ); portalFunctionsContext.setEncodeImageUrlParams( RenderTrace.isTraceOff() ); portalFunctionsContext.setSiteURLResolver( resolveSiteURLResolver() ); portalFunctionsContext.setPageRendererContext( context ); final ViewTransformationResult viewTransformationResult; final ViewTransformationTrace trace = ViewTransformationTracer.startTracing( livePortalTraceService ); try { final ResourceKey stylesheetKey = pageTemplate.getStyleKey(); final ResourceFile pageTemplateStylesheet = resourceService.getResourceFile( stylesheetKey ); if ( pageTemplateStylesheet == null ) { throw new StylesheetNotFoundException( stylesheetKey ); } ViewTransformationTracer.traceView( pageTemplateStylesheet.getPath(), trace ); final Document model; model = dataSourceResult.getAsJDOMDocument(); final TransformationParams transformationParams = new TransformationParams(); for ( Region region : context.getRegionsInPage().getRegions() ) { if ( transformationParams.notContains( region.getName() ) ) { transformationParams.add( new RegionTransformationParameter( region ) ); } } for ( TemplateParameter templateParam : pageTemplate.getTemplateParameters().values() ) { if ( transformationParams.notContains( templateParam.getName() ) ) { transformationParams.add( new TemplateParameterTransformationParameter( templateParam, TransformationParameterOrigin.PAGETEMPLATE ) ); } } PortalFunctionsFactory.get().setContext( portalFunctionsContext ); viewTransformationResult = pageTemplateXsltViewTransformer.transform( pageTemplateStylesheet, model, transformationParams ); } finally { PortalFunctionsFactory.get().removeContext(); ViewTransformationTracer.stopTracing( trace, livePortalTraceService ); } if ( RenderTrace.isTraceOn() ) { viewTransformationResult.setContent( TraceMarkerHelper.writePageMarker( RenderTrace.getCurrentRenderTraceInfo(), viewTransformationResult.getContent(), viewTransformationResult.getOutputMethod() ) ); } final RenderedPageResult renderedPageResult = new RenderedPageResult(); renderedPageResult.setRenderedAt( timeService.getNowAsDateTime() ); renderedPageResult.setHttpContentType( viewTransformationResult.getHttpContentType() ); renderedPageResult.setContent( viewTransformationResult.getContent() ); renderedPageResult.setOutputMethod( viewTransformationResult.getOutputMethod() ); if ( viewTransformationResult.getOutputEncoding() != null ) { renderedPageResult.setContentEncoding( viewTransformationResult.getOutputEncoding() ); } return renderedPageResult; } private String executePostProcessInstructions( PageTemplateEntity pageTemplate, String pageMarkup, String outputMode ) { WindowRendererContext windowRenderContext = new WindowRendererContext(); windowRenderContext.setContentFromRequest( context.getContentFromRequest() ); windowRenderContext.setOverridingSitePropertyCreateUrlAsPath( context.getOverridingSitePropertyCreateUrlAsPath() ); windowRenderContext.setDeviceClass( context.getDeviceClass() ); windowRenderContext.setEncodeURIs( context.isEncodeURIs() ); windowRenderContext.setForceNoCacheUsage( context.forceNoCacheUsage() ); windowRenderContext.setHttpRequest( context.getHttpRequest() ); windowRenderContext.setInvocationCache( invocationCache ); windowRenderContext.setLanguage( context.getLanguage() ); windowRenderContext.setLocale( context.getLocale() ); windowRenderContext.setMenuItem( context.getMenuItem() ); windowRenderContext.setOriginalSitePath( context.getOriginalSitePath() ); windowRenderContext.setPageRequestType( context.getPageRequestType() ); windowRenderContext.setPageTemplate( pageTemplate ); windowRenderContext.setPreviewContext( context.getPreviewContext() ); windowRenderContext.setProfile( context.getProfile() ); windowRenderContext.setRegionsInPage( context.getRegionsInPage() ); windowRenderContext.setRenderedInline( true ); windowRenderContext.setRenderer( context.getRenderer() ); windowRenderContext.setTicketId( context.getTicketId() ); windowRenderContext.setSite( context.getSite() ); windowRenderContext.setSitePath( context.getSitePath() ); windowRenderContext.setVerticalSession( context.getVerticalSession() ); windowRenderContext.setOriginalUrl( context.getOriginalUrl() ); final InstructionPostProcessingTrace instructionPostProcessingTrace = InstructionPostProcessingTracer.startTracingForPage( livePortalTraceService ); try { PostProcessInstructionContext postProcessInstructionContext = new PostProcessInstructionContext(); postProcessInstructionContext.setSite( context.getSite() ); postProcessInstructionContext.setEncodeImageUrlParams( RenderTrace.isTraceOff() ); postProcessInstructionContext.setPreviewContext( context.getPreviewContext() ); postProcessInstructionContext.setWindowRendererContext( windowRenderContext ); postProcessInstructionContext.setHttpRequest( context.getHttpRequest() ); postProcessInstructionContext.setInContextOfWindow( false ); postProcessInstructionContext.setSiteURLResolverEnableHtmlEscaping( siteURLResolver ); postProcessInstructionContext.setSiteURLResolverEnableHtmlEscaping( createSiteURLResolver( true ) ); postProcessInstructionContext.setSiteURLResolverDisableHtmlEscaping( createSiteURLResolver( false ) ); PostProcessInstructionProcessor postProcessInstructionProcessor = new PostProcessInstructionProcessor( postProcessInstructionContext, postProcessInstructionExecutor ); return postProcessInstructionProcessor.processInstructions( pageMarkup ); } finally { InstructionPostProcessingTracer.stopTracing( instructionPostProcessingTrace, livePortalTraceService ); } } private SiteURLResolver createSiteURLResolver( boolean escapeHtmlParameterAmps ) { SiteURLResolver siteURLResolver = new SiteURLResolver(); siteURLResolver.setCharacterEncoding( this.siteURLResolver.getCharacterEncoding() ); siteURLResolver.setSitePropertiesService( sitePropertiesService ); siteURLResolver.setHtmlEscapeParameterAmps( escapeHtmlParameterAmps ); if ( context.getOverridingSitePropertyCreateUrlAsPath() != null ) { siteURLResolver.setOverridingSitePropertyCreateUrlAsPath( context.getOverridingSitePropertyCreateUrlAsPath() ); } return siteURLResolver; } private XMLDocument executeDataSources( PageTemplateEntity pageTemplate ) { // resolve css key ResourceKey cssKey = context.getSite().getDefaultCssKey(); if ( pageTemplate.getCssKey() != null ) { cssKey = pageTemplate.getCssKey(); } ResourceKey[] cssKeys = null; if ( cssKey != null ) { cssKeys = new ResourceKey[]{cssKey}; } PortalInstanceKey portalInstanceKey = resolvePortalInstanceKey(); DataSourceExecutorContext datasourceExecutorContext = new DataSourceExecutorContext(); datasourceExecutorContext.setContentFromRequest( context.getContentFromRequest() ); datasourceExecutorContext.setCssKeys( cssKeys ); datasourceExecutorContext.setInvocationCache( invocationCache ); datasourceExecutorContext.setDataSourceType( DataSourceType.PAGETEMPLATE ); datasourceExecutorContext.setDeviceClass( context.getDeviceClass() ); datasourceExecutorContext.setHttpRequest( context.getHttpRequest() ); datasourceExecutorContext.setLanguage( context.getLanguage() ); datasourceExecutorContext.setLocale( context.getLocale() ); datasourceExecutorContext.setMenuItem( context.getMenuItem() ); datasourceExecutorContext.setOriginalSitePath( context.getOriginalSitePath() ); datasourceExecutorContext.setPageRequestType( context.getPageRequestType() ); datasourceExecutorContext.setPageTemplate( pageTemplate ); datasourceExecutorContext.setPortalInstanceKey( portalInstanceKey ); datasourceExecutorContext.setPreviewContext( context.getPreviewContext() ); datasourceExecutorContext.setProfile( context.getProfile() ); datasourceExecutorContext.setRegions( context.getRegionsInPage() ); datasourceExecutorContext.setSite( context.getSite() ); datasourceExecutorContext.setSiteProperties( sitePropertiesService.getSiteProperties( context.getSite().getKey() ) ); datasourceExecutorContext.setRequestParameters( context.getSitePath().getRequestParameters() ); datasourceExecutorContext.setVerticalSession( context.getVerticalSession() ); datasourceExecutorContext.setUser( context.getRunAsUser() ); DataSourceExecutor datasourceExecutor = dataSourceExecutorFactory.createDataSourceExecutor( datasourceExecutorContext ); return datasourceExecutor.execute( pageTemplate.getDatasources() ); } private PortalInstanceKey resolvePortalInstanceKey() { PortalInstanceKey portalInstanceKey; if ( context.getMenuItem() == null ) { //rendering pagetemplate for newsletter - special case portalInstanceKey = PortalInstanceKey.createSite( context.getSite().getKey() ); } else { portalInstanceKey = PortalInstanceKey.createPage( context.getMenuItem().getKey(), context.getSite().getKey() ); } return portalInstanceKey; } private void enterPageTrace( UserEntity runAsUser, PageTemplateEntity pageTemplate, CacheSettings menuItemCacheSettings ) { MenuItemEntity menuItem = context.getMenuItem(); if ( menuItem == null ) { return; } RenderTrace.enter(); PageTraceInfo info = RenderTrace.enterPage( menuItem.getKey().toInt() ); if ( info != null ) { info.setSiteKey( context.getSite().getKey() ); info.setName( menuItem.getName() ); info.setDisplayName( menuItem.getDisplayName() ); info.setPageTemplateName( pageTemplate.getName() ); info.setCacheable( menuItemCacheSettings.isEnabled() ); info.setRunAsUser( runAsUser.getQualifiedName() ); } } private void exitPageTrace() { MenuItemEntity menuItem = context.getMenuItem(); if ( menuItem == null ) { return; } RenderTrace.exitPage(); RenderTrace.exit(); } private boolean useCache() { if ( RenderTrace.isTraceOn() ) { return false; } else if ( context.getPreviewContext().isPreviewing() ) { return false; } else if ( !pageCache.isEnabled() ) { return false; } else if ( context.forceNoCacheUsage() || context.getMenuItem() == null ) { return false; } else { return resolvedMenuItemCacheSettings.isEnabled(); } } private CacheSettings resolveMenuItemCacheSettings( PageTemplateEntity pageTemplate ) { if ( resolvedMenuItemCacheSettings == null ) { resolvedMenuItemCacheSettings = context.getMenuItem().getCacheSettings( pageCache.getDefaultTimeToLive(), pageTemplate ); } return resolvedMenuItemCacheSettings; } private PageCacheKey resolvePageCacheKey() { PageCacheKey key = new PageCacheKey(); key.setMenuItemKey( context.getMenuItem().getKey() ); key.setUserKey( context.getRunAsUser().getKey().toString() ); key.setDeviceClass( context.getDeviceClass() ); key.setLocale( context.getLocale() ); key.setQueryString( context.getOriginalUrl() ); return key; } private SiteURLResolver resolveSiteURLResolver() { if ( context.getOverridingSitePropertyCreateUrlAsPath() == null ) { return siteURLResolver; } else { SiteURLResolver siteURLResolver = new SiteURLResolver(); siteURLResolver.setCharacterEncoding( this.siteURLResolver.getCharacterEncoding() ); siteURLResolver.setOverridingSitePropertyCreateUrlAsPath( context.getOverridingSitePropertyCreateUrlAsPath() ); siteURLResolver.setSitePropertiesService( sitePropertiesService ); return siteURLResolver; } } public void setDataSourceExecutorFactory( DataSourceExecutorFactory value ) { this.dataSourceExecutorFactory = value; } public void setResourceService( ResourceService value ) { this.resourceService = value; } public void setPageTemplateXsltViewTransformer( PageTemplateXsltViewTransformer value ) { this.pageTemplateXsltViewTransformer = value; } public void setPageCache( PageCache value ) { this.pageCache = value; } public void setSitePropertiesService( final SitePropertiesService value ) { this.sitePropertiesService = value; } public void setSiteURLResolver( SiteURLResolver value ) { this.siteURLResolver = value; } public void setTightestCacheSettingsResolver( TightestCacheSettingsResolver value ) { this.tightestCacheSettingsResolver = value; } public void setTimeService( TimeService value ) { this.timeService = value; } public void setPostProcessInstructionExecutor( PostProcessInstructionExecutor postProcessInstructionExecutor ) { this.postProcessInstructionExecutor = postProcessInstructionExecutor; } }