/* * Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.agiletec.plugins.jacms.aps.system.services.content; import com.agiletec.aps.system.ApsSystemUtils; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.entando.entando.aps.system.services.cache.CacheInfoEvict; import org.entando.entando.aps.system.services.cache.CacheableInfo; import org.entando.entando.aps.system.services.cache.ICacheInfoManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import com.agiletec.aps.system.SystemConstants; import com.agiletec.aps.system.common.entity.ApsEntityManager; import com.agiletec.aps.system.common.entity.IEntityDAO; import com.agiletec.aps.system.common.entity.IEntitySearcherDAO; import com.agiletec.aps.system.common.entity.model.EntitySearchFilter; import com.agiletec.aps.system.common.entity.model.IApsEntity; import com.agiletec.aps.system.exception.ApsSystemException; import com.agiletec.aps.system.services.category.CategoryUtilizer; import com.agiletec.aps.system.services.group.GroupUtilizer; import com.agiletec.aps.system.services.keygenerator.IKeyGeneratorManager; import com.agiletec.aps.system.services.page.PageUtilizer; import com.agiletec.plugins.jacms.aps.system.JacmsSystemConstants; import com.agiletec.plugins.jacms.aps.system.services.content.event.PublicContentChangedEvent; import com.agiletec.plugins.jacms.aps.system.services.content.model.Content; import com.agiletec.plugins.jacms.aps.system.services.content.model.ContentRecordVO; import com.agiletec.plugins.jacms.aps.system.services.content.model.SmallContentType; import com.agiletec.plugins.jacms.aps.system.services.resource.ResourceUtilizer; import java.util.Set; /** * Contents manager. This implements all the methods needed to create and manage the contents. * @author M.Diana - E.Santoboni */ public class ContentManager extends ApsEntityManager implements IContentManager, GroupUtilizer, PageUtilizer, ContentUtilizer, ResourceUtilizer, CategoryUtilizer { private static final Logger _logger = LoggerFactory.getLogger(ContentManager.class); @Override public void init() throws Exception { super.init(); this.createSmallContentTypes(); _logger.debug("{} ready. Initialized {} content types",this.getClass().getName(), super.getEntityTypes().size()); } @Override protected String getConfigItemName() { return JacmsSystemConstants.CONFIG_ITEM_CONTENT_TYPES; } private void createSmallContentTypes() { this._smallContentTypes = new HashMap<String, SmallContentType>(this.getEntityTypes().size()); List<IApsEntity> types = new ArrayList<IApsEntity>(this.getEntityTypes().values()); for (int i=0; i<types.size(); i++) { Content contentPrototype = (Content) types.get(i); SmallContentType smallContentType = new SmallContentType(); smallContentType.setCode(contentPrototype.getTypeCode()); smallContentType.setDescription(contentPrototype.getTypeDescription()); this._smallContentTypes.put(smallContentType.getCode(), smallContentType); } } @Override public void updateEntityPrototype(IApsEntity entityType) throws ApsSystemException { super.updateEntityPrototype(entityType); this.createSmallContentTypes(); } /** * Create a new instance of the requested content. The new content is forked (or cloned) * from the corresponding prototype, and it's returned empty. * @param typeCode The code of the requested (proto)type, as declared in the configuration. * @return The new content. */ @Override public Content createContentType(String typeCode) { Content content = (Content) super.getEntityPrototype(typeCode); return content; } /** * Return a list of the of the content types in a 'small form'. 'Small form' mans that * the contents returned are purged from all unnecessary information (eg. attributes). * @return The list of the types in a (small form). * @deprecated From Entando 4.1.2, use getSmallEntityTypes() method */ @Override public List<SmallContentType> getSmallContentTypes() { List<SmallContentType> smallContentTypes = new ArrayList<SmallContentType>(); smallContentTypes.addAll(this._smallContentTypes.values()); Collections.sort(smallContentTypes); return smallContentTypes; } /** * Return the map of the prototypes of the contents types. * Return a map, index by the code of the type, of the prototypes of the available content types. * @return The map of the prototypes of the content types in a 'SmallContentType' objects. */ @Override public Map<String, SmallContentType> getSmallContentTypesMap() { return this._smallContentTypes; } /** * Return the code of the default page used to display the given content. * The default page is defined at content type level; the type is extrapolated * from the code built following the conventions. * @param contentId The content ID * @return The page code. */ @Override public String getViewPage(String contentId) { Content type = this.getTypeById(contentId); String pageCode = type.getViewPage(); return pageCode; } /** * Return the code of the default model of content. * @param contentId The content code * @return Il requested model code */ @Override public String getDefaultModel(String contentId) { Content type = this.getTypeById(contentId); String defaultModel = type.getDefaultModel(); return defaultModel; } /** * Return the code of the model to be used when the content is rendered in list * @param contentId The code of the content * @return The code of the model */ @Override public String getListModel(String contentId) { Content type = this.getTypeById(contentId); String defaultListModel = type.getListModel(); return defaultListModel; } /** * Return a complete content given its ID; it is possible to choose to return the published * -unmodifiable!- content or the working copy. It also returns the data in the form of XML. * @param id The ID of the content * @param onLine Specifies the type of the content to return: 'true' references the published * content, 'false' the freely modifiable one. * @return The requested content. * @throws ApsSystemException In case of error. */ @Override @Cacheable(value = ICacheInfoManager.DEFAULT_CACHE_NAME, key = "T(com.agiletec.plugins.jacms.aps.system.JacmsSystemConstants).CONTENT_CACHE_PREFIX.concat(#id)", condition = "#onLine") @CacheableInfo(groups = "T(com.agiletec.plugins.jacms.aps.system.services.cache.CmsCacheWrapperManager).getContentCacheGroupsCsv(#id)") public Content loadContent(String id, boolean onLine) throws ApsSystemException { return this.loadContent(id, onLine, false); } @Override @Cacheable(value = ICacheInfoManager.DEFAULT_CACHE_NAME, key = "T(com.agiletec.plugins.jacms.aps.system.JacmsSystemConstants).CONTENT_CACHE_PREFIX.concat(#id)", condition = "#onLine and #cacheable") @CacheableInfo(groups = "T(com.agiletec.plugins.jacms.aps.system.services.cache.CmsCacheWrapperManager).getContentCacheGroupsCsv(#id)") public Content loadContent(String id, boolean onLine, boolean cacheable) throws ApsSystemException { Content content = null; try { ContentRecordVO contentVo = this.loadContentVO(id); content = this.createContent(contentVo, onLine); } catch (ApsSystemException e) { _logger.error("Error while loading content : id {}", id, e); throw new ApsSystemException("Error while loading content : id " + id, e); } return content; } protected Content createContent(ContentRecordVO contentVo, boolean onLine) throws ApsSystemException { Content content = null; try { if (contentVo != null) { String xmlData; if (onLine) { xmlData = contentVo.getXmlOnLine(); } else { xmlData = contentVo.getXmlWork(); } if (xmlData != null) { content = (Content) this.createEntityFromXml(contentVo.getTypeCode(), xmlData); content.setId(contentVo.getId()); content.setTypeCode(contentVo.getTypeCode()); content.setDescription(contentVo.getDescription()); content.setOnLine(contentVo.isOnLine()); content.setMainGroup(contentVo.getMainGroupCode()); if (null == content.getVersion()) { content.setVersion(contentVo.getVersion()); } if (null == content.getFirstEditor()) { content.setFirstEditor(contentVo.getFirstEditor()); } if (null == content.getLastEditor()) { content.setLastEditor(contentVo.getLastEditor()); } if (null == content.getCreated()) { content.setCreated(contentVo.getCreate()); } if (null == content.getLastModified()) { content.setLastModified(contentVo.getModify()); } if (null == content.getStatus()) { content.setStatus(contentVo.getStatus()); } } } } catch (ApsSystemException e) { _logger.error("Error while creating content by vo", e); throw new ApsSystemException("Error while creating content by vo", e); } return content; } /** * Return a {@link ContentRecordVO} (shortly: VO) containing the all content informations * stored in the DB. * @param id The id of the requested content. * @return The VO object corresponding to the wanted content. * @throws ApsSystemException in case of error. */ @Override public ContentRecordVO loadContentVO(String id) throws ApsSystemException { ContentRecordVO contentVo = null; try { contentVo = (ContentRecordVO) this.getContentDAO().loadEntityRecord(id); } catch (Throwable t) { _logger.error("Error while loading content vo : id {}", id, t); throw new ApsSystemException("Error while loading content vo : id " + id, t); } return contentVo; } /** * Save a content in the DB. * @param content The content to add. * @throws ApsSystemException in case of error. */ @Override public void saveContent(Content content) throws ApsSystemException { this.addContent(content); } @Override public void saveContentAndContinue(Content content) throws ApsSystemException { this.addUpdateContent(content, false); } /** * Save a content in the DB. Hopefully this method has no annotation attached * @param content * @throws ApsSystemException */ @Override public void addContent(Content content) throws ApsSystemException { this.addUpdateContent(content, true); } private void addUpdateContent(Content content, boolean updateDate) throws ApsSystemException { try { content.setLastModified(new Date()); if (updateDate) { content.incrementVersion(false); } String status = content.getStatus(); if (null == status) { content.setStatus(Content.STATUS_DRAFT); } else if (status.equals(Content.STATUS_PUBLIC)) { content.setStatus(Content.STATUS_READY); } if (null == content.getId()) { IKeyGeneratorManager keyGenerator = (IKeyGeneratorManager) this.getService(SystemConstants.KEY_GENERATOR_MANAGER); int key = keyGenerator.getUniqueKeyCurrentValue(); String id = content.getTypeCode() + key; content.setId(id); this.getContentDAO().addEntity(content); } else { this.getContentDAO().updateContent(content, updateDate); } } catch (Throwable t) { _logger.error("Error while saving content", t); throw new ApsSystemException("Error while saving content", t); } } /** * Publish a content. * @param content The ID associated to the content to be displayed in the portal. * @throws ApsSystemException in case of error. */ @Override @CacheEvict(value = ICacheInfoManager.DEFAULT_CACHE_NAME, key = "T(com.agiletec.plugins.jacms.aps.system.JacmsSystemConstants).CONTENT_CACHE_PREFIX.concat(#content.id)", condition = "#content.id != null") @CacheInfoEvict(value = ICacheInfoManager.DEFAULT_CACHE_NAME, groups = "T(com.agiletec.plugins.jacms.aps.system.services.cache.CmsCacheWrapperManager).getContentCacheGroupsToEvictCsv(#content.id, #content.typeCode)") public void insertOnLineContent(Content content) throws ApsSystemException { try { content.setLastModified(new Date()); if (null == content.getId()) { content.setCreated(new Date()); this.saveContent(content); } content.incrementVersion(true); content.setStatus(Content.STATUS_PUBLIC); this.getContentDAO().insertOnLineContent(content); int operationEventCode = -1; if (content.isOnLine()) { operationEventCode = PublicContentChangedEvent.UPDATE_OPERATION_CODE; } else { operationEventCode = PublicContentChangedEvent.INSERT_OPERATION_CODE; } this.notifyPublicContentChanging(content, operationEventCode); } catch (Throwable t) { _logger.error("Error while inserting content on line", t); throw new ApsSystemException("Error while inserting content on line", t); } } /** * Return the list of all the content IDs. * @return The list of all the content IDs. * @throws ApsSystemException In case of error * @deprecated Since Entando 2.0 version 2.0.9, use searchId(EntitySearchFilter[]) method */ @Override public List<String> getAllContentsId() throws ApsSystemException { return super.getAllEntityId(); } @Override public void reloadEntityReferences(String entityId) { try { ContentRecordVO contentVo = this.loadContentVO(entityId); Content content = this.createContent(contentVo, true); if (content != null) { this.getContentDAO().reloadPublicContentReferences(content); } Content workcontent = this.createContent(contentVo, false); if (workcontent != null) { this.getContentDAO().reloadWorkContentReferences(workcontent); } _logger.debug("Reloaded content references for content {}", entityId); } catch (Throwable t) { _logger.error("Error while reloading content references for content {}", entityId, t); } } /** * Unpublish a content, preventing it from being displayed in the portal. Obviously * the content itself is not deleted. * @param content the content to unpublish. * @throws ApsSystemException in case of error */ @Override @CacheEvict(value = ICacheInfoManager.DEFAULT_CACHE_NAME, key = "T(com.agiletec.plugins.jacms.aps.system.JacmsSystemConstants).CONTENT_CACHE_PREFIX.concat(#content.id)", condition = "#content.id != null") @CacheInfoEvict(value = ICacheInfoManager.DEFAULT_CACHE_NAME, groups = "T(com.agiletec.plugins.jacms.aps.system.services.cache.CmsCacheWrapperManager).getContentCacheGroupsToEvictCsv(#content.id, #content.typeCode)") public void removeOnLineContent(Content content) throws ApsSystemException { try { content.setLastModified(new Date()); content.incrementVersion(false); if (null != content.getStatus() && content.getStatus().equals(Content.STATUS_PUBLIC)) { content.setStatus(Content.STATUS_READY); } this.getContentDAO().removeOnLineContent(content); this.notifyPublicContentChanging(content, PublicContentChangedEvent.REMOVE_OPERATION_CODE); } catch (Throwable t) { _logger.error("Error while removing onLine content", t); throw new ApsSystemException("Error while removing onLine content", t); } } /** * Notify the modification of a published content. * @param content The modified content. * @param operationCode the operation code to notify. * @exception ApsSystemException in caso of error. */ private void notifyPublicContentChanging(Content content, int operationCode) throws ApsSystemException { PublicContentChangedEvent event = new PublicContentChangedEvent(); event.setContent(content); event.setOperationCode(operationCode); this.notifyEvent(event); } /** * Return the content type from the given ID code. * The code is extracted following the coding conventions: the first three characters * are the type of the code. * @param contentId the content ID whose content type is extracted. * @return The content type requested */ private Content getTypeById(String contentId) { String typeCode = contentId.substring(0, 3); Content type = (Content) super.getEntityTypes().get(typeCode); return type; } /** * Deletes a content from the DB. * @param content The content to delete. * @throws ApsSystemException in case of error. */ @Override @CacheEvict(value = ICacheInfoManager.DEFAULT_CACHE_NAME, key = "T(com.agiletec.plugins.jacms.aps.system.JacmsSystemConstants).CONTENT_CACHE_PREFIX.concat(#content.id)", condition = "#content.id != null") @CacheInfoEvict(value = ICacheInfoManager.DEFAULT_CACHE_NAME, groups = "T(com.agiletec.plugins.jacms.aps.system.services.cache.CmsCacheWrapperManager).getContentCacheGroupsToEvictCsv(#content.id, #content.typeCode)") public void deleteContent(Content content) throws ApsSystemException { try { this.getContentDAO().deleteEntity(content.getId()); } catch (Throwable t) { _logger.error("Error while deleting content {}", content.getId(), t); throw new ApsSystemException("Error while deleting content " + content.getId(), t); } } @Override public List<String> loadPublicContentsId(String contentType, String[] categories, EntitySearchFilter[] filters, Collection<String> userGroupCodes) throws ApsSystemException { return this.loadPublicContentsId(contentType, categories, false, filters, userGroupCodes); } @Override public List<String> loadPublicContentsId(String contentType, String[] categories, boolean orClauseCategoryFilter, EntitySearchFilter[] filters, Collection<String> userGroupCodes) throws ApsSystemException { List<String> contentsId = null; try { contentsId = this.getPublicContentSearcherDAO().loadPublicContentsId(contentType, categories, orClauseCategoryFilter, filters, userGroupCodes); } catch (Throwable t) { _logger.error("Error while loading contents", t); throw new ApsSystemException("Error while loading contents", t); } return contentsId; } @Override public List<String> loadPublicContentsId(String[] categories, EntitySearchFilter[] filters, Collection<String> userGroupCodes) throws ApsSystemException { return this.loadPublicContentsId(categories, false, filters, userGroupCodes); } @Override public List<String> loadPublicContentsId(String[] categories, boolean orClauseCategoryFilter, EntitySearchFilter[] filters, Collection<String> userGroupCodes) throws ApsSystemException { List<String> contentsId = null; try { contentsId = this.getPublicContentSearcherDAO().loadPublicContentsId(categories, orClauseCategoryFilter, filters, userGroupCodes); } catch (Throwable t) { _logger.error("Error while loading contents", t); throw new ApsSystemException("Error while loading contents", t); } return contentsId; } /** * @deprecated From jAPS 2.0 version 2.0.9. Use loadWorkContentsId or loadPublicContentsId */ @Override public List<String> loadContentsId(String[] categories, EntitySearchFilter[] filters, Collection<String> userGroupCodes, boolean onlyOwner) throws ApsSystemException { throw new ApsSystemException("'loadContentsId' method deprecated From jAPS 2.0 version 2.0.9. Use loadWorkContentsId or loadPublicContentsId"); } @Override public List<String> loadWorkContentsId(EntitySearchFilter[] filters, Collection<String> userGroupCodes) throws ApsSystemException { return this.loadWorkContentsId(null, false, filters, userGroupCodes); } @Override public List<String> loadWorkContentsId(String[] categories, EntitySearchFilter[] filters, Collection<String> userGroupCodes) throws ApsSystemException { return this.loadWorkContentsId(categories, false, filters, userGroupCodes); } @Override public List<String> loadWorkContentsId(String[] categories, boolean orClauseCategoryFilter, EntitySearchFilter[] filters, Collection<String> userGroupCodes) throws ApsSystemException { List<String> contentsId = null; try { contentsId = this.getWorkContentSearcherDAO().loadContentsId(categories, orClauseCategoryFilter, filters, userGroupCodes); } catch (Throwable t) { _logger.error("Error while loading work contents", t); throw new ApsSystemException("Error while loading work contents", t); } return contentsId; } @Override public List getPageUtilizers(String pageCode) throws ApsSystemException { List<String> contentIds = null; try { contentIds = this.getContentDAO().getPageUtilizers(pageCode); } catch (Throwable t) { throw new ApsSystemException("Error while loading referenced contents : page " + pageCode, t); } return contentIds; } @Override public List getContentUtilizers(String contentId) throws ApsSystemException { List<String> contentIds = null; try { contentIds = this.getContentDAO().getContentUtilizers(contentId); } catch (Throwable t) { throw new ApsSystemException("Error while loading referenced contents : content " + contentId, t); } return contentIds; } @Override public List getGroupUtilizers(String groupName) throws ApsSystemException { List<String> contentIds = null; try { contentIds = this.getContentDAO().getGroupUtilizers(groupName); } catch (Throwable t) { throw new ApsSystemException("Error while loading referenced contents : group " + groupName, t); } return contentIds; } @Override public List getResourceUtilizers(String resourceId) throws ApsSystemException { List<String> contentIds = null; try { contentIds = this.getContentDAO().getResourceUtilizers(resourceId); } catch (Throwable t) { throw new ApsSystemException("Error while loading referenced contents : resource " + resourceId, t); } return contentIds; } @Override public List getCategoryUtilizers(String resourceId) throws ApsSystemException { List<String> contentIds = null; try { contentIds = this.getContentDAO().getCategoryUtilizers(resourceId); } catch (Throwable t) { throw new ApsSystemException("Error while loading referenced contents : category " + resourceId, t); } return contentIds; } @Override public void reloadCategoryReferences(String categoryCode) { try { this.getContentUpdaterService().reloadCategoryReferences(categoryCode); } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "reloadCategoryReferences"); } } @SuppressWarnings("rawtypes") @Override public List getCategoryUtilizersForReloadReferences(String categoryCode) { List<String> contentIdToReload = new ArrayList<String>(); try { Set<String> contents = this.getContentUpdaterService().getContentsId(categoryCode); if (null != contents) { contentIdToReload.addAll(contents); } } catch (Throwable t) { ApsSystemUtils.logThrowable(t, this, "getCategoryUtilizersForReloadReferences"); } return contentIdToReload; } @Override public boolean isSearchEngineUser() { return true; } /** * Return the DAO which handles all the operations on the contents. * @return The DAO managing the contents. */ protected IContentDAO getContentDAO() { return _contentDao; } /** * Set the DAO which handles the operations on the contents. * @param contentDao The DAO managing the contents. */ public void setContentDAO(IContentDAO contentDao) { this._contentDao = contentDao; } @Override protected IEntitySearcherDAO getEntitySearcherDao() { return this.getWorkContentSearcherDAO(); } @Override protected IEntityDAO getEntityDao() { return this.getContentDAO(); } protected IWorkContentSearcherDAO getWorkContentSearcherDAO() { return _workContentSearcherDAO; } public void setWorkContentSearcherDAO(IWorkContentSearcherDAO workContentSearcherDAO) { this._workContentSearcherDAO = workContentSearcherDAO; } public IPublicContentSearcherDAO getPublicContentSearcherDAO() { return _publicContentSearcherDAO; } public void setPublicContentSearcherDAO(IPublicContentSearcherDAO publicContentSearcherDAO) { this._publicContentSearcherDAO = publicContentSearcherDAO; } protected IContentUpdaterService getContentUpdaterService() { return _contentUpdaterService; } public void setContentUpdaterService(IContentUpdaterService contentUpdaterService) { this._contentUpdaterService = contentUpdaterService; } @Override public IApsEntity getEntity(String entityId) throws ApsSystemException { return this.loadContent(entityId, false); } /** * @deprecated From jAPS 2.0 version 2.0.9, use getStatus() */ @Override public int getState() { return super.getStatus(); } /** * Map of the prototypes of the content types in the so called 'small form', indexed by * the type code. */ private Map<String, SmallContentType> _smallContentTypes; private IContentDAO _contentDao; private IWorkContentSearcherDAO _workContentSearcherDAO; private IPublicContentSearcherDAO _publicContentSearcherDAO; private IContentUpdaterService _contentUpdaterService; }