/*
* Copyright 2000-2013 Enonic AS
* http://www.enonic.com/license
*/
package com.enonic.cms.core.content.imports;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import com.enonic.cms.core.content.ContentEntity;
import com.enonic.cms.core.content.ContentKey;
import com.enonic.cms.core.content.ContentVersionEntity;
import com.enonic.cms.core.content.contentdata.ContentData;
import com.enonic.cms.core.content.contentdata.custom.CustomContentData;
import com.enonic.cms.core.content.contentdata.custom.DataEntry;
import com.enonic.cms.core.content.contentdata.custom.DateDataEntry;
import com.enonic.cms.core.content.contentdata.custom.contentkeybased.AbstractContentKeyBasedInputDataEntry;
import com.enonic.cms.core.content.contentdata.custom.stringbased.AbstractStringBasedInputDataEntry;
import com.enonic.cms.core.content.contenttype.ContentTypeEntity;
import com.enonic.cms.core.content.contenttype.CtyImportMappingConfig;
import com.enonic.cms.core.content.contenttype.dataentryconfig.DataEntryConfig;
import com.enonic.cms.core.content.imports.sourcevalueholders.AbstractSourceValue;
import com.enonic.cms.core.content.imports.sourcevalueholders.StringArraySourceValue;
import com.enonic.cms.core.content.index.ContentIndexQuery;
import com.enonic.cms.core.content.index.config.IndexDefinition;
import com.enonic.cms.core.content.index.config.IndexDefinitionBuilder;
import com.enonic.cms.core.content.resultset.ContentResultSet;
import com.enonic.cms.core.search.query.ContentIndexService;
import com.enonic.cms.store.dao.ContentTypeDao;
public class RelatedContentFinder
{
private ContentTypeDao contentTypeDao;
private ContentIndexService contentIndexService;
public RelatedContentFinder( ContentTypeDao contentTypeDao, ContentIndexService contentIndexService )
{
this.contentTypeDao = contentTypeDao;
this.contentIndexService = contentIndexService;
}
public ContentKey getContentKey( final Map.Entry<CtyImportMappingConfig, AbstractSourceValue> configAndValue )
{
final ContentIndexQuery query = getQuery( configAndValue );
final ContentResultSet resSet = contentIndexService.query( query );
if ( resSet.getLength() != 1 )
{
throw new ImportException(
"Could not find uniqely identify related content with query: \"" + query.getQuery() + "\". " + resSet.getLength() +
" contents found. Keys: " + getKeysToDisplay( resSet, 5 ) );
}
return resSet.getKey( 0 );
}
public List<ContentKey> getContentKeys( final Map.Entry<CtyImportMappingConfig, AbstractSourceValue> configAndValue )
{
final List<String> values = getValues( configAndValue.getValue() );
if ( values.isEmpty() )
{
return Collections.emptyList();
}
final ContentIndexQuery query = getQuery( configAndValue );
final ContentResultSet resSet = contentIndexService.query( query );
StringArraySourceValue value = (StringArraySourceValue) configAndValue.getValue();
LinkedList<String> orderMask = Lists.newLinkedList( value.getValues() );
String relatedKey = configAndValue.getKey().getRelatedField();
return getOrderedKeys( resSet, orderMask, relatedKey );
}
protected List<ContentKey> getOrderedKeys( final ContentResultSet resSet, List<String> orderMask, String relatedKey )
{
List<ContentEntity> contents = Lists.newArrayList( resSet.getContents() );
Collections.sort( contents, new OrderComparator( orderMask, relatedKey ) );
List<ContentKey> contentKeys = new LinkedList<ContentKey>();
for ( ContentEntity content : contents )
{
contentKeys.add( content.getKey() );
}
return contentKeys;
}
private static class OrderComparator
implements Comparator<ContentEntity>
{
private final List<String> orderMask;
private final String relatedKey;
private OrderComparator( List<String> orderMask, String relatedKey )
{
this.orderMask = orderMask;
this.relatedKey = relatedKey;
}
/*
* In <code>orderMask</code> linked list the position of element in list is used as order position
*/
public int compare( ContentEntity content1, ContentEntity content2 )
{
String value1 = getValue( content1 );
String value2 = getValue( content2 );
Integer order1 = orderMask.indexOf( value1 );
Integer order2 = orderMask.indexOf( value2 );
order1 = order1 == -1 ? Integer.MAX_VALUE : order1;
order2 = order2 == -1 ? Integer.MAX_VALUE : order2;
if ( order1.equals( order2 ) )
{
return 0;
}
return order1 > order2 ? 1 : -1;
}
private String getValue( ContentEntity content )
{
ContentVersionEntity contentVersion = content.getMainVersion();
ContentData contentData = contentVersion.getContentData();
if( contentData instanceof CustomContentData )
{
return doGetValueFromCustomContentData( (CustomContentData) contentData );
}
else
{
return contentData.getTitle();
}
}
private String doGetValueFromCustomContentData( CustomContentData contentData )
{
DataEntry dataEntry = contentData.getEntry( this.relatedKey );
if ( dataEntry instanceof AbstractStringBasedInputDataEntry )
{
return ( (AbstractStringBasedInputDataEntry) dataEntry ).getValue();
}
else if ( dataEntry instanceof AbstractContentKeyBasedInputDataEntry )
{
return ( (AbstractContentKeyBasedInputDataEntry) dataEntry ).getContentKey().toString();
}
else if ( dataEntry instanceof DateDataEntry )
{
Date date = ( (DateDataEntry) dataEntry ).getValue();
DateFormat isoFormat = new SimpleDateFormat( "yyyy-MM-dd" );
return isoFormat.format( date );
}
throw new IllegalArgumentException( "Illegal value: " + dataEntry );
}
}
private String getKeysToDisplay( ContentResultSet resSet, int maxKeyCount )
{
final StringBuilder msgBuilder = new StringBuilder();
final int count = Math.min( resSet.getLength(), maxKeyCount );
for ( int i = 0; i < count; i++ )
{
if ( i > 0 )
{
msgBuilder.append( ", " );
}
msgBuilder.append( resSet.getKey( i ) );
}
if ( count < resSet.getLength() )
{
msgBuilder.append( "..." );
}
return msgBuilder.toString();
}
private ContentIndexQuery getQuery( final Map.Entry<CtyImportMappingConfig, AbstractSourceValue> configAndValue )
{
final CtyImportMappingConfig mappingConfig = configAndValue.getKey();
final ContentTypeEntity contentType = getContentType( mappingConfig );
final String relatedField = mappingConfig.getRelatedField();
final String fieldXpath = getRelatedContentXpath( contentType, relatedField );
final List<String> values = getValues( configAndValue.getValue() );
final StringBuilder builder = new StringBuilder();
for ( int i = 0; i < values.size(); i++ )
{
if ( i > 0 )
{
builder.append( ", " );
}
builder.append( "\"" );
builder.append( values.get( i ) );
builder.append( "\"" );
}
return new ContentIndexQuery(
"contentTypeKey = " + contentType.getKey() + " AND " + fieldXpath + " IN(" + builder.toString() + ")" );
}
private List<String> getValues( AbstractSourceValue value )
{
try
{
return ImportValueFormater.getRelatedContent( value );
}
catch ( ImportException ex )
{
throw new ImportException( "Invalid related content value found.", ex );
}
}
private ContentTypeEntity getContentType( CtyImportMappingConfig mappingConfig )
{
final String contentTypeName = mappingConfig.getRelatedContentType();
final ContentTypeEntity contentType = contentTypeDao.findByName( contentTypeName );
if ( contentType == null )
{
throw new ImportException(
"Could not find related content type \"" + contentTypeName + "\" from import mapping \"" + mappingConfig.getDestination() +
"\"." );
}
return contentType;
}
private String getRelatedContentXpath( final ContentTypeEntity contentType, final String relatedField )
{
switch ( contentType.getContentHandlerName() )
{
case CUSTOM:
return getRelatedContentXpathFromCustomContent( contentType, relatedField );
case FILE:
case IMAGE:
return getRelatedContentXpathFromLegacyContent( contentType, relatedField );
default:
throw new ImportException(
"Import does not support related content types based on handler \"" + contentType.getContentHandlerName() + "\"" );
}
}
private String getRelatedContentXpathFromCustomContent( final ContentTypeEntity contentType, final String relatedField )
{
final DataEntryConfig dataEntryConfig = contentType.getContentTypeConfig().getInputConfig( relatedField );
if ( dataEntryConfig == null )
{
throw new ImportException(
"Could not find field from from mapping attribute \"relatedfield\" with value \"" + relatedField + "\" in content type \"" +
contentType.getName() + "\"" );
}
final String fieldXpath = dataEntryConfig.getXpath();
verifyXpath( contentType, relatedField, fieldXpath );
return fieldXpath;
}
private String getRelatedContentXpathFromLegacyContent( final ContentTypeEntity contentType, final String relatedField )
{
final String fieldXpath = "contentdata/" + relatedField;
verifyXpath( contentType, relatedField, fieldXpath );
return fieldXpath;
}
private void verifyXpath( ContentTypeEntity contentType, String relatedField, String fieldXpath )
{
if ( !isXpathIndexed( contentType, fieldXpath ) )
{
throw new ImportException(
"Could not find index using xpath \"" + fieldXpath + "\" from from mapping attribute \"relatedfield\" with value \"" +
relatedField + "\" in content type \"" + contentType.getName() + "\". Related field must be indexed." );
}
}
private boolean isXpathIndexed( ContentTypeEntity contentType, String syncXpath )
{
final List<IndexDefinition> indexes = new IndexDefinitionBuilder().buildList( contentType );
for ( IndexDefinition index : indexes )
{
if ( index.getXPath().equals( syncXpath ) )
{
return true;
}
}
return false;
}
}