Java tutorial
/* * Copyright (C) 2005-2012 BetaCONCEPT Limited * * This file is part of Astroboa. * * Astroboa 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 3 of the License, or * (at your option) any later version. * * Astroboa 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. * * You should have received a copy of the GNU Lesser General Public License * along with Astroboa. If not, see <http://www.gnu.org/licenses/>. */ package org.betaconceptframework.astroboa.engine.jcr.dao; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.List; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.version.VersionException; import javax.jcr.version.VersionHistory; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.time.DurationFormatUtils; import org.betaconceptframework.astroboa.api.model.BinaryChannel; import org.betaconceptframework.astroboa.api.model.CalendarProperty; import org.betaconceptframework.astroboa.api.model.CmsProperty; import org.betaconceptframework.astroboa.api.model.CmsRepositoryEntity; import org.betaconceptframework.astroboa.api.model.ContentObject; import org.betaconceptframework.astroboa.api.model.ContentObjectFolder; import org.betaconceptframework.astroboa.api.model.StringProperty; import org.betaconceptframework.astroboa.api.model.definition.CmsPropertyDefinition; import org.betaconceptframework.astroboa.api.model.definition.ContentObjectTypeDefinition; import org.betaconceptframework.astroboa.api.model.exception.CmsConcurrentModificationException; import org.betaconceptframework.astroboa.api.model.exception.CmsException; import org.betaconceptframework.astroboa.api.model.io.FetchLevel; import org.betaconceptframework.astroboa.api.model.io.ImportConfiguration; import org.betaconceptframework.astroboa.api.model.io.ImportConfiguration.PersistMode; import org.betaconceptframework.astroboa.api.model.io.ResourceRepresentationType; import org.betaconceptframework.astroboa.api.model.io.SerializationConfiguration; import org.betaconceptframework.astroboa.api.model.query.CacheRegion; import org.betaconceptframework.astroboa.api.model.query.CmsOutcome; import org.betaconceptframework.astroboa.api.model.query.criteria.ContentObjectCriteria; import org.betaconceptframework.astroboa.api.model.query.render.RenderProperties; import org.betaconceptframework.astroboa.context.AstroboaClientContextHolder; import org.betaconceptframework.astroboa.engine.cache.regions.JcrQueryCacheRegion; import org.betaconceptframework.astroboa.engine.jcr.io.SerializationBean.CmsEntityType; import org.betaconceptframework.astroboa.engine.jcr.query.CalendarInfo; import org.betaconceptframework.astroboa.engine.jcr.query.CmsQueryHandler; import org.betaconceptframework.astroboa.engine.jcr.query.CmsQueryResult; import org.betaconceptframework.astroboa.engine.jcr.renderer.BinaryChannelRenderer; import org.betaconceptframework.astroboa.engine.jcr.renderer.ContentObjectFolderRenderer; import org.betaconceptframework.astroboa.engine.jcr.renderer.ContentObjectRenderer; import org.betaconceptframework.astroboa.engine.jcr.util.CmsRepositoryEntityUtils; import org.betaconceptframework.astroboa.engine.jcr.util.Context; import org.betaconceptframework.astroboa.engine.jcr.util.JcrNodeUtils; import org.betaconceptframework.astroboa.engine.jcr.util.JcrValueUtils; import org.betaconceptframework.astroboa.engine.jcr.util.VersionUtils; import org.betaconceptframework.astroboa.engine.model.lazy.local.LazyComplexCmsPropertyLoader; import org.betaconceptframework.astroboa.model.factory.CmsCriteriaFactory; import org.betaconceptframework.astroboa.model.impl.ComplexCmsPropertyImpl; import org.betaconceptframework.astroboa.model.impl.ContentObjectImpl; import org.betaconceptframework.astroboa.model.impl.SaveMode; import org.betaconceptframework.astroboa.model.impl.item.CmsBuiltInItem; import org.betaconceptframework.astroboa.model.impl.item.ContentObjectProfileItem; import org.betaconceptframework.astroboa.model.impl.item.DefinitionReservedName; import org.betaconceptframework.astroboa.model.impl.item.JcrBuiltInItem; import org.betaconceptframework.astroboa.model.impl.query.CmsOutcomeImpl; import org.betaconceptframework.astroboa.model.impl.query.render.RenderPropertiesImpl; import org.betaconceptframework.astroboa.util.CmsConstants; import org.betaconceptframework.astroboa.util.DateUtils; import org.betaconceptframework.astroboa.util.PropertyPath; import org.betaconceptframework.astroboa.util.TreeDepth; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** * * @author Gregory Chomatas (gchomatas@betaconcept.com) * @author Savvas Triantafyllou (striantafyllou@betaconcept.com) * */ public class ContentDao extends JcrDaoSupport { private final Logger logger = LoggerFactory.getLogger(ContentDao.class); @Autowired private CmsRepositoryEntityUtils cmsRepositoryEntityUtils; @Autowired private ContentObjectDao contentObjectDao; @Autowired private JcrQueryCacheRegion jcrQueryCacheRegion; @Autowired private ContentDefinitionDao contentDefinitionDao; @Autowired private BinaryChannelRenderer binaryChannelRenderer; @Autowired private ContentObjectFolderRenderer contentObjectFolderRenderer; @Autowired private ContentObjectRenderer contentObjectRenderer; @Autowired private VersionUtils versionUtils; @Autowired private LazyComplexCmsPropertyLoader lazyComplexCmsPropertyLoader; @Autowired private CmsQueryHandler cmsQueryHandler; @Autowired private SerializationDao serializationDao; @Autowired private ImportDao importDao; /** * * @param contentObject * @param version * @param lockToken * @param updateLastModificationTime Flag to indicate that profile.modified should be updated * @return */ public ContentObject saveContentObject(Object contentSource, boolean version, String lockToken, boolean updateLastModificationTime, Context context) { long start = System.currentTimeMillis(); if (contentSource == null) { throw new CmsException("Cannot save null content object"); } if (contentSource instanceof String) { logger.debug(" Starting saving content object. First import will take place"); //Use importer to unmarshal String to ContentObject //and to save it as well. //What is happened is that importDao will create a ContentObject //and will pass it to ContentServiceImpl to save it. //It will end up in this method again as a ContentObject //if it passes the check of SecureContentObjectSaveAspect ImportConfiguration configuration = ImportConfiguration.object() .persist(PersistMode.PERSIST_ENTITY_TREE).version(version) .updateLastModificationTime(updateLastModificationTime).build(); return importDao.importContentObject((String) contentSource, configuration); } if (!(contentSource instanceof ContentObject)) { throw new CmsException( "Expecting either String or ContentObject and not " + contentSource.getClass().getName()); } ContentObject contentObject = (ContentObject) contentSource; logger.debug(" Starting saving content object {}", contentObject.getSystemName()); SaveMode saveMode = null; Session session = null; boolean disposeContextWhenSaveIsFinished = false; try { saveMode = cmsRepositoryEntityUtils.determineSaveMode(contentObject); //Populate content node with appropriate information if (context == null) { context = new Context(cmsRepositoryEntityUtils, cmsQueryHandler, getSession()); disposeContextWhenSaveIsFinished = true; } session = context.getSession(); if (StringUtils.isNotBlank(lockToken)) { session.getWorkspace().getLockManager().addLockToken(lockToken); } primaryCheck(contentObject); Node contentObjectNode = null; Calendar modifiedDateBeforeSave = null; switch (saveMode) { case INSERT: contentObjectNode = createNewContentObjectNode(contentObject, session, false); break; case UPDATE: contentObjectNode = cmsRepositoryEntityUtils.retrieveUniqueNodeForContentObject(session, contentObject.getId()); if (contentObjectNode == null) { //User has provided an id for content object. Create a new one with the specified id contentObjectNode = createNewContentObjectNode(contentObject, session, true); } else { //If node is locked and no valid lock token is provided then an exception is thrown session.getWorkspace().getVersionManager().checkout(contentObjectNode.getPath()); modifiedDateBeforeSave = retrieveProfileModifiedFromContentObjectNode(contentObjectNode); checkModificationDateBeforeSave(contentObject, updateLastModificationTime, modifiedDateBeforeSave); } break; default: break; } contentObjectDao.populateContentObjectNode(session, contentObject, contentObjectNode, saveMode, context); //Check if someone has already been saved by another user checkModificationDateAfterSave(contentObject, updateLastModificationTime, contentObjectNode, modifiedDateBeforeSave); session.save(); if (version) { session.getWorkspace().getVersionManager().checkin(contentObjectNode.getPath()); } //This must run after save was successful because these changes cannot not be rolled back ((ComplexCmsPropertyImpl) contentObject.getComplexCmsRootProperty()) .clearCmsPropertyNameWhichHaveBeenLoadedAndRemovedList(); return contentObject; } catch (CmsException e) { throw e; } catch (Throwable e) { throw new CmsException(e); } finally { if (disposeContextWhenSaveIsFinished) { if (context != null) { context.dispose(); context = null; } if (StringUtils.isNotBlank(lockToken)) { try { session.getWorkspace().getLockManager().removeLockToken(lockToken); } catch (RepositoryException e) { logger.error("Lock token " + lockToken + " could not be removed", e); } } } logger.debug(" Saved ContentObject {} in {}", contentObject.getSystemName(), DurationFormatUtils.formatDurationHMS(System.currentTimeMillis() - start)); } } private void checkModificationDateAfterSave(ContentObject contentObject, boolean updateLastModificationTime, Node contentObjectNode, Calendar modifiedDateBeforeSave) throws RepositoryException, ValueFormatException, PathNotFoundException, VersionException, LockException, ConstraintViolationException { if (modifiedDateBeforeSave != null) { //Retrieve modified date now Calendar modifiedDateAfterSave = retrieveProfileModifiedFromContentObjectNode(contentObjectNode); if (modifiedDateAfterSave != null && modifiedDateAfterSave.getTimeInMillis() != modifiedDateBeforeSave.getTimeInMillis()) { String pattern = "dd/MM/yyyy HH:mm:ss.SSSZ"; throw new CmsConcurrentModificationException("Content Object " + contentObject.getId() + "/" + contentObject.getSystemName() + " has been concurrently modified by another user or current user has tried to " + " set a value for profile.modified property. \n" + " profile.modified BEFORE save : " + DateUtils.format(modifiedDateBeforeSave, pattern) + "\n" + " profile.modified AFTER save : " + DateUtils.format(modifiedDateAfterSave, pattern)); } else { //Profile.modified will be updated if updateLastModificationTime is set to true //or profile.modified has no values if (updateLastModificationTime || modifiedDateAfterSave == null) { //Update modification date Calendar modifiedDate = Calendar.getInstance(); //Update jcr node contentObjectNode.getProperty("profile/modified").setValue(modifiedDate); //Update ContentObject if (contentObject.getComplexCmsRootProperty().isChildPropertyLoaded("profile.modified")) { ((CalendarProperty) contentObject.getCmsProperty("profile.modified")) .setSimpleTypeValue(modifiedDate); } } } } } private Calendar checkModificationDateBeforeSave(ContentObject contentObject, boolean updateLastModificationTime, Calendar modifiedDateBeforeSave) { if (modifiedDateBeforeSave != null) { //Make a check if provided ContentObject has a different modified date if (contentObject.getComplexCmsRootProperty() != null && contentObject.getComplexCmsRootProperty().isChildPropertyLoaded("profile.modified")) { CalendarProperty modifiedDateProvidedByUserProperty = (CalendarProperty) contentObject .getCmsProperty("profile.modified"); boolean throwException = false; if (modifiedDateProvidedByUserProperty.hasValues()) { Calendar modifiedDateProvidedByUser = modifiedDateProvidedByUserProperty.getSimpleTypeValue(); if (modifiedDateProvidedByUser == null || modifiedDateProvidedByUser .getTimeInMillis() != modifiedDateBeforeSave.getTimeInMillis()) { throwException = true; } else { return modifiedDateBeforeSave; } } if (throwException) { String pattern = "dd/MM/yyyy HH:mm:ss.SSSZ"; throw new CmsConcurrentModificationException("Content Object " + contentObject.getId() + "/" + contentObject.getSystemName() + " has been concurrently modified by another user or current user has tried to " + " set a value for profile.modified property. \n" + " profile.modified BEFORE save : \t" + DateUtils.format(modifiedDateBeforeSave, pattern) + "(full info " + modifiedDateBeforeSave.toString() + ")\n" + " profile.modified provided by user save : " + (modifiedDateProvidedByUserProperty.hasNoValues() ? " No date " : DateUtils.format(modifiedDateProvidedByUserProperty.getSimpleTypeValue(), pattern)) + "(full info " + modifiedDateProvidedByUserProperty.getSimpleTypeValue() + ")"); } } } return null; } private Calendar retrieveProfileModifiedFromContentObjectNode(Node contentObjectNode) throws RepositoryException, ValueFormatException, PathNotFoundException { if (!contentObjectNode.hasProperty("profile/modified")) { return null; } else { return contentObjectNode.getProperty("profile/modified").getDate(); } } private void primaryCheck(ContentObject contentObject) throws Exception { final String type = contentObject.getContentObjectType(); //Check if this type is defined if (StringUtils.isBlank(type)) throw new CmsException("Invalid type " + type); if (!contentDefinitionDao.hasContentObjectTypeDefinition(type)) throw new CmsException("Unregistered content object type " + type + " in repository " + AstroboaClientContextHolder.getActiveRepositoryId()); } private Node createNewContentObjectNode(ContentObject contentObject, Session session, boolean useProvidedId) throws Exception { String type = contentObject.getContentObjectType(); // Get Type folder node. If it does not exist, it will be Created Node contentTypeFolderNode = JcrNodeUtils.retrieveOrCreateContentTypeFolderNode(session, type); CalendarInfo calendarInfo = new CalendarInfo(Calendar.getInstance()); //Profile will provide year/month/day information String profileCreatedPropertyPath = ContentObjectProfileItem.Created.getItemForQuery().getLocalPart(); //Check that property created is defined for type ContentObjectTypeDefinition typeDefinition = contentDefinitionDao.getContentObjectTypeDefinition(type); if (typeDefinition.hasCmsPropertyDefinition(profileCreatedPropertyPath)) { CalendarProperty createdProperty = (CalendarProperty) contentObject .getCmsProperty(profileCreatedPropertyPath); if (createdProperty == null) { //Should never happen throw new CmsException("Content type " + typeDefinition.getName() + " defines built in property profile.created but could not create property instance"); } Calendar created = (createdProperty.hasNoValues()) ? null : (Calendar) createdProperty.getSimpleTypeValue(); if (created != null) { //In case create date has unset YEAR, MONTH, OR DAY //Calendar Info will set default values calendarInfo = new CalendarInfo(created); } //Update Created date createdProperty.setSimpleTypeValue(calendarInfo.getCalendar()); } else { //Should never happen throw new CmsException("Content type " + typeDefinition.getName() + " does not define built in property profile.created"); } //If minute folder does not exist, it will be Created Node contentObjectParentNode = JcrNodeUtils.createContentObjectParentNode(contentTypeFolderNode, calendarInfo); //Add new node. //Node's name will be the same with typeName Node contentObjectNode = JcrNodeUtils.addContentObjectNode(contentObjectParentNode, type); //Create or use provided astroboa identifier cmsRepositoryEntityUtils.createCmsIdentifier(contentObjectNode, contentObject, useProvidedId); //Add modified date String profileModifiedPropertyPath = ContentObjectProfileItem.Modified.getItemForQuery().getLocalPart(); if (typeDefinition.hasCmsPropertyDefinition(profileModifiedPropertyPath)) { CalendarProperty modifiedProperty = (CalendarProperty) contentObject .getCmsProperty(profileModifiedPropertyPath); if (modifiedProperty.hasNoValues()) { modifiedProperty.setSimpleTypeValue(calendarInfo.getCalendar()); } } else { //Should never happen throw new CmsException("Content type " + typeDefinition.getName() + " does not define built in property profile.modified"); } return contentObjectNode; } public BinaryChannel getBinaryChannelById(String binaryChannelId) { Session session = null; if (StringUtils.isBlank(binaryChannelId)) throw new CmsException("Blank binary channel id"); try { session = getSession(); Node binaryChannelNode = cmsRepositoryEntityUtils.retrieveUniqueNodeForBinaryChannel(session, binaryChannelId); return binaryChannelRenderer.render(binaryChannelNode, true); } catch (RepositoryException ex) { throw new CmsException(ex); } catch (Exception e) { throw new CmsException(e); } } public List<ContentObjectFolder> getRootContentObjectFolders(String locale) { return getRootContentObjectFolders(TreeDepth.ZERO.asInt(), locale); } public List<ContentObjectFolder> getRootContentObjectFolders(int depth, String locale) { Session session = null; try { session = getSession(); Node contentObjectRootNode = JcrNodeUtils.getContentObjectRootNode(session); NodeIterator contentObjectRootFolderNodes = contentObjectRootNode.getNodes(); List<ContentObjectFolder> outcome = new ArrayList<ContentObjectFolder>(); while (contentObjectRootFolderNodes.hasNext()) outcome.add(contentObjectFolderRenderer.render(session, contentObjectRootFolderNodes.nextNode(), depth, false, locale)); return outcome; } catch (RepositoryException ex) { throw new CmsException(ex); } catch (Exception e) { throw new CmsException(e); } } public ContentObjectFolder getContentObjectFolderTree(String parentFolderId, int depth, boolean renderContentObjectIds, String locale) { Session session = null; try { session = getSession(); Node contentObjectFolderNode = JcrNodeUtils.getNodeByNativeRepositoryIdentifier(session, parentFolderId); return contentObjectFolderRenderer.render(session, contentObjectFolderNode, depth, renderContentObjectIds, locale); } catch (RepositoryException ex) { throw new CmsException(ex); } catch (Exception e) { throw new CmsException(e); } } public boolean deleteContentObject(String objectIdOrSystemName) { Session session = null; Context context = null; try { session = getSession(); //Retrieve content object node Node contentObjectNode = getContentObjectNodeByIdOrSystemName(objectIdOrSystemName); if (contentObjectNode != null) { context = new Context(cmsRepositoryEntityUtils, cmsQueryHandler, session); contentObjectDao.removeContentObjectNode(contentObjectNode, true, session, context); session.save(); return true; } logger.info("Object [] does not exist and therefore cannot be deleted", objectIdOrSystemName); return false; //Clear cache //jcrQueryCacheRegion.removeRegion(); } catch (RepositoryException ex) { throw new CmsException(ex); } catch (Exception e) { throw new CmsException(e); } finally { if (context != null) { context.dispose(); context = null; } } } public ContentObject getContentObjectByVersionName(String contentObjectId, String versionName, String locale, CacheRegion cacheRegion) { Session session = null; try { session = getSession(); //Retrieve content object version history node VersionHistory contentObjectVersionHistory = versionUtils.getVersionHistoryForNode(session, contentObjectId); if (contentObjectVersionHistory == null) { return null; } RenderProperties renderProperties = new RenderPropertiesImpl(); //renderProperties.renderValuesForLocale(locale); renderProperties.renderVersionForContentObject(versionName); return contentObjectRenderer.render(session, contentObjectVersionHistory, renderProperties, new HashMap<String, ContentObjectTypeDefinition>(), new HashMap<String, CmsRepositoryEntity>()); } catch (RepositoryException ex) { throw new CmsException(ex); } catch (Exception e) { throw new CmsException(e); } } public <T> T searchContentObjects(ContentObjectCriteria contentObjectCriteria, ResourceRepresentationType<T> contentObjectOutput) { T queryResult = null; boolean queryReturnedAtLeastOneResult = false; ByteArrayOutputStream os = null; try { //Check if criteria is provided if (contentObjectCriteria == null) { return generateEmptyOutcome(contentObjectOutput); } //Initialize null parameters (if any) if (contentObjectOutput == null) { contentObjectOutput = (ResourceRepresentationType<T>) ResourceRepresentationType.CONTENT_OBJECT_LIST; } //Check cache if (contentObjectCriteria.isCacheable()) { queryResult = (T) jcrQueryCacheRegion.getJcrQueryResults(contentObjectCriteria, contentObjectOutput.getTypeAsString()); if (queryResult != null) { return queryResult; } } //User requested Objects as return type if (ResourceRepresentationType.CONTENT_OBJECT_INSTANCE.equals(contentObjectOutput) || ResourceRepresentationType.CONTENT_OBJECT_LIST.equals(contentObjectOutput)) { CmsOutcome<ContentObject> outcome = contentObjectDao.searchContentObjects(contentObjectCriteria, getSession()); //User requested one ContentObject. Throw an exception if more than //one returned if (ResourceRepresentationType.CONTENT_OBJECT_INSTANCE.equals(contentObjectOutput)) { queryResult = (T) returnSingleContentObjectFromOutcome(contentObjectCriteria, outcome); queryReturnedAtLeastOneResult = queryResult != null; } else { //Return type is CmsOutcome. queryResult = (T) outcome; queryReturnedAtLeastOneResult = outcome.getCount() > 0; } } else if (ResourceRepresentationType.XML.equals(contentObjectOutput) || ResourceRepresentationType.JSON.equals(contentObjectOutput)) { //User requested output to be XML or JSON os = new ByteArrayOutputStream(); SerializationConfiguration serializationConfiguration = SerializationConfiguration.object() .prettyPrint(contentObjectCriteria.getRenderProperties().isPrettyPrintEnabled()) .representationType(contentObjectOutput).serializeBinaryContent(false).build(); long numberOfResutls = serializationDao.serializeSearchResults(getSession(), contentObjectCriteria, os, FetchLevel.ENTITY, serializationConfiguration); queryReturnedAtLeastOneResult = numberOfResutls > 0; queryResult = (T) new String(os.toByteArray(), "UTF-8"); } else { throw new CmsException("Invalid resource representation type for content object search results " + contentObjectOutput); } if (contentObjectCriteria.isCacheable()) { String xpathQuery = contentObjectCriteria.getXPathQuery(); if (!StringUtils.isBlank(xpathQuery) && queryReturnedAtLeastOneResult) { jcrQueryCacheRegion.cacheJcrQueryResults(contentObjectCriteria, queryResult, contentObjectCriteria.getRenderProperties(), contentObjectOutput.getTypeAsString()); } } return queryResult; } catch (RepositoryException ex) { throw new CmsException(ex); } catch (Exception e) { throw new CmsException(e); } finally { IOUtils.closeQuietly(os); } } private ContentObject returnSingleContentObjectFromOutcome(ContentObjectCriteria contentObjectCriteria, CmsOutcome<ContentObject> outcome) { //User set limit to 1. Return content object found //a result is returned if (outcome.getLimit() == 1) { if (CollectionUtils.isNotEmpty(outcome.getResults())) { return outcome.getResults().get(0); } else { return null; } } else { //User specified limit different than 1 (either no limit or greater than 1) if (outcome.getCount() > 1) { throw new CmsException( outcome.getCount() + " content objects matched criteria, user has specified limit " + contentObjectCriteria.getLimit() + " but she also requested return type to be a single ContentObject."); } else { if (CollectionUtils.isNotEmpty(outcome.getResults())) { return outcome.getResults().get(0); } else { return null; } } } } private <T> T generateEmptyOutcome(ResourceRepresentationType<T> contentObjectOutput) { if (contentObjectOutput != null && contentObjectOutput.equals(ResourceRepresentationType.CONTENT_OBJECT_LIST)) { return (T) new CmsOutcomeImpl<T>(0, 0, 0); } else { return null; } } public String lockContentObject(String contentObjectId) { Session session = null; try { session = getSession(); Node contentObjectNode = cmsRepositoryEntityUtils.retrieveUniqueNodeForContentObject(session, contentObjectId); if (contentObjectNode == null) { return null; } //Deep lock and open-scoped if (!contentObjectNode.isLocked()) session.getWorkspace().getLockManager().lock(contentObjectNode.getPath(), true, false, Long.MAX_VALUE, ""); String lockToken = session.getWorkspace().getLockManager().getLock(contentObjectNode.getPath()) .getLockToken(); if (lockToken != null) { session.getWorkspace().getVersionManager().checkout(contentObjectNode.getPath()); contentObjectNode.setProperty(DefinitionReservedName.Locktoken.getJcrName(), lockToken); } else { //Lock Operation may have failed. Try get locktoken from contentObjectNode if (contentObjectNode.hasProperty(DefinitionReservedName.Locktoken.getJcrName())) lockToken = contentObjectNode.getProperty(DefinitionReservedName.Locktoken.getJcrName()) .getString(); } session.save(); return lockToken; } catch (RepositoryException ex) { throw new CmsException(ex); } catch (Exception e) { throw new CmsException(e); } } public void removeLockFromContentObject(String contentObjectId, String lockToken) { Session session = null; try { session = getSession(); Node contentObjectNode = cmsRepositoryEntityUtils.retrieveUniqueNodeForContentObject(session, contentObjectId); if (contentObjectNode != null && contentObjectNode.isLocked()) { //Deep lock and open-scoped session.getWorkspace().getLockManager().addLockToken(lockToken); session.getWorkspace().getLockManager().unlock(contentObjectNode.getPath()); //Unset contentObject equivalent property if (contentObjectNode.hasProperty(DefinitionReservedName.Locktoken.getJcrName())) { session.getWorkspace().getVersionManager().checkout(contentObjectNode.getPath()); contentObjectNode.getProperty(DefinitionReservedName.Locktoken.getJcrName()).remove(); } session.save(); } } catch (RepositoryException ex) { throw new CmsException(ex); } catch (Exception e) { throw new CmsException(e); } } public boolean isContentObjectLocked(String contentObjectId) { Session session = null; try { session = getSession(); Node contentObjectNode = cmsRepositoryEntityUtils.retrieveUniqueNodeForContentObject(session, contentObjectId); if (contentObjectNode == null) { return false; } return contentObjectNode.isLocked(); } catch (RepositoryException ex) { throw new CmsException(ex); } catch (Exception e) { throw new CmsException(e); } } public void increaseContentObjectViewCounter(String contentObjectId, long counter) { Session session = null; try { session = getSession(); Node contentObjectNode = cmsRepositoryEntityUtils.retrieveUniqueNodeForContentObject(session, contentObjectId); if (contentObjectNode == null) { logger.warn("Unable to find content object with id " + contentObjectId + ". Property 'viewCounter' is not increased"); } else { session.getWorkspace().getVersionManager().checkout(contentObjectNode.getPath()); contentObjectDao.increaseViewCounter(contentObjectNode, counter); session.save(); } } catch (RepositoryException ex) { logger.error("", ex); throw new CmsException(ex); } catch (Exception e) { logger.error("", e); throw new CmsException(e); } } public List<CmsProperty<?, ?>> loadChildCmsProperty(String childPropertyName, String parentComplexCmsPropertyDefinitionFullPath, String jcrNodeUUIDWhichCorrespondsToParentComplexCmsProperty, String jcrNodeUUIDWhichCorrespondsToContentObejct, RenderProperties renderProperties) throws Exception { CmsPropertyDefinition currentChildPropertyDefinition = contentDefinitionDao.getCmsPropertyDefinition( PropertyPath.createFullPropertyPath(parentComplexCmsPropertyDefinitionFullPath, childPropertyName)); //Since definition does not exist and its parent is a content object type, which //derives from the fact that parent full path has only one level //check if child refers to an aspect if (currentChildPropertyDefinition == null && !parentComplexCmsPropertyDefinitionFullPath.contains(CmsConstants.PERIOD_DELIM)) { currentChildPropertyDefinition = contentDefinitionDao.getAspectDefinition(childPropertyName); //At this point if such a definition exists we cannot tell that the aspect found is actually defined //for the content object which contains parent complex property. //This will be detected once the created template is actually added to the content object } if (currentChildPropertyDefinition == null) { logger.warn( "No cms property definition is provided for property {}.{} . ( parent node UUID {} and content object node UUID {} )", new Object[] { parentComplexCmsPropertyDefinitionFullPath, childPropertyName, jcrNodeUUIDWhichCorrespondsToParentComplexCmsProperty, jcrNodeUUIDWhichCorrespondsToContentObejct }); return null; } else { return lazyComplexCmsPropertyLoader.renderChildProperty(currentChildPropertyDefinition, jcrNodeUUIDWhichCorrespondsToParentComplexCmsProperty, jcrNodeUUIDWhichCorrespondsToContentObejct, renderProperties, getSession(), null); } } public void moveAspectToNativePropertyForAllContentObjectsOFContentType(String aspect, String newPropertyName, String contentType) { if (StringUtils.isBlank(aspect)) { throw new CmsException("No aspect is provided"); } if (StringUtils.isBlank(contentType)) { throw new CmsException("No content type is provided"); } Session session = null; try { session = getSession(); ContentObjectCriteria contentObjectCriteria = CmsCriteriaFactory.newContentObjectCriteria(contentType); CmsQueryResult contentObjectResults = cmsQueryHandler.getNodesFromXPathQuery(session, contentObjectCriteria, true); if (contentObjectResults.getTotalRowCount() > 0) { NodeIterator contentObjectNodesIterator = (NodeIterator) contentObjectResults.getNodeIterator(); int count = 0; while (contentObjectNodesIterator.hasNext()) { Node contentObjectNode = contentObjectNodesIterator.nextNode(); if (contentObjectNode.isNodeType(CmsBuiltInItem.StructuredContentObject.getJcrName())) { if (contentObjectNode.hasProperty(CmsBuiltInItem.Aspects.getJcrName())) { Value[] values = contentObjectNode.getProperty(CmsBuiltInItem.Aspects.getJcrName()) .getValues(); logger.info("ContentObjectNode " + contentObjectNode.getPath() + " has aspects " + printValues(values)); if (values != null) { for (Value value : values) { if (value.getString() != null && value.getString().equals(aspect)) { //Aspect found. Remove it from array JcrValueUtils.removeValue(contentObjectNode, CmsBuiltInItem.Aspects, session.getValueFactory().createValue(aspect), true); count++; if (contentObjectNode.hasProperty(CmsBuiltInItem.Aspects.getJcrName())) { logger.info( "After removal ContentObjectNode " + contentObjectNode.getPath() + " has aspects " + printValues(values)); } else { logger.info("After removal ContentObjectNode " + contentObjectNode.getPath() + " does not have any aspect "); } //Refactor the property only if the provided is different if (StringUtils.isNotBlank(newPropertyName) && !newPropertyName.equals(aspect)) { if (!contentObjectNode.hasNode(aspect)) { logger.warn("Although aspect " + aspect + " was found in " + CmsBuiltInItem.Aspects.getJcrName() + " no child property named after aspect " + " was found. Check further if there is a bug"); } else { NodeIterator aspectNodes = contentObjectNode.getNodes(aspect); if (aspectNodes.getSize() > 1) { logger.warn( "There are more than one child properties named after aspect " + aspect + ". This is however not the case." + "Check further if there is a bug. Refactoring will taking place normally as by now this property is a native property" + " and therefore can have multiple occurences. Make sure this is depicted in content type definition of " + contentType); } while (aspectNodes.hasNext()) { Node aspectNode = aspectNodes.nextNode(); session.move(aspectNode.getPath(), contentObjectNode.getPath() + CmsConstants.FORWARD_SLASH + newPropertyName); } } } break; } } } } } else { throw new CmsException("In a query for content objects of type " + contentType + " this node " + contentObjectNode.getPath() + " found in results but it is not a structured content object node"); } } session.save(); logger.info("Successfully move aspect in " + count + " content objects"); } } catch (RepositoryException ex) { throw new CmsException(ex); } catch (Exception e) { throw new CmsException(e); } } private String printValues(Value[] values) throws Exception { if (values == null) { return ""; } StringBuilder stringBuilder = new StringBuilder(); for (Value value : values) { stringBuilder.append(value.getString() + " "); } return stringBuilder.toString(); } public Node getContentObjectNodeByIdOrSystemName(String contentObjectIdOrSystemName) { try { Node contentObjectNode = null; if (CmsConstants.UUIDPattern.matcher(contentObjectIdOrSystemName).matches()) { contentObjectNode = cmsRepositoryEntityUtils.retrieveUniqueNodeForContentObject(getSession(), contentObjectIdOrSystemName); if (contentObjectNode != null) { return contentObjectNode; } } else { ContentObjectCriteria contentObjectCriteria = CmsCriteriaFactory.newContentObjectCriteria(); contentObjectCriteria.addSystemNameEqualsCriterion(contentObjectIdOrSystemName); contentObjectCriteria.setOffsetAndLimit(0, 1); CmsQueryResult nodes = cmsQueryHandler.getNodesFromXPathQuery(getSession(), contentObjectCriteria, true); if (nodes.getTotalRowCount() > 0) { return ((NodeIterator) nodes.getNodeIterator()).nextNode(); } } return null; } catch (Exception e) { throw new CmsException(e); } } @SuppressWarnings("unchecked") public <T> T serializeContentObject(String contentObjectIdOrSystemName, CacheRegion cacheRegion, ResourceRepresentationType<T> contentObjectOutput, List<String> propertyPathsToInclude, FetchLevel fetchLevel, boolean serializeBinaryContent, boolean prettyPrint) { ByteArrayOutputStream os = null; try { //Default values if (contentObjectOutput == null) { contentObjectOutput = (ResourceRepresentationType<T>) ResourceRepresentationType.CONTENT_OBJECT_INSTANCE; } T contentObject = null; //Check cache contentObject = getContentObjectFromcache(contentObjectIdOrSystemName, cacheRegion, contentObjectOutput); if (contentObject != null) { return contentObject; } //Content Object is not in the cache //Continue with the default values if (fetchLevel == null) { fetchLevel = FetchLevel.ENTITY; } propertyPathsToInclude = generateIfNecessaryPropertyPathsWhoseValuesWillBeIncludedInSerialization( fetchLevel, propertyPathsToInclude, contentObjectOutput); Node contentObjectNode = getContentObjectNodeByIdOrSystemName(contentObjectIdOrSystemName); if (contentObjectNode == null) { return generateEmptyOutcome(contentObjectOutput); } if (ResourceRepresentationType.CONTENT_OBJECT_INSTANCE.equals(contentObjectOutput) || ResourceRepresentationType.CONTENT_OBJECT_LIST.equals(contentObjectOutput)) { RenderProperties renderProperties = new RenderPropertiesImpl(); if (FetchLevel.FULL == fetchLevel) { renderProperties.renderAllContentObjectProperties(true); } contentObject = (T) contentObjectRenderer.render(getSession(), contentObjectNode, renderProperties, new HashMap<String, ContentObjectTypeDefinition>(), new HashMap<String, CmsRepositoryEntity>()); //Load properties if (CollectionUtils.isNotEmpty(propertyPathsToInclude)) { for (String propertyPath : propertyPathsToInclude) { ((ContentObject) contentObject).getCmsProperty(propertyPath); } } //Return appropriate type if (ResourceRepresentationType.CONTENT_OBJECT_LIST.equals(contentObjectOutput)) { //Return type is CmsOutcome. CmsOutcome<ContentObject> outcome = new CmsOutcomeImpl<ContentObject>(1, 0, 1); outcome.getResults().add((ContentObject) contentObject); contentObject = (T) outcome; } } else if (ResourceRepresentationType.XML.equals(contentObjectOutput) || ResourceRepresentationType.JSON.equals(contentObjectOutput)) { os = new ByteArrayOutputStream(); SerializationConfiguration serializationConfiguration = SerializationConfiguration.object() .prettyPrint(prettyPrint).representationType(contentObjectOutput) .serializeBinaryContent(serializeBinaryContent).build(); serializationDao.serializeCmsRepositoryEntity(contentObjectNode, os, CmsEntityType.OBJECT, propertyPathsToInclude, fetchLevel, true, serializationConfiguration); contentObject = (T) new String(os.toByteArray(), "UTF-8"); } else { throw new CmsException("Unsupported resource representation type for content object serialization " + contentObjectOutput); } if (cacheRegion != null) { jcrQueryCacheRegion.cacheContentObject(contentObjectIdOrSystemName, contentObject, cacheRegion, contentObjectOutput.getTypeAsString()); } return contentObject; } catch (RepositoryException ex) { throw new CmsException(ex); } catch (Exception e) { throw new CmsException(e); } finally { IOUtils.closeQuietly(os); } } private List<String> generateIfNecessaryPropertyPathsWhoseValuesWillBeIncludedInSerialization( FetchLevel fetchLevel, List<String> propertyPathsToInclude, ResourceRepresentationType<?> contentObjectOutput) { //Property Paths to be included in the serialization are ignored id FetchLevel is FULL if (fetchLevel == FetchLevel.FULL) { return null; } if (CollectionUtils.isEmpty(propertyPathsToInclude)) { //FetchLevel is either ENTITY or ENTITY_AND_CHILDREN but user did not specify any properties at all. //In this case profile.title is returned and owner propertyPathsToInclude = new ArrayList<String>(); propertyPathsToInclude.add("profile.title"); //Owner is always serialized when type is CONTENT_OBJECT_INSTANCE or CONTENT_OBJECT_LIST //as it is a special case of a property //However in the cases of XML or JSON, it should be specified as a regular one if (ResourceRepresentationType.XML.equals(contentObjectOutput) || ResourceRepresentationType.JSON.equals(contentObjectOutput)) { propertyPathsToInclude.add(CmsConstants.OWNER_ELEMENT_NAME); } } return propertyPathsToInclude; } private <T> T getContentObjectFromcache(String contentObjectIdOrSystemName, CacheRegion cacheRegion, ResourceRepresentationType<T> contentObjectOutput) throws Exception { if (cacheRegion != null) { return (T) jcrQueryCacheRegion.getContentObjectFromCache(contentObjectIdOrSystemName, cacheRegion, contentObjectOutput.getTypeAsString()); } return null; } /** * @param contetObjectId * @return */ public ContentObject copyContentObject(String contentObjectId) { if (StringUtils.isBlank(contentObjectId)) { return null; } ContentObject contentObjectToBeCopied = serializeContentObject(contentObjectId, CacheRegion.NONE, ResourceRepresentationType.CONTENT_OBJECT_INSTANCE, null, FetchLevel.FULL, true, false); if (contentObjectToBeCopied == null) { logger.warn("Could not retrieve content object with id {}. Copy operation cannot continue", contentObjectId); return null; } ((ContentObjectImpl) contentObjectToBeCopied).clean(); int index = 0; String systemNameToSearch = null; if (StringUtils.isNotBlank(contentObjectToBeCopied.getSystemName())) { systemNameToSearch = new String(contentObjectToBeCopied.getSystemName().getBytes()); if (systemNameToSearch.startsWith("copy")) { systemNameToSearch = systemNameToSearch.replaceFirst("copy[0-9]*", ""); } //Locate other copies in order to provide the correct copy index ContentObjectCriteria contentObjectCriteria = CmsCriteriaFactory.newContentObjectCriteria(); contentObjectCriteria.addSystemNameContainsCriterion("*" + systemNameToSearch); contentObjectCriteria.setOffsetAndLimit(0, 0); contentObjectCriteria.setCacheable(CacheRegion.NONE); CmsOutcome<ContentObject> outcome = searchContentObjects(contentObjectCriteria, ResourceRepresentationType.CONTENT_OBJECT_LIST); if (outcome != null) { index = (int) outcome.getCount(); } } String newIndexAsString = index == 0 ? "" : String.valueOf(index + 1); //Adjust systemName if (contentObjectToBeCopied.getSystemName() == null) { contentObjectToBeCopied.setSystemName("copy" + newIndexAsString); } else { if (contentObjectToBeCopied.getSystemName().startsWith("copy")) { contentObjectToBeCopied.setSystemName(contentObjectToBeCopied.getSystemName() .replaceFirst("copy[0-9]*", "copy" + newIndexAsString)); } else { contentObjectToBeCopied .setSystemName("copy" + newIndexAsString + contentObjectToBeCopied.getSystemName()); } } //Adjust title StringProperty titleProperty = (StringProperty) contentObjectToBeCopied.getCmsProperty("profile.title"); String title = titleProperty.getSimpleTypeValue(); boolean titleHasChanged = false; for (int i = 1; i <= index; i++) { if (title.endsWith(" " + String.valueOf(i))) { titleProperty.setSimpleTypeValue( StringUtils.substringBeforeLast(title, String.valueOf(i)) + newIndexAsString); titleHasChanged = true; break; } } if (!titleHasChanged) { titleProperty.setSimpleTypeValue(title + " " + newIndexAsString); } return contentObjectToBeCopied; } public boolean valueForPropertyExists(String propertyPath, String jcrNodeUUIDWhichCorrespondsToParentComplexCmsProperty) throws Exception { return lazyComplexCmsPropertyLoader.valueForPropertyExists(propertyPath, jcrNodeUUIDWhichCorrespondsToParentComplexCmsProperty, getSession()); } public List<ContentObject> saveContentObjectResourceCollection(Object contentSource, boolean version, boolean updateLastModificationTime, String lockToken) { long start = System.currentTimeMillis(); if (contentSource == null) { return new ArrayList<ContentObject>(); } if (contentSource instanceof String) { logger.debug(" Starting saving content object resource collection."); //Use importer to unmarshal String to ContentObject //and to save it as well. //What is happened is that importDao will create a ContentObject //and will pass it to ContentServiceImpl to save it. //It will end up in this method again as a ContentObject //if it passes the check of SecureContentObjectSaveAspect ImportConfiguration configuration = ImportConfiguration.object() .persist(PersistMode.PERSIST_ENTITY_TREE).version(version) .updateLastModificationTime(updateLastModificationTime).build(); return importDao.importResourceCollection((String) contentSource, configuration); } if (!(contentSource instanceof List)) { throw new CmsException( "Expecting either String or List<ContentObject> and not " + contentSource.getClass().getName()); } logger.debug(" Starting saving content object collection"); Session session = null; Context context = null; List<ContentObject> contentObjects = (List<ContentObject>) contentSource; try { context = new Context(cmsRepositoryEntityUtils, cmsQueryHandler, getSession()); session = context.getSession(); if (StringUtils.isNotBlank(lockToken)) { session.getWorkspace().getLockManager().addLockToken(lockToken); } for (ContentObject contentObject : contentObjects) { contentObject = saveContentObject(contentObject, version, lockToken, updateLastModificationTime, context); } session.save(); return contentObjects; } catch (CmsException e) { throw e; } catch (Throwable e) { throw new CmsException(e); } finally { if (context != null) { context.dispose(); context = null; } if (StringUtils.isNotBlank(lockToken)) { try { session.getWorkspace().getLockManager().removeLockToken(lockToken); } catch (RepositoryException e) { logger.error("Lock token " + lockToken + " could not be removed", e); } } logger.debug(" Saved ContentObject collection in {}", DurationFormatUtils.formatDurationHMS(System.currentTimeMillis() - start)); } } public byte[] getBinaryChannelContent(String jcrNodeUUIDWhichCorrespondsToTheBinaryChannel) { Session session = null; if (StringUtils.isBlank(jcrNodeUUIDWhichCorrespondsToTheBinaryChannel)) throw new CmsException("Blank binary channel id"); try { session = getSession(); Node binaryChannelNode = cmsRepositoryEntityUtils.retrieveUniqueNodeForBinaryChannel(session, jcrNodeUUIDWhichCorrespondsToTheBinaryChannel); if (binaryChannelNode == null) { logger.warn("Binary Channel Id {} does not correspond to a valid JCR node"); return null; } if (binaryChannelNode.hasProperty(JcrBuiltInItem.JcrData.getJcrName())) { return (byte[]) JcrValueUtils.getObjectValue( binaryChannelNode.getProperty(JcrBuiltInItem.JcrData.getJcrName()).getValue()); } else { return null; } } catch (RepositoryException ex) { throw new CmsException(ex); } catch (Exception e) { throw new CmsException(e); } } }