/* * 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.slf4j.Logger; import org.slf4j.LoggerFactory; import com.enonic.vertical.engine.handlers.MenuHandler; import com.enonic.cms.framework.util.GenericConcurrencyLock; import com.enonic.cms.framework.util.MimeTypeResolver; import com.enonic.cms.framework.xml.XMLDocument; import com.enonic.cms.framework.xml.XMLDocumentFactory; import com.enonic.cms.core.CacheObjectSettings; import com.enonic.cms.core.CacheSettings; import com.enonic.cms.core.CachedObject; import com.enonic.cms.core.RequestParameters; import com.enonic.cms.core.SiteURLResolver; import com.enonic.cms.core.portal.PortalInstanceKey; import com.enonic.cms.core.portal.PortalRenderingException; import com.enonic.cms.core.portal.WindowNotFoundException; import com.enonic.cms.core.portal.WindowReference; 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.xml.DataSourcesElement; 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.ViewTransformationTrace; import com.enonic.cms.core.portal.livetrace.ViewTransformationTracer; import com.enonic.cms.core.portal.livetrace.WindowRenderingTrace; import com.enonic.cms.core.portal.livetrace.WindowRenderingTracer; import com.enonic.cms.core.portal.page.PageRequestFactory; 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.PagePortletTraceInfo; 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.PortletXsltViewTransformer; import com.enonic.cms.core.portal.rendering.viewtransformer.StringTransformationParameter; 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.ResourceService; import com.enonic.cms.core.security.user.UserEntity; import com.enonic.cms.core.security.user.UserKey; import com.enonic.cms.core.structure.SitePropertiesService; import com.enonic.cms.core.structure.TemplateParameter; import com.enonic.cms.core.structure.TemplateParameterType; import com.enonic.cms.core.structure.menuitem.MenuItemEntity; import com.enonic.cms.core.structure.page.Window; import com.enonic.cms.core.structure.page.WindowKey; import com.enonic.cms.core.stylesheet.StylesheetNotFoundException; /** * Apr 17, 2009 */ public class WindowRenderer { private static final Logger LOG = LoggerFactory.getLogger( WindowRenderer.class ); private static final String DUMMY_XML = "<?xml version=\"1.0\" encoding=\"utf-8\"?><dummy/>"; private PageCache pageCache; private DataSourceExecutorFactory dataSourceExecutorFactory; private WindowRendererContext context; private PortletXsltViewTransformer portletXsltViewTransformer; private ResourceService resourceService; private SitePropertiesService sitePropertiesService; private SiteURLResolver siteURLResolver; private RequestParameters requestParameters; private MimeTypeResolver mimeTypeResolver; private PostProcessInstructionExecutor postProcessInstructionExecutor; private LivePortalTraceService liveTraceService; private MenuHandler menuHandler; private static GenericConcurrencyLock<WindowCacheKey> concurrencyLock = GenericConcurrencyLock.create(); /** * The window rendering trace for this window rendering. */ private WindowRenderingTrace windowRenderingTrace; public WindowRenderer( WindowRendererContext windowRendererContext, MenuHandler menuHandler ) { this.context = windowRendererContext; this.menuHandler = menuHandler; if ( windowRendererContext.getInvocationCache() == null ) { throw new IllegalArgumentException( "Datasource invocation cache not set" ); } } public RenderedWindowResult renderWindowInline( final WindowKey windowKey, final RequestParameters extraParams ) { windowRenderingTrace = WindowRenderingTracer.startTracing( windowKey, liveTraceService ); try { if ( !context.isRenderedInline() ) { throw new IllegalStateException( "Context is indicating that a render direct is expected, but render window inline was called. " + "(WindowKey = " + ( windowKey != null ? windowKey.asString() : "null" ) + ")" ); } final Window window = context.getRegionsInPage().getWindowByKey( windowKey ); if ( window == null ) { throw new WindowNotFoundException( context.getSite().getKey(), context.getSitePath().getLocalPath(), windowKey ); } WindowRenderingTracer.traceRequestedWindow( windowRenderingTrace, window ); requestParameters = new RequestParameters( context.getSitePath().getRequestParameters() ); for ( RequestParameters.Param param : extraParams.getParameters() ) { requestParameters.setParam( param ); } return doRenderWindow( window ); } finally { WindowRenderingTracer.stopTracing( windowRenderingTrace, liveTraceService ); } } public RenderedWindowResult renderWindowDirect( final WindowKey windowKey ) { windowRenderingTrace = WindowRenderingTracer.startTracing( windowKey, liveTraceService ); try { if ( context.isRenderedInline() ) { throw new IllegalStateException( "context is saying render inline, but render window direct was called" ); } final Window window = context.getRegionsInPage().getWindowByKey( windowKey ); if ( window == null ) { throw new WindowNotFoundException( context.getSite().getKey(), context.getSitePath().getLocalPath(), windowKey ); } WindowRenderingTracer.traceRequestedWindow( windowRenderingTrace, window ); requestParameters = context.getSitePath().getRequestParameters(); RenderedWindowResult result = doRenderWindow( window ); result.setContent( result.getContent().replace( TicketConstants.PLACEHOLDER, context.getTicketId() ) ); final WindowReference windowReference = context.getSitePath().getWindowReference(); if ( windowReference.hasExtension() ) { final String mimeType = mimeTypeResolver.getMimeTypeByExtension( windowReference.getExtension() ); result.setHttpContentType( mimeType ); } return result; } finally { WindowRenderingTracer.stopTracing( windowRenderingTrace, liveTraceService ); } } private RenderedWindowResult doRenderWindow( final Window window ) { if ( window == null ) { throw new IllegalArgumentException( "Given window cannot be null" ); } final UserEntity executor = resolveRunAsUser( window ); WindowRenderingTracer.traceRenderer( windowRenderingTrace, executor ); final CacheSettings portletCacheSettings = window.getPortlet().getCacheSettings( pageCache.getDefaultTimeToLive() ); enterTrace( window, executor, portletCacheSettings ); try { final boolean useCache = resolveUseCache( portletCacheSettings ); if ( !useCache ) { final RenderedWindowResult windowResult = doExecuteDatasourcesAndTransformView( window, executor ); WindowRenderingTracer.traceUsedCachedResult( windowRenderingTrace, false, false ); return cloneAndExecutePostProcessInstructions( windowResult ); } final WindowCacheKey cacheKey = resolveCacheKey( window, executor.getKey() ); final RenderedWindowResult windowResult; final Lock locker = concurrencyLock.getLock( cacheKey ); try { WindowRenderingTracer.startConcurrencyBlockTimer( windowRenderingTrace ); locker.lock(); WindowRenderingTracer.stopConcurrencyBlockTimer( windowRenderingTrace ); // see if window result is in cache final CachedObject cachedPortletHolder = pageCache.getCachedPortletWindow( cacheKey ); if ( cachedPortletHolder != null ) { windowResult = (RenderedWindowResult) cachedPortletHolder.getObject(); WindowRenderingTracer.traceUsedCachedResult( windowRenderingTrace, true, true ); } else { // window not in cache, need to render... windowResult = doExecuteDatasourcesAndTransformView( window, executor ); WindowRenderingTracer.traceUsedCachedResult( windowRenderingTrace, true, false ); // register the rendered window in the cache if ( windowResult.isErrorFree() ) { final CachedObject newCachedPortletHolder = pageCache.cachePortletWindow( cacheKey, windowResult, CacheObjectSettings.createFrom( portletCacheSettings ) ); windowResult.setExpirationTimeInCache( newCachedPortletHolder.getExpirationTime() ); } } } finally { locker.unlock(); } return cloneAndExecutePostProcessInstructions( windowResult ); } finally { exitTrace(); } } private RenderedWindowResult cloneAndExecutePostProcessInstructions( final RenderedWindowResult evaluatedPortlet ) { RenderedWindowResult clonedRenderedWindowResult = evaluatedPortlet.clone(); String content = clonedRenderedWindowResult.getContent(); String resolvedContent = executePostProcessInstructions( content ); clonedRenderedWindowResult.setContent( resolvedContent ); return clonedRenderedWindowResult; } private String executePostProcessInstructions( String pageMarkup ) { final InstructionPostProcessingTrace instructionPostProcessingTrace = InstructionPostProcessingTracer.startTracingForWindow( liveTraceService ); try { PostProcessInstructionContext postProcessInstructionContext = new PostProcessInstructionContext(); postProcessInstructionContext.setSite( context.getSite() ); postProcessInstructionContext.setEncodeImageUrlParams( RenderTrace.isTraceOff() ); postProcessInstructionContext.setHttpRequest( context.getHttpRequest() ); postProcessInstructionContext.setPreviewContext( context.getPreviewContext() ); postProcessInstructionContext.setInContextOfWindow( true ); postProcessInstructionContext.setSiteURLResolverEnableHtmlEscaping( createSiteURLResolver( true ) ); postProcessInstructionContext.setSiteURLResolverDisableHtmlEscaping( createSiteURLResolver( false ) ); PostProcessInstructionProcessor postProcessInstructionProcessor = new PostProcessInstructionProcessor( postProcessInstructionContext, postProcessInstructionExecutor ); return postProcessInstructionProcessor.processInstructions( pageMarkup ); } finally { InstructionPostProcessingTracer.stopTracing( instructionPostProcessingTrace, liveTraceService ); } } 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 RenderedWindowResult doExecuteDatasourcesAndTransformView( final Window window, final UserEntity exectuor ) { RenderedWindowResult portletResult; try { PageRequestFactory.getPageRequest().setCurrentPortletKey( window.getPortlet().getPortletKey() ); XMLDocument dataSourceResult = getDataSourceResult( window, exectuor ); ViewTransformationResult portletViewTransformation; final ViewTransformationTrace trace = ViewTransformationTracer.startTracing( liveTraceService ); try { portletViewTransformation = renderWindowView( window, dataSourceResult, trace ); if ( window.getPortlet().getBorderKey() != null ) { portletViewTransformation = renderWindowBorderView( window, portletViewTransformation.getContent() ); } } finally { PortalFunctionsFactory.get().removeContext(); ViewTransformationTracer.stopTracing( trace, liveTraceService ); } portletResult = new RenderedWindowResult(); portletResult.setHttpContentType( portletViewTransformation.getHttpContentType() ); portletResult.setContent( portletViewTransformation.getContent() ); portletResult.setOutputMethod( portletViewTransformation.getOutputMethod() ); if ( portletViewTransformation.getOutputEncoding() != null ) { portletResult.setContentEncoding( portletViewTransformation.getOutputEncoding() ); } } catch ( Exception e ) { String message = "Error occured rendering window \"" + window.getPortlet().getName() + "\" (key " + window.getPortlet().getKey() + ") while handling request to site path: " + context.getSitePath().asString(); PortletErrorMessageMarkupCreator portletErrorMessageMarkupCreator = new PortletErrorMessageMarkupCreator(); String errorMarkup = portletErrorMessageMarkupCreator.createMarkup( message, e ); portletResult = new ErrorRenderPortletResult(); portletResult.setHttpContentType( "text/html" ); portletResult.setContent( errorMarkup ); LOG.error( message, e ); } finally { PageRequestFactory.getPageRequest().setCurrentPortletKey( null ); } PagePortletTraceInfo portletTraceInfo = RenderTrace.getCurrentPageObjectTraceInfo(); if ( portletTraceInfo != null ) { TraceMarkerHelper.wrapResultWithPortletMarker( portletResult, portletTraceInfo ); } portletResult.stripXHTMLNamespaces(); return portletResult; } private ViewTransformationResult renderWindowView( final Window window, final XMLDocument xml, final ViewTransformationTrace trace ) { if ( window.getPortlet().getXmlDataAsJDOMDocument().getRootElement().getChild( "datasources" ) == null ) { throw new PortalRenderingException( "Datasources missing for portlet: " + window.getKey() ); } final SiteURLResolver siteURLResolver = new SiteURLResolver(); siteURLResolver.setCharacterEncoding( this.siteURLResolver.getCharacterEncoding() ); siteURLResolver.setOverridingSitePropertyCreateUrlAsPath( context.getOverridingSitePropertyCreateUrlAsPath() ); siteURLResolver.setSitePropertiesService( sitePropertiesService ); final PortalFunctionsContext portalFunctionsContext = new PortalFunctionsContext(); portalFunctionsContext.setInvocationCache( context.getInvocationCache() ); 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( context.getPageTemplate() ); portalFunctionsContext.setPortalInstanceKey( resolvePortalInstanceKey( window ) ); portalFunctionsContext.setRenderedInline( context.isRenderedInline() ); portalFunctionsContext.setEncodeImageUrlParams( RenderTrace.isTraceOff() ); portalFunctionsContext.setSiteURLResolver( siteURLResolver ); try { PortalFunctionsFactory.get().setContext( portalFunctionsContext ); final ResourceFile viewFile = resourceService.getResourceFile( window.getPortlet().getStyleKey() ); if ( viewFile == null ) { throw new StylesheetNotFoundException( window.getPortlet().getStyleKey() ); } ViewTransformationTracer.traceView( viewFile.getPath(), trace ); final TransformationParams transformationParams = new TransformationParams(); for ( TemplateParameter templateParameter : window.getPortlet().getTemplateParameters().values() ) { transformationParams.add( new TemplateParameterTransformationParameter( templateParameter, TransformationParameterOrigin.PORTLET ) ); } return portletXsltViewTransformer.transform( viewFile, transformationParams, xml ); } finally { PortalFunctionsFactory.get().removeContext(); } } private ViewTransformationResult renderWindowBorderView( Window window, String contentToBorder ) { TransformationParams viewParameters = new TransformationParams(); for ( TemplateParameter templateParam : window.getPortlet().getBorderTemplateParameters().values() ) { if ( TemplateParameterType.CONTENT.equals( templateParam.getType() ) ) { viewParameters.add( new StringTransformationParameter( templateParam.getName(), contentToBorder, TransformationParameterOrigin.BORDER ) ); } else { viewParameters.add( new StringTransformationParameter( templateParam.getName(), templateParam.getValue(), TransformationParameterOrigin.BORDER ) ); } } PortalInstanceKey portalInstanceKey = resolvePortalInstanceKey( window ); PortalFunctionsContext portalFunctionsContext = new PortalFunctionsContext(); portalFunctionsContext.setEncodeURIs( context.isEncodeURIs() ); portalFunctionsContext.setInvocationCache( context.getInvocationCache() ); portalFunctionsContext.setLocale( context.getLocale() ); portalFunctionsContext.setMenuItem( context.getMenuItem() ); portalFunctionsContext.setSitePath( context.getSitePath() ); portalFunctionsContext.setOriginalSitePath( context.getOriginalSitePath() ); portalFunctionsContext.setPageTemplate( context.getPageTemplate() ); portalFunctionsContext.setPortalInstanceKey( portalInstanceKey ); portalFunctionsContext.setRenderedInline( context.isRenderedInline() ); portalFunctionsContext.setEncodeImageUrlParams( RenderTrace.isTraceOff() ); portalFunctionsContext.setSite( context.getSite() ); portalFunctionsContext.setSiteURLResolver( resolveSiteURLResolver() ); PortalFunctionsFactory.get().setContext( portalFunctionsContext ); ResourceFile viewFile = resourceService.getResourceFile( window.getPortlet().getBorderKey() ); if ( viewFile == null ) { throw new StylesheetNotFoundException( window.getPortlet().getBorderKey() ); } try { return portletXsltViewTransformer.transform( viewFile, viewParameters, XMLDocumentFactory.create( DUMMY_XML ) ); } finally { PortalFunctionsFactory.get().removeContext(); } } private XMLDocument getDataSourceResult( Window window, UserEntity executor ) { DataSourcesElement datasources = window.getPortlet().getDatasources(); PortalInstanceKey portalInstanceKey = resolvePortalInstanceKey( window ); DataSourceExecutorContext datasourceExecutorContext = new DataSourceExecutorContext(); datasourceExecutorContext.setContentFromRequest( context.getContentFromRequest() ); datasourceExecutorContext.setPortletDocument( window.getPortlet().getGetDataDocmentChildElementDocumentAsRootElementInItsOwnDocument() ); datasourceExecutorContext.setInvocationCache( context.getInvocationCache() ); datasourceExecutorContext.setDataSourceType( DataSourceType.PORTLET ); 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( null ); datasourceExecutorContext.setPortalInstanceKey( portalInstanceKey ); datasourceExecutorContext.setPortletWindowRenderedInline( context.isRenderedInline() ); datasourceExecutorContext.setPreviewContext( context.getPreviewContext() ); datasourceExecutorContext.setProfile( context.getProfile() ); datasourceExecutorContext.setRequestParameters( this.requestParameters ); datasourceExecutorContext.setSite( context.getSite() ); datasourceExecutorContext.setUser( executor ); datasourceExecutorContext.setVerticalSession( context.getVerticalSession() ); datasourceExecutorContext.setWindow( window ); DataSourceExecutor dataSourceExecutor = dataSourceExecutorFactory.createDataSourceExecutor( datasourceExecutorContext ); return dataSourceExecutor.execute( datasources ); } private PortalInstanceKey resolvePortalInstanceKey( Window window ) { PortalInstanceKey portalInstanceKey; if ( context.getMenuItem() == null ) { //rendering pagetemplate for newsletter - special case portalInstanceKey = PortalInstanceKey.createSite( context.getSite().getKey() ); } else { portalInstanceKey = PortalInstanceKey.createWindow( window.getKey(), context.getSite().getKey() ); } return portalInstanceKey; } private void enterTrace( Window window, UserEntity executor, CacheSettings portletCacheSettings ) { PagePortletTraceInfo info = RenderTrace.enterPageObject( window.getKey().getPortletKey() ); if ( info != null ) { info.setSiteKey( context.getSite().getKey() ); info.setName( window.getPortlet().getName() ); info.setCacheable( portletCacheSettings.isEnabled() ); info.setRunAsUser( executor.getQualifiedName() ); } } private void exitTrace() { RenderTrace.exitPageObject(); } private UserEntity resolveRunAsUser( Window window ) { UserEntity current = context.getRenderer(); MenuItemEntity menuItem = context.getMenuItem(); UserEntity resolvedRunAsUser = PortletRunAsUserResolver.resolveRunAsUser( window.getPortlet(), current, menuItem, menuHandler ); if ( resolvedRunAsUser == null || resolvedRunAsUser.isDeleted() ) { resolvedRunAsUser = current; } return resolvedRunAsUser; } private WindowCacheKey resolveCacheKey( Window window, UserKey executorKey ) { WindowCacheKey key = new WindowCacheKey(); key.setMenuItemKey( context.getMenuItem().getKey() ); key.setUserKey( executorKey.toString() ); key.setPortletKey( window.getKey().getPortletKey().toInt() ); key.setDeviceClass( context.getDeviceClass() ); key.setLocale( context.getLocale() ); key.setParamsString( this.requestParameters.getAsString( false ) ); key.setQueryString( context.getOriginalUrl() ); return key; } private boolean resolveUseCache( CacheSettings portletCacheSettings ) { if ( RenderTrace.isTraceOn() ) { return false; } else if ( context.getPreviewContext().isPreviewing() ) { return false; } else if ( context.forceNoCacheUsage() ) { return false; } else if ( !pageCache.isEnabled() ) { return false; } else { return portletCacheSettings.isEnabled(); } } 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 setPortletXsltViewTransformer( PortletXsltViewTransformer value ) { this.portletXsltViewTransformer = value; } public void setPageCache( PageCache value ) { this.pageCache = value; } public void setResourceService( ResourceService resourceService ) { this.resourceService = resourceService; } public void setSiteURLResolver( SiteURLResolver siteURLResolver ) { this.siteURLResolver = siteURLResolver; } public void setMimeTypeResolver( final MimeTypeResolver mimeTypeResolver ) { this.mimeTypeResolver = mimeTypeResolver; } public void setSitePropertiesService( final SitePropertiesService sitePropertiesService ) { this.sitePropertiesService = sitePropertiesService; } public void setPostProcessInstructionExecutor( PostProcessInstructionExecutor postProcessInstructionExecutor ) { this.postProcessInstructionExecutor = postProcessInstructionExecutor; } public void setLiveTraceService( LivePortalTraceService liveTraceService ) { this.liveTraceService = liveTraceService; } }