/*
* Copyright 2000-2013 Enonic AS
* http://www.enonic.com/license
*/
package com.enonic.cms.itest.content;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.jdom.Document;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.orm.hibernate3.HibernateTemplate;
import com.enonic.cms.framework.cache.CacheManager;
import com.enonic.cms.framework.xml.XMLDocumentFactory;
import com.enonic.cms.core.content.ContentEntity;
import com.enonic.cms.core.content.ContentKey;
import com.enonic.cms.core.content.ContentService;
import com.enonic.cms.core.content.ContentStatus;
import com.enonic.cms.core.content.ContentVersionEntity;
import com.enonic.cms.core.content.RelatedContentFetcherForContentVersion;
import com.enonic.cms.core.content.command.CreateContentCommand;
import com.enonic.cms.core.content.command.UpdateContentCommand;
import com.enonic.cms.core.content.contentdata.custom.CustomContentData;
import com.enonic.cms.core.content.contentdata.custom.contentkeybased.RelatedContentDataEntry;
import com.enonic.cms.core.content.contentdata.custom.relationdataentrylistbased.RelatedContentsDataEntry;
import com.enonic.cms.core.content.contentdata.custom.stringbased.TextDataEntry;
import com.enonic.cms.core.content.contenttype.ContentHandlerName;
import com.enonic.cms.core.content.contenttype.ContentTypeConfigBuilder;
import com.enonic.cms.core.content.resultset.RelatedChildContent;
import com.enonic.cms.core.content.resultset.RelatedContentResultSet;
import com.enonic.cms.core.security.PortalSecurityHolder;
import com.enonic.cms.core.security.user.User;
import com.enonic.cms.core.security.user.UserType;
import com.enonic.cms.itest.AbstractSpringTest;
import com.enonic.cms.itest.util.DomainFactory;
import com.enonic.cms.itest.util.DomainFixture;
import com.enonic.cms.store.dao.ContentEntityDao;
import com.enonic.cms.store.dao.RelatedChildContentQuery;
import static org.junit.Assert.*;
public class RelatedContentFetcherForContentVersionTest
extends AbstractSpringTest
{
@Autowired
private HibernateTemplate hibernateTemplate;
@Qualifier("cacheFacadeManager")
@Autowired
private CacheManager cacheManager;
private OverridingContentEntityDao contentDao;
@Autowired
private ContentService contentService;
@Autowired
private DomainFixture fixture;
@Before
public void setUp()
{
DomainFactory factory = fixture.getFactory();
// setup needed common data for each test
fixture.initSystemData();
fixture.createAndStoreUserAndUserGroup( "testuser", "testuser fullname", UserType.NORMAL, "testuserstore" );
//SecurityHolder.setUser( findUserByName( User.ANONYMOUS_UID ).getKey() );
PortalSecurityHolder.setAnonUser( fixture.findUserByName( User.ANONYMOUS_UID ).getKey() );
fixture.save( factory.createContentHandler( "Custom content", ContentHandlerName.CUSTOM.getHandlerClassShortName() ) );
fixture.flushAndClearHibernateSession();
// setup content type
ContentTypeConfigBuilder ctyconf = new ContentTypeConfigBuilder( "MyRelatingContent", "title" );
ctyconf.startBlock( "General" );
ctyconf.addInput( "title", "text", "contentdata/title", "Title", true );
ctyconf.addRelatedContentInput( "myRelatedContent", "contentdata/myRelatedContent", "My related content", false, true );
ctyconf.endBlock();
Document configAsJDOMDocument = XMLDocumentFactory.create( ctyconf.toString() ).getAsJDOMDocument();
fixture.save(
factory.createContentType( "MyRelatingContent", ContentHandlerName.CUSTOM.getHandlerClassShortName(), configAsJDOMDocument ) );
fixture.flushAndClearHibernateSession();
fixture.save( factory.createUnit( "MyUnit", "en" ) );
fixture.save( factory.createCategory( "MyCategory", null, "MyRelatingContent", "MyUnit", "testuser", "testuser" ) );
fixture.save( factory.createCategoryAccessForUser( "MyCategory", "testuser", "read, create, approve" ) );
fixture.flushAndClearHibernateSession();
contentDao = new OverridingContentEntityDao();
contentDao.setHibernateTemplate( hibernateTemplate );
contentDao.setCacheManager( cacheManager );
}
@Test
public void eternal_loop_is_prevented_for_related_children_with_circular_reference_but_all_other_are_included()
{
// setup content to update
CreateContentCommand createCommand = setupDefaultCreateContentCommandForMyRelatingContent( "Content not relating yet 1", null );
ContentKey content_1 = contentService.createContent( createCommand );
createCommand = setupDefaultCreateContentCommandForMyRelatingContent( "Content not relating yet 2", null );
ContentKey content_2 = contentService.createContent( createCommand );
createCommand = setupDefaultCreateContentCommandForMyRelatingContent( "Content not relating yet 3", null );
ContentKey content_3 = contentService.createContent( createCommand );
createCommand = setupDefaultCreateContentCommandForMyRelatingContent( "Content not relating yet 4", null );
ContentKey content_4 = contentService.createContent( createCommand );
createCommand = setupDefaultCreateContentCommandForMyRelatingContent( "Content not relating yet 5", null );
ContentKey content_5 = contentService.createContent( createCommand );
createCommand = setupDefaultCreateContentCommandForMyRelatingContent( "Content not relating yet 6", null );
ContentKey content_6 = contentService.createContent( createCommand );
UpdateContentCommand updateCommand =
setupDefaultUpdateContentCommandForMyRelatingContent( content_1, "Relating content 1 to 2 and 6", content_2, content_6 );
contentService.updateContent( updateCommand );
updateCommand =
setupDefaultUpdateContentCommandForMyRelatingContent( content_2, "Relating content 2 to 3 and 5", content_3, content_5 );
contentService.updateContent( updateCommand );
updateCommand =
setupDefaultUpdateContentCommandForMyRelatingContent( content_3, "Relating content 3 to 1 and 4", content_1, content_4 );
contentService.updateContent( updateCommand );
contentDao.setMaxExpectedFindRelatedChildrenByKeysAttempts( 6 );
RelatedContentFetcherForContentVersion relatedContentFetcher = new RelatedContentFetcherForContentVersion( contentDao );
relatedContentFetcher.setAvailableCheckDate( new Date() );
relatedContentFetcher.setMaxChildrenLevel( Integer.MAX_VALUE );
relatedContentFetcher.setIncludeOfflineContent( true );
List<ContentVersionEntity> versions = new ArrayList<ContentVersionEntity>();
versions.add( fixture.findContentByKey( content_1 ).getMainVersion() );
RelatedContentResultSet resultSet = relatedContentFetcher.fetch( versions );
List<ContentKey> expectedRelatedContentKeys = new ArrayList<ContentKey>();
expectedRelatedContentKeys.add( content_2 );
expectedRelatedContentKeys.add( content_3 );
expectedRelatedContentKeys.add( content_4 );
expectedRelatedContentKeys.add( content_5 );
expectedRelatedContentKeys.add( content_6 );
assertRelatedContent( expectedRelatedContentKeys, resultSet.getContentKeys() );
}
@Test
public void eternal_loop_is_prevented_for_related_children_with_multiple_circular_references()
{
// setup content to update
CreateContentCommand createCommand = setupDefaultCreateContentCommandForMyRelatingContent( "Content not relating yet 1", null );
ContentKey content_1 = contentService.createContent( createCommand );
createCommand = setupDefaultCreateContentCommandForMyRelatingContent( "Content not relating yet 2", null );
ContentKey content_2 = contentService.createContent( createCommand );
createCommand = setupDefaultCreateContentCommandForMyRelatingContent( "Content not relating yet 3", null );
ContentKey content_3 = contentService.createContent( createCommand );
createCommand = setupDefaultCreateContentCommandForMyRelatingContent( "Content not relating yet 4", null );
ContentKey content_4 = contentService.createContent( createCommand );
UpdateContentCommand updateCommand =
setupDefaultUpdateContentCommandForMyRelatingContent( content_1, "Relating content 1 to 2, 3 and 4", content_2, content_3,
content_4 );
contentService.updateContent( updateCommand );
updateCommand = setupDefaultUpdateContentCommandForMyRelatingContent( content_2, "Relating content 2 to 1", content_1 );
contentService.updateContent( updateCommand );
updateCommand = setupDefaultUpdateContentCommandForMyRelatingContent( content_3, "Relating content 3 to 1", content_1 );
contentService.updateContent( updateCommand );
updateCommand = setupDefaultUpdateContentCommandForMyRelatingContent( content_4, "Relating content 4 to 1", content_1 );
contentService.updateContent( updateCommand );
contentDao.setMaxExpectedFindRelatedChildrenByKeysAttempts( 4 );
RelatedContentFetcherForContentVersion relatedContentFetcher = new RelatedContentFetcherForContentVersion( contentDao );
relatedContentFetcher.setAvailableCheckDate( new Date() );
relatedContentFetcher.setMaxChildrenLevel( Integer.MAX_VALUE );
relatedContentFetcher.setIncludeOfflineContent( true );
List<ContentVersionEntity> versions = new ArrayList<ContentVersionEntity>();
versions.add( fixture.findContentByKey( content_1 ).getMainVersion() );
RelatedContentResultSet resultSet = relatedContentFetcher.fetch( versions );
List<ContentKey> expectedRelatedContentKeys = new ArrayList<ContentKey>();
expectedRelatedContentKeys.add( content_2 );
expectedRelatedContentKeys.add( content_3 );
expectedRelatedContentKeys.add( content_4 );
assertRelatedContent( expectedRelatedContentKeys, resultSet.getContentKeys() );
}
@Test
public void eternal_loop_is_prevented_for_related_children_of_children()
{
// setup content to update
CreateContentCommand createCommand = setupDefaultCreateContentCommandForMyRelatingContent( "Content not relating yet 1", null );
ContentKey content_1 = contentService.createContent( createCommand );
createCommand = setupDefaultCreateContentCommandForMyRelatingContent( "Content not relating yet 2", null );
ContentKey content_2 = contentService.createContent( createCommand );
createCommand = setupDefaultCreateContentCommandForMyRelatingContent( "Content not relating yet 3", null );
ContentKey content_3 = contentService.createContent( createCommand );
createCommand = setupDefaultCreateContentCommandForMyRelatingContent( "Content not relating yet 4", null );
ContentKey content_4 = contentService.createContent( createCommand );
UpdateContentCommand updateCommand =
setupDefaultUpdateContentCommandForMyRelatingContent( content_1, "Relating content 1-2" + content_2, content_2 );
contentService.updateContent( updateCommand );
updateCommand = setupDefaultUpdateContentCommandForMyRelatingContent( content_2, "Relating content 2-3" + content_3, content_3 );
contentService.updateContent( updateCommand );
updateCommand = setupDefaultUpdateContentCommandForMyRelatingContent( content_3, "Relating content 3-4" + content_4, content_4 );
contentService.updateContent( updateCommand );
updateCommand = setupDefaultUpdateContentCommandForMyRelatingContent( content_4, "Relating content 4-3" + content_3, content_3 );
contentService.updateContent( updateCommand );
fixture.flushAndClearHibernateSession();
contentDao.setMaxExpectedFindRelatedChildrenByKeysAttempts( 4 );
RelatedContentFetcherForContentVersion relatedContentFetcher = new RelatedContentFetcherForContentVersion( contentDao );
relatedContentFetcher.setAvailableCheckDate( new Date() );
relatedContentFetcher.setMaxChildrenLevel( Integer.MAX_VALUE );
relatedContentFetcher.setIncludeOfflineContent( true );
List<ContentVersionEntity> versions = new ArrayList<ContentVersionEntity>();
versions.add( fixture.findContentByKey( content_1 ).getMainVersion() );
RelatedContentResultSet resultSet = relatedContentFetcher.fetch( versions );
List<ContentKey> expectedRelatedContentKeys = new ArrayList<ContentKey>();
expectedRelatedContentKeys.add( content_2 );
expectedRelatedContentKeys.add( content_3 );
expectedRelatedContentKeys.add( content_4 );
assertRelatedContent( expectedRelatedContentKeys, resultSet.getContentKeys() );
}
@Test
public void including_visited_returns_all_when_true_but_not_when_false()
{
// setup content to update
CreateContentCommand createCommand = setupDefaultCreateContentCommandForMyRelatingContent( "Main content 1", null );
ContentKey content_1 = contentService.createContent( createCommand );
createCommand = setupDefaultCreateContentCommandForMyRelatingContent( "Main content 2", null );
ContentKey content_2 = contentService.createContent( createCommand );
createCommand = setupDefaultCreateContentCommandForMyRelatingContent( "Related Content A", null );
ContentKey content_A = contentService.createContent( createCommand );
createCommand = setupDefaultCreateContentCommandForMyRelatingContent( "Related Content B", null );
ContentKey content_B = contentService.createContent( createCommand );
createCommand = setupDefaultCreateContentCommandForMyRelatingContent( "Related Content C", null );
ContentKey content_C = contentService.createContent( createCommand );
UpdateContentCommand updateCommand =
setupDefaultUpdateContentCommandForMyRelatingContent( content_1, "Relating content 1 to A and B", content_A, content_B );
contentService.updateContent( updateCommand );
updateCommand =
setupDefaultUpdateContentCommandForMyRelatingContent( content_2, "Relating content 2 to A and C", content_A, content_C );
contentService.updateContent( updateCommand );
RelatedContentFetcherForContentVersion relatedContentFetcher = new RelatedContentFetcherForContentVersion( contentDao );
relatedContentFetcher.setAvailableCheckDate( new Date() );
relatedContentFetcher.setMaxChildrenLevel( Integer.MAX_VALUE );
relatedContentFetcher.setIncludeOfflineContent( true );
// First attempt. Regular retrieval: A and B are related to 1.
List<ContentVersionEntity> versions = new ArrayList<ContentVersionEntity>();
versions.add( fixture.findContentByKey( content_1 ).getMainVersion() );
RelatedContentResultSet resultSet = relatedContentFetcher.fetch( versions );
List<ContentKey> expectedRelatedContentKeys = new ArrayList<ContentKey>();
expectedRelatedContentKeys.add( content_A );
expectedRelatedContentKeys.add( content_B );
assertRelatedContent( expectedRelatedContentKeys, resultSet.getContentKeys() );
// Second attempt. Regular retrieval: Only C that has not been retrieved before is related to 2.
versions = new ArrayList<ContentVersionEntity>();
versions.add( fixture.findContentByKey( content_2 ).getMainVersion() );
resultSet = relatedContentFetcher.fetch( versions );
expectedRelatedContentKeys = new ArrayList<ContentKey>();
expectedRelatedContentKeys.add( content_C );
assertRelatedContent( expectedRelatedContentKeys, resultSet.getContentKeys() );
// Third attempt. Regular retrieval: All related items have been retrieved before. Empty result.
versions = new ArrayList<ContentVersionEntity>();
versions.add( fixture.findContentByKey( content_1 ).getMainVersion() );
resultSet = relatedContentFetcher.fetch( versions );
expectedRelatedContentKeys = new ArrayList<ContentKey>();
assertRelatedContent( expectedRelatedContentKeys, resultSet.getContentKeys() );
// Fourth attempt. RequireAll retrieval: A and C are related to 2.
versions = new ArrayList<ContentVersionEntity>();
versions.add( fixture.findContentByKey( content_2 ).getMainVersion() );
resultSet = relatedContentFetcher.fetch( versions, true );
expectedRelatedContentKeys = new ArrayList<ContentKey>();
expectedRelatedContentKeys.add( content_A );
expectedRelatedContentKeys.add( content_C );
assertRelatedContent( expectedRelatedContentKeys, resultSet.getContentKeys() );
}
private CreateContentCommand setupDefaultCreateContentCommandForMyRelatingContent( String title, ContentKey contentToRelateTo )
{
CreateContentCommand createCommand = new CreateContentCommand();
createCommand.setAccessRightsStrategy( CreateContentCommand.AccessRightsStrategy.INHERIT_FROM_CATEGORY );
createCommand.setCategory( fixture.findCategoryByName( "MyCategory" ).getKey() );
createCommand.setCreator( fixture.findUserByName( "testuser" ).getKey() );
createCommand.setPriority( 0 );
createCommand.setLanguage( fixture.findLanguageByCode( "en" ) );
createCommand.setStatus( ContentStatus.APPROVED );
createCommand.setContentName( "testcontent" );
CustomContentData contentData =
new CustomContentData( fixture.findContentTypeByName( "MyRelatingContent" ).getContentTypeConfig() );
contentData.add( new TextDataEntry( contentData.getInputConfig( "title" ), title ) );
if ( contentToRelateTo != null )
{
contentData.add( new RelatedContentDataEntry( contentData.getInputConfig( "myRelatedContent" ), contentToRelateTo ) );
}
createCommand.setContentData( contentData );
return createCommand;
}
private UpdateContentCommand setupDefaultUpdateContentCommandForMyRelatingContent( ContentKey contentKeyToUpdate, String title,
ContentKey... contentToRelateTo )
{
ContentEntity contentToUpdate = fixture.findContentByKey( contentKeyToUpdate );
UpdateContentCommand command = UpdateContentCommand.updateExistingVersion2( contentToUpdate.getMainVersion().getKey() );
command.setContentKey( contentToUpdate.getKey() );
command.setUpdateStrategy( UpdateContentCommand.UpdateStrategy.MODIFY );
command.setModifier( fixture.findUserByName( "testuser" ).getKey() );
command.setPriority( 0 );
command.setLanguage( fixture.findLanguageByCode( "en" ) );
command.setStatus( ContentStatus.APPROVED );
CustomContentData contentData =
new CustomContentData( fixture.findContentTypeByName( "MyRelatingContent" ).getContentTypeConfig() );
contentData.add( new TextDataEntry( contentData.getInputConfig( "title" ), title ) );
if ( contentToRelateTo != null )
{
RelatedContentsDataEntry relatedContentsDataEntry =
new RelatedContentsDataEntry( contentData.getInputConfig( "myRelatedContent" ) );
for ( ContentKey singleContentToRelateTo : contentToRelateTo )
{
relatedContentsDataEntry.add(
new RelatedContentDataEntry( contentData.getInputConfig( "myRelatedContent" ), singleContentToRelateTo ) );
}
contentData.add( relatedContentsDataEntry );
}
command.setContentData( contentData );
return command;
}
private void assertRelatedContent( Collection<ContentKey> expectedRelatedContentKeys, Collection<ContentKey> actual )
{
for ( ContentKey expectedContentKey : expectedRelatedContentKeys )
{
assertTrue( "expected related content with key: " + expectedContentKey, actual.contains( expectedContentKey ) );
}
assertEquals( "unexpected number of related content", expectedRelatedContentKeys.size(), actual.size() );
}
class OverridingContentEntityDao
extends ContentEntityDao
{
private int numberOfFindRelatedChildrenByKeysAttempts = 0;
private int maxExpectedFindRelatedChildrenByKeysAttempts = Integer.MAX_VALUE;
public void setMaxExpectedFindRelatedChildrenByKeysAttempts( int maxExpectedFindRelatedChildrenByKeysAttempts )
{
this.maxExpectedFindRelatedChildrenByKeysAttempts = maxExpectedFindRelatedChildrenByKeysAttempts;
}
@Override
public Collection<RelatedChildContent> findRelatedChildrenByKeys( RelatedChildContentQuery relatedChildContentQuery )
{
numberOfFindRelatedChildrenByKeysAttempts++;
if ( numberOfFindRelatedChildrenByKeysAttempts > maxExpectedFindRelatedChildrenByKeysAttempts )
{
fail( "max expected findRelatedChildrenByKeys attempts exceeded : " + numberOfFindRelatedChildrenByKeysAttempts );
}
return super.findRelatedChildrenByKeys( relatedChildContentQuery );
}
}
}