/* * Copyright 2000-2013 Enonic AS * http://www.enonic.com/license */ package com.enonic.cms.core.content; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.enonic.cms.framework.xml.XMLDocument; import com.enonic.cms.core.content.category.CategoryEntity; import com.enonic.cms.core.content.category.CategoryKey; import com.enonic.cms.core.content.command.AssignContentCommand; import com.enonic.cms.core.content.command.CreateContentCommand; import com.enonic.cms.core.content.image.GenerateLowResImagesCommand; import com.enonic.cms.core.content.command.SnapshotContentCommand; import com.enonic.cms.core.content.command.UnassignContentCommand; import com.enonic.cms.core.content.command.UpdateAssignmentCommand; import com.enonic.cms.core.content.command.UpdateContentCommand; import com.enonic.cms.core.content.contenttype.ContentTypeEntity; import com.enonic.cms.core.content.contenttype.ContentTypeKey; import com.enonic.cms.core.content.index.ContentIndexQuery; import com.enonic.cms.core.content.query.AbstractContentArchiveQuery; import com.enonic.cms.core.content.query.ContentByCategoryQuery; import com.enonic.cms.core.content.query.ContentByContentQuery; import com.enonic.cms.core.content.query.ContentByQueryQuery; import com.enonic.cms.core.content.query.ContentBySectionQuery; import com.enonic.cms.core.content.query.OpenContentQuery; import com.enonic.cms.core.content.query.RelatedChildrenContentQuery; import com.enonic.cms.core.content.query.RelatedContentQuery; import com.enonic.cms.core.content.resultset.ContentResultSet; import com.enonic.cms.core.content.resultset.ContentResultSetLazyFetcher; import com.enonic.cms.core.content.resultset.ContentResultSetNonLazy; import com.enonic.cms.core.content.resultset.RelatedContentResultSet; import com.enonic.cms.core.content.resultset.RelatedContentResultSetImpl; import com.enonic.cms.core.log.LogService; import com.enonic.cms.core.log.LogType; import com.enonic.cms.core.log.StoreNewLogEntryCommand; import com.enonic.cms.core.log.Table; import com.enonic.cms.core.portal.livetrace.LivePortalTraceService; import com.enonic.cms.core.portal.livetrace.RelatedContentFetchTrace; import com.enonic.cms.core.portal.livetrace.RelatedContentFetchTracer; import com.enonic.cms.core.search.IndexTransactionService; import com.enonic.cms.core.search.query.AggregatedQuery; import com.enonic.cms.core.search.query.ContentIndexService; import com.enonic.cms.core.search.query.IndexValueQuery; import com.enonic.cms.core.security.group.GroupKey; import com.enonic.cms.core.security.user.UserEntity; import com.enonic.cms.core.security.user.UserKey; import com.enonic.cms.core.structure.SiteEntity; import com.enonic.cms.core.structure.SiteKey; import com.enonic.cms.core.structure.menuitem.MenuItemEntity; import com.enonic.cms.core.structure.menuitem.MenuItemKey; import com.enonic.cms.store.dao.CategoryDao; import com.enonic.cms.store.dao.ContentDao; import com.enonic.cms.store.dao.ContentTypeDao; import com.enonic.cms.store.dao.ContentVersionDao; import com.enonic.cms.store.dao.MenuItemDao; import com.enonic.cms.store.dao.SiteDao; @Service("contentService") public class ContentServiceImpl implements ContentService { @Autowired private ContentIndexService contentIndexService; @Autowired private MenuItemDao menuItemDao; @Autowired private ContentDao contentDao; @Autowired private ContentVersionDao contentVersionDao; @Autowired private ContentSecurityFilterResolver contentSecurityFilterResolver; @Autowired private CategoryDao categoryDao; @Autowired private ContentTypeDao contentTypeDao; private ContentStorer contentStorer; @Autowired private LogService logService; @Autowired private IndexTransactionService indexTransactionService; @Autowired private LivePortalTraceService livePortalTraceService; @Autowired private SiteDao siteDao; @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public SnapshotContentResult snapshotContent( SnapshotContentCommand command ) { try { return contentStorer.snapshotContent( command ); } catch ( RuntimeException e ) { throw new SnapshotContentException( e ); } } @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public AssignContentResult assignContent( AssignContentCommand command ) { try { indexTransactionService.startTransaction(); final AssignContentResult result = contentStorer.assignContent( command ); return result; } catch ( RuntimeException e ) { throw new AssignContentException( e ); } } @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void updateAssignment( UpdateAssignmentCommand command ) { try { indexTransactionService.startTransaction(); contentStorer.updateAssignment( command ); } catch ( RuntimeException e ) { throw new UpdateAssignmentException( e ); } } @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public UnassignContentResult unassignContent( UnassignContentCommand command ) { try { indexTransactionService.startTransaction(); final UnassignContentResult result = contentStorer.unassignContent( command ); return result; } catch ( RuntimeException e ) { throw new UnassignContentException( e ); } } @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public ContentKey createContent( CreateContentCommand command ) { try { indexTransactionService.startTransaction(); final ContentEntity content = contentStorer.createContent( command ); logEvent( command.getCreator(), content, LogType.ENTITY_CREATED, command.getSiteKey() ); return content.getKey(); } catch ( RuntimeException e ) { throw new CreateContentException( e ); } } @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public UpdateContentResult updateContent( UpdateContentCommand command ) { try { indexTransactionService.startTransaction(); final UpdateContentResult updateContentResult = contentStorer.updateContent( command ); if ( updateContentResult.isAnyChangesMade() ) { logEvent( command.getModifier(), updateContentResult.getTargetedVersion().getContent(), LogType.ENTITY_UPDATED, command.getSiteKey() ); } return updateContentResult; } catch ( RuntimeException e ) { throw new UpdateContentException( e ); } } @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public int generateLowResImages( final GenerateLowResImagesCommand command ) { try { indexTransactionService.startTransaction(); return contentStorer.generateScaledImagesOfMainVersion( command ); } catch ( RuntimeException e ) { throw new UpdateContentException( e ); } } @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void deleteContent( final UserEntity deleter, final ContentEntity content, final SiteKey siteKey ) { if ( content == null ) { throw new IllegalArgumentException( "Given content cannot be null" ); } if ( deleter == null ) { throw new IllegalArgumentException( "Given deleter cannot be null" ); } if ( content.hasDirectMenuItemPlacements() ) { throw new RuntimeException( "Cannot delete content because it is in use by a page" ); } indexTransactionService.startTransaction(); contentStorer.deleteContent( deleter, content ); logEvent( deleter.getKey(), content, LogType.ENTITY_REMOVED, siteKey ); } @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void deleteVersion( UserEntity deleter, final ContentVersionKey contentVersionKey ) { indexTransactionService.startTransaction(); if ( deleter == null ) { throw new IllegalArgumentException( "Given deleter cannot be null" ); } ContentVersionEntity version = contentVersionDao.findByKey( contentVersionKey ); if ( version == null ) { throw new IllegalArgumentException( "Content version does not exists: " + contentVersionKey ); } if ( version.isMainVersion() ) { throw new IllegalArgumentException( "Cannot delete main version of a content" ); } if ( version.getStatus() == ContentStatus.APPROVED ) { throw new IllegalArgumentException( "Cannot delete the approved version of a content" ); } if ( version.getStatus() == ContentStatus.ARCHIVED ) { throw new IllegalArgumentException( "Cannot delete an archived version of a content" ); } if ( version.getContent().getVersionCount() == 1 ) { throw new IllegalArgumentException( "Cannot delete the last version of a content" ); } contentStorer.deleteVersion( deleter, version ); } @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public boolean archiveContent( final UserEntity archiver, final ContentEntity content ) { indexTransactionService.startTransaction(); if ( content == null ) { throw new IllegalArgumentException( "Given content cannot be null" ); } if ( archiver == null ) { throw new IllegalArgumentException( "Given archiver cannot be null" ); } boolean updated = contentStorer.archiveMainVersion( archiver, content ); if ( updated ) { logEvent( archiver.getKey(), content, LogType.ENTITY_UPDATED ); } return updated; } @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public boolean approveContent( final UserEntity approver, final ContentEntity content ) { indexTransactionService.startTransaction(); if ( content == null ) { throw new IllegalArgumentException( "Given content cannot be null" ); } if ( approver == null ) { throw new IllegalArgumentException( "Given approver cannot be null" ); } boolean updated = contentStorer.approveMainVersion( approver, content ); if ( updated ) { logEvent( approver.getKey(), content, LogType.ENTITY_UPDATED ); } return updated; } @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void moveContent( UserEntity mover, ContentEntity content, CategoryEntity toCategory ) { indexTransactionService.startTransaction(); contentStorer.moveContent( mover, content, toCategory ); } @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public ContentKey copyContent( UserEntity copier, ContentEntity content, CategoryEntity toCategory ) { indexTransactionService.startTransaction(); final ContentKey contentKey = contentStorer.copyContent( copier, content, toCategory ); return contentKey; } public ContentResultSet queryContent( ContentBySectionQuery spec ) { spec.validate(); Collection<MenuItemEntity> sections = doGetSectionKeysByMenuItems( spec.getMenuItemKeys(), spec.getLevels() ); Collection<GroupKey> securityFilter = spec.getUser() != null ? contentSecurityFilterResolver.resolveGroupKeys( spec.getUser() ) : null; ContentIndexQuery query = spec.createAndSetupContentQuery( sections, securityFilter ); return contentIndexService.query( query ); } public ContentResultSet queryContent( OpenContentQuery query ) { return doQueryContent( query ); } public ContentResultSet queryContent( ContentByQueryQuery query ) { return doQueryContent( query ); } public ContentResultSet queryContent( ContentByCategoryQuery query ) { return doQueryContent( query ); } public ContentResultSet queryContent( ContentByContentQuery query ) { return doQueryContent( query ); } private ContentResultSet doQueryContent( AbstractContentArchiveQuery spec ) { spec.validate(); Set<CategoryKey> allCategories = null; if ( spec.useCategoryKeyFilter() ) { allCategories = new HashSet<CategoryKey>(); if ( spec.getLevels() == 0 ) { fillInSubCategories( allCategories, spec.getCategoryKeyFilter(), Integer.MAX_VALUE ); } else if ( spec.getLevels() > 0 ) { fillInSubCategories( allCategories, spec.getCategoryKeyFilter(), spec.getLevels() - 1 ); } allCategories.addAll( spec.getCategoryKeyFilter() ); } Collection<GroupKey> securityFilter = spec.getUser() != null ? contentSecurityFilterResolver.resolveGroupKeys( spec.getUser() ) : null; ContentIndexQuery query = spec.createAndSetupContentQuery( allCategories, securityFilter ); return contentIndexService.query( query ); } public ContentResultSet getContent( ContentSpecification specification, String orderByCol, int count, int index ) { List<ContentKey> contentKeys = contentDao.findBySpecification( specification, orderByCol, count + index ); final int totalCount = contentDao.findCountBySpecification( specification ); final int queryResultTotalSize = contentKeys.size(); if ( index > queryResultTotalSize ) { return (ContentResultSet) new ContentResultSetNonLazy( index ).addError( "Index greater than result count: " + index + " greater than " + queryResultTotalSize ); } int fromIndex = Math.max( index, 0 ); int toIndex = Math.min( queryResultTotalSize, fromIndex + count ); final List<ContentKey> actualKeysWanted = contentKeys.subList( fromIndex, toIndex ); return new ContentResultSetLazyFetcher( new ContentEntityFetcherImpl( contentDao ), actualKeysWanted, index, totalCount ); } public RelatedContentResultSet queryRelatedContent( final RelatedContentQuery spec ) { final RelatedContentFetchTrace trace = RelatedContentFetchTracer.startTracing( livePortalTraceService ); try { final RelatedContentFetcher fetcher = new RelatedContentFetcher( contentDao, trace ); fetcher.setSecurityFilter( spec.getUser() != null ? contentSecurityFilterResolver.resolveGroupKeys( spec.getUser() ) : null ); fetcher.setMaxChildrenLevel( spec.getChildrenLevel() ); fetcher.setMaxParentLevel( spec.getParentLevel() ); fetcher.setMaxParentChildrenLevel( spec.getParentChildrenLevel() ); fetcher.setIncludeOnlyMainVersions( spec.includeOnlyMainVersions() ); fetcher.setAvailableCheckDate( spec.getOnlineCheckDate() ); fetcher.setIncludeOfflineContent( !spec.isFilterContentOnline() ); RelatedContentFetchTracer.traceDefinition( fetcher, trace ); return fetcher.fetch( spec.getContentResultSet() ); } finally { RelatedContentFetchTracer.stopTracing( trace, livePortalTraceService ); } } public RelatedContentResultSet queryRelatedContent( final RelatedChildrenContentQuery spec ) { final RelatedContentFetchTrace trace = RelatedContentFetchTracer.startTracing( livePortalTraceService ); try { final RelatedContentFetcherForContentVersion fetcher = new RelatedContentFetcherForContentVersion( contentDao, trace ); fetcher.setSecurityFilter( spec.getUser() != null ? contentSecurityFilterResolver.resolveGroupKeys( spec.getUser() ) : null ); fetcher.setAvailableCheckDate( spec.getOnlineCheckDate() ); fetcher.setMaxChildrenLevel( spec.getChildrenLevel() ); fetcher.setIncludeOfflineContent( !spec.isOnline() ); return fetcher.fetch( spec.getContentVersions(), false ); } finally { RelatedContentFetchTracer.stopTracing( trace, livePortalTraceService ); } } public RelatedContentResultSet getRelatedContentRequiresAll( final UserEntity user, final int relation, final ContentResultSet contents ) { RelatedContentFetchTrace trace = RelatedContentFetchTracer.startTracing( livePortalTraceService ); final int parentLevel = relation > 0 ? 0 : 1; final int childrenLevel = relation > 0 ? 1 : 0; final Set<ContentKey> relatedContentIntersectionSet = new HashSet<ContentKey>(); final RelatedContentResultSet firstRelatedContentSet; try { final RelatedContentFetcher relatedContentFetcher = new RelatedContentFetcher( contentDao, trace ); relatedContentFetcher.setSecurityFilter( user != null ? contentSecurityFilterResolver.resolveGroupKeys( user ) : null ); relatedContentFetcher.setAvailableCheckDate( new Date() ); relatedContentFetcher.setMaxChildrenLevel( childrenLevel ); relatedContentFetcher.setMaxParentLevel( parentLevel ); relatedContentFetcher.setMaxParentChildrenLevel( 0 ); firstRelatedContentSet = relatedContentFetcher.fetch( contents.getContent( 0 ) ); } finally { RelatedContentFetchTracer.stopTracing( trace, livePortalTraceService ); } relatedContentIntersectionSet.addAll( firstRelatedContentSet.getContentKeys() ); for ( int i = 1; i < contents.getLength(); i++ ) { trace = RelatedContentFetchTracer.startTracing( livePortalTraceService ); try { final RelatedContentFetcher relatedContentFetcher = new RelatedContentFetcher( contentDao, trace ); relatedContentFetcher.setSecurityFilter( user != null ? contentSecurityFilterResolver.resolveGroupKeys( user ) : null ); relatedContentFetcher.setAvailableCheckDate( new Date() ); relatedContentFetcher.setMaxChildrenLevel( childrenLevel ); relatedContentFetcher.setMaxParentLevel( parentLevel ); relatedContentFetcher.setMaxParentChildrenLevel( 0 ); final RelatedContentResultSet otherRelatedContent = relatedContentFetcher.fetch( contents.getContent( i ), true ); relatedContentIntersectionSet.retainAll( otherRelatedContent.getContentKeys() ); if ( relatedContentIntersectionSet.size() == 0 ) { break; } } finally { RelatedContentFetchTracer.stopTracing( trace, livePortalTraceService ); } } final RelatedContentResultSetImpl relatedContent = new RelatedContentResultSetImpl(); for ( ContentKey contentKey : relatedContentIntersectionSet ) { relatedContent.add( firstRelatedContentSet.getRelatedContent( contentKey ) ); } return relatedContent; } public ContentResultSet getPageContent( int menuItemKey ) { MenuItemEntity menuItem = menuItemDao.findByKey( new MenuItemKey( menuItemKey ) ); List<ContentEntity> resultList = new ArrayList<ContentEntity>(); if ( menuItem != null ) { ContentEntity pageContent = menuItem.getContent(); if ( pageContent != null ) { resultList.add( pageContent ); } } return new ContentResultSetNonLazy( resultList, 0, resultList.size() ); } /** * @inheritDoc */ public XMLDocument getAggregatedIndexValues( UserEntity user, String field, Collection<CategoryKey> categoryFilter, boolean includeSubCategories, Collection<ContentTypeKey> contentTypeFilter ) { Set<CategoryKey> categoryKeySet = new HashSet<CategoryKey>(); categoryKeySet.addAll( categoryFilter ); if ( includeSubCategories ) { fillInSubCategories( categoryKeySet, categoryFilter, Integer.MAX_VALUE ); } Collection<GroupKey> userGroups = user != null ? contentSecurityFilterResolver.resolveGroupKeys( user ) : null; AggregatedQuery query = new AggregatedQuery( field ); query.setCategoryFilter( categoryKeySet ); query.setSecurityFilter( userGroups ); query.setContentTypeFilter( contentTypeFilter ); ContentIndexValuesXMLCreator xmlCreator = new ContentIndexValuesXMLCreator(); return xmlCreator.createIndexValuesDocument( field, contentIndexService.query( query ) ); } /** * @inheritDoc */ public XMLDocument getIndexValues( UserEntity user, String field, Collection<CategoryKey> categoryFilter, boolean includeSubCategories, Collection<ContentTypeKey> contentTypeFilter, int index, int count, boolean descOrder ) { Set<CategoryKey> categoryKeySet = new HashSet<CategoryKey>(); categoryKeySet.addAll( categoryFilter ); if ( includeSubCategories ) { fillInSubCategories( categoryKeySet, categoryFilter, Integer.MAX_VALUE ); } Collection<GroupKey> userGroups = user != null ? contentSecurityFilterResolver.resolveGroupKeys( user ) : null; IndexValueQuery query = new IndexValueQuery( field ); query.setCategoryFilter( categoryKeySet ); query.setSecurityFilter( userGroups ); query.setContentTypeFilter( contentTypeFilter ); query.setIndex( index ); query.setCount( count ); query.setDescOrder( descOrder ); ContentIndexValuesXMLCreator xmlCreator = new ContentIndexValuesXMLCreator(); return xmlCreator.createIndexValuesDocument( field, contentIndexService.query( query ) ); } private void fillInSubCategories( Set<CategoryKey> subCategories, Collection<CategoryKey> categoryKeys, int levels ) { if ( ( categoryKeys != null ) && ( categoryKeys.size() > 0 ) && levels > 0 ) { for ( CategoryKey categoryKey : categoryKeys ) { CategoryEntity category = categoryDao.findByKey( categoryKey ); if ( category == null ) { continue; } List<CategoryKey> childrenKeys = category.getChildrenKeys(); fillInSubCategories( subCategories, childrenKeys, levels - 1 ); subCategories.addAll( childrenKeys ); } } } private Set<MenuItemEntity> doGetSectionKeysByMenuItems( Collection<MenuItemKey> menuItemKeys, int levels ) { Set<MenuItemEntity> sections = new HashSet<MenuItemEntity>(); if ( ( levels >= 0 ) && ( menuItemKeys != null ) && ( menuItemKeys.size() > 0 ) ) { if ( levels == 0 ) { levels = Integer.MAX_VALUE; } List<MenuItemEntity> menuItems = menuItemDao.findByKeys( menuItemKeys ); for ( MenuItemEntity menuItem : menuItems ) { if ( menuItem.isSection() ) { sections.add( menuItem ); } Collection<MenuItemEntity> descendantSections = menuItem.getDescendantSections( levels - 1 ); for ( MenuItemEntity section : descendantSections ) { sections.add( section ); } } } return sections; } @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public List<ContentKey> findContentKeysByContentType( ContentTypeEntity contentType ) { return contentDao.findContentKeysByContentType( contentType ); } public List<ContentTypeEntity> getAllContentTypes() { return contentTypeDao.getAll(); } public boolean isContentInUse( List<ContentKey> contentKeys ) { return doIsContentInUse( contentKeys ); } private boolean doIsContentInUse( List<ContentKey> contentKeys ) { int numberOfRelatedParents = contentDao.getNumberOfRelatedParentsByKey( contentKeys ); if ( numberOfRelatedParents > 0 ) { return true; } for ( ContentKey contentKey : contentKeys ) { ContentEntity contentEntity = contentDao.findByKey( contentKey ); if ( contentEntity.hasDirectMenuItemPlacements() ) { return true; } // check if content is published in a section page if ( !contentEntity.getSectionContents().isEmpty() ) { return true; } } return false; } @Autowired public void setContentStorer( ContentStorer value ) { this.contentStorer = value; } private void logEvent( UserKey actor, ContentEntity content, LogType type ) { logEvent( actor, content, type, null ); } private void logEvent( UserKey actor, ContentEntity content, LogType type, SiteKey siteKey ) { String title = content.getMainVersion().getTitle(); String titleKey = " (" + content.getKey().toInt() + ")"; if ( title.length() + titleKey.length() > ContentTitleValidator.CONTENT_TITLE_MAX_LENGTH ) { title = title.substring( 0, ContentTitleValidator.CONTENT_TITLE_MAX_LENGTH - titleKey.length() ); } title = title + titleKey; StoreNewLogEntryCommand command = new StoreNewLogEntryCommand(); command.setUser( actor ); command.setTableKeyValue( content.getKey().toInt() ); command.setTableKey( Table.CONTENT ); command.setType( type ); command.setTitle( title ); command.setPath( content.getPathAsString() ); command.setXmlData( content.getMainVersion().getContentDataAsJDomDocument() ); if ( siteKey != null ) { SiteEntity site = siteDao.findByKey( siteKey ); command.setSite( site ); } logService.storeNew( command ); } }