/*
* 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.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.version.Version;
import javax.jcr.version.VersionHistory;
import javax.jcr.version.VersionIterator;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.betaconceptframework.astroboa.api.model.CmsRepositoryEntity;
import org.betaconceptframework.astroboa.api.model.ContentObject;
import org.betaconceptframework.astroboa.api.model.ValueType;
import org.betaconceptframework.astroboa.api.model.definition.CmsPropertyDefinition;
import org.betaconceptframework.astroboa.api.model.definition.ComplexCmsPropertyDefinition;
import org.betaconceptframework.astroboa.api.model.definition.ContentObjectTypeDefinition;
import org.betaconceptframework.astroboa.api.model.exception.CmsException;
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.engine.cache.regions.JcrQueryCacheRegion;
import org.betaconceptframework.astroboa.engine.database.dao.CmsRepositoryEntityAssociationDao;
import org.betaconceptframework.astroboa.engine.jcr.PrototypeFactory;
import org.betaconceptframework.astroboa.engine.jcr.query.CmsQueryHandler;
import org.betaconceptframework.astroboa.engine.jcr.query.CmsQueryResult;
import org.betaconceptframework.astroboa.engine.jcr.query.CmsScoreNode;
import org.betaconceptframework.astroboa.engine.jcr.query.CmsScoreNodeIterator;
import org.betaconceptframework.astroboa.engine.jcr.query.CmsScoreNodeIteratorUsingJcrRangeIterator;
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.EntityAssociationDeleteHelper;
import org.betaconceptframework.astroboa.engine.jcr.util.JcrNodeUtils;
import org.betaconceptframework.astroboa.engine.jcr.util.PopulateContentObject;
import org.betaconceptframework.astroboa.engine.jcr.util.RendererUtils;
import org.betaconceptframework.astroboa.model.factory.CmsCriteriaFactory;
import org.betaconceptframework.astroboa.model.impl.SaveMode;
import org.betaconceptframework.astroboa.model.impl.item.CmsBuiltInItem;
import org.betaconceptframework.astroboa.model.impl.item.CmsReadOnlyItem;
import org.betaconceptframework.astroboa.model.impl.item.JcrBuiltInItem;
import org.betaconceptframework.astroboa.model.impl.query.CmsOutcomeImpl;
import org.betaconceptframework.astroboa.model.jaxb.MarshalUtils;
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 ContentObjectDao {
private final Logger logger = LoggerFactory.getLogger(ContentObjectDao.class);
@Autowired
private ContentDefinitionDao contentDefinitionDao;
@Autowired
private CmsRepositoryEntityUtils cmsRepositoryEntityUtils;
@Autowired
private ContentObjectRenderer contentObjectRenderer;
@Autowired
private RendererUtils rendererUtils;
@Autowired
private JcrQueryCacheRegion jcrQueryCacheRegion;
@Autowired
private CmsQueryHandler cmsQueryHandler;
@Autowired
private PrototypeFactory prototypeFactory;
@Autowired
private CmsRepositoryEntityAssociationDao cmsRepositoryEntityAssociationDao;
public void populateContentObjectNode(Session session, ContentObject contentObject, Node contentObjectNode, SaveMode saveMode, Context context) throws Exception{
ContentObjectTypeDefinition contentObjectTypeDefinition = contentDefinitionDao.getContentObjectTypeDefinition(contentObject.getContentObjectType());
PopulateContentObject populateContentObject = prototypeFactory.newPopulateContentObject();
populateContentObject.setContentObject(contentObject);
populateContentObject.setContentObjectNode(contentObjectNode);
populateContentObject.setSaveMode(saveMode);
populateContentObject.setSession(session);
populateContentObject.setContext(context);
populateContentObject.populate(contentObjectTypeDefinition);
}
public void removeContentObjectNode(Node contentObjectNode, boolean deleteVersionHistory, Session session, Context context) throws RepositoryException {
//Remove References
EntityAssociationDeleteHelper<ContentObject> contentObjectRemover =
new EntityAssociationDeleteHelper<ContentObject>(session,cmsRepositoryEntityAssociationDao, context);
contentObjectRemover.setCmsRepositoryEntityIdToBeRemoved(cmsRepositoryEntityUtils.getCmsIdentifier(contentObjectNode));
contentObjectRemover.removeOrReplaceAllReferences(ContentObject.class);
//Get a reference to content object's history
VersionHistory versionHistory = session.getWorkspace().getVersionManager().getVersionHistory(contentObjectNode.getPath());
deleteVersionHistory(versionHistory);
// remove node
contentObjectNode.remove();
}
private void deleteVersionHistory(VersionHistory versionHistory) throws RepositoryException {
VersionIterator versions = versionHistory.getAllVersions();
//Version History is a protected node. Therefore cannot be removed
//versionHistory.remove();
//Try to remove as much versions as we can
while (versions.hasNext())
{
Version currentVersion = versions.nextVersion();
String versionName = currentVersion.getName();
//Jcr:rootVersion is a protected node. Therefore cannot be removed
//Base Version cannot be removed as well as it is referenced bu VersionHistory.jcr:baseVersion
if (! versionName.equals(JcrBuiltInItem.JcrRootVersion.getJcrName()) && currentVersion.getReferences().getSize() == 0)
versionHistory.removeVersion(versionName);
}
}
public ContentObjectCriteria copyContentObjectCriteria(Session session, ContentObjectCriteria contentObjectCriteria) throws Exception {
ContentObjectCriteria localContentObjectCriteria = CmsCriteriaFactory.newContentObjectCriteria();
if (contentObjectCriteria != null){
contentObjectCriteria.copyTo(localContentObjectCriteria);
}
return localContentObjectCriteria;
}
public void increaseViewCounter(Node contentObjectNode, long counter) throws RepositoryException{
Node statisticNode = null;
if (contentObjectNode.hasNode("statistic"))
statisticNode = contentObjectNode.getNode("statistic");
else{
statisticNode = JcrNodeUtils.addNodeForComplexCmsProperty(contentObjectNode, "statistic");
statisticNode.setProperty(CmsBuiltInItem.CmsIdentifier.getJcrName(), statisticNode.getIdentifier());
}
if (statisticNode == null)
logger.warn("Could not find or create statistic node for contentObject {}",contentObjectNode.getPath());
else{
if (statisticNode.hasProperty(CmsReadOnlyItem.ViewCounter.getJcrName())){
counter = statisticNode.getProperty(CmsReadOnlyItem.ViewCounter.getJcrName()).getLong() + counter;
}
statisticNode.setProperty(CmsReadOnlyItem.ViewCounter.getJcrName(), counter);
}
}
private <T> CmsOutcome<T> createCmsOutcome(Session session, ContentObjectCriteria contentObjectCriteria) throws Exception {
//Keep render properties before rendering since during render they may change
RenderProperties renderPropertiesFromCriteria = contentObjectCriteria.getRenderProperties();
RenderProperties renderPropertiesForCache = rendererUtils.copyRenderPropertiesFromCriteria(contentObjectCriteria);
//CmsQueryResultSecurityHandler cmsQueryResultSecurityHandler = new CmsQueryResultSecurityHandler(cmsQueryHandler,
// contentObjectCriteria,session, accessManager);
CmsQueryResult cmsQueryResult = cmsQueryHandler.getNodesFromXPathQuery(session, contentObjectCriteria, false);
//Create outcome
CmsOutcome<T> outcome = new CmsOutcomeImpl<T>(
cmsQueryResult.getTotalRowCount(),
contentObjectCriteria.getOffset(),
contentObjectCriteria.getLimit()
);
//Limit is zero, that is user has requested no content object
//JCR implementation (Jackrabbit) takes limit into account only if it is
//a value greater than 0. Since for Astroboa value -1 stands for unlimited
// and value 0 stands for no returned value
//we have to stop rendering if limit is 0 as Jackrabbit
//will return a result when calling iterator.hasNext()
if (contentObjectCriteria.getLimit() != 0){
// Now order row iterator using order properties
CmsScoreNodeIterator orderedResults = new CmsScoreNodeIteratorUsingJcrRangeIterator(cmsQueryResult.getRowIterator());
Map<String, ContentObjectTypeDefinition> cachedContentObjectTypeDefinitions = new HashMap<String, ContentObjectTypeDefinition>();
Map<String, CmsRepositoryEntity> cachedCmsRepositoryEntities = new HashMap<String, CmsRepositoryEntity>();
List<String> projections = contentObjectCriteria.getPropertyPathsWhichWillBePreLoaded();
//Render result.
while (orderedResults.hasNext()){
CmsScoreNode nextScoreNode = orderedResults.nextCmsScoreNode();
T contentObject = (T) contentObjectRenderer.render(session, nextScoreNode.getJcrNode(),
renderPropertiesFromCriteria, cachedContentObjectTypeDefinitions, cachedCmsRepositoryEntities);
//Preload properties
if (CollectionUtils.isNotEmpty(projections)){
loadProjectedPaths(projections, (ContentObject) contentObject);
}
((CmsOutcomeImpl)outcome).addResult(contentObject, nextScoreNode.getScore());
}
}
//((CmsOutcomeImpl<CmsRankedOutcome<ContentObject>>)outcome).setCount(cmsQueryResultSecurityHandler.getSize());
//Cache results
if (contentObjectCriteria.isCacheable()){
String xpathQuery = contentObjectCriteria.getXPathQuery();
if (!StringUtils.isBlank(xpathQuery) && outcome != null && outcome.getCount() > 0){
jcrQueryCacheRegion.cacheJcrQueryResults(contentObjectCriteria,
outcome, renderPropertiesForCache);
}
}
return outcome;
}
private void loadProjectedPaths(List<String> projections, ContentObject contentObject) {
for (String projectedPath : projections){
if (contentObject.getComplexCmsRootProperty().isChildPropertyDefined(projectedPath)) {
CmsPropertyDefinition propertyDefinition = contentObject.getTypeDefinition().getCmsPropertyDefinition(projectedPath);
if (propertyDefinition == null){
//Look for aspect
propertyDefinition = contentObject.getComplexCmsRootProperty().getAspectDefinitions().get(projectedPath);
}
if (propertyDefinition == null){
throw new CmsException("ComplexCmsRootProperty.isChildPRopertyDefined returned true for property "+projectedPath
+" but its definition could not " +
" be retrieved neither from type definition nor from aspect definitions for content object " +
"(SystemName / Id / ContentType) ("+
contentObject.getSystemName() + " / "+ contentObject.getId() + " / "+contentObject.getContentObjectType()+")");
}
contentObject.getCmsProperty(projectedPath);
if (ValueType.Complex == propertyDefinition.getValueType() &&
((ComplexCmsPropertyDefinition)propertyDefinition).hasChildCmsPropertyDefinitions()){
Collection<CmsPropertyDefinition> childPropertyDefinitions = ((ComplexCmsPropertyDefinition)propertyDefinition).getChildCmsPropertyDefinitions().values();
for (CmsPropertyDefinition childPropertyDefinition : childPropertyDefinitions){
if (MarshalUtils.propertyShouldBeMarshalled(projections, childPropertyDefinition.getName(), childPropertyDefinition.getPath())){
contentObject.getCmsProperty(childPropertyDefinition.getPath());
}
}
}
}
else{
logger.warn("Cannot pre load property {} for contentObject (SystemName / Id) ({}) as no such property " +
"is defined in content object's type '{}'. Also no aspect found with that name as well",
new Object[]{projectedPath, contentObject.getSystemName() + " / "+ contentObject.getId(), contentObject.getContentObjectType()});
}
}
}
public CmsOutcome searchContentObjects(ContentObjectCriteria contentObjectCriteria, Session session) throws Exception {
ContentObjectCriteria newContentObjectCriteria = copyContentObjectCriteria(session, contentObjectCriteria);
return createCmsOutcome(session, newContentObjectCriteria);
}
/* Not used due to Jackrabbit pproblems
* public CmsOutcome<CmsRankedOutcome<ContentObject>> findContentObjectWhichReferToTopicIdAndTaxonomy(String topicId, String taxonomyName,
ContentObjectCriteria contentObjectCriteria, Session session) throws Exception {
if (StringUtils.isBlank(topicId)){
return prototypes.newCmsOutcome(0);
}
//First use cmsRepositoryEntityAssociationDao to retrieve all topics
//Do not use taxonomyName for now
List<String> contentObjectIds = cmsRepositoryEntityAssociationDao.getContentObjectIdsWhichReferToTopicAndTaxonomy(topicId, null);
if (CollectionUtils.isEmpty(contentObjectIds)){
return prototypes.newCmsOutcome(0);
}
//Now use these ids to query repository to do the proper ordering
contentObjectCriteria.addCriterion(CriterionFactory.equals(CmsBuiltInItem.CmsIdentifier.getJcrName(), Condition.OR, contentObjectIds));
return searchContentObject(contentObjectCriteria, session);
}*/
}