/*
* Copyright 2000-2013 Enonic AS
* http://www.enonic.com/license
*/
package com.enonic.cms.core.portal.instruction;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.enonic.cms.framework.util.UrlPathEncoder;
import com.enonic.cms.core.Path;
import com.enonic.cms.core.RequestParameters;
import com.enonic.cms.core.content.ContentEntity;
import com.enonic.cms.core.content.ContentKey;
import com.enonic.cms.core.content.binary.AttachmentNativeLinkKey;
import com.enonic.cms.core.content.binary.AttachmentNativeLinkKeyParser;
import com.enonic.cms.core.content.binary.AttachmentNativeLinkKeyWithBinaryKey;
import com.enonic.cms.core.content.binary.AttachmentNativeLinkKeyWithLabel;
import com.enonic.cms.core.content.binary.BinaryDataEntity;
import com.enonic.cms.core.image.ImageRequest;
import com.enonic.cms.core.image.ImageRequestParams;
import com.enonic.cms.core.image.ImageRequestParser;
import com.enonic.cms.core.link.NativeLinkKey;
import com.enonic.cms.core.portal.PathToContentResolver;
import com.enonic.cms.core.portal.ReservedLocalPaths;
import com.enonic.cms.core.portal.image.ImageService;
import com.enonic.cms.core.portal.rendering.RenderedWindowResult;
import com.enonic.cms.core.portal.rendering.WindowRenderer;
import com.enonic.cms.core.portal.rendering.WindowRendererFactory;
import com.enonic.cms.core.portal.rendering.portalfunctions.PortalFunctionException;
import com.enonic.cms.core.resource.FileResource;
import com.enonic.cms.core.resource.FileResourceName;
import com.enonic.cms.core.resource.FileResourceService;
import com.enonic.cms.core.structure.SiteEntity;
import com.enonic.cms.core.structure.SitePath;
import com.enonic.cms.core.structure.menuitem.MenuItemEntity;
import com.enonic.cms.core.structure.menuitem.MenuItemKey;
import com.enonic.cms.core.structure.page.WindowKey;
import com.enonic.cms.store.dao.ContentDao;
import com.enonic.cms.store.dao.MenuItemDao;
import com.enonic.cms.store.dao.SectionContentDao;
@Component
public class PostProcessInstructionExecutorImpl
implements PostProcessInstructionExecutor
{
private static final Logger LOG = LoggerFactory.getLogger( PostProcessInstructionExecutorImpl.class );
public static final String UNDEFINED = "[undefined]";
private static final String DEFAULT_FILE_FORMAT = "png";
private static final String TIMESTAMP_PARAM_NAME = "_ts";
private FileResourceService fileResourceService;
private final ImageRequestParser requestParser = new ImageRequestParser();
private ContentDao contentDao;
private MenuItemDao menuItemDao;
private ImageService imagesService;
private WindowRendererFactory windowRendererFactory;
private SectionContentDao sectionContentDao;
public String execute( PostProcessInstruction instruction, PostProcessInstructionContext context )
{
PostProcessInstructionType type = instruction.getType();
if ( type == PostProcessInstructionType.CREATE_WINDOWPLACEHOLDER && context.isInContextOfWindow() )
{
throw new IllegalArgumentException( "Not allowed to render portlet inside portlet" );
}
try
{
switch ( type )
{
case CREATE_WINDOWPLACEHOLDER:
return executeRenderWindowInstruction( (RenderWindowInstruction) instruction, context );
case CREATE_RESOURCEURL:
return executeCreateResourceUrlInstruction( (CreateResourceUrlInstruction) instruction, context );
case CREATE_ATTACHMENTURL:
return executeCreateAttachmentUrlInstruction( (CreateAttachmentUrlInstruction) instruction, context );
case CREATE_IMAGEURL:
return executeCreateImageUrlInstruction( (CreateImageUrlInstruction) instruction, context );
case CREATE_CONTENTURL:
return executeCreateContentUrlInstruction( (CreateContentUrlInstruction) instruction, context );
}
}
catch ( Exception e )
{
return handleException( type.name(), e, context );
}
return null;
}
private String executeCreateContentUrlInstruction( CreateContentUrlInstruction instruction, PostProcessInstructionContext context )
{
final ContentKey contentKey = new ContentKey( instruction.getContentKey() );
Path localPath;
if ( instruction.isCreateAsPermalink() )
{
localPath = resolveContentPermalink( contentKey, context );
}
else
{
localPath = resolveContentUrlLocalPath( contentKey, context );
}
SitePath sitePath = new SitePath( context.getSite().getKey(), localPath );
addParamsToSitePath( instruction.getParams(), sitePath );
String result = doCreateUrl( instruction.doDisableOutputEscaping(), context, sitePath );
return returnResult( instruction, result );
}
private String returnResult( PostProcessInstruction instruction, String result )
{
if ( instruction.doUrlEncodeResult() )
{
result = UrlPathEncoder.encode( result );
}
return result;
}
private Path resolveContentUrlLocalPath( ContentKey contentKey, PostProcessInstructionContext context )
{
ContentEntity content = resolveContent( contentKey, context );
if ( content == null || content.isDeleted() )
{
return new Path( PathToContentResolver.CONTENT_PATH_SEPARATOR + contentKey );
}
PathToContentResolver pathToContentResolver = new PathToContentResolver( sectionContentDao );
return pathToContentResolver.resolveContentUrlLocalPath( content, context.getSite().getKey() );
}
private Path resolveContentPermalink( ContentKey contentKey, PostProcessInstructionContext context )
{
ContentEntity content = resolveContent( contentKey, context );
if ( content == null || content.isDeleted() )
{
return new Path( PathToContentResolver.CONTENT_PATH_SEPARATOR + contentKey );
}
PathToContentResolver pathToContentResolver = new PathToContentResolver( sectionContentDao );
return pathToContentResolver.resolveContentPermalink( content );
}
private String executeCreateResourceUrlInstruction( CreateResourceUrlInstruction instruction, PostProcessInstructionContext context )
{
String resolvedPath = instruction.getResolvedPath();
String[] params = instruction.getParams();
parseParamsForPostProcessInstructions( params, context );
SitePath sitePath = new SitePath( context.getSite().getKey(), new Path( resolvedPath ) );
addParamsToSitePath( params, sitePath );
addTimestampParameterForResource( resolvedPath, sitePath );
String result = doCreateUrl( instruction.doDisableOutputEscaping(), context, sitePath );
return returnResult( instruction, result );
}
private String executeCreateAttachmentUrlInstruction( CreateAttachmentUrlInstruction instruction,
PostProcessInstructionContext context )
{
String[] params = instruction.getParams();
String nativeLinkKey = instruction.getNativeLinkKey();
Path nativeLinkAsPath = new Path( nativeLinkKey );
parseParamsForPostProcessInstructions( params, context );
AttachmentNativeLinkKey nativeKey = AttachmentNativeLinkKeyParser.parse( nativeLinkAsPath );
ContentEntity content = resolveContent( nativeKey.getContentKey(), context );
BinaryDataEntity binaryData = findBinaryData( nativeKey, content );
String menuItemPath = getMenuItemPath( instruction.getRequestedMenuItemKey() );
String resolvedPath = menuItemPath + ReservedLocalPaths.PATH_ATTACHMENT + "/" + nativeKey.asUrlRepresentation();
SitePath sitePath = new SitePath( context.getSite().getKey(), new Path( resolvedPath ) );
addParamsToSitePath( params, sitePath );
addTimestampParamForBinary( binaryData, sitePath );
String result = doCreateUrl( instruction.doDisableOutputEscaping(), context, sitePath );
return returnResult( instruction, result );
}
private String getMenuItemPath( String menuItemKey )
{
String menuItemPath = "";
if ( StringUtils.isNotEmpty( menuItemKey ) )
{
MenuItemEntity menuItem = menuItemDao.findByKey( new MenuItemKey( menuItemKey ) );
if ( menuItem != null )
{
menuItemPath = menuItem.getPathAsString() + "/";
}
}
return menuItemPath;
}
private String executeCreateImageUrlInstruction( CreateImageUrlInstruction instruction, PostProcessInstructionContext context )
{
String key = instruction.getKey();
String format = instruction.getFormat();
String filter = instruction.getFilter();
String background = instruction.getBackground();
String quality = instruction.getQuality();
String name = ensureFormatExtension( key, format );
String menuItemPath = getMenuItemPath( instruction.getRequestedMenuItemKey() );
String resolvedPath = menuItemPath + ReservedLocalPaths.PATH_IMAGE + "/" + name;
final SitePath sitePath = new SitePath( context.getSite().getKey(), resolvedPath );
final ImageRequestParams params = new ImageRequestParams();
params.setFilter( filter );
params.setBackgroundColor( background );
params.setQuality( quality );
sitePath.addParams( params.getParams( context.doEncodeImageUrlParams() ) );
ImageRequest imageRequest = requestParser.parse( sitePath, context.doEncodeImageUrlParams() );
Long imageTimestamp = imagesService.getImageTimestamp( imageRequest );
if ( imageTimestamp != null )
{
addTimeStampParameter( imageTimestamp, sitePath );
}
String result = doCreateUrl( instruction.doDisableOutputEscaping(), context, sitePath );
return returnResult( instruction, result );
}
private String executeRenderWindowInstruction( RenderWindowInstruction instruction, PostProcessInstructionContext context )
{
WindowKey portletWindowKey = new WindowKey( instruction.getPortletWindowKey() );
String[] params = instruction.getParams();
parseParamsForPostProcessInstructions( params, context );
HashMap<String, String> map = createParamsMap( params );
WindowRenderer windowRenderer = windowRendererFactory.createPortletRenderer( context.getWindowRendererContext() );
RequestParameters portletParams = new RequestParameters();
for ( Map.Entry<String, String> entry : map.entrySet() )
{
portletParams.addParameterValue( entry.getKey(), entry.getValue() );
}
RenderedWindowResult renderedWindowResult = windowRenderer.renderWindowInline( portletWindowKey, portletParams );
return renderedWindowResult.getContent();
}
private void parseParamsForPostProcessInstructions( String[] params, PostProcessInstructionContext context )
{
for ( int i = 0; i < params.length / 2; i++ )
{
String value = params[i * 2 + 1];
if ( StringUtils.isNotBlank( value ) )
{
params[i * 2 + 1] = executePostProcessInstructionForParam( value, context );
}
}
}
private String executePostProcessInstructionForParam( String paramValue, PostProcessInstructionContext context )
{
PostProcessInstructionProcessor processor = new PostProcessInstructionProcessor( context, this );
return processor.processInstructions( paramValue );
}
private void addParamsToSitePath( String[] params, SitePath sitePath )
{
if ( params != null )
{
for ( int i = 0; i < params.length / 2; i++ )
{
String name = params[i * 2];
String value = params[i * 2 + 1];
sitePath.addParam( UrlPathEncoder.encode( name ), UrlPathEncoder.encode( value == null ? "" : value ) );
}
}
}
private void addTimestampParameterForResource( String resourcePath, SitePath sitePath )
{
FileResourceName fileResourceName = new FileResourceName( resourcePath );
FileResource fileResource = fileResourceService.getResource( fileResourceName );
if ( fileResource != null && !fileResource.isFolder() )
{
Long timeStamp = fileResource.getLastModified().getMillis();
addTimeStampParameter( timeStamp, sitePath );
}
}
private void addTimestampParamForBinary( BinaryDataEntity binaryData, SitePath sitePath )
{
if ( binaryData != null )
{
addTimeStampParameter( binaryData.getCreatedAt().getTime(), sitePath );
}
}
private void addTimeStampParameter( Long timeStamp, SitePath sitePath )
{
sitePath.addParam( UrlPathEncoder.encode( TIMESTAMP_PARAM_NAME ), UrlPathEncoder.encode( Long.toHexString( timeStamp ) ) );
}
private BinaryDataEntity findBinaryData( NativeLinkKey nativeKey, ContentEntity content )
{
BinaryDataEntity binaryData;
if ( nativeKey instanceof AttachmentNativeLinkKeyWithBinaryKey )
{
AttachmentNativeLinkKeyWithBinaryKey binaryNativeLinkKey = (AttachmentNativeLinkKeyWithBinaryKey) nativeKey;
binaryData = content.getMainVersion().getBinaryData( binaryNativeLinkKey.getBinaryKey() );
}
else if ( nativeKey instanceof AttachmentNativeLinkKeyWithLabel )
{
String label = ( (AttachmentNativeLinkKeyWithLabel) nativeKey ).getLabel();
binaryData = content.getMainVersion().getBinaryData( label );
}
else
{
binaryData = content.getMainVersion().getBinaryData( "source" );
if ( binaryData == null )
{
binaryData = content.getMainVersion().getOneAndOnlyBinaryData();
}
}
return binaryData;
}
private String ensureFormatExtension( String key, String format )
{
String name = key;
if ( !StringUtils.isBlank( format ) )
{
name = name + "." + format;
}
else
{
name = name + "." + DEFAULT_FILE_FORMAT;
}
return name;
}
private HashMap<String, String> createParamsMap( String[] params )
{
HashMap<String, String> map = new HashMap<String, String>();
if ( ( params != null ) && ( params.length > 0 ) )
{
for ( int i = 0; i < ( params.length / 2 ); i++ )
{
map.put( params[i * 2], params[i * 2 + 1] );
}
}
return map;
}
private String doCreateUrl( boolean disableHtmlEscaping, PostProcessInstructionContext context, SitePath sitePath )
{
String createdUrl;
if ( disableHtmlEscaping )
{
createdUrl = context.getSiteURLResolverDisableHtmlEscaping().createUrl( context.getHttpRequest(), sitePath, true );
}
else
{
createdUrl = context.getSiteURLResolverEnabledHtmlEscaping().createUrl( context.getHttpRequest(), sitePath, true );
}
return createdUrl;
}
private static String handleException( final String functioname, final Exception e, PostProcessInstructionContext context )
{
final String failureReason = resolveFailureReason( e );
final String failureMessage = buildFailureMessage( functioname, failureReason, context );
LOG.warn( failureMessage );
return UNDEFINED + ": " + failureReason;
}
private static String resolveFailureReason( final Exception e )
{
final String failureReason;
if ( e instanceof PortalFunctionException )
{
PortalFunctionException pfe = (PortalFunctionException) e;
failureReason = pfe.getFailureReason();
}
else
{
failureReason = e.getMessage();
}
return failureReason;
}
private static String buildFailureMessage( String functionName, String failureReason, PostProcessInstructionContext context )
{
StringBuffer message = new StringBuffer();
message.append( "Failure calling function " ).append( functionName );
SiteEntity site = context.getSite();
if ( site != null )
{
message.append( " in site [" );
message.append( site.getName() ).append( "]" );
}
if ( context.getWindowRendererContext() != null )
{
if ( context.getWindowRendererContext().getMenuItem() != null )
{
MenuItemEntity menuItem = context.getWindowRendererContext().getMenuItem();
message.append( " in menu item [" ).append( menuItem.getKey().toString() ).append( ": " ).append(
menuItem.getDisplayName() ).append( "]" );
}
if ( context.getWindowRendererContext().getPageTemplate() != null )
{
message.append( " using page template [" ).append( context.getWindowRendererContext().getPageTemplate().getName() ).append(
"]" );
}
if ( context.getWindowRendererContext().getOriginalUrl() != null )
{
message.append( " originated with url [" ).append( context.getWindowRendererContext().getOriginalUrl() ).append( "]" );
}
}
message.append( ". Reason: " );
message.append( failureReason );
return message.toString();
}
private ContentEntity resolveContent( ContentKey contentKey, PostProcessInstructionContext context )
{
if ( context.getPreviewContext().isPreviewingContent() &&
context.getPreviewContext().getContentPreviewContext().isContentPreviewed( contentKey ) )
{
return context.getPreviewContext().getContentPreviewContext().getContentPreviewed();
}
return contentDao.findByKey( contentKey );
}
@Autowired
public void setMenuItemDao( MenuItemDao menuItemDao )
{
this.menuItemDao = menuItemDao;
}
@Autowired
public void setContentDao( ContentDao contentDao )
{
this.contentDao = contentDao;
}
@Autowired
public void setImagesService( ImageService imagesService )
{
this.imagesService = imagesService;
}
@Autowired
public void setWindowRendererFactory( WindowRendererFactory windowRendererFactory )
{
this.windowRendererFactory = windowRendererFactory;
}
@Autowired
public void setFileResourceService( FileResourceService fileResourceService )
{
this.fileResourceService = fileResourceService;
}
@Autowired
public void setSectionContentDao( SectionContentDao sectionContentDao )
{
this.sectionContentDao = sectionContentDao;
}
}